Skip to content

Commit 0358247

Browse files
authored
Merge pull request #132 from tomato42/alpn
Support for ALPN extension from RFC 7301
2 parents e6182d5 + 04b0186 commit 0358247

File tree

6 files changed

+342
-28
lines changed

6 files changed

+342
-28
lines changed

scripts/tls.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,13 @@ def printUsage(s=None):
7676
[--reqcert] HOST:PORT
7777
7878
client
79-
[-k KEY] [-c CERT] [-u USER] [-p PASS] [-l LABEL] [-L LENGTH]
79+
[-k KEY] [-c CERT] [-u USER] [-p PASS] [-l LABEL] [-L LENGTH] [-a ALPN]
8080
HOST:PORT
8181
8282
LABEL - TLS exporter label
8383
LENGTH - amount of info to export using TLS exporter
84+
ALPN - name of protocol for ALPN negotiation, can be present multiple times
85+
in client to specify multiple protocols supported
8486
""")
8587
sys.exit(-1)
8688

@@ -109,6 +111,7 @@ def handleArgs(argv, argString, flagsList=[]):
109111
directory = None
110112
expLabel = None
111113
expLength = 20
114+
alpn = []
112115

113116
for opt, arg in opts:
114117
if opt == "-k":
@@ -142,9 +145,14 @@ def handleArgs(argv, argString, flagsList=[]):
142145
expLabel = arg
143146
elif opt == "-L":
144147
expLength = int(arg)
148+
elif opt == "-a":
149+
alpn.append(bytearray(arg, 'utf-8'))
145150
else:
146151
assert(False)
147-
152+
153+
# when no names provided, don't return array
154+
if not alpn:
155+
alpn = None
148156
if not argv:
149157
printError("Missing address")
150158
if len(argv)>1:
@@ -178,6 +186,8 @@ def handleArgs(argv, argString, flagsList=[]):
178186
retList.append(expLabel)
179187
if "L" in argString:
180188
retList.append(expLength)
189+
if "a" in argString:
190+
retList.append(alpn)
181191
return retList
182192

183193

@@ -216,6 +226,9 @@ def printGoodConnection(connection, seconds):
216226
emptyStr = "\n (via TACK Certificate)"
217227
print(" TACK: %s" % emptyStr)
218228
print(str(connection.session.tackExt))
229+
if connection.session.appProto:
230+
print(" Application Layer Protocol negotiated: {0}".format(
231+
connection.session.appProto.decode('utf-8')))
219232
print(" Next-Protocol Negotiated: %s" % connection.next_proto)
220233
print(" Encrypt-then-MAC: {0}".format(connection.encryptThenMAC))
221234
print(" Extended Master Secret: {0}".format(
@@ -233,8 +246,8 @@ def printExporter(connection, expLabel, expLength):
233246

234247
def clientCmd(argv):
235248
(address, privateKey, certChain, username, password, expLabel,
236-
expLength) = \
237-
handleArgs(argv, "kcuplL")
249+
expLength, alpn) = \
250+
handleArgs(argv, "kcuplLa")
238251

239252
if (certChain and not privateKey) or (not certChain and privateKey):
240253
raise SyntaxError("Must specify CERT and KEY together")
@@ -261,7 +274,7 @@ def clientCmd(argv):
261274
settings=settings, serverName=address[0])
262275
else:
263276
connection.handshakeClientCert(certChain, privateKey,
264-
settings=settings, serverName=address[0])
277+
settings=settings, serverName=address[0], alpn=alpn)
265278
stop = time.clock()
266279
print("Handshake success")
267280
except TLSLocalAlert as a:
@@ -342,6 +355,7 @@ def handshake(self, connection):
342355
sessionCache=sessionCache,
343356
settings=settings,
344357
nextProtos=[b"http/1.1"],
358+
alpn=[bytearray(b'http/1.1')],
345359
reqCert=reqCert)
346360
# As an example (does not work here):
347361
#nextProtos=[b"spdy/3", b"spdy/2", b"http/1.1"])

tlslite/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class ExtensionType: # RFC 6066 / 4366
118118
ec_point_formats = 11 # RFC 4492
119119
srp = 12 # RFC 5054
120120
signature_algorithms = 13 # RFC 5246
121+
alpn = 16 # RFC 7301
121122
client_hello_padding = 21 # RFC 7685
122123
encrypt_then_mac = 22 # RFC 7366
123124
extended_master_secret = 23 # RFC 7627
@@ -283,6 +284,7 @@ class AlertDescription(TLSEnum):
283284
unsupported_extension = 110 # RFC 5246
284285
unrecognized_name = 112 # RFC 6066
285286
unknown_psk_identity = 115
287+
no_application_protocol = 120 # RFC 7301
286288

287289

288290
class CipherSuite:

tlslite/extensions.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,93 @@ def parse(self, parser):
11611161

11621162
return self
11631163

1164+
1165+
class ALPNExtension(TLSExtension):
1166+
"""
1167+
Handling of Application Layer Protocol Negotiation extension from RFC 7301.
1168+
1169+
@type protocol_names: list of bytearrays
1170+
@ivar protocol_names: list of protocol names acceptable or selected by peer
1171+
1172+
@type extType: int
1173+
@ivar extType: numberic type of ALPNExtension, i.e. 16
1174+
1175+
@type extData: bytearray
1176+
@ivar extData: raw encoding of the extension data
1177+
"""
1178+
1179+
def __init__(self):
1180+
"""
1181+
Create instance of ALPNExtension
1182+
1183+
See also: L{create} and L{parse}
1184+
"""
1185+
super(ALPNExtension, self).__init__(extType=ExtensionType.alpn)
1186+
1187+
self.protocol_names = None
1188+
1189+
def __repr__(self):
1190+
"""
1191+
Create programmer-readable representation of object
1192+
1193+
@rtype: str
1194+
"""
1195+
return "ALPNExtension(protocol_names={0!r})".format(self.protocol_names)
1196+
1197+
@property
1198+
def extData(self):
1199+
"""
1200+
Return encoded payload of the extension
1201+
1202+
@rtype: bytearray
1203+
"""
1204+
if self.protocol_names is None:
1205+
return bytearray(0)
1206+
1207+
writer = Writer()
1208+
for prot in self.protocol_names:
1209+
writer.add(len(prot), 1)
1210+
writer.bytes += prot
1211+
1212+
writer2 = Writer()
1213+
writer2.add(len(writer.bytes), 2)
1214+
writer2.bytes += writer.bytes
1215+
1216+
return writer2.bytes
1217+
1218+
def create(self, protocol_names=None):
1219+
"""
1220+
Create an instance of ALPNExtension with specified protocols
1221+
1222+
@type protocols: list of bytearray
1223+
@param protocols: list of protocol names that are to be sent
1224+
"""
1225+
self.protocol_names = protocol_names
1226+
return self
1227+
1228+
def parse(self, parser):
1229+
"""
1230+
Parse the extension from on the wire format
1231+
1232+
@type parser: L{tlslite.util.codec.Parser}
1233+
@param parser: data to be parsed as extension
1234+
1235+
@raise SyntaxError: when the encoding of the extension is self
1236+
inconsistent
1237+
1238+
@rtype: L{ALPNExtension}
1239+
"""
1240+
self.protocol_names = []
1241+
parser.startLengthCheck(2)
1242+
while not parser.atLengthCheck():
1243+
name_len = parser.get(1)
1244+
self.protocol_names.append(parser.getFixBytes(name_len))
1245+
parser.stopLengthCheck()
1246+
if parser.getRemainingLength() != 0:
1247+
raise SyntaxError("Trailing data after protocol_name_list")
1248+
return self
1249+
1250+
11641251
TLSExtension._universalExtensions = \
11651252
{
11661253
ExtensionType.server_name: SNIExtension,
@@ -1169,6 +1256,7 @@ def parse(self, parser):
11691256
ExtensionType.ec_point_formats: ECPointFormatsExtension,
11701257
ExtensionType.srp: SRPExtension,
11711258
ExtensionType.signature_algorithms: SignatureAlgorithmsExtension,
1259+
ExtensionType.alpn: ALPNExtension,
11721260
ExtensionType.supports_npn: NPNExtension,
11731261
ExtensionType.client_hello_padding: PaddingExtension,
11741262
ExtensionType.renegotiation_info: RenegotiationInfoExtension}

tlslite/session.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ class Session(object):
4646
@type encryptThenMAC: bool
4747
@ivar encryptThenMAC: True if connection uses CBC cipher in
4848
encrypt-then-MAC mode
49+
50+
@type appProto: bytearray
51+
@ivar appProto: name of the negotiated application level protocol, None
52+
if not negotiated
4953
"""
5054

