Skip to content

Commit

Permalink
add support for Brainpool curves in TLS 1.3 (RFC8734)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomato42 committed Sep 11, 2024
1 parent 0156727 commit 593c97d
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 26 deletions.
51 changes: 50 additions & 1 deletion tests/tlstest.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
from xmlrpc import client as xmlrpclib
import ssl
from tlslite import *
from tlslite.constants import KeyUpdateMessageType
from tlslite.constants import KeyUpdateMessageType, SignatureScheme

try:
from tack.structures.Tack import Tack
Expand Down Expand Up @@ -340,6 +340,32 @@ def connect():

test_no += 1

for curve, keySize, exp_sig_alg in (
("brainpoolP256r1tls13", 256,
SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256),
("brainpoolP384r1tls13", 384,
SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384),
("brainpoolP512r1tls13", 512,
SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512)):
print("Test {0} - Two good ECDSA certs - {1}, TLSv1.3".format(test_no, curve))
synchro.recv(1)
connection = connect()
settings = HandshakeSettings()
settings.minVersion = (3, 4)
settings.maxVersion = (3, 4)
settings.eccCurves = [curve]
settings.keyShares = []
connection.handshakeClientCert(settings=settings)
testConnClient(connection)
assert connection.serverSigAlg == exp_sig_alg, \
connection.serverSigAlg
assert isinstance(connection.session.serverCertChain, X509CertChain)
assert len(connection.session.serverCertChain.getEndEntityPublicKey()) \
== keySize
connection.close()

test_no += 1

print("Test {0} - Two good ECDSA certs - secp256r1, TLSv1.2".format(test_no))
synchro.recv(1)
connection = connect()
Expand Down Expand Up @@ -2233,6 +2259,29 @@ def connect():

test_no += 1

for curve, certChain, key in (("brainpoolP256r1tls13", x509ecdsaBrainpoolP256r1Chain, x509ecdsaBrainpoolP256r1Key),
("brainpoolP384r1tls13", x509ecdsaBrainpoolP384r1Chain, x509ecdsaBrainpoolP384r1Key),
("brainpoolP512r1tls13", x509ecdsaBrainpoolP512r1Chain, x509ecdsaBrainpoolP512r1Key)):
print("Test {0} - Two good ECDSA certs - {1}, TLSv1.3".format(test_no, curve))
synchro.send(b'R')
connection = connect()
settings = HandshakeSettings()
settings.minVersion = (3, 4)
settings.maxVersion = (3, 4)
settings.eccCurves = [curve, "secp256r1"]
settings.keyShares = []
v_host = VirtualHost()
v_host.keys = [Keypair(x509ecdsaKey, x509ecdsaChain.x509List)]
settings.virtual_hosts = [v_host]
connection.handshakeServer(certChain=certChain,
privateKey=key, settings=settings)
assert connection.extendedMasterSecret
#XXX assert connection.session.serverCertChain == certChain
testConnServer(connection)
connection.close()

test_no += 1

