Skip to content

Commit 78430db

Browse files
authored
Merge pull request #437 from inikolcev/tls12_session_tickets
Implementation of RFC5077 SessionTickets resumption.
2 parents efc997e + 3e89340 commit 78430db

File tree

12 files changed

+719
-115
lines changed

12 files changed

+719
-115
lines changed

scripts/tls.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -324,19 +324,19 @@ def handleArgs(argv, argString, flagsList=[]):
324324
def printGoodConnection(connection, seconds):
325325
print(" Handshake time: %.3f seconds" % seconds)
326326
print(" Version: %s" % connection.getVersionName())
327-
print(" Cipher: %s %s" % (connection.getCipherName(),
327+
print(" Cipher: %s %s" % (connection.getCipherName(),
328328
connection.getCipherImplementation()))
329329
print(" Ciphersuite: {0}".\
330330
format(CipherSuite.ietfNames[connection.session.cipherSuite]))
331331
if connection.session.srpUsername:
332332
print(" Client SRP username: %s" % connection.session.srpUsername)
333333
if connection.session.clientCertChain:
334-
print(" Client X.509 SHA1 fingerprint: %s" %
334+
print(" Client X.509 SHA1 fingerprint: %s" %
335335
connection.session.clientCertChain.getFingerprint())
336336
else:
337337
print(" No client certificate provided by peer")
338338
if connection.session.serverCertChain:
339-
print(" Server X.509 SHA1 fingerprint: %s" %
339+
print(" Server X.509 SHA1 fingerprint: %s" %
340340
connection.session.serverCertChain.getFingerprint())
341341
if connection.version >= (3, 3) and connection.serverSigAlg is not None:
342342
scheme = SignatureScheme.toRepr(connection.serverSigAlg)
@@ -352,20 +352,21 @@ def printGoodConnection(connection, seconds):
352352
print(" DH group size: {0} bits".format(connection.dhGroupSize))
353353
if connection.session.serverName:
354354
print(" SNI: %s" % connection.session.serverName)
355-
if connection.session.tackExt:
355+
if connection.session.tackExt:
356356
if connection.session.tackInHelloExt:
357357
emptyStr = "\n (via TLS Extension)"
358358
else:
359-
emptyStr = "\n (via TACK Certificate)"
359+
emptyStr = "\n (via TACK Certificate)"
360360
print(" TACK: %s" % emptyStr)
361361
print(str(connection.session.tackExt))
362362
if connection.session.appProto:
363363
print(" Application Layer Protocol negotiated: {0}".format(
364364
connection.session.appProto.decode('utf-8')))
365-
print(" Next-Protocol Negotiated: %s" % connection.next_proto)
365+
print(" Next-Protocol Negotiated: %s" % connection.next_proto)
366366
print(" Encrypt-then-MAC: {0}".format(connection.encryptThenMAC))
367367
print(" Extended Master Secret: {0}".format(
368368
connection.extendedMasterSecret))
369+
print(" Session Resumed: {0}".format(connection.resumed))
369370

370371
def printExporter(connection, expLabel, expLength):
371372
if expLabel is None:
@@ -464,12 +465,9 @@ def clientCmd(argv):
464465
# unreasumable, override it
465466
session.resumable = True
466467

467-
print("Received {0} ticket[s]".format(len(connection.tickets)))
468+
print("Received {0} ticket[s]".format(len(connection.tickets) + len(connection.tls_1_0_tickets)))
468469
assert connection.tickets is session.tickets
469470

470-
if not session.tickets:
471-
return
472-
473471
if not resumption:
474472
return
475473

@@ -480,11 +478,10 @@ def clientCmd(argv):
480478
sock.connect(address)
481479
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
482480
connection = TLSConnection(sock)
483-
484481
try:
485482
start = time_stamp()
486483
connection.handshakeClientCert(serverName=address[0], alpn=alpn,
487-
session=session)
484+
session=session, settings=settings)
488485
stop = time_stamp()
489486
print("Handshake success")
490487
except TLSLocalAlert as a:

tests/tlstest.py

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,22 +1493,26 @@ def connect():
14931493
testConnClient(connection)
14941494
assert(isinstance(connection.session.serverCertChain, X509CertChain))
14951495
assert(connection.session.serverName == address[0])
1496+
assert(connection.version == (3, 3))
14961497
assert(not connection.resumed)
14971498
assert(connection.encryptThenMAC)
1499+
assert(connection.session.tls_1_0_tickets)
14981500
connection.close()
14991501
session = connection.session
15001502

15011503
# resume
15021504
synchro.recv(1)
15031505
connection = connect()
15041506
settings = HandshakeSettings()
1507+
settings.macNames.remove("aead")
15051508
settings.maxVersion = (3, 3)
15061509
connection.handshakeClientCert(serverName=address[0], session=session,
15071510
settings=settings)
15081511
testConnClient(connection)
15091512
assert(isinstance(connection.session.serverCertChain, X509CertChain))
15101513
assert(connection.session.serverName == address[0])
15111514
assert(connection.resumed)
1515+
assert(connection.session.encryptThenMAC)
15121516
assert(connection.encryptThenMAC)
15131517
connection.close()
15141518

@@ -1548,6 +1552,53 @@ def connect():
15481552

15491553
test_no += 1
15501554

1555+
print("Test {0} - session_ticket resumption in TLSv1.2".format(test_no))
1556+
synchro.recv(1)
1557+
connection = connect()
1558+
settings = HandshakeSettings()
1559+
connection.handshakeClientCert(serverName=address[0], settings=settings)
1560+
testConnClient(connection)
1561+
assert isinstance(connection.session.serverCertChain, X509CertChain)
1562+
assert connection.session.serverName == address[0]
1563+
assert not connection.resumed
1564+
session = connection.session
1565+
connection.close()
1566+
1567+
# resume
1568+
synchro.recv(1)
1569+
settings = HandshakeSettings()
1570+
connection = connect()
1571+
connection.handshakeClientCert(serverName=address[0], settings=settings, session=session)
1572+
testConnClient(connection)
1573+
assert connection.resumed
1574+
connection.close()
1575+
1576+
test_no += 1
1577+
1578+
print("Test {0} - session_ticket resumption in TLSv1.2 "
1579+
"with expired ticket".format(test_no))
1580+
synchro.recv(1)
1581+
connection = connect()
1582+
settings = HandshakeSettings()
1583+
connection.handshakeClientCert(serverName=address[0], settings=settings)
1584+
testConnClient(connection)
1585+
assert isinstance(connection.session.serverCertChain, X509CertChain)
1586+
assert connection.session.serverName == address[0]
1587+
assert not connection.resumed
1588+
session = connection.session
1589+
connection.close()
1590+
1591+
# resume
1592+
synchro.recv(1)
1593+
settings = HandshakeSettings()
1594+
connection = connect()
1595+
connection.handshakeClientCert(serverName=address[0], settings=settings, session=session)
1596+
testConnClient(connection)
1597+
assert not connection.resumed
1598+
connection.close()
1599+
1600+
test_no += 1
1601+
15511602
print("Test {0} - resumption in TLSv1.3".format(test_no))
15521603
synchro.recv(1)
15531604
connection = connect()
@@ -3069,6 +3120,7 @@ def server_bind(self):
30693120
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
30703121
settings=settings)
30713122
testConnServer(connection)
3123+
assert(not connection.encryptThenMAC)
30723124
connection.close()
30733125

30743126
test_no += 1
@@ -3078,24 +3130,28 @@ def server_bind(self):
30783130
connection = connect()
30793131
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key)
30803132
testConnServer(connection)
3133+
assert(not connection.encryptThenMAC)
30813134
connection.close()
30823135

30833136
test_no += 1
30843137

30853138
print("Test {0} - resumption with EtM".format(test_no))
30863139
synchro.send(b'R')
30873140
sessionCache = SessionCache()
3141+
settings = HandshakeSettings()
3142+
settings.ticketKeys = [getRandomBytes(32)]
30883143
connection = connect()
30893144
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
3090-
sessionCache=sessionCache)
3145+
sessionCache=sessionCache, settings=settings)
30913146
testConnServer(connection)
3147+
assert(connection.encryptThenMAC)
30923148
connection.close()
30933149

