forked from loafle/openapi-generator-original
[Python-experimental] Use DER encoding for ECDSA signatures, add parameter to configure hash algorithm (#5924)
* Use DER encoding for ECDSA signatures * Use DER encoding for ECDSA signatures * Use DER encoding for ECDSA signatures * Use DER encoding for ECDSA signatures * fix python unit tests for http message signature * Fix error message * format python code * format python code
This commit is contained in:
@@ -53,6 +53,10 @@ ALGORITHM_ECDSA_KEY_SIGNING_ALGORITHMS = {
|
||||
ALGORITHM_ECDSA_MODE_DETERMINISTIC_RFC6979
|
||||
}
|
||||
|
||||
# The cryptographic hash algorithm for the message signature.
|
||||
HASH_SHA256 = 'sha256'
|
||||
HASH_SHA512 = 'sha512'
|
||||
|
||||
|
||||
class HttpSigningConfiguration(object):
|
||||
"""The configuration parameters for the HTTP signature security scheme.
|
||||
@@ -98,9 +102,15 @@ class HttpSigningConfiguration(object):
|
||||
Supported values are:
|
||||
1. For RSA keys: RSASSA-PSS, RSASSA-PKCS1-v1_5.
|
||||
2. For ECDSA keys: fips-186-3, deterministic-rfc6979.
|
||||
The default value is inferred from the private key.
|
||||
The default value for RSA keys is RSASSA-PSS.
|
||||
The default value for ECDSA keys is fips-186-3.
|
||||
If None, the signing algorithm is inferred from the private key.
|
||||
The default signing algorithm for RSA keys is RSASSA-PSS.
|
||||
The default signing algorithm for ECDSA keys is fips-186-3.
|
||||
:param hash_algorithm: The hash algorithm for the signature. Supported values are
|
||||
sha256 and sha512.
|
||||
If the signing_scheme is rsa-sha256, the hash algorithm must be set
|
||||
to None or sha256.
|
||||
If the signing_scheme is rsa-sha512, the hash algorithm must be set
|
||||
to None or sha512.
|
||||
:param signature_max_validity: The signature max validity, expressed as
|
||||
a datetime.timedelta value. It must be a positive value.
|
||||
"""
|
||||
@@ -108,6 +118,7 @@ class HttpSigningConfiguration(object):
|
||||
private_key_passphrase=None,
|
||||
signed_headers=None,
|
||||
signing_algorithm=None,
|
||||
hash_algorithm=None,
|
||||
signature_max_validity=None):
|
||||
self.key_id = key_id
|
||||
if signing_scheme not in {SCHEME_HS2019, SCHEME_RSA_SHA256, SCHEME_RSA_SHA512}:
|
||||
@@ -118,6 +129,24 @@ class HttpSigningConfiguration(object):
|
||||
self.private_key_path = private_key_path
|
||||
self.private_key_passphrase = private_key_passphrase
|
||||
self.signing_algorithm = signing_algorithm
|
||||
self.hash_algorithm = hash_algorithm
|
||||
if signing_scheme == SCHEME_RSA_SHA256:
|
||||
if self.hash_algorithm is None:
|
||||
self.hash_algorithm = HASH_SHA256
|
||||
elif self.hash_algorithm != HASH_SHA256:
|
||||
raise Exception("Hash algorithm must be sha256 when security scheme is %s" %
|
||||
SCHEME_RSA_SHA256)
|
||||
elif signing_scheme == SCHEME_RSA_SHA512:
|
||||
if self.hash_algorithm is None:
|
||||
self.hash_algorithm = HASH_SHA512
|
||||
elif self.hash_algorithm != HASH_SHA512:
|
||||
raise Exception("Hash algorithm must be sha512 when security scheme is %s" %
|
||||
SCHEME_RSA_SHA512)
|
||||
elif signing_scheme == SCHEME_HS2019:
|
||||
if self.hash_algorithm is None:
|
||||
self.hash_algorithm = HASH_SHA256
|
||||
elif self.hash_algorithm not in {HASH_SHA256, HASH_SHA512}:
|
||||
raise Exception("Invalid hash algorithm")
|
||||
if signature_max_validity is not None and signature_max_validity.total_seconds() < 0:
|
||||
raise Exception("The signature max validity must be a positive value")
|
||||
self.signature_max_validity = signature_max_validity
|
||||
@@ -309,14 +338,14 @@ class HttpSigningConfiguration(object):
|
||||
The prefix is a string that identifies the cryptographc hash. It is used
|
||||
to generate the 'Digest' header as specified in RFC 3230.
|
||||
"""
|
||||
if self.signing_scheme in {SCHEME_RSA_SHA512, SCHEME_HS2019}:
|
||||
if self.hash_algorithm == HASH_SHA512:
|
||||
digest = SHA512.new()
|
||||
prefix = 'SHA-512='
|
||||
elif self.signing_scheme == SCHEME_RSA_SHA256:
|
||||
elif self.hash_algorithm == HASH_SHA256:
|
||||
digest = SHA256.new()
|
||||
prefix = 'SHA-256='
|
||||
else:
|
||||
raise Exception("Unsupported signing algorithm: {0}".format(self.signing_scheme))
|
||||
raise Exception("Unsupported hash algorithm: {0}".format(self.hash_algorithm))
|
||||
digest.update(data)
|
||||
return digest, prefix
|
||||
|
||||
@@ -340,7 +369,10 @@ class HttpSigningConfiguration(object):
|
||||
if sig_alg is None:
|
||||
sig_alg = ALGORITHM_ECDSA_MODE_FIPS_186_3
|
||||
if sig_alg in ALGORITHM_ECDSA_KEY_SIGNING_ALGORITHMS:
|
||||
signature = DSS.new(self.private_key, sig_alg).sign(digest)
|
||||
# draft-ietf-httpbis-message-signatures-00 does not specify the ECDSA encoding.
|
||||
# Issue: https://github.com/w3c-ccg/http-signatures/issues/107
|
||||
signature = DSS.new(key=self.private_key, mode=sig_alg,
|
||||
encoding='der').sign(digest)
|
||||
else:
|
||||
raise Exception("Unsupported signature algorithm: {0}".format(sig_alg))
|
||||
else:
|
||||
|
||||
@@ -61,6 +61,10 @@ ALGORITHM_ECDSA_KEY_SIGNING_ALGORITHMS = {
|
||||
ALGORITHM_ECDSA_MODE_DETERMINISTIC_RFC6979
|
||||
}
|
||||
|
||||
# The cryptographic hash algorithm for the message signature.
|
||||
HASH_SHA256 = 'sha256'
|
||||
HASH_SHA512 = 'sha512'
|
||||
|
||||
|
||||
class HttpSigningConfiguration(object):
|
||||
"""The configuration parameters for the HTTP signature security scheme.
|
||||
@@ -106,9 +110,15 @@ class HttpSigningConfiguration(object):
|
||||
Supported values are:
|
||||
1. For RSA keys: RSASSA-PSS, RSASSA-PKCS1-v1_5.
|
||||
2. For ECDSA keys: fips-186-3, deterministic-rfc6979.
|
||||
The default value is inferred from the private key.
|
||||
The default value for RSA keys is RSASSA-PSS.
|
||||
The default value for ECDSA keys is fips-186-3.
|
||||
If None, the signing algorithm is inferred from the private key.
|
||||
The default signing algorithm for RSA keys is RSASSA-PSS.
|
||||
The default signing algorithm for ECDSA keys is fips-186-3.
|
||||
:param hash_algorithm: The hash algorithm for the signature. Supported values are
|
||||
sha256 and sha512.
|
||||
If the signing_scheme is rsa-sha256, the hash algorithm must be set
|
||||
to None or sha256.
|
||||
If the signing_scheme is rsa-sha512, the hash algorithm must be set
|
||||
to None or sha512.
|
||||
:param signature_max_validity: The signature max validity, expressed as
|
||||
a datetime.timedelta value. It must be a positive value.
|
||||
"""
|
||||
@@ -116,6 +126,7 @@ class HttpSigningConfiguration(object):
|
||||
private_key_passphrase=None,
|
||||
signed_headers=None,
|
||||
signing_algorithm=None,
|
||||
hash_algorithm=None,
|
||||
signature_max_validity=None):
|
||||
self.key_id = key_id
|
||||
if signing_scheme not in {SCHEME_HS2019, SCHEME_RSA_SHA256, SCHEME_RSA_SHA512}:
|
||||
@@ -126,6 +137,24 @@ class HttpSigningConfiguration(object):
|
||||
self.private_key_path = private_key_path
|
||||
self.private_key_passphrase = private_key_passphrase
|
||||
self.signing_algorithm = signing_algorithm
|
||||
self.hash_algorithm = hash_algorithm
|
||||
if signing_scheme == SCHEME_RSA_SHA256:
|
||||
if self.hash_algorithm is None:
|
||||
self.hash_algorithm = HASH_SHA256
|
||||
elif self.hash_algorithm != HASH_SHA256:
|
||||
raise Exception("Hash algorithm must be sha256 when security scheme is %s" %
|
||||
SCHEME_RSA_SHA256)
|
||||
elif signing_scheme == SCHEME_RSA_SHA512:
|
||||
if self.hash_algorithm is None:
|
||||
self.hash_algorithm = HASH_SHA512
|
||||
elif self.hash_algorithm != HASH_SHA512:
|
||||
raise Exception("Hash algorithm must be sha512 when security scheme is %s" %
|
||||
SCHEME_RSA_SHA512)
|
||||
elif signing_scheme == SCHEME_HS2019:
|
||||
if self.hash_algorithm is None:
|
||||
self.hash_algorithm = HASH_SHA256
|
||||
elif self.hash_algorithm not in {HASH_SHA256, HASH_SHA512}:
|
||||
raise Exception("Invalid hash algorithm")
|
||||
if signature_max_validity is not None and signature_max_validity.total_seconds() < 0:
|
||||
raise Exception("The signature max validity must be a positive value")
|
||||
self.signature_max_validity = signature_max_validity
|
||||
@@ -317,14 +346,14 @@ class HttpSigningConfiguration(object):
|
||||
The prefix is a string that identifies the cryptographc hash. It is used
|
||||
to generate the 'Digest' header as specified in RFC 3230.
|
||||
"""
|
||||
if self.signing_scheme in {SCHEME_RSA_SHA512, SCHEME_HS2019}:
|
||||
if self.hash_algorithm == HASH_SHA512:
|
||||
digest = SHA512.new()
|
||||
prefix = 'SHA-512='
|
||||
elif self.signing_scheme == SCHEME_RSA_SHA256:
|
||||
elif self.hash_algorithm == HASH_SHA256:
|
||||
digest = SHA256.new()
|
||||
prefix = 'SHA-256='
|
||||
else:
|
||||
raise Exception("Unsupported signing algorithm: {0}".format(self.signing_scheme))
|
||||
raise Exception("Unsupported hash algorithm: {0}".format(self.hash_algorithm))
|
||||
digest.update(data)
|
||||
return digest, prefix
|
||||
|
||||
@@ -348,7 +377,10 @@ class HttpSigningConfiguration(object):
|
||||
if sig_alg is None:
|
||||
sig_alg = ALGORITHM_ECDSA_MODE_FIPS_186_3
|
||||
if sig_alg in ALGORITHM_ECDSA_KEY_SIGNING_ALGORITHMS:
|
||||
signature = DSS.new(self.private_key, sig_alg).sign(digest)
|
||||
# draft-ietf-httpbis-message-signatures-00 does not specify the ECDSA encoding.
|
||||
# Issue: https://github.com/w3c-ccg/http-signatures/issues/107
|
||||
signature = DSS.new(key=self.private_key, mode=sig_alg,
|
||||
encoding='der').sign(digest)
|
||||
else:
|
||||
raise Exception("Unsupported signature algorithm: {0}".format(sig_alg))
|
||||
else:
|
||||
|
||||
@@ -151,12 +151,12 @@ class MockPoolManager(object):
|
||||
"{0}: {1}".format(key.lower(), value) for key, value in signed_headers_list]
|
||||
string_to_sign = "\n".join(header_items)
|
||||
digest = None
|
||||
if self.signing_cfg.signing_scheme in {signing.SCHEME_RSA_SHA512, signing.SCHEME_HS2019}:
|
||||
if self.signing_cfg.hash_algorithm == signing.HASH_SHA512:
|
||||
digest = SHA512.new()
|
||||
elif self.signing_cfg.signing_scheme == signing.SCHEME_RSA_SHA256:
|
||||
elif self.signing_cfg.hash_algorithm == signing.HASH_SHA256:
|
||||
digest = SHA256.new()
|
||||
else:
|
||||
self._tc.fail("Unsupported signature scheme: {0}".format(self.signing_cfg.signing_scheme))
|
||||
self._tc.fail("Unsupported hash algorithm: {0}".format(self.signing_cfg.hash_algorithm))
|
||||
digest.update(string_to_sign.encode())
|
||||
b64_body_digest = base64.b64encode(digest.digest()).decode()
|
||||
|
||||
@@ -182,10 +182,12 @@ class MockPoolManager(object):
|
||||
elif signing_alg == signing.ALGORITHM_RSASSA_PSS:
|
||||
pss.new(self.pubkey).verify(digest, signature)
|
||||
elif signing_alg == signing.ALGORITHM_ECDSA_MODE_FIPS_186_3:
|
||||
verifier = DSS.new(self.pubkey, signing.ALGORITHM_ECDSA_MODE_FIPS_186_3)
|
||||
verifier = DSS.new(key=self.pubkey, mode=signing.ALGORITHM_ECDSA_MODE_FIPS_186_3,
|
||||
encoding='der')
|
||||
verifier.verify(digest, signature)
|
||||
elif signing_alg == signing.ALGORITHM_ECDSA_MODE_DETERMINISTIC_RFC6979:
|
||||
verifier = DSS.new(self.pubkey, signing.ALGORITHM_ECDSA_MODE_DETERMINISTIC_RFC6979)
|
||||
verifier = DSS.new(key=self.pubkey, mode=signing.ALGORITHM_ECDSA_MODE_DETERMINISTIC_RFC6979,
|
||||
encoding='der')
|
||||
verifier.verify(digest, signature)
|
||||
else:
|
||||
self._tc.fail("Unsupported signing algorithm: {0}".format(signing_alg))
|
||||
@@ -295,7 +297,7 @@ class PetApiTests(unittest.TestCase):
|
||||
headers={'Content-Type': r'application/json',
|
||||
'Authorization': r'Signature keyId="my-key-id",algorithm="hs2019",created=[0-9]+,'
|
||||
r'headers="\(request-target\) \(created\) host date digest content-type",'
|
||||
r'signature="[a-zA-Z0-9+/]+="',
|
||||
r'signature="[a-zA-Z0-9+/=]+"',
|
||||
'User-Agent': r'OpenAPI-Generator/1.0.0/python'},
|
||||
preload_content=True, timeout=None)
|
||||
|
||||
@@ -326,7 +328,7 @@ class PetApiTests(unittest.TestCase):
|
||||
headers={'Content-Type': r'application/json',
|
||||
'Authorization': r'Signature keyId="my-key-id",algorithm="hs2019",created=[0-9]+,'
|
||||
r'headers="\(created\)",'
|
||||
r'signature="[a-zA-Z0-9+/]+="',
|
||||
r'signature="[a-zA-Z0-9+/=]+"',
|
||||
'User-Agent': r'OpenAPI-Generator/1.0.0/python'},
|
||||
preload_content=True, timeout=None)
|
||||
|
||||
@@ -362,7 +364,7 @@ class PetApiTests(unittest.TestCase):
|
||||
headers={'Content-Type': r'application/json',
|
||||
'Authorization': r'Signature keyId="my-key-id",algorithm="hs2019",created=[0-9]+,'
|
||||
r'headers="\(request-target\) \(created\)",'
|
||||
r'signature="[a-zA-Z0-9+/]+="',
|
||||
r'signature="[a-zA-Z0-9+/=]+"',
|
||||
'User-Agent': r'OpenAPI-Generator/1.0.0/python'},
|
||||
preload_content=True, timeout=None)
|
||||
|
||||
@@ -398,7 +400,7 @@ class PetApiTests(unittest.TestCase):
|
||||
headers={'Content-Type': r'application/json',
|
||||
'Authorization': r'Signature keyId="my-key-id",algorithm="hs2019",created=[0-9]+,'
|
||||
r'headers="\(request-target\) \(created\)",'
|
||||
r'signature="[a-zA-Z0-9+/]+="',
|
||||
r'signature="[a-zA-Z0-9+/=]+"',
|
||||
'User-Agent': r'OpenAPI-Generator/1.0.0/python'},
|
||||
preload_content=True, timeout=None)
|
||||
|
||||
@@ -411,6 +413,7 @@ class PetApiTests(unittest.TestCase):
|
||||
signing_scheme=signing.SCHEME_HS2019,
|
||||
private_key_path=privkey_path,
|
||||
private_key_passphrase=self.private_key_passphrase,
|
||||
hash_algorithm=signing.HASH_SHA512,
|
||||
signed_headers=[
|
||||
signing.HEADER_REQUEST_TARGET,
|
||||
signing.HEADER_CREATED,
|
||||
@@ -433,7 +436,7 @@ class PetApiTests(unittest.TestCase):
|
||||
headers={'Content-Type': r'application/json',
|
||||
'Authorization': r'Signature keyId="my-key-id",algorithm="hs2019",created=[0-9]+,'
|
||||
r'headers="\(request-target\) \(created\)",'
|
||||
r'signature="[a-zA-Z0-9+/]+"',
|
||||
r'signature="[a-zA-Z0-9+/=]+"',
|
||||
'User-Agent': r'OpenAPI-Generator/1.0.0/python'},
|
||||
preload_content=True, timeout=None)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user