first commit
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
from .._rfc7516.models import Recipient, JWEKeyAgreement, JWEKeyWrapping, JWEEncModel
|
||||
from .._rfc7518.jwe_algs import (
|
||||
A128KW,
|
||||
A192KW,
|
||||
A256KW,
|
||||
)
|
||||
from .._rfc7518.ec_key import ECKey
|
||||
from .._rfc7518.derive_key import (
|
||||
derive_key_for_concat_kdf,
|
||||
)
|
||||
from .._rfc7518.jwe_encs import CBCHS2EncModel
|
||||
from ..registry import HeaderParameter
|
||||
from ..errors import InvalidEncryptionAlgorithmError
|
||||
|
||||
|
||||
__all__ = ["ECDH1PUAlgModel", "register_ecdh_1pu", "JWE_ALG_MODELS"]
|
||||
|
||||
|
||||
class ECDH1PUAlgModel(JWEKeyAgreement):
|
||||
"""Key Agreement with Elliptic Curve Diffie-Hellman One-Pass Unified Model (ECDH-1PU)
|
||||
|
||||
https://datatracker.ietf.org/doc/html/draft-madden-jose-ecdh-1pu-04
|
||||
"""
|
||||
|
||||
more_header_registry = {
|
||||
"epk": HeaderParameter("Ephemeral Public Key", "jwk", True),
|
||||
"apu": HeaderParameter("Agreement PartyUInfo", "str"),
|
||||
"apv": HeaderParameter("Agreement PartyVInfo", "str"),
|
||||
"skid": HeaderParameter("Sender Key ID", "str"),
|
||||
}
|
||||
key_types = ["EC", "OKP"]
|
||||
tag_aware = True
|
||||
|
||||
def __init__(self, key_wrapping: JWEKeyWrapping | None):
|
||||
if key_wrapping is None:
|
||||
self.name = "ECDH-1PU"
|
||||
self.description = "ECDH-1PU using one-pass KDF and CEK in the Direct Key Agreement mode"
|
||||
self.key_size = None
|
||||
else:
|
||||
self.name = f"ECDH-1PU+{key_wrapping.name}"
|
||||
self.description = f"ECDH-1PU using one-pass KDF and CEK wrapped with {key_wrapping.name}"
|
||||
self.key_size = key_wrapping.key_size
|
||||
self.key_wrapping = key_wrapping
|
||||
|
||||
def _check_enc(self, enc: JWEEncModel) -> None:
|
||||
# https://datatracker.ietf.org/doc/html/draft-madden-jose-ecdh-1pu-04#section-2.1
|
||||
# The AES_CBC_HMAC_SHA2 algorithms described in section 5.2 of [RFC7518] are compactly
|
||||
# committing and can be used with ECDH-1PU in Key Agreement with Key Wrapping mode.
|
||||
# Other content encryption algorithms MUST be rejected. In Direct Key Agreement
|
||||
# mode, any JWE content encryption algorithm MAY be used.
|
||||
if self.key_wrapping and not isinstance(enc, CBCHS2EncModel):
|
||||
description = (
|
||||
"In key agreement with key wrapping mode ECDH-1PU algorithm "
|
||||
"only supports AES_CBC_HMAC_SHA2 family encryption algorithms"
|
||||
)
|
||||
raise InvalidEncryptionAlgorithmError(description)
|
||||
|
||||
def encrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes:
|
||||
self._check_enc(enc)
|
||||
return self.__encrypt_agreed_upon_key(enc, recipient, None)
|
||||
|
||||
def encrypt_agreed_upon_key_with_tag(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes) -> bytes:
|
||||
self._check_enc(enc)
|
||||
return self.__encrypt_agreed_upon_key(enc, recipient, tag)
|
||||
|
||||
def decrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes:
|
||||
return self.__decrypt_agreed_upon_key(enc, recipient, None)
|
||||
|
||||
def decrypt_agreed_upon_key_with_tag(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes) -> bytes:
|
||||
return self.__decrypt_agreed_upon_key(enc, recipient, tag)
|
||||
|
||||
def __encrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes | None) -> bytes:
|
||||
sender_key = recipient.sender_key
|
||||
recipient_key = recipient.recipient_key
|
||||
ephemeral_key = recipient.ephemeral_key
|
||||
assert sender_key is not None
|
||||
assert recipient_key is not None
|
||||
assert ephemeral_key is not None
|
||||
|
||||
sender_shared_key = sender_key.exchange_derive_key(recipient_key)
|
||||
ephemeral_shared_key = ephemeral_key.exchange_derive_key(recipient_key)
|
||||
shared_key = ephemeral_shared_key + sender_shared_key
|
||||
headers = recipient.headers()
|
||||
return derive_key_for_concat_kdf(shared_key, headers, enc.cek_size, self.key_size, tag)
|
||||
|
||||
def __decrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes | None) -> bytes:
|
||||
self._check_enc(enc)
|
||||
headers = recipient.headers()
|
||||
assert "epk" in headers
|
||||
|
||||
sender_key = recipient.sender_key
|
||||
recipient_key = recipient.recipient_key
|
||||
assert sender_key is not None
|
||||
assert recipient_key is not None
|
||||
|
||||
ephemeral_key = recipient_key.import_key(headers["epk"])
|
||||
sender_shared_key = recipient_key.exchange_derive_key(sender_key)
|
||||
ephemeral_shared_key = recipient_key.exchange_derive_key(ephemeral_key)
|
||||
shared_key = ephemeral_shared_key + sender_shared_key
|
||||
return derive_key_for_concat_kdf(shared_key, headers, enc.cek_size, self.key_size, tag)
|
||||
|
||||
|
||||
JWE_ALG_MODELS = [
|
||||
ECDH1PUAlgModel(None), # ECDH-1PU
|
||||
ECDH1PUAlgModel(A128KW), # ECDH-1PU+A128KW
|
||||
ECDH1PUAlgModel(A192KW), # ECDH-1PU+A192KW
|
||||
ECDH1PUAlgModel(A256KW), # ECDH-1PU+A256KW
|
||||
]
|
||||
|
||||
|
||||
def register_ecdh_1pu() -> None:
|
||||
from ..jwe import JWERegistry
|
||||
from ..jwk import KeySet
|
||||
|
||||
for model in JWE_ALG_MODELS:
|
||||
JWERegistry.register(model)
|
||||
KeySet.algorithm_keys[model.name] = model.key_types
|
||||
Reference in New Issue
Block a user