[prev in list] [next in list] [prev in thread] [next in thread] 

List:       pypy-svn
Subject:    [pypy-commit] pypy stdlib-2.7.9: Add password parameter to ctx.load_cert_chain()
From:       amauryfa <noreply () buildbot ! pypy ! org>
Date:       2015-01-30 23:45:13
Message-ID: 20150130234513.3D99C1C0FAB () cobra ! cs ! uni-duesseldorf ! de
[Download RAW message or body]

Author: Amaury Forgeot d'Arc <amauryfa@gmail.com>
Branch: stdlib-2.7.9
Changeset: r75593:ee52b4559ba7
Date: 2015-01-31 00:08 +0100
http://bitbucket.org/pypy/pypy/changeset/ee52b4559ba7/

Log:	Add password parameter to ctx.load_cert_chain()

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
@@ -1,4 +1,4 @@
-from rpython.rlib import rpoll, rsocket
+from rpython.rlib import rpoll, rsocket, rthread
 from rpython.rlib.rarithmetic import intmask, widen, r_uint
 from rpython.rlib.ropenssl import *
 from rpython.rlib.rposix import get_errno, set_errno
@@ -999,6 +999,39 @@
         libssl_BIO_free(cert)
 
 
+# Data structure for the password callbacks
+class PasswordInfo(object):
+    w_callable = None
+    password = None
+    operationerror = None
+PWINFO_STORAGE = {}
+
+def _password_callback(buf, size, rwflag, userdata):
+    index = rffi.cast(lltype.Signed, userdata)
+    pw_info = PWINFO_STORAGE.get(index, None)
+    if not pw_info:
+        return rffi.cast(rffi.INT, -1)
+    space = pw_info.space
+    password = ""
+    if pw_info.w_callable:
+        try:
+            password = pw_info.space.str_w(
+                space.call_function(pw_info.w_callable))
+        except OperationError as e:
+            pw_info.operationerror = e
+            return rffi.cast(rffi.INT, -1)
+    else:
+        password = pw_info.password
+    size = widen(size)
+    if len(password) > size:
+        pw_info.operationerror = oefmt(
+            space.w_ValueError,
+            "password cannot be longer than %d bytes", size)
+        return rffi.cast(rffi.INT, -1)
+    for i, c in enumerate(password):
+        buf[i] = c
+    return rffi.cast(rffi.INT, len(password))
+
 class _SSLContext(W_Root):
     @staticmethod
     @unwrap_spec(protocol=int)
@@ -1101,7 +1134,8 @@
                         "CERT_OPTIONAL or CERT_REQUIRED")
         self.check_hostname = check_hostname
 
-    def load_cert_chain_w(self, space, w_certfile, w_keyfile=None):
+    def load_cert_chain_w(self, space, w_certfile, w_keyfile=None,
+                          w_password=None):
         if space.is_none(w_certfile):
             certfile = None
         else:
@@ -1110,33 +1144,63 @@
             keyfile = certfile
         else:
             keyfile = space.str_w(w_keyfile)
+        pw_info = PasswordInfo()
+        pw_info.space = space
+        index = -1
+        if not space.is_none(w_password):
+            index = rthread.get_ident()
+            PWINFO_STORAGE[index] = pw_info
 
-        set_errno(0)
+            if space.is_true(space.callable(w_password)):
+                pw_info.w_callable = w_password
+            else:
+                pw_info.password = space.str_w(w_password)
 
