[Python-checkins] r58097 - in python/trunk: Doc/library/hashlib.rst Doc/library/ssl.rst Lib/httplib.py Lib/imaplib.py Lib/poplib.py Lib/smtplib.py Lib/socket.py Lib/ssl.py Lib/test/badcert.pem Lib/test/badkey.pem Lib/test/nullcert.pem Lib/test/regrtest.py Lib/test/test_socket_ssl.py Lib/test/test_ssl.py Modules/_ssl.c

bill.janssen python-checkins at python.org
Mon Sep 10 23:51:03 CEST 2007


Author: bill.janssen
Date: Mon Sep 10 23:51:02 2007
New Revision: 58097

Added:
   python/trunk/Lib/test/badcert.pem   (contents, props changed)
   python/trunk/Lib/test/badkey.pem   (contents, props changed)
   python/trunk/Lib/test/nullcert.pem   (contents, props changed)
Modified:
   python/trunk/Doc/library/hashlib.rst
   python/trunk/Doc/library/ssl.rst
   python/trunk/Lib/httplib.py
   python/trunk/Lib/imaplib.py
   python/trunk/Lib/poplib.py
   python/trunk/Lib/smtplib.py
   python/trunk/Lib/socket.py
   python/trunk/Lib/ssl.py
   python/trunk/Lib/test/regrtest.py
   python/trunk/Lib/test/test_socket_ssl.py
   python/trunk/Lib/test/test_ssl.py
   python/trunk/Modules/_ssl.c
Log:
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.



Modified: python/trunk/Doc/library/hashlib.rst
==============================================================================
--- python/trunk/Doc/library/hashlib.rst	(original)
+++ python/trunk/Doc/library/hashlib.rst	Mon Sep 10 23:51:02 2007
@@ -32,7 +32,7 @@
 :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

Modified: python/trunk/Doc/library/ssl.rst
==============================================================================
--- python/trunk/Doc/library/ssl.rst	(original)
+++ python/trunk/Doc/library/ssl.rst	Mon Sep 10 23:51:02 2007
@@ -12,6 +12,10 @@
 .. sectionauthor::  Bill Janssen <bill.janssen at gmail.com>
 
 
+.. 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 @@
 .. 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 @@
      '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 @@
 
 .. _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 @@
 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 @@
 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 <http://www.cacert.org/index.php?id=3>`_,
+`Thawte <http://www.thawte.com/roots/>`_,
+`Verisign <http://www.verisign.com/support/roots.html>`_,
+`Equifax and GeoTrust <http://www.geotrust.com/resources/root_certificates/index.asp>`_.
+
+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.
 
-
-sslsocket Objects
------------------
-
-.. 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.
+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::
+
+  % 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 at myserver.mygroup.myorganization.com
+  %
+
+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 @@
    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 @@
    # 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 @@
    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 @@
       # 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 @@
 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 <http://old.pseudonym.org/ssl/wwwj-index.html>`_
+       Frederick J. Hirsch
 
-`Introducing SSL and Certificates using OpenSSL <http://old.pseudonym.org/ssl/wwwj-index.html>`_, by Frederick J. Hirsch
+   `RFC 1422: Privacy Enhancement for Internet Electronic Mail: Part II: Certificate-Based Key Management <http://www.ietf.org/rfc/rfc1422>`_
+       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 <http://www.ietf.org/rfc/rfc1750>`_
+       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 <http://www.ietf.org/rfc/rfc3280>`_
+       Housley et. al.

Modified: python/trunk/Lib/httplib.py
==============================================================================
--- python/trunk/Lib/httplib.py	(original)
+++ python/trunk/Lib/httplib.py	Mon Sep 10 23:51:02 2007
@@ -1051,7 +1051,7 @@
             "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 @@
 
     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
 

