Skip to content

Commit 3e7bddd

Browse files
authored
Merge pull request #350 from tomato42/post-handshake-auth
Post handshake auth
2 parents 1abaefe + 114cdd5 commit 3e7bddd

File tree

6 files changed

+455
-55
lines changed

6 files changed

+455
-55
lines changed

scripts/tls.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def printUsage(s=None):
7979
[-c CERT] [-k KEY] [-t TACK] [-v VERIFIERDB] [-d DIR] [-l LABEL] [-L LENGTH]
8080
[--reqcert] [--param DHFILE] [--psk PSK] [--psk-ident IDENTITY]
8181
[--psk-sha384] [--ssl3] [--max-ver VER] [--tickets COUNT] [--cipherlist]
82+
[--request-pha] [--require-pha]
8283
HOST:PORT
8384
8485
client
@@ -102,6 +103,9 @@ def printUsage(s=None):
102103
finished
103104
--cipherlist - comma separated ciphers to enable. For ex. aes128ccm,3des
104105
You can specify this option multiple times.
106+
--request-pha - ask client for post-handshake authentication
107+
--require-pha - abort connection if client didn't provide certificate in
108+
post-handshake authentication
105109
CERT, KEY - the file with key and certificates that will be used by client or
106110
server. The server can accept multiple pairs of `-c` and `-k` options
107111
to configure different certificates (like RSA and ECDSA)
@@ -159,6 +163,8 @@ def handleArgs(argv, argString, flagsList=[]):
159163
max_ver = None
160164
tickets = None
161165
ciphers = []
166+
request_pha = False
167+
require_pha = False
162168

163169
for opt, arg in opts:
164170
if opt == "-k":
@@ -232,6 +238,10 @@ def handleArgs(argv, argString, flagsList=[]):
232238
tickets = int(arg)
233239
elif opt == "--cipherlist":
234240
ciphers.append(arg)
241+
elif opt == "--request-pha":
242+
request_pha = True
243+
elif opt == "--require-pha":
244+
require_pha = True
235245
else:
236246
assert(False)
237247

@@ -294,6 +304,10 @@ def handleArgs(argv, argString, flagsList=[]):
294304
retList.append(tickets)
295305
if "cipherlist=" in flagsList:
296306
retList.append(ciphers)
307+
if "request-pha" in flagsList:
308+
retList.append(request_pha)
309+
if "require-pha" in flagsList:
310+
retList.append(require_pha)
297311
return retList
298312

299313

@@ -494,11 +508,11 @@ def serverCmd(argv):
494508
(address, privateKey, cert_chain, virtual_hosts, tacks, verifierDB,
495509
directory, reqCert,
496510
expLabel, expLength, dhparam, psk, psk_ident, psk_hash, ssl3,
497-
max_ver, tickets, cipherlist) = \
511+
max_ver, tickets, cipherlist, request_pha, require_pha) = \
498512
handleArgs(argv, "kctbvdlL",
499513
["reqcert", "param=", "psk=",
500514
"psk-ident=", "psk-sha384", "ssl3", "max-ver=",
501-
"tickets=", "cipherlist="])
515+
"tickets=", "cipherlist=", "request-pha", "require-pha"])
502516

503517

504518
if (cert_chain and not privateKey) or (not cert_chain and privateKey):
@@ -558,6 +572,28 @@ def do_GET(self):
558572
else:
559573
raise ValueError("Invalid return from "
560574
"send_keyupdate_request")
575+
if self.path.startswith('/secret'):
576+
try:
577+
for i in self.connection.request_post_handshake_auth():
578+
pass
579+
except ValueError:
580+
self.wfile.write(b'HTTP/1.0 401 Certificate authentication'
581+
b' required\r\n')
582+
self.wfile.write(b'Connection: close\r\n')
583+
self.wfile.write(b'Content-Length: 0\r\n\r\n')
584+
return
585+
self.connection.read(0, 0)
586+
if self.connection.session.clientCertChain:
587+
print(" Got client certificate in post-handshake auth: "
588+
"{0}".format(self.connection.session
589+
.clientCertChain.getFingerprint()))
590+
else:
591+
print(" No certificate from client received")
592+
self.wfile.write(b'HTTP/1.0 401 Certificate authentication'
593+
b' required\r\n')
594+
self.wfile.write(b'Connection: close\r\n')
595+
self.wfile.write(b'Content-Length: 0\r\n\r\n')
596+
return
561597
return super(MySimpleHTTPHandler, self).do_GET()
562598

