Skip to content

Commit dc34709

Browse files
authored
Merge pull request #529 from tlsfuzzer/brainpool_in_tls13
add support for Brainpool curves in TLS 1.3 (RFC8734)
2 parents 768c262 + 4128eb6 commit dc34709

File tree

6 files changed

+235
-49
lines changed

6 files changed

+235
-49
lines changed

scripts/tls.py

+22-3
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def printUsage(s=None):
109109
[-c CERT] [-k KEY] [-t TACK] [-v VERIFIERDB] [-d DIR] [-l LABEL] [-L LENGTH]
110110
[--reqcert] [--param DHFILE] [--psk PSK] [--psk-ident IDENTITY]
111111
[--psk-sha384] [--ssl3] [--max-ver VER] [--tickets COUNT] [--cipherlist]
112-
[--request-pha] [--require-pha] [--echo]
112+
[--request-pha] [--require-pha] [--echo] [--groups GROUPS]
113113
HOST:PORT
114114
115115
client
@@ -137,6 +137,8 @@ def printUsage(s=None):
137137
--require-pha - abort connection if client didn't provide certificate in
138138
post-handshake authentication
139139
--echo - function as an echo server
140+
--groups - specify what key exchange groups should be supported
141+
GROUPS - comma-separated list of enabled key exchange groups
140142
CERT, KEY - the file with key and certificates that will be used by client or
141143
server. The server can accept multiple pairs of `-c` and `-k` options
142144
to configure different certificates (like RSA and ECDSA)
@@ -197,6 +199,7 @@ def handleArgs(argv, argString, flagsList=[]):
197199
request_pha = False
198200
require_pha = False
199201
echo = False
202+
groups = None
200203

201204
for opt, arg in opts:
202205
if opt == "-k":
@@ -270,6 +273,8 @@ def handleArgs(argv, argString, flagsList=[]):
270273
tickets = int(arg)
271274
elif opt == "--cipherlist":
272275
ciphers.append(arg)
276+
elif opt == "--groups":
277+
groups = arg.split(',')
273278
elif opt == "--request-pha":
274279
request_pha = True
275280
elif opt == "--require-pha":
@@ -344,6 +349,8 @@ def handleArgs(argv, argString, flagsList=[]):
344349
retList.append(require_pha)
345350
if "echo" in flagsList:
346351
retList.append(echo)
352+
if "groups=" in flagsList:
353+
retList.append(groups)
347354
return retList
348355

349356

@@ -547,12 +554,13 @@ def serverCmd(argv):
547554
(address, privateKey, cert_chain, virtual_hosts, tacks, verifierDB,
548555
directory, reqCert,
549556
expLabel, expLength, dhparam, psk, psk_ident, psk_hash, ssl3,
550-
max_ver, tickets, cipherlist, request_pha, require_pha, echo) = \
557+
max_ver, tickets, cipherlist, request_pha, require_pha, echo,
558+
groups) = \
551559
handleArgs(argv, "kctbvdlL",
552560
["reqcert", "param=", "psk=",
553561
"psk-ident=", "psk-sha384", "ssl3", "max-ver=",
554562
"tickets=", "cipherlist=", "request-pha", "require-pha",
555-
"echo"])
563+
"echo", "groups="])
556564

557565

558566
if (cert_chain and not privateKey) or (not cert_chain and privateKey):
@@ -599,6 +607,17 @@ def serverCmd(argv):
599607
if cipherlist:
600608
settings.cipherNames = [item for cipher in cipherlist
601609
for item in cipher.split(',')]
610+
if groups:
611+
dh_groups = []
612+
ecc_groups = []
613+
for item in groups:
614+
if "ffdh" in item:
615+
dh_groups.append(item)
616+
else:
617+
ecc_groups.append(item)
618+
settings.dhGroups = dh_groups
619+
settings.eccCurves = ecc_groups
620+
settings.keyShares = []
602621

603622
class MySimpleEchoHandler(BaseRequestHandler):
604623
def handle(self):

tests/tlstest.py

+67-8
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
from xmlrpc import client as xmlrpclib
4545
import ssl
4646
from tlslite import *
47-
from tlslite.constants import KeyUpdateMessageType
47+
from tlslite.constants import KeyUpdateMessageType, SignatureScheme
4848

4949
try:
5050
from tack.structures.Tack import Tack
@@ -340,6 +340,32 @@ def connect():
340340

341341
test_no += 1
342342

343+
for curve, keySize, exp_sig_alg in (
344+
("brainpoolP256r1tls13", 256,
345+
SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256),
346+
("brainpoolP384r1tls13", 384,
347+
SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384),
348+
("brainpoolP512r1tls13", 512,
349+
SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512)):
350+
print("Test {0} - Two good ECDSA certs - {1}, TLSv1.3".format(test_no, curve))
351+
synchro.recv(1)
352+
connection = connect()
353+
settings = HandshakeSettings()
354+
settings.minVersion = (3, 4)
355+
settings.maxVersion = (3, 4)
356+
settings.eccCurves = [curve]
357+
settings.keyShares = []
358+
connection.handshakeClientCert(settings=settings)
359+
testConnClient(connection)
360+
assert connection.serverSigAlg == exp_sig_alg, \
361+
connection.serverSigAlg
362+
assert isinstance(connection.session.serverCertChain, X509CertChain)
363+
assert len(connection.session.serverCertChain.getEndEntityPublicKey()) \
364+
== keySize
365+
connection.close()
366+
367+
test_no += 1
368+
343369
print("Test {0} - Two good ECDSA certs - secp256r1, TLSv1.2".format(test_no))
344370
synchro.recv(1)
345371
connection = connect()
@@ -431,7 +457,7 @@ def connect():
431457

432458
test_no += 1
433459

