diff -r 287d7397218f Doc/library/ssl.rst --- a/Doc/library/ssl.rst Tue Oct 29 22:21:16 2013 +0100 +++ b/Doc/library/ssl.rst Wed Oct 30 10:50:07 2013 +0100 @@ -389,6 +389,54 @@ .. versionadded:: 3.4 + +Misc +^^^^^^^^^^^^^^^^^^^^ + +.. class:: ASN1Object + + Represents an ASN1_OBJECT + + .. versionadded:: 3.4 + + .. attribute:: ASN1Object.nid + + OpenSSL's internal numeric id. + + .. attribute:: ASN1Object.shortname + + short name + + .. attribute:: ASN1Object.longname + + long name + + .. attribute:: ASN1Object.oid + + OID or None + +.. function:: nid2obj(nid) + + Lookup a :class:`ssl.ASN1Object` by numeric id. + + .. versionadded:: 3.4 + +.. function:: txt2obj(txt, name=False) + + Lookup a :class:`ssl.ASN1Object` by OID, short name or long name:: + + >>> ssl.txt2obj('serverAuth') + Traceback (most recent call last): + ... + ValueError: Unknown object + >>> ssl.txt2obj('serverAuth', name=True) + ASN1Object(nid=129, shortname='serverAuth', longname='TLS Web Server Authentication', oid='1.3.6.1.5.5.7.3.1') + >>> ssl.txt2obj('1.3.6.1.5.5.7.3.1') + ASN1Object(nid=129, shortname='serverAuth', longname='TLS Web Server Authentication', oid='1.3.6.1.5.5.7.3.1') + + .. versionadded:: 3.4 + + Constants ^^^^^^^^^ diff -r 287d7397218f Lib/ssl.py --- a/Lib/ssl.py Tue Oct 29 22:21:16 2013 +0100 +++ b/Lib/ssl.py Wed Oct 30 10:50:07 2013 +0100 @@ -102,6 +102,7 @@ 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): @@ -274,6 +275,19 @@ *parts) +ASN1Object = collections.namedtuple("ASN1Object", + "nid shortname longname oid") + +def txt2obj(txt, name=False): + """Lookup ASN1 Object by OID or name + """ + return ASN1Object(*_txt2obj(txt, name)) + +def nid2obj(nid): + """Lookup ASN1 object by numeric id""" + return ASN1Object(*_nid2obj(nid)) + + class SSLContext(_SSLContext): """An SSLContext holds various SSL-related configuration options and data, such as certificates and possibly a private key.""" diff -r 287d7397218f Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py Tue Oct 29 22:21:16 2013 +0100 +++ b/Lib/test/test_ssl.py Wed Oct 30 10:50:07 2013 +0100 @@ -539,6 +539,33 @@ 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') + self.assertEqual(ssl.nid2obj(129), expected) + self.assertRaises(ValueError, ssl.nid2obj, -1) + self.assertRaises(ValueError, ssl.nid2obj, 100000) + for i in range(1000): + try: + obj = ssl.nid2obj(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))) + + self.assertEqual(ssl.txt2obj('1.3.6.1.5.5.7.3.1'), expected) + self.assertEqual(ssl.txt2obj('1.3.6.1.5.5.7.3.1').nid, 129) + self.assertRaises(ValueError, ssl.txt2obj, 'serverAuth') + self.assertEqual(ssl.txt2obj('serverAuth', True), expected) + self.assertRaises(ValueError, ssl.txt2obj, 'serverauth', True) + self.assertEqual(ssl.txt2obj('serverAuth', name=True), expected) + self.assertEqual(ssl.txt2obj('TLS Web Server Authentication', + name=True), expected) + + class ContextTests(unittest.TestCase): @skip_if_broken_ubuntu_ssl diff -r 287d7397218f Modules/_ssl.c --- a/Modules/_ssl.c Tue Oct 29 22:21:16 2013 +0100 +++ b/Modules/_ssl.c Wed Oct 30 10:50:07 2013 +0100 @@ -2990,6 +2990,93 @@ 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\ @@ -3137,6 +3224,10 @@ {"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 */ };