From bd3a7f90b51c6bd7d057bad9d7addd0bed8a6e7d Mon Sep 17 00:00:00 2001
From: Christian Heimes <christian@cheimes.de>
Date: Thu, 21 Nov 2013 03:40:15 +0100
Subject: Issue #18379: SSLSocket.getpeercert() returns CA issuer AIA fields,
 OCSP and CRL distribution points.

---
 Doc/library/ssl.rst  |   4 ++
 Lib/test/test_ssl.py |   8 ++-
 Misc/NEWS            |   3 +
 Modules/_ssl.c       | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 165 insertions(+), 2 deletions(-)

diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index 943a8f8..ce37c95 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -733,6 +733,10 @@ SSL sockets also have the following additional methods and attributes:
    .. versionchanged:: 3.4
       :exc:`ValueError` is raised when the handshake isn't done.
 
+   .. versionchanged:: 3.4
+      The returned dictionary includes additional X509v3 extension items
+      such as ``crlDistributionPoints``, ``caIssuers`` and ``OCSP`` URIs.
+
 .. method:: SSLSocket.cipher()
 
    Returns a three-value tuple containing the name of the cipher being used, the
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index b4eef36..8016728 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -212,6 +212,12 @@ class BasicSocketTests(unittest.TestCase):
                          (('DNS', 'projects.developer.nokia.com'),
                           ('DNS', 'projects.forum.nokia.com'))
                         )
+        # extra OCSP and AIA fields
+        self.assertEqual(p['OCSP'], ('http://ocsp.verisign.com',))
+        self.assertEqual(p['caIssuers'],
+                         ('http://SVRIntl-G3-aia.verisign.com/SVRIntlG3.cer',))
+        self.assertEqual(p['crlDistributionPoints'],
+                         ('http://SVRIntl-G3-crl.verisign.com/SVRIntlG3.crl',))
 
     def test_parse_cert_CVE_2013_4238(self):
         p = ssl._ssl._test_decode_cert(NULLBYTECERT)
@@ -905,6 +911,7 @@ class ContextTests(unittest.TestCase):
               'notAfter': asn1time('Mar 29 12:29:49 2033 GMT'),
               'notBefore': asn1time('Mar 30 12:29:49 2003 GMT'),
               'serialNumber': '00',
