[Python-checkins] cpython: Issue #19448: Add private API to SSL module to lookup ASN.1 objects by OID,

christian.heimes python-checkins at python.org
Sun Nov 17 20:04:55 CET 2013


http://hg.python.org/cpython/rev/f43f65038e2a
changeset:   87214:f43f65038e2a
user:        Christian Heimes <christian at cheimes.de>
date:        Sun Nov 17 19:59:14 2013 +0100
summary:
  Issue #19448: Add private API to SSL module to lookup ASN.1 objects by OID, NID, short name and long name.

files:
  Lib/ssl.py           |  26 ++++++++-
  Lib/test/test_ssl.py |  38 +++++++++++++
  Misc/NEWS            |   3 +
  Modules/_ssl.c       |  91 ++++++++++++++++++++++++++++++++
  4 files changed, 156 insertions(+), 2 deletions(-)


diff --git a/Lib/ssl.py b/Lib/ssl.py
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -91,7 +91,7 @@
 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 @@
     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 @@
             "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 @@
                               *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
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -539,6 +539,44 @@
         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
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -50,6 +50,9 @@
 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
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -2998,6 +2998,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\
@@ -3145,6 +3232,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 */
 };
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list