diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 398169bb561..3816aca8288 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -359,6 +359,20 @@ Certificate handling .. versionadded:: 3.4 +.. function:: enum_cert_store(store_name, cert_type='certificate') + + Retrieve certificates from Windows' system cert store. *store_name* may be + one of ``CA``, ``ROOT`` or ``MY``. Windows may provide additional cert + stores, too. *cert_type* is either ``certificate`` for X.509 certificates + or ``crl`` for X.509 certificate revocation lists. + + The function returns a list of (bytes, encoding_type) tuples. The + encoding_type flag can be interpreted with :const:`X509_ASN_ENCODING` or + :const:`PKCS_7_ASN_ENCODING`. + + Availability: Windows. + + .. versionadded:: 3.4 Constants ^^^^^^^^^ @@ -598,6 +612,15 @@ Constants .. versionadded:: 3.4 +.. data:: X509_ASN_ENCODING + PKCS_7_ASN_ENCODING + + Encoding flags for :func:`enum_cert_store`. + + Availability: Windows. + + .. versionadded:: 3.4 + SSL Sockets ----------- diff --git a/Lib/ssl.py b/Lib/ssl.py index 5d48f1be626..d78d96d160e 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -89,6 +89,7 @@ ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY import textwrap import re +import sys import os import collections @@ -139,6 +140,9 @@ else: _PROTOCOL_NAMES[PROTOCOL_TLSv1_1] = "TLSv1.1" _PROTOCOL_NAMES[PROTOCOL_TLSv1_2] = "TLSv1.2" +if sys.platform == "win32": + from _ssl import enum_cert_store, X509_ASN_ENCODING, PKCS_7_ASN_ENCODING + from socket import getnameinfo as _getnameinfo from socket import socket, AF_INET, SOCK_STREAM, create_connection import base64 # for DER-to-PEM translation diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 6cecc17f2fd..08a07bab80f 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -407,6 +407,29 @@ class BasicSocketTests(unittest.TestCase): self.assertEqual(paths.capath, CAPATH) + @unittest.skipUnless(sys.platform == "win32", "Windows specific") + def test_enum_cert_store(self): + self.assertEqual(ssl.X509_ASN_ENCODING, 1) + self.assertEqual(ssl.PKCS_7_ASN_ENCODING, 0x00010000) + + self.assertEqual(ssl.enum_cert_store("CA"), + ssl.enum_cert_store("CA", "certificate")) + ssl.enum_cert_store("CA", "crl") + self.assertEqual(ssl.enum_cert_store("ROOT"), + ssl.enum_cert_store("ROOT", "certificate")) + ssl.enum_cert_store("ROOT", "crl") + + self.assertRaises(TypeError, ssl.enum_cert_store) + self.assertRaises(WindowsError, ssl.enum_cert_store, "") + self.assertRaises(ValueError, ssl.enum_cert_store, "CA", "wrong") + + ca = ssl.enum_cert_store("CA") + self.assertIsInstance(ca, list) + self.assertIsInstance(ca[0], tuple) + self.assertEqual(len(ca[0]), 2) + self.assertIsInstance(ca[0][0], bytes) + self.assertIsInstance(ca[0][1], int) + class ContextTests(unittest.TestCase): @skip_if_broken_ubuntu_ssl diff --git a/Misc/NEWS b/Misc/NEWS index fd54ea64405..bada8783950 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -115,6 +115,8 @@ Core and Builtins Library ------- +- Issue #17134: Add ssl.enum_cert_store() as interface to Windows' cert store. + - Issue #18143: Implement ssl.get_default_verify_paths() in order to debug the default locations for cafile and capath. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 999dd095332..5959f18ab02 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -2801,7 +2801,129 @@ get_default_verify_paths(PyObject *self) return NULL; } +#ifdef _MSC_VER +PyDoc_STRVAR(PySSL_enum_cert_store_doc, +"enum_cert_store(store_name, cert_type='certificate') -> []\n\ +\n\ +Retrieve certificates from Windows' cert store. store_name may be one of\n\ +'CA', 'ROOT' or 'MY'. The system may provide more cert storages, too.\n\ +cert_type must be either 'certificate' or 'crl'.\n\ +The function returns a list of (bytes, encoding_type) tuples. The\n\ +encoding_type flag can be interpreted with X509_ASN_ENCODING or\n\ +PKCS_7_ASN_ENCODING."); +static PyObject * +PySSL_enum_cert_store(PyObject *self, PyObject *args, PyObject *kwds) +{ + char *kwlist[] = {"store_name", "cert_type", NULL}; + char *store_name; + char *cert_type = "certificate"; + HCERTSTORE hStore = NULL; + PyObject *result = NULL; + PyObject *tup = NULL, *cert = NULL, *enc = NULL; + int ok = 1; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s:enum_cert_store", + kwlist, &store_name, &cert_type)) { + return NULL; + } + + if ((strcmp(cert_type, "certificate") != 0) && + (strcmp(cert_type, "crl") != 0)) { + return PyErr_Format(PyExc_ValueError, + "cert_type must be 'certificate' or 'crl', " + "not %.100s", cert_type); + } + + if ((result = PyList_New(0)) == NULL) { + return NULL; + } + + if ((hStore = CertOpenSystemStore(NULL, store_name)) == NULL) { + Py_DECREF(result); + return PyErr_SetFromWindowsErr(GetLastError()); + } + + if (strcmp(cert_type, "certificate") == 0) { + PCCERT_CONTEXT pCertCtx = NULL; + while (pCertCtx = CertEnumCertificatesInStore(hStore, pCertCtx)) { + cert = PyBytes_FromStringAndSize((const char*)pCertCtx->pbCertEncoded, + pCertCtx->cbCertEncoded); + if (!cert) { + ok = 0; + break; + } + if ((enc = PyLong_FromLong(pCertCtx->dwCertEncodingType)) == NULL) { + ok = 0; + break; + } + if ((tup = PyTuple_New(2)) == NULL) { + ok = 0; + break; + } + PyTuple_SET_ITEM(tup, 0, cert); cert = NULL; + PyTuple_SET_ITEM(tup, 1, enc); enc = NULL; + + if (PyList_Append(result, tup) < 0) { + ok = 0; + break; + } + Py_CLEAR(tup); + } + if (pCertCtx) { + /* loop ended with an error, need to clean up context manually */ + CertFreeCertificateContext(pCertCtx); + } + } else { + PCCRL_CONTEXT pCrlCtx = NULL; + while (pCrlCtx = CertEnumCRLsInStore(hStore, pCrlCtx)) { + cert = PyBytes_FromStringAndSize((const char*)pCrlCtx->pbCrlEncoded, + pCrlCtx->cbCrlEncoded); + if (!cert) { + ok = 0; + break; + } + if ((enc = PyLong_FromLong(pCrlCtx->dwCertEncodingType)) == NULL) { + ok = 0; + break; + } + if ((tup = PyTuple_New(2)) == NULL) { + ok = 0; + break; + } + PyTuple_SET_ITEM(tup, 0, cert); cert = NULL; + PyTuple_SET_ITEM(tup, 1, enc); enc = NULL; + + if (PyList_Append(result, tup) < 0) { + ok = 0; + break; + } + Py_CLEAR(tup); + } + if (pCrlCtx) { + /* loop ended with an error, need to clean up context manually */ + CertFreeCRLContext(pCrlCtx); + } + } + + /* In error cases cert, enc and tup may not be NULL */ + Py_XDECREF(cert); + Py_XDECREF(enc); + Py_XDECREF(tup); + + if (!CertCloseStore(hStore, 0)) { + /* This error case might shadow another exception.*/ + Py_DECREF(result); + return PyErr_SetFromWindowsErr(GetLastError()); + } + if (ok) { + return result; + } else { + Py_DECREF(result); + return NULL; + } +} +#endif /* List of functions exported by this module. */ @@ -2822,6 +2944,10 @@ static PyMethodDef PySSL_methods[] = { #endif {"get_default_verify_paths", (PyCFunction)get_default_verify_paths, METH_NOARGS, PySSL_get_default_verify_paths_doc}, +#ifdef _MSC_VER + {"enum_cert_store", (PyCFunction)PySSL_enum_cert_store, + METH_VARARGS | METH_KEYWORDS, PySSL_enum_cert_store_doc}, +#endif {NULL, NULL} /* Sentinel */ }; @@ -3034,6 +3160,12 @@ PyInit__ssl(void) PyModule_AddIntConstant(m, "CERT_REQUIRED", PY_SSL_CERT_REQUIRED); +#ifdef _MSC_VER + /* Windows dwCertEncodingType */ + PyModule_AddIntMacro(m, X509_ASN_ENCODING); + PyModule_AddIntMacro(m, PKCS_7_ASN_ENCODING); +#endif + /* Alert Descriptions from ssl.h */ /* note RESERVED constants no longer intended for use have been removed */ /* http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-6 */ diff --git a/PC/VS9.0/_socket.vcproj b/PC/VS9.0/_socket.vcproj index ff1f6d4e3f5..8c7979c4ca5 100644 --- a/PC/VS9.0/_socket.vcproj +++ b/PC/VS9.0/_socket.vcproj @@ -54,7 +54,7 @@ /> @@ -423,7 +423,7 @@ /> diff --git a/PCbuild/_ssl.vcxproj b/PCbuild/_ssl.vcxproj index ae71d19dc5e..7378794b3d9 100644 --- a/PCbuild/_ssl.vcxproj +++ b/PCbuild/_ssl.vcxproj @@ -158,7 +158,7 @@ - ws2_32.lib;$(opensslDir)\out32\libeay32.lib;$(opensslDir)\out32\ssleay32.lib;%(AdditionalDependencies) + ws2_32.lib;crypt32.lib;$(opensslDir)\out32\libeay32.lib;$(opensslDir)\out32\ssleay32.lib;%(AdditionalDependencies) @@ -173,7 +173,7 @@ - ws2_32.lib;$(opensslDir)\out64\libeay32.lib;$(opensslDir)\out64\ssleay32.lib;%(AdditionalDependencies) + ws2_32.lib;crypt32.lib;$(opensslDir)\out64\libeay32.lib;$(opensslDir)\out64\ssleay32.lib;%(AdditionalDependencies) @@ -185,7 +185,7 @@ - ws2_32.lib;$(opensslDir)\out32\libeay32.lib;$(opensslDir)\out32\ssleay32.lib;%(AdditionalDependencies) + ws2_32.lib;crypt32.lib;$(opensslDir)\out32\libeay32.lib;$(opensslDir)\out32\ssleay32.lib;%(AdditionalDependencies) @@ -200,7 +200,7 @@ - ws2_32.lib;$(opensslDir)\out64\libeay32.lib;$(opensslDir)\out64\ssleay32.lib;%(AdditionalDependencies) + ws2_32.lib;crypt32.lib;$(opensslDir)\out64\libeay32.lib;$(opensslDir)\out64\ssleay32.lib;%(AdditionalDependencies) @@ -212,7 +212,7 @@ - ws2_32.lib;$(opensslDir)\out32\libeay32.lib;$(opensslDir)\out32\ssleay32.lib;%(AdditionalDependencies) + ws2_32.lib;crypt32.lib;$(opensslDir)\out32\libeay32.lib;$(opensslDir)\out32\ssleay32.lib;%(AdditionalDependencies) @@ -227,7 +227,7 @@ - ws2_32.lib;$(opensslDir)\out64\libeay32.lib;$(opensslDir)\out64\ssleay32.lib;%(AdditionalDependencies) + ws2_32.lib;crypt32.lib;$(opensslDir)\out64\libeay32.lib;$(opensslDir)\out64\ssleay32.lib;%(AdditionalDependencies) MachineX64 @@ -240,7 +240,7 @@ - ws2_32.lib;$(opensslDir)\out32\libeay32.lib;$(opensslDir)\out32\ssleay32.lib;%(AdditionalDependencies) + ws2_32.lib;crypt32.lib;$(opensslDir)\out32\libeay32.lib;$(opensslDir)\out32\ssleay32.lib;%(AdditionalDependencies) @@ -255,7 +255,7 @@ - ws2_32.lib;$(opensslDir)\out64\libeay32.lib;$(opensslDir)\out64\ssleay32.lib;%(AdditionalDependencies) + ws2_32.lib;crypt32.lib;$(opensslDir)\out64\libeay32.lib;$(opensslDir)\out64\ssleay32.lib;%(AdditionalDependencies) MachineX64