From 44109d7de7f24b681c973880aa3e1d39042078fd Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 22 Nov 2013 01:51:30 +0100 Subject: [PATCH] Issue #17134: Finalize interface to Windows' certificate store. Cert and CRL enumeration are now two functions. enum_certificates() also returns purpose flags as set of OIDs. --- Doc/library/ssl.rst | 45 ++++-- Lib/ssl.py | 2 +- Lib/test/test_ssl.py | 53 +++++--- Misc/NEWS | 4 + Modules/_ssl.c | 316 ++++++++++++++++++++++++++++++------------- 5 files changed, 289 insertions(+), 131 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 99386c0132d..7685ade3f0b 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -372,21 +372,45 @@ Certificate handling .. versionadded:: 3.4 -.. function:: enum_cert_store(store_name, cert_type='certificate') +.. function:: enum_certificates(store_name) 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. + stores, too. - 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`. + The function returns a list of (cert_bytes, encoding_type, trust) tuples. + The encoding_type specifies the encoding of cert_bytes. It is either + :const:`x509_asn` for X.509 ASN.1 data or :const:`pkcs_7_asn` for + PKCS#7 ASN.1 data. Trust specifies the purpose of the certificate as a set + of OIDS or exactly ``True`` if the certificate is trustworthy for all + purposes. + + Example:: + + >>> ssl.enum_certificates("CA") + [(b'data...', 'x509_asn', {'1.3.6.1.5.5.7.3.1', '1.3.6.1.5.5.7.3.2'}), + (b'data...', 'x509_asn', True)] Availability: Windows. .. versionadded:: 3.4 +.. function:: enum_crls(store_name) + + Retrieve CRLs from Windows' system cert store. *store_name* may be + one of ``CA``, ``ROOT`` or ``MY``. Windows may provide additional cert + stores, too. + + The function returns a list of (cert_bytes, encoding_type, trust) tuples. + The encoding_type specifies the encoding of cert_bytes. It is either + :const:`x509_asn` for X.509 ASN.1 data or :const:`pkcs_7_asn` for + PKCS#7 ASN.1 data. + + Availability: Windows. + + .. versionadded:: 3.4 + + Constants ^^^^^^^^^ @@ -657,15 +681,6 @@ 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 7ce097f0417..d4c7bad6f80 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -144,7 +144,7 @@ def _import_symbols(prefix): _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 _ssl import enum_certificates, enum_crls from socket import getnameinfo as _getnameinfo from socket import socket, AF_INET, SOCK_STREAM, create_connection diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 9996ff129db..2190d9f6bdb 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -528,29 +528,44 @@ def test_get_default_verify_paths(self): self.assertEqual(paths.cafile, CERTFILE) self.assertEqual(paths.capath, CAPATH) + @unittest.skipUnless(sys.platform == "win32", "Windows specific") + def test_enum_certificates(self): + self.assertTrue(ssl.enum_certificates("CA")) + self.assertTrue(ssl.enum_certificates("ROOT")) + + self.assertRaises(TypeError, ssl.enum_certificates) + self.assertRaises(WindowsError, ssl.enum_certificates, "") + + names = set() + ca = ssl.enum_certificates("CA") + self.assertIsInstance(ca, list) + for element in ca: + self.assertIsInstance(element, tuple) + self.assertEqual(len(element), 3) + cert, enc, trust = element + self.assertIsInstance(cert, bytes) + self.assertIn(enc, {"x509_asn", "pkcs_7_asn"}) + self.assertIsInstance(trust, (set, bool)) + if isinstance(trust, set): + names.update(trust) + + serverAuth = "1.3.6.1.5.5.7.3.1" + self.assertIn(serverAuth, names) @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) + def test_enum_crls(self): + self.assertTrue(ssl.enum_crls("CA")) + self.assertRaises(TypeError, ssl.enum_crls) + self.assertRaises(WindowsError, ssl.enum_crls, "") - 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") + crls = ssl.enum_crls("CA") + self.assertIsInstance(crls, list) + for element in crls: + self.assertIsInstance(element, tuple) + self.assertEqual(len(element), 2) + self.assertIsInstance(element[0], bytes) + self.assertIn(element[1], {"x509_asn", "pkcs_7_asn"}) - 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) def test_asn1object(self): expected = (129, 'serverAuth', 'TLS Web Server Authentication', diff --git a/Misc/NEWS b/Misc/NEWS index 295b456171d..b5916f42c7e 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -59,6 +59,10 @@ Core and Builtins Library ------- +- Issue #17134: Finalize interface to Windows' certificate store. Cert and + CRL enumeration are now two functions. enum_certificates() also returns + purpose flags as set of OIDs. + - Issue #19555: Restore sysconfig.get_config_var('SO'), with a DeprecationWarning pointing people at $EXT_SUFFIX. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 69b1eef5f87..4768e446bbb 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -3422,130 +3422,258 @@ PySSL_nid2obj(PyObject *self, PyObject *args) return result; } - #ifdef _MSC_VER -PyDoc_STRVAR(PySSL_enum_cert_store_doc, -"enum_cert_store(store_name, cert_type='certificate') -> []\n\ + +static PyObject* +certEncodingType(DWORD encodingType) +{ + static PyObject *x509_asn = NULL; + static PyObject *pkcs_7_asn = NULL; + + if (x509_asn == NULL) { + x509_asn = PyUnicode_InternFromString("x509_asn"); + if (x509_asn == NULL) + return NULL; + } + if (pkcs_7_asn == NULL) { + pkcs_7_asn = PyUnicode_InternFromString("pkcs_7_asn"); + if (pkcs_7_asn == NULL) + return NULL; + } + switch(encodingType) { + case X509_ASN_ENCODING: + Py_INCREF(x509_asn); + return x509_asn; + case PKCS_7_ASN_ENCODING: + Py_INCREF(pkcs_7_asn); + return pkcs_7_asn; + default: + return PyLong_FromLong(encodingType); + } +} + +static PyObject* +parseKeyUsage(PCCERT_CONTEXT pCertCtx, DWORD flags) +{ + CERT_ENHKEY_USAGE *usage; + DWORD size, error, i; + PyObject *retval; + + if (!CertGetEnhancedKeyUsage(pCertCtx, flags, NULL, &size)) { + error = GetLastError(); + if (error == CRYPT_E_NOT_FOUND) { + Py_RETURN_TRUE; + } + return PyErr_SetFromWindowsErr(error); + } + + usage = (CERT_ENHKEY_USAGE*)PyMem_Malloc(size); + if (usage == NULL) { + return PyErr_NoMemory(); + } + + /* Now get the actual enhanced usage property */ + if (!CertGetEnhancedKeyUsage(pCertCtx, flags, usage, &size)) { + PyMem_Free(usage); + error = GetLastError(); + if (error == CRYPT_E_NOT_FOUND) { + Py_RETURN_TRUE; + } + return PyErr_SetFromWindowsErr(error); + } + retval = PySet_New(NULL); + if (retval == NULL) { + goto error; + } + for (i = 0; i < usage->cUsageIdentifier; ++i) { + if (usage->rgpszUsageIdentifier[i]) { + PyObject *oid; + int err; + oid = PyUnicode_FromString(usage->rgpszUsageIdentifier[i]); + if (oid == NULL) { + Py_CLEAR(retval); + goto error; + } + err = PySet_Add(retval, oid); + Py_DECREF(oid); + if (err == -1) { + Py_CLEAR(retval); + goto error; + } + } + } + error: + PyMem_Free(usage); + return retval; +} + +PyDoc_STRVAR(PySSL_enum_certificates_doc, +"enum_certificates(store_name) -> []\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\ +The function returns a list of (bytes, encoding_type, trust) tuples. The\n\ encoding_type flag can be interpreted with X509_ASN_ENCODING or\n\ -PKCS_7_ASN_ENCODING."); +PKCS_7_ASN_ENCODING. The trust setting is either a set of OIDs or the\n\ +boolean True."); static PyObject * -PySSL_enum_cert_store(PyObject *self, PyObject *args, PyObject *kwds) +PySSL_enum_certificates(PyObject *self, PyObject *args, PyObject *kwds) { - char *kwlist[] = {"store_name", "cert_type", NULL}; + char *kwlist[] = {"store_name", NULL}; char *store_name; - char *cert_type = "certificate"; HCERTSTORE hStore = NULL; + PCCERT_CONTEXT pCertCtx = NULL; + PyObject *keyusage = NULL, *cert = NULL, *enc = NULL, *tup = 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) { + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s:enum_certificates", + kwlist, &store_name)) { return NULL; } - - if ((hStore = CertOpenSystemStore((HCRYPTPROV)NULL, store_name)) == NULL) { + result = PyList_New(0); + if (result == NULL) { + return NULL; + } + hStore = CertOpenSystemStore((HCRYPTPROV)NULL, store_name); + if (hStore == 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); + while (pCertCtx = CertEnumCertificatesInStore(hStore, pCertCtx)) { + cert = PyBytes_FromStringAndSize((const char*)pCertCtx->pbCertEncoded, + pCertCtx->cbCertEncoded); + if (!cert) { + Py_CLEAR(result); + break; } - if (pCertCtx) { - /* loop ended with an error, need to clean up context manually */ - CertFreeCertificateContext(pCertCtx); + if ((enc = certEncodingType(pCertCtx->dwCertEncodingType)) == NULL) { + Py_CLEAR(result); + break; } - } 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); + keyusage = parseKeyUsage(pCertCtx, CERT_FIND_PROP_ONLY_ENHKEY_USAGE_FLAG); + if (keyusage == Py_True) { + Py_DECREF(keyusage); + keyusage = parseKeyUsage(pCertCtx, CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG); } - if (pCrlCtx) { - /* loop ended with an error, need to clean up context manually */ - CertFreeCRLContext(pCrlCtx); + if (keyusage == NULL) { + Py_CLEAR(result); + break; } + if ((tup = PyTuple_New(3)) == NULL) { + Py_CLEAR(result); + break; + } + PyTuple_SET_ITEM(tup, 0, cert); + cert = NULL; + PyTuple_SET_ITEM(tup, 1, enc); + enc = NULL; + PyTuple_SET_ITEM(tup, 2, keyusage); + keyusage = NULL; + if (PyList_Append(result, tup) < 0) { + Py_CLEAR(result); + break; + } + Py_CLEAR(tup); + } + if (pCertCtx) { + /* loop ended with an error, need to clean up context manually */ + CertFreeCertificateContext(pCertCtx); } /* In error cases cert, enc and tup may not be NULL */ Py_XDECREF(cert); Py_XDECREF(enc); + Py_XDECREF(keyusage); Py_XDECREF(tup); if (!CertCloseStore(hStore, 0)) { /* This error case might shadow another exception.*/ + Py_XDECREF(result); + return PyErr_SetFromWindowsErr(GetLastError()); + } + return result; +} + +PyDoc_STRVAR(PySSL_enum_crls_doc, +"enum_crls(store_name) -> []\n\ +\n\ +Retrieve CRLs from Windows' cert store. store_name may be one of\n\ +'CA', 'ROOT' or 'MY'. The system may provide more cert storages, too.\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_crls(PyObject *self, PyObject *args, PyObject *kwds) +{ + char *kwlist[] = {"store_name", NULL}; + char *store_name; + HCERTSTORE hStore = NULL; + PCCRL_CONTEXT pCrlCtx = NULL; + PyObject *crl = NULL, *enc = NULL, *tup = NULL; + PyObject *result = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s:enum_crls", + kwlist, &store_name)) { + return NULL; + } + result = PyList_New(0); + if (result == NULL) { + return NULL; + } + hStore = CertOpenSystemStore((HCRYPTPROV)NULL, store_name); + if (hStore == NULL) { Py_DECREF(result); return PyErr_SetFromWindowsErr(GetLastError()); } - if (ok) { - return result; - } else { - Py_DECREF(result); - return NULL; + + while (pCrlCtx = CertEnumCRLsInStore(hStore, pCrlCtx)) { + crl = PyBytes_FromStringAndSize((const char*)pCrlCtx->pbCrlEncoded, + pCrlCtx->cbCrlEncoded); + if (!crl) { + Py_CLEAR(result); + break; + } + if ((enc = certEncodingType(pCrlCtx->dwCertEncodingType)) == NULL) { + Py_CLEAR(result); + break; + } + if ((tup = PyTuple_New(2)) == NULL) { + Py_CLEAR(result); + break; + } + PyTuple_SET_ITEM(tup, 0, crl); + crl = NULL; + PyTuple_SET_ITEM(tup, 1, enc); + enc = NULL; + + if (PyList_Append(result, tup) < 0) { + Py_CLEAR(result); + 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(crl); + Py_XDECREF(enc); + Py_XDECREF(tup); + + if (!CertCloseStore(hStore, 0)) { + /* This error case might shadow another exception.*/ + Py_XDECREF(result); + return PyErr_SetFromWindowsErr(GetLastError()); + } + return result; } -#endif + +#endif /* _MSC_VER */ /* List of functions exported by this module. */ @@ -3567,8 +3695,10 @@ static PyMethodDef PySSL_methods[] = { {"get_default_verify_paths", (PyCFunction)PySSL_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}, + {"enum_certificates", (PyCFunction)PySSL_enum_certificates, + METH_VARARGS | METH_KEYWORDS, PySSL_enum_certificates_doc}, + {"enum_crls", (PyCFunction)PySSL_enum_crls, + METH_VARARGS | METH_KEYWORDS, PySSL_enum_crls_doc}, #endif {"txt2obj", (PyCFunction)PySSL_txt2obj, METH_VARARGS | METH_KEYWORDS, PySSL_txt2obj_doc}, @@ -3811,12 +3941,6 @@ PyInit__ssl(void) PyModule_AddIntConstant(m, "VERIFY_X509_STRICT", X509_V_FLAG_X509_STRICT); -#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 */