Modified: python/trunk/Lib/imaplib.py
==============================================================================
--- python/trunk/Lib/imaplib.py	(original)
+++ python/trunk/Lib/imaplib.py	Mon Sep 10 23:51:02 2007
@@ -1147,7 +1147,7 @@
             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 @@
         def ssl(self):
             """Return SSLObject instance used to communicate with the IMAP4 server.
 
-            ssl = ssl.sslsocket(<instance>.socket)
+            ssl = ssl.wrap_socket(<instance>.socket)
             """
             return self.sslobj
 

Modified: python/trunk/Lib/poplib.py
==============================================================================
--- python/trunk/Lib/poplib.py	(original)
+++ python/trunk/Lib/poplib.py	Mon Sep 10 23:51:02 2007
@@ -348,7 +348,7 @@
             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()
 

Modified: python/trunk/Lib/smtplib.py
==============================================================================
--- python/trunk/Lib/smtplib.py	(original)
+++ python/trunk/Lib/smtplib.py	Mon Sep 10 23:51:02 2007
@@ -587,7 +587,7 @@
         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 @@
         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")

Modified: python/trunk/Lib/socket.py
==============================================================================
--- python/trunk/Lib/socket.py	(original)
+++ python/trunk/Lib/socket.py	Mon Sep 10 23:51:02 2007
@@ -56,13 +56,13 @@
         # 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, \

Modified: python/trunk/Lib/ssl.py
==============================================================================
--- python/trunk/Lib/ssl.py	(original)
+++ python/trunk/Lib/ssl.py	Mon Sep 10 23:51:02 2007
@@ -6,11 +6,11 @@
 
 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 @@
 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 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 "<unknown>"
+
 
-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 @@
 
         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 @@
         # 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,10 +231,18 @@
         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
 

Added: python/trunk/Lib/test/badcert.pem
==============================================================================
--- (empty file)
+++ python/trunk/Lib/test/badcert.pem	Mon Sep 10 23:51:02 2007
@@ -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-----

Added: python/trunk/Lib/test/badkey.pem
==============================================================================
--- (empty file)
+++ python/trunk/Lib/test/badkey.pem	Mon Sep 10 23:51:02 2007
@@ -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-----

Added: python/trunk/Lib/test/nullcert.pem
==============================================================================

Modified: python/trunk/Lib/test/regrtest.py
==============================================================================
--- python/trunk/Lib/test/regrtest.py	(original)
+++ python/trunk/Lib/test/regrtest.py	Mon Sep 10 23:51:02 2007
@@ -1108,7 +1108,6 @@
 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 @@
             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')

Modified: python/trunk/Lib/test/test_socket_ssl.py
==============================================================================
--- python/trunk/Lib/test/test_socket_ssl.py	(original)
+++ python/trunk/Lib/test/test_socket_ssl.py	Mon Sep 10 23:51:02 2007
@@ -115,7 +115,7 @@
             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:

Modified: python/trunk/Lib/test/test_ssl.py
==============================================================================
--- python/trunk/Lib/test/test_ssl.py	(original)
+++ python/trunk/Lib/test/test_ssl.py	Mon Sep 10 23:51:02 2007
@@ -5,7 +5,6 @@
 from test import test_support
 import socket
 import errno
-import threading
 import subprocess
 import time
 import os
@@ -23,57 +22,21 @@
 
 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 @@
             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
-            threading.Thread.__init__(self)
-            self.setDaemon(True)
+            """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 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")
+            def __init__(self, server, connsock):
+                self.server = server
                 self.running = False
-                # normally, we'd just stop here, but for the test
-                # harness, we want to stop the server
-                self.server.stop()
-                return
+                self.sock = connsock
+                self.sock.setblocking(1)
+                self.sslconn = None
+                threading.Thread.__init__(self)
+                self.setDaemon(True)
 
-            while self.running:
+            def wrap_conn (self):
                 try:
-                    msg = sslconn.read()
-                    if not msg:
-                        # eof, so quit this handler
+                    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
-                        sslconn.close()
-                    elif msg.strip() == 'over':
-                        sslconn.close()
+                        # 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
-                    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()
+                        # 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(False)
+
+        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:
+                try:
+                    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 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 stop (self):
+            self.active = False
+            self.sock.close()
+
+    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")
-
-    def stop (self):
-        self.active = False
-        self.sock.close()
+                    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()
+
 
 CERTFILE_CONFIG_TEMPLATE = """
 # create RSA certs - Server
