[pypy-commit] pypy py3.3: SSL: Add support for npn_protocols

amauryfa noreply at buildbot.pypy.org
Thu Dec 18 00:38:20 CET 2014


Author: Amaury Forgeot d'Arc <amauryfa at gmail.com>
Branch: py3.3
Changeset: r75001:c62ae9b1f120
Date: 2014-12-16 18:40 +0100
http://bitbucket.org/pypy/pypy/changeset/c62ae9b1f120/

Log:	SSL: Add support for npn_protocols

diff --git a/lib-python/3/test/test_ssl.py b/lib-python/3/test/test_ssl.py
--- a/lib-python/3/test/test_ssl.py
+++ b/lib-python/3/test/test_ssl.py
@@ -1830,9 +1830,9 @@
                         outdata = s.read()
                         if outdata != indata.lower():
                             self.fail(
-                                "While sending with <<{name:s}>> bad data "
-                                "<<{outdata:r}>> ({nout:d}) received; "
-                                "expected <<{indata:r}>> ({nin:d})\n".format(
+                                "While sending with <<{name}>> bad data "
+                                "<<{outdata!r}>> ({nout}) received; "
+                                "expected <<{indata!r}>> ({nin})\n".format(
                                     name=meth_name, outdata=outdata[:20],
                                     nout=len(outdata),
                                     indata=indata[:20], nin=len(indata)
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,9 +1,10 @@
 import weakref
 
 from rpython.rlib import rpoll, rsocket
-from rpython.rlib.rarithmetic import intmask
+from rpython.rlib.rarithmetic import intmask, widen, r_uint
 from rpython.rlib.ropenssl import *
 from rpython.rlib.rposix import get_errno, set_errno
+from rpython.rlib.rweakref import RWeakValueDictionary
 from rpython.rtyper.lltypesystem import lltype, rffi
 
 from pypy.interpreter.baseobjspace import W_Root
@@ -69,7 +70,7 @@
 constants["OP_NO_TLSv1"] = SSL_OP_NO_TLSv1
 constants["HAS_SNI"] = HAS_SNI
 constants["HAS_ECDH"] = True  # To break the test suite
-constants["HAS_NPN"] = True  # To break the test suite
+constants["HAS_NPN"] = HAS_NPN
 constants["HAS_TLS_UNIQUE"] = True  # To break the test suite
 
 # OpenSSL version
@@ -95,6 +96,53 @@
                                       space.wrap(errno), space.wrap(msg))
     return OperationError(w_errtype, w_exception)
 
+class SSLNpnProtocols(object):
+
+    def __init__(self, ctx, protos):
+        self.protos = protos
+        self.buf, self.pinned, self.is_raw = rffi.get_nonmovingbuffer(protos)
+        NPN_STORAGE.set(r_uint(rffi.cast(rffi.UINT, self.buf)), self)
+
+        # set both server and client callbacks, because the context
+        # can be used to create both types of sockets
+        libssl_SSL_CTX_set_next_protos_advertised_cb(
+            ctx, self.advertiseNPN_cb, self.buf)
+        libssl_SSL_CTX_set_next_proto_select_cb(
+            ctx, self.selectNPN_cb, self.buf)
+
+    def __del__(self):
+        rffi.free_nonmovingbuffer(
+            self.protos, self.buf, self.pinned, self.is_raw)    
+
+    @staticmethod
+    def advertiseNPN_cb(s, data_ptr, len_ptr, args):
+        npn = NPN_STORAGE.get(r_uint(rffi.cast(rffi.UINT, args)))
+        if npn and npn.protos:
+            data_ptr[0] = npn.buf
+            len_ptr[0] = rffi.cast(rffi.UINT, len(npn.protos))
+        else:
+            data_ptr[0] = lltype.nullptr(rffi.CCHARP.TO)
+            len_ptr[0] = rffi.cast(rffi.UINT, 0)
+
+        return rffi.cast(rffi.INT, SSL_TLSEXT_ERR_OK)
+
+    @staticmethod
+    def selectNPN_cb(s, out_ptr, outlen_ptr, server, server_len, args):
+        npn = NPN_STORAGE.get(r_uint(rffi.cast(rffi.UINT, args)))
+        if npn and npn.protos:
+            client = npn.buf
+            client_len = len(npn.protos)
+        else:
+            client = lltype.nullptr(rffi.CCHARP.TO)
+            client_len = 0            
+
+        libssl_SSL_select_next_proto(out_ptr, outlen_ptr,
+                                     server, server_len,
+                                     client, client_len)
+        return rffi.cast(rffi.INT, SSL_TLSEXT_ERR_OK)
+
+NPN_STORAGE = RWeakValueDictionary(r_uint, SSLNpnProtocols)
+
 
 class SSLContext(W_Root):
     ctx = lltype.nullptr(SSL_CTX.TO)
@@ -277,6 +325,13 @@
         if ret != 1:
             raise _ssl_seterror(space, None, -1)
 
+    @unwrap_spec(protos='bufferstr')
+    def set_npn_protocols_w(self, space, protos):
+        if not HAS_NPN:
+            raise oefmt(space.w_NotImplementedError,
+                        "The NPN extension requires OpenSSL 1.0.1 or later.")
+
+        self.npn_protocols = SSLNpnProtocols(self.ctx, protos)
 
 SSLContext.typedef = TypeDef(
     "_SSLContext",
@@ -291,6 +346,7 @@
     load_verify_locations = interp2app(SSLContext.load_verify_locations_w),
     session_stats = interp2app(SSLContext.session_stats_w),
     set_default_verify_paths=interp2app(SSLContext.set_default_verify_paths_w),
+    _set_npn_protocols=interp2app(SSLContext.set_npn_protocols_w),
 )
 
     
@@ -640,6 +696,15 @@
             else:
                 return _decode_certificate(space, self.peer_cert)
 
+    def selected_npn_protocol(self, space):
+        with lltype.scoped_alloc(rffi.CCHARPP.TO, 1) as out_ptr:
+            with lltype.scoped_alloc(rffi.UINTP.TO, 1) as len_ptr:
+                libssl_SSL_get0_next_proto_negotiated(self.ssl,
+                                                      out_ptr, len_ptr)
+                if out_ptr[0]:
+                    return space.wrap(
+                        rffi.charpsize2str(out_ptr[0], widen(len_ptr[0])))
+
 def _decode_certificate(space, certificate, verbose=False):
     w_retval = space.newdict()
 
@@ -850,6 +915,7 @@
     shutdown = interp2app(SSLSocket.shutdown),
     cipher = interp2app(SSLSocket.cipher),
     peer_certificate = interp2app(SSLSocket.peer_certificate),
+    selected_npn_protocol = interp2app(SSLSocket.selected_npn_protocol),
 )
 
 
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
@@ -181,6 +181,15 @@
         self.s.close()
         del ss; gc.collect()
         
+    def test_npn_protocol(self):
+        import socket, _ssl, gc
+        ctx = _ssl._SSLContext()
+        ctx._set_npn_protocols(b'\x08http/1.1\x06spdy/2')
+        ss = ctx._wrap_socket(self.s, True,
+                              server_hostname="svn.python.org")
+        self.s.close()
+        del ss; gc.collect()
+
 
 class AppTestConnectedSSL_Timeout(AppTestConnectedSSL):
     # Same tests, with a socket timeout
diff --git a/rpython/rlib/ropenssl.py b/rpython/rlib/ropenssl.py
--- a/rpython/rlib/ropenssl.py
+++ b/rpython/rlib/ropenssl.py
@@ -80,6 +80,7 @@
     SSL_OP_SINGLE_DH_USE = rffi_platform.ConstantInteger(
         "SSL_OP_SINGLE_DH_USE")
     HAS_SNI = rffi_platform.Defined("SSL_CTRL_SET_TLSEXT_HOSTNAME")
+    HAS_NPN = rffi_platform.Defined("OPENSSL_NPN_NEGOTIATED")
     SSL_VERIFY_NONE = rffi_platform.ConstantInteger("SSL_VERIFY_NONE")
     SSL_VERIFY_PEER = rffi_platform.ConstantInteger("SSL_VERIFY_PEER")
     SSL_VERIFY_FAIL_IF_NO_PEER_CERT = rffi_platform.ConstantInteger("SSL_VERIFY_FAIL_IF_NO_PEER_CERT")
@@ -99,6 +100,7 @@
         "SSL_RECEIVED_SHUTDOWN")
     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")