5155
def __init__(self):
@@ -61,11 +65,13 @@ def __init__(self):
6165
self.resumable = False
6266
self.encryptThenMAC = False
6367
self.extendedMasterSecret = False
68+
self.appProto = bytearray(0)
6469

6570
def create(self, masterSecret, sessionID, cipherSuite,
6671
srpUsername, clientCertChain, serverCertChain,
6772
tackExt, tackInHelloExt, serverName, resumable=True,
68-
encryptThenMAC=False, extendedMasterSecret=False):
73+
encryptThenMAC=False, extendedMasterSecret=False,
74+
appProto=bytearray(0)):
6975
self.masterSecret = masterSecret
7076
self.sessionID = sessionID
7177
self.cipherSuite = cipherSuite
@@ -78,6 +84,7 @@ def create(self, masterSecret, sessionID, cipherSuite,
7884
self.resumable = resumable
7985
self.encryptThenMAC = encryptThenMAC
8086
self.extendedMasterSecret = extendedMasterSecret
87+
self.appProto = appProto
8188

8289
def _clone(self):
8390
other = Session()
@@ -93,6 +100,7 @@ def _clone(self):
93100
other.resumable = self.resumable
94101
other.encryptThenMAC = self.encryptThenMAC
95102
other.extendedMasterSecret = self.extendedMasterSecret
103+
other.appProto = self.appProto
96104
return other
97105

98106
def valid(self):

0 commit comments

Comments
 (0)