Issue #19448: Add private API to SSL module to lookup ASN.1 objects by OID, NID, short name and long name.
This commit is contained in:
parent
35e4ad71ba
commit
a6bc95aa02
26
Lib/ssl.py
26
Lib/ssl.py
|
@ -91,7 +91,7 @@ import textwrap
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import collections
|
from collections import namedtuple
|
||||||
|
|
||||||
import _ssl # if we can't import it, let the error propagate
|
import _ssl # if we can't import it, let the error propagate
|
||||||
|
|
||||||
|
@ -102,6 +102,7 @@ from _ssl import (
|
||||||
SSLSyscallError, SSLEOFError,
|
SSLSyscallError, SSLEOFError,
|
||||||
)
|
)
|
||||||
from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
|
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
|
from _ssl import RAND_status, RAND_egd, RAND_add, RAND_bytes, RAND_pseudo_bytes
|
||||||
|
|
||||||
def _import_symbols(prefix):
|
def _import_symbols(prefix):
|
||||||
|
@ -256,7 +257,7 @@ def match_hostname(cert, hostname):
|
||||||
"subjectAltName fields were found")
|
"subjectAltName fields were found")
|
||||||
|
|
||||||
|
|
||||||
DefaultVerifyPaths = collections.namedtuple("DefaultVerifyPaths",
|
DefaultVerifyPaths = namedtuple("DefaultVerifyPaths",
|
||||||
"cafile capath openssl_cafile_env openssl_cafile openssl_capath_env "
|
"cafile capath openssl_cafile_env openssl_cafile openssl_capath_env "
|
||||||
"openssl_capath")
|
"openssl_capath")
|
||||||
|
|
||||||
|
@ -274,6 +275,27 @@ def get_default_verify_paths():
|
||||||
*parts)
|
*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):
|
class SSLContext(_SSLContext):
|
||||||
"""An SSLContext holds various SSL-related configuration options and
|
"""An SSLContext holds various SSL-related configuration options and
|
||||||
data, such as certificates and possibly a private key."""
|
data, such as certificates and possibly a private key."""
|
||||||
|
|
|
@ -539,6 +539,44 @@ class BasicSocketTests(unittest.TestCase):
|
||||||
self.assertIsInstance(ca[0][0], bytes)
|
self.assertIsInstance(ca[0][0], bytes)
|
||||||
self.assertIsInstance(ca[0][1], int)
|
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):
|
class ContextTests(unittest.TestCase):
|
||||||
|
|
||||||
@skip_if_broken_ubuntu_ssl
|
@skip_if_broken_ubuntu_ssl
|
||||||
|
|
|
@ -50,6 +50,9 @@ Core and Builtins
|
||||||
Library
|
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
|
- Issue #19282: dbm.open now supports the context manager protocol. (Inital
|
||||||
patch by Claudiu Popa)
|
patch by Claudiu Popa)
|
||||||
|
|
||||||
|
|
|
@ -2998,6 +2998,93 @@ PySSL_get_default_verify_paths(PyObject *self)
|
||||||
return NULL;
|
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
|
#ifdef _MSC_VER
|
||||||
PyDoc_STRVAR(PySSL_enum_cert_store_doc,
|
PyDoc_STRVAR(PySSL_enum_cert_store_doc,
|
||||||
"enum_cert_store(store_name, cert_type='certificate') -> []\n\
|
"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,
|
{"enum_cert_store", (PyCFunction)PySSL_enum_cert_store,
|
||||||
METH_VARARGS | METH_KEYWORDS, PySSL_enum_cert_store_doc},
|
METH_VARARGS | METH_KEYWORDS, PySSL_enum_cert_store_doc},
|
||||||
#endif
|
#endif
|
||||||
|
{"txt2obj", (PyCFunction)PySSL_txt2obj,
|
||||||
|
METH_VARARGS | METH_KEYWORDS, PySSL_txt2obj_doc},
|
||||||
|
{"nid2obj", (PyCFunction)PySSL_nid2obj,
|
||||||
|
METH_VARARGS, PySSL_nid2obj_doc},
|
||||||
{NULL, NULL} /* Sentinel */
|
{NULL, NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue