class SSLTests(TestCaseBase): """Tests of SSL communication""" def setUp(self): """Prepares to run the tests. The preparation consist on creating temporary files containing the keys and certificates and starting a thread that runs a simple SSL server. """ # Save the key to a file: with tempfile.NamedTemporaryFile(delete=False) as tmp: tmp.write(KEY) self.keyfile = tmp.name # Save the certificate to a file: with tempfile.NamedTemporaryFile(delete=False) as tmp: tmp.write(CERTIFICATE) self.certfile = tmp.name # Create the server socket: self.server = socket.socket() self.server = SSLServerSocket( raw=self.server, keyfile=self.keyfile, certfile=self.certfile, ca_certs=self.certfile) self.address = self.tryBind(ADDRESS) self.server.listen(5) # Start the server thread: self.thread = SSLServerThread(self.server) self.thread.start() def tryBind(self, address): ipadd, port = address while True: try: self.server.bind((ipadd, port)) return (ipadd, port) except socket.error as ex: if ex.errno == errno.EADDRINUSE: port += 1 if port > 65535: raise socket.error( errno.EADDRINUSE, "Can not find available port to bind") else: raise def tearDown(self): """Release the resources used by the tests. Removes the temporary files containing the keys and certifites, stops the server thread and closes the server socket. """ # Delete the temporary files: os.remove(self.keyfile) os.remove(self.certfile) # Stop the server thread and wait for it to finish: self.thread.shutdown() self.thread.join() del self.thread # Close the server socket: self.server.shutdown(socket.SHUT_RDWR) self.server.close() del self.server def runSClient(self, args=None, input=None): """This method runs the OpenSSL s_client command. The address parameter is a tuple containg the address of the host and the port number that will be used to build the -connect option of the command. The args parameter is the list of additional parameters to pass to the command. The input parameter is the data that will be piped to the standard input of the command. The method returns a tuple containing the exit code of the command and the data generated in the standard output. """ command = [ "openssl", "s_client", "-connect", "%s:%d" % self.address, ] if args: command += args print("command=%s" % command) process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = process.communicate(input) rc = process.wait() print("rc=%d" % rc) print("out=%s" % out) print("err=%s" % err) return rc, out def extractField(self, name, text): """ Extracts the value of one of the informative fields provided in the output of the s_client command. The name parameter is the name of the field, for example Session-ID for the SSL session identifier. The text parameter should be the output of the execution of the s_client command. Returns the value of the given field or None if that field can't be fond in the provided output of the s_client command. """ pattern = r"^\s*%s\s*:\s*(?P<value>[^\s]*)\s*$" % name expression = re.compile(pattern, flags=re.MULTILINE) match = expression.search(text) if not match: return None value = match.group("value") print("%s=%s" % (name, value)) return value def testConnectWithoutCertificateFails(self): """ Verify that the connection without a client certificate fails. """ rc, _ = self.runSClient() self.assertNotEquals(rc, 0) def testConnectWithCertificateSucceeds(self): """ Verify that the connection with a valid client certificate works correctly. """ rc, _ = self.runSClient([ "-cert", self.certfile, "-key", self.keyfile, ]) self.assertEquals(rc, 0) def testSessionIsCached(self): """ Verify that SSL the session identifier is preserved when connecting two times without stopping the server. """ # Create a temporary file to store the session details: sessionDetailsFile = tempfile.NamedTemporaryFile(delete=False) # Connect first time and save the session to a file: rc, out = self.runSClient([ "-cert", self.certfile, "-key", self.keyfile, "-sess_out", sessionDetailsFile.name, ]) self.assertEquals(rc, 0) # Get the session id from the output of the command: firstSessionId = self.extractField("Session-ID", out) self.assertTrue(firstSessionId is not None) # Connect second time using the saved session file: rc, out = self.runSClient([ "-cert", self.certfile, "-key", self.keyfile, "-sess_in", sessionDetailsFile.name, ]) self.assertEquals(rc, 0) # Get the session id again: secondSessionId = self.extractField("Session-ID", out) self.assertTrue(secondSessionId is not None) # Remove the temporary file used to store the session details, # as we don't need it any longer: os.remove(sessionDetailsFile.name) # Compare the session ids: self.assertEquals(secondSessionId, firstSessionId)