forked from twisted/twisted
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge tls-alpn-npn-7860-25: Add NPN and ALPN support for the TLS tran…
…sport. Author: lukasa Reviewers: hawkowl, adiroiban, glyph Fixes: twisted#7860 git-svn-id: svn://svn.twistedmatrix.com/svn/Twisted/trunk@46146 bbbe8e31-12d6-0310-92fd-ac37d47ddeeb
- Loading branch information
adiroiban
committed
Nov 12, 2015
1 parent
19fbb8e
commit 2f67bc3
Showing
10 changed files
with
823 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
#!/usr/bin/env python | ||
# Copyright (c) Twisted Matrix Laboratories. | ||
# See LICENSE for details. | ||
""" | ||
tls_alpn_npn_client | ||
~~~~~~~~~~~~~~~~~~~ | ||
This test script demonstrates the usage of the acceptableProtocols API as a | ||
client peer. | ||
It performs next protocol negotiation using NPN and ALPN. | ||
It will print what protocol was negotiated and exit. | ||
The global variables are provided as input values. | ||
This is set up to run against the server from | ||
tls_alpn_npn_server.py from the directory that contains this example. | ||
It assumes that you have a self-signed server certificate, named | ||
`server-cert.pem` and located in the working directory. | ||
""" | ||
from twisted.internet import ssl, protocol, endpoints, task, defer | ||
from twisted.python.filepath import FilePath | ||
|
||
# The hostname the remote server to contact. | ||
TARGET_HOST = u'localhost' | ||
|
||
# The port to contact. | ||
TARGET_PORT = 8080 | ||
|
||
# The list of protocols we'd be prepared to speak after the TLS negotiation is | ||
# complete. | ||
# The order of the protocols here is an order of preference: most servers will | ||
# attempt to respect our preferences when doing the negotiation. This indicates | ||
# that we'd prefer to use HTTP/2 if possible (where HTTP/2 is using the token | ||
# 'h2'), but would also accept HTTP/1.1. | ||
# The bytes here are sent literally on the wire, and so there is no room for | ||
# ambiguity about text encodings. | ||
# Try changing this list by adding, removing, and reordering protocols to see | ||
# how it affects the result. | ||
ACCEPTABLE_PROTOCOLS = [b'h2', b'http/1.1'] | ||
|
||
# Some safe initial data to send. This data is specific to HTTP/2: it is part | ||
# of the HTTP/2 client preface (see RFC 7540 Section 3.5). This is used to | ||
# signal to the remote server that it is aiming to speak HTTP/2, and to prevent | ||
# a remote HTTP/1.1 server from expecting a 'proper' HTTP/1.1 request. | ||
# | ||
# FIXME: https://twistedmatrix.com/trac/ticket/6024 | ||
# This is only required because there is no event that fires when the TLS | ||
# handshake is done. Instead, we wait for one that is implicitly after the | ||
# TLS handshake is done: dataReceived. To trigger the remote peer to send data, | ||
# we send some ourselves. | ||
TLS_TRIGGER_DATA = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' | ||
|
||
|
||
def main(reactor): | ||
certData = FilePath('server-cert.pem').getContent() | ||
serverCertificate = ssl.Certificate.loadPEM(certData) | ||
options = ssl.optionsForClientTLS( | ||
hostname=TARGET_HOST, | ||
trustRoot=serverCertificate, | ||
# `acceptableProtocols` is the targetted option for this example. | ||
acceptableProtocols=ACCEPTABLE_PROTOCOLS, | ||
) | ||
|
||
class BasicH2Request(protocol.Protocol): | ||
def connectionMade(self): | ||
print("Connection made") | ||
# Add a deferred that fires where we're done with the connection. | ||
# This deferred is returned to the reactor, and when we call it | ||
# back the reactor will clean up the protocol. | ||
self.complete = defer.Deferred() | ||
|
||
# Write some data to trigger the SSL handshake. | ||
self.transport.write(TLS_TRIGGER_DATA) | ||
|
||
def dataReceived(self, data): | ||
# We can only safely be sure what the next protocol is when we know | ||
# the TLS handshake is over. This is generally *not* in the call to | ||
# connectionMade, but instead only when we've received some data | ||
# back. | ||
print('Next protocol is: %s' % self.transport.negotiatedProtocol) | ||
self.transport.loseConnection() | ||
|
||
# If this is the first data write, we can tell the reactor we're | ||
# done here by firing the callback we gave it. | ||
if self.complete is not None: | ||
self.complete.callback(None) | ||
self.complete = None | ||
|
||
def connectionLost(self, reason): | ||
# If we haven't received any data, an error occurred. Otherwise, | ||
# we lost the connection on purpose. | ||
if self.complete is not None: | ||
print("Connection lost due to error %s" % (reason,)) | ||
self.complete.callback(None) | ||
else: | ||
print("Connection closed cleanly") | ||
|
||
return endpoints.connectProtocol( | ||
endpoints.SSL4ClientEndpoint( | ||
reactor, | ||
TARGET_HOST, | ||
TARGET_PORT, | ||
options | ||
), | ||
BasicH2Request() | ||
).addCallback(lambda protocol: protocol.complete) | ||
|
||
task.react(main) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
#!/usr/bin/env python | ||
# Copyright (c) Twisted Matrix Laboratories. | ||
# See LICENSE for details. | ||
""" | ||
tls_alpn_npn_server | ||
~~~~~~~~~~~~~~~~~~~ | ||
This test script demonstrates the usage of the acceptableProtocols API as a | ||
server peer. | ||
It performs next protocol negotiation using NPN and ALPN. | ||
It will print what protocol was negotiated for each connection that is made to | ||
it. | ||
To exit the server, use CTRL+C on the command-line. | ||
Before using this, you should generate a new RSA private key and an associated | ||
X.509 certificate and place it in the working directory as `server-key.pem` | ||
and `server-cert.pem`. | ||
You can generate a self signed certificate using OpenSSL: | ||
openssl req -new -newkey rsa:2048 -days 3 -nodes -x509 \ | ||
-keyout server-key.pem -out server-cert.pem | ||
To test this, use OpenSSL's s_client command, with either or both of the | ||
-nextprotoneg and -alpn arguments. For example: | ||
openssl s_client -connect localhost:8080 -alpn h2,http/1.1 | ||
openssl s_client -connect localhost:8080 -nextprotoneg h2,http/1.1 | ||
Alternatively, use the tls_alpn_npn_client.py script found in the examples | ||
directory. | ||
""" | ||
from OpenSSL import crypto | ||
|
||
from twisted.internet.endpoints import SSL4ServerEndpoint | ||
from twisted.internet.protocol import Protocol, Factory | ||
from twisted.internet import reactor, ssl | ||
from twisted.python.filepath import FilePath | ||
|
||
|
||
# The list of protocols we'd be prepared to speak after the TLS negotiation is | ||
# complete. | ||
# The order of the protocols here is an order of preference. This indicates | ||
# that we'd prefer to use HTTP/2 if possible (where HTTP/2 is using the token | ||
# 'h2'), but would also accept HTTP/1.1. | ||
# The bytes here are sent literally on the wire, and so there is no room for | ||
# ambiguity about text encodings. | ||
# Try changing this list by adding, removing, and reordering protocols to see | ||
# how it affects the result. | ||
ACCEPTABLE_PROTOCOLS = [b'h2', b'http/1.1'] | ||
|
||
# The port that the server will listen on. | ||
LISTEN_PORT = 8080 | ||
|
||
|
||
|
||
class NPNPrinterProtocol(Protocol): | ||
""" | ||
This protocol accepts incoming connections and waits for data. When | ||
received, it prints what the negotiated protocol is, echoes the data back, | ||
and then terminates the connection. | ||
""" | ||
def connectionMade(self): | ||
self.complete = False | ||
print("Connection made") | ||
|
||
|
||
def dataReceived(self, data): | ||
print(self.transport.negotiatedProtocol) | ||
self.transport.write(data) | ||
self.complete = True | ||
self.transport.loseConnection() | ||
|
||
|
||
def connectionLost(self, reason): | ||
# If we haven't received any data, an error occurred. Otherwise, | ||
# we lost the connection on purpose. | ||
if self.complete: | ||
print("Connection closed cleanly") | ||
else: | ||
print("Connection lost due to error %s" % (reason,)) | ||
|
||
|
||
|
||
class ResponderFactory(Factory): | ||
def buildProtocol(self, addr): | ||
return NPNPrinterProtocol() | ||
|
||
|
||
|
||
privateKeyData = FilePath('server-key.pem').getContent() | ||
privateKey = crypto.load_privatekey(crypto.FILETYPE_PEM, privateKeyData) | ||
certData = FilePath('server-cert.pem').getContent() | ||
certificate = crypto.load_certificate(crypto.FILETYPE_PEM, certData) | ||
|
||
options = ssl.CertificateOptions( | ||
privateKey=privateKey, | ||
certificate=certificate, | ||
acceptableProtocols=ACCEPTABLE_PROTOCOLS, | ||
) | ||
endpoint = SSL4ServerEndpoint(reactor, LISTEN_PORT, options) | ||
endpoint.listen(ResponderFactory()) | ||
reactor.run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.