Skip to content

Commit a789b3f

Browse files
committed
client side support for post handshake authentication
1 parent 1abaefe commit a789b3f

File tree

3 files changed

+109
-4
lines changed

3 files changed

+109
-4
lines changed

tlslite/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ class ExtensionType(TLSEnum):
171171
supported_versions = 43 # TLS 1.3
172172
cookie = 44 # TLS 1.3
173173
psk_key_exchange_modes = 45 # TLS 1.3
174+
post_handshake_auth = 49 # TLS 1.3
174175
signature_algorithms_cert = 50 # TLS 1.3
175176
key_share = 51 # TLS 1.3
176177
supports_npn = 13172

tlslite/tlsconnection.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,14 @@ def _clientSendClientHello(self, settings, session, srpUsername,
717717
session_id = bytearray()
718718
# when TLS 1.3 advertised, add key shares, set fake session_id
719719
if next((i for i in settings.versions if i > (3, 3)), None):
720+
# if we have a client cert configured, do indicate we're willing
721+
# to perform Post Handshake Authentication
722+
if certParams and certParams[1]:
723+
extensions.append(TLSExtension(
724+
extType=ExtensionType.post_handshake_auth).
725+
create(bytearray(b'')))
726+
self._client_keypair = certParams
727+
720728
session_id = getRandomBytes(32)
721729
extensions.append(SupportedVersionsExtension().
722730
create(settings.versions))
@@ -1435,6 +1443,8 @@ def _clientTLS13Handshake(self, settings, session, clientHello,
14351443
# fully switch to application data
14361444
self._changeWriteState()
14371445

1446+
self._first_handshake_hashes = self._handshake_hash.copy()
1447+
14381448
resumption_master_secret = derive_secret(secret,
14391449
bytearray(b'res master'),
14401450
self._handshake_hash, prfName)

tlslite/tlsrecordlayer.py

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from .utils.compat import *
1919
from .utils.cryptomath import *
2020
from .utils.codec import Parser
21-
from .utils.lists import to_str_delimiter
21+
from .utils.lists import to_str_delimiter, getFirstMatching
2222
from .errors import *
2323
from .messages import *
2424
from .mathtls import *
@@ -27,6 +27,8 @@
2727
from .defragmenter import Defragmenter
2828
from .handshakehashes import HandshakeHashes
2929
from .bufferedsocket import BufferedSocket
30+
from .handshakesettings import HandshakeSettings
31+
from .keyexchange import KeyExchange
3032

3133
class TLSRecordLayer(object):
3234
"""
@@ -182,6 +184,10 @@ def __init__(self, sock):
182184
self._buffer_content_type = None
183185
self._buffer = bytearray()
184186

187+
# tuple with list of certificates and the private key that will be
188+
# used for post handshake authentication in TLS 1.3
189+
self._client_keypair = None
190+
185191
@property
186192
def _send_record_limit(self):
187193
"""Maximum size of payload that can be sent."""
@@ -300,8 +306,13 @@ def readAsync(self, max=None, min=1):
300306
if self.version > (3, 3):
301307
allowedTypes = (ContentType.application_data,
302308
ContentType.handshake)
303-
allowedHsTypes = (HandshakeType.new_session_ticket,
304-
HandshakeType.key_update)
309+
if self._client_keypair:
310+
allowedHsTypes = (HandshakeType.new_session_ticket,
311+
HandshakeType.key_update,
312+
HandshakeType.certificate_request)
313+
else:
314+
allowedHsTypes = (HandshakeType.new_session_ticket,
315+
HandshakeType.key_update)
305316
else:
306317
allowedTypes = ContentType.application_data
307318
allowedHsTypes = None
@@ -316,10 +327,15 @@ def readAsync(self, max=None, min=1):
316327
result.time = time.time()
317328
self.tickets.append(result)
318329
continue
319-
if isinstance(result, KeyUpdate):
330+
elif isinstance(result, KeyUpdate):
320331
for result in self._handle_keyupdate_request(result):
321332
yield result
322333
continue
334+
elif isinstance(result, CertificateRequest):
335+
for result in self._handle_pha(result):
336+
if result in (0, 1):
337+
yield
338+
continue
323339
applicationData = result
324340
self._readBuffer += applicationData.write()
325341
except TLSRemoteAlert as alert:
@@ -611,6 +627,84 @@ def fileno(self):
611627
# Public Functions END
612628
#*********************************************************
613629

630+
def _handle_pha(self, cert_request):
631+
cert, p_key = self._client_keypair
632+
633+
handshake_context = self._first_handshake_hashes.copy()
634+
handshake_context.update(cert_request.write())
635+
636+
prf_name = 'sha256'
637+
prf_size = 32
638+
if self.session.cipherSuite in CipherSuite.sha384PrfSuites:
639+
prf_name = 'sha384'
640+
prf_size = 48
641+
642+
msgs = []
643+
msgs.append(Certificate(CertificateType.x509, self.version)
644+
.create(cert, cert_request.certificate_request_context))
645+
handshake_context.update(msgs[0].write())
646+
if p_key:
647+
# sign the CertificateVerify only when we have a private key to do
648+
# that
649+
valid_sig_algs = cert_request.supported_signature_algs
650+
if not valid_sig_algs:
651+
for result in self._sendError(
652+
AlertDescription.missing_extension,
653+
"No signature algorithms found in CertificateRequest"):
654+
yield result
655+
avail_sig_algs = self._sigHashesToList(HandshakeSettings(), p_key,
656+
cert, version=(3, 4))
657+
sig_scheme = getFirstMatching(avail_sig_algs, valid_sig_algs)
658+
scheme = SignatureScheme.toRepr(sig_scheme)
659+
sig_scheme = getattr(SignatureScheme, scheme)
660+
661+
signature_context = \
662+
KeyExchange.calcVerifyBytes((3, 4),
663+
handshake_context,
664+
sig_scheme, None, None, None,
665+
prf_name, b'client')
666+
667+
if sig_scheme[1] == SignatureAlgorithm.ecdsa:
668+
pad_type = None
669+
hash_name = HashAlgorithm.toRepr(sig_scheme[0])
670+
salt_len = None
671+
else:
672+
pad_type = SignatureScheme.getPadding(scheme)
673+
hash_name = SignatureScheme.getHash(scheme)
674+
salt_len = getattr(hashlib, hash_name)().digest_size
675+
676+
signature = p_key.sign(signature_context,
677+
pad_type,
678+
hash_name,
679+
salt_len)
680+
if not p_key.verify(signature, signature_context,
681+
pad_type,
682+
hash_name,
683+
salt_len):
684+
for result in self._sendError(
685+
AlertDescription.internal_error,
686+
"Certificate Verify signature failed"):
687+
yield result
688+
certificate_verify = CertificateVerify(self.version)
689+
certificate_verify.create(signature, sig_scheme)
690+
691+
msgs.append(certificate_verify)
692+
handshake_context.update(certificate_verify.write())
693+
694+
finished_key = HKDF_expand_label(self.session.cl_app_secret,
695+
b"finished", b"",
696+
prf_size, prf_name)
697+
verify_data = secureHMAC(finished_key,
698+
handshake_context.digest(prf_name),
699+
prf_name)
700+
701+
finished = Finished((3, 4), prf_size)
702+
finished.create(verify_data)
703+
msgs.append(finished)
704+
705+
for result in self._sendMsgs(msgs):
706+
yield result
707+
614708
def _shutdown(self, resumable):
615709
self._recordLayer.shutdown()
616710
self.version = (0,0)

0 commit comments

Comments
 (0)