diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -343,6 +343,27 @@ Given a certificate as an ASCII PEM string, returns a DER-encoded sequence of bytes for that same certificate. +.. function:: get_default_verify_paths(raw=False) + + Returns a named tuple with paths to OpenSSL's default cafile and capath. + The paths are the same as used by + :meth:`SSLContext.set_default_verify_paths`. If ``raw`` is true the function + returns unprocessed environment keys and paths. + + Example:: + + >>> import ssl + >>> ssl.get_default_verify_paths(raw=True) + RawDefaultVerifyPaths(cafile_env_key='SSL_CERT_FILE', cafile='/usr/lib/ssl/cert.pem', capath_env_key='SSL_CERT_DIR', capath='/usr/lib/ssl/certs') + # cafile doesn't exist and neither SSL_CERT_FILE nor SSL_CERT_DIR are set + >>> ssl.get_default_verify_paths() + DefaultVerifyPaths(cafile=None, capath='/usr/lib/ssl/certs') + >>> os.environ["SSL_CERT_DIR"] = "/non/existing/path" + >>> os.environ["SSL_CERT_FILE"] = "/etc/ssl/certs/ca.pem" + >>> ssl.get_default_verify_paths() + DefaultVerifyPaths(cafile='/etc/ssl/certs/ca.pem', capath=None) + + Constants ^^^^^^^^^ diff --git a/Lib/ssl.py b/Lib/ssl.py --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -89,6 +89,8 @@ import textwrap import re +import os +import collections import _ssl # if we can't import it, let the error propagate @@ -222,6 +224,26 @@ "subjectAltName fields were found") +RawDefaultVerifyPaths = collections.namedtuple("RawDefaultVerifyPaths", + "cafile_env_key cafile capath_env_key capath") +DefaultVerifyPaths = collections.namedtuple("DefaultVerifyPaths", + "cafile capath") + +def get_default_verify_paths(raw=False): + """Return paths to default cafile and capath. + """ + parts = _ssl.get_default_verify_paths() + if raw: + return RawDefaultVerifyPaths(*parts) + + # environment vars shadow paths + cafile = os.environ.get(parts[0], parts[1]) + capath = os.environ.get(parts[2], parts[3]) + + return DefaultVerifyPaths(cafile if os.path.isfile(cafile) else None, + capath if os.path.isdir(capath) else None) + + 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 @@ -394,6 +394,19 @@ support.gc_collect() self.assertIn(r, str(cm.warning.args[0])) + def test_get_default_verify_paths(self): + paths = ssl.get_default_verify_paths() + self.assertEqual(len(paths), 2) + raw_paths = ssl.get_default_verify_paths(raw=True) + self.assertEqual(len(raw_paths), 4) + + with support.EnvironmentVarGuard() as env: + env["SSL_CERT_DIR"] = CAPATH + env["SSL_CERT_FILE"] = CERTFILE + paths = tuple(ssl.get_default_verify_paths()) + self.assertEqual(paths, (CERTFILE, CAPATH)) + + class ContextTests(unittest.TestCase): @skip_if_broken_ubuntu_ssl diff --git a/Modules/_ssl.c b/Modules/_ssl.c --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -2761,6 +2761,46 @@ #endif +PyDoc_STRVAR(PySSL_get_default_verify_paths_doc, +"get_default_verify_paths() -> tuple\n\ +\n\ +Return search paths and environment vars that are used by SSLContext's\n\ +set_default_verify_paths() to load default CAs. The values are\n\ +'cert_file_env', 'cert_file', 'cert_dir_env', 'cert_dir'."); + +static PyObject * +get_default_verify_paths(PyObject *self) +{ + PyObject *ofile_env = NULL; + PyObject *ofile = NULL; + PyObject *odir_env = NULL; + PyObject *odir = NULL; + +#define convert(info, target) { \ + const char *tmp = (info); \ + target = NULL; \ + if (!tmp) { Py_INCREF(Py_None); target = Py_None; } \ + else if ((target = PyUnicode_DecodeFSDefault(tmp)) == NULL) { \ + target = PyBytes_FromString(tmp); } \ + if (!target) goto error; \ + } while(0) + + convert(X509_get_default_cert_file_env(), ofile_env); + convert(X509_get_default_cert_file(), ofile); + convert(X509_get_default_cert_dir_env(), odir_env); + convert(X509_get_default_cert_dir(), odir); +#undef convert + + return Py_BuildValue("(OOOO)", ofile_env, ofile, odir_env, odir); + + error: + Py_XDECREF(ofile_env); + Py_XDECREF(ofile); + Py_XDECREF(odir_env); + Py_XDECREF(odir); + return NULL; +} + /* List of functions exported by this module. */ @@ -2779,6 +2819,8 @@ PySSL_RAND_egd_doc}, {"RAND_status", (PyCFunction)PySSL_RAND_status, METH_NOARGS, PySSL_RAND_status_doc}, + {"get_default_verify_paths", (PyCFunction)get_default_verify_paths, + METH_NOARGS, PySSL_get_default_verify_paths_doc}, #endif {NULL, NULL} /* Sentinel */ };