Skip to content

Commit 6bf3c8b

Browse files
committed
add support for requiring certificate presence
1 parent a609453 commit 6bf3c8b

File tree

5 files changed

+67
-36
lines changed

5 files changed

+67
-36
lines changed

tests/tlstest.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,27 @@ def connect():
723723

724724
test_no += 1
725725

726+
print("Test {0} - mutual X.509, PHA, no client cert, TLSv1.3".format(test_no))
727+
synchro.recv(1)
728+
connection = connect()
729+
settings = HandshakeSettings()
730+
settings.minVersion = (3, 4)
731+
settings.maxVersion = (3, 4)
732+
connection.handshakeClientCert(X509CertChain(), x509Key, settings=settings)
733+
synchro.recv(1)
734+
b = connection.read(0, 0)
735+
assert b == b''
736+
try:
737+
connection.read(0, 0)
738+
assert False
739+
except TLSRemoteAlert as e:
740+
assert e.description == AlertDescription.certificate_required
741+
assert "certificate_required" in str(e), str(e)
742+
743+
connection.close()
744+
745+
test_no += 1
746+
726747
print("Test {0} - good mutual X.509, TLSv1.1".format(test_no))
727748
synchro.recv(1)
728749
connection = connect()
@@ -1970,6 +1991,32 @@ def connect():
19701991

19711992
test_no += 1
19721993

1994+
print("Test {0} - mutual X.509, PHA, no client cert, TLSv1.3".format(test_no))
1995+
synchro.send(b'R')
1996+
connection = connect()
1997+
settings = HandshakeSettings()
1998+
settings.minVersion = (3, 4)
1999+
settings.maxVersion = (3, 4)
2000+
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
2001+
settings=settings)
2002+
connection.client_cert_required = True
2003+
assert connection.session.clientCertChain is None
2004+
for result in connection.request_post_handshake_auth(settings):
2005+
assert result in (0, 1)
2006+
synchro.send(b'R')
2007+
try:
2008+
testConnServer(connection)
2009+
assert False
2010+
except TLSLocalAlert as e:
2011+
assert "Client did not provide a certificate in post-handshake" in \
2012+
str(e)
2013+
assert e.description == AlertDescription.certificate_required
2014+
2015+
assert connection.session.clientCertChain is None
2016+
connection.close()
2017+
2018+
test_no += 1
2019+
19732020
print("Test {0} - good mutual X.509, TLSv1.1".format(test_no))
19742021
synchro.send(b'R')
19752022
connection = connect()

tlslite/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ class AlertDescription(TLSEnum):
488488
bad_certificate_status_response = 113 # RFC 6066
489489
bad_certificate_hash_value = 114 # RFC 6066
490490
unknown_psk_identity = 115
491+
certificate_required = 116 # RFC 8446
491492
no_application_protocol = 120 # RFC 7301
492493

493494

tlslite/errors.py

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -59,34 +59,6 @@ class TLSAlert(TLSError):
5959

6060
pass
6161

62-
_descriptionStr = {\
63-
AlertDescription.close_notify: "close_notify",\
64-
AlertDescription.unexpected_message: "unexpected_message",\
65-
AlertDescription.bad_record_mac: "bad_record_mac",\
66-
AlertDescription.decryption_failed: "decryption_failed",\
67-
AlertDescription.record_overflow: "record_overflow",\
68-
AlertDescription.decompression_failure: "decompression_failure",\
69-
AlertDescription.handshake_failure: "handshake_failure",\
70-
AlertDescription.no_certificate: "no certificate",\
71-
AlertDescription.bad_certificate: "bad_certificate",\
72-
AlertDescription.unsupported_certificate: "unsupported_certificate",\
73-
AlertDescription.certificate_revoked: "certificate_revoked",\
74-
AlertDescription.certificate_expired: "certificate_expired",\
75-
AlertDescription.certificate_unknown: "certificate_unknown",\
76-
AlertDescription.illegal_parameter: "illegal_parameter",\
77-
AlertDescription.unknown_ca: "unknown_ca",\
78-
AlertDescription.access_denied: "access_denied",\
79-
AlertDescription.decode_error: "decode_error",\
80-
AlertDescription.decrypt_error: "decrypt_error",\
81-
AlertDescription.export_restriction: "export_restriction",\
82-
AlertDescription.protocol_version: "protocol_version",\
83-
AlertDescription.insufficient_security: "insufficient_security",\
84-
AlertDescription.internal_error: "internal_error",\
85-
AlertDescription.inappropriate_fallback: "inappropriate_fallback",\
86-
AlertDescription.user_canceled: "user_canceled",\
87-
AlertDescription.no_renegotiation: "no_renegotiation",\
88-
AlertDescription.unknown_psk_identity: "unknown_psk_identity"}
89-
9062