+              'crlDistributionPoints': ('https://www.cacert.org/revoke.crl',),
               'subject': ((('organizationName', 'Root CA'),),
                           (('organizationalUnitName', 'http://www.cacert.org'),),
                           (('commonName', 'CA Cert Signing Authority'),),
@@ -1269,7 +1276,6 @@ class NetworkedTests(unittest.TestCase):
                 s.close()
             self.assertEqual(len(ctx.get_ca_certs()), 1)
 
-
 try:
     import threading
 except ImportError:
diff --git a/Misc/NEWS b/Misc/NEWS
index a41a5d0..443802b 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -59,6 +59,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #18379: SSLSocket.getpeercert() returns CA issuer AIA fields, OCSP
+  and CRL distribution points.
+
 - Issue #18138: Implement cadata argument of SSLContext.load_verify_location()
   to load CA certificates and CRL from memory. It supports PEM and DER
   encoded strings.
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index c61c38b..8f3cc71 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -965,6 +965,120 @@ _get_peer_alt_names (X509 *certificate) {
 }
 
 static PyObject *
+_get_aia_uri(X509 *certificate, int nid) {
+    PyObject *lst = NULL, *ostr = NULL;
+    int i, result;
+    AUTHORITY_INFO_ACCESS *info;
+
+    info = X509_get_ext_d2i(certificate, NID_info_access, NULL, NULL);
+    if ((info == NULL) || (sk_ACCESS_DESCRIPTION_num(info) == 0)) {
+        return Py_None;
+    }
+
+    if ((lst = PyList_New(0)) == NULL) {
+        goto fail;
+    }
+
+    for (i = 0; i < sk_ACCESS_DESCRIPTION_num(info); i++) {
+        ACCESS_DESCRIPTION *ad = sk_ACCESS_DESCRIPTION_value(info, i);
+        ASN1_IA5STRING *uri;
+
+        if ((OBJ_obj2nid(ad->method) != nid) ||
+                (ad->location->type != GEN_URI)) {
+            continue;
+        }
+        uri = ad->location->d.uniformResourceIdentifier;
+        ostr = PyUnicode_FromStringAndSize((char *)uri->data,
+                                           uri->length);
+        if (ostr == NULL) {
+            goto fail;
+        }
+        result = PyList_Append(lst, ostr);
+        Py_DECREF(ostr);
+        if (result < 0) {
+            goto fail;
+        }
+    }
+    AUTHORITY_INFO_ACCESS_free(info);
+
+    /* convert to tuple or None */
+    if (PyList_Size(lst) == 0) {
+        Py_DECREF(lst);
+        return Py_None;
+    } else {
+        PyObject *tup;
+        tup = PyList_AsTuple(lst);
+        Py_DECREF(lst);
+        return tup;
+    }
+
+  fail:
+    AUTHORITY_INFO_ACCESS_free(info);
+    Py_DECREF(lst);
+    return NULL;
+}
+
+static PyObject *
+_get_crl_dp(X509 *certificate) {
+    STACK_OF(DIST_POINT) *dps;
+    int i, j, result;
+    PyObject *lst;
+
+    /* Calls x509v3_cache_extensions and sets up crldp */
+    X509_check_ca(certificate);
+    dps = certificate->crldp;
+    if (dps == NULL) {
+        return Py_None;
+    }
+
+    if ((lst = PyList_New(0)) == NULL) {
+        return NULL;
+    }
+
+    for (i=0; i < sk_DIST_POINT_num(dps); i++) {
+        DIST_POINT *dp;
+        STACK_OF(GENERAL_NAME) *gns;
+
+        dp = sk_DIST_POINT_value(dps, i);
+        gns = dp->distpoint->name.fullname;
+
+        for (j=0; j < sk_GENERAL_NAME_num(gns); j++) {
+            GENERAL_NAME *gn;
+            ASN1_IA5STRING *uri;
+            PyObject *ouri;
+
+            gn = sk_GENERAL_NAME_value(gns, j);
+            if (gn->type != GEN_URI) {
+                continue;
+            }
+            uri = gn->d.uniformResourceIdentifier;
+            ouri = PyUnicode_FromStringAndSize((char *)uri->data,
+                                               uri->length);
+            if (ouri == NULL) {
+                Py_DECREF(lst);
+                return NULL;
+            }
+            result = PyList_Append(lst, ouri);
+            Py_DECREF(ouri);
+            if (result < 0) {
+                Py_DECREF(lst);
+                return NULL;
+            }
+        }
+    }
+    /* convert to tuple or None */
+    if (PyList_Size(lst) == 0) {
+        Py_DECREF(lst);
+        return Py_None;
+    } else {
+        PyObject *tup;
+        tup = PyList_AsTuple(lst);
+        Py_DECREF(lst);
+        return tup;
+    }
+}
+
+static PyObject *
 _decode_certificate(X509 *certificate) {
 
     PyObject *retval = NULL;
@@ -974,9 +1088,10 @@ _decode_certificate(X509 *certificate) {
     PyObject *issuer;
     PyObject *version;
     PyObject *sn_obj;
+    PyObject *obj;
     ASN1_INTEGER *serialNumber;
     char buf[2048];
-    int len;
+    int len, result;
     ASN1_TIME *notBefore, *notAfter;
     PyObject *pnotBefore, *pnotAfter;
 
@@ -1082,6 +1197,41 @@ _decode_certificate(X509 *certificate) {
         Py_DECREF(peer_alt_names);
     }
 
+    /* Authority Information Access: OCSP URIs */
+    obj = _get_aia_uri(certificate, NID_ad_OCSP);
+    if (obj == NULL) {
+        goto fail1;
+    } else if (obj != Py_None) {
+        result = PyDict_SetItemString(retval, "OCSP", obj);
+        Py_DECREF(obj);
+        if (result < 0) {
+            goto fail1;
+        }
+    }
+
+    obj = _get_aia_uri(certificate, NID_ad_ca_issuers);
+    if (obj == NULL) {
+        goto fail1;
+    } else if (obj != Py_None) {
+        result = PyDict_SetItemString(retval, "caIssuers", obj);
+        Py_DECREF(obj);
+        if (result < 0) {
+            goto fail1;
+        }
+    }
+
+    /* CDP (CRL distribution points) */
+    obj = _get_crl_dp(certificate);
+    if (obj == NULL) {
+        goto fail1;
+    } else if (obj != Py_None) {
+        result = PyDict_SetItemString(retval, "crlDistributionPoints", obj);
+        Py_DECREF(obj);
+        if (result < 0) {
+            goto fail1;
+        }
+    }
+
     BIO_free(biobuf);
     return retval;
 
-- 
cgit v0.12

