diff -r 7d914d4b05fe Lib/asyncio/selector_events.py --- a/Lib/asyncio/selector_events.py Fri Nov 22 16:20:53 2013 +0100 +++ b/Lib/asyncio/selector_events.py Fri Nov 22 18:04:20 2013 +0100 @@ -571,10 +571,8 @@ # context; in that case the sslcontext passed is None. # The default is the same as used by urllib with # cadefault=True. - sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - sslcontext.options |= ssl.OP_NO_SSLv2 - sslcontext.set_default_verify_paths() - sslcontext.verify_mode = ssl.CERT_REQUIRED + sslcontext = ssl.create_default_context( + cert_reqs=ssl.CERT_REQUIRED) wrap_kwargs = { 'server_side': server_side, diff -r 7d914d4b05fe Lib/ftplib.py --- a/Lib/ftplib.py Fri Nov 22 16:20:53 2013 +0100 +++ b/Lib/ftplib.py Fri Nov 22 18:04:20 2013 +0100 @@ -727,6 +727,10 @@ "exclusive") self.keyfile = keyfile self.certfile = certfile + if context is None: + context = ssl.create_default_context(self.ssl_version, + certfile=certfile, + keyfile=keyfile) self.context = context self._prot_p = False FTP.__init__(self, host, user, passwd, acct, timeout, source_address) @@ -744,12 +748,7 @@ resp = self.voidcmd('AUTH TLS') else: resp = self.voidcmd('AUTH SSL') - if self.context is not None: - self.sock = self.context.wrap_socket(self.sock) - else: - self.sock = ssl.wrap_socket(self.sock, self.keyfile, - self.certfile, - ssl_version=self.ssl_version) + self.sock = self.context.wrap_socket(self.sock) self.file = self.sock.makefile(mode='r', encoding=self.encoding) return resp @@ -788,11 +787,7 @@ def ntransfercmd(self, cmd, rest=None): conn, size = FTP.ntransfercmd(self, cmd, rest) if self._prot_p: - if self.context is not None: - conn = self.context.wrap_socket(conn) - else: - conn = ssl.wrap_socket(conn, self.keyfile, self.certfile, - ssl_version=self.ssl_version) + conn = self.context.wrap_socket(conn) return conn, size def abort(self): diff -r 7d914d4b05fe Lib/http/client.py --- a/Lib/http/client.py Fri Nov 22 16:20:53 2013 +0100 +++ b/Lib/http/client.py Fri Nov 22 18:04:20 2013 +0100 @@ -1179,17 +1179,16 @@ self.key_file = key_file self.cert_file = cert_file if context is None: - # Some reasonable defaults - context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - context.options |= ssl.OP_NO_SSLv2 + context = ssl.create_default_context(certfile=cert_file, + keyfile=key_file) + elif key_file or cert_file: + context.load_cert_chain(cert_file, key_file) will_verify = context.verify_mode != ssl.CERT_NONE if check_hostname is None: check_hostname = will_verify elif check_hostname and not will_verify: raise ValueError("check_hostname needs a SSL context with " "either CERT_OPTIONAL or CERT_REQUIRED") - if key_file or cert_file: - context.load_cert_chain(cert_file, key_file) self._context = context self._check_hostname = check_hostname diff -r 7d914d4b05fe Lib/imaplib.py --- a/Lib/imaplib.py Fri Nov 22 16:20:53 2013 +0100 +++ b/Lib/imaplib.py Fri Nov 22 18:04:20 2013 +0100 @@ -742,9 +742,7 @@ raise self.abort('TLS not supported by server') # Generate a default SSL context if none was passed. if ssl_context is None: - ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - # SSLv2 considered harmful. - ssl_context.options |= ssl.OP_NO_SSLv2 + ssl_context = ssl.create_default_context() typ, dat = self._simple_command(name) if typ == 'OK': self.sock = ssl_context.wrap_socket(self.sock) @@ -1210,15 +1208,15 @@ self.keyfile = keyfile self.certfile = certfile + if ssl_context is None: + ssl_context = ssl.create_default_context(certfile=certfile, + keyfile=keyfile) self.ssl_context = ssl_context IMAP4.__init__(self, host, port) def _create_socket(self): sock = IMAP4._create_socket(self) - if self.ssl_context: - return self.ssl_context.wrap_socket(sock) - else: - return ssl.wrap_socket(sock, self.keyfile, self.certfile) + return self.ssl_context.wrap_socket(sock) def open(self, host='', port=IMAP4_SSL_PORT): """Setup connection to remote server on "host:port". diff -r 7d914d4b05fe Lib/nntplib.py --- a/Lib/nntplib.py Fri Nov 22 16:20:53 2013 +0100 +++ b/Lib/nntplib.py Fri Nov 22 18:04:20 2013 +0100 @@ -288,9 +288,7 @@ """ # Generate a default SSL context if none was passed. if context is None: - context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - # SSLv2 considered harmful. - context.options |= ssl.OP_NO_SSLv2 + context = ssl.create_default_context() return context.wrap_socket(sock) diff -r 7d914d4b05fe Lib/poplib.py --- a/Lib/poplib.py Fri Nov 22 16:20:53 2013 +0100 +++ b/Lib/poplib.py Fri Nov 22 18:04:20 2013 +0100 @@ -385,8 +385,7 @@ if not 'STLS' in caps: raise error_proto('-ERR STLS not supported by server') if context is None: - context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - context.options |= ssl.OP_NO_SSLv2 + context = ssl.create_default_context() resp = self._shortcmd('STLS') self.sock = context.wrap_socket(self.sock) self.file = self.sock.makefile('rb') @@ -421,15 +420,15 @@ "exclusive") self.keyfile = keyfile self.certfile = certfile + if context is None: + context = ssl.create_default_context(certfile=certfile, + keyfile=keyfile) self.context = context POP3.__init__(self, host, port, timeout) def _create_socket(self, timeout): sock = POP3._create_socket(self, timeout) - if self.context is not None: - sock = self.context.wrap_socket(sock) - else: - sock = ssl.wrap_socket(sock, self.keyfile, self.certfile) + sock = self.context.wrap_socket(sock) return sock def stls(self, keyfile=None, certfile=None, context=None): diff -r 7d914d4b05fe Lib/smtplib.py --- a/Lib/smtplib.py Fri Nov 22 16:20:53 2013 +0100 +++ b/Lib/smtplib.py Fri Nov 22 18:04:20 2013 +0100 @@ -664,10 +664,10 @@ if context is not None and certfile is not None: raise ValueError("context and certfile arguments are mutually " "exclusive") - if context is not None: - self.sock = context.wrap_socket(self.sock) - else: - self.sock = ssl.wrap_socket(self.sock, keyfile, certfile) + if context is None: + context = ssl.create_default_context(certfile=certfile, + keyfile=keyfile) + self.sock = context.wrap_socket(self.sock) self.file = None # RFC 3207: # The client MUST discard any knowledge obtained from @@ -880,6 +880,9 @@ "exclusive") self.keyfile = keyfile self.certfile = certfile + if context is None: + context = ssl.create_default_context(certfile=self.certfile, + keyfile=self.keyfile) self.context = context SMTP.__init__(self, host, port, local_hostname, timeout, source_address) @@ -889,10 +892,7 @@ print('connect:', (host, port), file=stderr) new_socket = socket.create_connection((host, port), timeout, self.source_address) - if self.context is not None: - new_socket = self.context.wrap_socket(new_socket) - else: - new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile) + new_socket = self.context.wrap_socket(new_socket) return new_socket __all__.append("SMTP_SSL") diff -r 7d914d4b05fe Lib/ssl.py --- a/Lib/ssl.py Fri Nov 22 16:20:53 2013 +0100 +++ b/Lib/ssl.py Fri Nov 22 18:04:20 2013 +0100 @@ -92,6 +92,7 @@ import sys import os from collections import namedtuple +import enum as _enum import _ssl # if we can't import it, let the error propagate @@ -298,6 +299,23 @@ return super().__new__(cls, *_txt2obj(name, name=True)) +class PurposeEKU(_ASN1Object, _enum.Enum): + """X509v3 Extended Key Usage objects + """ + serverAuth = '1.3.6.1.5.5.7.3.1' + clientAuth = '1.3.6.1.5.5.7.3.2' + #codeSigning = '1.3.6.1.5.5.7.3.3' + #emailProtection = '1.3.6.1.5.5.7.3.4' + #ipsecEndSystem = '1.3.6.1.5.5.7.3.5' + #ipsecTunnel = '1.3.6.1.5.5.7.3.6' + #ipsecUser = '1.3.6.1.5.5.7.3.7' + #timeStamping = '1.3.6.1.5.5.7.3.8' + #OCSPSigning = '1.3.6.1.5.5.7.3.9' + #DVCS = '1.3.6.1.5.5.7.3.10' + #nsSGC = '2.16.840.1.113730.4.1' + #msSGC = '1.3.6.1.4.1.311.10.3.3' + + class SSLContext(_SSLContext): """An SSLContext holds various SSL-related configuration options and data, such as certificates and possibly a private key.""" @@ -334,6 +352,58 @@ self._set_npn_protocols(protos) + def load_default_certs(self, purpose): + if not isinstance(purpose, _ASN1Object): + raise TypeError(purpose) + self.set_default_verify_paths() + + +def create_default_context(protocol=PROTOCOL_SSLv23, *, cert_reqs=None, + purpose=PurposeEKU.serverAuth, + certfile=None, keyfile=None, + cafile=None, capath=None, cadata=None): + """Create a SSLContext object with default settings. + + NOTE: The protocol and settings may change anytime without prior + deprecation. The values represent a fair balance between maximum + compatibility and security. + """ + if not isinstance(purpose, _ASN1Object): + raise TypeError(purpose) + + context = SSLContext(protocol) + # SSLv2 considered harmful. + context.options |= OP_NO_SSLv2 + + if cert_reqs is None: + if purpose == PurposeEKU.serverAuth: + # authenticate a TLS web server (for client sockets). The default + # setting may change in the future. + cert_reqs = CERT_NONE + elif purpose == PurposeEKU.clientAuth: + # authenticate a TLS web client (for server sockets). The default + # setting is guaranteed to be stable and will never change. + cert_reqs = CERT_NONE + else: + # other (code signing, S/MIME, IPSEC, ...), default may change. + cert_reqs = CERT_NONE + context.verify_mode = cert_reqs + + # load cert file and key file + if certfile or keyfile: + context.load_cert_chain(certfile, keyfile) + + # load CA root certs + if cafile or capath or cadata: + context.load_verify_locations(cafile, capath, cadata) + elif cert_reqs != CERT_NONE and purpose is not None: + # no explicit cafile, capath or cadata but the verify mode is + # CERT_OPTIONAL or CERT_REQUIRED. Let's try to load default system + # root CA certificates for the given purpose. This may fail silently. + context.load_default_certs(purpose) + + return context + class SSLSocket(socket): """This class implements a subtype of socket.socket that wraps @@ -759,22 +829,22 @@ d = pem_cert_string.strip()[len(PEM_HEADER):-len(PEM_FOOTER)] return base64.decodebytes(d.encode('ASCII', 'strict')) -def get_server_certificate(addr, ssl_version=PROTOCOL_SSLv3, ca_certs=None): +def get_server_certificate(addr, ssl_version=PROTOCOL_SSLv3, ca_certs=None, + cert_reqs=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): + if ca_certs is not None: cert_reqs = CERT_REQUIRED - else: - cert_reqs = CERT_NONE - s = create_connection(addr) - s = wrap_socket(s, ssl_version=ssl_version, - cert_reqs=cert_reqs, ca_certs=ca_certs) - dercert = s.getpeercert(True) - s.close() + context = create_default_context(ssl_version, + cert_reqs=cert_reqs, + cafile=ca_certs) + with create_connection(addr) as sock: + with context.wrap_socket(sock) as sslsock: + dercert = sslsock.getpeercert(True) return DER_cert_to_PEM_cert(dercert) def get_protocol_name(protocol_code): diff -r 7d914d4b05fe Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py Fri Nov 22 16:20:53 2013 +0100 +++ b/Lib/test/test_ssl.py Fri Nov 22 18:04:20 2013 +0100 @@ -607,6 +607,29 @@ with self.assertRaisesRegex(ValueError, "unknown object 'serverauth'"): ssl._ASN1Object.fromname('serverauth') + def test_create_default_context(self): + ctx = ssl.create_default_context() + self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23) + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) + + ctx = ssl.create_default_context(ssl.PROTOCOL_TLSv1) + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1) + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) + + ctx = ssl.create_default_context(ssl.PROTOCOL_TLSv1_1, + cert_reqs=ssl.CERT_REQUIRED) + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1_1) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) + + ctx = ssl.create_default_context(certfile=CERTFILE) + ctx = ssl.create_default_context(certfile=ONLYCERT, + keyfile=ONLYKEY) + with self.assertRaises(FileNotFoundError): + ssl.create_default_context(certfile=WRONGCERT) + class ContextTests(unittest.TestCase): diff -r 7d914d4b05fe Lib/urllib/request.py --- a/Lib/urllib/request.py Fri Nov 22 16:20:53 2013 +0100 +++ b/Lib/urllib/request.py Fri Nov 22 18:04:20 2013 +0100 @@ -141,13 +141,9 @@ if cafile or capath or cadefault: if not _have_ssl: raise ValueError('SSL support not available') - context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - context.options |= ssl.OP_NO_SSLv2 - context.verify_mode = ssl.CERT_REQUIRED - if cafile or capath: - context.load_verify_locations(cafile, capath) - else: - context.set_default_verify_paths() + context = ssl.create_default_context(cert_reqs=ssl.CERT_REQUIRED, + cafile=cafile, + capath=capath) https_handler = HTTPSHandler(context=context, check_hostname=True) opener = build_opener(https_handler) elif _opener is None: