Skip to content

Commit

Permalink
implementation of session_ticket resumption in tls1.2
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Nikolchev authored and tomato42 committed Jul 31, 2023
1 parent b0afe1c commit 68ebaab
Show file tree
Hide file tree
Showing 12 changed files with 719 additions and 116 deletions.
21 changes: 9 additions & 12 deletions scripts/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,19 +324,19 @@ def handleArgs(argv, argString, flagsList=[]):
def printGoodConnection(connection, seconds):
print(" Handshake time: %.3f seconds" % seconds)
print(" Version: %s" % connection.getVersionName())
print(" Cipher: %s %s" % (connection.getCipherName(),
print(" Cipher: %s %s" % (connection.getCipherName(),
connection.getCipherImplementation()))
print(" Ciphersuite: {0}".\
format(CipherSuite.ietfNames[connection.session.cipherSuite]))
if connection.session.srpUsername:
print(" Client SRP username: %s" % connection.session.srpUsername)
if connection.session.clientCertChain:
print(" Client X.509 SHA1 fingerprint: %s" %
print(" Client X.509 SHA1 fingerprint: %s" %
connection.session.clientCertChain.getFingerprint())
else:
print(" No client certificate provided by peer")
if connection.session.serverCertChain:
print(" Server X.509 SHA1 fingerprint: %s" %
print(" Server X.509 SHA1 fingerprint: %s" %
connection.session.serverCertChain.getFingerprint())
if connection.version >= (3, 3) and connection.serverSigAlg is not None:
scheme = SignatureScheme.toRepr(connection.serverSigAlg)
Expand All @@ -352,20 +352,21 @@ def printGoodConnection(connection, seconds):
print(" DH group size: {0} bits".format(connection.dhGroupSize))
if connection.session.serverName:
print(" SNI: %s" % connection.session.serverName)
if connection.session.tackExt:
if connection.session.tackExt:
if connection.session.tackInHelloExt:
emptyStr = "\n (via TLS Extension)"
else:
emptyStr = "\n (via TACK Certificate)"
emptyStr = "\n (via TACK Certificate)"
print(" TACK: %s" % emptyStr)
print(str(connection.session.tackExt))
if connection.session.appProto:
print(" Application Layer Protocol negotiated: {0}".format(
connection.session.appProto.decode('utf-8')))
print(" Next-Protocol Negotiated: %s" % connection.next_proto)
print(" Next-Protocol Negotiated: %s" % connection.next_proto)
print(" Encrypt-then-MAC: {0}".format(connection.encryptThenMAC))
print(" Extended Master Secret: {0}".format(
connection.extendedMasterSecret))
print(" Session Resumed: {0}".format(connection.resumed))

def printExporter(connection, expLabel, expLength):
if expLabel is None:
Expand Down Expand Up @@ -464,12 +465,9 @@ def clientCmd(argv):
# unreasumable, override it
session.resumable = True

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

if not session.tickets:
return

if not resumption:
return

Expand All @@ -480,11 +478,10 @@ def clientCmd(argv):
sock.connect(address)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
connection = TLSConnection(sock)

try:
start = time_stamp()
connection.handshakeClientCert(serverName=address[0], alpn=alpn,
session=session)
session=session, settings=settings)
stop = time_stamp()
print("Handshake success")
except TLSLocalAlert as a:
Expand Down
107 changes: 105 additions & 2 deletions tests/tlstest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1446,22 +1446,26 @@ def connect():
testConnClient(connection)
assert(isinstance(connection.session.serverCertChain, X509CertChain))
assert(connection.session.serverName == address[0])
assert(connection.version == (3, 3))
assert(not connection.resumed)
assert(connection.encryptThenMAC)
assert(connection.session.tls_1_0_tickets)
connection.close()
session = connection.session

# resume
synchro.recv(1)
connection = connect()
settings = HandshakeSettings()
settings.macNames.remove("aead")
settings.maxVersion = (3, 3)
connection.handshakeClientCert(serverName=address[0], session=session,
settings=settings)
testConnClient(connection)
assert(isinstance(connection.session.serverCertChain, X509CertChain))
assert(connection.session.serverName == address[0])
assert(connection.resumed)
assert(connection.session.encryptThenMAC)
assert(connection.encryptThenMAC)
connection.close()

Expand Down Expand Up @@ -1501,6 +1505,53 @@ def connect():

test_no += 1

print("Test {0} - session_ticket resumption in TLSv1.2".format(test_no))
synchro.recv(1)
connection = connect()
settings = HandshakeSettings()
connection.handshakeClientCert(serverName=address[0], settings=settings)
testConnClient(connection)
assert isinstance(connection.session.serverCertChain, X509CertChain)
assert connection.session.serverName == address[0]
assert not connection.resumed
session = connection.session
connection.close()

# resume
synchro.recv(1)
settings = HandshakeSettings()
connection = connect()
connection.handshakeClientCert(serverName=address[0], settings=settings, session=session)
testConnClient(connection)
assert connection.resumed
connection.close()

test_no += 1

print("Test {0} - session_ticket resumption in TLSv1.2 "
"with expired ticket".format(test_no))
synchro.recv(1)
connection = connect()
settings = HandshakeSettings()
connection.handshakeClientCert(serverName=address[0], settings=settings)
testConnClient(connection)
assert isinstance(connection.session.serverCertChain, X509CertChain)
assert connection.session.serverName == address[0]
assert not connection.resumed
session = connection.session
connection.close()

# resume
synchro.recv(1)
settings = HandshakeSettings()
connection = connect()
connection.handshakeClientCert(serverName=address[0], settings=settings, session=session)
testConnClient(connection)
assert not connection.resumed
connection.close()

test_no += 1

print("Test {0} - resumption in TLSv1.3".format(test_no))
synchro.recv(1)
connection = connect()
Expand Down Expand Up @@ -2986,6 +3037,7 @@ def server_bind(self):
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
settings=settings)
testConnServer(connection)
assert(not connection.encryptThenMAC)
connection.close()

test_no += 1
Expand All @@ -2995,24 +3047,28 @@ def server_bind(self):
connection = connect()
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key)
testConnServer(connection)
assert(not connection.encryptThenMAC)
connection.close()

test_no += 1

print("Test {0} - resumption with EtM".format(test_no))
synchro.send(b'R')
sessionCache = SessionCache()
settings = HandshakeSettings()
settings.ticketKeys = [getRandomBytes(32)]
connection = connect()
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
sessionCache=sessionCache)
sessionCache=sessionCache, settings=settings)
testConnServer(connection)
assert(connection.encryptThenMAC)
connection.close()