563599
class MyHTTPServer(ThreadingMixIn, TLSSocketServerMixIn, HTTPServer):
@@ -576,6 +612,7 @@ def handshake(self, connection):
576612
1)
577613
connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
578614
struct.pack('ii', 1, 5))
615+
connection.client_cert_required = require_pha
579616
connection.handshakeServer(certChain=cert_chain,
580617
privateKey=privateKey,
581618
verifierDB=verifierDB,
@@ -589,6 +626,13 @@ def handshake(self, connection):
589626
sni=sni)
590627
# As an example (does not work here):
591628
#nextProtos=[b"spdy/3", b"spdy/2", b"http/1.1"])
629+
try:
630+
if request_pha:
631+
for i in connection.request_post_handshake_auth():
632+
pass
633+
except ValueError:
634+
# if we can't do PHA, we can't do it
635+
pass
592636
stop = time_stamp()
593637
except TLSRemoteAlert as a:
594638
if a.description == AlertDescription.user_canceled:

tests/tlstest.py

Lines changed: 97 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -499,10 +499,10 @@ def connect():
499499
connection = connect()
500500
try:
501501
connection.handshakeClientCert(settings=settings)
502-
assert(False)
502+
assert False
503503
except TLSLocalAlert as alert:
504504
if alert.description != AlertDescription.illegal_parameter:
505-
raise
505+
raise
506506
connection.close()
507507
else:
508508
test_no += 1
@@ -707,6 +707,43 @@ 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+
b = connection.read(0, 0)
719+
assert b == b''
720+
testConnClient(connection)
721+
assert(isinstance(connection.session.serverCertChain, X509CertChain))
722+
connection.close()
723+
724+
test_no += 1
725+
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+
710747
print("Test {0} - good mutual X.509, TLSv1.1".format(test_no))
711748
synchro.recv(1)
712749
connection = connect()
@@ -784,7 +821,7 @@ def connect():
784821
connection.handshakeClientSRP("test", "garbage",
785822
serverName=address[0],
786823
session=session, settings=settings)
787-
assert(False)
824+
assert False
788825
except TLSRemoteAlert as alert:
789826
if alert.description != AlertDescription.bad_record_mac:
790827
raise
@@ -1014,7 +1051,7 @@ def connect():
10141051
settings.maxVersion = (3, 2)
10151052
try:
10161053
connection.handshakeClientCert(settings=settings)
1017-
assert()
1054+
assert False
10181055
except TLSRemoteAlert as alert:
10191056
if alert.description != AlertDescription.inappropriate_fallback:
10201057
raise
@@ -1110,6 +1147,7 @@ def connect():
11101147
try:
11111148
connection.handshakeClientCert(serverName=address[0], session=session,
11121149
settings=settings)
1150+
assert False
11131151
except TLSRemoteAlert as e:
11141152
assert(str(e) == "illegal_parameter")
11151153
else:
@@ -1344,7 +1382,8 @@ def heartbeat_response_check(message):
13441382

13451383
print("Test {0}: POP3 good".format(test_no))
13461384
except (socket.error, socket.timeout) as e:
1347-
print("Non-critical error: socket error trying to reach internet server: ", e)
1385+
print("Non-critical error: socket error trying to reach internet "
1386+
"server: ", e)
13481387

13491388
synchro.close()
13501389

@@ -1603,6 +1642,7 @@ def connect():
16031642
try:
16041643
connection.handshakeServer(certChain=x509ecdsaChain,
16051644
privateKey=x509ecdsaKey, settings=settings)
1645+
assert False
16061646
except TLSRemoteAlert as e:
16071647
assert "handshake_failure" in str(e)
16081648
connection.close()
@@ -1634,6 +1674,7 @@ def connect():
16341674
try:
16351675
connection.handshakeServer(certChain=x509ecdsaChain,
16361676
privateKey=x509ecdsaKey, settings=settings)
1677+
assert False
16371678
except TLSLocalAlert as e:
16381679
assert "No common signature algorithms" in str(e)
16391680
connection.close()
@@ -1740,7 +1781,7 @@ def connect():
17401781
try:
17411782
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
17421783
tacks=[tackUnrelated], settings=settings)
1743-
assert(False)
1784+
assert False
17441785
except TLSRemoteAlert as alert:
17451786
if alert.description != AlertDescription.illegal_parameter:
17461787
raise
@@ -1934,6 +1975,52 @@ def connect():
19341975

