118 lines
4.9 KiB
Python
118 lines
4.9 KiB
Python
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
|