Skip to content

Commit f7c0749

Browse files
committed
handling PHA in server
1 parent a789b3f commit f7c0749

File tree

3 files changed

+211
-4
lines changed

3 files changed

+211
-4
lines changed

tests/tlstest.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ def testConnClient(conn):
7777
conn.write(b1)
7878
conn.write(b10)
7979
conn.write(b100)
80-
conn.write(b1000)
8180
r1 = conn.read(min=1, max=1)
8281
assert len(r1) == 1
8382
assert r1 == b1
@@ -87,6 +86,7 @@ def testConnClient(conn):
8786
r100 = conn.read(min=100, max=100)
8887
assert len(r100) == 100
8988
assert r100 == b100
89+
conn.write(b1000)
9090
r1000 = conn.read(min=1000, max=1000)
9191
assert len(r1000) == 1000
9292
assert r1000 == b1000
@@ -707,6 +707,20 @@ def connect():
707707

708708
test_no += 1
709709

710+
print("Test {0} - good mutual X.509, PHA, TLSv1.3".format(test_no))
711+
synchro.recv(1)
712+
connection = connect()
713+
settings = HandshakeSettings()
714+
settings.minVersion = (3, 4)
715+
settings.maxVersion = (3, 4)
716+
connection.handshakeClientCert(x509Chain, x509Key, settings=settings)
717+
synchro.recv(1)
718+
testConnClient(connection)
719+
assert(isinstance(connection.session.serverCertChain, X509CertChain))
720+
connection.close()
721+
722+
test_no += 1
723+
710724
print("Test {0} - good mutual X.509, TLSv1.1".format(test_no))
711725
synchro.recv(1)
712726
connection = connect()
@@ -1934,6 +1948,26 @@ def connect():
19341948

19351949
test_no += 1
19361950

1951+
print("Test {0} - good mutual X.509, PHA, TLSv1.3".format(test_no))
1952+
synchro.send(b'R')
1953+
connection = connect()
1954+
settings = HandshakeSettings()
1955+
settings.minVersion = (3, 4)
1956+
settings.maxVersion = (3, 4)
1957+
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
1958+
settings=settings)
1959+
assert connection.session.clientCertChain is None
1960+
for result in connection.request_post_handshake_auth(settings):
1961+
assert result in (0, 1)
1962+
synchro.send(b'R')
1963+
testConnServer(connection)
1964+
1965+
assert connection.session.clientCertChain is not None
1966+
assert isinstance(connection.session.clientCertChain, X509CertChain)
1967+
connection.close()
1968+
1969+
test_no += 1
1970+
19371971
print("Test {0} - good mutual X.509, TLSv1.1".format(test_no))
19381972
synchro.send(b'R')
19391973
connection = connect()

tlslite/tlsconnection.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def __init__(self, sock):
8585
# if and how big is the limit on records peer is willing to accept
8686
# used only for TLS 1.2 and earlier
8787
self._peer_record_size_limit = None
88+
self._pha_supported = False
8889