for curve, exp_chain in (("secp256r1", x509ecdsaChain),
("secp384r1", x509ecdsaP384Chain)):
print("Test {0} - Two good ECDSA certs - {1}, TLSv1.2"
Expand Down
17 changes: 13 additions & 4 deletions tlslite/handshakesettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,19 @@
DSA_SIGNATURE_HASHES = ["sha512", "sha384", "sha256", "sha224", "sha1"]
ECDSA_SIGNATURE_HASHES = ["sha512", "sha384", "sha256", "sha224", "sha1"]
ALL_RSA_SIGNATURE_HASHES = RSA_SIGNATURE_HASHES + ["md5"]
SIGNATURE_SCHEMES = ["Ed25519", "Ed448"]
SIGNATURE_SCHEMES = ["Ed25519", "Ed448", "ecdsa_brainpoolP256r1tls13_sha256",
"ecdsa_brainpoolP384r1tls13_sha384",
"ecdsa_brainpoolP512r1tls13_sha512"]
RSA_SCHEMES = ["pss", "pkcs1"]
# while secp521r1 is the most secure, it's also much slower than the others
# so place it as the last one
CURVE_NAMES = ["x25519", "x448", "secp384r1", "secp256r1",
"secp521r1"]
ALL_CURVE_NAMES = CURVE_NAMES + ["secp256k1", "brainpoolP512r1",
"brainpoolP384r1", "brainpoolP256r1"]
"brainpoolP384r1", "brainpoolP256r1",
"brainpoolP256r1tls13",
"brainpoolP384r1tls13",
"brainpoolP512r1tls13"]
if ecdsaAllCurves:
ALL_CURVE_NAMES += ["secp224r1", "secp192r1"]
ALL_DH_GROUP_NAMES = ["ffdhe2048", "ffdhe3072", "ffdhe4096", "ffdhe6144",
Expand All @@ -57,7 +62,8 @@
TLS13_PERMITTED_GROUPS = ["secp256r1", "secp384r1", "secp521r1",
"x25519", "x448", "ffdhe2048",
"ffdhe3072", "ffdhe4096", "ffdhe6144",
"ffdhe8192"]
"ffdhe8192", "brainpoolP256r1tls13",
"brainpoolP384r1tls13", "brainpoolP512r1tls13"]
KNOWN_VERSIONS = ((3, 0), (3, 1), (3, 2), (3, 3), (3, 4))
TICKET_CIPHERS = ["chacha20-poly1305", "aes256gcm", "aes128gcm", "aes128ccm",
"aes128ccm_8", "aes256ccm", "aes256ccm_8"]
Expand Down Expand Up @@ -273,7 +279,10 @@ class HandshakeSettings(object):
:ivar more_sig_schemes: List of additional signatures schemes (ones
that don't use RSA-PKCS#1 v1.5, RSA-PSS, DSA, or ECDSA) to advertise
as supported.
Currently supported are: "Ed25519", and "Ed448".
Currently supported are: "Ed25519", "Ed448",
"ecdsa_brainpoolP256r1tls13_sha256",
"ecdsa_brainpoolP384r1tls13_sha384",
"ecdsa_brainpoolP512r1tls13_sha512".
:vartype eccCurves: list(str)
:ivar eccCurves: List of named curves that are to be advertised as
Expand Down
116 changes: 96 additions & 20 deletions tlslite/tlsconnection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1419,6 +1419,16 @@ def _clientTLS13Handshake(self, settings, session, clientHello,
"server selected signature method invalid for the "
"certificate it presented (curve mismatch)")

salt_len = None
method = publicKey.verify
elif signature_scheme in (
SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256,
SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384,
SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512,
):
scheme = SignatureScheme.toRepr(signature_scheme)
pad_type = None
hash_name = SignatureScheme.getHash(scheme)
salt_len = None
method = publicKey.verify
else:
Expand Down Expand Up @@ -1525,6 +1535,16 @@ def _clientTLS13Handshake(self, settings, session, clientHello,
salt_len = None
sig_func = privateKey.sign
ver_func = privateKey.verify
elif signature_scheme in (
SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256,
SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384,
SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512,
):
pad_type = None
hash_name = SignatureScheme.getHash(scheme)
salt_len = None
sig_func = privateKey.sign
ver_func = privateKey.verify
else:
pad_type = SignatureScheme.getPadding(scheme)
hash_name = SignatureScheme.getHash(scheme)
Expand Down Expand Up @@ -1965,7 +1985,9 @@ def _check_certchain_with_settings(self, cert_chain, settings):
"advertise support for: {0}".format(curve_name)):
yield result
if self.version >= (3, 4):
if curve_name not in ('secp256r1', 'secp384r1', 'secp521r1'):
if curve_name not in ('secp256r1', 'secp384r1', 'secp521r1',
'brainpoolP256r1', 'brainpoolP384r1',
'brainpoolP512r1'):
for result in self._sendError(
AlertDescription.illegal_parameter,
"Peer sent certificate with curve not supported "
Expand All @@ -1975,8 +1997,14 @@ def _check_certchain_with_settings(self, cert_chain, settings):
sig_alg_for_curve = 'sha256'
elif curve_name == 'secp384r1':
sig_alg_for_curve = 'sha384'
elif curve_name == 'secp521r1':
sig_alg_for_curve = 'sha512'
elif curve_name == 'brainpoolP256r1':
sig_alg_for_curve = 'sha256'
elif curve_name == 'brainpoolP384r1':
sig_alg_for_curve = 'sha384'
else:
assert curve_name == 'secp521r1'
assert curve_name == 'brainpoolP512r1'
sig_alg_for_curve = 'sha512'
if sig_alg_for_curve not in settings.ecdsaSigHashes:
for result in self._sendError(
Expand Down Expand Up @@ -2951,6 +2979,7 @@ def _serverTLS13Handshake(self, settings, clientHello, cipherSuite,
certificate_verify = CertificateVerify(self.version)

signature_scheme = getattr(SignatureScheme, scheme)
self.serverSigAlg = signature_scheme

signature_context = \
KeyExchange.calcVerifyBytes((3, 4), self._handshake_hash,
Expand All @@ -2970,6 +2999,16 @@ def _serverTLS13Handshake(self, settings, clientHello, cipherSuite,
saltLen = None
sig_func = privateKey.sign
ver_func = privateKey.verify
elif signature_scheme in (
SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256,
SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384,
SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512,
):
hashName = SignatureScheme.getHash(scheme)
padType = None
saltLen = None
sig_func = privateKey.sign
ver_func = privateKey.verify
else:
padType = SignatureScheme.getPadding(scheme)
hashName = SignatureScheme.getHash(scheme)
Expand Down Expand Up @@ -3091,6 +3130,15 @@ def _serverTLS13Handshake(self, settings, clientHello, cipherSuite,
pad_type = None
salt_len = None
ver_func = public_key.verify
elif signature_scheme in (
SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256,
SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384,
SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512,
):
pad_type = None
salt_len = None
hash_name = SignatureScheme.getHash(scheme)
ver_func = public_key.verify
else:
scheme = SignatureScheme.toRepr(signature_scheme)
pad_type = SignatureScheme.getPadding(scheme)
Expand Down Expand Up @@ -4262,10 +4310,14 @@ def _server_select_certificate(self, settings, client_hello,

if version >= (3, 4):
if GroupName.toRepr(curve) not in \
('secp256r1', 'secp384r1', 'secp521r1'):
('secp256r1', 'secp384r1', 'secp521r1',
'brainpoolP256r1', 'brainpoolP384r1',
'brainpoolP512r1'):
raise TLSIllegalParameterException(
"Curve in public key is not supported "
"in TLS1.3")
"Curve in public key ({0}) is not "
"supported "
"in TLS1.3".format(
GroupName.toRepr(curve)))

# If all mandatory checks passed add
# this as possible certificate we can use.
Expand Down Expand Up @@ -4817,7 +4869,6 @@ def _pickServerKeyExchangeSig(settings, clientHello, certList=None,
supported = TLSConnection._sigHashesToList(settings,
certList=certs,
version=version)

for schemeID in supported:
if schemeID in hashAndAlgsExt.sigalgs:
name = SignatureScheme.toRepr(schemeID)
Expand Down Expand Up @@ -4851,24 +4902,43 @@ def _sigHashesToList(settings, privateKey=None, certList=None,
continue
if certType and sig_scheme != certType:
continue
sigAlgs.append(getattr(SignatureScheme, sig_scheme.lower()))
try:
sigAlgs.append(getattr(SignatureScheme, sig_scheme))
except AttributeError:
sigAlgs.append(
getattr(SignatureScheme, sig_scheme.lower()))

if not certType or certType == "ecdsa":
for hashName in settings.ecdsaSigHashes:
# only SHA256, SHA384 and SHA512 are allowed in TLS 1.3
if version > (3, 3) and hashName in ("sha1", "sha224"):
continue

# in TLS 1.3 ECDSA key curve is bound to hash
if publicKey and version > (3, 3):
curve = publicKey.curve_name
matching_hash = TLSConnection._curve_name_to_hash_name(
curve)
if hashName != matching_hash:
if version > (3, 3) and publicKey and \
"BRAINPOOL" in publicKey.curve_name:
# brainpool in TLS 1.3 uses special signature schemes
curve = publicKey.curve_name
if curve == "BRAINPOOLP256r1":
sigAlgs.append(
SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256)
elif curve == "BRAINPOOLP384r1":
sigAlgs.append(
SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384)
else:
assert curve == "BRAINPOOLP512r1"
sigAlgs.append(
SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512)
else:
for hashName in settings.ecdsaSigHashes:
# only SHA256, SHA384 and SHA512 are allowed in TLS 1.3
if version > (3, 3) and hashName in ("sha1", "sha224"):
continue

sigAlgs.append((getattr(HashAlgorithm, hashName),
SignatureAlgorithm.ecdsa))
# in TLS 1.3 ECDSA key curve is bound to hash
if publicKey and version > (3, 3):
curve = publicKey.curve_name
matching_hash = TLSConnection._curve_name_to_hash_name(
curve)
if hashName != matching_hash:
continue

sigAlgs.append((getattr(HashAlgorithm, hashName),
SignatureAlgorithm.ecdsa))

if not certType or certType == "dsa":
for hashName in settings.dsaSigHashes:
Expand Down Expand Up @@ -4934,5 +5004,11 @@ def _curve_name_to_hash_name(curve_name):
return "sha384"
if curve_name == "NIST521p":
return "sha512"
if curve_name == "BRAINPOOLP256r1":
return "sha256"
if curve_name == "BRAINPOOLP384r1":
return "sha384"
if curve_name == "BRAINPOOLP512r1":
return "sha512"
raise TLSIllegalParameterException(
"Curve {0} is not supported in TLS 1.3".format(curve_name))
5 changes: 4 additions & 1 deletion tlslite/utils/ecc.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ def getCurveByName(curveName):
'secp256k1':ecdsa.SECP256k1,
'brainpoolP256r1': ecdsa.BRAINPOOLP256r1,
'brainpoolP384r1': ecdsa.BRAINPOOLP384r1,
'brainpoolP512r1': ecdsa.BRAINPOOLP512r1}
'brainpoolP512r1': ecdsa.BRAINPOOLP512r1,
'brainpoolP256r1tls13': ecdsa.BRAINPOOLP256r1,
'brainpoolP384r1tls13': ecdsa.BRAINPOOLP384r1,
'brainpoolP512r1tls13': ecdsa.BRAINPOOLP512r1}
if ecdsaAllCurves:
curveMap['secp224r1'] = ecdsa.NIST224p
curveMap['secp192r1'] = ecdsa.NIST192p
Expand Down

0 comments on commit 593c97d

Please sign in to comment.