Skip to content

Commit ae60aa8

Browse files
committed
add support for EdDSA for client certificates
1 parent 8c97544 commit ae60aa8

File tree

7 files changed

+228
-42
lines changed

7 files changed

+228
-42
lines changed

tests/clientEd25519Cert.pem

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIBOjCB7aADAgECAhQ15c2VkK6IVUB62L0UG1houuxlozAFBgMrZXAwEzERMA8G
3+
A1UEAwwISm9obiBEb2UwHhcNMjEwNzI3MTgxMjA2WhcNMjEwODI2MTgxMjA2WjAT
4+
MREwDwYDVQQDDAhKb2huIERvZTAqMAUGAytlcAMhADjqYm4QU+seahrT1xHlASeM
5+
ZTe63eoQ95oBAeZRL8Ofo1MwUTAdBgNVHQ4EFgQUWW+x7NNy1nmJwl/gr6AoPGrC
6+
0GgwHwYDVR0jBBgwFoAUWW+x7NNy1nmJwl/gr6AoPGrC0GgwDwYDVR0TAQH/BAUw
7+
AwEB/zAFBgMrZXADQQDQxNKFkySCfap/8yEtD8aJFDpDQLF/QL1rKIo/198n/18X
8+
D5K63FFkmQOS5dv2h/QiSHWkNpBklqe0bRfW0nEB
9+
-----END CERTIFICATE-----

tests/clientEd25519Key.pem

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MC4CAQAwBQYDK2VwBCIEIOm5ZND3cKuM23aTESsnqkkunnQNuhksWOosOHf06vw6
3+
-----END PRIVATE KEY-----

tests/tlstest.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,45 @@ def connect():
883883

884884
test_no += 1
885885

886+
print("Test {0} - good mutual Ed25519 X.509".format(test_no))
887+
with open(os.path.join(dir, "clientEd25519Cert.pem")) as f:
888+
x509EdCert = X509().parse(f.read())
889+
x509EdChain = X509CertChain([x509EdCert])
890+
with open(os.path.join(dir, "clientEd25519Key.pem")) as f:
891+
x509EdKey = parsePEMKey(f.read(), private=True)
892+
893+
synchro.recv(1)
894+
connection = connect()
895+
connection.handshakeClientCert(x509EdChain, x509EdKey)
896+
testConnClient(connection)
897+
assert isinstance(connection.session.serverCertChain, X509CertChain)
898+
assert connection.session.serverCertChain.getEndEntityPublicKey().key_type\
899+
== "Ed25519"
900+
connection.close()
901+
902+
test_no += 1
903+
904+
print("Test {0} - good mutual Ed25519 X.509, TLS 1.2".format(test_no))
905+
with open(os.path.join(dir, "clientEd25519Cert.pem")) as f:
906+
x509EdCert = X509().parse(f.read())
907+
x509EdChain = X509CertChain([x509EdCert])
908+
with open(os.path.join(dir, "clientEd25519Key.pem")) as f:
909+
x509EdKey = parsePEMKey(f.read(), private=True)
910+
911+
synchro.recv(1)
912+
connection = connect()
913+
settings = HandshakeSettings()
914+
settings.minVersion = (3, 3)
915+
settings.maxVersion = (3, 3)
916+
connection.handshakeClientCert(x509EdChain, x509EdKey, settings=settings)
917+
testConnClient(connection)
918+
assert isinstance(connection.session.serverCertChain, X509CertChain)
919+
assert connection.session.serverCertChain.getEndEntityPublicKey().key_type\
920+
== "Ed25519"
921+
connection.close()
922+
923+
test_no += 1
924+
886925
print("Test {0} - good X.509 DSA, SSLv3".format(test_no))
887926
synchro.recv(1)
888927
connection = connect()
@@ -989,6 +1028,24 @@ def connect():
9891028

9901029
test_no += 1
9911030

1031+
print("Test {0} - good mutual X.509 Ed25519, PHA, TLSv1.3".format(test_no))
1032+
synchro.recv(1)
1033+
connection = connect()
1034+
settings = HandshakeSettings()
1035+
settings.minVersion = (3, 4)
1036+
settings.maxVersion = (3, 4)
1037+
connection.handshakeClientCert(x509EdChain, x509EdKey, settings=settings)
1038+
synchro.recv(1)
1039+
b = connection.read(0, 0)
1040+
assert b == b''
1041+
testConnClient(connection)
1042+
assert isinstance(connection.session.serverCertChain, X509CertChain)
1043+
assert connection.session.serverCertChain.getEndEntityPublicKey().key_type\
1044+
== "Ed25519"
1045+
connection.close()
1046+
1047+
test_no += 1
1048+
9921049
print("Test {0} - good mutual X.509, PHA and KeyUpdate, TLSv1.3".format(test_no))
9931050
synchro.recv(1)
9941051
connection = connect()
@@ -2435,6 +2492,36 @@ def connect():
24352492

