Skip to content

Commit

Permalink
Add Brainpool EC-curves support
Browse files Browse the repository at this point in the history
This commit adds the support of Brainpool curves to jwcrypto. The Brainpool curves defined in RFC 5639 are mandatory for use in german e-health systems as defined by the Federal Office of Information Security (BSI) and National Digital Health Agency (gematik GmbH).

In order to use the public E-Health APIs clients are required to:

* Load and use the Brainpool keys using JWK
* Sign and verify the signatures using the Brainpool elliptic curves using JWS
* Encrypt and decrypt the data using the Brainpool elliptic curves and AES using JWE

At the time of this commit there is no official standardization of these algorithms for JOSE/JWK/JWS/JWE. The use of these algorithms is specified solely by the gematik GmbH – National Digital Health Agency - for use in german e-health applications.

Signed-off-by: Sergej Suskov <git@spilikin.dev>
  • Loading branch information
spilikin committed Oct 10, 2022
1 parent 14d1f81 commit 55458fa
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 3 deletions.
53 changes: 52 additions & 1 deletion jwcrypto/jwa.py
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,54 @@ class _A256Gcm(_AesGcm, JWAAlgorithm):
algorithm_use = 'enc'


class _BP256R1(_RawEC, JWAAlgorithm):

name = "BP256R1"
description = (
"ECDSA using Brainpool256R1 curve and SHA-256"
" (unregistered, custom-defined in breach"
" of IETF rules by gematik GmbH)"
)
keysize = 256
algorithm_usage_location = 'alg'
algorithm_use = 'sig'

def __init__(self):
super(_BP256R1, self).__init__('BP-256', hashes.SHA256())


class _BP384R1(_RawEC, JWAAlgorithm):

name = "BP384R1"
description = (
"ECDSA using Brainpool384R1 curve and SHA-384"
" (unregistered, custom-defined in breach"
" of IETF rules by gematik GmbH)"
)
keysize = 384
algorithm_usage_location = 'alg'
algorithm_use = 'sig'

def __init__(self):
super(_BP384R1, self).__init__('BP-384', hashes.SHA384())


class _BP512R1(_RawEC, JWAAlgorithm):

name = "BP512R1"
description = (
"ECDSA using Brainpool512R1 curve and SHA-512"
" (unregistered, custom-defined in breach"
" of IETF rules by gematik GmbH)"
)
keysize = 512
algorithm_usage_location = 'alg'
algorithm_use = 'sig'

def __init__(self):
super(_BP512R1, self).__init__('BP-512', hashes.SHA512())