-        ret = libssl_SSL_CTX_use_certificate_chain_file(self.ctx, certfile)
-        if ret != 1:
-            errno = get_errno()
-            if errno:
-                libssl_ERR_clear_error()
-                raise wrap_oserror(space, OSError(errno, ''),
-                                   exception_name = 'w_IOError')
-            else:
+            libssl_SSL_CTX_set_default_passwd_cb(
+                self.ctx, _password_callback)
+            libssl_SSL_CTX_set_default_passwd_cb_userdata(
+                self.ctx, rffi.cast(rffi.VOIDP, index))
+
+
+        try:
+            set_errno(0)
+            ret = libssl_SSL_CTX_use_certificate_chain_file(self.ctx, certfile)
+            if ret != 1:
+                if pw_info.operationerror:
+                    libssl_ERR_clear_error()
+                    raise pw_info.operationerror
+                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)
+
+            ret = libssl_SSL_CTX_use_PrivateKey_file(self.ctx, keyfile,
+                                                     SSL_FILETYPE_PEM)
+            if ret != 1:
+                if pw_info.operationerror:
+                    libssl_ERR_clear_error()
+                    raise pw_info.operationerror
+                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)
+
+            ret = libssl_SSL_CTX_check_private_key(self.ctx)
+            if ret != 1:
                 raise _ssl_seterror(space, None, -1)
-
-        ret = libssl_SSL_CTX_use_PrivateKey_file(self.ctx, keyfile,
-                                                 SSL_FILETYPE_PEM)
-        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)
-
-        ret = libssl_SSL_CTX_check_private_key(self.ctx)
-        if ret != 1:
-            raise _ssl_seterror(space, None, -1)
+        finally:
+            if index >= 0:
+                del PWINFO_STORAGE[index]
+            libssl_SSL_CTX_set_default_passwd_cb(
+                self.ctx, lltype.nullptr(pem_password_cb.TO))
+            libssl_SSL_CTX_set_default_passwd_cb_userdata(
+                self.ctx, None)
 
     @unwrap_spec(filepath=str)
     def load_dh_params_w(self, space, filepath):
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
@@ -290,6 +290,9 @@
         tmpfile = udir / "emptycert.pem"
         tmpfile.write(SSL_EMPTYCERT)
         cls.w_emptycert = cls.space.wrap(str(tmpfile))
+        tmpfile = udir / "cert.passwd.pem"
+        tmpfile.write(SSL_CERTIFICATE_PROTECTED)
+        cls.w_cert_protected = cls.space.wrap(str(tmpfile))
         cls.w_dh512 = cls.space.wrap(os.path.join(
             os.path.dirname(__file__), 'dh512.pem'))
 
@@ -301,6 +304,15 @@
         raises(IOError, ctx.load_cert_chain, "inexistent.pem")
         raises(_ssl.SSLError, ctx.load_cert_chain, self.badcert)
         raises(_ssl.SSLError, ctx.load_cert_chain, self.emptycert)
+        # Password protected key and cert
+        raises(_ssl.SSLError, ctx.load_cert_chain, self.cert_protected,
+               password="badpass")
+        ctx.load_cert_chain(self.cert_protected, password="somepass")
+        ctx.load_cert_chain(self.cert_protected, password=lambda: "somepass")
+        raises(_ssl.SSLError, ctx.load_cert_chain, self.cert_protected,
+               password=lambda: "badpass")
+        raises(TypeError, ctx.load_cert_chain, self.cert_protected,
+               password=lambda: 3)
 
     def test_load_verify_locations(self):
         import _ssl