24362493
test_no += 1
24372494

2495+
print("Test {0} - good mutual Ed25519 X.509".format(test_no))
2496+
synchro.send(b'R')
2497+
connection = connect()
2498+
connection.handshakeServer(certChain=x509Ed25519Chain,
2499+
privateKey=x509Ed25519Key, reqCert=True)
2500+
testConnServer(connection)
2501+
assert(isinstance(connection.session.clientCertChain, X509CertChain))
2502+
assert connection.session.clientCertChain.getEndEntityPublicKey().key_type\
2503+
== "Ed25519"
2504+
connection.close()
2505+
2506+
test_no += 1
2507+
2508+
print("Test {0} - good mutual Ed25519 X.509, TLS 1.2".format(test_no))
2509+
synchro.send(b'R')
2510+
connection = connect()
2511+
settings = HandshakeSettings()
2512+
settings.minVersion = (3, 3)
2513+
settings.maxVersion = (3, 3)
2514+
connection.handshakeServer(certChain=x509Ed25519Chain,
2515+
privateKey=x509Ed25519Key, reqCert=True,
2516+
settings=settings)
2517+
testConnServer(connection)
2518+
assert(isinstance(connection.session.clientCertChain, X509CertChain))
2519+
assert connection.session.clientCertChain.getEndEntityPublicKey().key_type\
2520+
== "Ed25519"
2521+
connection.close()
2522+
2523+
test_no += 1
2524+
24382525
print("Test {0} - good X.509 DSA, SSLv3".format(test_no))
24392526
synchro.send(b'R')
24402527
connection = connect()
@@ -2534,6 +2621,29 @@ def connect():
25342621

25352622
test_no += 1
25362623

2624+
print("Test {0} - good mutual X.509 Ed25519, PHA, TLSv1.3".format(test_no))
2625+
synchro.send(b'R')
2626+
connection = connect()
2627+
settings = HandshakeSettings()
2628+
settings.minVersion = (3, 4)
2629+
settings.maxVersion = (3, 4)
2630+
connection.handshakeServer(certChain=x509Ed25519Chain,
2631+
privateKey=x509Ed25519Key,
2632+
settings=settings)
2633+
assert connection.session.clientCertChain is None
2634+
for result in connection.request_post_handshake_auth(settings):
2635+
assert result in (0, 1)
2636+
synchro.send(b'R')
2637+
testConnServer(connection)
2638+
2639+
assert connection.session.clientCertChain is not None
2640+
assert isinstance(connection.session.clientCertChain, X509CertChain)
2641+
assert connection.session.clientCertChain.getEndEntityPublicKey().key_type\
2642+
== "Ed25519"
2643+
connection.close()
2644+
2645+
test_no += 1
2646+
25372647
print("Test {0} - good mutual X.509, PHA and KeyUpdate, TLSv1.3".format(test_no))
25382648
synchro.send(b'R')
25392649
connection = connect()

tlslite/handshakehashes.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def __init__(self):
2424
self._handshakeSHA256 = hashlib.sha256()
2525
self._handshakeSHA384 = hashlib.sha384()
2626
self._handshakeSHA512 = hashlib.sha512()
27+
self._handshake_buffer = bytearray()
2728

2829
def update(self, data):
2930
"""
@@ -38,6 +39,7 @@ def update(self, data):
3839
self._handshakeSHA256.update(text)
3940
self._handshakeSHA384.update(text)
4041
self._handshakeSHA512.update(text)
42+
self._handshake_buffer += text
4143

4244
def digest(self, digest=None):
4345
"""
@@ -61,6 +63,8 @@ def digest(self, digest=None):
6163
return self._handshakeSHA384.digest()
6264
elif digest == 'sha512':
6365
return self._handshakeSHA512.digest()
66+
elif digest == "intrinsic":
67+
return self._handshake_buffer
6468
else:
6569
raise ValueError("Unknown digest name")
6670

