diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -716,6 +716,10 @@ The returned dictionary includes additional items such as ``issuer`` and ``notBefore``. + .. 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/capath/5f267794.0 b/Lib/test/capath/5f267794.0 new file mode 100644 --- /dev/null +++ b/Lib/test/capath/5f267794.0 @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC +VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u +ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc +KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u +ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 +MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE +ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j +b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF +bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg +U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA +A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ +I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 +wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC +AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb +oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 +BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p +dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk +MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp +b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu +dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 +MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi +E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa +MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI +hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN +95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd +2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= +-----END CERTIFICATE----- diff --git a/Lib/test/capath/ed524cf5.0 b/Lib/test/capath/ed524cf5.0 new file mode 100644 --- /dev/null +++ b/Lib/test/capath/ed524cf5.0 @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC +VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u +ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc +KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u +ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 +MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE +ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j +b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF +bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg +U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA +A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ +I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 +wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC +AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb +oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 +BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p +dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk +MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp +b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu +dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 +MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi +E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa +MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI +hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN +95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd +2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= +-----END CERTIFICATE----- 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 @@ -1052,6 +1052,24 @@ s.close() self.assertEqual(len(ctx.get_ca_certs()), 1) + def test_cert_cdp_aia(self): + # capath certs are loaded on request + with support.transient_internet("pypi.python.org"): + ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv3) + ctx.verify_mode = ssl.CERT_REQUIRED + ctx.load_verify_locations(capath=CAPATH) + s = ctx.wrap_socket(socket.socket(socket.AF_INET)) + s.connect(("pypi.python.org", 443)) + try: + cert = s.getpeercert() + finally: + s.close() + self.assertEqual(cert['OCSP'], ('http://ocsp.digicert.com',)) + self.assertEqual(cert['caIssuers'], + ('http://cacerts.digicert.com/DigiCertHighAssuranceCA-3.crt',)) + self.assertEqual(cert['crlDistributionPoints'], + ('http://crl3.digicert.com/ca3-g22.crl', + 'http://crl4.digicert.com/ca3-g22.crl')) try: import threading diff --git a/Modules/_ssl.c b/Modules/_ssl.c --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -897,6 +897,120 @@ } 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; @@ -906,9 +1020,10 @@ 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; @@ -1012,6 +1127,41 @@ 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;