summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/ssl.rst39
-rw-r--r--Lib/ssl.py336
-rw-r--r--Lib/test/test_socket_ssl.py2
-rw-r--r--Lib/test/test_ssl.py374
-rw-r--r--Modules/_ssl.c53
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
diff --git a/Lib/ssl.py b/Lib/ssl.py
index b987ec3..9288144 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -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}
};