@@ -452,3 +464,38 @@
 -----END CERTIFICATE-----
 """
 SSL_EMPTYCERT = ""
+SSL_CERTIFICATE_PROTECTED = """
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,1A8D9D2A02EC698A
+
+kJYbfZ8L0sfe9Oty3gw0aloNnY5E8fegRfQLZlNoxTl6jNt0nIwI8kDJ36CZgR9c
+u3FDJm/KqrfUoz8vW+qEnWhSG7QPX2wWGPHd4K94Yz/FgrRzZ0DoK7XxXq9gOtVA
+AVGQhnz32p+6WhfGsCr9ArXEwRZrTk/FvzEPaU5fHcoSkrNVAGX8IpSVkSDwEDQr
+Gv17+cfk99UV1OCza6yKHoFkTtrC+PZU71LomBabivS2Oc4B9hYuSR2hF01wTHP+
+YlWNagZOOVtNz4oKK9x9eNQpmfQXQvPPTfusexKIbKfZrMvJoxcm1gfcZ0H/wK6P
+6wmXSG35qMOOztCZNtperjs1wzEBXznyK8QmLcAJBjkfarABJX9vBEzZV0OUKhy+
+noORFwHTllphbmydLhu6ehLUZMHPhzAS5UN7srtpSN81eerDMy0RMUAwA7/PofX1
+94Me85Q8jP0PC9ETdsJcPqLzAPETEYu0ELewKRcrdyWi+tlLFrpE5KT/s5ecbl9l
+7B61U4Kfd1PIXc/siINhU3A3bYK+845YyUArUOnKf1kEox7p1RpD7yFqVT04lRTo
+cibNKATBusXSuBrp2G6GNuhWEOSafWCKJQAzgCYIp6ZTV2khhMUGppc/2H3CF6cO
+zX0KtlPVZC7hLkB6HT8SxYUwF1zqWY7+/XPPdc37MeEZ87Q3UuZwqORLY+Z0hpgt
+L5JXBCoklZhCAaN2GqwFLXtGiRSRFGY7xXIhbDTlE65Wv1WGGgDLMKGE1gOz3yAo
+2jjG1+yAHJUdE69XTFHSqSkvaloA1W03LdMXZ9VuQJ/ySXCie6ABAQ==
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIICVDCCAb2gAwIBAgIJANfHOBkZr8JOMA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNV
+BAYTAlhZMRcwFQYDVQQHEw5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9u
+IFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMDEw
+MDgyMzAxNTZaFw0yMDEwMDUyMzAxNTZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQH
+Ew5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9uIFNvZnR3YXJlIEZvdW5k
+YXRpb24xEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEA21vT5isq7F68amYuuNpSFlKDPrMUCa4YWYqZRt2OZ+/3NKaZ2xAiSwr7
+6MrQF70t5nLbSPpqE5+5VrS58SY+g/sXLiFd6AplH1wJZwh78DofbFYXUggktFMt
+pTyiX8jtP66bkcPkDADA089RI1TQR6Ca+n7HFa7c1fabVV6i3zkCAwEAAaMYMBYw
+FAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBBQUAA4GBAHPctQBEQ4wd
+BJ6+JcpIraopLn8BGhbjNWj40mmRqWB/NAWF6M5ne7KpGAu7tLeG4hb1zLaldK8G
+lxy2GPSRF6LFS48dpEj2HbMv2nvv6xxalDMJ9+DicWgAKTQ6bcX2j3GUkCR0g/T1
+CRlNBAAlvhKzO7Clpf9l0YKBEfraJByX
+-----END CERTIFICATE-----
+"""
diff --git a/rpython/rlib/ropenssl.py b/rpython/rlib/ropenssl.py
--- a/rpython/rlib/ropenssl.py
+++ b/rpython/rlib/ropenssl.py
@@ -261,6 +261,9 @@
 ssl_external('SSL_CTX_load_verify_locations', [SSL_CTX, rffi.CCHARP, rffi.CCHARP], rffi.INT)
 ssl_external('SSL_CTX_check_private_key', [SSL_CTX], rffi.INT)
 ssl_external('SSL_CTX_set_session_id_context', [SSL_CTX, rffi.CCHARP, rffi.UINT], rffi.INT)
+pem_password_cb = lltype.Ptr(lltype.FuncType([rffi.CCHARP, rffi.INT, rffi.INT, rffi.VOIDP], rffi.INT))
+ssl_external('SSL_CTX_set_default_passwd_cb', [SSL_CTX, pem_password_cb], lltype.Void)
+ssl_external('SSL_CTX_set_default_passwd_cb_userdata', [SSL_CTX, rffi.VOIDP], lltype.Void)
 SSL_CTX_STATS_NAMES = """
     number connect connect_good connect_renegotiate accept accept_good
     accept_renegotiate hits misses timeouts cache_full""".split()
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit
[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic