[pypy-commit] pypy stdlib-2.7.9: SSL module: add "cadata" parameter to load_verify_locations().

amauryfa noreply at buildbot.pypy.org
Fri Jan 23 23:41:12 CET 2015


Author: Amaury Forgeot d'Arc <amauryfa at gmail.com>
Branch: stdlib-2.7.9
Changeset: r75506:8e49368b697a
Date: 2015-01-21 19:04 +0100
http://bitbucket.org/pypy/pypy/changeset/8e49368b697a/

Log:	SSL module: add "cadata" parameter to load_verify_locations().

diff --git a/pypy/module/_ssl/interp_ssl.py b/pypy/module/_ssl/interp_ssl.py
--- a/pypy/module/_ssl/interp_ssl.py
+++ b/pypy/module/_ssl/interp_ssl.py
@@ -962,7 +962,8 @@
         if ret != 1:
             raise _ssl_seterror(space, None, -1)
 
-    def load_verify_locations_w(self, space, w_cafile=None, w_capath=None):
+    def load_verify_locations_w(self, space, w_cafile=None, w_capath=None,
+                                w_cadata=None):
         if space.is_none(w_cafile):
             cafile = None
         else:
@@ -971,21 +972,112 @@
             capath = None
         else:
             capath = space.str_w(w_capath)
-        if cafile is None and capath is None:
+        if space.is_none(w_cadata):
+            cadata = None
+            ca_file_type = -1
+        else:
+            if not space.isinstance_w(w_cadata, space.w_unicode):
+                ca_file_type = SSL_FILETYPE_ASN1
+                cadata = space.bufferstr_w(w_cadata)
+            else:
+                ca_file_type = SSL_FILETYPE_PEM
+                try:
+                    cadata = space.unicode_w(w_cadata).encode('ascii')
+                except UnicodeEncodeError:
+                    raise oefmt(space.w_TypeError,
+                                "cadata should be a ASCII string or a "
+                                "bytes-like object")
+        if cafile is None and capath is None and cadata is None:
             raise OperationError(space.w_TypeError, space.wrap(
                     "cafile and capath cannot be both omitted"))
-        set_errno(0)
-        ret = libssl_SSL_CTX_load_verify_locations(
-            self.ctx, cafile, capath)
-        if ret != 1:
-            errno = get_errno()
-            if errno:
-                libssl_ERR_clear_error()
-                raise wrap_oserror(space, OSError(errno, ''),
-                                   exception_name = 'w_IOError')
+        # load from cadata
+        if cadata:
+            biobuf = libssl_BIO_new_mem_buf(cadata, len(cadata))
+            if not biobuf:
+                raise ssl_error(space, "Can't allocate buffer")
+            try:
+                store = libssl_SSL_CTX_get_cert_store(self.ctx)
+                loaded = 0
+                while True:
+                    if ca_file_type == SSL_FILETYPE_ASN1:
+                        cert = libssl_d2i_X509_bio(
+                            biobuf, None)
+                    else:
+                        cert = libssl_PEM_read_bio_X509(
+                            biobuf, None, None, None)
+                    if not cert:
+                        break
+                    try:
+                        r = libssl_X509_STORE_add_cert(store, cert)
+                    finally:
+                        libssl_X509_free(cert)
+                    if not r:
+                        err = libssl_ERR_peek_last_error()
+                        if (libssl_ERR_GET_LIB(err) == ERR_LIB_X509 and
+                            libssl_ERR_GET_REASON(err) ==
+                            X509_R_CERT_ALREADY_IN_HASH_TABLE):
+                            # cert already in hash table, not an error
+                            libssl_ERR_clear_error()
+                        else:
+                            break
+                    loaded += 1
+
+                err = libssl_ERR_peek_last_error()
+                if (ca_file_type == SSL_FILETYPE_ASN1 and
+                    loaded > 0 and
+                    libssl_ERR_GET_LIB(err) == ERR_LIB_ASN1 and
+                    libssl_ERR_GET_REASON(err) == ASN1_R_HEADER_TOO_LONG):
+                    # EOF ASN1 file, not an error
+                    libssl_ERR_clear_error()
+                elif (ca_file_type == SSL_FILETYPE_PEM and
+                      loaded > 0 and
+                      libssl_ERR_GET_LIB(err) == ERR_LIB_PEM and
+                      libssl_ERR_GET_REASON(err) == PEM_R_NO_START_LINE):
+                    # EOF PEM file, not an error
+                    libssl_ERR_clear_error
+                else:
+                    _ssl_seterror(space, None, 0)
+            finally:
+                libssl_BIO_free(biobuf)
+            
+        # load cafile or capath
+        if cafile or capath:
+            set_errno(0)
+            ret = libssl_SSL_CTX_load_verify_locations(
+                self.ctx, cafile, capath)
+            if ret != 1:
+                errno = get_errno()
+                if errno:
+                    libssl_ERR_clear_error()
+                    raise wrap_oserror(space, OSError(errno, ''),
+                                       exception_name = 'w_IOError')
+                else:
+                    raise _ssl_seterror(space, None, -1)
+
+    def cert_store_stats_w(self, space):
+        store = libssl_SSL_CTX_get_cert_store(self.ctx)
+        counters = {'x509': 0, 'x509_ca': 0, 'crl': 0}
+        for i in range(libssl_sk_X509_OBJECT_num(store[0].c_objs)):
+            obj = libssl_sk_X509_OBJECT_value(store[0].c_objs, i)
+            if intmask(obj.c_type) == X509_LU_X509:
+                counters['x509'] += 1
+                if libssl_pypy_X509_OBJECT_data_x509(obj):
+                    counters['x509_ca'] += 1
+            elif intmask(obj.c_type) == X509_LU_CRL:
+                counters['crl'] += 1
             else:
-                raise _ssl_seterror(space, None, -1)
-
+                # Ignore X509_LU_FAIL, X509_LU_RETRY, X509_LU_PKEY.
+                # As far as I can tell they are internal states and never
+                # stored in a cert store
+                pass
+        w_result = space.newdict()
+        space.setitem(w_result,
+                      space.wrap('x509'), space.wrap(counters['x509']))
+        space.setitem(w_result,
+                      space.wrap('x509_ca'), space.wrap(counters['x509_ca']))
+        space.setitem(w_result,
+                      space.wrap('crl'), space.wrap(counters['crl']))
+        return w_result
 
 _SSLContext.typedef = TypeDef(
     "_ssl._SSLContext",
@@ -993,6 +1085,7 @@
     _wrap_socket=interp2app(_SSLContext.descr_wrap_socket),
     set_ciphers=interp2app(_SSLContext.descr_set_ciphers),
     load_verify_locations=interp2app(_SSLContext.load_verify_locations_w),
+    cert_store_stats=interp2app(_SSLContext.cert_store_stats_w),
     load_cert_chain=interp2app(_SSLContext.load_cert_chain_w),
     set_default_verify_paths=interp2app(_SSLContext.descr_set_default_verify_paths),
 
diff --git a/pypy/module/_ssl/test/test_ssl.py b/pypy/module/_ssl/test/test_ssl.py
--- a/pypy/module/_ssl/test/test_ssl.py
+++ b/pypy/module/_ssl/test/test_ssl.py
@@ -276,6 +276,11 @@
         ctx.load_verify_locations(self.keycert)
         ctx.load_verify_locations(cafile=self.keycert, capath=None)
 
+        ctx = _ssl._SSLContext(_ssl.PROTOCOL_TLSv1)
+        with open(self.keycert) as f:
+            cacert_pem = f.read().decode('ascii')
+        ctx.load_verify_locations(cadata=cacert_pem)
+        assert ctx.cert_store_stats()["x509_ca"]
 
 SSL_CERTIFICATE = """
 -----BEGIN CERTIFICATE-----
diff --git a/rpython/rlib/ropenssl.py b/rpython/rlib/ropenssl.py
--- a/rpython/rlib/ropenssl.py
+++ b/rpython/rlib/ropenssl.py
@@ -38,6 +38,7 @@
         # Unnamed structures are not supported by rffi_platform.
         # So we replace an attribute access with a macro call.
         '#define pypy_GENERAL_NAME_dirn(name) (name->d.dirn)',
+        '#define pypy_X509_OBJECT_data_x509(obj) (obj->data.x509)',
     ],
 )
 
@@ -47,9 +48,11 @@
           include_dir='inc32', library_dir='out32'),
      ])
 
+X509 = rffi.COpaquePtr('X509')
 ASN1_STRING = lltype.Ptr(lltype.ForwardReference())
 ASN1_ITEM = rffi.COpaquePtr('ASN1_ITEM')
 X509_NAME = rffi.COpaquePtr('X509_NAME')
+stack_st_X509_OBJECT = rffi.COpaquePtr('struct stack_st_X509_OBJECT')
 
 class CConfigBootstrap:
     _compilation_info_ = eci
@@ -72,6 +75,7 @@
     OPENSSL_NO_ECDH = rffi_platform.Defined("OPENSSL_NO_ECDH")
     OPENSSL_NPN_NEGOTIATED = rffi_platform.Defined("OPENSSL_NPN_NEGOTIATED")
     SSL_FILETYPE_PEM = rffi_platform.ConstantInteger("SSL_FILETYPE_PEM")
+    SSL_FILETYPE_ASN1 = rffi_platform.ConstantInteger("SSL_FILETYPE_ASN1")
     SSL_OP_ALL = rffi_platform.ConstantInteger("SSL_OP_ALL")
     SSL_OP_NO_SSLv2 = rffi_platform.ConstantInteger("SSL_OP_NO_SSLv2")
     SSL_OP_NO_SSLv3 = rffi_platform.ConstantInteger("SSL_OP_NO_SSLv3")
@@ -102,6 +106,15 @@
     SSL_MODE_AUTO_RETRY = rffi_platform.ConstantInteger("SSL_MODE_AUTO_RETRY")
     SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER = rffi_platform.ConstantInteger("SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER")
 
+    ERR_LIB_X509 = rffi_platform.ConstantInteger("ERR_LIB_X509")
+    ERR_LIB_PEM = rffi_platform.ConstantInteger("ERR_LIB_PEM")
+    ERR_LIB_ASN1 = rffi_platform.ConstantInteger("ERR_LIB_ASN1")
+    PEM_R_NO_START_LINE = rffi_platform.ConstantInteger("PEM_R_NO_START_LINE")
+    ASN1_R_HEADER_TOO_LONG = rffi_platform.ConstantInteger(
+        "ASN1_R_HEADER_TOO_LONG")
+    X509_R_CERT_ALREADY_IN_HASH_TABLE = rffi_platform.ConstantInteger(
+        "X509_R_CERT_ALREADY_IN_HASH_TABLE")
+
     NID_undef = rffi_platform.ConstantInteger("NID_undef")
     NID_subject_alt_name = rffi_platform.ConstantInteger("NID_subject_alt_name")
     GEN_DIRNAME = rffi_platform.ConstantInteger("GEN_DIRNAME")
@@ -128,6 +141,17 @@
     X509_extension_st = rffi_platform.Struct(
         'struct X509_extension_st',
         [('value', ASN1_STRING)])
+    x509_store_st = rffi_platform.Struct(
+        'struct x509_store_st',
+        [('objs', stack_st_X509_OBJECT)])
+    
+    x509_object_st = rffi_platform.Struct(
+        'struct x509_object_st',
+        [('type', rffi.INT)])
+
+    X509_LU_X509 = rffi_platform.ConstantInteger("X509_LU_X509")
+    X509_LU_CRL = rffi_platform.ConstantInteger("X509_LU_CRL")
+
     X509V3_EXT_D2I = lltype.FuncType([rffi.VOIDP, rffi.CCHARPP, rffi.LONG],
                                      rffi.VOIDP)
     v3_ext_method = rffi_platform.Struct(
@@ -150,12 +174,6 @@
          ('name', rffi.CCHARP),
          ])
 
-    OBJ_NAME_st = rffi_platform.Struct(
-        'OBJ_NAME',
-        [('alias', rffi.INT),
-         ('name', rffi.CCHARP),
-         ])
-
 
 for k, v in rffi_platform.configure(CConfig).items():
     globals()[k] = v
@@ -166,9 +184,10 @@
 SSL_CIPHER = rffi.COpaquePtr('SSL_CIPHER')
 SSL = rffi.COpaquePtr('SSL')
 BIO = rffi.COpaquePtr('BIO')
-X509 = rffi.COpaquePtr('X509')
 X509_NAME_ENTRY = rffi.CArrayPtr(X509_name_entry_st)
 X509_EXTENSION = rffi.CArrayPtr(X509_extension_st)
+X509_STORE = rffi.CArrayPtr(x509_store_st)
+X509_OBJECT = lltype.Ptr(x509_object_st)
 X509V3_EXT_METHOD = rffi.CArrayPtr(v3_ext_method)
 ASN1_OBJECT = rffi.COpaquePtr('ASN1_OBJECT')
 ASN1_STRING.TO.become(asn1_string_st)
@@ -217,6 +236,7 @@
 ssl_external('SSLv23_method', [], SSL_METHOD)
 ssl_external('SSL_CTX_use_PrivateKey_file', [SSL_CTX, rffi.CCHARP, rffi.INT], rffi.INT)
 ssl_external('SSL_CTX_use_certificate_chain_file', [SSL_CTX, rffi.CCHARP], rffi.INT)
+ssl_external('SSL_CTX_get_cert_store', [SSL_CTX], X509_STORE)
 ssl_external('SSL_CTX_get_options', [SSL_CTX], rffi.LONG, macro=True)
 ssl_external('SSL_CTX_set_options', [SSL_CTX, rffi.LONG], rffi.LONG, macro=True)
 if HAVE_SSL_CTX_CLEAR_OPTIONS:
@@ -264,6 +284,7 @@
 ssl_external('X509_NAME_ENTRY_get_data', [X509_NAME_ENTRY], ASN1_STRING)
 ssl_external('i2d_X509', [X509, rffi.CCHARPP], rffi.INT)
 ssl_external('X509_free', [X509], lltype.Void, releasegil=False)
+ssl_external('X509_check_ca', [X509], rffi.INT)
 ssl_external('X509_get_notBefore', [X509], ASN1_TIME, macro=True)
 ssl_external('X509_get_notAfter', [X509], ASN1_TIME, macro=True)
 ssl_external('X509_get_serialNumber', [X509], ASN1_INTEGER)
@@ -272,6 +293,7 @@
 ssl_external('X509_get_ext', [X509, rffi.INT], X509_EXTENSION)
 ssl_external('X509V3_EXT_get', [X509_EXTENSION], X509V3_EXT_METHOD)
 
+ssl_external('X509_STORE_add_cert', [X509_STORE, X509], rffi.INT)
 
 ssl_external('OBJ_obj2txt',
              [rffi.CCHARP, rffi.INT, ASN1_OBJECT, rffi.INT], rffi.INT)
@@ -293,6 +315,13 @@
              macro=True)
 ssl_external('sk_GENERAL_NAME_value', [GENERAL_NAMES, rffi.INT], GENERAL_NAME,
              macro=True)
+ssl_external('sk_X509_OBJECT_num', [stack_st_X509_OBJECT], rffi.INT,
+             macro=True)
+ssl_external('sk_X509_OBJECT_value', [stack_st_X509_OBJECT, rffi.INT],
+             X509_OBJECT, macro=True)
+ssl_external('pypy_X509_OBJECT_data_x509', [X509_OBJECT], X509,
+             macro=True)
+
 ssl_external('GENERAL_NAME_print', [BIO, GENERAL_NAME], rffi.INT)
 ssl_external('pypy_GENERAL_NAME_dirn', [GENERAL_NAME], X509_NAME,
              macro=True)
@@ -306,6 +335,8 @@
 ssl_external('ERR_peek_last_error', [], rffi.INT)
 ssl_external('ERR_error_string', [rffi.ULONG, rffi.CCHARP], rffi.CCHARP)
 ssl_external('ERR_clear_error', [], lltype.Void)
+ssl_external('ERR_GET_LIB', [rffi.ULONG], rffi.INT, macro=True)
+ssl_external('ERR_GET_REASON', [rffi.ULONG], rffi.INT, macro=True)
 
 # 'releasegil=False' here indicates that this function will be called
 # with the GIL held, and so is allowed to run in a RPython __del__ method.
@@ -323,10 +354,14 @@
 ssl_external('BIO_s_file', [], BIO_METHOD)
 ssl_external('BIO_new', [BIO_METHOD], BIO)
 ssl_external('BIO_set_nbio', [BIO, rffi.INT], rffi.INT, macro=True)
+ssl_external('BIO_new_mem_buf', [rffi.VOIDP, rffi.INT], BIO)
 ssl_external('BIO_free', [BIO], rffi.INT)
 ssl_external('BIO_reset', [BIO], rffi.INT, macro=True)
 ssl_external('BIO_read_filename', [BIO, rffi.CCHARP], rffi.INT, macro=True)
 ssl_external('BIO_gets', [BIO, rffi.CCHARP, rffi.INT], rffi.INT)
+ssl_external('d2i_X509_bio', [BIO, rffi.VOIDP], X509)
+ssl_external('PEM_read_bio_X509',
+             [BIO, rffi.VOIDP, rffi.VOIDP, rffi.VOIDP], X509)
 ssl_external('PEM_read_bio_X509_AUX',
              [BIO, rffi.VOIDP, rffi.VOIDP, rffi.VOIDP], X509)
 
diff --git a/rpython/rtyper/tool/rffi_platform.py b/rpython/rtyper/tool/rffi_platform.py
--- a/rpython/rtyper/tool/rffi_platform.py
+++ b/rpython/rtyper/tool/rffi_platform.py
@@ -493,7 +493,7 @@
     def prepare_code(self):
         yield '#ifdef %s' % self.macro
         yield 'int i;'
-        yield 'char *p = %s;' % self.name
+        yield 'const char *p = %s;' % self.name
         yield 'dump("defined", 1);'
         yield 'for (i = 0; p[i] != 0; i++ ) {'
         yield '  printf("value_%d: %d\\n", i, (int)(unsigned char)p[i]);'


More information about the pypy-commit mailing list