9163
class TLSLocalAlert(TLSAlert):
9264
"""A TLS alert has been signalled by the local implementation.
@@ -109,9 +81,7 @@ def __init__(self, alert, message=None):
10981
self.message = message
11082

11183
def __str__(self):
112-
alertStr = TLSAlert._descriptionStr.get(self.description)
113-
if alertStr == None:
114-
alertStr = str(self.description)
84+
alertStr = AlertDescription.toStr(self.description)
11585
if self.message:
11686
return alertStr + ": " + self.message
11787
else:
@@ -136,9 +106,7 @@ def __init__(self, alert):
136106
self.level = alert.level
137107

138108
def __str__(self):
139-
alertStr = TLSAlert._descriptionStr.get(self.description)
140-
if alertStr == None:
141-
alertStr = str(self.description)
109+
alertStr = AlertDescription.toStr(self.description)
142110
return alertStr
143111

144112

tlslite/tlsconnection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4212,7 +4212,7 @@ def _sigHashesToList(settings, privateKey=None, certList=None,
42124212
"""Convert list of valid signature hashes to array of tuples"""
42134213
certType = None
42144214
publicKey = None
4215-
if certList:
4215+
if certList and certList.x509List:
42164216
certType = certList.x509List[0].certAlg
42174217
publicKey = certList.x509List[0].publicKey
42184218

tlslite/tlsrecordlayer.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ class TLSRecordLayer(object):
111111
112112
:vartype tickets: list of bytearray
113113
:ivar tickets: list of session tickets received from server, oldest first.
114+
115+
:vartype client_cert_required: bool
116+
:ivar client_cert_required: Set to True to make the post-handshake
117+
authentication fail when client doesn't provide a certificate in
118+
response
114119
"""
115120

116121
def __init__(self, sock):
@@ -193,6 +198,10 @@ def __init__(self, sock):
193198
# messages (which are the values)
194199
self._cert_requests = {}
195200

201+
# boolean to control if PHA needs to be aborted when the client
202+
# doesn't provide a certificate
203+
self.client_cert_required = False
204+
196205
@property
197206
def _send_record_limit(self):
198207
"""Maximum size of payload that can be sent."""
@@ -663,7 +672,7 @@ def _handle_pha(self, cert_request):
663672
msgs.append(Certificate(CertificateType.x509, self.version)
664673
.create(cert, cert_request.certificate_request_context))
665674
handshake_context.update(msgs[0].write())
666-
if p_key:
675+
if cert.x509List and p_key:
667676
# sign the CertificateVerify only when we have a private key to do
668677
# that
669678
valid_sig_algs = cert_request.supported_signature_algs
@@ -809,6 +818,12 @@ def _handle_srv_pha(self, cert):
809818
"Signature verification failed"):
810819
yield result
811820
handshake_context.update(cert_verify.write())
821+
elif self.client_cert_required:
822+
for result in self._sendError(
823+
AlertDescription.certificate_required,
824+
"Client did not provide a certificate in post-handshake "
825+
"authentication"):
826+
yield result
812827

813828
finished_key = HKDF_expand_label(self.session.cl_app_secret,
814829
b'finished', b'',

0 commit comments

Comments
 (0)