30943150
# resume
30953151
synchro.send(b'R')
30963152
connection = connect()
30973153
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
3098-
sessionCache=sessionCache)
3154+
sessionCache=sessionCache, settings=settings)
30993155
testConnServer(connection)
31003156
connection.close()
31013157

@@ -3125,6 +3181,52 @@ def server_bind(self):
31253181

31263182
test_no += 1
31273183

3184+
print("Test {0} - session_ticket resumption in TLSv1.2".format(test_no))
3185+
synchro.send(b'R')
3186+
connection = connect()
3187+
settings = HandshakeSettings()
3188+
settings.maxVersion = (3, 3)
3189+
settings.ticketKeys = [getRandomBytes(32)]
3190+
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
3191+
settings=settings)
3192+
testConnServer(connection)
3193+
connection.close()
3194+
3195+
# resume
3196+
synchro.send(b'R')
3197+
connection = connect()
3198+
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
3199+
settings=settings)
3200+
testConnServer(connection)
3201+
connection.close()
3202+
3203+
test_no += 1
3204+
3205+
print("Test {0} - session_ticket resumption in TLSv1.2 "
3206+
"with expired ticket".format(test_no))
3207+
synchro.send(b'R')
3208+
connection = connect()
3209+
settings = HandshakeSettings()
3210+
settings.ticketLifetime = 1
3211+
settings.maxVersion = (3, 3)
3212+
settings.ticketKeys = [getRandomBytes(32)]
3213+
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
3214+
settings=settings)
3215+
testConnServer(connection)
3216+
connection.close()
3217+
3218+
time.sleep(2)
3219+
3220+
# resume
3221+
synchro.send(b'R')
3222+
connection = connect()
3223+
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
3224+
settings=settings)
3225+
testConnServer(connection)
3226+
connection.close()
3227+
3228+
test_no += 1
3229+
31283230
print("Test {0} - resumption in TLSv1.3".format(test_no))
31293231
synchro.send(b'R')
31303232
connection = connect()
@@ -3155,6 +3257,7 @@ def server_bind(self):
31553257
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
31563258
reqCert=True, settings=settings)
31573259
testConnServer(connection)
3260+
assert connection.session.clientCertChain
31583261
connection.close()
31593262

