[Python-checkins] cpython: Issue #19292: Add SSLContext.load_default_certs() to load default root CA

christian.heimes python-checkins at python.org
Sat Nov 23 13:57:08 CET 2013


http://hg.python.org/cpython/rev/dfd33140a2b5
changeset:   87406:dfd33140a2b5
user:        Christian Heimes <christian at cheimes.de>
date:        Sat Nov 23 13:56:58 2013 +0100
summary:
  Issue #19292: Add SSLContext.load_default_certs() to load default root CA
certificates from default stores or system stores. By default the method
loads CA certs for authentication of server certs.

files:
  Doc/library/ssl.rst  |  31 ++++++++++++++++++++++++++++++-
  Lib/ssl.py           |  28 ++++++++++++++++++++++++++++
  Lib/test/test_ssl.py |  32 ++++++++++++++++++++++++++++++++
  Misc/NEWS            |   4 ++++
  4 files changed, 94 insertions(+), 1 deletions(-)


diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -681,6 +681,20 @@
 
    .. versionadded:: 3.4
 
+.. data:: Purpose.SERVER_AUTH
+
+   Option for :meth:`SSLContext.load_default_certs` to load CA certificates
+   for TLS web server authentication (client side socket).
+
+   .. versionadded:: 3.4
+
+.. data:: Purpose.clientAuth
+
+   Option for :meth:`SSLContext.load_default_certs` to load CA certificates
+   for TLS web client authentication (server side socket).
+
+   .. versionadded:: 3.4
+
 
 SSL Sockets
 -----------
@@ -903,6 +917,22 @@
    .. versionchanged:: 3.3
       New optional argument *password*.
 
+.. method:: SSLContext.load_default_certs(purpose=Purpose.SERVER_AUTH)
+
+   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.SERVER_AUTH` 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 +961,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 --git a/Lib/ssl.py b/Lib/ssl.py
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -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,19 @@
         return super().__new__(cls, *_txt2obj(name, name=True))
 
 
+class Purpose(_ASN1Object, _Enum):
+    """SSLContext purpose flags with X509v3 Extended Key Usage objects
+    """
+    SERVER_AUTH = '1.3.6.1.5.5.7.3.1'
+    CLIENT_AUTH = '1.3.6.1.5.5.7.3.2'
+
+
 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 +343,25 @@
 
         self._set_npn_protocols(protos)
 
+    def _load_windows_store_certs(self, storename, purpose):
+        certs = bytearray()
+        for cert, encoding, trust in enum_certificates(storename):
+            # CA certs are never PKCS#7 encoded
+            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.SERVER_AUTH):
+        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 --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -611,6 +611,23 @@
         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.SERVER_AUTH, ssl._ASN1Object)
+        self.assertEqual(ssl.Purpose.SERVER_AUTH, val)
+        self.assertEqual(ssl.Purpose.SERVER_AUTH.nid, 129)
+        self.assertEqual(ssl.Purpose.SERVER_AUTH.shortname, 'serverAuth')
+        self.assertEqual(ssl.Purpose.SERVER_AUTH.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.CLIENT_AUTH, ssl._ASN1Object)
+        self.assertEqual(ssl.Purpose.CLIENT_AUTH, val)
+        self.assertEqual(ssl.Purpose.CLIENT_AUTH.nid, 130)
+        self.assertEqual(ssl.Purpose.CLIENT_AUTH.shortname, 'clientAuth')
+        self.assertEqual(ssl.Purpose.CLIENT_AUTH.oid,
+                              '1.3.6.1.5.5.7.3.2')
+
 
 class ContextTests(unittest.TestCase):
 
@@ -967,6 +984,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.SERVER_AUTH)
+        ctx.load_default_certs()
+
+        ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+        ctx.load_default_certs(ssl.Purpose.CLIENT_AUTH)
+
+        ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+        self.assertRaises(TypeError, ctx.load_default_certs, None)
+        self.assertRaises(TypeError, ctx.load_default_certs, 'SERVER_AUTH')
+
 
 class SSLErrorTests(unittest.TestCase):
 
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -68,6 +68,10 @@
 Library
 -------
 
+- Issue #19292: Add SSLContext.load_default_certs() to load default root CA
+  certificates from default stores or system stores. By default the method
+  loads CA certs for authentication of server certs.
+
 - Issue #19673: Add pathlib to the stdlib as a provisional module (PEP 428).
 
 - Issue #17916: Added dis.Bytecode.from_traceback() and

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list