Add support for asyncore server-side SSL support. This requires
adding the 'makefile' method to ssl.SSLSocket, and importing the requisite fakefile class from socket.py, and making the appropriate changes to it to make it use the SSL connection. Added sample HTTPS server to test_ssl.py, and test that uses it. Change SSL tests to use https://svn.python.org/, instead of www.sf.net and pop.gmail.com. Added utility function to ssl module, get_server_certificate, to wrap up the several things to be done to pull a certificate from a remote server.
This commit is contained in:
parent
7e84c7f4b5
commit
296a59d3be
|
@ -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
|
||||
|
|
336
Lib/ssl.py
336
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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue