Skip to content

Commit

Permalink
Merge 1e2e206 into 45d0a0c
Browse files Browse the repository at this point in the history
  • Loading branch information
tomato42 committed Nov 26, 2019
2 parents 45d0a0c + 1e2e206 commit 210f4ed
Show file tree
Hide file tree
Showing 5 changed files with 366 additions and 13 deletions.
40 changes: 37 additions & 3 deletions scripts/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def printUsage(s=None):
server
[-c CERT] [-k KEY] [-t TACK] [-v VERIFIERDB] [-d DIR] [-l LABEL] [-L LENGTH]
[--reqcert] [--param DHFILE] [--psk PSK] [--psk-ident IDENTITY]
[--psk-sha384] [--ssl3] [--max-ver VER] [--tickets COUNT]
[--psk-sha384] [--ssl3] [--max-ver VER] [--tickets COUNT] [--request-pha]
HOST:PORT
client
Expand Down Expand Up @@ -156,6 +156,7 @@ def handleArgs(argv, argString, flagsList=[]):
ssl3 = False
max_ver = None
tickets = None
request_pha = False

for opt, arg in opts:
if opt == "-k":
Expand Down Expand Up @@ -227,6 +228,8 @@ def handleArgs(argv, argString, flagsList=[]):
max_ver = ver_to_tuple(arg)
elif opt == "--tickets":
tickets = int(arg)
elif opt == "--request-pha":
request_pha = True
else:
assert(False)

Expand Down Expand Up @@ -287,6 +290,8 @@ def handleArgs(argv, argString, flagsList=[]):
retList.append(max_ver)
if "tickets=" in flagsList:
retList.append(tickets)
if "request-pha" in flagsList:
retList.append(request_pha)
return retList


Expand Down Expand Up @@ -484,11 +489,11 @@ def serverCmd(argv):
(address, privateKey, cert_chain, virtual_hosts, tacks, verifierDB,
directory, reqCert,
expLabel, expLength, dhparam, psk, psk_ident, psk_hash, ssl3,
max_ver, tickets) = \
max_ver, tickets, request_pha) = \
handleArgs(argv, "kctbvdlL",
["reqcert", "param=", "psk=",
"psk-ident=", "psk-sha384", "ssl3", "max-ver=",
"tickets="])
"tickets=", "request-pha"])


if (cert_chain and not privateKey) or (not cert_chain and privateKey):
Expand Down Expand Up @@ -545,6 +550,28 @@ def do_GET(self):
else:
raise ValueError("Invalid return from "
"send_keyupdate_request")
if self.path.startswith('/secret'):
try:
for i in self.connection.request_post_handshake_auth():
pass
except ValueError:
self.wfile.write(b'HTTP/1.0 401 Certificate authentication'
b' required\r\n')
self.wfile.write(b'Connection: close\r\n')
self.wfile.write(b'Content-Length: 0\r\n\r\n')
return
self.connection.read(0, 0)
if self.connection.session.clientCertChain:
print(" Got client certificate in post-handshake auth: "
"{0}".format(self.connection.session
.clientCertChain.getFingerprint()))
else:
print(" No certificate from client received")
self.wfile.write(b'HTTP/1.0 401 Certificate authentication'
b' required\r\n')
self.wfile.write(b'Connection: close\r\n')
self.wfile.write(b'Content-Length: 0\r\n\r\n')
return
return super(MySimpleHTTPHandler, self).do_GET()

class MyHTTPServer(ThreadingMixIn, TLSSocketServerMixIn, HTTPServer):
Expand Down Expand Up @@ -576,6 +603,13 @@ def handshake(self, connection):
sni=sni)
# As an example (does not work here):
#nextProtos=[b"spdy/3", b"spdy/2", b"http/1.1"])
try:
if request_pha:
for i in connection.request_post_handshake_auth():
pass
except ValueError:
# if we can't do PHA, we can't do it
pass
stop = time_stamp()
except TLSRemoteAlert as a:
if a.description == AlertDescription.user_canceled:
Expand Down
36 changes: 36 additions & 0 deletions tests/tlstest.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,22 @@ def connect():

test_no += 1

print("Test {0} - good mutual X.509, PHA, TLSv1.3".format(test_no))
synchro.recv(1)
connection = connect()
settings = HandshakeSettings()
settings.minVersion = (3, 4)
settings.maxVersion = (3, 4)
connection.handshakeClientCert(x509Chain, x509Key, settings=settings)
synchro.recv(1)
b = connection.read(0, 0)
assert b == b''
testConnClient(connection)
assert(isinstance(connection.session.serverCertChain, X509CertChain))
connection.close()

test_no += 1

print("Test {0} - good mutual X.509, TLSv1.1".format(test_no))
synchro.recv(1)
connection = connect()
Expand Down Expand Up @@ -1934,6 +1950,26 @@ def connect():

test_no += 1

print("Test {0} - good mutual X.509, PHA, TLSv1.3".format(test_no))
synchro.send(b'R')
connection = connect()
settings = HandshakeSettings()
settings.minVersion = (3, 4)
settings.maxVersion = (3, 4)
connection.handshakeServer(certChain=x509Chain, privateKey=x509Key,
settings=settings)
assert connection.session.clientCertChain is None
for result in connection.request_post_handshake_auth(settings):
assert result in (0, 1)
synchro.send(b'R')
testConnServer(connection)

assert connection.session.clientCertChain is not None
assert isinstance(connection.session.clientCertChain, X509CertChain)
connection.close()

test_no += 1

print("Test {0} - good mutual X.509, TLSv1.1".format(test_no))
synchro.send(b'R')
connection = connect()
Expand Down
1 change: 1 addition & 0 deletions tlslite/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ class ExtensionType(TLSEnum):
supported_versions = 43 # TLS 1.3
cookie = 44 # TLS 1.3
psk_key_exchange_modes = 45 # TLS 1.3
post_handshake_auth = 49 # TLS 1.3
signature_algorithms_cert = 50 # TLS 1.3
key_share = 51 # TLS 1.3
supports_npn = 13172
Expand Down
57 changes: 57 additions & 0 deletions tlslite/tlsconnection.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def __init__(self, sock):
# if and how big is the limit on records peer is willing to accept
# used only for TLS 1.2 and earlier
self._peer_record_size_limit = None
self._pha_supported = False

def keyingMaterialExporter(self, label, length=20):
"""Return keying material as described in RFC 5705
Expand Down Expand Up @@ -717,6 +718,14 @@ def _clientSendClientHello(self, settings, session, srpUsername,
session_id = bytearray()
# when TLS 1.3 advertised, add key shares, set fake session_id
if next((i for i in settings.versions if i > (3, 3)), None):
# if we have a client cert configured, do indicate we're willing
# to perform Post Handshake Authentication
if certParams and certParams[1]:
extensions.append(TLSExtension(
extType=ExtensionType.post_handshake_auth).
create(bytearray(b'')))
self._client_keypair = certParams

session_id = getRandomBytes(32)
extensions.append(SupportedVersionsExtension().
create(settings.versions))
Expand Down Expand Up @@ -1435,6 +1444,8 @@ def _clientTLS13Handshake(self, settings, session, clientHello,
# fully switch to application data
self._changeWriteState()

self._first_handshake_hashes = self._handshake_hash.copy()

resumption_master_secret = derive_secret(secret,
bytearray(b'res master'),
self._handshake_hash, prfName)
Expand Down Expand Up @@ -2317,6 +2328,40 @@ def _handshakeServerAsyncHelper(self, verifierDB,
self._serverRandom = serverHello.random
self._clientRandom = clientHello.random

def request_post_handshake_auth(self, settings=None):
"""
Request Post-handshake Authentication from client.
The PHA process is asynchronous, and client may send some data before
its certificates are added to Session object. Calling this generator
will only request for the new identity of client, it will not wait for
it.
"""
if self.version != (3, 4):
raise ValueError("PHA is supported only in TLS 1.3")
if self._client:
raise ValueError("PHA can only be requested by server")
if not self._pha_supported:
raise ValueError("PHA not supported by client")

settings = settings or HandshakeSettings()
settings = settings.validate()

valid_sig_algs = self._sigHashesToList(settings)
if not valid_sig_algs:
raise ValueError("No signature algorithms enabled in "
"HandshakeSettings")

context = bytes(getRandomBytes(32))

certificate_request = CertificateRequest(self.version)
certificate_request.create(context=context, sig_algs=valid_sig_algs)

self._cert_requests[context] = certificate_request

for result in self._sendMsg(certificate_request):
yield result

@staticmethod
def _derive_key_iv(nonce, user_key, settings):
"""Derive the IV and key for session ticket encryption."""
Expand Down Expand Up @@ -2810,6 +2855,8 @@ def _serverTLS13Handshake(self, settings, clientHello, cipherSuite,
self._handshake_hash,
prf_name)

self._first_handshake_hashes = self._handshake_hash.copy()

self.session = Session()
self.extendedMasterSecret = True
server_name = None
Expand Down Expand Up @@ -2976,6 +3023,16 @@ def _serverGetClientHello(self, settings, private_key, cert_chain,
sup_groups = clientHello.getExtension(
ExtensionType.supported_groups)

pha = clientHello.getExtension(ExtensionType.post_handshake_auth)
if pha:
if pha.extData:
for result in self._sendError(
AlertDescription.decode_error,
"Invalid encoding of post_handshake_auth extension"
):
yield result
self._pha_supported = True

key_exchange = None

if psk_modes:
Expand Down
Loading

0 comments on commit 210f4ed

Please sign in to comment.