8990
def keyingMaterialExporter(self, label, length=20):
9091
"""Return keying material as described in RFC 5705
@@ -2327,6 +2328,40 @@ def _handshakeServerAsyncHelper(self, verifierDB,
23272328
self._serverRandom = serverHello.random
23282329
self._clientRandom = clientHello.random
23292330

2331+
def request_post_handshake_auth(self, settings=None):
2332+
"""
2333+
Request Post-handshake Authentication from client.
2334+
2335+
The PHA process is asynchronous, and client may send some data before
2336+
its certificates are added to Session object. Calling this generator
2337+
will only request for the new identity of client, it will not wait for
2338+
it.
2339+
"""
2340+
if self.version != (3, 4):
2341+
raise ValueError("PHA is supported only in TLS 1.3")
2342+
if self._client:
2343+
raise ValueError("PHA can only be requested by server")
2344+
if not self._pha_supported:
2345+
raise ValueError("PHA not supported by client")
2346+
2347+
settings = settings or HandshakeSettings()
2348+
settings = settings.validate()
2349+
2350+
valid_sig_algs = self._sigHashesToList(settings)
2351+
if not valid_sig_algs:
2352+
raise ValueError("No signature algorithms enabled in "
2353+
"HandshakeSettings")
2354+
2355+
context = bytes(getRandomBytes(32))
2356+
2357+
certificate_request = CertificateRequest(self.version)
2358+
certificate_request.create(context=context, sig_algs=valid_sig_algs)
2359+
2360+
self._cert_requests[context] = certificate_request
2361+
2362+
for result in self._sendMsg(certificate_request):
2363+
yield result
2364+
23302365
@staticmethod
23312366
def _derive_key_iv(nonce, user_key, settings):
23322367
"""Derive the IV and key for session ticket encryption."""
@@ -2820,6 +2855,8 @@ def _serverTLS13Handshake(self, settings, clientHello, cipherSuite,
28202855
self._handshake_hash,
28212856
prf_name)
28222857

2858+
self._first_handshake_hashes = self._handshake_hash.copy()
2859+
28232860
self.session = Session()
28242861
self.extendedMasterSecret = True
28252862
server_name = None
@@ -2986,6 +3023,16 @@ def _serverGetClientHello(self, settings, private_key, cert_chain,
29863023
sup_groups = clientHello.getExtension(
29873024
ExtensionType.supported_groups)
29883025

3026+
pha = clientHello.getExtension(ExtensionType.post_handshake_auth)
3027+
if pha:
3028+
if pha.extData:
3029+
for result in self._sendError(
3030+
AlertDescription.decode_error,
3031+
"Invalid encoding of post_handshake_auth extension"
3032+
):
3033+
yield result
3034+
self._pha_supported = True
3035+
29893036
key_exchange = None
29903037

29913038
if psk_modes:

tlslite/tlsrecordlayer.py

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,11 @@ def __init__(self, sock):
188188
# used for post handshake authentication in TLS 1.3
189189
self._client_keypair = None
190190

191+
# dictionary with CertificateRequest messages we (as a server) have
192+
# sent, the keys are the "certificate request context" from the
193+
# messages (which are the values)
194+
self._cert_requests = {}
195+
191196
@property
192197
def _send_record_limit(self):
193198
"""Maximum size of payload that can be sent."""
@@ -303,13 +308,19 @@ def readAsync(self, max=None, min=1):
303308
:rtype: iterable
304309
:returns: A generator; see above for details.
305310
"""
311+
constructor_type = None
306312
if self.version > (3, 3):
307313
allowedTypes = (ContentType.application_data,
308314
ContentType.handshake)
309315
if self._client_keypair:
310316
allowedHsTypes = (HandshakeType.new_session_ticket,
311317
HandshakeType.key_update,
312318
HandshakeType.certificate_request)
319+
elif self._cert_requests:
320+
allowedHsTypes = (HandshakeType.new_session_ticket,
321+
HandshakeType.key_update,
322+
HandshakeType.certificate)
323+
constructor_type = CertificateType.x509
313324
else:
314325
allowedHsTypes = (HandshakeType.new_session_ticket,
315326
HandshakeType.key_update)
@@ -320,7 +331,8 @@ def readAsync(self, max=None, min=1):
320331
while len(self._readBuffer) < min and not self.closed:
321332
try:
322333
for result in self._getMsg(allowedTypes,
323-
allowedHsTypes):
334+
allowedHsTypes,
335+
constructor_type):
324336
if result in (0, 1):
325337
yield result
326338
if isinstance(result, NewSessionTicket):
@@ -331,10 +343,13 @@ def readAsync(self, max=None, min=1):
331343
for result in self._handle_keyupdate_request(result):
332344
yield result
333345
continue
346+
elif isinstance(result, Certificate):
347+
for result in self._handle_srv_pha(result):
348+
yield result
349+
continue
334350
elif isinstance(result, CertificateRequest):
335351
for result in self._handle_pha(result):
336-
if result in (0, 1):
337-
yield
352+
yield result
338353
continue
339354
applicationData = result
340355
self._readBuffer += applicationData.write()
@@ -705,6 +720,117 @@ def _handle_pha(self, cert_request):
705720
for result in self._sendMsgs(msgs):
706721
yield result
707722

723+
def _handle_srv_pha(self, cert):
724+
"""Process the post-handshake authentication from client."""
725+
prf_name = 'sha256'
726+
prf_size = 32
727+
if self.session.cipherSuite in CipherSuite.sha384PrfSuites:
728+
prf_name = 'sha384'
729+
prf_size = 48
730+
731+
cr_context = cert.certificate_request_context
732+
if not cr_context:
733+
for result in self._sendError(
734+
AlertDescription.illegal_parameter,
735+
"Certificate Request context missing in Certificate "
736+
"message from client"):
737+
yield result
738+
739+
try:
740+
cr = self._cert_requests.pop(bytes(cr_context))
741+
except KeyError:
742+
for result in self._sendError(
743+
AlertDescription.illegal_parameter,
744+
"Certificiate Request context is incorrect or was already "
745+
"handled previously"):
746+
yield result
747+
748+
# TODO: verify that the extensions used by client were sent by us in
749+
# CertificateReuest
750+
751+
handshake_context = self._first_handshake_hashes.copy()
752+
handshake_context.update(cr.write())
753+
handshake_context.update(cert.write())
754+
755+
if cert.cert_chain:
756+
for result in self._getMsg(ContentType.handshake,
757+
HandshakeType.certificate_verify):
758+
if result in (0, 1):
759+
yield result
760+
else:
761+
break
762+
assert isinstance(result, CertificateVerify)
763+
cert_verify = result
764+
765+
valid_sig_algs = cr.supported_signature_algs
766+
if cert_verify.signatureAlgorithm not in valid_sig_algs:
767+
for result in self._sendError(
768+
AlertDescription.illegal_parameter,
769+
"Client selected signature algorithm we didn't "
770+
"advertise"):
771+
yield result
772+
avail_sig_algs = self._sigHashesToList(HandshakeSettings(), None,
773+
cert.cert_chain,
774+
version=(3, 4))
775+
if cert_verify.signatureAlgorithm not in avail_sig_algs:
776+
for result in self._sendError(
777+
AlertDescription.illegal_parameter,
778+
"Client selected signature algorithm not consistent "
779+
"with public key in its certificate"):
780+
yield result
781+
scheme = SignatureScheme.toRepr(cert_verify.signatureAlgorithm)
782+
sig_scheme = getattr(SignatureScheme, scheme)
783+
784+
signature_context = \
785+
KeyExchange.calcVerifyBytes((3, 4),
786+
handshake_context,
787+
sig_scheme, None, None, None,
788+
prf_name, b'client')
789+
790+
if sig_scheme[1] == SignatureAlgorithm.ecdsa:
791+
pad_type = None
792+
hash_name = HashAlgorithm.toRepr(sig_scheme[0])
793+
salt_len = None
794+
else:
795+
pad_type = SignatureScheme.getPadding(scheme)
796+
hash_name = SignatureScheme.getHash(scheme)
797+
salt_len = getattr(hashlib, hash_name)().digest_size
798+
799+
if not cert.cert_chain.getEndEntityPublicKey().verify(
800+
cert_verify.signature, signature_context, pad_type,
801+
hash_name, salt_len):
802+
for result in self._sendError(
803+
AlertDescription.decrypt_error,
804+
"Signature verification failed"):
805+
yield result
806+
handshake_context.update(cert_verify.write())
807+
808+
finished_key = HKDF_expand_label(self.session.cl_app_secret,
809+
b'finished', b'',
810+
prf_size, prf_name)
811+
verify_data = secureHMAC(finished_key,
812+
handshake_context.digest(prf_name),
813+
prf_name)
814+
815+
for result in self._getMsg(ContentType.handshake,
816+
HandshakeType.finished,
817+
prf_size):
818+
if result in (0, 1):
819+
yield result
820+
else:
821+
break
822+
assert isinstance(result, Finished)
823+
824+
finished = result
825+
826+
if finished.verify_data != verify_data:
827+
for result in self._sendError(
828+
AlertDescription.decrypt_error,
829+
"Invalid Finished verify_data from client"):
830+
yield result
831+
832+
self.session.clientCertChain = cert.cert_chain
833+
708834
def _shutdown(self, resumable):
709835
self._recordLayer.shutdown()
710836
self.version = (0,0)

0 commit comments

Comments
 (0)