[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