class JWA:
"""JWA Signing Algorithms.
Expand Down Expand Up @@ -1097,7 +1145,10 @@ class JWA:
'A256CBC-HS512': _A256CbcHs512,
'A128GCM': _A128Gcm,
'A192GCM': _A192Gcm,
'A256GCM': _A256Gcm
'A256GCM': _A256Gcm,
'BP256R1': _BP256R1,
'BP384R1': _BP384R1,
'BP512R1': _BP512R1
}

@classmethod
Expand Down
23 changes: 21 additions & 2 deletions jwcrypto/jwk.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,17 @@ class ParmType(Enum):
'Ed25519': 'Ed25519 signature algorithm key pairs',
'Ed448': 'Ed448 signature algorithm key pairs',
'X25519': 'X25519 function key pairs',
'X448': 'X448 function key pairs'}
'X448': 'X448 function key pairs',
'BP-256': 'BrainpoolP256R1 curve'
' (unregistered, custom-defined in breach'
' of IETF rules by gematik GmbH)',
'BP-384': 'BrainpoolP384R1 curve'
' (unregistered, custom-defined in breach'
' of IETF rules by gematik GmbH)',
'BP-512': 'BrainpoolP512R1 curve'
' (unregistered, custom-defined in breach'
' of IETF rules by gematik GmbH)'
}
"""Registry of allowed Elliptic Curves"""

# RFC 7517 - 8.2
Expand All @@ -186,7 +196,10 @@ class ParmType(Enum):
JWKpycaCurveMap = {'secp256r1': 'P-256',
'secp384r1': 'P-384',
'secp521r1': 'P-521',
'secp256k1': 'secp256k1'}
'secp256k1': 'secp256k1',
'brainpoolP256r1': 'BP-256',
'brainpoolP384r1': 'BP-384',
'brainpoolP512r1': 'BP-512'}

IANANamedInformationHashAlgorithmRegistry = {
'sha-256': hashes.SHA256(),
Expand Down Expand Up @@ -453,6 +466,12 @@ def _get_curve_by_name(self, name, ctype=None):
return ec.SECP521R1()
elif cname == 'secp256k1':
return ec.SECP256K1()
elif cname == 'BP-256':
return ec.BrainpoolP256R1()
elif cname == 'BP-384':
return ec.BrainpoolP384R1()
elif cname == 'BP-512':
return ec.BrainpoolP512R1()
elif cname in _OKP_CURVES_TABLE:
return _OKP_CURVES_TABLE[cname]
else:
Expand Down
103 changes: 103 additions & 0 deletions jwcrypto/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,66 @@
"x": "Ss6na3mcci8Ud4lQrjaB_T40sfKApEcl2RLIWOJdjow",
"y": "7l9qIKtKPW6oEiOYBt7r22Sm0mtFJU-yBkkvMvpscd8",
"d": "GYhU2vrYGZrjLZn71Xniqm54Mi53xiYtaTLawzaf9dA"
}
]
}

PublicKeys_brainpool = {
"keys": [
{
"kty": "EC",
"crv": "BP-256",
"x": "mpkJ29_CYAD0mzQ_MsrbjFMFYtcc9Oxpro37Fa4cLfI",
"y": "iBfhNHk0cI73agNpjbKW62dvuVxn7kxp1Sm8oDnzHl8",
},
{
"kty": "EC",
"crv": "BP-384",
"x": ("WZanneaC2Hi3xslA4znJv7otyEdV5dTPzNUvBjBXPM"
"ytf4mRY9JaAITdItjvUTAh"),
"y": ("KNLRTNdvUg66aB_TVW4POZkE3q8S0YoQrCzYUrExRDe"
"_BXikkqIama-GYQ3UBOQL"),
},
{
"kty": "EC",
"crv": "BP-512",
"x": ("aQXpvz7DH9OK5eFNO9dY3BdPY1v0-8Rg9KC322PY1Jy"
"BJq3EhT0uR_-tgbL2E_aGP6k56lF1xIOOtQxo8zziGA"),
"y": ("l9XLHHncigOPr5Tvnj_mVzBFv6i7rdBQrLTq3RXZlCC"
"_f_q6L2o79K9IrN_J2wWxAfS8ekuGPGlHZUzK-3D9sA"),
}
]
}

PrivateKeys_brainpool = {
"keys": [
{
"kty": "EC",
"crv": "BP-256",
"x": "mpkJ29_CYAD0mzQ_MsrbjFMFYtcc9Oxpro37Fa4cLfI",
"y": "iBfhNHk0cI73agNpjbKW62dvuVxn7kxp1Sm8oDnzHl8",
"d": "KdKRgq0WEM97BQw3jpW_fTOep6fn-Samv4DfDNb-4s4"
},
{
"kty": "EC",
"crv": "BP-384",
"x": ("WZanneaC2Hi3xslA4znJv7otyEdV5dTPzNUvBjBXPM"
"ytf4mRY9JaAITdItjvUTAh"),
"y": ("KNLRTNdvUg66aB_TVW4POZkE3q8S0YoQrCzYUrExRDe"
"_BXikkqIama-GYQ3UBOQL"),
"d": ("B5WeRV0-RztAPAhRbphSAUrsIzy-eSfWGSM5FxOQGlJ"
"cq-ECLA_-SIlH7NdWIEJY")
},
{
"kty": "EC",
"crv": "BP-512",
"x": ("aQXpvz7DH9OK5eFNO9dY3BdPY1v0-8Rg9KC322PY1Jy"
"BJq3EhT0uR_-tgbL2E_aGP6k56lF1xIOOtQxo8zziGA"),
"y": ("l9XLHHncigOPr5Tvnj_mVzBFv6i7rdBQrLTq3RXZlCC"
"_f_q6L2o79K9IrN_J2wWxAfS8ekuGPGlHZUzK-3D9sA"),
"d": ("F_LJ9rebAjOtxoMUfngIywYsnJlZNjy3gxNAEvHjSkL"
"m6RUUdLXDwc50EMp0LeTh1ku039D5kldK3S9Xi0yKZA")
}
]
}

Expand Down Expand Up @@ -385,6 +444,15 @@ def test_generate_EC_key(self):
# New secp256k curve
key = jwk.JWK.generate(kty='EC', curve='secp256k1')
key.get_op_key('verify', 'secp256k1')
# Brainpool256R1 curve
key = jwk.JWK.generate(kty='EC', crv='BP-256')
key.get_op_key('verify', 'BP-256')
# Brainpool384R1 curve
key = jwk.JWK.generate(kty='EC', crv='BP-384')
key.get_op_key('verify', 'BP-384')
# Brainpool256R1 curve
key = jwk.JWK.generate(kty='EC', crv='BP-512')
key.get_op_key('verify', 'BP-512')

def test_generate_OKP_keys(self):
for crv in jwk.ImplementedOkpCurves:
Expand Down Expand Up @@ -576,6 +644,16 @@ def test_create_priKeys_secp256k1(self):
for key in keylist:
jwk.JWK(**key)

def test_create_pubKeys_brainpool(self):
keylist = PublicKeys_brainpool['keys']
for key in keylist:
jwk.JWK(**key)

def test_create_priKeys_brainpool(self):
keylist = PrivateKeys_brainpool['keys']
for key in keylist:
jwk.JWK(**key)

def test_thumbprint_eddsa(self):
for i in range(0, len(PublicKeys_EdDsa['keys'])):
k = jwk.JWK(**PublicKeys_EdDsa['keys'][i])
Expand Down Expand Up @@ -987,6 +1065,31 @@ def test_secp256k1_signing_and_verification(self):
jws_verify.verify(key.public())
self.assertEqual(jws_verify.payload, payload)

def test_brainpool_signing_and_verification(self):
for key_data in PrivateKeys_brainpool['keys']:
key = jwk.JWK(**key_data)
payload = bytes(bytearray(A1_payload))
jws_test = jws.JWS(payload)

curve_name = key.get('crv')
if curve_name == "BP-256":
alg = "BP256R1"
elif curve_name == "BP-384":
alg = "BP384R1"
else:
alg = "BP512R1"

jws_test.allowed_algs = [alg]
jws_test.add_signature(key, None, json_encode({"alg": alg}), None)
jws_test_serialization_compact = jws_test.serialize(compact=True)

jws_verify = jws.JWS()
jws_verify.allowed_algs = [alg]
jws_verify.deserialize(jws_test_serialization_compact)
jws_verify.verify(key.public())

self.assertEqual(jws_verify.payload, payload)

def test_jws_issue_224(self):
key = jwk.JWK().generate(kty='oct')

Expand Down

0 comments on commit 55458fa

Please sign in to comment.