# resume
synchro.send(b'R')
connection = connect()
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
sessionCache=sessionCache)
sessionCache=sessionCache, settings=settings)
testConnServer(connection)
connection.close()

Expand Down Expand Up @@ -3042,6 +3098,52 @@ def server_bind(self):

test_no += 1

print("Test {0} - session_ticket resumption in TLSv1.2".format(test_no))
synchro.send(b'R')
connection = connect()
settings = HandshakeSettings()
settings.maxVersion = (3, 3)
settings.ticketKeys = [getRandomBytes(32)]
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
settings=settings)
testConnServer(connection)
connection.close()

# resume
synchro.send(b'R')
connection = connect()
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
settings=settings)
testConnServer(connection)
connection.close()

test_no += 1

print("Test {0} - session_ticket resumption in TLSv1.2 "
"with expired ticket".format(test_no))
synchro.send(b'R')
connection = connect()
settings = HandshakeSettings()
settings.ticketLifetime = 1
settings.maxVersion = (3, 3)
settings.ticketKeys = [getRandomBytes(32)]
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
settings=settings)
testConnServer(connection)
connection.close()

time.sleep(2)

# resume
synchro.send(b'R')
connection = connect()
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
settings=settings)
testConnServer(connection)
connection.close()

test_no += 1

print("Test {0} - resumption in TLSv1.3".format(test_no))
synchro.send(b'R')
connection = connect()
Expand Down Expand Up @@ -3072,6 +3174,7 @@ def server_bind(self):
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
reqCert=True, settings=settings)
testConnServer(connection)
assert connection.session.clientCertChain
connection.close()

# resume
Expand Down
1 change: 1 addition & 0 deletions tlslite/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ class ExtensionType(TLSEnum):
encrypt_then_mac = 22 # RFC 7366
extended_master_secret = 23 # RFC 7627
record_size_limit = 28 # RFC 8449
session_ticket = 35 # RFC 5077
extended_random = 40 # draft-rescorla-tls-extended-random-02
pre_shared_key = 41 # TLS 1.3
early_data = 42 # TLS 1.3
Expand Down
50 changes: 49 additions & 1 deletion tlslite/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2111,6 +2111,53 @@ def __init__(self):
2, 'record_size_limit', ExtensionType.record_size_limit)


class SessionTicketExtension(TLSExtension):
"""
Client and server session ticket extension from RFC 5077
"""
def __init__(self):
"""Create instance of the object."""
super(SessionTicketExtension, self).__init__(extType=ExtensionType.
session_ticket)
self.ticket = None

def create(self, ticket):

self.ticket = ticket
return self

@property
def extData(self):
"""Serialise the payload of the extension."""
if not self.ticket:
return bytearray(0)

w = Writer()
w.bytes += self.ticket
return w.bytes

def parse(self, parser):
"""
Parse the extension from on the wire format.
:param Parser parser: data to be parsed
:rtype: SessionTicketExtension
"""
if not parser.getRemainingLength():
self.ticket = bytearray(0)
return self
self.ticket = parser.getFixBytes(parser.getRemainingLength())

return self

def __repr__(self):
"""Return human readable representation of the extension."""
return "{0}({1}={2!r})".format(self.__class__.__name__,
"ticket",
self.ticket)


TLSExtension._universalExtensions = \
{
ExtensionType.server_name: SNIExtension,
Expand All @@ -2132,7 +2179,8 @@ def __init__(self):
ExtensionType.pre_shared_key: PreSharedKeyExtension,
ExtensionType.psk_key_exchange_modes: PskKeyExchangeModesExtension,
ExtensionType.cookie: CookieExtension,
ExtensionType.record_size_limit: RecordSizeLimitExtension}
ExtensionType.record_size_limit: RecordSizeLimitExtension,
ExtensionType.session_ticket: SessionTicketExtension}

TLSExtension._serverExtensions = \
{
Expand Down
5 changes: 5 additions & 0 deletions tlslite/handshakesettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,11 @@ class HandshakeSettings(object):
:ivar ticketLifetime: maximum allowed lifetime of ticket encryption key,
in seconds. 1 day by default
:vartype ticket_count: int
:ivar ticket_count: number of tickets the server will send to the client
after establishing the connection in TLS 1.3. If a positive integer,
it enabled support for ticket based resumption in TLS 1.2 and earlier.
:vartype psk_modes: list(str)
:ivar psk_modes: acceptable modes for the PSK key exchange in TLS 1.3
Expand Down
Loading

0 comments on commit 68ebaab

Please sign in to comment.