diff options
-rw-r--r-- | Lib/ssl.py | 26 | ||||
-rw-r--r-- | Lib/test/test_ssl.py | 38 | ||||
-rw-r--r-- | Misc/NEWS | 3 | ||||
-rw-r--r-- | Modules/_ssl.c | 91 |
4 files changed, 156 insertions, 2 deletions
@@ -91,7 +91,7 @@ import textwrap import re import sys import os -import collections +from collections import namedtuple import _ssl # if we can't import it, let the error propagate @@ -102,6 +102,7 @@ from _ssl import ( SSLSyscallError, SSLEOFError, ) from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED +from _ssl import txt2obj as _txt2obj, nid2obj as _nid2obj from _ssl import RAND_status, RAND_egd, RAND_add, RAND_bytes, RAND_pseudo_bytes def _import_symbols(prefix): @@ -256,7 +257,7 @@ def match_hostname(cert, hostname): "subjectAltName fields were found") -DefaultVerifyPaths = collections.namedtuple("DefaultVerifyPaths", +DefaultVerifyPaths = namedtuple("DefaultVerifyPaths", "cafile capath openssl_cafile_env openssl_cafile openssl_capath_env " "openssl_capath") @@ -274,6 +275,27 @@ def get_default_verify_paths(): *parts) +class _ASN1Object(namedtuple("_ASN1Object", "nid shortname longname oid")): + """ASN.1 object identifier lookup + """ + __slots__ = () + + def __new__(cls, oid): + return super().__new__(cls, *_txt2obj(oid, name=False)) + + @classmethod + def fromnid(cls, nid): + """Create _ASN1Object from OpenSSL numeric ID + """ + return super().__new__(cls, *_nid2obj(nid)) + + @classmethod + def fromname(cls, name): + """Create _ASN1Object from short name, long name or OID + """ + return super().__new__(cls, *_txt2obj(name, name=True)) + + class SSLContext(_SSLContext): """An SSLContext holds various SSL-related configuration options and data, such as certificates and possibly a private key.""" diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index b1cb8c5..61a4e77 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -539,6 +539,44 @@ class BasicSocketTests(unittest.TestCase): self.assertIsInstance(ca[0][0], bytes) self.assertIsInstance(ca[0][1], int) + def test_asn1object(self): + expected = (129, 'serverAuth', 'TLS Web Server Authentication', + '1.3.6.1.5.5.7.3.1') + + val = ssl._ASN1Object('1.3.6.1.5.5.7.3.1') + self.assertEqual(val, expected) + self.assertEqual(val.nid, 129) + self.assertEqual(val.shortname, 'serverAuth') + self.assertEqual(val.longname, 'TLS Web Server Authentication') + self.assertEqual(val.oid, '1.3.6.1.5.5.7.3.1') + self.assertIsInstance(val, ssl._ASN1Object) + self.assertRaises(ValueError, ssl._ASN1Object, 'serverAuth') + + val = ssl._ASN1Object.fromnid(129) + self.assertEqual(val, expected) + self.assertIsInstance(val, ssl._ASN1Object) + self.assertRaises(ValueError, ssl._ASN1Object.fromnid, -1) + self.assertRaises(ValueError, ssl._ASN1Object.fromnid, 100000) + for i in range(1000): + try: + obj = ssl._ASN1Object.fromnid(i) + except ValueError: + pass + else: + self.assertIsInstance(obj.nid, int) + self.assertIsInstance(obj.shortname, str) + self.assertIsInstance(obj.longname, str) + self.assertIsInstance(obj.oid, (str, type(None))) + + val = ssl._ASN1Object.fromname('TLS Web Server Authentication') + self.assertEqual(val, expected) + self.assertIsInstance(val, ssl._ASN1Object) + self.assertEqual(ssl._ASN1Object.fromname('serverAuth'), expected) + self.assertEqual(ssl._ASN1Object.fromname('1.3.6.1.5.5.7.3.1'), + expected) + self.assertRaises(ValueError, ssl._ASN1Object.fromname, 'serverauth') + + class ContextTests(unittest.TestCase): @skip_if_broken_ubuntu_ssl @@ -50,6 +50,9 @@ Core and Builtins Library ------- +- Issue #19448: Add private API to SSL module to lookup ASN.1 objects by OID, + NID, short name and long name. + - Issue #19282: dbm.open now supports the context manager protocol. (Inital patch by Claudiu Popa) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 83a271e..75b8360 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -2998,6 +2998,93 @@ PySSL_get_default_verify_paths(PyObject *self) return NULL; } +static PyObject* +asn1obj2py(ASN1_OBJECT *obj) +{ + int nid; + const char *ln, *sn; + char buf[100]; + int buflen; + + nid = OBJ_obj2nid(obj); + if (nid == NID_undef) { + PyErr_Format(PyExc_ValueError, "Unknown object"); + return NULL; + } + sn = OBJ_nid2sn(nid); + ln = OBJ_nid2ln(nid); + buflen = OBJ_obj2txt(buf, sizeof(buf), obj, 1); + if (buflen < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + return NULL; + } + if (buflen) { + return Py_BuildValue("isss#", nid, sn, ln, buf, buflen); + } else { + return Py_BuildValue("issO", nid, sn, ln, Py_None); + } +} + +PyDoc_STRVAR(PySSL_txt2obj_doc, +"txt2obj(txt, name=False) -> (nid, shortname, longname, oid)\n\ +\n\ +Lookup NID, short name, long name and OID of an ASN1_OBJECT. By default\n\ +objects are looked up by OID. With name=True short and long name are also\n\ +matched."); + +static PyObject* +PySSL_txt2obj(PyObject *self, PyObject *args, PyObject *kwds) +{ + char *kwlist[] = {"txt", "name", NULL}; + PyObject *result = NULL; + char *txt; + int name = 0; + ASN1_OBJECT *obj; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|p:txt2obj", + kwlist, &txt, &name)) { + return NULL; + } + obj = OBJ_txt2obj(txt, name ? 0 : 1); + if (obj == NULL) { + PyErr_Format(PyExc_ValueError, "Unknown object"); + return NULL; + } + result = asn1obj2py(obj); + ASN1_OBJECT_free(obj); + return result; +} + +PyDoc_STRVAR(PySSL_nid2obj_doc, +"nid2obj(nid) -> (nid, shortname, longname, oid)\n\ +\n\ +Lookup NID, short name, long name and OID of an ASN1_OBJECT by NID."); + +static PyObject* +PySSL_nid2obj(PyObject *self, PyObject *args) +{ + PyObject *result = NULL; + int nid; + ASN1_OBJECT *obj; + + if (!PyArg_ParseTuple(args, "i:nid2obj", &nid)) { + return NULL; + } + if (nid < NID_undef) { + PyErr_Format(PyExc_ValueError, "NID must be positive."); + return NULL; + } + obj = OBJ_nid2obj(nid); + if (obj == NULL) { + PyErr_Format(PyExc_ValueError, "Unknown NID"); + return NULL; + } + result = asn1obj2py(obj); + ASN1_OBJECT_free(obj); + return result; +} + + #ifdef _MSC_VER PyDoc_STRVAR(PySSL_enum_cert_store_doc, "enum_cert_store(store_name, cert_type='certificate') -> []\n\ @@ -3145,6 +3232,10 @@ static PyMethodDef PySSL_methods[] = { {"enum_cert_store", (PyCFunction)PySSL_enum_cert_store, METH_VARARGS | METH_KEYWORDS, PySSL_enum_cert_store_doc}, #endif + {"txt2obj", (PyCFunction)PySSL_txt2obj, + METH_VARARGS | METH_KEYWORDS, PySSL_txt2obj_doc}, + {"nid2obj", (PyCFunction)PySSL_nid2obj, + METH_VARARGS, PySSL_nid2obj_doc}, {NULL, NULL} /* Sentinel */ }; |