@@ -337,33 +674,21 @@
 
     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)
-
-    thread_info = test_support.threading_setup()
+    if _have_threads:
+        thread_info = test_support.threading_setup()
+        if CERTFILE and thread_info and test_support.is_resource_enabled('network'):
+            tests.append(ConnectedTests)
 
-    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.run_unittest(*tests)
 
-    test_support.threading_cleanup(*thread_info)
+    if _have_threads:
+        test_support.threading_cleanup(*thread_info)
 
 if __name__ == "__main__":
     test_main()

Modified: python/trunk/Modules/_ssl.c
==============================================================================
--- python/trunk/Modules/_ssl.c	(original)
+++ python/trunk/Modules/_ssl.c	Mon Sep 10 23:51:02 2007
@@ -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 @@
 #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 @@
 /* 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_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 @@
 
 	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 @@
 	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 @@
 	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 @@
 		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 @@
                                   "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 @@
 	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 @@
 		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 @@
 	}
 	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 @@
 }
 
 static PyObject *
+_create_tuple_for_attribute (ASN1_OBJECT *name, ASN1_STRING *value) {
+
+	char namebuf[X509_NAME_MAXLEN];
+	int buflen;
+	PyObject *name_obj;
+	PyObject *value_obj;
+	PyObject *attr;
+	unsigned char *valuebuf = NULL;
+
+	buflen = OBJ_obj2txt(namebuf, sizeof(namebuf), name, 0);
+	if (buflen < 0) {
+		_setSSLError(NULL, 0, __FILE__, __LINE__);
+		goto fail;
+	}
+	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;
+
+  fail:
+	return NULL;
+}
+
+static PyObject *
 _create_tuple_for_X509_NAME (X509_NAME *xname)
 {
-	PyObject *pt = NULL;
-        PyObject *entry_tuple = NULL;
+	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;
 
-        pt = PyTuple_New(entry_count);
-	if (pt == NULL)
+        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 < X509_NAME_entry_count(xname);
+	     index_counter < entry_count;
 	     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;
+		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);
-		buflen = ASN1_STRING_to_UTF8(&valuebuf, value);
-		if (buflen < 0) {
-			Py_DECREF(name_obj);
+		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;
-		}
-		value_obj = PyUnicode_DecodeUTF8((char *) valuebuf,
-						 buflen, "strict");
-		OPENSSL_free(valuebuf);
-		if (value_obj == NULL) {
-			Py_DECREF(name_obj);
+		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;
 		}
-                entry_tuple = PyTuple_New(2);
-                if (entry_tuple == NULL) {
-			Py_DECREF(name_obj);
-			Py_DECREF(value_obj);
-			goto fail0;
+		
+		/* 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);
 		}
-                PyTuple_SET_ITEM(entry_tuple, 0, name_obj);
-                PyTuple_SET_ITEM(entry_tuple, 1, value_obj);
-                PyTuple_SET_ITEM(pt, index_counter, entry_tuple);
 	}
-	return pt;
+	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);
+	}
 
-  fail0:
-	Py_XDECREF(pt);
 	return NULL;
 }
 
 static PyObject *
-PySSL_peercert(PySSLObject *self)
-{
+_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 @@
 	}
 	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 @@
 	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 @@
 
 		/* 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 @@
 	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 @@
 	}
 	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 @@
 		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 @@
 	}
 	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 @@
 \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 @@
 		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 @@
 
 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 @@
 "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 @@
 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 @@
 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 @@
 };
 
 
+#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 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)


More information about the Python-checkins mailing list