Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Python-experimental] Use DER encoding for ECDSA signatures, add parameter to configure hash algorithm #5924

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ 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.
Expand Down Expand Up @@ -98,16 +103,23 @@ 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.
"""
def __init__(self, key_id, signing_scheme, private_key_path,
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}:
Expand All @@ -118,6 +130,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 == None:
sebastien-rosset marked this conversation as resolved.
Show resolved Hide resolved
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 == 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 == 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
Expand Down Expand Up @@ -309,14 +339,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

Expand All @@ -340,7 +370,9 @@ 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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
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.
Expand Down Expand Up @@ -106,16 +111,23 @@ 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.
"""
def __init__(self, key_id, signing_scheme, private_key_path,
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}:
Expand All @@ -126,6 +138,24 @@ def __init__(self, key_id, signing_scheme, private_key_path,
self.private_key_path = private_key_path
self.private_key_passphrase = private_key_passphrase
self.signing_algorithm = signing_algorithm
self.hash_algorithm = hash_algorithm
spacether marked this conversation as resolved.
Show resolved Hide resolved
if signing_scheme == SCHEME_RSA_SHA256:
if self.hash_algorithm == 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 == 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 == 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
Expand Down Expand Up @@ -317,14 +347,14 @@ def _get_message_digest(self, data):
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

Expand All @@ -348,7 +378,9 @@ def _sign_digest(self, digest):
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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ def test_valid_http_signature(self):
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)

Expand Down Expand Up @@ -326,7 +326,7 @@ def test_valid_http_signature_with_defaults(self):
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)

Expand Down Expand Up @@ -362,7 +362,7 @@ def test_valid_http_signature_rsassa_pkcs1v15(self):
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)

Expand Down Expand Up @@ -398,7 +398,7 @@ def test_valid_http_signature_rsassa_pss(self):
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)

Expand Down Expand Up @@ -433,7 +433,7 @@ def test_valid_http_signature_ec_p521(self):
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)

Expand Down