+    SSL_TLSEXT_ERR_OK = rffi_platform.ConstantInteger("SSL_TLSEXT_ERR_OK")
 
     NID_subject_alt_name = rffi_platform.ConstantInteger("NID_subject_alt_name")
     GEN_DIRNAME = rffi_platform.ConstantInteger("GEN_DIRNAME")
@@ -312,6 +314,24 @@
 ssl_external('PEM_read_bio_X509_AUX',
              [BIO, rffi.VOIDP, rffi.VOIDP, rffi.VOIDP], X509)
 
+if HAS_NPN:
+    SSL_NEXT_PROTOS_ADV_CB = lltype.Ptr(lltype.FuncType(
+        [SSL, rffi.CCHARPP, rffi.UINTP, rffi.VOIDP], rffi.INT))
+    ssl_external('SSL_CTX_set_next_protos_advertised_cb',
+                 [SSL_CTX, SSL_NEXT_PROTOS_ADV_CB, rffi.VOIDP], lltype.Void)
+    SSL_NEXT_PROTOS_SEL_CB = lltype.Ptr(lltype.FuncType(
+        [SSL, rffi.CCHARPP, rffi.UCHARP, rffi.CCHARP, rffi.UINT, rffi.VOIDP],
+        rffi.INT))
+    ssl_external('SSL_CTX_set_next_proto_select_cb',
+                 [SSL_CTX, SSL_NEXT_PROTOS_SEL_CB, rffi.VOIDP], lltype.Void)
+    ssl_external(
+        'SSL_select_next_proto', [rffi.CCHARPP, rffi.UCHARP,
+                                  rffi.CCHARP, rffi.UINT,
+                                  rffi.CCHARP, rffi.UINT], rffi.INT)
+    ssl_external(
+        'SSL_get0_next_proto_negotiated', [
+            SSL, rffi.CCHARPP, rffi.UINTP], lltype.Void)
+
 EVP_MD_CTX = rffi.COpaquePtr('EVP_MD_CTX', compilation_info=eci)
 EVP_MD     = lltype.Ptr(EVP_MD_st)
 


More information about the pypy-commit mailing list