diff -r 40d4be2b7258 Doc/library/ssl.rst --- a/Doc/library/ssl.rst Sat Nov 23 11:24:32 2013 +0100 +++ b/Doc/library/ssl.rst Sat Nov 23 13:20:12 2013 +0100 @@ -681,6 +681,18 @@ .. versionadded:: 3.4 +.. data:: Purpose.serverAuth + + Flag for TLS web server authentication + + .. versionadded:: 3.4 + +.. data:: Purpose.clientAuth + + Flag for TLS web client authentication + + .. versionadded:: 3.4 + SSL Sockets ----------- @@ -903,6 +915,22 @@ .. versionchanged:: 3.3 New optional argument *password*. +.. method:: SSLContext.load_default_certs(purpose=Purpose.serverAuth) + + Load a set of default "certification authority" (CA) certificates from + default locations. On Windows it loads CA certs from the ``CA`` and + ``ROOT`` system stores. On other systems it calls + :meth:`SSLContext.set_default_verify_paths`. In the future the method may + load CA certificates from other locations, too. + + The *purpose* flag specifies what kind of CA certificates are loaded. The + default settings :data:`Purpose.serverAuth` loads certificates, that are + flagged and trusted for TLS web server authentication (client side + sockets). :data:`Purpose.clientAuth` loads CA certificates for client + certificate verification on the server side. + + .. versionadded:: 3.4 + .. method:: SSLContext.load_verify_locations(cafile=None, capath=None, cadata=None) Load a set of "certification authority" (CA) certificates used to validate @@ -931,7 +959,6 @@ .. versionchanged:: 3.4 New optional argument *cadata* - .. method:: SSLContext.get_ca_certs(binary_form=False) Get a list of loaded "certification authority" (CA) certificates. If the diff -r 40d4be2b7258 Lib/ssl.py --- a/Lib/ssl.py Sat Nov 23 11:24:32 2013 +0100 +++ b/Lib/ssl.py Sat Nov 23 13:20:12 2013 +0100 @@ -92,6 +92,7 @@ import sys import os from collections import namedtuple +from enum import Enum as _Enum import _ssl # if we can't import it, let the error propagate @@ -298,11 +299,29 @@ return super().__new__(cls, *_txt2obj(name, name=True)) +class Purpose(_ASN1Object, _Enum): + """SSLContext purpose flags with 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.""" __slots__ = ('protocol', '__weakref__') + windows_cert_stores = ("CA", "ROOT") def __new__(cls, protocol, *args, **kwargs): self = _SSLContext.__new__(cls, protocol) @@ -334,6 +353,24 @@ self._set_npn_protocols(protos) + def _load_windows_store_certs(self, storename, purpose): + certs = bytearray() + for cert, encoding, trust in enum_certificates(storename): + if encoding == "x509_asn": + if trust is True or purpose.oid in trust: + certs.extend(cert) + self.load_verify_locations(cadata=certs) + return certs + + def load_default_certs(self, purpose=Purpose.serverAuth): + if not isinstance(purpose, _ASN1Object): + raise TypeError(purpose) + if sys.platform == "win32": + for storename in self.windows_cert_stores: + self._load_windows_store_certs(storename, purpose) + else: + self.set_default_verify_paths() + class SSLSocket(socket): """This class implements a subtype of socket.socket that wraps diff -r 40d4be2b7258 Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py Sat Nov 23 11:24:32 2013 +0100 +++ b/Lib/test/test_ssl.py Sat Nov 23 13:20:12 2013 +0100 @@ -611,6 +611,26 @@ with self.assertRaisesRegex(ValueError, "unknown object 'serverauth'"): ssl._ASN1Object.fromname('serverauth') + def test_purpose_enum(self): + val = ssl._ASN1Object('1.3.6.1.5.5.7.3.1') + self.assertIsInstance(ssl.Purpose.serverAuth, ssl._ASN1Object) + self.assertEqual(ssl.Purpose.serverAuth, val) + self.assertEqual(ssl.Purpose.serverAuth.nid, 129) + self.assertEqual(ssl.Purpose.serverAuth.shortname, 'serverAuth') + self.assertEqual(ssl.Purpose.serverAuth.oid, + '1.3.6.1.5.5.7.3.1') + + val = ssl._ASN1Object('1.3.6.1.5.5.7.3.2') + self.assertIsInstance(ssl.Purpose.clientAuth, ssl._ASN1Object) + self.assertEqual(ssl.Purpose.clientAuth, val) + self.assertEqual(ssl.Purpose.clientAuth.nid, 130) + self.assertEqual(ssl.Purpose.clientAuth.shortname, 'clientAuth') + self.assertEqual(ssl.Purpose.clientAuth.oid, + '1.3.6.1.5.5.7.3.2') + + for name, obj in ssl.Purpose.__members__.items(): + self.assertEqual(name, obj.shortname) + class ContextTests(unittest.TestCase): @@ -967,6 +987,21 @@ der = ssl.PEM_cert_to_DER_cert(pem) self.assertEqual(ctx.get_ca_certs(True), [der]) + def test_load_default_certs(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + ctx.load_default_certs() + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + ctx.load_default_certs(ssl.Purpose.serverAuth) + ctx.load_default_certs() + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + ctx.load_default_certs(ssl.Purpose.clientAuth) + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + self.assertRaises(TypeError, ctx.load_default_certs, None) + self.assertRaises(TypeError, ctx.load_default_certs, 'serverAuth') + class SSLErrorTests(unittest.TestCase):