434-
print("Test {0} - good X509 RSA and ECDSA, correct RSA and ECDSA sigalgs, RSA, TLSv1.3"
460+
print("Test {0} - good X509 RSA and ECDSA, correct RSA and ECDSA sigalgs, ECDSA, TLSv1.3"
435461
.format(test_no))
436462
synchro.recv(1)
437463
connection = connect()
@@ -444,7 +470,7 @@ def connect():
444470
testConnClient(connection)
445471
assert isinstance(connection.session.serverCertChain, X509CertChain)
446472
assert connection.session.serverCertChain.getEndEntityPublicKey().key_type\
447-
== "rsa"
473+
== "ecdsa"
448474
assert connection.version == (3, 4)
449475
connection.close()
450476

@@ -2233,6 +2259,29 @@ def connect():
22332259

22342260
test_no += 1
22352261

2262+
for curve, certChain, key in (("brainpoolP256r1tls13", x509ecdsaBrainpoolP256r1Chain, x509ecdsaBrainpoolP256r1Key),
2263+
("brainpoolP384r1tls13", x509ecdsaBrainpoolP384r1Chain, x509ecdsaBrainpoolP384r1Key),
2264+
("brainpoolP512r1tls13", x509ecdsaBrainpoolP512r1Chain, x509ecdsaBrainpoolP512r1Key)):
2265+
print("Test {0} - Two good ECDSA certs - {1}, TLSv1.3".format(test_no, curve))
2266+
synchro.send(b'R')
2267+
connection = connect()
2268+
settings = HandshakeSettings()
2269+
settings.minVersion = (3, 4)
2270+
settings.maxVersion = (3, 4)
2271+
settings.eccCurves = [curve, "secp256r1"]
2272+
settings.keyShares = []
2273+
v_host = VirtualHost()
2274+
v_host.keys = [Keypair(x509ecdsaKey, x509ecdsaChain.x509List)]
2275+
settings.virtual_hosts = [v_host]
2276+
connection.handshakeServer(certChain=certChain,
2277+
privateKey=key, settings=settings)
2278+
assert connection.extendedMasterSecret
2279+
#XXX assert connection.session.serverCertChain == certChain
2280+
testConnServer(connection)
2281+
connection.close()
2282+
2283+
test_no += 1
2284+
22362285
for curve, exp_chain in (("secp256r1", x509ecdsaChain),
22372286
("secp384r1", x509ecdsaP384Chain)):
22382287
print("Test {0} - Two good ECDSA certs - {1}, TLSv1.2"
@@ -2254,10 +2303,14 @@ def connect():
22542303

22552304
test_no += 1
22562305

2257-
for tls_ver in ("TLSv1.2", "TLSv1,3"):
2306+
for tls_ver in ("TLSv1.2", "TLSv1.3"):
22582307

2259-
print("Test {0} - good X509 RSA and ECDSA, correct RSA and ECDSA sigalgs, RSA, {1}"
2260-
.format(test_no, tls_ver))
2308+
if tls_ver == "TLSv1.2":
2309+
expected = "RSA"
2310+
else:
2311+
expected = "ECDSA"
2312+
print("Test {0} - good X509 RSA and ECDSA, correct RSA and ECDSA sigalgs, {2}, {1}"
2313+
.format(test_no, tls_ver, expected))
22612314
synchro.send(b'R')
22622315
connection = connect()
22632316
settings = HandshakeSettings()
@@ -2270,13 +2323,19 @@ def connect():
22702323
privateKey=x509KeyRSANonCA,
22712324
settings=settings)
22722325
assert connection.extendedMasterSecret
2273-
assert connection.session.serverCertChain == x509ChainRSANonCA
2326+
if tls_ver == "TLSv1.2":
2327+
# because in TLS 1.2 we don't send the signature_algorithms_cert
2328+
# extension, but send sig_algs with PKCS#1v1.5 sigalgs, RSA can be picked
2329+
# in TLS 1.3 we filter out PKCS#v1.5 so RSA cert will be picked only
2330+
# as a fallback
2331+
assert connection.session.serverCertChain == x509ChainRSANonCA, connection.session.serverCertChain.getEndEntityPublicKey().key_type
2332+
else:
2333+
assert connection.session.serverCertChain == x509ChainECDSANonCA, connection.session.serverCertChain.getEndEntityPublicKey().key_type
22742334
testConnServer(connection)
22752335
connection.close()
22762336

22772337
test_no += 1
22782338

2279-
22802339
print("Test {0} - good X509 RSA and ECDSA, bad RSA and good ECDSA sigalgs, ECDSA, {1}"
22812340
.format(test_no, tls_ver))
22822341
synchro.send(b'R')

tlslite/constants.py

+8
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,14 @@ def getHash(scheme):
321321
return hName
322322

323323

324+
# set of TLS 1.3 specific schemes for Brainpool curves
325+
TLS_1_3_BRAINPOOL_SIG_SCHEMES = set([
326+
SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256,
327+
SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384,
328+
SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512,
329+
])
330+
331+
324332
class AlgorithmOID(TLSEnum):
325333
"""
326334
Algorithm OIDs as defined in rfc5758(ecdsa),

tlslite/handshakesettings.py

+17-7
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@
3232
DSA_SIGNATURE_HASHES = ["sha512", "sha384", "sha256", "sha224", "sha1"]
3333
ECDSA_SIGNATURE_HASHES = ["sha512", "sha384", "sha256", "sha224", "sha1"]
3434
ALL_RSA_SIGNATURE_HASHES = RSA_SIGNATURE_HASHES + ["md5"]
35-
SIGNATURE_SCHEMES = ["Ed25519", "Ed448"]
35+
SIGNATURE_SCHEMES = ["Ed25519", "Ed448",
36+
"ecdsa_brainpoolP512r1tls13_sha512",
37+
"ecdsa_brainpoolP384r1tls13_sha384",
38+
"ecdsa_brainpoolP256r1tls13_sha256"]
3639
RSA_SCHEMES = ["pss", "pkcs1"]
3740
CURVE_NAMES = []
3841
if ML_KEM_AVAILABLE:
@@ -41,9 +44,12 @@
4144
# while secp521r1 is the most secure, it's also much slower than the others
4245
# so place it as the last one
4346
CURVE_NAMES += ["x25519", "x448", "secp384r1", "secp256r1",
44-
"secp521r1"]
45-
ALL_CURVE_NAMES = CURVE_NAMES + ["secp256k1", "brainpoolP512r1",
46-
"brainpoolP384r1", "brainpoolP256r1"]
47+
"secp521r1", "brainpoolP512r1",
48+
"brainpoolP384r1", "brainpoolP256r1",
49+
"brainpoolP256r1tls13",
50+
"brainpoolP384r1tls13",
51+
"brainpoolP512r1tls13"]
52+
ALL_CURVE_NAMES = CURVE_NAMES + ["secp256k1"]
4753
if ecdsaAllCurves:
4854
ALL_CURVE_NAMES += ["secp224r1", "secp192r1"]
4955
ALL_DH_GROUP_NAMES = ["ffdhe2048", "ffdhe3072", "ffdhe4096", "ffdhe6144",
@@ -62,7 +68,8 @@
6268
"x25519", "x448", "ffdhe2048",
6369
"ffdhe3072", "ffdhe4096", "ffdhe6144",
6470
"ffdhe8192", "secp256r1mlkem768", "x25519mlkem768",
65-
"secp384r1mlkem1024"]
71+
"secp384r1mlkem1024", "brainpoolP256r1tls13",
72+
"brainpoolP384r1tls13", "brainpoolP512r1tls13"]
6673
KNOWN_VERSIONS = ((3, 0), (3, 1), (3, 2), (3, 3), (3, 4))
6774
TICKET_CIPHERS = ["chacha20-poly1305", "aes256gcm", "aes128gcm", "aes128ccm",
6875
"aes128ccm_8", "aes256ccm", "aes256ccm_8"]
@@ -278,7 +285,10 @@ class HandshakeSettings(object):
278285
:ivar more_sig_schemes: List of additional signatures schemes (ones
279286
that don't use RSA-PKCS#1 v1.5, RSA-PSS, DSA, or ECDSA) to advertise
280287
as supported.
281-
Currently supported are: "Ed25519", and "Ed448".
288+
Currently supported are: "Ed25519", "Ed448",
289+
"ecdsa_brainpoolP256r1tls13_sha256",
290+
"ecdsa_brainpoolP384r1tls13_sha384",
291+
"ecdsa_brainpoolP512r1tls13_sha512".
282292
283293
:vartype eccCurves: list(str)
284294
:ivar eccCurves: List of named curves that are to be advertised as
@@ -534,7 +544,7 @@ def _sanityCheckECDHSettings(other):
534544
.format(unknownDHGroup))
535545

536546
# TLS 1.3 limits the allowed groups (RFC 8446,ch. 4.2.7.)
537-
if other.maxVersion == (3, 4):
547+
if (3, 3) not in other.versions and (3, 4) in other.versions:
538548
forbiddenGroup = HandshakeSettings._not_matching(other.eccCurves, TLS13_PERMITTED_GROUPS)
539549
if forbiddenGroup:
540550
raise ValueError("The following enabled groups are forbidden in TLS 1.3: {0}"

0 commit comments

Comments
 (0)