diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 57a17bc7980..39d1cfec96b 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -433,11 +433,9 @@ They also have the following additional methods and attributes: certificate was not validated, the dict is empty. If the certificate was validated, it returns a dict with the keys ``subject`` (the principal for which the certificate was issued), and ``notAfter`` (the time after which the - certificate should not be trusted). The certificate was already validated, - so the ``notBefore`` and ``issuer`` fields are not returned. If a - certificate contains an instance of the *Subject Alternative Name* extension - (see :rfc:`3280`), there will also be a ``subjectAltName`` key in the - dictionary. + certificate should not be trusted). If a certificate contains an instance + of the *Subject Alternative Name* extension (see :rfc:`3280`), there will + also be a ``subjectAltName`` key in the dictionary. The "subject" field is a tuple containing the sequence of relative distinguished names (RDNs) given in the certificate's data structure for the @@ -459,6 +457,10 @@ They also have the following additional methods and attributes: been validated, but if :const:`CERT_NONE` was used to establish the connection, the certificate, if present, will not have been validated. + .. versionchanged:: 3.2 + The returned dictionary includes additional items such as ``issuer`` + and ``notBefore``. + .. 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 bb9cebf76b4..df9b9872894 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -109,7 +109,7 @@ class BasicSocketTests(unittest.TestCase): # note that this uses an 'unofficial' function in _ssl.c, # provided solely for this test, to exercise the certificate # parsing code - p = ssl._ssl._test_decode_cert(CERTFILE, False) + p = ssl._ssl._test_decode_cert(CERTFILE) if support.verbose: sys.stdout.write("\n" + pprint.pformat(p) + "\n") @@ -1059,6 +1059,11 @@ else: self.fail( "Missing or invalid 'organizationName' field in certificate subject; " "should be 'Python Software Foundation'.") + self.assertIn('notBefore', cert) + self.assertIn('notAfter', cert) + before = ssl.cert_time_to_seconds(cert['notBefore']) + after = ssl.cert_time_to_seconds(cert['notAfter']) + self.assertLess(before, after) s.close() finally: server.stop() diff --git a/Misc/NEWS b/Misc/NEWS index 519c1899e9f..1faf2956d22 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -60,6 +60,9 @@ Core and Builtins Library ------- +- Issue #10022: The dictionary returned by the ``getpeercert()`` method + of SSL sockets now has additional items such as ``issuer`` and ``notBefore``. + - ``usenetrc`` is now false by default for NNTP objects. - Issue #1926: Add support for NNTP over SSL on port 563, as well as diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 6fa65b294de..78ea293b39a 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -700,7 +700,7 @@ _get_peer_alt_names (X509 *certificate) { } static PyObject * -_decode_certificate (X509 *certificate, int verbose) { +_decode_certificate(X509 *certificate) { PyObject *retval = NULL; BIO *biobuf = NULL; @@ -729,65 +729,60 @@ _decode_certificate (X509 *certificate, int verbose) { } Py_DECREF(peer); - if (verbose) { - issuer = _create_tuple_for_X509_NAME( - X509_get_issuer_name(certificate)); - if (issuer == NULL) - goto fail0; - if (PyDict_SetItemString(retval, (const char *)"issuer", issuer) < 0) { - Py_DECREF(issuer); - goto fail0; - } + issuer = _create_tuple_for_X509_NAME( + X509_get_issuer_name(certificate)); + if (issuer == NULL) + goto fail0; + if (PyDict_SetItemString(retval, (const char *)"issuer", issuer) < 0) { Py_DECREF(issuer); - - version = PyLong_FromLong(X509_get_version(certificate) + 1); - if (PyDict_SetItemString(retval, "version", version) < 0) { - Py_DECREF(version); - goto fail0; - } - Py_DECREF(version); + goto fail0; } + Py_DECREF(issuer); + + version = PyLong_FromLong(X509_get_version(certificate) + 1); + if (PyDict_SetItemString(retval, "version", version) < 0) { + Py_DECREF(version); + goto fail0; + } + Py_DECREF(version); /* get a memory buffer */ biobuf = BIO_new(BIO_s_mem()); - if (verbose) { - - (void) BIO_reset(biobuf); - serialNumber = X509_get_serialNumber(certificate); - /* should not exceed 20 octets, 160 bits, so buf is big enough */ - i2a_ASN1_INTEGER(biobuf, serialNumber); - len = BIO_gets(biobuf, buf, sizeof(buf)-1); - if (len < 0) { - _setSSLError(NULL, 0, __FILE__, __LINE__); - goto fail1; - } - sn_obj = PyUnicode_FromStringAndSize(buf, len); - if (sn_obj == NULL) - goto fail1; - if (PyDict_SetItemString(retval, "serialNumber", sn_obj) < 0) { - Py_DECREF(sn_obj); - goto fail1; - } - Py_DECREF(sn_obj); - - (void) BIO_reset(biobuf); - notBefore = X509_get_notBefore(certificate); - ASN1_TIME_print(biobuf, notBefore); - len = BIO_gets(biobuf, buf, sizeof(buf)-1); - if (len < 0) { - _setSSLError(NULL, 0, __FILE__, __LINE__); - goto fail1; - } - pnotBefore = PyUnicode_FromStringAndSize(buf, len); - if (pnotBefore == NULL) - goto fail1; - if (PyDict_SetItemString(retval, "notBefore", pnotBefore) < 0) { - Py_DECREF(pnotBefore); - goto fail1; - } - Py_DECREF(pnotBefore); + (void) BIO_reset(biobuf); + serialNumber = X509_get_serialNumber(certificate); + /* should not exceed 20 octets, 160 bits, so buf is big enough */ + i2a_ASN1_INTEGER(biobuf, serialNumber); + len = BIO_gets(biobuf, buf, sizeof(buf)-1); + if (len < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto fail1; } + sn_obj = PyUnicode_FromStringAndSize(buf, len); + if (sn_obj == NULL) + goto fail1; + if (PyDict_SetItemString(retval, "serialNumber", sn_obj) < 0) { + Py_DECREF(sn_obj); + goto fail1; + } + Py_DECREF(sn_obj); + + (void) BIO_reset(biobuf); + notBefore = X509_get_notBefore(certificate); + ASN1_TIME_print(biobuf, notBefore); + len = BIO_gets(biobuf, buf, sizeof(buf)-1); + if (len < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto fail1; + } + pnotBefore = PyUnicode_FromStringAndSize(buf, len); + if (pnotBefore == NULL) + goto fail1; + if (PyDict_SetItemString(retval, "notBefore", pnotBefore) < 0) { + Py_DECREF(pnotBefore); + goto fail1; + } + Py_DECREF(pnotBefore); (void) BIO_reset(biobuf); notAfter = X509_get_notAfter(certificate); @@ -839,10 +834,9 @@ PySSL_test_decode_certificate (PyObject *mod, PyObject *args) { PyObject *filename; X509 *x=NULL; BIO *cert; - int verbose = 1; - if (!PyArg_ParseTuple(args, "O&|i:test_decode_certificate", - PyUnicode_FSConverter, &filename, &verbose)) + if (!PyArg_ParseTuple(args, "O&:test_decode_certificate", + PyUnicode_FSConverter, &filename)) return NULL; if ((cert=BIO_new(BIO_s_file())) == NULL) { @@ -864,7 +858,7 @@ PySSL_test_decode_certificate (PyObject *mod, PyObject *args) { goto fail0; } - retval = _decode_certificate(x, verbose); + retval = _decode_certificate(x); X509_free(x); fail0: @@ -910,7 +904,7 @@ PySSL_peercert(PySSLSocket *self, PyObject *args) if ((verification & SSL_VERIFY_PEER) == 0) return PyDict_New(); else - return _decode_certificate (self->peer_cert, 0); + return _decode_certificate(self->peer_cert); } }