Add --eab-hmac-alg parameter to support custom HMAC algorithm for EAB (#10319)

fixed: #10281
This commit is contained in:
ldlb
2025-06-27 14:16:38 -07:00
committed by GitHub
parent 372813175d
commit 295d8c3c49
6 changed files with 59 additions and 5 deletions
+30 -2
View File
@@ -1,5 +1,6 @@
"""Tests for acme.messages.""" """Tests for acme.messages."""
import sys import sys
import json
from typing import Dict from typing import Dict
import unittest import unittest
from unittest import mock from unittest import mock
@@ -218,17 +219,43 @@ class ExternalAccountBindingTest(unittest.TestCase):
self.key = jose.jwk.JWKRSA(key=KEY.public_key()) self.key = jose.jwk.JWKRSA(key=KEY.public_key())
self.kid = "kid-for-testing" self.kid = "kid-for-testing"
self.hmac_key = "hmac-key-for-testing" self.hmac_key = "hmac-key-for-testing"
self.hmac_alg = "HS256"
self.dir = Directory({ self.dir = Directory({
'newAccount': 'http://url/acme/new-account', 'newAccount': 'http://url/acme/new-account',
}) })
def test_from_data(self): def test_from_data(self):
from acme.messages import ExternalAccountBinding from acme.messages import ExternalAccountBinding
eab = ExternalAccountBinding.from_data(self.key, self.kid, self.hmac_key, self.dir) eab = ExternalAccountBinding.from_data(self.key, self.kid, self.hmac_key, self.dir, self.hmac_alg)
assert len(eab) == 3 assert len(eab) == 3
assert sorted(eab.keys()) == sorted(['protected', 'payload', 'signature']) assert sorted(eab.keys()) == sorted(['protected', 'payload', 'signature'])
def test_from_data_invalid_hmac_alg(self):
from acme.messages import ExternalAccountBinding
invalid_alg = "HS9999"
with pytest.raises(ValueError) as info:
ExternalAccountBinding.from_data(self.key, self.kid, self.hmac_key, self.dir, invalid_alg)
assert "Invalid value for hmac_alg" in str(info.value)
def test_from_data_default_hmac_alg(self):
from acme.messages import ExternalAccountBinding
eab_default = ExternalAccountBinding.from_data(self.key, self.kid, self.hmac_key, self.dir)
assert len(eab_default) == 3
assert sorted(eab_default.keys()) == sorted(['protected', 'payload', 'signature'])
eab_explicit = ExternalAccountBinding.from_data(
self.key, self.kid, self.hmac_key, self.dir, "HS256"
)
assert eab_default == eab_explicit
protected_default = json.loads(
jose.b64.b64decode(eab_default['protected']).decode()
)
assert protected_default['alg'] == 'HS256'
class RegistrationTest(unittest.TestCase): class RegistrationTest(unittest.TestCase):
"""Tests for acme.messages.Registration.""" """Tests for acme.messages.Registration."""
@@ -268,10 +295,11 @@ class RegistrationTest(unittest.TestCase):
key = jose.jwk.JWKRSA(key=KEY.public_key()) key = jose.jwk.JWKRSA(key=KEY.public_key())
kid = "kid-for-testing" kid = "kid-for-testing"
hmac_key = "hmac-key-for-testing" hmac_key = "hmac-key-for-testing"
hmac_alg = "HS256"
directory = Directory({ directory = Directory({
'newAccount': 'http://url/acme/new-account', 'newAccount': 'http://url/acme/new-account',
}) })
eab = ExternalAccountBinding.from_data(key, kid, hmac_key, directory) eab = ExternalAccountBinding.from_data(key, kid, hmac_key, directory, hmac_alg)
reg = NewRegistration.from_data(email='admin@foo.com', external_account_binding=eab) reg = NewRegistration.from_data(email='admin@foo.com', external_account_binding=eab)
assert reg.contact == ( assert reg.contact == (
'mailto:admin@foo.com', 'mailto:admin@foo.com',
+13 -2
View File
@@ -304,15 +304,26 @@ class ExternalAccountBinding:
@classmethod @classmethod
def from_data(cls, account_public_key: jose.JWK, kid: str, hmac_key: str, def from_data(cls, account_public_key: jose.JWK, kid: str, hmac_key: str,
directory: Directory) -> Dict[str, Any]: directory: Directory, hmac_alg: str = "HS256") -> Dict[str, Any]:
"""Create External Account Binding Resource from contact details, kid and hmac.""" """Create External Account Binding Resource from contact details, kid and hmac."""
key_json = json.dumps(account_public_key.to_partial_json()).encode() key_json = json.dumps(account_public_key.to_partial_json()).encode()
decoded_hmac_key = jose.b64.b64decode(hmac_key) decoded_hmac_key = jose.b64.b64decode(hmac_key)
url = directory["newAccount"] url = directory["newAccount"]
hmac_alg_map = {
"HS256": jose.jwa.HS256,
"HS384": jose.jwa.HS384,
"HS512": jose.jwa.HS512,
}
alg = hmac_alg_map.get(hmac_alg)
if alg is None:
supported = ", ".join(hmac_alg_map.keys())
raise ValueError(f"Invalid value for hmac_alg: {hmac_alg}. "
f"Expected one of: {supported}.")
eab = jws.JWS.sign(key_json, jose.jwk.JWKOct(key=decoded_hmac_key), eab = jws.JWS.sign(key_json, jose.jwk.JWKOct(key=decoded_hmac_key),
jose.jwa.HS256, None, alg, None,
url, kid) url, kid)
return eab.to_partial_json() return eab.to_partial_json()
+6
View File
@@ -23,6 +23,12 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
More details about these changes can be found on our GitHub repo. More details about these changes can be found on our GitHub repo.
## 4.2.0
### Added
* Added `--eab-hmac-alg` parameter to support custom HMAC algorithm for External Account Binding.
## 4.1.1 - 2025-06-12 ## 4.1.1 - 2025-06-12
### Fixed ### Fixed
@@ -134,6 +134,13 @@ def prepare_and_parse_args(plugins: plugins_disco.PluginsRegistry, args: List[st
metavar="EAB_HMAC_KEY", metavar="EAB_HMAC_KEY",
help="HMAC key for External Account Binding" help="HMAC key for External Account Binding"
) )
helpful.add(
[None, "run", "certonly", "register"],
"--eab-hmac-alg", dest="eab_hmac_alg",
metavar="EAB_HMAC_ALG",
default=flag_default("eab_hmac_alg"),
help="HMAC algorithm for External Account Binding"
)
helpful.add( helpful.add(
[None, "run", "certonly", "manage", "delete", "certificates", [None, "run", "certonly", "manage", "delete", "certificates",
"renew", "enhance", "reconfigure"], "--cert-name", dest="certname", "renew", "enhance", "reconfigure"], "--cert-name", dest="certname",
+2 -1
View File
@@ -238,7 +238,8 @@ def perform_registration(acme: acme_client.ClientV2, config: configuration.Names
eab = messages.ExternalAccountBinding.from_data(account_public_key=account_public_key, eab = messages.ExternalAccountBinding.from_data(account_public_key=account_public_key,
kid=config.eab_kid, kid=config.eab_kid,
hmac_key=config.eab_hmac_key, hmac_key=config.eab_hmac_key,
directory=acme.directory) directory=acme.directory,
hmac_alg=config.eab_hmac_alg)
else: else:
eab = None eab = None
@@ -82,6 +82,7 @@ CLI_DEFAULTS: Dict[str, Any] = dict( # pylint: disable=use-dict-literal
random_sleep_on_renew=True, random_sleep_on_renew=True,
eab_hmac_key=None, eab_hmac_key=None,
eab_kid=None, eab_kid=None,
eab_hmac_alg="HS256",
issuance_timeout=90, issuance_timeout=90,
run_deploy_hooks=False, run_deploy_hooks=False,