31603263
# resume

tlslite/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ class ExtensionType(TLSEnum):
169169
encrypt_then_mac = 22 # RFC 7366
170170
extended_master_secret = 23 # RFC 7627
171171
record_size_limit = 28 # RFC 8449
172+
session_ticket = 35 # RFC 5077
172173
extended_random = 40 # draft-rescorla-tls-extended-random-02
173174
pre_shared_key = 41 # TLS 1.3
174175
early_data = 42 # TLS 1.3

tlslite/extensions.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,53 @@ def __init__(self):
21112111
2, 'record_size_limit', ExtensionType.record_size_limit)
21122112

21132113

2114+
class SessionTicketExtension(TLSExtension):
2115+
"""
2116+
Client and server session ticket extension from RFC 5077
2117+
"""
2118+
def __init__(self):
2119+
"""Create instance of the object."""
2120+
super(SessionTicketExtension, self).__init__(extType=ExtensionType.
2121+
session_ticket)
2122+
self.ticket = None
2123+
2124+
def create(self, ticket):
2125+
2126+
self.ticket = ticket
2127+
return self
2128+
2129+
@property
2130+
def extData(self):
2131+
"""Serialise the payload of the extension."""
2132+
if not self.ticket:
2133+
return bytearray(0)
2134+
2135+
w = Writer()
2136+
w.bytes += self.ticket
2137+
return w.bytes
2138+
2139+
def parse(self, parser):
2140+
"""
2141+
Parse the extension from on the wire format.
2142+
2143+
:param Parser parser: data to be parsed
2144+
2145+
:rtype: SessionTicketExtension
2146+
"""
2147+
if not parser.getRemainingLength():
2148+
self.ticket = bytearray(0)
2149+
return self
2150+
self.ticket = parser.getFixBytes(parser.getRemainingLength())
2151+
2152+
return self
2153+
2154+
def __repr__(self):
2155+
"""Return human readable representation of the extension."""
2156+
return "{0}({1}={2!r})".format(self.__class__.__name__,
2157+
"ticket",
2158+
self.ticket)
2159+
2160+
21142161
TLSExtension._universalExtensions = \
21152162
{
21162163
ExtensionType.server_name: SNIExtension,
@@ -2132,7 +2179,8 @@ def __init__(self):
21322179
ExtensionType.pre_shared_key: PreSharedKeyExtension,
21332180
ExtensionType.psk_key_exchange_modes: PskKeyExchangeModesExtension,
21342181
ExtensionType.cookie: CookieExtension,
2135-
ExtensionType.record_size_limit: RecordSizeLimitExtension}
2182+
ExtensionType.record_size_limit: RecordSizeLimitExtension,
2183+
ExtensionType.session_ticket: SessionTicketExtension}
21362184

21372185
TLSExtension._serverExtensions = \
21382186
{

tlslite/handshakesettings.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,11 @@ class HandshakeSettings(object):
321321
:ivar ticketLifetime: maximum allowed lifetime of ticket encryption key,
322322
in seconds. 1 day by default
323323
324+
:vartype ticket_count: int
325+
:ivar ticket_count: number of tickets the server will send to the client
326+
after establishing the connection in TLS 1.3. If a positive integer,
327+
it enabled support for ticket based resumption in TLS 1.2 and earlier.
328+
324329
:vartype psk_modes: list(str)
325330
:ivar psk_modes: acceptable modes for the PSK key exchange in TLS 1.3
326331

0 commit comments

Comments
 (0)