Пример #1
0
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)