@@ -107,4 +111,5 @@ def copy(self):
107111
other._handshakeSHA256 = self._handshakeSHA256.copy()
108112
other._handshakeSHA384 = self._handshakeSHA384.copy()
109113
other._handshakeSHA512 = self._handshakeSHA512.copy()
114+
other._handshake_buffer = bytearray(self._handshake_buffer)
110115
return other

tlslite/keyexchange.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,11 @@ def calcVerifyBytes(version, handshakeHashes, signatureAlg,
369369
else:
370370
verifyBytes = handshakeHashes.digest("sha1")
371371
elif version == (3, 3):
372-
if signatureAlg[1] != SignatureAlgorithm.ecdsa:
372+
if signatureAlg in (SignatureScheme.ed25519,
373+
SignatureScheme.ed448):
374+
hashName = "intrinsic"
375+
padding = None
376+
elif signatureAlg[1] != SignatureAlgorithm.ecdsa:
373377
scheme = SignatureScheme.toRepr(signatureAlg)
374378
if scheme is None:
375379
hashName = HashAlgorithm.toRepr(signatureAlg[0])
@@ -436,12 +440,21 @@ def makeCertificateVerify(version, handshakeHashes, validSigAlgs,
436440
clientRandom,
437441
serverRandom,
438442
key_type=privateKey.key_type)
439-
if signatureAlgorithm and \
443+
if signatureAlgorithm and signatureAlgorithm in (
444+
SignatureScheme.ed25519, SignatureScheme.ed448):
445+
padding = None
446+
hashName = "intrinsic"
447+
saltLen = None
448+
sig_func = privateKey.hashAndSign
449+
ver_func = privateKey.hashAndVerify
450+
elif signatureAlgorithm and \
440451
signatureAlgorithm[1] == SignatureAlgorithm.ecdsa:
441452
padding = None
442453
hashName = HashAlgorithm.toRepr(signatureAlgorithm[0])
443454
saltLen = None
444455
verifyBytes = verifyBytes[:privateKey.private_key.curve.baselen]
456+
sig_func = privateKey.sign
457+
ver_func = privateKey.verify
445458
else:
446459
scheme = SignatureScheme.toRepr(signatureAlgorithm)
447460
# for pkcs1 signatures hash is used to add PKCS#1 prefix, but
@@ -455,13 +468,15 @@ def makeCertificateVerify(version, handshakeHashes, validSigAlgs,
455468
if padding == 'pss':
456469
hashName = SignatureScheme.getHash(scheme)
457470
saltLen = getattr(hashlib, hashName)().digest_size
458-
459-
signedBytes = privateKey.sign(verifyBytes,
460-
padding,
461-
hashName,
462-
saltLen)
463-
if not privateKey.verify(signedBytes, verifyBytes, padding, hashName,
464-
saltLen):
471+
sig_func = privateKey.sign
472+
ver_func = privateKey.verify
473+
474+
signedBytes = sig_func(verifyBytes,
475+
padding,
476+
hashName,
477+
saltLen)
478+
if not ver_func(signedBytes, verifyBytes, padding, hashName,
479+
saltLen):
465480
raise TLSInternalError("Certificate Verify signature invalid")
466481
certificateVerify = CertificateVerify(version)
467482
certificateVerify.create(signedBytes, signatureAlgorithm)

tlslite/tlsconnection.py

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,23 +1434,34 @@ def _clientTLS13Handshake(self, settings, session, clientHello,
14341434
signature_scheme, None, None,
14351435
None, prfName, b'client')
14361436

1437-
if signature_scheme[1] == SignatureAlgorithm.ecdsa:
1437+
if signature_scheme in (SignatureScheme.ed25519,
1438+
SignatureScheme.ed448):
1439+
pad_type = None
1440+
hash_name = "intrinsic"
1441+
salt_len = None
1442+
sig_func = privateKey.hashAndSign
1443+
ver_func = privateKey.hashAndVerify
1444+
elif signature_scheme[1] == SignatureAlgorithm.ecdsa:
14381445
pad_type = None
14391446
hash_name = HashAlgorithm.toRepr(signature_scheme[0])
14401447
salt_len = None
1448+
sig_func = privateKey.sign
1449+
ver_func = privateKey.verify
14411450
else:
14421451
pad_type = SignatureScheme.getPadding(scheme)
14431452
hash_name = SignatureScheme.getHash(scheme)
14441453
salt_len = getattr(hashlib, hash_name)().digest_size
1454+
sig_func = privateKey.sign
1455+
ver_func = privateKey.verify
14451456

1446-
signature = privateKey.sign(signature_context,
1447-
pad_type,
1448-
hash_name,
1449-
salt_len)
1450-
if not privateKey.verify(signature, signature_context,
1451-
pad_type,
1452-
hash_name,
1453-
salt_len):
1457+
signature = sig_func(signature_context,
1458+
pad_type,
1459+
hash_name,
1460+
salt_len)
1461+
if not ver_func(signature, signature_context,
1462+
pad_type,
1463+
hash_name,
1464+
salt_len):
14541465
for result in self._sendError(
14551466
AlertDescription.internal_error,
14561467
"Certificate Verify signature failed"):
@@ -2894,21 +2905,29 @@ def _serverTLS13Handshake(self, settings, clientHello, cipherSuite,
28942905

28952906
public_key = client_cert_chain.getEndEntityPublicKey()
28962907

2897-
if signature_scheme[1] == SignatureAlgorithm.ecdsa:
2908+
if signature_scheme in (SignatureScheme.ed25519,
2909+
SignatureScheme.ed448):
2910+
hash_name = "intrinsic"
2911+
pad_type = None
2912+
salt_len = None
2913+
ver_func = public_key.hashAndVerify
2914+
elif signature_scheme[1] == SignatureAlgorithm.ecdsa:
28982915
hash_name = HashAlgorithm.toRepr(signature_scheme[0])
28992916
pad_type = None
29002917
salt_len = None
2918+
ver_func = public_key.verify
29012919
else:
29022920
scheme = SignatureScheme.toRepr(signature_scheme)
29032921
pad_type = SignatureScheme.getPadding(scheme)
29042922
hash_name = SignatureScheme.getHash(scheme)
29052923
salt_len = getattr(hashlib, hash_name)().digest_size
2924+
ver_func = public_key.verify
29062925

2907-
if not public_key.verify(certificate_verify.signature,
2908-
signature_context,
2909-
pad_type,
2910-
hash_name,
2911-
salt_len):
2926+
if not ver_func(certificate_verify.signature,
2927+
signature_context,
2928+
pad_type,
2929+
hash_name,
2930+
salt_len):
29122931
for result in self._sendError(
29132932
AlertDescription.decrypt_error,
29142933
"signature verification failed"):
@@ -4189,7 +4208,13 @@ def _serverCertKeyExchange(self, clientHello, serverHello, sigHashAlg,
41894208
else: break
41904209
public_key = result
41914210

4192-
if not signatureAlgorithm or \
4211+
if signatureAlgorithm and signatureAlgorithm in (
4212+
SignatureScheme.ed25519, SignatureScheme.ed448):
4213+
hash_name = "intrinsic"
4214+
salt_len = None
4215+
padding = None
4216+
ver_func = public_key.hashAndVerify
4217+
elif not signatureAlgorithm or \
41934218
signatureAlgorithm[1] != SignatureAlgorithm.ecdsa:
41944219
scheme = SignatureScheme.toRepr(signatureAlgorithm)
41954220
# for pkcs1 signatures hash is used to add PKCS#1 prefix, but
@@ -4203,18 +4228,20 @@ def _serverCertKeyExchange(self, clientHello, serverHello, sigHashAlg,
42034228
if padding == 'pss':
42044229
hash_name = SignatureScheme.getHash(scheme)
42054230
salt_len = getattr(hashlib, hash_name)().digest_size
4231+
ver_func = public_key.verify
42064232
else:
42074233
hash_name = HashAlgorithm.toStr(signatureAlgorithm[0])
42084234
verify_bytes = verify_bytes[
42094235
:public_key.public_key.curve.baselen]
42104236
padding = None
42114237
salt_len = None
4238+
ver_func = public_key.verify
42124239

4213-
if not public_key.verify(certificateVerify.signature,
4214-
verify_bytes,
4215-
padding,
4216-
hash_name,
4217-
salt_len):
4240+
if not ver_func(certificateVerify.signature,
4241+
verify_bytes,
4242+
padding,
4243+
hash_name,
4244+
salt_len):
42184245
for result in self._sendError(
42194246
AlertDescription.decrypt_error,
42204247
"Signature failed to verify"):

0 commit comments

Comments
 (0)