19351976
test_no += 1
19361977

1978+
print("Test {0} - good mutual X.509, PHA, TLSv1.3".format(test_no))
1979+
synchro.send(b'R')
1980+
connection = connect()
1981+
settings = HandshakeSettings()
1982+
settings.minVersion = (3, 4)
1983+
settings.maxVersion = (3, 4)
1984+
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
1985+
settings=settings)
1986+
assert connection.session.clientCertChain is None
1987+
for result in connection.request_post_handshake_auth(settings):
1988+
assert result in (0, 1)
1989+
synchro.send(b'R')
1990+
testConnServer(connection)
1991+
1992+
assert connection.session.clientCertChain is not None
1993+
assert isinstance(connection.session.clientCertChain, X509CertChain)
1994+
connection.close()
1995+
1996+
test_no += 1
1997+
1998+
print("Test {0} - mutual X.509, PHA, no client cert, TLSv1.3".format(test_no))
1999+
synchro.send(b'R')
2000+
connection = connect()
2001+
settings = HandshakeSettings()
2002+
settings.minVersion = (3, 4)
2003+
settings.maxVersion = (3, 4)
2004+
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
2005+
settings=settings)
2006+
connection.client_cert_required = True
2007+
assert connection.session.clientCertChain is None
2008+
for result in connection.request_post_handshake_auth(settings):
2009+
assert result in (0, 1)
2010+
synchro.send(b'R')
2011+
try:
2012+
testConnServer(connection)
2013+
assert False
2014+
except TLSLocalAlert as e:
2015+
assert "Client did not provide a certificate in post-handshake" in \
2016+
str(e)
2017+
assert e.description == AlertDescription.certificate_required
2018+
2019+
assert connection.session.clientCertChain is None
2020+
connection.close()
2021+
2022+
test_no += 1
2023+
19372024
print("Test {0} - good mutual X.509, TLSv1.1".format(test_no))
19382025
synchro.send(b'R')
19392026
connection = connect()
@@ -1995,13 +2082,14 @@ def connect():
19952082
synchro.send(b'R')
19962083
try:
19972084
connection.read(min=1, max=1)
1998-
assert() #Client is going to close the socket without a close_notify
2085+
assert False #Client is going to close the socket without a close_notify
19992086
except TLSAbruptCloseError as e:
20002087
pass
20012088
synchro.send(b'R')
20022089
connection = connect()
20032090
try:
20042091
connection.handshakeServer(verifierDB=verifierDB, sessionCache=sessionCache)
2092+
assert False
20052093
except TLSLocalAlert as alert:
20062094
if alert.description != AlertDescription.bad_record_mac:
20072095
raise
@@ -2210,7 +2298,7 @@ def server_bind(self):
22102298
try:
22112299
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
22122300
settings=settings)
2213-
assert()
2301+
assert False
22142302
except TLSLocalAlert as alert:
22152303
if alert.description != AlertDescription.inappropriate_fallback:
22162304
raise
@@ -2273,6 +2361,7 @@ def server_bind(self):
22732361
try:
22742362
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
22752363
sessionCache=sessionCache)
2364+
assert False
22762365
except TLSLocalAlert as e:
22772366
assert(str(e) == "illegal_parameter")
22782367
else:

tlslite/constants.py

Lines changed: 2 additions & 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
@@ -487,6 +488,7 @@ class AlertDescription(TLSEnum):
487488
bad_certificate_status_response = 113 # RFC 6066
488489
bad_certificate_hash_value = 114 # RFC 6066
489490
unknown_psk_identity = 115
491+
certificate_required = 116 # RFC 8446
490492
no_application_protocol = 120 # RFC 7301
491493

492494

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

0 commit comments

Comments
 (0)