From 98d19dafd9c9d95338887b9e53c77ec6960918e0 Mon Sep 17 00:00:00 2001 From: Bill Janssen Date: Mon, 10 Sep 2007 21:51:02 +0000 Subject: [PATCH] More work on SSL support. * Much expanded test suite: All protocols tested against all other protocols. All protocols tested with all certificate options. Tests for bad key and bad cert. Test of STARTTLS functionality. Test of RAND_* functions. * Fixes for threading/malloc bug. * Issue 1065 fixed: sslsocket class renamed to SSLSocket. sslerror class renamed to SSLError. Function "wrap_socket" now used to wrap an existing socket. * Issue 1583946 finally fixed: Support for subjectAltName added. Subject name now returned as proper DN list of RDNs. * SSLError exported from socket as "sslerror". * RAND_* functions properly exported from ssl.py. * Documentation improved: Example of how to create a self-signed certificate. Better indexing. --- Doc/library/hashlib.rst | 2 +- Doc/library/ssl.rst | 392 ++++++++++------- Lib/httplib.py | 4 +- Lib/imaplib.py | 4 +- Lib/poplib.py | 2 +- Lib/smtplib.py | 4 +- Lib/socket.py | 4 +- Lib/ssl.py | 49 ++- Lib/test/badcert.pem | 36 ++ Lib/test/badkey.pem | 40 ++ Lib/test/nullcert.pem | 0 Lib/test/regrtest.py | 10 +- Lib/test/test_socket_ssl.py | 2 +- Lib/test/test_ssl.py | 745 +++++++++++++++++++++++--------- Modules/_ssl.c | 827 +++++++++++++++++++++++++++++------- 15 files changed, 1602 insertions(+), 519 deletions(-) create mode 100644 Lib/test/badcert.pem create mode 100644 Lib/test/badkey.pem create mode 100644 Lib/test/nullcert.pem diff --git a/Doc/library/hashlib.rst b/Doc/library/hashlib.rst index f2555545741..59d65a75be6 100644 --- a/Doc/library/hashlib.rst +++ b/Doc/library/hashlib.rst @@ -32,7 +32,7 @@ using the :meth:`update` method. At any point you can ask it for the :dfn:`digest` of the concatenation of the strings fed to it so far using the :meth:`digest` or :meth:`hexdigest` methods. -.. index:: single: OpenSSL +.. index:: single: OpenSSL; (use in module hashlib) Constructors for hash algorithms that are always present in this module are :func:`md5`, :func:`sha1`, :func:`sha224`, :func:`sha256`, :func:`sha384`, and diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index bd67644f77c..1074a33be16 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -12,6 +12,10 @@ .. sectionauthor:: Bill Janssen +.. index:: single: OpenSSL; (use in module ssl) + +.. index:: TLS, SSL, Transport Layer Security, Secure Sockets Layer + This module provides access to Transport Layer Security (often known as "Secure Sockets Layer") encryption and peer authentication facilities for network sockets, both client-side and server-side. @@ -22,19 +26,113 @@ platforms, as long as OpenSSL is installed on that platform. .. note:: Some behavior may be platform dependent, since calls are made to the operating - system socket APIs. + system socket APIs. The installed version of OpenSSL may also cause + variations in behavior. This section documents the objects and functions in the ``ssl`` module; for more general information about TLS, SSL, and certificates, the -reader is referred to the documents in the :ref:`ssl-references` section. +reader is referred to the documents in the "See Also" section at +the bottom. -This module defines a class, :class:`ssl.sslsocket`, which is +This module defines a class, :class:`ssl.SSLSocket`, which is derived from the :class:`socket.socket` type, and supports additional :meth:`read` and :meth:`write` methods, along with a method, :meth:`getpeercert`, to retrieve the certificate of the other side of the connection. This module defines the following functions, exceptions, and constants: +.. function:: wrap_socket (sock [, keyfile=None, certfile=None, server_side=False, + cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None]) + + Takes an instance ``sock`` of :class:`socket.socket`, and returns an instance of :class:`ssl.SSLSocket`, a subtype + of :class:`socket.socket`, which wraps the underlying socket in an SSL context. + For client-side sockets, the context construction is lazy; if the underlying socket isn't + connected yet, the context construction will be performed after :meth:`connect` is called + on the socket. For server-side sockets, if the socket has no remote peer, it is assumed + to be a listening socket, and the server-side SSL wrapping is automatically performed + on client connections accepted via the :meth:`accept` method. + + The ``keyfile`` and ``certfile`` parameters specify optional files which contain a certificate + to be used to identify the local side of the connection. See the discussion of :ref:`ssl-certificates` + for more information on how the certificate is stored in the ``certfile``. + + Often the private key is stored + in the same file as the certificate; in this case, only the ``certfile`` parameter need be + passed. If the private key is stored in a separate file, both parameters must be used. + If the private key is stored in the ``certfile``, it should come before the first certificate + in the certificate chain:: + + -----BEGIN RSA PRIVATE KEY----- + ... (private key in base64 encoding) ... + -----END RSA PRIVATE KEY----- + -----BEGIN CERTIFICATE----- + ... (certificate in base64 PEM encoding) ... + -----END CERTIFICATE----- + + The parameter ``server_side`` is a boolean which identifies whether server-side or client-side + behavior is desired from this socket. + + The parameter ``cert_reqs`` specifies whether a certificate is + required from the other side of the connection, and whether it will + be validated if provided. It must be one of the three values + :const:`CERT_NONE` (certificates ignored), :const:`CERT_OPTIONAL` (not required, + but validated if provided), or :const:`CERT_REQUIRED` (required and + validated). If the value of this parameter is not :const:`CERT_NONE`, then + the ``ca_certs`` parameter must point to a file of CA certificates. + + The ``ca_certs`` file contains a set of concatenated "certification authority" certificates, + which are used to validate certificates passed from the other end of the connection. + See the discussion of :ref:`ssl-certificates` for more information about how to arrange + the certificates in this file. + + The parameter ``ssl_version`` specifies which version of the SSL protocol to use. + Typically, the server chooses a particular protocol version, and the client + must adapt to the server's choice. Most of the versions are not interoperable + with the other versions. If not specified, for client-side operation, the + default SSL version is SSLv3; for server-side operation, SSLv23. These + version selections provide the most compatibility with other versions. + + Here's a table showing which versions in a client (down the side) + can connect to which versions in a server (along the top): + + .. table:: + + ======================== ========= ========= ========== ========= + *client* / **server** **SSLv2** **SSLv3** **SSLv23** **TLSv1** + *SSLv2* yes no yes* no + *SSLv3* yes yes yes no + *SSLv23* yes no yes no + *TLSv1* no no yes yes + ======================== ========= ========= ========== ========= + + `*` In some older versions of OpenSSL (for instance, 0.9.7l on OS X 10.4), + an SSLv2 client could not connect to an SSLv23 server. + +.. function:: RAND_status() + + Returns True if the SSL pseudo-random number generator has been + seeded with 'enough' randomness, and False otherwise. You can use + :func:`ssl.RAND_egd` and :func:`ssl.RAND_add` to increase the randomness + of the pseudo-random number generator. + +.. function:: RAND_egd(path) + + If you are running an entropy-gathering daemon (EGD) somewhere, and ``path`` + is the pathname of a socket connection open to it, this will read + 256 bytes of randomness from the socket, and add it to the SSL pseudo-random number generator + to increase the security of generated secret keys. This is typically only + necessary on systems without better sources of randomness. + + See http://egd.sourceforge.net/ or http://prngd.sourceforge.net/ for + sources of EGDs. + +.. function:: RAND_add(bytes, entropy) + + Mixes the given ``bytes`` into the SSL pseudo-random number generator. + The parameter ``entropy`` (a float) is a lower bound on the entropy + contained in string (so you can always use :const:`0.0`). + See :rfc:`1750` for more information on sources of entropy. + .. function:: cert_time_to_seconds(timestring) Returns a floating-point value containing a normal seconds-after-the-epoch time @@ -51,7 +149,7 @@ This module defines the following functions, exceptions, and constants: 'Wed May 9 00:00:00 2007' >>> -.. exception:: sslerror +.. exception:: SSLError Raised to signal an error from the underlying SSL implementation. This signifies some problem in the higher-level @@ -104,6 +202,60 @@ This module defines the following functions, exceptions, and constants: .. _ssl-certificates: +SSLSocket Objects +----------------- + +.. method:: SSLSocket.read([nbytes=1024]) + + Reads up to ``nbytes`` bytes from the SSL-encrypted channel and returns them. + +.. method:: SSLSocket.write(data) + + Writes the ``data`` to the other side of the connection, using the SSL channel to encrypt. Returns the number + of bytes written. + +.. method:: SSLSocket.getpeercert() + + If there is no certificate for the peer on the other end of the connection, returns ``None``. + If a certificate was received from the peer, but not validated, returns an empty ``dict`` instance. + If a certificate was received and validated, returns a ``dict`` instance with the fields + ``subject`` (the principal for which the certificate was issued), + and ``notAfter`` (the time after which the certificate should not be trusted) filled in. + The certificate was already validated, so the ``notBefore`` and ``issuer`` fields are not + returned. If a certificate contains an instance of the *subjectAltName* extension, + there will also be a ``subjectAltName`` field in the dictionary. + + The "subject" field is a tuple containing the sequence + of relative distinguished names (RDNs) given in the certificate's data structure + for the principal, and each RDN is a sequence of name-value pairs:: + + {'notAfter': 'Feb 16 16:54:50 2013 GMT', + 'subject': ((('countryName', u'US'),), + (('stateOrProvinceName', u'Delaware'),), + (('localityName', u'Wilmington'),), + (('organizationName', u'Python Software Foundation'),), + (('organizationalUnitName', u'SSL'),), + (('commonName', u'somemachine.python.org'),))} + + +.. method:: SSLSocket.cipher() + + Returns a three-value tuple containing the name of the cipher being + used, the version of the SSL protocol that defines its use, and the + number of secret bits being used. If no connection has been + established, returns ``None``. + +.. method:: SSLSocket.ssl_shutdown() + + Closes the SSL context (if any) over the socket, but leaves the socket connection + open for further use, if both sides are willing. This is different from :meth:`socket.socket.shutdown`, + which will close the connection, but leave the local socket available for further use. + + +.. index:: single: certificates + +.. index:: single: X509 certificate + Certificates ------------ @@ -130,8 +282,12 @@ can use a certificate to prove who they are. The other side of a network connection can also be required to produce a certificate, and that certificate can be validated to the satisfaction of the client or server that requires such validation. -The connection can be set to fail automatically if such -validation is not achieved. +The connection attempt can be set to raise an exception if +the validation fails. Validation is done +automatically, by the underlying OpenSSL framework; the +application need not concern itself with its mechanics. +But the application does usually need to provide +sets of certificates to allow this process to take place. Python uses files to contain certificates. They should be formatted as "PEM" (see :rfc:`1422`), which is a base-64 encoded form wrapped @@ -170,108 +326,54 @@ certificate, you need to provide a "CA certs" file, filled with the certificate chains for each issuer you are willing to trust. Again, this file just contains these chains concatenated together. For validation, Python will use the first chain it finds in the file which matches. -Some "standard" root certificates are available at -http://www.thawte.com/roots/ (for Thawte roots) and -http://www.verisign.com/support/roots.html (for Verisign roots). -See also :rfc:`4158` for more discussion of the way in which +Some "standard" root certificates are available from various certification +authorities: +`CACert.org `_, +`Thawte `_, +`Verisign `_, +`Equifax and GeoTrust `_. + +In general, if you are using +SSL3 or TLS1, you don't need to put the full chain in your "CA certs" file; +you only need the root certificates, and the remote peer is supposed to +furnish the other certificates necessary to chain from its certificate to +a root certificate. +See :rfc:`4158` for more discussion of the way in which certification chains can be built. +If you are going to create a server that provides SSL-encrypted +connection services, you will need to acquire a certificate for that +service. There are many ways of acquiring appropriate certificates, +such as buying one from a certification authority. Another common +practice is to generate a self-signed certificate. The simplest +way to do this is with the OpenSSL package, using something like +the following:: -sslsocket Objects ------------------ + % openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout cert.pem + Generating a 1024 bit RSA private key + .......++++++ + .............................++++++ + writing new private key to 'cert.pem' + ----- + You are about to be asked to enter information that will be incorporated + into your certificate request. + What you are about to enter is what is called a Distinguished Name or a DN. + There are quite a few fields but you can leave some blank + For some fields there will be a default value, + If you enter '.', the field will be left blank. + ----- + Country Name (2 letter code) [AU]:US + State or Province Name (full name) [Some-State]:MyState + Locality Name (eg, city) []:Some City + Organization Name (eg, company) [Internet Widgits Pty Ltd]:My Organization, Inc. + Organizational Unit Name (eg, section) []:My Group + Common Name (eg, YOUR name) []:myserver.mygroup.myorganization.com + Email Address []:ops@myserver.mygroup.myorganization.com + % -.. class:: sslsocket(sock [, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version=PROTOCOL_SSLv23, ca_certs=None]) - - Takes an instance ``sock`` of :class:`socket.socket`, and returns an instance of a subtype - of :class:`socket.socket` which wraps the underlying socket in an SSL context. - For client-side sockets, the context construction is lazy; if the underlying socket isn't - connected yet, the context construction will be performed after :meth:`connect` is called - on the socket. - - The ``keyfile`` and ``certfile`` parameters specify optional files which contain a certificate - to be used to identify the local side of the connection. See the above discussion of :ref:`ssl-certificates` - for more information on how the certificate is stored in the ``certfile``. - - Often the private key is stored - in the same file as the certificate; in this case, only the ``certfile`` parameter need be - passed. If the private key is stored in a separate file, both parameters must be used. - If the private key is stored in the ``certfile``, it should come before the first certificate - in the certificate chain:: - - -----BEGIN RSA PRIVATE KEY----- - ... (private key in base64 encoding) ... - -----END RSA PRIVATE KEY----- - -----BEGIN CERTIFICATE----- - ... (certificate in base64 PEM encoding) ... - -----END CERTIFICATE----- - - The parameter ``server_side`` is a boolean which identifies whether server-side or client-side - behavior is desired from this socket. - - The parameter ``cert_reqs`` specifies whether a certificate is - required from the other side of the connection, and whether it will - be validated if provided. It must be one of the three values - :const:`CERT_NONE` (certificates ignored), :const:`CERT_OPTIONAL` (not required, - but validated if provided), or :const:`CERT_REQUIRED` (required and - validated). If the value of this parameter is not :const:`CERT_NONE`, then - the ``ca_certs`` parameter must point to a file of CA certificates. - - The parameter ``ssl_version`` specifies which version of the SSL protocol to use. Typically, - the server specifies this, and a client connecting to it must use the same protocol. An - SSL server using :const:`PROTOCOL_SSLv23` can understand a client connecting via SSL2, SSL3, or TLS1, - but a client using :const:`PROTOCOL_SSLv23` can only connect to an SSL2 server. - - The ``ca_certs`` file contains a set of concatenated "certification authority" certificates, - which are used to validate certificates passed from the other end of the connection. - See the above discussion of :ref:`ssl-certificates` for more information about how to arrange - the certificates in this file. - -.. method:: sslsocket.read([nbytes]) - - Reads up to ``nbytes`` bytes from the SSL-encrypted channel and returns them. - -.. method:: sslsocket.write(data) - - Writes the ``data`` to the other side of the connection, using the SSL channel to encrypt. Returns the number - of bytes written. - -.. method:: sslsocket.getpeercert() - - If there is no certificate for the peer on the other end of the connection, returns ``None``. - If a certificate was received from the peer, but not validated, returns an empty ``dict`` instance. - If a certificate was received and validated, returns a ``dict`` instance with the fields - ``subject`` (the principal for which the certificate was issued), ``issuer`` (the signer of - the certificate), ``notBefore`` (the time before which the certificate should not be trusted), - and ``notAfter`` (the time after which the certificate should not be trusted) filled in. - - The "subject" and "issuer" fields are tuples containing the name-value fields - given in the certificate's data structure for each principal:: - - {'issuer': (('countryName', u'US'), - ('stateOrProvinceName', u'Delaware'), - ('localityName', u'Wilmington'), - ('organizationName', u'Python Software Foundation'), - ('organizationalUnitName', u'SSL'), - ('commonName', u'somemachine.python.org')), - 'notAfter': 'Feb 16 16:54:50 2013 GMT', - 'notBefore': 'Aug 27 16:54:50 2007 GMT', - 'subject': (('countryName', u'US'), - ('stateOrProvinceName', u'Delaware'), - ('localityName', u'Wilmington'), - ('organizationName', u'Python Software Foundation'), - ('organizationalUnitName', u'SSL'), - ('commonName', u'somemachine.python.org')), - 'version': 2} - - This certificate is said to be *self-signed*, because the subject - and issuer are the same entity. The *version* field refers to the X509 version - that's used for the certificate. - -.. method:: sslsocket.ssl_shutdown() - - Closes the SSL context (if any) over the socket, but leaves the socket connection - open for further use, if both sides are willing. This is different from :meth:`socket.socket.shutdown`, - which will close the connection, but leave the local socket available for further use. +The disadvantage of a self-signed certificate is that it is its +own root certificate, and no one else will have it in their cache +of known (and trusted) root certificates. Examples @@ -298,11 +400,16 @@ sends some bytes, and reads part of the response:: import socket, ssl, pprint s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - ssl_sock = ssl.sslsocket(s, ca_certs="/etc/ca_certs_file", cert_reqs=ssl.CERT_REQUIRED) + + # require a certificate from the server + ssl_sock = ssl.wrap_socket(s, + ca_certs="/etc/ca_certs_file", + cert_reqs=ssl.CERT_REQUIRED) ssl_sock.connect(('www.verisign.com', 443)) print repr(ssl_sock.getpeername()) + print ssl_sock.cipher() print pprint.pformat(ssl_sock.getpeercert()) # Set a simple HTTP request -- use httplib in actual code. @@ -313,35 +420,29 @@ sends some bytes, and reads part of the response:: # read all the data returned by the server. data = ssl_sock.read() - # note that closing the sslsocket will also close the underlying socket + # note that closing the SSLSocket will also close the underlying socket ssl_sock.close() -As of September 4, 2007, the certificate printed by this program +As of September 6, 2007, the certificate printed by this program looked like this:: - {'issuer': (('countryName', u'US'), - ('organizationName', u'VeriSign, Inc.'), - ('organizationalUnitName', u'VeriSign Trust Network'), - ('organizationalUnitName', - u'Terms of use at https://www.verisign.com/rpa (c)06'), - ('commonName', - u'VeriSign Class 3 Extended Validation SSL SGC CA')), - 'notAfter': 'May 8 23:59:59 2009 GMT', - 'notBefore': 'May 9 00:00:00 2007 GMT', - 'subject': (('serialNumber', u'2497886'), - ('1.3.6.1.4.1.311.60.2.1.3', u'US'), - ('1.3.6.1.4.1.311.60.2.1.2', u'Delaware'), - ('countryName', u'US'), - ('postalCode', u'94043'), - ('stateOrProvinceName', u'California'), - ('localityName', u'Mountain View'), - ('streetAddress', u'487 East Middlefield Road'), - ('organizationName', u'VeriSign, Inc.'), - ('organizationalUnitName', u'Production Security Services'), - ('organizationalUnitName', - u'Terms of use at www.verisign.com/rpa (c)06'), - ('commonName', u'www.verisign.com')), - 'version': 2} + {'notAfter': 'May 8 23:59:59 2009 GMT', + 'subject': ((('serialNumber', u'2497886'),), + (('1.3.6.1.4.1.311.60.2.1.3', u'US'),), + (('1.3.6.1.4.1.311.60.2.1.2', u'Delaware'),), + (('countryName', u'US'),), + (('postalCode', u'94043'),), + (('stateOrProvinceName', u'California'),), + (('localityName', u'Mountain View'),), + (('streetAddress', u'487 East Middlefield Road'),), + (('organizationName', u'VeriSign, Inc.'),), + (('organizationalUnitName', + u'Production Security Services'),), + (('organizationalUnitName', + u'Terms of use at www.verisign.com/rpa (c)06'),), + (('commonName', u'www.verisign.com'),))} + +which is a fairly poorly-formed ``subject`` field. Server-side operation ^^^^^^^^^^^^^^^^^^^^^ @@ -357,12 +458,15 @@ to connect:: bindsocket.listen(5) When one did, you'd call :meth:`accept` on the socket to get the new socket from the other -end, and use :func:`sslsocket` to create a server-side SSL context for it:: +end, and use :func:`wrap_socket` to create a server-side SSL context for it:: while True: newsocket, fromaddr = bindsocket.accept() - connstream = ssl.sslsocket(newsocket, server_side=True, certfile="mycertfile", - keyfile="mykeyfile", ssl_protocol=ssl.PROTOCOL_TLSv1) + connstream = ssl.wrap_socket(newsocket, + server_side=True, + certfile="mycertfile", + keyfile="mykeyfile", + ssl_protocol=ssl.PROTOCOL_TLSv1) deal_with_client(connstream) Then you'd read data from the ``connstream`` and do something with it till you are finished with the client (or the client is finished with you):: @@ -373,7 +477,8 @@ Then you'd read data from the ``connstream`` and do something with it till you a # null data means the client is finished with us while data: if not do_something(connstream, data): - # we'll assume do_something returns False when we're finished with client + # we'll assume do_something returns False + # when we're finished with client break data = connstream.read() # finished with client @@ -382,16 +487,19 @@ Then you'd read data from the ``connstream`` and do something with it till you a And go back to listening for new client connections. -.. _ssl-references: +.. seealso:: -References ----------- + Class :class:`socket.socket` + Documentation of underlying :mod:`socket` class -Class :class:`socket.socket` - Documentation of underlying :mod:`socket` class + `Introducing SSL and Certificates using OpenSSL `_ + Frederick J. Hirsch -`Introducing SSL and Certificates using OpenSSL `_, by Frederick J. Hirsch + `RFC 1422: Privacy Enhancement for Internet Electronic Mail: Part II: Certificate-Based Key Management `_ + Steve Kent -`Privacy Enhancement for Internet Electronic Mail: Part II: Certificate-Based Key Management`, :rfc:`1422`, by Steve Kent + `RFC 1750: Randomness Recommendations for Security `_ + D. Eastlake et. al. -`Internet X.509 Public Key Infrastructure Certificate and CRL Profile`, :rfc:`3280`, Housley et. al. + `RFC 3280: Internet X.509 Public Key Infrastructure Certificate and CRL Profile `_ + Housley et. al. diff --git a/Lib/httplib.py b/Lib/httplib.py index b9260822578..8dbe8a07e41 100644 --- a/Lib/httplib.py +++ b/Lib/httplib.py @@ -1051,7 +1051,7 @@ else: "Connect to a host on a given (SSL) port." sock = socket.create_connection((self.host, self.port), self.timeout) - self.sock = ssl.sslsocket(sock, self.key_file, self.cert_file) + self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file) __all__.append("HTTPSConnection") @@ -1083,7 +1083,7 @@ else: def FakeSocket (sock, sslobj): warnings.warn("FakeSocket is deprecated, and won't be in 3.x. " + - "Use the result of ssl.sslsocket directly instead.", + "Use the result of ssl.wrap_socket() directly instead.", DeprecationWarning, stacklevel=2) return sslobj diff --git a/Lib/imaplib.py b/Lib/imaplib.py index 5a7adf4c800..c05abb470a6 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -1147,7 +1147,7 @@ else: self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((host, port)) - self.sslobj = ssl.sslsocket(self.sock, self.keyfile, self.certfile) + self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile) def read(self, size): @@ -1199,7 +1199,7 @@ else: def ssl(self): """Return SSLObject instance used to communicate with the IMAP4 server. - ssl = ssl.sslsocket(.socket) + ssl = ssl.wrap_socket(.socket) """ return self.sslobj diff --git a/Lib/poplib.py b/Lib/poplib.py index c4215298368..149675a4aee 100644 --- a/Lib/poplib.py +++ b/Lib/poplib.py @@ -348,7 +348,7 @@ else: if not self.sock: raise socket.error, msg self.file = self.sock.makefile('rb') - self.sslobj = ssl.sslsocket(self.sock, self.keyfile, self.certfile) + self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile) self._debugging = 0 self.welcome = self._getresp() diff --git a/Lib/smtplib.py b/Lib/smtplib.py index 529984833be..5604241dee8 100755 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -587,7 +587,7 @@ class SMTP: if resp == 220: if not _have_ssl: raise RuntimeError("No SSL support included in this Python") - self.sock = ssl.sslsocket(self.sock, keyfile, certfile) + self.sock = ssl.wrap_socket(self.sock, keyfile, certfile) self.file = SSLFakeFile(self.sock) return (resp, reply) @@ -720,7 +720,7 @@ if _have_ssl: def _get_socket(self, host, port, timeout): if self.debuglevel > 0: print>>stderr, 'connect:', (host, port) self.sock = socket.create_connection((host, port), timeout) - self.sock = ssl.sslsocket(self.sock, self.keyfile, self.certfile) + self.sock = ssl.wrap_socket(self.sock, self.keyfile, self.certfile) self.file = SSLFakeFile(self.sock) __all__.append("SMTP_SSL") diff --git a/Lib/socket.py b/Lib/socket.py index 313151c5055..30be5c5324c 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -56,13 +56,13 @@ else: # we do an internal import here because the ssl # module imports the socket module import ssl as _realssl - warnings.warn("socket.ssl() is deprecated. Use ssl.sslsocket() instead.", + warnings.warn("socket.ssl() is deprecated. Use ssl.wrap_socket() instead.", DeprecationWarning, stacklevel=2) return _realssl.sslwrap_simple(sock, keyfile, certfile) # we need to import the same constants we used to... + from _ssl import SSLError as sslerror from _ssl import \ - sslerror, \ RAND_add, \ RAND_egd, \ RAND_status, \ diff --git a/Lib/ssl.py b/Lib/ssl.py index 5ad94476005..b987ec339de 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -6,11 +6,11 @@ This module provides some more Pythonic support for SSL. Object types: - sslsocket -- subtype of socket.socket which does SSL over the socket + SSLSocket -- subtype of socket.socket which does SSL over the socket Exceptions: - sslerror -- exception raised for I/O errors + SSLError -- exception raised for I/O errors Functions: @@ -58,9 +58,11 @@ PROTOCOL_TLSv1 import os, sys import _ssl # if we can't import it, let the error propagate -from _ssl import sslerror + +from _ssl import SSLError from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED from _ssl import PROTOCOL_SSLv2, PROTOCOL_SSLv3, PROTOCOL_SSLv23, PROTOCOL_TLSv1 +from _ssl import RAND_status, RAND_egd, RAND_add from _ssl import \ SSL_ERROR_ZERO_RETURN, \ SSL_ERROR_WANT_READ, \ @@ -75,8 +77,20 @@ from _ssl import \ from socket import socket from socket import getnameinfo as _getnameinfo +def get_protocol_name (protocol_code): + if protocol_code == PROTOCOL_TLSv1: + return "TLSv1" + elif protocol_code == PROTOCOL_SSLv23: + return "SSLv23" + elif protocol_code == PROTOCOL_SSLv2: + return "SSLv2" + elif protocol_code == PROTOCOL_SSLv3: + return "SSLv3" + else: + return "" -class sslsocket (socket): + +class SSLSocket (socket): """This class implements a subtype of socket.socket that wraps the underlying OS socket in an SSL context when necessary, and @@ -119,14 +133,21 @@ class sslsocket (socket): return self._sslobj.write(data) - def getpeercert(self): + def getpeercert(self, binary_form=False): """Returns a formatted version of the data in the certificate provided by the other end of the SSL channel. Return None if no certificate was provided, {} if a certificate was provided, but not validated.""" - return self._sslobj.peer_certificate() + return self._sslobj.peer_certificate(binary_form) + + def cipher (self): + + if not self._sslobj: + return None + else: + return self._sslobj.cipher() def send (self, data, flags=0): if self._sslobj: @@ -197,7 +218,7 @@ class sslsocket (socket): # Here we assume that the socket is client-side, and not # connected at the time of the call. We connect it, then wrap it. if self._sslobj: - raise ValueError("attempt to connect already-connected sslsocket!") + raise ValueError("attempt to connect already-connected SSLSocket!") socket.connect(self, addr) self._sslobj = _ssl.sslwrap(self._sock, False, self.keyfile, self.certfile, self.cert_reqs, self.ssl_version, @@ -210,11 +231,19 @@ class sslsocket (socket): SSL channel, and the address of the remote client.""" newsock, addr = socket.accept(self) - return (sslsocket(newsock, True, self.keyfile, self.certfile, - self.cert_reqs, self.ssl_version, - self.ca_certs), addr) + return (SSLSocket(newsock, True, self.keyfile, self.certfile, + self.cert_reqs, self.ssl_version, + self.ca_certs), addr) +def wrap_socket(sock, keyfile=None, certfile=None, + server_side=False, cert_reqs=CERT_NONE, + ssl_version=PROTOCOL_SSLv23, ca_certs=None): + + return SSLSocket(sock, keyfile=keyfile, certfile=certfile, + server_side=server_side, cert_reqs=cert_reqs, + ssl_version=ssl_version, ca_certs=ca_certs) + # some utility functions def cert_time_to_seconds(cert_time): diff --git a/Lib/test/badcert.pem b/Lib/test/badcert.pem new file mode 100644 index 00000000000..c4191460f9e --- /dev/null +++ b/Lib/test/badcert.pem @@ -0,0 +1,36 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXwIBAAKBgQC8ddrhm+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9L +opdJhTvbGfEj0DQs1IE8M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVH +fhi/VwovESJlaBOp+WMnfhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQAB +AoGBAK0FZpaKj6WnJZN0RqhhK+ggtBWwBnc0U/ozgKz2j1s3fsShYeiGtW6CK5nU +D1dZ5wzhbGThI7LiOXDvRucc9n7vUgi0alqPQ/PFodPxAN/eEYkmXQ7W2k7zwsDA +IUK0KUhktQbLu8qF/m8qM86ba9y9/9YkXuQbZ3COl5ahTZrhAkEA301P08RKv3KM +oXnGU2UHTuJ1MAD2hOrPxjD4/wxA/39EWG9bZczbJyggB4RHu0I3NOSFjAm3HQm0 +ANOu5QK9owJBANgOeLfNNcF4pp+UikRFqxk5hULqRAWzVxVrWe85FlPm0VVmHbb/ +loif7mqjU8o1jTd/LM7RD9f2usZyE2psaw8CQQCNLhkpX3KO5kKJmS9N7JMZSc4j +oog58yeYO8BBqKKzpug0LXuQultYv2K4veaIO04iL9VLe5z9S/Q1jaCHBBuXAkEA +z8gjGoi1AOp6PBBLZNsncCvcV/0aC+1se4HxTNo2+duKSDnbq+ljqOM+E7odU+Nq +ewvIWOG//e8fssd0mq3HywJBAJ8l/c8GVmrpFTx8r/nZ2Pyyjt3dH1widooDXYSV +q6Gbf41Llo5sYAtmxdndTLASuHKecacTgZVhy0FryZpLKrU= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +Just bad cert data +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +MIICXwIBAAKBgQC8ddrhm+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9L +opdJhTvbGfEj0DQs1IE8M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVH +fhi/VwovESJlaBOp+WMnfhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQAB +AoGBAK0FZpaKj6WnJZN0RqhhK+ggtBWwBnc0U/ozgKz2j1s3fsShYeiGtW6CK5nU +D1dZ5wzhbGThI7LiOXDvRucc9n7vUgi0alqPQ/PFodPxAN/eEYkmXQ7W2k7zwsDA +IUK0KUhktQbLu8qF/m8qM86ba9y9/9YkXuQbZ3COl5ahTZrhAkEA301P08RKv3KM +oXnGU2UHTuJ1MAD2hOrPxjD4/wxA/39EWG9bZczbJyggB4RHu0I3NOSFjAm3HQm0 +ANOu5QK9owJBANgOeLfNNcF4pp+UikRFqxk5hULqRAWzVxVrWe85FlPm0VVmHbb/ +loif7mqjU8o1jTd/LM7RD9f2usZyE2psaw8CQQCNLhkpX3KO5kKJmS9N7JMZSc4j +oog58yeYO8BBqKKzpug0LXuQultYv2K4veaIO04iL9VLe5z9S/Q1jaCHBBuXAkEA +z8gjGoi1AOp6PBBLZNsncCvcV/0aC+1se4HxTNo2+duKSDnbq+ljqOM+E7odU+Nq +ewvIWOG//e8fssd0mq3HywJBAJ8l/c8GVmrpFTx8r/nZ2Pyyjt3dH1widooDXYSV +q6Gbf41Llo5sYAtmxdndTLASuHKecacTgZVhy0FryZpLKrU= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +Just bad cert data +-----END CERTIFICATE----- diff --git a/Lib/test/badkey.pem b/Lib/test/badkey.pem new file mode 100644 index 00000000000..1c8a9557193 --- /dev/null +++ b/Lib/test/badkey.pem @@ -0,0 +1,40 @@ +-----BEGIN RSA PRIVATE KEY----- +Bad Key, though the cert should be OK +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICpzCCAhCgAwIBAgIJAP+qStv1cIGNMA0GCSqGSIb3DQEBBQUAMIGJMQswCQYD +VQQGEwJVUzERMA8GA1UECBMIRGVsYXdhcmUxEzARBgNVBAcTCldpbG1pbmd0b24x +IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMQwwCgYDVQQLEwNT +U0wxHzAdBgNVBAMTFnNvbWVtYWNoaW5lLnB5dGhvbi5vcmcwHhcNMDcwODI3MTY1 +NDUwWhcNMTMwMjE2MTY1NDUwWjCBiTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCERl +bGF3YXJlMRMwEQYDVQQHEwpXaWxtaW5ndG9uMSMwIQYDVQQKExpQeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbjEMMAoGA1UECxMDU1NMMR8wHQYDVQQDExZzb21lbWFj +aGluZS5weXRob24ub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8ddrh +m+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9LopdJhTvbGfEj0DQs1IE8 +M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVHfhi/VwovESJlaBOp+WMn +fhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQABoxUwEzARBglghkgBhvhC +AQEEBAMCBkAwDQYJKoZIhvcNAQEFBQADgYEAF4Q5BVqmCOLv1n8je/Jw9K669VXb +08hyGzQhkemEBYQd6fzQ9A/1ZzHkJKb1P6yreOLSEh4KcxYPyrLRC1ll8nr5OlCx +CMhKkTnR6qBsdNV0XtdU2+N25hqW+Ma4ZeqsN/iiJVCGNOZGnvQuvCAGWF8+J/f/ +iHkC6gGdBJhogs4= +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +Bad Key, though the cert should be OK +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICpzCCAhCgAwIBAgIJAP+qStv1cIGNMA0GCSqGSIb3DQEBBQUAMIGJMQswCQYD +VQQGEwJVUzERMA8GA1UECBMIRGVsYXdhcmUxEzARBgNVBAcTCldpbG1pbmd0b24x +IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMQwwCgYDVQQLEwNT +U0wxHzAdBgNVBAMTFnNvbWVtYWNoaW5lLnB5dGhvbi5vcmcwHhcNMDcwODI3MTY1 +NDUwWhcNMTMwMjE2MTY1NDUwWjCBiTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCERl +bGF3YXJlMRMwEQYDVQQHEwpXaWxtaW5ndG9uMSMwIQYDVQQKExpQeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbjEMMAoGA1UECxMDU1NMMR8wHQYDVQQDExZzb21lbWFj +aGluZS5weXRob24ub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8ddrh +m+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9LopdJhTvbGfEj0DQs1IE8 +M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVHfhi/VwovESJlaBOp+WMn +fhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQABoxUwEzARBglghkgBhvhC +AQEEBAMCBkAwDQYJKoZIhvcNAQEFBQADgYEAF4Q5BVqmCOLv1n8je/Jw9K669VXb +08hyGzQhkemEBYQd6fzQ9A/1ZzHkJKb1P6yreOLSEh4KcxYPyrLRC1ll8nr5OlCx +CMhKkTnR6qBsdNV0XtdU2+N25hqW+Ma4ZeqsN/iiJVCGNOZGnvQuvCAGWF8+J/f/ +iHkC6gGdBJhogs4= +-----END CERTIFICATE----- diff --git a/Lib/test/nullcert.pem b/Lib/test/nullcert.pem new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py index 99803ff3c22..7db63cb60a2 100755 --- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -1108,7 +1108,6 @@ _expectations['freebsd7'] = _expectations['freebsd4'] class _ExpectedSkips: def __init__(self): import os.path - from test import test_socket_ssl from test import test_timeout self.valid = False @@ -1122,8 +1121,13 @@ class _ExpectedSkips: if not os.path.supports_unicode_filenames: self.expected.add('test_pep277') - if test_socket_ssl.skip_expected: - self.expected.add('test_socket_ssl') + try: + from test import test_socket_ssl + except ImportError: + pass + else: + if test_socket_ssl.skip_expected: + self.expected.add('test_socket_ssl') if test_timeout.skip_expected: self.expected.add('test_timeout') diff --git a/Lib/test/test_socket_ssl.py b/Lib/test/test_socket_ssl.py index f7c1aa13968..d6a54bbe219 100644 --- a/Lib/test/test_socket_ssl.py +++ b/Lib/test/test_socket_ssl.py @@ -115,7 +115,7 @@ class BasicTests(unittest.TestCase): s = socket.socket(socket.AF_INET) s.connect(("www.sf.net", 443)) fd = s._sock.fileno() - sock = ssl.sslsocket(s) + sock = ssl.wrap_socket(s) s = None sock.close() try: diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 04daab2f361..7eedbdc8ac4 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -5,7 +5,6 @@ import unittest from test import test_support import socket import errno -import threading import subprocess import time import os @@ -23,57 +22,21 @@ except ImportError: CERTFILE = None +TESTPORT = 10025 def handle_error(prefix): exc_format = ' '.join(traceback.format_exception(*sys.exc_info())) - sys.stdout.write(prefix + exc_format) + if test_support.verbose: + sys.stdout.write(prefix + exc_format) class BasicTests(unittest.TestCase): - def testRudeShutdown(self): - # Some random port to connect to. - PORT = [9934] - - listener_ready = threading.Event() - listener_gone = threading.Event() - - # `listener` runs in a thread. It opens a socket listening on - # PORT, and sits in an accept() until the main thread connects. - # Then it rudely closes the socket, and sets Event `listener_gone` - # to let the main thread know the socket is gone. - def listener(): - s = socket.socket() - PORT[0] = test_support.bind_port(s, '', PORT[0]) - s.listen(5) - listener_ready.set() - s.accept() - s = None # reclaim the socket object, which also closes it - listener_gone.set() - - def connector(): - listener_ready.wait() - s = socket.socket() - s.connect(('localhost', PORT[0])) - listener_gone.wait() - try: - ssl_sock = socket.ssl(s) - except socket.sslerror: - pass - else: - raise test_support.TestFailed( - 'connecting to closed SSL socket should have failed') - - t = threading.Thread(target=listener) - t.start() - connector() - t.join() - def testSSLconnect(self): import os with test_support.transient_internet(): - s = ssl.sslsocket(socket.socket(socket.AF_INET), - cert_reqs=ssl.CERT_NONE) + s = ssl.wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_NONE) s.connect(("pop.gmail.com", 995)) c = s.getpeercert() if c: @@ -81,177 +44,551 @@ class BasicTests(unittest.TestCase): s.close() # this should fail because we have no verification certs - s = ssl.sslsocket(socket.socket(socket.AF_INET), - cert_reqs=ssl.CERT_REQUIRED) + s = ssl.wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED) try: s.connect(("pop.gmail.com", 995)) - except ssl.sslerror: + except ssl.SSLError: pass finally: s.close() -class ConnectedTests(unittest.TestCase): + def testCrucialConstants(self): + ssl.PROTOCOL_SSLv2 + ssl.PROTOCOL_SSLv23 + ssl.PROTOCOL_SSLv3 + ssl.PROTOCOL_TLSv1 + ssl.CERT_NONE + ssl.CERT_OPTIONAL + ssl.CERT_REQUIRED - def testTLSecho (self): - - s1 = socket.socket() + def testRAND(self): + v = ssl.RAND_status() + if test_support.verbose: + sys.stdout.write("\n RAND_status is %d (%s)\n" + % (v, (v and "sufficient randomness") or + "insufficient randomness")) try: - s1.connect(('127.0.0.1', 10024)) - except: - handle_error("connection failure:\n") - raise test_support.TestFailed("Can't connect to test server") + ssl.RAND_egd(1) + except TypeError: + pass else: - try: - c1 = ssl.sslsocket(s1, ssl_version=ssl.PROTOCOL_TLSv1) - except: - handle_error("SSL handshake failure:\n") - raise test_support.TestFailed("Can't SSL-handshake with test server") - else: - if not c1: - raise test_support.TestFailed("Can't SSL-handshake with test server") - indata = "FOO\n" - c1.write(indata) - outdata = c1.read() - if outdata != indata.lower(): - raise test_support.TestFailed("bad data <<%s>> received; expected <<%s>>\n" % (data, indata.lower())) - c1.close() + print "didn't raise TypeError" + ssl.RAND_add("this is a random string", 75.0) - def testReadCert(self): + def testParseCert(self): + # note that this uses an 'unofficial' function in _ssl.c, + # provided solely for this test, to exercise the certificate + # parsing code + p = ssl._ssl._test_decode_cert(CERTFILE, False) + if test_support.verbose: + sys.stdout.write("\n" + pprint.pformat(p) + "\n") - s2 = socket.socket() - try: - s2.connect(('127.0.0.1', 10024)) - except: - handle_error("connection failure:\n") - raise test_support.TestFailed("Can't connect to test server") - else: - try: - c2 = ssl.sslsocket(s2, ssl_version=ssl.PROTOCOL_TLSv1, - cert_reqs=ssl.CERT_REQUIRED, ca_certs=CERTFILE) - except: - handle_error("SSL handshake failure:\n") - raise test_support.TestFailed("Can't SSL-handshake with test server") - else: - if not c2: - raise test_support.TestFailed("Can't SSL-handshake with test server") - cert = c2.getpeercert() - if not cert: - raise test_support.TestFailed("Can't get peer certificate.") - if test_support.verbose: - sys.stdout.write(pprint.pformat(cert) + '\n') - if not cert.has_key('subject'): - raise test_support.TestFailed( - "No subject field in certificate: %s." % - pprint.pformat(cert)) - if not ('organizationName', 'Python Software Foundation') in cert['subject']: - raise test_support.TestFailed( - "Missing or invalid 'organizationName' field in certificate subject; " - "should be 'Python Software Foundation'."); - c2.close() +try: + import threading +except ImportError: + _have_threads = False +else: + _have_threads = True -class ThreadedEchoServer(threading.Thread): + class ThreadedEchoServer(threading.Thread): - class ConnectionHandler(threading.Thread): + class ConnectionHandler(threading.Thread): - def __init__(self, server, connsock): - self.server = server - self.running = False - self.sock = connsock + """A mildly complicated class, because we want it to work both + with and without the SSL wrapper around the socket connection, so + that we can test the STARTTLS functionality.""" + + def __init__(self, server, connsock): + self.server = server + self.running = False + self.sock = connsock + self.sock.setblocking(1) + self.sslconn = None + threading.Thread.__init__(self) + self.setDaemon(True) + + def wrap_conn (self): + try: + self.sslconn = ssl.wrap_socket(self.sock, server_side=True, + certfile=self.server.certificate, + ssl_version=self.server.protocol, + ca_certs=self.server.cacerts, + cert_reqs=self.server.certreqs) + except: + if self.server.chatty: + handle_error("\n server: bad connection attempt from " + + str(self.sock.getpeername()) + ":\n") + if not self.server.expect_bad_connects: + # here, we want to stop the server, because this shouldn't + # happen in the context of our test case + self.running = False + # normally, we'd just stop here, but for the test + # harness, we want to stop the server + self.server.stop() + return False + + else: + if self.server.certreqs == ssl.CERT_REQUIRED: + cert = self.sslconn.getpeercert() + if test_support.verbose and self.server.chatty: + sys.stdout.write(" client cert is " + pprint.pformat(cert) + "\n") + cert_binary = self.sslconn.getpeercert(True) + if test_support.verbose and self.server.chatty: + sys.stdout.write(" cert binary is " + str(len(cert_binary)) + " bytes\n") + cipher = self.sslconn.cipher() + if test_support.verbose and self.server.chatty: + sys.stdout.write(" server: connection cipher is now " + str(cipher) + "\n") + return True + + def read(self): + if self.sslconn: + return self.sslconn.read() + else: + return self.sock.recv(1024) + + def write(self, bytes): + if self.sslconn: + return self.sslconn.write(bytes) + else: + return self.sock.send(bytes) + + def close(self): + if self.sslconn: + self.sslconn.close() + else: + self.sock.close() + + def run (self): + self.running = True + if not self.server.starttls_server: + if not self.wrap_conn(): + return + while self.running: + try: + msg = self.read() + if not msg: + # eof, so quit this handler + self.running = False + self.close() + elif msg.strip() == 'over': + if test_support.verbose and self.server.connectionchatty: + sys.stdout.write(" server: client closed connection\n") + self.close() + return + elif self.server.starttls_server and msg.strip() == 'STARTTLS': + if test_support.verbose and self.server.connectionchatty: + sys.stdout.write(" server: read STARTTLS from client, sending OK...\n") + self.write("OK\n") + if not self.wrap_conn(): + return + else: + if (test_support.verbose and + self.server.connectionchatty): + ctype = (self.sslconn and "encrypted") or "unencrypted" + sys.stdout.write(" server: read %s (%s), sending back %s (%s)...\n" + % (repr(msg), ctype, repr(msg.lower()), ctype)) + self.write(msg.lower()) + except ssl.SSLError: + if self.server.chatty: + handle_error("Test server failure:\n") + self.close() + self.running = False + # normally, we'd just stop here, but for the test + # harness, we want to stop the server + self.server.stop() + except: + handle_error('') + + def __init__(self, port, certificate, ssl_version=None, + certreqs=None, cacerts=None, expect_bad_connects=False, + chatty=True, connectionchatty=False, starttls_server=False): + if ssl_version is None: + ssl_version = ssl.PROTOCOL_TLSv1 + if certreqs is None: + certreqs = ssl.CERT_NONE + self.certificate = certificate + self.protocol = ssl_version + self.certreqs = certreqs + self.cacerts = cacerts + self.expect_bad_connects = expect_bad_connects + self.chatty = chatty + self.connectionchatty = connectionchatty + self.starttls_server = starttls_server + self.sock = socket.socket() + self.flag = None + if hasattr(socket, 'SO_REUSEADDR'): + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if hasattr(socket, 'SO_REUSEPORT'): + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + self.sock.bind(('127.0.0.1', port)) + self.active = False threading.Thread.__init__(self) - self.setDaemon(True) + self.setDaemon(False) + + def start (self, flag=None): + self.flag = flag + threading.Thread.start(self) def run (self): - self.running = True - try: - sslconn = ssl.sslsocket(self.sock, server_side=True, - certfile=self.server.certificate, - ssl_version=self.server.protocol, - cert_reqs=self.server.certreqs) - except: - # here, we want to stop the server, because this shouldn't - # happen in the context of our test case - handle_error("Test server failure:\n") - self.running = False - # normally, we'd just stop here, but for the test - # harness, we want to stop the server - self.server.stop() - return - - while self.running: + self.sock.settimeout(0.5) + self.sock.listen(5) + self.active = True + if self.flag: + # signal an event + self.flag.set() + while self.active: try: - msg = sslconn.read() - if not msg: - # eof, so quit this handler - self.running = False - sslconn.close() - elif msg.strip() == 'over': - sslconn.close() - self.server.stop() - self.running = False - else: - if test_support.verbose: - sys.stdout.write("\nserver: %s\n" % msg.strip().lower()) - sslconn.write(msg.lower()) - except ssl.sslerror: - handle_error("Test server failure:\n") - sslconn.close() - self.running = False - # normally, we'd just stop here, but for the test - # harness, we want to stop the server - self.server.stop() + newconn, connaddr = self.sock.accept() + if test_support.verbose and self.chatty: + sys.stdout.write(' server: new connection from ' + + str(connaddr) + '\n') + handler = self.ConnectionHandler(self, newconn) + handler.start() + except socket.timeout: + pass + except KeyboardInterrupt: + self.stop() except: - handle_error('') + if self.chatty: + handle_error("Test server failure:\n") - def __init__(self, port, certificate, ssl_version=None, - certreqs=None, cacerts=None): - if ssl_version is None: - ssl_version = ssl.PROTOCOL_TLSv1 - if certreqs is None: - certreqs = ssl.CERT_NONE - self.certificate = certificate - self.protocol = ssl_version - self.certreqs = certreqs - self.cacerts = cacerts - self.sock = socket.socket() - self.flag = None - if hasattr(socket, 'SO_REUSEADDR'): - self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - if hasattr(socket, 'SO_REUSEPORT'): - self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - self.sock.bind(('127.0.0.1', port)) - self.active = False - threading.Thread.__init__(self) - self.setDaemon(False) + def stop (self): + self.active = False + self.sock.close() - def start (self, flag=None): - self.flag = flag - threading.Thread.start(self) - - def run (self): - self.sock.settimeout(0.5) - self.sock.listen(5) - self.active = True - if self.flag: - # signal an event - self.flag.set() - while self.active: + def badCertTest (certfile): + server = ThreadedEchoServer(TESTPORT, CERTFILE, + certreqs=ssl.CERT_REQUIRED, + cacerts=CERTFILE, chatty=False) + flag = threading.Event() + server.start(flag) + # wait for it to start + flag.wait() + # try to connect + try: try: - newconn, connaddr = self.sock.accept() + s = ssl.wrap_socket(socket.socket(), + certfile=certfile, + ssl_version=ssl.PROTOCOL_TLSv1) + s.connect(('127.0.0.1', TESTPORT)) + except ssl.SSLError, x: if test_support.verbose: - sys.stdout.write('\nserver: new connection from ' + str(connaddr) + '\n') - handler = self.ConnectionHandler(self, newconn) - handler.start() - except socket.timeout: - pass - except KeyboardInterrupt: - self.stop() - except: - handle_error("Test server failure:\n") + sys.stdout.write("\nSSLError is %s\n" % x[1]) + else: + raise test_support.TestFailed( + "Use of invalid cert should have failed!") + finally: + server.stop() + server.join() + + def serverParamsTest (certfile, protocol, certreqs, cacertsfile, + client_certfile, client_protocol=None, indata="FOO\n", + chatty=True, connectionchatty=False): + + server = ThreadedEchoServer(TESTPORT, certfile, + certreqs=certreqs, + ssl_version=protocol, + cacerts=cacertsfile, + chatty=chatty, + connectionchatty=connectionchatty) + flag = threading.Event() + server.start(flag) + # wait for it to start + flag.wait() + # try to connect + if client_protocol is None: + client_protocol = protocol + try: + try: + s = ssl.wrap_socket(socket.socket(), + certfile=client_certfile, + ca_certs=cacertsfile, + cert_reqs=certreqs, + ssl_version=client_protocol) + s.connect(('127.0.0.1', TESTPORT)) + except ssl.SSLError, x: + raise test_support.TestFailed("Unexpected SSL error: " + str(x)) + except Exception, x: + raise test_support.TestFailed("Unexpected exception: " + str(x)) + else: + if connectionchatty: + if test_support.verbose: + sys.stdout.write( + " client: sending %s...\n" % (repr(indata))) + s.write(indata) + outdata = s.read() + if connectionchatty: + if test_support.verbose: + sys.stdout.write(" client: read %s\n" % repr(outdata)) + if outdata != indata.lower(): + raise test_support.TestFailed( + "bad data <<%s>> (%d) received; expected <<%s>> (%d)\n" + % (outdata[:min(len(outdata),20)], len(outdata), + indata[:min(len(indata),20)].lower(), len(indata))) + s.write("over\n") + if connectionchatty: + if test_support.verbose: + sys.stdout.write(" client: closing connection.\n") + s.ssl_shutdown() + s.close() + finally: + server.stop() + server.join() + + def tryProtocolCombo (server_protocol, + client_protocol, + expectedToWork, + certsreqs=ssl.CERT_NONE): + + if certsreqs == ssl.CERT_NONE: + certtype = "CERT_NONE" + elif certsreqs == ssl.CERT_OPTIONAL: + certtype = "CERT_OPTIONAL" + elif certsreqs == ssl.CERT_REQUIRED: + certtype = "CERT_REQUIRED" + if test_support.verbose: + formatstr = (expectedToWork and " %s->%s %s\n") or " {%s->%s} %s\n" + sys.stdout.write(formatstr % + (ssl.get_protocol_name(client_protocol), + ssl.get_protocol_name(server_protocol), + certtype)) + try: + serverParamsTest(CERTFILE, server_protocol, certsreqs, + CERTFILE, CERTFILE, client_protocol, chatty=False) + except test_support.TestFailed: + if expectedToWork: + raise + else: + if not expectedToWork: + raise test_support.TestFailed( + "Client protocol %s succeeded with server protocol %s!" + % (ssl.get_protocol_name(client_protocol), + ssl.get_protocol_name(server_protocol))) + + + class ConnectedTests(unittest.TestCase): + + def testRudeShutdown(self): + + listener_ready = threading.Event() + listener_gone = threading.Event() + + # `listener` runs in a thread. It opens a socket listening on + # PORT, and sits in an accept() until the main thread connects. + # Then it rudely closes the socket, and sets Event `listener_gone` + # to let the main thread know the socket is gone. + def listener(): + s = socket.socket() + if hasattr(socket, 'SO_REUSEPORT'): + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + port = test_support.bind_port(s, 'localhost', TESTPORT) + s.listen(5) + listener_ready.set() + s.accept() + s = None # reclaim the socket object, which also closes it + listener_gone.set() + + def connector(): + listener_ready.wait() + s = socket.socket() + s.connect(('localhost', TESTPORT)) + listener_gone.wait() + try: + ssl_sock = ssl.wrap_socket(s) + except socket.sslerror: + pass + else: + raise test_support.TestFailed( + 'connecting to closed SSL socket should have failed') + + t = threading.Thread(target=listener) + t.start() + connector() + t.join() + + def testEcho (self): + + if test_support.verbose: + sys.stdout.write("\n") + serverParamsTest(CERTFILE, ssl.PROTOCOL_TLSv1, ssl.CERT_NONE, + CERTFILE, CERTFILE, ssl.PROTOCOL_TLSv1, + chatty=True, connectionchatty=True) + + def testReadCert(self): + + if test_support.verbose: + sys.stdout.write("\n") + s2 = socket.socket() + server = ThreadedEchoServer(TESTPORT, CERTFILE, + certreqs=ssl.CERT_NONE, + ssl_version=ssl.PROTOCOL_SSLv23, + cacerts=CERTFILE, + chatty=False) + flag = threading.Event() + server.start(flag) + # wait for it to start + flag.wait() + # try to connect + try: + try: + s = ssl.wrap_socket(socket.socket(), + certfile=CERTFILE, + ca_certs=CERTFILE, + cert_reqs=ssl.CERT_REQUIRED, + ssl_version=ssl.PROTOCOL_SSLv23) + s.connect(('127.0.0.1', TESTPORT)) + except ssl.SSLError, x: + raise test_support.TestFailed( + "Unexpected SSL error: " + str(x)) + except Exception, x: + raise test_support.TestFailed( + "Unexpected exception: " + str(x)) + else: + if not s: + raise test_support.TestFailed( + "Can't SSL-handshake with test server") + cert = s.getpeercert() + if not cert: + raise test_support.TestFailed( + "Can't get peer certificate.") + cipher = s.cipher() + if test_support.verbose: + sys.stdout.write(pprint.pformat(cert) + '\n') + sys.stdout.write("Connection cipher is " + str(cipher) + '.\n') + if not cert.has_key('subject'): + raise test_support.TestFailed( + "No subject field in certificate: %s." % + pprint.pformat(cert)) + if ((('organizationName', 'Python Software Foundation'),) + not in cert['subject']): + raise test_support.TestFailed( + "Missing or invalid 'organizationName' field in certificate subject; " + "should be 'Python Software Foundation'."); + s.close() + finally: + server.stop() + server.join() + + def testNULLcert(self): + badCertTest(os.path.join(os.path.dirname(__file__) or os.curdir, + "nullcert.pem")) + def testMalformedCert(self): + badCertTest(os.path.join(os.path.dirname(__file__) or os.curdir, + "badcert.pem")) + def testMalformedKey(self): + badCertTest(os.path.join(os.path.dirname(__file__) or os.curdir, + "badkey.pem")) + + def testProtocolSSL2(self): + if test_support.verbose: + sys.stdout.write("\n") + tryProtocolCombo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_SSLv2, True) + tryProtocolCombo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_SSLv2, True, ssl.CERT_OPTIONAL) + tryProtocolCombo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_SSLv2, True, ssl.CERT_REQUIRED) + tryProtocolCombo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_SSLv23, True) + tryProtocolCombo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_SSLv3, False) + tryProtocolCombo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_TLSv1, False) + + def testProtocolSSL23(self): + if test_support.verbose: + sys.stdout.write("\n") + try: + tryProtocolCombo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv2, True) + except test_support.TestFailed, x: + # this fails on some older versions of OpenSSL (0.9.7l, for instance) + if test_support.verbose: + sys.stdout.write( + " SSL2 client to SSL23 server test unexpectedly failed:\n %s\n" + % str(x)) + tryProtocolCombo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv3, True) + tryProtocolCombo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv23, True) + tryProtocolCombo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1, True) + + tryProtocolCombo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv3, True, ssl.CERT_OPTIONAL) + tryProtocolCombo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv23, True, ssl.CERT_OPTIONAL) + tryProtocolCombo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1, True, ssl.CERT_OPTIONAL) + + tryProtocolCombo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv3, True, ssl.CERT_REQUIRED) + tryProtocolCombo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv23, True, ssl.CERT_REQUIRED) + tryProtocolCombo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1, True, ssl.CERT_REQUIRED) + + def testProtocolSSL3(self): + if test_support.verbose: + sys.stdout.write("\n") + tryProtocolCombo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, True) + tryProtocolCombo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, True, ssl.CERT_OPTIONAL) + tryProtocolCombo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, True, ssl.CERT_REQUIRED) + tryProtocolCombo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv2, False) + tryProtocolCombo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv23, False) + tryProtocolCombo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_TLSv1, False) + + def testProtocolTLS1(self): + if test_support.verbose: + sys.stdout.write("\n") + tryProtocolCombo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, True) + tryProtocolCombo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, True, ssl.CERT_OPTIONAL) + tryProtocolCombo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, True, ssl.CERT_REQUIRED) + tryProtocolCombo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_SSLv2, False) + tryProtocolCombo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_SSLv3, False) + tryProtocolCombo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_SSLv23, False) + + def testSTARTTLS (self): + + msgs = ("msg 1", "MSG 2", "STARTTLS", "MSG 3", "msg 4") + + server = ThreadedEchoServer(TESTPORT, CERTFILE, + ssl_version=ssl.PROTOCOL_TLSv1, + starttls_server=True, + chatty=True, + connectionchatty=True) + flag = threading.Event() + server.start(flag) + # wait for it to start + flag.wait() + # try to connect + wrapped = False + try: + try: + s = socket.socket() + s.setblocking(1) + s.connect(('127.0.0.1', TESTPORT)) + except Exception, x: + raise test_support.TestFailed("Unexpected exception: " + str(x)) + else: + if test_support.verbose: + sys.stdout.write("\n") + for indata in msgs: + if test_support.verbose: + sys.stdout.write(" client: sending %s...\n" % repr(indata)) + if wrapped: + conn.write(indata) + outdata = conn.read() + else: + s.send(indata) + outdata = s.recv(1024) + if indata == "STARTTLS" and outdata.strip().lower().startswith("ok"): + if test_support.verbose: + sys.stdout.write(" client: read %s from server, starting TLS...\n" % repr(outdata)) + conn = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_TLSv1) + + wrapped = True + else: + if test_support.verbose: + sys.stdout.write(" client: read %s from server\n" % repr(outdata)) + if test_support.verbose: + sys.stdout.write(" client: closing connection.\n") + if wrapped: + conn.write("over\n") + conn.ssl_shutdown() + else: + s.send("over\n") + s.close() + finally: + server.stop() + server.join() - def stop (self): - self.active = False - self.sock.close() CERTFILE_CONFIG_TEMPLATE = """ # create RSA certs - Server @@ -337,33 +674,21 @@ def test_main(verbose=False): global CERTFILE CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir, - "keycert.pem") - if not CERTFILE: - sys.__stdout__.write("Skipping test_ssl ConnectedTests; " - "couldn't create a certificate.\n") + "keycert.pem") + if (not os.path.exists(CERTFILE)): + raise test_support.TestFailed("Can't read certificate files!") tests = [BasicTests] - server = None - if CERTFILE and test_support.is_resource_enabled('network'): - server = ThreadedEchoServer(10024, CERTFILE) - flag = threading.Event() - server.start(flag) - # wait for it to start - flag.wait() - tests.append(ConnectedTests) + if _have_threads: + thread_info = test_support.threading_setup() + if CERTFILE and thread_info and test_support.is_resource_enabled('network'): + tests.append(ConnectedTests) - thread_info = test_support.threading_setup() + test_support.run_unittest(*tests) - try: - test_support.run_unittest(*tests) - finally: - if server is not None and server.active: - server.stop() - # wait for it to stop - server.join() - - test_support.threading_cleanup(*thread_info) + if _have_threads: + test_support.threading_cleanup(*thread_info) if __name__ == "__main__": test_main() diff --git a/Modules/_ssl.c b/Modules/_ssl.c index a34c3c84507..45a5490d4ce 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -1,14 +1,38 @@ /* SSL socket module SSL support based on patches by Brian E Gallew and Laszlo Kovacs. + Re-worked a bit by Bill Janssen to add server-side support and + certificate decoding. - This module is imported by socket.py. It should *not* be used + This module is imported by ssl.py. It should *not* be used directly. + XXX should partial writes be enabled, SSL_MODE_ENABLE_PARTIAL_WRITE? + + XXX what about SSL_MODE_AUTO_RETRY */ #include "Python.h" +#ifdef WITH_THREAD +#include "pythread.h" +#define PySSL_BEGIN_ALLOW_THREADS { \ + PyThreadState *_save; \ + if (_ssl_locks_count>0) {_save = PyEval_SaveThread();} +#define PySSL_BLOCK_THREADS if (_ssl_locks_count>0){PyEval_RestoreThread(_save)}; +#define PySSL_UNBLOCK_THREADS if (_ssl_locks_count>0){_save = PyEval_SaveThread()}; +#define PySSL_END_ALLOW_THREADS if (_ssl_locks_count>0){PyEval_RestoreThread(_save);} \ + } + +#else /* no WITH_THREAD */ + +#define PySSL_BEGIN_ALLOW_THREADS +#define PySSL_BLOCK_THREADS +#define PySSL_UNBLOCK_THREADS +#define PySSL_END_ALLOW_THREADS + +#endif + enum py_ssl_error { /* these mirror ssl.h */ PY_SSL_ERROR_NONE, @@ -55,6 +79,7 @@ enum py_ssl_version { #include "openssl/rsa.h" #include "openssl/crypto.h" #include "openssl/x509.h" +#include "openssl/x509v3.h" #include "openssl/pem.h" #include "openssl/ssl.h" #include "openssl/err.h" @@ -63,6 +88,15 @@ enum py_ssl_version { /* SSL error object */ static PyObject *PySSLErrorObject; +#ifdef WITH_THREAD + +/* serves as a flag to see whether we've initialized the SSL thread support. */ +/* 0 means no, greater than 0 means yes */ + +static unsigned int _ssl_locks_count = 0; + +#endif /* def WITH_THREAD */ + /* SSL socket object */ #define X509_NAME_MAXLEN 256 @@ -90,8 +124,9 @@ static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args); static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args); static int check_socket_and_wait_for_timeout(PySocketSockObject *s, int writing); -static PyObject *PySSL_peercert(PySSLObject *self); - +static PyObject *PySSL_peercert(PySSLObject *self, PyObject *args); +static PyObject *PySSL_cipher(PySSLObject *self); +static PyObject *PySSL_SSLshutdown(PySSLObject *self); #define PySSLObject_Check(v) (Py_Type(v) == &PySSL_Type) @@ -126,7 +161,7 @@ PySSL_SetError(PySSLObject *obj, int ret, char *filename, int lineno) assert(ret <= 0); - if ((obj != NULL) && (obj->ssl != NULL)) { + if (obj->ssl != NULL) { err = SSL_get_error(obj->ssl, ret); switch (err) { @@ -202,6 +237,25 @@ PySSL_SetError(PySSLObject *obj, int ret, char *filename, int lineno) return NULL; } +static PyObject * +_setSSLError (char *errstr, int errcode, char *filename, int lineno) { + + char buf[2048]; + PyObject *v; + + if (errstr == NULL) { + errcode = ERR_peek_last_error(); + errstr = ERR_error_string(errcode, NULL); + } + PyOS_snprintf(buf, sizeof(buf), "_ssl.c:%d: %s", lineno, errstr); + v = Py_BuildValue("(is)", errcode, buf); + if (v != NULL) { + PyErr_SetObject(PySSLErrorObject, v); + Py_DECREF(v); + } + return NULL; +} + static PySSLObject * newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, enum py_ssl_server_or_client socket_type, @@ -226,6 +280,10 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, self->ctx = NULL; self->Socket = NULL; + /* Make sure the SSL error state is initialized */ + (void) ERR_get_state(); + ERR_clear_error(); + if ((key_file && !cert_file) || (!key_file && cert_file)) { errstr = ERRSTR("Both the key & certificate files " "must be specified"); @@ -239,16 +297,16 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, goto fail; } - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS if (proto_version == PY_SSL_VERSION_TLS1) self->ctx = SSL_CTX_new(TLSv1_method()); /* Set up context */ else if (proto_version == PY_SSL_VERSION_SSL3) self->ctx = SSL_CTX_new(SSLv3_method()); /* Set up context */ else if (proto_version == PY_SSL_VERSION_SSL2) self->ctx = SSL_CTX_new(SSLv2_method()); /* Set up context */ - else + else if (proto_version == PY_SSL_VERSION_SSL23) self->ctx = SSL_CTX_new(SSLv23_method()); /* Set up context */ - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if (self->ctx == NULL) { errstr = ERRSTR("Invalid SSL protocol variant specified."); @@ -261,39 +319,46 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, "verification of other-side certificates."); goto fail; } else { - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS ret = SSL_CTX_load_verify_locations(self->ctx, cacerts_file, NULL); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if (ret != 1) { - PySSL_SetError(NULL, 0, __FILE__, __LINE__); + _setSSLError(NULL, 0, __FILE__, __LINE__); goto fail; } } } if (key_file) { - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS ret = SSL_CTX_use_PrivateKey_file(self->ctx, key_file, SSL_FILETYPE_PEM); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if (ret != 1) { - PySSL_SetError(NULL, 0, __FILE__, __LINE__); + _setSSLError(NULL, ret, __FILE__, __LINE__); goto fail; } - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS ret = SSL_CTX_use_certificate_chain_file(self->ctx, - cert_file); - Py_END_ALLOW_THREADS + cert_file); + PySSL_END_ALLOW_THREADS if (ret != 1) { - PySSL_SetError(NULL, 0, __FILE__, __LINE__); - goto fail; + /* + fprintf(stderr, "ret is %d, errcode is %lu, %lu, with file \"%s\"\n", + ret, ERR_peek_error(), ERR_peek_last_error(), cert_file); + */ + if (ERR_peek_last_error() != 0) { + _setSSLError(NULL, ret, __FILE__, __LINE__); + goto fail; + } } - /* ssl compatibility */ - SSL_CTX_set_options(self->ctx, SSL_OP_ALL); } + /* ssl compatibility */ + SSL_CTX_set_options(self->ctx, SSL_OP_ALL); + verification_mode = SSL_VERIFY_NONE; if (certreq == PY_SSL_CERT_OPTIONAL) verification_mode = SSL_VERIFY_PEER; @@ -303,9 +368,9 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, SSL_CTX_set_verify(self->ctx, verification_mode, NULL); /* set verify lvl */ - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS self->ssl = SSL_new(self->ctx); /* New ssl struct */ - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS SSL_set_fd(self->ssl, Sock->sock_fd); /* Set the socket for SSL */ /* If the socket is in non-blocking mode or timeout mode, set the BIO @@ -317,24 +382,24 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, BIO_set_nbio(SSL_get_wbio(self->ssl), 1); } - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS if (socket_type == PY_SSL_CLIENT) SSL_set_connect_state(self->ssl); else SSL_set_accept_state(self->ssl); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS /* Actually negotiate SSL connection */ /* XXX If SSL_connect() returns 0, it's also a failure. */ sockstate = 0; do { - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS if (socket_type == PY_SSL_CLIENT) ret = SSL_connect(self->ssl); else ret = SSL_accept(self->ssl); err = SSL_get_error(self->ssl, ret); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if(PyErr_CheckSignals()) { goto fail; } @@ -367,14 +432,14 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, } self->ssl->debug = 1; - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS if ((self->peer_cert = SSL_get_peer_certificate(self->ssl))) { X509_NAME_oneline(X509_get_subject_name(self->peer_cert), self->server, X509_NAME_MAXLEN); X509_NAME_oneline(X509_get_issuer_name(self->peer_cert), self->issuer, X509_NAME_MAXLEN); } - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS self->Socket = Sock; Py_INCREF(self->Socket); return self; @@ -437,96 +502,313 @@ PySSL_issuer(PySSLObject *self) } static PyObject * -_create_tuple_for_X509_NAME (X509_NAME *xname) -{ - PyObject *pt = NULL; - PyObject *entry_tuple = NULL; - int entry_count = X509_NAME_entry_count(xname); - int index_counter; +_create_tuple_for_attribute (ASN1_OBJECT *name, ASN1_STRING *value) { - pt = PyTuple_New(entry_count); - if (pt == NULL) - return NULL; + char namebuf[X509_NAME_MAXLEN]; + int buflen; + PyObject *name_obj; + PyObject *value_obj; + PyObject *attr; + unsigned char *valuebuf = NULL; - for (index_counter = 0; - index_counter < X509_NAME_entry_count(xname); - index_counter++) - { - char namebuf[X509_NAME_MAXLEN]; - int buflen; - PyObject *name_obj; - ASN1_STRING *value; - PyObject *value_obj; - unsigned char *valuebuf = NULL; - - X509_NAME_ENTRY *entry = X509_NAME_get_entry(xname, - index_counter); - - ASN1_OBJECT *name = X509_NAME_ENTRY_get_object(entry); - buflen = OBJ_obj2txt(namebuf, sizeof(namebuf), name, 0); - if (buflen < 0) - goto fail0; - name_obj = PyString_FromStringAndSize(namebuf, buflen); - if (name_obj == NULL) - goto fail0; - - value = X509_NAME_ENTRY_get_data(entry); - buflen = ASN1_STRING_to_UTF8(&valuebuf, value); - if (buflen < 0) { - Py_DECREF(name_obj); - goto fail0; - } - value_obj = PyUnicode_DecodeUTF8((char *) valuebuf, - buflen, "strict"); - OPENSSL_free(valuebuf); - if (value_obj == NULL) { - Py_DECREF(name_obj); - goto fail0; - } - entry_tuple = PyTuple_New(2); - if (entry_tuple == NULL) { - Py_DECREF(name_obj); - Py_DECREF(value_obj); - goto fail0; - } - PyTuple_SET_ITEM(entry_tuple, 0, name_obj); - PyTuple_SET_ITEM(entry_tuple, 1, value_obj); - PyTuple_SET_ITEM(pt, index_counter, entry_tuple); + buflen = OBJ_obj2txt(namebuf, sizeof(namebuf), name, 0); + if (buflen < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto fail; } - return pt; + name_obj = PyString_FromStringAndSize(namebuf, buflen); + if (name_obj == NULL) + goto fail; + + buflen = ASN1_STRING_to_UTF8(&valuebuf, value); + if (buflen < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + Py_DECREF(name_obj); + goto fail; + } + value_obj = PyUnicode_DecodeUTF8((char *) valuebuf, + buflen, "strict"); + OPENSSL_free(valuebuf); + if (value_obj == NULL) { + Py_DECREF(name_obj); + goto fail; + } + attr = PyTuple_New(2); + if (attr == NULL) { + Py_DECREF(name_obj); + Py_DECREF(value_obj); + goto fail; + } + PyTuple_SET_ITEM(attr, 0, name_obj); + PyTuple_SET_ITEM(attr, 1, value_obj); + return attr; - fail0: - Py_XDECREF(pt); + fail: return NULL; } static PyObject * -PySSL_peercert(PySSLObject *self) +_create_tuple_for_X509_NAME (X509_NAME *xname) { + PyObject *dn = NULL; /* tuple which represents the "distinguished name" */ + PyObject *rdn = NULL; /* tuple to hold a "relative distinguished name" */ + PyObject *rdnt; + PyObject *attr = NULL; /* tuple to hold an attribute */ + int entry_count = X509_NAME_entry_count(xname); + X509_NAME_ENTRY *entry; + ASN1_OBJECT *name; + ASN1_STRING *value; + int index_counter; + int rdn_level = -1; + int retcode; + + dn = PyList_New(0); + if (dn == NULL) + return NULL; + /* now create another tuple to hold the top-level RDN */ + rdn = PyList_New(0); + if (rdn == NULL) + goto fail0; + + for (index_counter = 0; + index_counter < entry_count; + index_counter++) + { + entry = X509_NAME_get_entry(xname, index_counter); + + /* check to see if we've gotten to a new RDN */ + if (rdn_level >= 0) { + if (rdn_level != entry->set) { + /* yes, new RDN */ + /* add old RDN to DN */ + rdnt = PyList_AsTuple(rdn); + Py_DECREF(rdn); + if (rdnt == NULL) + goto fail0; + retcode = PyList_Append(dn, rdnt); + Py_DECREF(rdnt); + if (retcode < 0) + goto fail0; + /* create new RDN */ + rdn = PyList_New(0); + if (rdn == NULL) + goto fail0; + } + } + rdn_level = entry->set; + + /* now add this attribute to the current RDN */ + name = X509_NAME_ENTRY_get_object(entry); + value = X509_NAME_ENTRY_get_data(entry); + attr = _create_tuple_for_attribute(name, value); + /* + fprintf(stderr, "RDN level %d, attribute %s: %s\n", + entry->set, + PyString_AS_STRING(PyTuple_GET_ITEM(attr, 0)), + PyString_AS_STRING(PyTuple_GET_ITEM(attr, 1))); + */ + if (attr == NULL) + goto fail1; + retcode = PyList_Append(rdn, attr); + Py_DECREF(attr); + if (retcode < 0) + goto fail1; + } + /* now, there's typically a dangling RDN */ + if ((rdn != NULL) && (PyList_Size(rdn) > 0)) { + rdnt = PyList_AsTuple(rdn); + Py_DECREF(rdn); + if (rdnt == NULL) + goto fail0; + retcode = PyList_Append(dn, rdnt); + Py_DECREF(rdnt); + if (retcode < 0) + goto fail0; + } + + /* convert list to tuple */ + rdnt = PyList_AsTuple(dn); + Py_DECREF(dn); + if (rdnt == NULL) + return NULL; + return rdnt; + + fail1: + Py_XDECREF(rdn); + + fail0: + Py_XDECREF(dn); + return NULL; +} + +static PyObject * +_get_peer_alt_names (X509 *certificate) { + + /* this code follows the procedure outlined in + OpenSSL's crypto/x509v3/v3_prn.c:X509v3_EXT_print() + function to extract the STACK_OF(GENERAL_NAME), + then iterates through the stack to add the + names. */ + + int i, j; + PyObject *peer_alt_names = Py_None; + PyObject *v, *t; + X509_EXTENSION *ext = NULL; + GENERAL_NAMES *names = NULL; + GENERAL_NAME *name; + X509V3_EXT_METHOD *method; + BIO *biobuf = NULL; + char buf[2048]; + char *vptr; + int len; + const unsigned char *p; + + if (certificate == NULL) + return peer_alt_names; + + /* get a memory buffer */ + biobuf = BIO_new(BIO_s_mem()); + + i = 0; + while ((i = X509_get_ext_by_NID( + certificate, NID_subject_alt_name, i)) >= 0) { + + if (peer_alt_names == Py_None) { + peer_alt_names = PyList_New(0); + if (peer_alt_names == NULL) + goto fail; + } + + /* now decode the altName */ + ext = X509_get_ext(certificate, i); + if(!(method = X509V3_EXT_get(ext))) { + PyErr_SetString(PySSLErrorObject, + ERRSTR("No method for internalizing subjectAltName!")); + goto fail; + } + + p = ext->value->data; + if(method->it) + names = (GENERAL_NAMES*) (ASN1_item_d2i(NULL, + &p, + ext->value->length, + ASN1_ITEM_ptr(method->it))); + else + names = (GENERAL_NAMES*) (method->d2i(NULL, + &p, + ext->value->length)); + + for(j = 0; j < sk_GENERAL_NAME_num(names); j++) { + + /* get a rendering of each name in the set of names */ + + name = sk_GENERAL_NAME_value(names, j); + if (name->type == GEN_DIRNAME) { + + /* we special-case DirName as a tuple of tuples of attributes */ + + t = PyTuple_New(2); + if (t == NULL) { + goto fail; + } + + v = PyString_FromString("DirName"); + if (v == NULL) { + Py_DECREF(t); + goto fail; + } + PyTuple_SET_ITEM(t, 0, v); + + v = _create_tuple_for_X509_NAME (name->d.dirn); + if (v == NULL) { + Py_DECREF(t); + goto fail; + } + PyTuple_SET_ITEM(t, 1, v); + + } else { + + /* for everything else, we use the OpenSSL print form */ + + (void) BIO_reset(biobuf); + GENERAL_NAME_print(biobuf, name); + len = BIO_gets(biobuf, buf, sizeof(buf)-1); + if (len < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto fail; + } + vptr = strchr(buf, ':'); + if (vptr == NULL) + goto fail; + t = PyTuple_New(2); + if (t == NULL) + goto fail; + v = PyString_FromStringAndSize(buf, (vptr - buf)); + if (v == NULL) { + Py_DECREF(t); + goto fail; + } + PyTuple_SET_ITEM(t, 0, v); + v = PyString_FromStringAndSize((vptr + 1), (len - (vptr - buf + 1))); + if (v == NULL) { + Py_DECREF(t); + goto fail; + } + PyTuple_SET_ITEM(t, 1, v); + } + + /* and add that rendering to the list */ + + if (PyList_Append(peer_alt_names, t) < 0) { + Py_DECREF(t); + goto fail; + } + Py_DECREF(t); + } + } + BIO_free(biobuf); + if (peer_alt_names != Py_None) { + v = PyList_AsTuple(peer_alt_names); + Py_DECREF(peer_alt_names); + return v; + } else { + return peer_alt_names; + } + + + fail: + if (biobuf != NULL) + BIO_free(biobuf); + + if (peer_alt_names != Py_None) { + Py_XDECREF(peer_alt_names); + } + + return NULL; +} + +static PyObject * +_decode_certificate (X509 *certificate, int verbose) { + PyObject *retval = NULL; BIO *biobuf = NULL; PyObject *peer; + PyObject *peer_alt_names = NULL; PyObject *issuer; PyObject *version; + PyObject *sn_obj; + ASN1_INTEGER *serialNumber; char buf[2048]; int len; ASN1_TIME *notBefore, *notAfter; PyObject *pnotBefore, *pnotAfter; - int verification; - - if (!self->peer_cert) - Py_RETURN_NONE; retval = PyDict_New(); if (retval == NULL) return NULL; - verification = SSL_CTX_get_verify_mode(self->ctx); - if ((verification & SSL_VERIFY_PEER) == 0) - return retval; - peer = _create_tuple_for_X509_NAME( - X509_get_subject_name(self->peer_cert)); + X509_get_subject_name(certificate)); if (peer == NULL) goto fail0; if (PyDict_SetItemString(retval, (const char *) "subject", peer) < 0) { @@ -535,51 +817,98 @@ PySSL_peercert(PySSLObject *self) } Py_DECREF(peer); - issuer = _create_tuple_for_X509_NAME( - X509_get_issuer_name(self->peer_cert)); - if (issuer == NULL) - goto fail0; - if (PyDict_SetItemString(retval, (const char *)"issuer", issuer) < 0) { + if (verbose) { + issuer = _create_tuple_for_X509_NAME( + X509_get_issuer_name(certificate)); + if (issuer == NULL) + goto fail0; + if (PyDict_SetItemString(retval, (const char *)"issuer", issuer) < 0) { + Py_DECREF(issuer); + goto fail0; + } Py_DECREF(issuer); - goto fail0; - } - Py_DECREF(issuer); - - version = PyInt_FromLong(X509_get_version(self->peer_cert)); - if (PyDict_SetItemString(retval, "version", version) < 0) { + + version = PyInt_FromLong(X509_get_version(certificate) + 1); + if (PyDict_SetItemString(retval, "version", version) < 0) { + Py_DECREF(version); + goto fail0; + } Py_DECREF(version); - goto fail0; } - Py_DECREF(version); - + /* get a memory buffer */ biobuf = BIO_new(BIO_s_mem()); + + if (verbose) { - notBefore = X509_get_notBefore(self->peer_cert); - ASN1_TIME_print(biobuf, notBefore); - len = BIO_gets(biobuf, buf, sizeof(buf)-1); - pnotBefore = PyString_FromStringAndSize(buf, len); - if (pnotBefore == NULL) - goto fail1; - if (PyDict_SetItemString(retval, "notBefore", pnotBefore) < 0) { + (void) BIO_reset(biobuf); + serialNumber = X509_get_serialNumber(certificate); + /* should not exceed 20 octets, 160 bits, so buf is big enough */ + i2a_ASN1_INTEGER(biobuf, serialNumber); + len = BIO_gets(biobuf, buf, sizeof(buf)-1); + if (len < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto fail1; + } + sn_obj = PyString_FromStringAndSize(buf, len); + if (sn_obj == NULL) + goto fail1; + if (PyDict_SetItemString(retval, "serialNumber", sn_obj) < 0) { + Py_DECREF(sn_obj); + goto fail1; + } + Py_DECREF(sn_obj); + + (void) BIO_reset(biobuf); + notBefore = X509_get_notBefore(certificate); + ASN1_TIME_print(biobuf, notBefore); + len = BIO_gets(biobuf, buf, sizeof(buf)-1); + if (len < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto fail1; + } + pnotBefore = PyString_FromStringAndSize(buf, len); + if (pnotBefore == NULL) + goto fail1; + if (PyDict_SetItemString(retval, "notBefore", pnotBefore) < 0) { + Py_DECREF(pnotBefore); + goto fail1; + } Py_DECREF(pnotBefore); - goto fail1; } - Py_DECREF(pnotBefore); (void) BIO_reset(biobuf); - notAfter = X509_get_notAfter(self->peer_cert); + notAfter = X509_get_notAfter(certificate); ASN1_TIME_print(biobuf, notAfter); len = BIO_gets(biobuf, buf, sizeof(buf)-1); - BIO_free(biobuf); + if (len < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto fail1; + } pnotAfter = PyString_FromStringAndSize(buf, len); if (pnotAfter == NULL) - goto fail0; + goto fail1; if (PyDict_SetItemString(retval, "notAfter", pnotAfter) < 0) { Py_DECREF(pnotAfter); - goto fail0; + goto fail1; } Py_DECREF(pnotAfter); + + /* Now look for subjectAltName */ + + peer_alt_names = _get_peer_alt_names(certificate); + if (peer_alt_names == NULL) + goto fail1; + else if (peer_alt_names != Py_None) { + if (PyDict_SetItemString(retval, "subjectAltName", + peer_alt_names) < 0) { + Py_DECREF(peer_alt_names); + goto fail1; + } + Py_DECREF(peer_alt_names); + } + + BIO_free(biobuf); return retval; fail1: @@ -590,6 +919,141 @@ PySSL_peercert(PySSLObject *self) return NULL; } + +static PyObject * +PySSL_test_decode_certificate (PyObject *mod, PyObject *args) { + + PyObject *retval = NULL; + char *filename = NULL; + X509 *x=NULL; + BIO *cert; + int verbose = 1; + + if (!PyArg_ParseTuple(args, "s|i:test_decode_certificate", &filename, &verbose)) + return NULL; + + if ((cert=BIO_new(BIO_s_file())) == NULL) { + PyErr_SetString(PySSLErrorObject, "Can't malloc memory to read file"); + goto fail0; + } + + if (BIO_read_filename(cert,filename) <= 0) { + PyErr_SetString(PySSLErrorObject, "Can't open file"); + goto fail0; + } + + x = PEM_read_bio_X509_AUX(cert,NULL, NULL, NULL); + if (x == NULL) { + PyErr_SetString(PySSLErrorObject, "Error decoding PEM-encoded file"); + goto fail0; + } + + retval = _decode_certificate(x, verbose); + + fail0: + + if (cert != NULL) BIO_free(cert); + return retval; +} + + +static PyObject * +PySSL_peercert(PySSLObject *self, PyObject *args) +{ + PyObject *retval = NULL; + int len; + int verification; + PyObject *binary_mode = Py_None; + + if (!PyArg_ParseTuple(args, "|O:peer_certificate", &binary_mode)) + return NULL; + + if (!self->peer_cert) + Py_RETURN_NONE; + + if (PyObject_IsTrue(binary_mode)) { + /* return cert in DER-encoded format */ + + unsigned char *bytes_buf = NULL; + + bytes_buf = NULL; + len = i2d_X509(self->peer_cert, &bytes_buf); + if (len < 0) { + PySSL_SetError(self, len, __FILE__, __LINE__); + return NULL; + } + retval = PyString_FromStringAndSize((const char *) bytes_buf, len); + OPENSSL_free(bytes_buf); + return retval; + + } else { + + verification = SSL_CTX_get_verify_mode(self->ctx); + if ((verification & SSL_VERIFY_PEER) == 0) + return PyDict_New(); + else + return _decode_certificate (self->peer_cert, 0); + } +} + +PyDoc_STRVAR(PySSL_peercert_doc, +"peer_certificate([der=False]) -> certificate\n\ +\n\ +Returns the certificate for the peer. If no certificate was provided,\n\ +returns None. If a certificate was provided, but not validated, returns\n\ +an empty dictionary. Otherwise returns a dict containing information\n\ +about the peer certificate.\n\ +\n\ +If the optional argument is True, returns a DER-encoded copy of the\n\ +peer certificate, or None if no certificate was provided. This will\n\ +return the certificate even if it wasn't validated."); + +static PyObject *PySSL_cipher (PySSLObject *self) { + + PyObject *retval, *v; + SSL_CIPHER *current; + char *cipher_name; + char *cipher_protocol; + + if (self->ssl == NULL) + return Py_None; + current = SSL_get_current_cipher(self->ssl); + if (current == NULL) + return Py_None; + + retval = PyTuple_New(3); + if (retval == NULL) + return NULL; + + cipher_name = (char *) SSL_CIPHER_get_name(current); + if (cipher_name == NULL) { + PyTuple_SET_ITEM(retval, 0, Py_None); + } else { + v = PyString_FromString(cipher_name); + if (v == NULL) + goto fail0; + PyTuple_SET_ITEM(retval, 0, v); + } + cipher_protocol = SSL_CIPHER_get_version(current); + if (cipher_protocol == NULL) { + PyTuple_SET_ITEM(retval, 1, Py_None); + } else { + v = PyString_FromString(cipher_protocol); + if (v == NULL) + goto fail0; + PyTuple_SET_ITEM(retval, 1, v); + } + v = PyInt_FromLong(SSL_CIPHER_get_bits(current, NULL)); + if (v == NULL) + goto fail0; + PyTuple_SET_ITEM(retval, 2, v); + return retval; + + fail0: + Py_DECREF(retval); + return NULL; +} + static void PySSL_dealloc(PySSLObject *self) { if (self->peer_cert) /* Possible not to have one? */ @@ -636,9 +1100,9 @@ check_socket_and_wait_for_timeout(PySocketSockObject *s, int writing) /* s->sock_timeout is in seconds, timeout in ms */ timeout = (int)(s->sock_timeout * 1000 + 0.5); - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS rc = poll(&pollfd, 1, timeout); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS goto normal_return; } @@ -657,12 +1121,12 @@ check_socket_and_wait_for_timeout(PySocketSockObject *s, int writing) FD_SET(s->sock_fd, &fds); /* See if the socket is ready */ - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS if (writing) rc = select(s->sock_fd+1, NULL, &fds, NULL, &tv); else rc = select(s->sock_fd+1, &fds, NULL, NULL, &tv); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS normal_return: /* Return SOCKET_TIMED_OUT on timeout, SOCKET_OPERATION_OK otherwise @@ -697,10 +1161,10 @@ static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args) } do { err = 0; - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS len = SSL_write(self->ssl, data, count); err = SSL_get_error(self->ssl, len); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if(PyErr_CheckSignals()) { return NULL; } @@ -752,9 +1216,9 @@ static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args) return NULL; /* first check if there are bytes ready to be read */ - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS count = SSL_pending(self->ssl); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if (!count) { sockstate = check_socket_and_wait_for_timeout(self->Socket, 0); @@ -785,10 +1249,10 @@ static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args) } do { err = 0; - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS count = SSL_read(self->ssl, PyString_AsString(buf), len); err = SSL_get_error(self->ssl, count); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if(PyErr_CheckSignals()) { Py_DECREF(buf); return NULL; @@ -831,7 +1295,7 @@ PyDoc_STRVAR(PySSL_SSLread_doc, \n\ Read up to len bytes from the SSL socket."); -static PyObject *PySSL_SSLshutdown(PySSLObject *self, PyObject *args) +static PyObject *PySSL_SSLshutdown(PySSLObject *self) { int err; @@ -842,13 +1306,13 @@ static PyObject *PySSL_SSLshutdown(PySSLObject *self, PyObject *args) return NULL; } - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS err = SSL_shutdown(self->ssl); if (err == 0) { /* we need to call it again to finish the shutdown */ err = SSL_shutdown(self->ssl); } - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if (err < 0) return PySSL_SetError(self, err, __FILE__, __LINE__); @@ -866,12 +1330,14 @@ the underlying socket object."); static PyMethodDef PySSLMethods[] = { {"write", (PyCFunction)PySSL_SSLwrite, METH_VARARGS, - PySSL_SSLwrite_doc}, + PySSL_SSLwrite_doc}, {"read", (PyCFunction)PySSL_SSLread, METH_VARARGS, - PySSL_SSLread_doc}, + PySSL_SSLread_doc}, {"server", (PyCFunction)PySSL_server, METH_NOARGS}, {"issuer", (PyCFunction)PySSL_issuer, METH_NOARGS}, - {"peer_certificate", (PyCFunction)PySSL_peercert, METH_NOARGS}, + {"peer_certificate", (PyCFunction)PySSL_peercert, METH_VARARGS, + PySSL_peercert_doc}, + {"cipher", (PyCFunction)PySSL_cipher, METH_NOARGS}, {"shutdown", (PyCFunction)PySSL_SSLshutdown, METH_NOARGS, PySSL_SSLshutdown_doc}, {NULL, NULL} @@ -921,7 +1387,7 @@ PyDoc_STRVAR(PySSL_RAND_add_doc, "RAND_add(string, entropy)\n\ \n\ Mix string into the OpenSSL PRNG state. entropy (a float) is a lower\n\ -bound on the entropy contained in string."); +bound on the entropy contained in string. See RFC 1750."); static PyObject * PySSL_RAND_status(PyObject *self) @@ -958,9 +1424,9 @@ PySSL_RAND_egd(PyObject *self, PyObject *arg) PyDoc_STRVAR(PySSL_RAND_egd_doc, "RAND_egd(path) -> bytes\n\ \n\ -Queries the entropy gather daemon (EGD) on socket path. Returns number\n\ -of bytes read. Raises ssl.sslerror if connection to EGD fails or\n\ -if it does provide enough data to seed PRNG."); +Queries the entropy gather daemon (EGD) on the socket named by 'path'.\n\ +Returns number of bytes read. Raises SSLError if connection to EGD\n\ +fails or if it does provide enough data to seed PRNG."); #endif @@ -969,6 +1435,8 @@ if it does provide enough data to seed PRNG."); static PyMethodDef PySSL_methods[] = { {"sslwrap", PySSL_sslwrap, METH_VARARGS, ssl_doc}, + {"_test_decode_cert", PySSL_test_decode_certificate, + METH_VARARGS}, #ifdef HAVE_OPENSSL_RAND {"RAND_add", PySSL_RAND_add, METH_VARARGS, PySSL_RAND_add_doc}, @@ -981,6 +1449,73 @@ static PyMethodDef PySSL_methods[] = { }; +#ifdef WITH_THREAD + +/* an implementation of OpenSSL threading operations in terms + of the Python C thread library */ + +static PyThread_type_lock *_ssl_locks = NULL; + +static unsigned long _ssl_thread_id_function (void) { + return PyThread_get_thread_ident(); +} + +static void _ssl_thread_locking_function (int mode, int n, const char *file, int line) { + /* this function is needed to perform locking on shared data + structures. (Note that OpenSSL uses a number of global data + structures that will be implicitly shared whenever multiple threads + use OpenSSL.) Multi-threaded applications will crash at random if + it is not set. + + locking_function() must be able to handle up to CRYPTO_num_locks() + different mutex locks. It sets the n-th lock if mode & CRYPTO_LOCK, and + releases it otherwise. + + file and line are the file number of the function setting the + lock. They can be useful for debugging. + */ + + if ((_ssl_locks == NULL) || + (n < 0) || (n >= _ssl_locks_count)) + return; + + if (mode & CRYPTO_LOCK) { + PyThread_acquire_lock(_ssl_locks[n], 1); + } else { + PyThread_release_lock(_ssl_locks[n]); + } +} + +static int _setup_ssl_threads(void) { + + int i; + + if (_ssl_locks == NULL) { + _ssl_locks_count = CRYPTO_num_locks(); + _ssl_locks = (PyThread_type_lock *) + malloc(sizeof(PyThread_type_lock) * _ssl_locks_count); + if (_ssl_locks == NULL) + return 0; + memset(_ssl_locks, 0, sizeof(PyThread_type_lock) * _ssl_locks_count); + for (i = 0; i < _ssl_locks_count; i++) { + _ssl_locks[i] = PyThread_allocate_lock(); + if (_ssl_locks[i] == NULL) { + int j; + for (j = 0; j < i; j++) { + PyThread_free_lock(_ssl_locks[j]); + } + free(_ssl_locks); + return 0; + } + } + CRYPTO_set_locking_callback(_ssl_thread_locking_function); + CRYPTO_set_id_callback(_ssl_thread_id_function); + } + return 1; +} + +#endif /* def HAVE_THREAD */ + PyDoc_STRVAR(module_doc, "Implementation module for SSL socket operations. See the socket module\n\ for documentation."); @@ -1003,15 +1538,21 @@ init_ssl(void) /* Init OpenSSL */ SSL_load_error_strings(); +#ifdef WITH_THREAD + /* note that this will start threading if not already started */ + if (!_setup_ssl_threads()) { + return; + } +#endif SSLeay_add_ssl_algorithms(); /* Add symbols to module dict */ - PySSLErrorObject = PyErr_NewException("ssl.sslerror", + PySSLErrorObject = PyErr_NewException("ssl.SSLError", PySocketModule.error, NULL); if (PySSLErrorObject == NULL) return; - if (PyDict_SetItemString(d, "sslerror", PySSLErrorObject) != 0) + if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0) return; if (PyDict_SetItemString(d, "SSLType", (PyObject *)&PySSL_Type) != 0)