diff options
-rw-r--r-- | Doc/library/ssl.rst | 39 | ||||
-rw-r--r-- | Lib/ssl.py | 336 | ||||
-rw-r--r-- | Lib/test/test_socket_ssl.py | 2 | ||||
-rw-r--r-- | Lib/test/test_ssl.py | 374 | ||||
-rw-r--r-- | Modules/_ssl.c | 53 |
5 files changed, 616 insertions, 188 deletions
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 2ab6207..bb172cd 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -54,7 +54,7 @@ Functions, Constants, and Exceptions network connection. This error is a subtype of :exc:`socket.error`, which in turn is a subtype of :exc:`IOError`. -.. function:: wrap_socket (sock [, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None]) +.. 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. @@ -162,6 +162,28 @@ Functions, Constants, and Exceptions 'Wed May 9 00:00:00 2007' >>> +.. function:: get_server_certificate (addr, ssl_version=PROTOCOL_SSLv3, ca_certs=None) + + Given the address ``addr`` of an SSL-protected server, as a + (*hostname*, *port-number*) pair, fetches the server's certificate, + and returns it as a PEM-encoded string. If ``ssl_version`` is + specified, uses that version of the SSL protocol to attempt to + connect to the server. If ``ca_certs`` is specified, it should be + a file containing a list of root certificates, the same format as + used for the same parameter in :func:`wrap_socket`. The call will + attempt to validate the server certificate against that set of root + certificates, and will fail if the validation attempt fails. + +.. function:: DER_cert_to_PEM_cert (DER_cert_bytes) + + Given a certificate as a DER-encoded blob of bytes, returns a PEM-encoded + string version of the same certificate. + +.. function:: PEM_cert_to_DER_cert (PEM_cert_string) + + Given a certificate as an ASCII PEM string, returns a DER-encoded + sequence of bytes for that same certificate. + .. data:: CERT_NONE Value to pass to the ``cert_reqs`` parameter to :func:`sslobject` @@ -253,8 +275,12 @@ SSLSocket Objects If the ``binary_form`` parameter is :const:`True`, and a certificate was provided, this method returns the DER-encoded form - of the entire certificate as a sequence of bytes. Note that this - binary certificate may not be valid. + of the entire certificate as a sequence of bytes, or :const:`None` if the + peer did not provide a certificate. This return + value is independent of validation; if validation was required + (:const:`CERT_OPTIONAL` or :const:`CERT_REQUIRED`), it will have + been validated, but if :const:`CERT_NONE` was used to establish the + connection, the certificate, if present, will not have been validated. .. method:: SSLSocket.cipher() @@ -263,12 +289,6 @@ SSLSocket Objects 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 @@ -351,6 +371,7 @@ 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>`_, +`Positive SSL <http://www.PositiveSSL.com/ssl-certificate-support/cert_installation/UTN-USERFirst-Hardware.crt>`_ (used by python.org), `Equifax and GeoTrust <http://www.geotrust.com/resources/root_certificates/index.asp>`_. In general, if you are using @@ -55,7 +55,7 @@ PROTOCOL_SSLv23 PROTOCOL_TLSv1 """ -import os, sys +import os, sys, textwrap import _ssl # if we can't import it, let the error propagate @@ -76,19 +76,7 @@ from _ssl import \ from socket import socket from socket import getnameinfo as _getnameinfo - -def get_protocol_name (protocol_code): - if protocol_code == PROTOCOL_TLSv1: - return "TLSv1" - elif protocol_code == PROTOCOL_SSLv23: - return "SSLv23" - elif protocol_code == PROTOCOL_SSLv2: - return "SSLv2" - elif protocol_code == PROTOCOL_SSLv3: - return "SSLv3" - else: - return "<unknown>" - +import base64 # for DER-to-PEM translation class SSLSocket (socket): @@ -193,21 +181,12 @@ class SSLSocket (socket): else: return socket.recv_from(self, addr, buflen, flags) - def ssl_shutdown(self): - - """Shuts down the SSL channel over this socket (if active), - without closing the socket connection.""" - - if self._sslobj: - self._sslobj.shutdown() - self._sslobj = None - def shutdown(self, how): - self.ssl_shutdown() + self._sslobj = None socket.shutdown(self, how) def close(self): - self.ssl_shutdown() + self._sslobj = None socket.close(self) def connect(self, addr): @@ -236,6 +215,248 @@ class SSLSocket (socket): self.ca_certs), addr) + def makefile(self, mode='r', bufsize=-1): + + """Ouch. Need to make and return a file-like object that + works with the SSL connection.""" + + if self._sslobj: + return SSLFileStream(self._sslobj, mode, bufsize) + else: + return socket.makefile(self, mode, bufsize) + + +class SSLFileStream: + + """A class to simulate a file stream on top of a socket. + Most of this is just lifted from the socket module, and + adjusted to work with an SSL stream instead of a socket.""" + + + default_bufsize = 8192 + name = "<SSL stream>" + + __slots__ = ["mode", "bufsize", "softspace", + # "closed" is a property, see below + "_sslobj", "_rbufsize", "_wbufsize", "_rbuf", "_wbuf", + "_close", "_fileno"] + + def __init__(self, sslobj, mode='rb', bufsize=-1, close=False): + self._sslobj = sslobj + self.mode = mode # Not actually used in this version + if bufsize < 0: + bufsize = self.default_bufsize + self.bufsize = bufsize + self.softspace = False + if bufsize == 0: + self._rbufsize = 1 + elif bufsize == 1: + self._rbufsize = self.default_bufsize + else: + self._rbufsize = bufsize + self._wbufsize = bufsize + self._rbuf = "" # A string + self._wbuf = [] # A list of strings + self._close = close + self._fileno = -1 + + def _getclosed(self): + return self._sslobj is None + closed = property(_getclosed, doc="True if the file is closed") + + def fileno(self): + return self._fileno + + def close(self): + try: + if self._sslobj: + self.flush() + finally: + if self._close and self._sslobj: + self._sslobj.close() + self._sslobj = None + + def __del__(self): + try: + self.close() + except: + # close() may fail if __init__ didn't complete + pass + + def flush(self): + if self._wbuf: + buffer = "".join(self._wbuf) + self._wbuf = [] + count = 0 + while (count < len(buffer)): + written = self._sslobj.write(buffer) + count += written + buffer = buffer[written:] + + def write(self, data): + data = str(data) # XXX Should really reject non-string non-buffers + if not data: + return + self._wbuf.append(data) + if (self._wbufsize == 0 or + self._wbufsize == 1 and '\n' in data or + self._get_wbuf_len() >= self._wbufsize): + self.flush() + + def writelines(self, list): + # XXX We could do better here for very long lists + # XXX Should really reject non-string non-buffers + self._wbuf.extend(filter(None, map(str, list))) + if (self._wbufsize <= 1 or + self._get_wbuf_len() >= self._wbufsize): + self.flush() + + def _get_wbuf_len(self): + buf_len = 0 + for x in self._wbuf: + buf_len += len(x) + return buf_len + + def read(self, size=-1): + data = self._rbuf + if size < 0: + # Read until EOF + buffers = [] + if data: + buffers.append(data) + self._rbuf = "" + if self._rbufsize <= 1: + recv_size = self.default_bufsize + else: + recv_size = self._rbufsize + while True: + data = self._sslobj.read(recv_size) + if not data: + break + buffers.append(data) + return "".join(buffers) + else: + # Read until size bytes or EOF seen, whichever comes first + buf_len = len(data) + if buf_len >= size: + self._rbuf = data[size:] + return data[:size] + buffers = [] + if data: + buffers.append(data) + self._rbuf = "" + while True: + left = size - buf_len + recv_size = max(self._rbufsize, left) + data = self._sslobj.read(recv_size) + if not data: + break + buffers.append(data) + n = len(data) + if n >= left: + self._rbuf = data[left:] + buffers[-1] = data[:left] + break + buf_len += n + return "".join(buffers) + + def readline(self, size=-1): + data = self._rbuf + if size < 0: + # Read until \n or EOF, whichever comes first + if self._rbufsize <= 1: + # Speed up unbuffered case + assert data == "" + buffers = [] + while data != "\n": + data = self._sslobj.read(1) + if not data: + break + buffers.append(data) + return "".join(buffers) + nl = data.find('\n') + if nl >= 0: + nl += 1 + self._rbuf = data[nl:] + return data[:nl] + buffers = [] + if data: + buffers.append(data) + self._rbuf = "" + while True: + data = self._sslobj.read(self._rbufsize) + if not data: + break + buffers.append(data) + nl = data.find('\n') + if nl >= 0: + nl += 1 + self._rbuf = data[nl:] + buffers[-1] = data[:nl] + break + return "".join(buffers) + else: + # Read until size bytes or \n or EOF seen, whichever comes first + nl = data.find('\n', 0, size) + if nl >= 0: + nl += 1 + self._rbuf = data[nl:] + return data[:nl] + buf_len = len(data) + if buf_len >= size: + self._rbuf = data[size:] + return data[:size] + buffers = [] + if data: + buffers.append(data) + self._rbuf = "" + while True: + data = self._sslobj.read(self._rbufsize) + if not data: + break + buffers.append(data) + left = size - buf_len + nl = data.find('\n', 0, left) + if nl >= 0: + nl += 1 + self._rbuf = data[nl:] + buffers[-1] = data[:nl] + break + n = len(data) + if n >= left: + self._rbuf = data[left:] + buffers[-1] = data[:left] + break + buf_len += n + return "".join(buffers) + + def readlines(self, sizehint=0): + total = 0 + list = [] + while True: + line = self.readline() + if not line: + break + list.append(line) + total += len(line) + if sizehint and total >= sizehint: + break + return list + + # Iterator protocols + + def __iter__(self): + return self + + def next(self): + line = self.readline() + if not line: + raise StopIteration + return line + + + + def wrap_socket(sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version=PROTOCOL_SSLv23, ca_certs=None): @@ -255,6 +476,71 @@ def cert_time_to_seconds(cert_time): import time return time.mktime(time.strptime(cert_time, "%b %d %H:%M:%S %Y GMT")) +PEM_HEADER = "-----BEGIN CERTIFICATE-----" +PEM_FOOTER = "-----END CERTIFICATE-----" + +def DER_cert_to_PEM_cert(der_cert_bytes): + + """Takes a certificate in binary DER format and returns the + PEM version of it as a string.""" + + if hasattr(base64, 'standard_b64encode'): + # preferred because older API gets line-length wrong + f = base64.standard_b64encode(der_cert_bytes) + return (PEM_HEADER + '\n' + + textwrap.fill(f, 64) + + PEM_FOOTER + '\n') + else: + return (PEM_HEADER + '\n' + + base64.encodestring(der_cert_bytes) + + PEM_FOOTER + '\n') + +def PEM_cert_to_DER_cert(pem_cert_string): + + """Takes a certificate in ASCII PEM format and returns the + DER-encoded version of it as a byte sequence""" + + if not pem_cert_string.startswith(PEM_HEADER): + raise ValueError("Invalid PEM encoding; must start with %s" + % PEM_HEADER) + if not pem_cert_string.strip().endswith(PEM_FOOTER): + raise ValueError("Invalid PEM encoding; must end with %s" + % PEM_FOOTER) + d = pem_cert_string.strip()[len(PEM_HEADER):-len(PEM_FOOTER)] + return base64.decodestring(d) + +def get_server_certificate (addr, ssl_version=PROTOCOL_SSLv3, ca_certs=None): + + """Retrieve the certificate from the server at the specified address, + and return it as a PEM-encoded string. + If 'ca_certs' is specified, validate the server cert against it. + If 'ssl_version' is specified, use it in the connection attempt.""" + + host, port = addr + if (ca_certs is not None): + cert_reqs = CERT_REQUIRED + else: + cert_reqs = CERT_NONE + s = wrap_socket(socket(), ssl_version=ssl_version, + cert_reqs=cert_reqs, ca_certs=ca_certs) + s.connect(addr) + dercert = s.getpeercert(True) + s.close() + return DER_cert_to_PEM_cert(dercert) + +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>" + + # a replacement for the old socket.ssl function def sslwrap_simple (sock, keyfile=None, certfile=None): diff --git a/Lib/test/test_socket_ssl.py b/Lib/test/test_socket_ssl.py index d6a54bb..0d9dd7a 100644 --- a/Lib/test/test_socket_ssl.py +++ b/Lib/test/test_socket_ssl.py @@ -113,7 +113,7 @@ class BasicTests(unittest.TestCase): import os, httplib, ssl with test_support.transient_internet(): s = socket.socket(socket.AF_INET) - s.connect(("www.sf.net", 443)) + s.connect(("svn.python.org", 443)) fd = s._sock.fileno() sock = ssl.wrap_socket(s) s = None diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index d064790..5f6d6a4 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -9,10 +9,13 @@ import subprocess import time import os import pprint -import urllib +import urllib, urlparse import shutil import traceback +from BaseHTTPServer import HTTPServer +from SimpleHTTPServer import SimpleHTTPRequestHandler + # Optionally test SSL support, if we have it in the tested platform skip_expected = False try: @@ -21,6 +24,7 @@ except ImportError: skip_expected = True CERTFILE = None +SVN_PYTHON_ORG_ROOT_CERT = None TESTPORT = 10025 @@ -34,24 +38,24 @@ class BasicTests(unittest.TestCase): def testSSLconnect(self): import os - with test_support.transient_internet(): - 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: - raise test_support.TestFailed("Peer cert %s shouldn't be here!") + s = ssl.wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_NONE) + s.connect(("svn.python.org", 443)) + c = s.getpeercert() + if c: + raise test_support.TestFailed("Peer cert %s shouldn't be here!") + s.close() + + # this should fail because we have no verification certs + s = ssl.wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED) + try: + s.connect(("svn.python.org", 443)) + except ssl.SSLError: + pass + finally: s.close() - # this should fail because we have no verification certs - s = ssl.wrap_socket(socket.socket(socket.AF_INET), - cert_reqs=ssl.CERT_REQUIRED) - try: - s.connect(("pop.gmail.com", 995)) - except ssl.SSLError: - pass - finally: - s.close() def testCrucialConstants(self): ssl.PROTOCOL_SSLv2 @@ -84,6 +88,70 @@ class BasicTests(unittest.TestCase): if test_support.verbose: sys.stdout.write("\n" + pprint.pformat(p) + "\n") + def testDERtoPEM(self): + + pem = open(SVN_PYTHON_ORG_ROOT_CERT, 'r').read() + d1 = ssl.PEM_cert_to_DER_cert(pem) + p2 = ssl.DER_cert_to_PEM_cert(d1) + d2 = ssl.PEM_cert_to_DER_cert(p2) + if (d1 != d2): + raise test_support.TestFailed("PEM-to-DER or DER-to-PEM translation failed") + + +class NetworkTests(unittest.TestCase): + + def testConnect(self): + import os + s = ssl.wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_NONE) + s.connect(("svn.python.org", 443)) + c = s.getpeercert() + if c: + raise test_support.TestFailed("Peer cert %s shouldn't be here!") + s.close() + + # this should fail because we have no verification certs + s = ssl.wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED) + try: + s.connect(("svn.python.org", 443)) + except ssl.SSLError: + pass + finally: + s.close() + + # this should succeed because we specify the root cert + s = ssl.wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED, + ca_certs=SVN_PYTHON_ORG_ROOT_CERT) + try: + s.connect(("svn.python.org", 443)) + except ssl.SSLError, x: + raise test_support.TestFailed("Unexpected exception %s" % x) + finally: + s.close() + + def testFetchServerCert(self): + + pem = ssl.get_server_certificate(("svn.python.org", 443)) + if not pem: + raise test_support.TestFailed("No server certificate on svn.python.org:443!") + + try: + pem = ssl.get_server_certificate(("svn.python.org", 443), ca_certs=CERTFILE) + except ssl.SSLError: + #should fail + pass + else: + raise test_support.TestFailed("Got server certificate %s for svn.python.org!" % pem) + + pem = ssl.get_server_certificate(("svn.python.org", 443), ca_certs=SVN_PYTHON_ORG_ROOT_CERT) + if not pem: + raise test_support.TestFailed("No server certificate on svn.python.org:443!") + if test_support.verbose: + sys.stdout.write("\nVerified certificate for svn.python.org:443 is\n%s\n" % pem) + + try: import threading except ImportError: @@ -258,6 +326,133 @@ else: self.active = False self.sock.close() + + class AsyncoreHTTPSServer(threading.Thread): + + class HTTPSServer(HTTPServer): + + def __init__(self, server_address, RequestHandlerClass, certfile): + + HTTPServer.__init__(self, server_address, RequestHandlerClass) + # we assume the certfile contains both private key and certificate + self.certfile = certfile + self.active = False + self.allow_reuse_address = True + + def get_request (self): + # override this to wrap socket with SSL + sock, addr = self.socket.accept() + sslconn = ssl.wrap_socket(sock, server_side=True, + certfile=self.certfile) + return sslconn, addr + + # The methods overridden below this are mainly so that we + # can run it in a thread and be able to stop it from another + # You probably wouldn't need them in other uses. + + def server_activate(self): + # We want to run this in a thread for testing purposes, + # so we override this to set timeout, so that we get + # a chance to stop the server + self.socket.settimeout(0.5) + HTTPServer.server_activate(self) + + def serve_forever(self): + # We want this to run in a thread, so we use a slightly + # modified version of "forever". + self.active = True + while self.active: + try: + self.handle_request() + except socket.timeout: + pass + except KeyboardInterrupt: + self.server_close() + return + except: + sys.stdout.write(''.join(traceback.format_exception(*sys.exc_info()))); + + def server_close(self): + # Again, we want this to run in a thread, so we need to override + # close to clear the "active" flag, so that serve_forever() will + # terminate. + HTTPServer.server_close(self) + self.active = False + + class RootedHTTPRequestHandler(SimpleHTTPRequestHandler): + + # need to override translate_path to get a known root, + # instead of using os.curdir, since the test could be + # run from anywhere + + server_version = "TestHTTPS/1.0" + + root = None + + def translate_path(self, path): + """Translate a /-separated PATH to the local filename syntax. + + Components that mean special things to the local file system + (e.g. drive or directory names) are ignored. (XXX They should + probably be diagnosed.) + + """ + # abandon query parameters + path = urlparse.urlparse(path)[2] + path = os.path.normpath(urllib.unquote(path)) + words = path.split('/') + words = filter(None, words) + path = self.root + for word in words: + drive, word = os.path.splitdrive(word) + head, word = os.path.split(word) + if word in self.root: continue + path = os.path.join(path, word) + return path + + def log_message(self, format, *args): + + # we override this to suppress logging unless "verbose" + + if test_support.verbose: + sys.stdout.write(" server (%s, %d, %s):\n [%s] %s\n" % + (self.server.server_name, + self.server.server_port, + self.request.cipher(), + self.log_date_time_string(), + format%args)) + + + def __init__(self, port, certfile): + self.flag = None + self.active = False + self.RootedHTTPRequestHandler.root = os.path.split(CERTFILE)[0] + self.server = self.HTTPSServer( + ('', port), self.RootedHTTPRequestHandler, certfile) + threading.Thread.__init__(self) + self.setDaemon(True) + + def __str__(self): + return '<%s %s:%d>' % (self.__class__.__name__, + self.server.server_name, + self.server.server_port) + + def start (self, flag=None): + self.flag = flag + threading.Thread.start(self) + + def run (self): + self.active = True + if self.flag: + self.flag.set() + self.server.serve_forever() + self.active = False + + def stop (self): + self.active = False + self.server.server_close() + + def badCertTest (certfile): server = ThreadedEchoServer(TESTPORT, CERTFILE, certreqs=ssl.CERT_REQUIRED, @@ -331,7 +526,6 @@ else: if connectionchatty: if test_support.verbose: sys.stdout.write(" client: closing connection.\n") - s.ssl_shutdown() s.close() finally: server.stop() @@ -388,7 +582,7 @@ else: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if hasattr(socket, 'SO_REUSEPORT'): s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - port = test_support.bind_port(s, '127.0.0.1', TESTPORT) + s.bind(('127.0.0.1', TESTPORT)) s.listen(5) listener_ready.set() s.accept() @@ -566,27 +760,31 @@ else: sys.stdout.write("\n") for indata in msgs: if test_support.verbose: - sys.stdout.write(" client: sending %s...\n" % repr(indata)) + 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 (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)) + 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)) + 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() @@ -594,83 +792,43 @@ else: server.stop() server.join() + def testAsyncore(self): -CERTFILE_CONFIG_TEMPLATE = """ -# create RSA certs - Server - -[ req ] -default_bits = 1024 -encrypt_key = yes -distinguished_name = req_dn -x509_extensions = cert_type - -[ req_dn ] -countryName = Country Name (2 letter code) -countryName_default = US -countryName_min = 2 -countryName_max = 2 - -stateOrProvinceName = State or Province Name (full name) -stateOrProvinceName_default = %(state)s - -localityName = Locality Name (eg, city) -localityName_default = %(city)s - -0.organizationName = Organization Name (eg, company) -0.organizationName_default = %(organization)s - -organizationalUnitName = Organizational Unit Name (eg, section) -organizationalUnitName_default = %(unit)s - -0.commonName = Common Name (FQDN of your server) -0.commonName_default = %(common-name)s - -# To create a certificate for more than one name uncomment: -# 1.commonName = DNS alias of your server -# 2.commonName = DNS alias of your server -# ... -# See http://home.netscape.com/eng/security/ssl_2.0_certificate.html -# to see how Netscape understands commonName. - -[ cert_type ] -nsCertType = server -""" - -def create_cert_files(hostname=None): - - """This is the routine that was run to create the certificate - and private key contained in keycert.pem.""" - - import tempfile, socket, os - d = tempfile.mkdtemp() - # now create a configuration file for the CA signing cert - fqdn = hostname or socket.getfqdn() - crtfile = os.path.join(d, "cert.pem") - conffile = os.path.join(d, "ca.conf") - fp = open(conffile, "w") - fp.write(CERTFILE_CONFIG_TEMPLATE % - {'state': "Delaware", - 'city': "Wilmington", - 'organization': "Python Software Foundation", - 'unit': "SSL", - 'common-name': fqdn, - }) - fp.close() - error = os.system( - "openssl req -batch -new -x509 -days 2000 -nodes -config %s " - "-keyout \"%s\" -out \"%s\" > /dev/null < /dev/null 2>&1" % - (conffile, crtfile, crtfile)) - # now we have a self-signed server cert in crtfile - os.unlink(conffile) - if (os.WEXITSTATUS(error) or - not os.path.exists(crtfile) or os.path.getsize(crtfile) == 0): - if test_support.verbose: - sys.stdout.write("Unable to create certificate for test, " - + "error status %d\n" % (error >> 8)) - crtfile = None - elif test_support.verbose: - sys.stdout.write(open(crtfile, 'r').read() + '\n') - return d, crtfile + server = AsyncoreHTTPSServer(TESTPORT, CERTFILE) + flag = threading.Event() + server.start(flag) + # wait for it to start + flag.wait() + # try to connect + try: + if test_support.verbose: + sys.stdout.write('\n') + d1 = open(CERTFILE, 'r').read() + d2 = '' + # now fetch the same data from the HTTPS server + url = 'https://127.0.0.1:%d/%s' % ( + TESTPORT, os.path.split(CERTFILE)[1]) + f = urllib.urlopen(url) + dlen = f.info().getheader("content-length") + if dlen and (int(dlen) > 0): + d2 = f.read(int(dlen)) + if test_support.verbose: + sys.stdout.write( + " client: read %d bytes from remote server '%s'\n" + % (len(d2), server)) + f.close() + except: + msg = ''.join(traceback.format_exception(*sys.exc_info())) + if test_support.verbose: + sys.stdout.write('\n' + msg) + raise test_support.TestFailed(msg) + else: + if not (d1 == d2): + raise test_support.TestFailed( + "Couldn't fetch data from HTTPS server") + finally: + server.stop() + server.join() def findtestsocket(start, end): @@ -695,10 +853,15 @@ def test_main(verbose=False): if skip_expected: raise test_support.TestSkipped("No SSL support") - global CERTFILE, TESTPORT + global CERTFILE, TESTPORT, SVN_PYTHON_ORG_ROOT_CERT CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir, - "keycert.pem") - if (not os.path.exists(CERTFILE)): + "keycert.pem") + SVN_PYTHON_ORG_ROOT_CERT = os.path.join( + os.path.dirname(__file__) or os.curdir, + "https_svn_python_org_root.pem") + + if (not os.path.exists(CERTFILE) or + not os.path.exists(SVN_PYTHON_ORG_ROOT_CERT)): raise test_support.TestFailed("Can't read certificate files!") TESTPORT = findtestsocket(10025, 12000) if not TESTPORT: @@ -706,9 +869,12 @@ def test_main(verbose=False): tests = [BasicTests] + if test_support.is_resource_enabled('network'): + tests.append(NetworkTests) + if _have_threads: thread_info = test_support.threading_setup() - if CERTFILE and thread_info and test_support.is_resource_enabled('network'): + if thread_info and test_support.is_resource_enabled('network'): tests.append(ConnectedTests) test_support.run_unittest(*tests) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 45a5490..d3b0aae 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -126,7 +126,6 @@ static int check_socket_and_wait_for_timeout(PySocketSockObject *s, int writing); 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) @@ -661,7 +660,7 @@ _get_peer_alt_names (X509 *certificate) { char buf[2048]; char *vptr; int len; - const unsigned char *p; + unsigned char *p; if (certificate == NULL) return peer_alt_names; @@ -1233,18 +1232,9 @@ static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args) Py_DECREF(buf); return NULL; } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) { - if (SSL_get_shutdown(self->ssl) != - SSL_RECEIVED_SHUTDOWN) - { - Py_DECREF(buf); - PyErr_SetString(PySSLErrorObject, - "Socket closed without SSL shutdown handshake"); - return NULL; - } else { - /* should contain a zero-length string */ - _PyString_Resize(&buf, 0); - return buf; - } + /* should contain a zero-length string */ + _PyString_Resize(&buf, 0); + return buf; } } do { @@ -1295,39 +1285,6 @@ PyDoc_STRVAR(PySSL_SSLread_doc, \n\ Read up to len bytes from the SSL socket."); -static PyObject *PySSL_SSLshutdown(PySSLObject *self) -{ - int err; - - /* Guard against closed socket */ - if (self->Socket->sock_fd < 0) { - PyErr_SetString(PySSLErrorObject, - "Underlying socket has been closed."); - return NULL; - } - - 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); - } - PySSL_END_ALLOW_THREADS - - if (err < 0) - return PySSL_SetError(self, err, __FILE__, __LINE__); - else { - Py_INCREF(self->Socket); - return (PyObject *) (self->Socket); - } -} - -PyDoc_STRVAR(PySSL_SSLshutdown_doc, -"shutdown(s) -> socket\n\ -\n\ -Does the SSL shutdown handshake with the remote end, and returns\n\ -the underlying socket object."); - static PyMethodDef PySSLMethods[] = { {"write", (PyCFunction)PySSL_SSLwrite, METH_VARARGS, PySSL_SSLwrite_doc}, @@ -1338,8 +1295,6 @@ static PyMethodDef PySSLMethods[] = { {"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} }; |