From ffc2af0516f18142db0e75afd9523fe96231c9a8 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Sun, 12 Nov 2023 18:07:19 -0500 Subject: [PATCH 1/7] feat: add authlib crypto backend Signed-off-by: Daniel Bluhm --- didcomm_messaging/crypto/__init__.py | 48 ++++- didcomm_messaging/crypto/askar/__init__.py | 63 +----- didcomm_messaging/crypto/authlib.py | 200 +++++++++++++++++ didcomm_messaging/multiformats/multibase.py | 31 ++- pdm.lock | 226 +++++++++++++------- pyproject.toml | 4 + tests/authlib/__init__.py | 0 tests/authlib/test_authlib.py | 49 +++++ 8 files changed, 479 insertions(+), 142 deletions(-) create mode 100644 didcomm_messaging/crypto/authlib.py create mode 100644 tests/authlib/__init__.py create mode 100644 tests/authlib/test_authlib.py diff --git a/didcomm_messaging/crypto/__init__.py b/didcomm_messaging/crypto/__init__.py index 59ea374..2f06583 100644 --- a/didcomm_messaging/crypto/__init__.py +++ b/didcomm_messaging/crypto/__init__.py @@ -2,11 +2,11 @@ from abc import ABC, abstractmethod -from typing import Generic, Optional, Sequence, TypeVar, Union +from typing import Generic, Mapping, Optional, Sequence, TypeVar, Union from pydid import VerificationMethod -from .jwe import JweEnvelope +from didcomm_messaging.multiformats import multibase, multicodec class CryptoServiceError(Exception): @@ -16,11 +16,49 @@ class CryptoServiceError(Exception): class PublicKey(ABC): """Key representation for CryptoService.""" + type_to_codec: Mapping[str, str] = { + "Ed25519VerificationKey2018": "ed25519-pub", + "X25519KeyAgreementKey2019": "x25519-pub", + "Ed25519VerificationKey2020": "ed25519-pub", + "X25519KeyAgreementKey2020": "x25519-pub", + } + @classmethod @abstractmethod def from_verification_method(cls, vm: VerificationMethod) -> "PublicKey": """Create a Key instance from a DID Document Verification Method.""" + @classmethod + def key_bytes_from_verification_method(cls, vm: VerificationMethod) -> bytes: + """Get the key bytes from a DID Document Verification Method.""" + if vm.public_key_multibase and vm.public_key_base58: + raise ValueError( + "Only one of public_key_multibase or public_key_base58 must be given" + ) + if not vm.public_key_multibase and not vm.public_key_base58: + raise ValueError( + "One of public_key_multibase or public_key_base58 must be given)" + ) + + if vm.public_key_multibase: + decoded = multibase.decode(vm.public_key_multibase) + if len(decoded) == 32: + # No multicodec prefix + return decoded + else: + codec, decoded = multicodec.unwrap(decoded) + expected_codec = cls.type_to_codec.get(vm.type) + if not expected_codec: + raise ValueError("Unsupported verification method type") + if codec.name != expected_codec: + raise ValueError("Type and codec mismatch") + return decoded + + if vm.public_key_base58: + return multibase.decode("z" + vm.public_key_base58) + + raise ValueError("Invalid verification method") + @property @abstractmethod def kid(self) -> str: @@ -53,9 +91,7 @@ async def ecdh_es_encrypt(self, to_keys: Sequence[P], message: bytes) -> bytes: """Encode a message into DIDComm v2 anonymous encryption.""" @abstractmethod - async def ecdh_es_decrypt( - self, wrapper: Union[JweEnvelope, str, bytes], recip_key: S - ) -> bytes: + async def ecdh_es_decrypt(self, wrapper: Union[str, bytes], recip_key: S) -> bytes: """Decode a message from DIDComm v2 anonymous encryption.""" @abstractmethod @@ -70,7 +106,7 @@ async def ecdh_1pu_encrypt( @abstractmethod async def ecdh_1pu_decrypt( self, - wrapper: Union[JweEnvelope, str, bytes], + wrapper: Union[str, bytes], recip_key: S, sender_key: P, ) -> bytes: diff --git a/didcomm_messaging/crypto/askar/__init__.py b/didcomm_messaging/crypto/askar/__init__.py index 4d8691e..2d567d9 100644 --- a/didcomm_messaging/crypto/askar/__init__.py +++ b/didcomm_messaging/crypto/askar/__init__.py @@ -75,44 +75,6 @@ def multikey_to_key(cls, multikey: str) -> Key: except AskarError as err: raise ValueError("Invalid key") from err - @classmethod - def _expected_alg_and_material_to_key( - cls, - alg: KeyAlg, - public_key_multibase: Optional[str] = None, - public_key_base58: Optional[str] = None, - ) -> Key: - """Convert an Ed25519 key to an Askar Key instance.""" - if public_key_multibase and public_key_base58: - raise ValueError( - "Only one of public_key_multibase or public_key_base58 must be given" - ) - if not public_key_multibase and not public_key_base58: - raise ValueError( - "One of public_key_multibase or public_key_base58 must be given)" - ) - - if public_key_multibase: - decoded = multibase.decode(public_key_multibase) - if len(decoded) == 32: - # No multicodec prefix - try: - key = Key.from_public_bytes(alg, decoded) - except AskarError as err: - raise ValueError("Invalid key") from err - return key - else: - key = cls.multikey_to_key(public_key_multibase) - if key.algorithm != alg: - raise ValueError("Type and algorithm mismatch") - return key - - if public_key_base58: - decoded = multibase.decode("z" + public_key_base58) - return Key.from_public_bytes(alg, decoded) - - raise ValueError("Failed to parse key") - @classmethod def from_verification_method(cls, vm: VerificationMethod) -> "AskarKey": """Create a Key instance from a DID Document Verification Method.""" @@ -133,11 +95,8 @@ def from_verification_method(cls, vm: VerificationMethod) -> "AskarKey": if not alg: raise ValueError("Unsupported verification method type: {vm_type}") - base58 = vm.public_key_base58 - multi = vm.public_key_multibase - key = cls._expected_alg_and_material_to_key( - alg, public_key_base58=base58, public_key_multibase=multi - ) + key_bytes = cls.key_bytes_from_verification_method(vm) + key = Key.from_public_bytes(alg, key_bytes) return cls(key, kid) @property @@ -220,14 +179,13 @@ async def ecdh_es_encrypt( async def ecdh_es_decrypt( self, - wrapper: Union[JweEnvelope, str, bytes], + enc_message: Union[str, bytes], recip_key: AskarSecretKey, ) -> bytes: """Decode a message from DIDComm v2 anonymous encryption.""" - if isinstance(wrapper, bytes): - wrapper = wrapper.decode("utf-8") - if not isinstance(wrapper, JweEnvelope): - wrapper = JweEnvelope.from_json(wrapper) + if isinstance(enc_message, bytes): + wrapper = enc_message.decode("utf-8") + wrapper = JweEnvelope.from_json(enc_message) alg_id = wrapper.protected.get("alg") @@ -362,15 +320,14 @@ async def ecdh_1pu_encrypt( async def ecdh_1pu_decrypt( self, - wrapper: Union[JweEnvelope, str, bytes], + enc_message: Union[str, bytes], recip_key: AskarSecretKey, sender_key: AskarKey, ): """Decode a message from DIDComm v2 authenticated encryption.""" - if isinstance(wrapper, bytes): - wrapper = wrapper.decode("utf-8") - if not isinstance(wrapper, JweEnvelope): - wrapper = JweEnvelope.from_json(wrapper) + if isinstance(enc_message, bytes): + wrapper = enc_message.decode("utf-8") + wrapper = JweEnvelope.from_json(enc_message) alg_id = wrapper.protected.get("alg") if alg_id and alg_id in ("ECDH-1PU+A128KW", "ECDH-1PU+A256KW"): diff --git a/didcomm_messaging/crypto/authlib.py b/didcomm_messaging/crypto/authlib.py new file mode 100644 index 0000000..32ec85f --- /dev/null +++ b/didcomm_messaging/crypto/authlib.py @@ -0,0 +1,200 @@ +"""Authlib implementation of DIDComm crypto.""" + +import hashlib +from typing import Mapping, Optional, Sequence, Tuple, Union + +from pydid import VerificationMethod +from didcomm_messaging.multiformats import multibase, multicodec +from didcomm_messaging.multiformats.multibase import Base64UrlEncoder +from . import CryptoService, PublicKey, SecretKey + + +try: + from authlib.jose import JsonWebEncryption, JsonWebKey + from authlib.jose.rfc7517 import AsymmetricKey + from authlib.jose.drafts import register_jwe_draft + + register_jwe_draft(JsonWebEncryption) +except ImportError: + raise ImportError("Authlib backend requires the 'authlib' extra to be installed") + +base64 = Base64UrlEncoder() + + +class AuthlibKey(PublicKey): + """Authlib implementation of DIDComm PublicKey.""" + + kty_crv_to_codec: Mapping[Tuple[str, Optional[str]], str] = { + ("OKP", "Ed25519"): "ed25519-pub", + ("OKP", "X25519"): "x25519-pub", + } + codec_to_kty_crv: Mapping[str, Tuple[str, str]] = { + "ed25519-pub": ("OKP", "Ed25519"), + "x25519-pub": ("OKP", "X25519"), + } + + def __init__(self, key: AsymmetricKey, kid: str): + """Initialize the AuthlibKey.""" + self.key = key + self._kid = kid + self._multikey = self.key_to_multikey(key) + + @property + def kid(self) -> str: + """Return the key ID.""" + return self._kid + + @property + def multikey(self) -> str: + """Return the key in multikey format.""" + return self._multikey + + @classmethod + def key_to_multikey(cls, key: AsymmetricKey) -> str: + """Convert an Authlib key to a multikey.""" + jwk = key.as_dict(is_private=False) + codec = cls.kty_crv_to_codec.get((jwk["kty"], jwk.get("crv"))) + + if not codec: + raise ValueError("Unsupported key type") + + key_bytes = base64.decode(jwk["x"]) + return multibase.encode( + multicodec.wrap(multicodec.multicodec(codec), key_bytes), "base58btc" + ) + + @classmethod + def multikey_to_key(cls, multikey: str) -> AsymmetricKey: + """Convert a multikey to an Authlib key.""" + decoded = multibase.decode(multikey) + codec, key = multicodec.unwrap(decoded) + try: + kty, crv = cls.codec_to_kty_crv[codec.name] + except KeyError: + raise ValueError(f"Unsupported key type: {codec.name}") + + jwk = {"kty": kty, "crv": crv, "x": base64.encode(key)} + + try: + return JsonWebKey.import_key(jwk) + except Exception as err: + raise ValueError("Invalid key") from err + + @classmethod + def from_verification_method(cls, vm: VerificationMethod) -> "AuthlibKey": + """Return a PublicKey from a verification method.""" + # TODO Reduce code duplication between this and Askar + if not vm.id.did: + kid = vm.id.as_absolute(vm.controller) + else: + kid = vm.id + + if vm.type == "Multikey": + multikey = vm.public_key_multibase + if not multikey: + raise ValueError("Multikey verification method missing key") + + key = cls.multikey_to_key(multikey) + return cls(key, kid) + + codec = cls.type_to_codec.get(vm.type) + if not codec: + raise ValueError("Unsupported verification method type: {vm_type}") + + try: + kty, crv = cls.codec_to_kty_crv[codec] + except KeyError: + raise ValueError("Unsupported verification method type") + + key_bytes = cls.key_bytes_from_verification_method(vm) + jwk = {"kty": kty, "crv": crv, "x": base64.encode(key_bytes)} + + key = JsonWebKey.import_key(jwk) + return cls(key, kid) + + +class AuthlibSecretKey(SecretKey): + """Authlib implementation of SecretKey.""" + + def __init__(self, key: AsymmetricKey, kid: str): + """Initialize the AuthlibSecretKey.""" + self.key = key + self._kid = kid + + @property + def kid(self) -> str: + """Return the key ID.""" + return self._kid + + +class AuthlibCryptoService(CryptoService[AuthlibKey, AuthlibSecretKey]): + """Authlib implementation of CryptoService.""" + + @classmethod + def verification_method_to_public_key(cls, vm: VerificationMethod) -> AuthlibKey: + """Return a PublicKey from a verification method.""" + return AuthlibKey.from_verification_method(vm) + + def _build_header( + self, to: Sequence[AuthlibKey], frm: AuthlibSecretKey, alg: str, enc: str + ): + skid = frm.kid + kids = [to_key.kid for to_key in to] + + apu = base64.encode(skid.encode()) + apv = base64.encode(hashlib.sha256((".".join(sorted(kids))).encode()).digest()) + protected = { + "typ": "application/didcomm-encrypted+json", + "alg": alg, + "enc": enc, + "apu": apu, + "apv": apv, + "skid": skid, + } + recipients = [{"header": {"kid": kid}} for kid in kids] + return {"protected": protected, "recipients": recipients} + + async def ecdh_es_encrypt( + self, to_keys: Sequence[AuthlibKey], message: bytes + ) -> bytes: + """Encrypt a message using ECDH-ES.""" + return await super().ecdh_es_encrypt(to_keys, message) + + async def ecdh_es_decrypt( + self, enc_message: Union[str, bytes], recip_key: AuthlibSecretKey + ) -> bytes: + """Decrypt a message using ECDH-ES.""" + return await super().ecdh_es_decrypt(enc_message, recip_key) + + async def ecdh_1pu_encrypt( + self, + to_keys: Sequence[AuthlibKey], + sender_key: AuthlibSecretKey, + message: bytes, + ) -> bytes: + """Encrypt a message using ECDH-1PU.""" + header = self._build_header( + to_keys, sender_key, "ECDH-1PU+A256KW", "A256CBC-HS512" + ) + jwe = JsonWebEncryption() + res = jwe.serialize_json( + header, message, [value.key for value in to_keys], sender_key=sender_key.key + ) + return res + + async def ecdh_1pu_decrypt( + self, + enc_message: Union[str, bytes], + recip_key: AuthlibSecretKey, + sender_key: AuthlibKey, + ) -> bytes: + """Decrypt a message using ECDH-1PU.""" + try: + jwe = JsonWebEncryption() + res = jwe.deserialize_json( + enc_message, recip_key.key, sender_key=sender_key.key + ) + except Exception as err: + raise ValueError("Invalid JWE") from err + + return res["payload"] diff --git a/didcomm_messaging/multiformats/multibase.py b/didcomm_messaging/multiformats/multibase.py index f66163c..30b428f 100644 --- a/didcomm_messaging/multiformats/multibase.py +++ b/didcomm_messaging/multiformats/multibase.py @@ -55,7 +55,36 @@ def decode(self, value: str) -> bytes: """Decode a base64url encoded string.""" import base64 - return base64.urlsafe_b64decode(value + "=" * (-len(value) % 4)) + # Ensure correct padding + padding_needed = 4 - (len(value) % 4) + if padding_needed != 4: + value += "=" * padding_needed + + return base64.urlsafe_b64decode(value) + + +class Base64Encoder(MultibaseEncoder): + """Base64URL encoding.""" + + name = "base64" + character = "m" + + def encode(self, value: bytes) -> str: + """Encode a byte string using the base64 encoding.""" + import base64 + + return base64.b64encode(value).decode().rstrip("=") + + def decode(self, value: str) -> bytes: + """Decode a base64 encoded string.""" + import base64 + + # Ensure correct padding + padding_needed = 4 - (len(value) % 4) + if padding_needed != 4: + value += "=" * padding_needed + + return base64.b64decode(value) class Encoding(Enum): diff --git a/pdm.lock b/pdm.lock index 772a8e3..58d5969 100644 --- a/pdm.lock +++ b/pdm.lock @@ -2,23 +2,21 @@ # It is not intended for manual editing. [metadata] -groups = ["default", "askar", "dev", "did_peer"] +groups = ["default", "authlib", "dev"] strategy = ["cross_platform"] lock_version = "4.4" -content_hash = "sha256:5824490f0dd97d7a39b6eff1bfc1c4574c978fad5c63f5287f5cbd67ad4446e4" +content_hash = "sha256:b4969ff34317a0c41035e9f1b731a2736c54054d7f2b62b4ddb3821caf285b19" [[package]] -name = "aries-askar" -version = "0.2.9" -requires_python = ">=3.6.3" -summary = "UNKNOWN" +name = "authlib" +version = "1.2.1" +summary = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." dependencies = [ - "cached-property~=1.5", + "cryptography>=3.2", ] files = [ - {file = "aries_askar-0.2.9-py3-none-macosx_10_9_universal2.whl", hash = "sha256:e8231845ab5eee3be08070c3d005a1be22da2c3fdf84b6adc06e97ee47abcd26"}, - {file = "aries_askar-0.2.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:73a4b9384b762423da563ebbe72abc8edf4c11416150148b7b758afd98ef7758"}, - {file = "aries_askar-0.2.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9084f10f193a331275c6af72235ecc816063dafe71dc3eca5c920bf30ff069d1"}, + {file = "Authlib-1.2.1-py2.py3-none-any.whl", hash = "sha256:c88984ea00149a90e3537c964327da930779afa4564e354edfd98410bea01911"}, + {file = "Authlib-1.2.1.tar.gz", hash = "sha256:421f7c6b468d907ca2d9afede256f068f87e34d23dd221c07d13d4c234726afb"}, ] [[package]] @@ -33,7 +31,7 @@ files = [ [[package]] name = "black" -version = "23.10.1" +version = "23.11.0" requires_python = ">=3.8" summary = "The uncompromising code formatter." dependencies = [ @@ -46,29 +44,75 @@ dependencies = [ "typing-extensions>=4.0.1; python_version < \"3.11\"", ] files = [ - {file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"}, - {file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"}, - {file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"}, - {file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"}, - {file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"}, - {file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"}, - {file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"}, - {file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"}, - {file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"}, - {file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"}, - {file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"}, - {file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"}, - {file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"}, - {file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"}, + {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, + {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, + {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, + {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, + {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, + {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, + {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, + {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, + {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, + {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, + {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, + {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, + {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, + {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, ] [[package]] -name = "cached-property" -version = "1.5.2" -summary = "A decorator for caching properties in classes." -files = [ - {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, - {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +name = "cffi" +version = "1.16.0" +requires_python = ">=3.8" +summary = "Foreign Function Interface for Python calling C code." +dependencies = [ + "pycparser", +] +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, ] [[package]] @@ -105,29 +149,37 @@ files = [ ] [[package]] -name = "did-peer-2" -version = "0.1.2" -requires_python = ">=3.9" -summary = "An implementation of did:peer:2" -dependencies = [ - "base58>=2.1.1", -] -files = [ - {file = "did_peer_2-0.1.2-py3-none-any.whl", hash = "sha256:d5908cda2d52b7c34428a421044507d7847fd79b78dc8360441c408f4507d612"}, - {file = "did_peer_2-0.1.2.tar.gz", hash = "sha256:af8623f62022732e9fadc0289dfb886fd8267767251c4fa0b63694ecd29a7086"}, -] - -[[package]] -name = "did-peer-4" -version = "0.1.2" -requires_python = ">=3.9" -summary = "An implementation of did:peer:4" +name = "cryptography" +version = "41.0.5" +requires_python = ">=3.7" +summary = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." dependencies = [ - "base58>=2.1.1", -] -files = [ - {file = "did_peer_4-0.1.2-py3-none-any.whl", hash = "sha256:b619230bb8b66ef47d25c9ecedbb806d04582245a4a5aaaa5402c6f52627c462"}, - {file = "did_peer_4-0.1.2.tar.gz", hash = "sha256:e211a8b5d2b84552460000dd81445e93a9574d83bbd8003a30e5bdab2eba469a"}, + "cffi>=1.12", +] +files = [ + {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"}, + {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"}, + {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"}, + {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"}, + {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"}, + {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"}, + {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, ] [[package]] @@ -151,22 +203,22 @@ files = [ [[package]] name = "filelock" -version = "3.13.0" +version = "3.13.1" requires_python = ">=3.8" summary = "A platform independent file lock." files = [ - {file = "filelock-3.13.0-py3-none-any.whl", hash = "sha256:a552f4fde758f4eab33191e9548f671970f8b06d436d31388c9aa1e5861a710f"}, - {file = "filelock-3.13.0.tar.gz", hash = "sha256:63c6052c82a1a24c873a549fbd39a26982e8f35a3016da231ead11a5be9dad44"}, + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, ] [[package]] name = "identify" -version = "2.5.30" +version = "2.5.31" requires_python = ">=3.8" summary = "File identification library for Python" files = [ - {file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"}, - {file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"}, + {file = "identify-2.5.31-py2.py3-none-any.whl", hash = "sha256:90199cb9e7bd3c5407a9b7e81b4abec4bb9d249991c79439ec8af740afc6293d"}, + {file = "identify-2.5.31.tar.gz", hash = "sha256:7736b3c7a28233637e3c36550646fc6389bedd74ae84cb788200cc8e2dd60b75"}, ] [[package]] @@ -269,6 +321,16 @@ files = [ {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, ] +[[package]] +name = "pycparser" +version = "2.21" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "C parser in Python" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + [[package]] name = "pydantic" version = "1.10.13" @@ -351,15 +413,15 @@ files = [ [[package]] name = "pytest-ruff" -version = "0.1.1" +version = "0.2.1" requires_python = ">=3.7,<4.0" summary = "pytest plugin to check ruff requirements." dependencies = [ "ruff>=0.0.242", ] files = [ - {file = "pytest_ruff-0.1.1-py3-none-any.whl", hash = "sha256:db33c8d32d730d61d372c1ac4615b1036c47a14c781cbc0ae71811c4cadadc47"}, - {file = "pytest_ruff-0.1.1.tar.gz", hash = "sha256:f599768ff3834d6b1d6d26b25a030a5b1dcc9cf187239bd9621a7f25f7d8fe46"}, + {file = "pytest_ruff-0.2.1-py3-none-any.whl", hash = "sha256:f586bbd7978cb5782b673c8e55fa069d83430139931b918bd72232ba3f71eb67"}, + {file = "pytest_ruff-0.2.1.tar.gz", hash = "sha256:078ad696bfa347b466991ed4f9cc5ec807f5a171d7f06091660d8f16ba03a5dc"}, ] [[package]] @@ -403,27 +465,27 @@ files = [ [[package]] name = "ruff" -version = "0.1.3" +version = "0.1.5" requires_python = ">=3.7" -summary = "An extremely fast Python linter, written in Rust." -files = [ - {file = "ruff-0.1.3-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b46d43d51f7061652eeadb426a9e3caa1e0002470229ab2fc19de8a7b0766901"}, - {file = "ruff-0.1.3-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:b8afeb9abd26b4029c72adc9921b8363374f4e7edb78385ffaa80278313a15f9"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca3cf365bf32e9ba7e6db3f48a4d3e2c446cd19ebee04f05338bc3910114528b"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4874c165f96c14a00590dcc727a04dca0cfd110334c24b039458c06cf78a672e"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eec2dd31eed114e48ea42dbffc443e9b7221976554a504767ceaee3dd38edeb8"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dc3ec4edb3b73f21b4aa51337e16674c752f1d76a4a543af56d7d04e97769613"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e3de9ed2e39160800281848ff4670e1698037ca039bda7b9274f849258d26ce"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c595193881922cc0556a90f3af99b1c5681f0c552e7a2a189956141d8666fe8"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f75e670d529aa2288cd00fc0e9b9287603d95e1536d7a7e0cafe00f75e0dd9d"}, - {file = "ruff-0.1.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:76dd49f6cd945d82d9d4a9a6622c54a994689d8d7b22fa1322983389b4892e20"}, - {file = "ruff-0.1.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:918b454bc4f8874a616f0d725590277c42949431ceb303950e87fef7a7d94cb3"}, - {file = "ruff-0.1.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8859605e729cd5e53aa38275568dbbdb4fe882d2ea2714c5453b678dca83784"}, - {file = "ruff-0.1.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0b6c55f5ef8d9dd05b230bb6ab80bc4381ecb60ae56db0330f660ea240cb0d4a"}, - {file = "ruff-0.1.3-py3-none-win32.whl", hash = "sha256:3e7afcbdcfbe3399c34e0f6370c30f6e529193c731b885316c5a09c9e4317eef"}, - {file = "ruff-0.1.3-py3-none-win_amd64.whl", hash = "sha256:7a18df6638cec4a5bd75350639b2bb2a2366e01222825562c7346674bdceb7ea"}, - {file = "ruff-0.1.3-py3-none-win_arm64.whl", hash = "sha256:12fd53696c83a194a2db7f9a46337ce06445fb9aa7d25ea6f293cf75b21aca9f"}, - {file = "ruff-0.1.3.tar.gz", hash = "sha256:3ba6145369a151401d5db79f0a47d50e470384d0d89d0d6f7fab0b589ad07c34"}, +summary = "An extremely fast Python linter and code formatter, written in Rust." +files = [ + {file = "ruff-0.1.5-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:32d47fc69261c21a4c48916f16ca272bf2f273eb635d91c65d5cd548bf1f3d96"}, + {file = "ruff-0.1.5-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:171276c1df6c07fa0597fb946139ced1c2978f4f0b8254f201281729981f3c17"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ef33cd0bb7316ca65649fc748acc1406dfa4da96a3d0cde6d52f2e866c7b39"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2c205827b3f8c13b4a432e9585750b93fd907986fe1aec62b2a02cf4401eee6"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb408e3a2ad8f6881d0f2e7ad70cddb3ed9f200eb3517a91a245bbe27101d379"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f20dc5e5905ddb407060ca27267c7174f532375c08076d1a953cf7bb016f5a24"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aafb9d2b671ed934998e881e2c0f5845a4295e84e719359c71c39a5363cccc91"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4894dddb476597a0ba4473d72a23151b8b3b0b5f958f2cf4d3f1c572cdb7af7"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00a7ec893f665ed60008c70fe9eeb58d210e6b4d83ec6654a9904871f982a2a"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8c11206b47f283cbda399a654fd0178d7a389e631f19f51da15cbe631480c5b"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fa29e67b3284b9a79b1a85ee66e293a94ac6b7bb068b307a8a373c3d343aa8ec"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9b97fd6da44d6cceb188147b68db69a5741fbc736465b5cea3928fdac0bc1aeb"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:721f4b9d3b4161df8dc9f09aa8562e39d14e55a4dbaa451a8e55bdc9590e20f4"}, + {file = "ruff-0.1.5-py3-none-win32.whl", hash = "sha256:f80c73bba6bc69e4fdc73b3991db0b546ce641bdcd5b07210b8ad6f64c79f1ab"}, + {file = "ruff-0.1.5-py3-none-win_amd64.whl", hash = "sha256:c21fe20ee7d76206d290a76271c1af7a5096bc4c73ab9383ed2ad35f852a0087"}, + {file = "ruff-0.1.5-py3-none-win_arm64.whl", hash = "sha256:82bfcb9927e88c1ed50f49ac6c9728dab3ea451212693fe40d08d314663e412f"}, + {file = "ruff-0.1.5.tar.gz", hash = "sha256:5cbec0ef2ae1748fb194f420fb03fb2c25c3258c86129af7172ff8f198f125ab"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 11aa872..2de6eeb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,10 @@ did_peer = [ "did-peer-2>=0.1.2", "did-peer-4>=0.1.2", ] +authlib = [ + "authlib>=1.2.1", +] + [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" diff --git a/tests/authlib/__init__.py b/tests/authlib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/authlib/test_authlib.py b/tests/authlib/test_authlib.py new file mode 100644 index 0000000..51b3318 --- /dev/null +++ b/tests/authlib/test_authlib.py @@ -0,0 +1,49 @@ +"""Test the authlib crypto service implementation.""" + +from authlib.jose import OKPKey +import pytest + +from didcomm_messaging.crypto.authlib import ( + AuthlibCryptoService, + AuthlibKey, + AuthlibSecretKey, +) + + +ALICE_KID = "did:example:alice#key-1" +BOB_KID = "did:example:bob#key-1" +CAROL_KID = "did:example:carol#key-2" +MESSAGE = b"Expecto patronum" + + +@pytest.fixture +def crypto(): + yield AuthlibCryptoService() + + +def test_multikey_roundtrip(): + """Test multikey round trip.""" + key = OKPKey.generate_key("Ed25519", is_private=False) + multikey = AuthlibKey.key_to_multikey(key) + back_again = AuthlibKey.multikey_to_key(multikey) + assert key.as_dict() == back_again.as_dict() + + +@pytest.mark.asyncio +async def test_1pu_round_trip(crypto: AuthlibCryptoService): + """Test 1PU round trip.""" + alice_sk = OKPKey.generate_key("X25519", is_private=True) + alice_pk = alice_sk.get_public_key() + bob_sk = OKPKey.generate_key("X25519", is_private=True) + bob_pk = bob_sk.get_public_key() + + bob_key = AuthlibKey(bob_sk, BOB_KID) + bob_priv_key = AuthlibSecretKey(bob_sk, BOB_KID) + + alice_key = AuthlibKey(alice_sk, ALICE_KID) + alice_priv_key = AuthlibSecretKey(alice_sk, ALICE_KID) + + enc_message = await crypto.ecdh_1pu_encrypt([bob_key], alice_priv_key, MESSAGE) + + plaintext = await crypto.ecdh_1pu_decrypt(enc_message, bob_priv_key, alice_key) + assert plaintext == MESSAGE From 9ca2e6a65968ec6d1dc553d00a49ba55b49d69d1 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Sun, 12 Nov 2023 18:15:07 -0500 Subject: [PATCH 2/7] refactor: rearrange structure of crypto backends Signed-off-by: Daniel Bluhm --- didcomm_messaging/crypto/__init__.py | 131 +----------------- didcomm_messaging/crypto/backend/__init__.py | 1 + .../{askar/__init__.py => backend/askar.py} | 15 +- .../crypto/{ => backend}/authlib.py | 14 +- .../crypto/{ => backend}/basic.py | 2 +- didcomm_messaging/crypto/base.py | 130 +++++++++++++++++ example.py | 4 +- tests/askar/test_crypto.py | 6 +- tests/authlib/test_authlib.py | 2 +- 9 files changed, 156 insertions(+), 149 deletions(-) create mode 100644 didcomm_messaging/crypto/backend/__init__.py rename didcomm_messaging/crypto/{askar/__init__.py => backend/askar.py} (98%) rename didcomm_messaging/crypto/{ => backend}/authlib.py (94%) rename didcomm_messaging/crypto/{ => backend}/basic.py (94%) create mode 100644 didcomm_messaging/crypto/base.py diff --git a/didcomm_messaging/crypto/__init__.py b/didcomm_messaging/crypto/__init__.py index 2f06583..01f5a09 100644 --- a/didcomm_messaging/crypto/__init__.py +++ b/didcomm_messaging/crypto/__init__.py @@ -1,130 +1,5 @@ -"""Key Management Service (CryptoService) interface for DIDComm Messaging.""" +"""DIDComm Messaging Cryptography and Secrets Interfaces.""" +from .base import CryptoService, SecretsManager, PublicKey, SecretKey, P, S -from abc import ABC, abstractmethod -from typing import Generic, Mapping, Optional, Sequence, TypeVar, Union - -from pydid import VerificationMethod - -from didcomm_messaging.multiformats import multibase, multicodec - - -class CryptoServiceError(Exception): - """Represents an error from a CryptoService.""" - - -class PublicKey(ABC): - """Key representation for CryptoService.""" - - type_to_codec: Mapping[str, str] = { - "Ed25519VerificationKey2018": "ed25519-pub", - "X25519KeyAgreementKey2019": "x25519-pub", - "Ed25519VerificationKey2020": "ed25519-pub", - "X25519KeyAgreementKey2020": "x25519-pub", - } - - @classmethod - @abstractmethod - def from_verification_method(cls, vm: VerificationMethod) -> "PublicKey": - """Create a Key instance from a DID Document Verification Method.""" - - @classmethod - def key_bytes_from_verification_method(cls, vm: VerificationMethod) -> bytes: - """Get the key bytes from a DID Document Verification Method.""" - if vm.public_key_multibase and vm.public_key_base58: - raise ValueError( - "Only one of public_key_multibase or public_key_base58 must be given" - ) - if not vm.public_key_multibase and not vm.public_key_base58: - raise ValueError( - "One of public_key_multibase or public_key_base58 must be given)" - ) - - if vm.public_key_multibase: - decoded = multibase.decode(vm.public_key_multibase) - if len(decoded) == 32: - # No multicodec prefix - return decoded - else: - codec, decoded = multicodec.unwrap(decoded) - expected_codec = cls.type_to_codec.get(vm.type) - if not expected_codec: - raise ValueError("Unsupported verification method type") - if codec.name != expected_codec: - raise ValueError("Type and codec mismatch") - return decoded - - if vm.public_key_base58: - return multibase.decode("z" + vm.public_key_base58) - - raise ValueError("Invalid verification method") - - @property - @abstractmethod - def kid(self) -> str: - """Get the key ID.""" - - @property - @abstractmethod - def multikey(self) -> str: - """Get the key in multikey format.""" - - -class SecretKey(ABC): - """Secret Key Type.""" - - @property - @abstractmethod - def kid(self) -> str: - """Get the key ID.""" - - -P = TypeVar("P", bound=PublicKey) -S = TypeVar("S", bound=SecretKey) - - -class CryptoService(ABC, Generic[P, S]): - """Key Management Service (CryptoService) interface for DIDComm Messaging.""" - - @abstractmethod - async def ecdh_es_encrypt(self, to_keys: Sequence[P], message: bytes) -> bytes: - """Encode a message into DIDComm v2 anonymous encryption.""" - - @abstractmethod - async def ecdh_es_decrypt(self, wrapper: Union[str, bytes], recip_key: S) -> bytes: - """Decode a message from DIDComm v2 anonymous encryption.""" - - @abstractmethod - async def ecdh_1pu_encrypt( - self, - to_keys: Sequence[P], - sender_key: S, - message: bytes, - ) -> bytes: - """Encode a message into DIDComm v2 authenticated encryption.""" - - @abstractmethod - async def ecdh_1pu_decrypt( - self, - wrapper: Union[str, bytes], - recip_key: S, - sender_key: P, - ) -> bytes: - """Decode a message from DIDComm v2 authenticated encryption.""" - - @classmethod - @abstractmethod - def verification_method_to_public_key(cls, vm: VerificationMethod) -> P: - """Convert a verification method to a public key.""" - - -class SecretsManager(ABC, Generic[S]): - """Secrets Resolver interface. - - Thie secrets resolver may be used to supplement the CryptoService backend to provide - greater flexibility. - """ - - @abstractmethod - async def get_secret_by_kid(self, kid: str) -> Optional[S]: - """Get a secret key by its ID.""" +__all__ = ["CryptoService", "SecretsManager", "PublicKey", "SecretKey", "P", "S"] diff --git a/didcomm_messaging/crypto/backend/__init__.py b/didcomm_messaging/crypto/backend/__init__.py new file mode 100644 index 0000000..4b93467 --- /dev/null +++ b/didcomm_messaging/crypto/backend/__init__.py @@ -0,0 +1 @@ +"""Cryptography and Secrets Management backends.""" diff --git a/didcomm_messaging/crypto/askar/__init__.py b/didcomm_messaging/crypto/backend/askar.py similarity index 98% rename from didcomm_messaging/crypto/askar/__init__.py rename to didcomm_messaging/crypto/backend/askar.py index 2d567d9..f7d8fe5 100644 --- a/didcomm_messaging/crypto/askar/__init__.py +++ b/didcomm_messaging/crypto/backend/askar.py @@ -1,25 +1,22 @@ """Askar backend for DIDComm Messaging.""" from collections import OrderedDict +import hashlib import json from typing import Optional, Sequence, Union -import hashlib from pydid import VerificationMethod -from didcomm_messaging.crypto import SecretsManager -from ..jwe import ( - JweBuilder, - JweEnvelope, - JweRecipient, - b64url, -) -from didcomm_messaging.crypto import ( + +from didcomm_messaging.crypto.base import ( CryptoService, CryptoServiceError, PublicKey, SecretKey, + SecretsManager, ) from didcomm_messaging.multiformats import multibase, multicodec +from ..jwe import JweBuilder, JweEnvelope, JweRecipient, b64url + try: from aries_askar import Key, ecdh, AskarError, KeyAlg, Store except ImportError: diff --git a/didcomm_messaging/crypto/authlib.py b/didcomm_messaging/crypto/backend/authlib.py similarity index 94% rename from didcomm_messaging/crypto/authlib.py rename to didcomm_messaging/crypto/backend/authlib.py index 32ec85f..9fe28b5 100644 --- a/didcomm_messaging/crypto/authlib.py +++ b/didcomm_messaging/crypto/backend/authlib.py @@ -6,7 +6,7 @@ from pydid import VerificationMethod from didcomm_messaging.multiformats import multibase, multicodec from didcomm_messaging.multiformats.multibase import Base64UrlEncoder -from . import CryptoService, PublicKey, SecretKey +from didcomm_messaging.crypto.base import CryptoService, PublicKey, SecretKey try: @@ -18,7 +18,7 @@ except ImportError: raise ImportError("Authlib backend requires the 'authlib' extra to be installed") -base64 = Base64UrlEncoder() +b64url = Base64UrlEncoder() class AuthlibKey(PublicKey): @@ -58,7 +58,7 @@ def key_to_multikey(cls, key: AsymmetricKey) -> str: if not codec: raise ValueError("Unsupported key type") - key_bytes = base64.decode(jwk["x"]) + key_bytes = b64url.decode(jwk["x"]) return multibase.encode( multicodec.wrap(multicodec.multicodec(codec), key_bytes), "base58btc" ) @@ -73,7 +73,7 @@ def multikey_to_key(cls, multikey: str) -> AsymmetricKey: except KeyError: raise ValueError(f"Unsupported key type: {codec.name}") - jwk = {"kty": kty, "crv": crv, "x": base64.encode(key)} + jwk = {"kty": kty, "crv": crv, "x": b64url.encode(key)} try: return JsonWebKey.import_key(jwk) @@ -107,7 +107,7 @@ def from_verification_method(cls, vm: VerificationMethod) -> "AuthlibKey": raise ValueError("Unsupported verification method type") key_bytes = cls.key_bytes_from_verification_method(vm) - jwk = {"kty": kty, "crv": crv, "x": base64.encode(key_bytes)} + jwk = {"kty": kty, "crv": crv, "x": b64url.encode(key_bytes)} key = JsonWebKey.import_key(jwk) return cls(key, kid) @@ -141,8 +141,8 @@ def _build_header( skid = frm.kid kids = [to_key.kid for to_key in to] - apu = base64.encode(skid.encode()) - apv = base64.encode(hashlib.sha256((".".join(sorted(kids))).encode()).digest()) + apu = b64url.encode(skid.encode()) + apv = b64url.encode(hashlib.sha256((".".join(sorted(kids))).encode()).digest()) protected = { "typ": "application/didcomm-encrypted+json", "alg": alg, diff --git a/didcomm_messaging/crypto/basic.py b/didcomm_messaging/crypto/backend/basic.py similarity index 94% rename from didcomm_messaging/crypto/basic.py rename to didcomm_messaging/crypto/backend/basic.py index 94868ac..8ca714a 100644 --- a/didcomm_messaging/crypto/basic.py +++ b/didcomm_messaging/crypto/backend/basic.py @@ -1,7 +1,7 @@ """Basic Crypto Implementations.""" from typing import Optional -from . import S, SecretsManager +from .base import S, SecretsManager class InMemorySecretsManager(SecretsManager[S]): diff --git a/didcomm_messaging/crypto/base.py b/didcomm_messaging/crypto/base.py new file mode 100644 index 0000000..42e715f --- /dev/null +++ b/didcomm_messaging/crypto/base.py @@ -0,0 +1,130 @@ +"""CryptoService and SecretsManager interfaces for DIDComm Messaging.""" + + +from abc import ABC, abstractmethod +from typing import Generic, Mapping, Optional, Sequence, TypeVar, Union + +from pydid import VerificationMethod + +from didcomm_messaging.multiformats import multibase, multicodec + + +class CryptoServiceError(Exception): + """Represents an error from a CryptoService.""" + + +class PublicKey(ABC): + """Key representation for CryptoService.""" + + type_to_codec: Mapping[str, str] = { + "Ed25519VerificationKey2018": "ed25519-pub", + "X25519KeyAgreementKey2019": "x25519-pub", + "Ed25519VerificationKey2020": "ed25519-pub", + "X25519KeyAgreementKey2020": "x25519-pub", + } + + @classmethod + @abstractmethod + def from_verification_method(cls, vm: VerificationMethod) -> "PublicKey": + """Create a Key instance from a DID Document Verification Method.""" + + @classmethod + def key_bytes_from_verification_method(cls, vm: VerificationMethod) -> bytes: + """Get the key bytes from a DID Document Verification Method.""" + if vm.public_key_multibase and vm.public_key_base58: + raise ValueError( + "Only one of public_key_multibase or public_key_base58 must be given" + ) + if not vm.public_key_multibase and not vm.public_key_base58: + raise ValueError( + "One of public_key_multibase or public_key_base58 must be given)" + ) + + if vm.public_key_multibase: + decoded = multibase.decode(vm.public_key_multibase) + if len(decoded) == 32: + # No multicodec prefix + return decoded + else: + codec, decoded = multicodec.unwrap(decoded) + expected_codec = cls.type_to_codec.get(vm.type) + if not expected_codec: + raise ValueError("Unsupported verification method type") + if codec.name != expected_codec: + raise ValueError("Type and codec mismatch") + return decoded + + if vm.public_key_base58: + return multibase.decode("z" + vm.public_key_base58) + + raise ValueError("Invalid verification method") + + @property + @abstractmethod + def kid(self) -> str: + """Get the key ID.""" + + @property + @abstractmethod + def multikey(self) -> str: + """Get the key in multikey format.""" + + +class SecretKey(ABC): + """Secret Key Type.""" + + @property + @abstractmethod + def kid(self) -> str: + """Get the key ID.""" + + +P = TypeVar("P", bound=PublicKey) +S = TypeVar("S", bound=SecretKey) + + +class CryptoService(ABC, Generic[P, S]): + """Key Management Service (CryptoService) interface for DIDComm Messaging.""" + + @abstractmethod + async def ecdh_es_encrypt(self, to_keys: Sequence[P], message: bytes) -> bytes: + """Encode a message into DIDComm v2 anonymous encryption.""" + + @abstractmethod + async def ecdh_es_decrypt(self, wrapper: Union[str, bytes], recip_key: S) -> bytes: + """Decode a message from DIDComm v2 anonymous encryption.""" + + @abstractmethod + async def ecdh_1pu_encrypt( + self, + to_keys: Sequence[P], + sender_key: S, + message: bytes, + ) -> bytes: + """Encode a message into DIDComm v2 authenticated encryption.""" + + @abstractmethod + async def ecdh_1pu_decrypt( + self, + wrapper: Union[str, bytes], + recip_key: S, + sender_key: P, + ) -> bytes: + """Decode a message from DIDComm v2 authenticated encryption.""" + + @classmethod + @abstractmethod + def verification_method_to_public_key(cls, vm: VerificationMethod) -> P: + """Convert a verification method to a public key.""" + + +class SecretsManager(ABC, Generic[S]): + """Secrets Resolver interface. + + Thie secrets resolver may be used to supplement the CryptoService backend to provide + greater flexibility. + """ + + @abstractmethod + async def get_secret_by_kid(self, kid: str) -> Optional[S]: + """Get a secret key by its ID.""" diff --git a/example.py b/example.py index 32b7904..b24a021 100644 --- a/example.py +++ b/example.py @@ -1,8 +1,8 @@ """Example of using DIDComm Messaging.""" from aries_askar import Key, KeyAlg -from didcomm_messaging.crypto.askar import AskarCryptoService, AskarSecretKey -from didcomm_messaging.crypto.basic import InMemorySecretsManager +from didcomm_messaging.crypto.backend.askar import AskarCryptoService, AskarSecretKey +from didcomm_messaging.crypto.backend.basic import InMemorySecretsManager from didcomm_messaging.packaging import PackagingService from didcomm_messaging.multiformats import multibase from didcomm_messaging.multiformats import multicodec diff --git a/tests/askar/test_crypto.py b/tests/askar/test_crypto.py index f38d2e8..ae8b94e 100644 --- a/tests/askar/test_crypto.py +++ b/tests/askar/test_crypto.py @@ -1,7 +1,11 @@ from aries_askar import Key, KeyAlg import pytest -from didcomm_messaging.crypto.askar import AskarCryptoService, AskarKey, AskarSecretKey +from didcomm_messaging.crypto.backend.askar import ( + AskarCryptoService, + AskarKey, + AskarSecretKey, +) ALICE_KID = "did:example:alice#key-1" diff --git a/tests/authlib/test_authlib.py b/tests/authlib/test_authlib.py index 51b3318..47bd005 100644 --- a/tests/authlib/test_authlib.py +++ b/tests/authlib/test_authlib.py @@ -3,7 +3,7 @@ from authlib.jose import OKPKey import pytest -from didcomm_messaging.crypto.authlib import ( +from didcomm_messaging.crypto.backend.authlib import ( AuthlibCryptoService, AuthlibKey, AuthlibSecretKey, From f089daf2d17bfd14a2450d489653c5ef7bfd3161 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Sun, 12 Nov 2023 18:35:09 -0500 Subject: [PATCH 3/7] feat: test compatibility between askar and authlib And add a failing test for further debugging Signed-off-by: Daniel Bluhm --- didcomm_messaging/crypto/backend/authlib.py | 3 +- tests/{authlib => crypto}/__init__.py | 0 .../test_crypto.py => crypto/test_askar.py} | 0 tests/crypto/test_askar_x_authlib.py | 87 +++++++++++++++++++ tests/{authlib => crypto}/test_authlib.py | 0 5 files changed, 89 insertions(+), 1 deletion(-) rename tests/{authlib => crypto}/__init__.py (100%) rename tests/{askar/test_crypto.py => crypto/test_askar.py} (100%) create mode 100644 tests/crypto/test_askar_x_authlib.py rename tests/{authlib => crypto}/test_authlib.py (100%) diff --git a/didcomm_messaging/crypto/backend/authlib.py b/didcomm_messaging/crypto/backend/authlib.py index 9fe28b5..871165f 100644 --- a/didcomm_messaging/crypto/backend/authlib.py +++ b/didcomm_messaging/crypto/backend/authlib.py @@ -1,6 +1,7 @@ """Authlib implementation of DIDComm crypto.""" import hashlib +import json from typing import Mapping, Optional, Sequence, Tuple, Union from pydid import VerificationMethod @@ -180,7 +181,7 @@ async def ecdh_1pu_encrypt( res = jwe.serialize_json( header, message, [value.key for value in to_keys], sender_key=sender_key.key ) - return res + return json.dumps(res).encode() async def ecdh_1pu_decrypt( self, diff --git a/tests/authlib/__init__.py b/tests/crypto/__init__.py similarity index 100% rename from tests/authlib/__init__.py rename to tests/crypto/__init__.py diff --git a/tests/askar/test_crypto.py b/tests/crypto/test_askar.py similarity index 100% rename from tests/askar/test_crypto.py rename to tests/crypto/test_askar.py diff --git a/tests/crypto/test_askar_x_authlib.py b/tests/crypto/test_askar_x_authlib.py new file mode 100644 index 0000000..344fae4 --- /dev/null +++ b/tests/crypto/test_askar_x_authlib.py @@ -0,0 +1,87 @@ +"""Test compabibility between Askar and Authlib.""" +import json +from aries_askar import Key, KeyAlg +from authlib.jose import OKPKey +import pytest + +from didcomm_messaging.crypto.backend.askar import ( + AskarCryptoService, + AskarKey, + AskarSecretKey, +) +from didcomm_messaging.crypto.backend.authlib import ( + AuthlibCryptoService, + AuthlibKey, + AuthlibSecretKey, +) + + +ALICE_KID = "did:example:alice#key-1" +BOB_KID = "did:example:bob#key-1" + + +@pytest.fixture +def alice_askar_key(): + yield Key.generate(KeyAlg.X25519) + + +@pytest.fixture +def bob_askar_key(): + yield Key.generate(KeyAlg.X25519) + + +@pytest.fixture +def alice_authlib_key(alice_askar_key: Key): + yield OKPKey.import_key(json.loads(alice_askar_key.get_jwk_public())) + + +@pytest.fixture +def bob_authlib_key(bob_askar_key: Key): + yield OKPKey.import_key(json.loads(bob_askar_key.get_jwk_secret())) + + +@pytest.fixture +def alice(alice_askar_key: Key, alice_authlib_key: OKPKey): + yield AskarSecretKey(alice_askar_key, ALICE_KID), AuthlibKey( + alice_authlib_key, ALICE_KID + ) + + +@pytest.fixture +def bob(bob_askar_key: Key, bob_authlib_key: OKPKey): + yield AuthlibSecretKey(bob_authlib_key, BOB_KID), AskarKey(bob_askar_key, BOB_KID) + + +@pytest.fixture +def askar(): + yield AskarCryptoService() + + +@pytest.fixture +def authlib(): + yield AuthlibCryptoService() + + +@pytest.mark.asyncio +async def test_compat( + askar: AskarCryptoService, + authlib: AuthlibCryptoService, + alice: tuple[AskarSecretKey, AuthlibKey], + bob: tuple[AuthlibSecretKey, AskarKey], +): + """Test compabibility between Askar and Authlib. + + Alice uses Askar, Bob uses Authlib. + """ + alice_sk, alice_pk = alice + bob_sk, bob_pk = bob + + to_alice = b"Dear alice, please decrypt this" + enc_message = await authlib.ecdh_1pu_encrypt([alice_pk], bob_sk, to_alice) + plaintext = await askar.ecdh_1pu_decrypt(enc_message, alice_sk, bob_pk) + assert plaintext == to_alice + + to_bob = b"Dear bob, please decrypt this" + enc_message = await askar.ecdh_1pu_encrypt([bob_pk], alice_sk, to_bob) + plaintext = await authlib.ecdh_1pu_decrypt(enc_message, bob_sk, alice_pk) + assert plaintext == to_bob diff --git a/tests/authlib/test_authlib.py b/tests/crypto/test_authlib.py similarity index 100% rename from tests/authlib/test_authlib.py rename to tests/crypto/test_authlib.py From 91196b31755704fa662e30c2ae7c906a1681f3f6 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Sun, 12 Nov 2023 21:46:30 -0500 Subject: [PATCH 4/7] fix: incompatibility issues Signed-off-by: Daniel Bluhm --- didcomm_messaging/crypto/backend/askar.py | 39 ++++++++++-------- didcomm_messaging/crypto/backend/authlib.py | 40 +++++++++++++++--- didcomm_messaging/crypto/jwe.py | 18 ++++++++- pdm.lock | 31 +++++++++++++- pyproject.toml | 1 + tests/crypto/test_askar.py | 17 ++++++++ tests/crypto/test_askar_x_authlib.py | 45 ++++++++++++++++++--- tests/crypto/test_authlib.py | 20 +++++++++ 8 files changed, 181 insertions(+), 30 deletions(-) diff --git a/didcomm_messaging/crypto/backend/askar.py b/didcomm_messaging/crypto/backend/askar.py index f7d8fe5..69a34be 100644 --- a/didcomm_messaging/crypto/backend/askar.py +++ b/didcomm_messaging/crypto/backend/askar.py @@ -143,26 +143,37 @@ async def ecdh_es_encrypt( except AskarError: raise CryptoServiceError("Error creating content encryption key") + apv = [] + for recip_key in to_keys: + apv.append(recip_key.kid) + apv.sort() + apv = hashlib.sha256((".".join(apv)).encode()).digest() + for recip_key in to_keys: try: epk = Key.generate(recip_key.key.algorithm, ephemeral=True) except AskarError: raise CryptoServiceError("Error creating ephemeral key") - enc_key = ecdh.EcdhEs(alg_id, None, None).sender_wrap_key( # type: ignore + enc_key = ecdh.EcdhEs(alg_id, None, apv).sender_wrap_key( # type: ignore wrap_alg, epk, recip_key.key, cek ) builder.add_recipient( JweRecipient( encrypted_key=enc_key.ciphertext, - header={"kid": recip_key.kid, "epk": epk.get_jwk_public()}, + header={ + "kid": recip_key.kid, + "epk": json.loads(epk.get_jwk_public()), + }, ) ) builder.set_protected( OrderedDict( [ + ("typ", "application/didcomm-encrypted+json"), ("alg", alg_id), ("enc", enc_id), + ("apv", b64url(apv)), ] ) ) @@ -218,12 +229,10 @@ async def ecdh_es_decrypt( except AskarError: raise CryptoServiceError("Error loading ephemeral key") - apu = recip.header.get("apu") - apv = recip.header.get("apv") - # apu and apv are allowed to be None - try: - cek = ecdh.EcdhEs(alg_id, apu, apv).receiver_unwrap_key( # type: ignore + cek = ecdh.EcdhEs( + alg_id, None, wrapper.apv_bytes + ).receiver_unwrap_key( # type: ignore wrap_alg, enc_alg, epk, @@ -273,7 +282,7 @@ async def ecdh_1pu_encrypt( except AskarError: raise CryptoServiceError("Error creating ephemeral key") - apu = b64url(sender_key.kid) + apu = sender_key.kid apv = [] for recip_key in to_keys: if agree_alg: @@ -283,15 +292,15 @@ async def ecdh_1pu_encrypt( agree_alg = recip_key.key.algorithm apv.append(recip_key.kid) apv.sort() - apv = b64url(hashlib.sha256((".".join(apv)).encode()).digest()) + apv = hashlib.sha256((".".join(apv)).encode()).digest() builder.set_protected( OrderedDict( [ ("alg", alg_id), ("enc", enc_id), - ("apu", apu), - ("apv", apv), + ("apu", b64url(apu)), + ("apv", b64url(apv)), ("epk", json.loads(epk.get_jwk_public())), ("skid", sender_key.kid), ] @@ -351,12 +360,10 @@ async def ecdh_1pu_decrypt( except AskarError: raise CryptoServiceError("Error loading ephemeral key") - apu = wrapper.protected.get("apu") - apv = wrapper.protected.get("apv") - # apu and apv are allowed to be None - try: - cek = ecdh.Ecdh1PU(alg_id, apu, apv).receiver_unwrap_key( # type: ignore + cek = ecdh.Ecdh1PU( + alg_id, wrapper.apu_bytes, wrapper.apv_bytes + ).receiver_unwrap_key( # type: ignore wrap_alg, enc_alg, epk, diff --git a/didcomm_messaging/crypto/backend/authlib.py b/didcomm_messaging/crypto/backend/authlib.py index 871165f..f14bafe 100644 --- a/didcomm_messaging/crypto/backend/authlib.py +++ b/didcomm_messaging/crypto/backend/authlib.py @@ -5,9 +5,15 @@ from typing import Mapping, Optional, Sequence, Tuple, Union from pydid import VerificationMethod + +from didcomm_messaging.crypto.base import ( + CryptoService, + CryptoServiceError, + PublicKey, + SecretKey, +) from didcomm_messaging.multiformats import multibase, multicodec from didcomm_messaging.multiformats.multibase import Base64UrlEncoder -from didcomm_messaging.crypto.base import CryptoService, PublicKey, SecretKey try: @@ -136,7 +142,7 @@ def verification_method_to_public_key(cls, vm: VerificationMethod) -> AuthlibKey """Return a PublicKey from a verification method.""" return AuthlibKey.from_verification_method(vm) - def _build_header( + def _build_header_ecdh_1pu( self, to: Sequence[AuthlibKey], frm: AuthlibSecretKey, alg: str, enc: str ): skid = frm.kid @@ -155,17 +161,39 @@ def _build_header( recipients = [{"header": {"kid": kid}} for kid in kids] return {"protected": protected, "recipients": recipients} + def _build_header_ecdh_es(self, to: Sequence[AuthlibKey], alg: str, enc: str): + kids = [to_key.kid for to_key in to] + + apv = b64url.encode(hashlib.sha256((".".join(sorted(kids))).encode()).digest()) + protected = { + "typ": "application/didcomm-encrypted+json", + "alg": alg, + "enc": enc, + "apv": apv, + } + recipients = [{"header": {"kid": kid}} for kid in kids] + return {"protected": protected, "recipients": recipients} + async def ecdh_es_encrypt( self, to_keys: Sequence[AuthlibKey], message: bytes ) -> bytes: """Encrypt a message using ECDH-ES.""" - return await super().ecdh_es_encrypt(to_keys, message) + header = self._build_header_ecdh_es(to_keys, "ECDH-ES+A256KW", "XC20P") + jwe = JsonWebEncryption() + res = jwe.serialize_json(header, message, [value.key for value in to_keys]) + return json.dumps(res).encode() async def ecdh_es_decrypt( self, enc_message: Union[str, bytes], recip_key: AuthlibSecretKey ) -> bytes: """Decrypt a message using ECDH-ES.""" - return await super().ecdh_es_decrypt(enc_message, recip_key) + try: + jwe = JsonWebEncryption() + res = jwe.deserialize_json(enc_message, recip_key.key) + except Exception as err: + raise CryptoServiceError("Invalid JWE") from err + + return res["payload"] async def ecdh_1pu_encrypt( self, @@ -174,7 +202,7 @@ async def ecdh_1pu_encrypt( message: bytes, ) -> bytes: """Encrypt a message using ECDH-1PU.""" - header = self._build_header( + header = self._build_header_ecdh_1pu( to_keys, sender_key, "ECDH-1PU+A256KW", "A256CBC-HS512" ) jwe = JsonWebEncryption() @@ -196,6 +224,6 @@ async def ecdh_1pu_decrypt( enc_message, recip_key.key, sender_key=sender_key.key ) except Exception as err: - raise ValueError("Invalid JWE") from err + raise CryptoServiceError("Invalid JWE") from err return res["payload"] diff --git a/didcomm_messaging/crypto/jwe.py b/didcomm_messaging/crypto/jwe.py index 66c1333..96e2c78 100644 --- a/didcomm_messaging/crypto/jwe.py +++ b/didcomm_messaging/crypto/jwe.py @@ -367,7 +367,7 @@ def get_recipients(self) -> Iterable[JweRecipient]: outer envelope. """ header = self.protected.copy() - header.update(self.unprotected) + header.update(self.unprotected or {}) for recip in self.recipients: if recip.header: recip_h = header.copy() @@ -393,7 +393,7 @@ def get_recipient(self, kid: str) -> JweRecipient: for recip in self.recipients: if recip.header and recip.header.get("kid") == kid: header = self.protected.copy() - header.update(self.unprotected) + header.update(self.unprotected or {}) header.update(recip.header) return JweRecipient(encrypted_key=recip.encrypted_key, header=header) raise ValueError(f"Unknown recipient: {kid}") @@ -405,3 +405,17 @@ def combined_aad(self) -> bytes: if self.aad: aad += b"." + b64url(self.aad).encode("utf-8") return aad + + @property + def apu_bytes(self) -> bytes: + """Accessor for the Agreement PartyUInfo.""" + if "apu" in self.protected: + return from_b64url(self.protected["apu"]) + raise ValueError("Missing apu") + + @property + def apv_bytes(self) -> bytes: + """Accessor for the Agreement PartyVInfo.""" + if "apv" in self.protected: + return from_b64url(self.protected["apv"]) + raise ValueError("Missing apv") diff --git a/pdm.lock b/pdm.lock index 58d5969..93f89fe 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "authlib", "dev"] strategy = ["cross_platform"] lock_version = "4.4" -content_hash = "sha256:b4969ff34317a0c41035e9f1b731a2736c54054d7f2b62b4ddb3821caf285b19" +content_hash = "sha256:457084dc08847eebe96ff14040e06a7e7674753c45fa2d27578a6d4d9626a0f6" [[package]] name = "authlib" @@ -331,6 +331,35 @@ files = [ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] +[[package]] +name = "pycryptodomex" +version = "3.19.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +summary = "Cryptographic library for Python" +files = [ + {file = "pycryptodomex-3.19.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:a77b79852175064c822b047fee7cf5a1f434f06ad075cc9986aa1c19a0c53eb0"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5b883e1439ab63af976656446fb4839d566bb096f15fc3c06b5a99cde4927188"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3866d68e2fc345162b1b9b83ef80686acfe5cec0d134337f3b03950a0a8bf56"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74eb1f73f788facece7979ce91594dc177e1a9b5d5e3e64697dd58299e5cb4d"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cb51096a6a8d400724104db8a7e4f2206041a1f23e58924aa3d8d96bcb48338"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a588a1cb7781da9d5e1c84affd98c32aff9c89771eac8eaa659d2760666f7139"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:d4dd3b381ff5a5907a3eb98f5f6d32c64d319a840278ceea1dcfcc65063856f3"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:263de9a96d2fcbc9f5bd3a279f14ea0d5f072adb68ebd324987576ec25da084d"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-win32.whl", hash = "sha256:67c8eb79ab33d0fbcb56842992298ddb56eb6505a72369c20f60bc1d2b6fb002"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-win_amd64.whl", hash = "sha256:09c9401dc06fb3d94cb1ec23b4ea067a25d1f4c6b7b118ff5631d0b5daaab3cc"}, + {file = "pycryptodomex-3.19.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:edbe083c299835de7e02c8aa0885cb904a75087d35e7bab75ebe5ed336e8c3e2"}, + {file = "pycryptodomex-3.19.0-pp27-pypy_73-win32.whl", hash = "sha256:136b284e9246b4ccf4f752d435c80f2c44fc2321c198505de1d43a95a3453b3c"}, + {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5d73e9fa3fe830e7b6b42afc49d8329b07a049a47d12e0ef9225f2fd220f19b2"}, + {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b2f1982c5bc311f0aab8c293524b861b485d76f7c9ab2c3ac9a25b6f7655975"}, + {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb040b5dda1dff1e197d2ef71927bd6b8bfcb9793bc4dfe0bb6df1e691eaacb"}, + {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:800a2b05cfb83654df80266692f7092eeefe2a314fa7901dcefab255934faeec"}, + {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c01678aee8ac0c1a461cbc38ad496f953f9efcb1fa19f5637cbeba7544792a53"}, + {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2126bc54beccbede6eade00e647106b4f4c21e5201d2b0a73e9e816a01c50905"}, + {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b801216c48c0886742abf286a9a6b117e248ca144d8ceec1f931ce2dd0c9cb40"}, + {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50cb18d4dd87571006fd2447ccec85e6cec0136632a550aa29226ba075c80644"}, + {file = "pycryptodomex-3.19.0.tar.gz", hash = "sha256:af83a554b3f077564229865c45af0791be008ac6469ef0098152139e6bd4b5b6"}, +] + [[package]] name = "pydantic" version = "1.10.13" diff --git a/pyproject.toml b/pyproject.toml index 2de6eeb..dc0ae36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ did_peer = [ ] authlib = [ "authlib>=1.2.1", + "pycryptodomex>=3.19.0", ] [build-system] diff --git a/tests/crypto/test_askar.py b/tests/crypto/test_askar.py index ae8b94e..ff51a02 100644 --- a/tests/crypto/test_askar.py +++ b/tests/crypto/test_askar.py @@ -35,3 +35,20 @@ async def test_1pu_round_trip(crypto: AskarCryptoService): plaintext = await crypto.ecdh_1pu_decrypt(enc_message, bob_priv_key, alice_key) assert plaintext == MESSAGE + + +@pytest.mark.asyncio +async def test_es_round_trip(crypto: AskarCryptoService): + """Test ECDH-ES round trip.""" + alg = KeyAlg.X25519 + alice_sk = Key.generate(alg) + alice_pk = Key.from_jwk(alice_sk.get_jwk_public()) + bob_sk = Key.generate(alg) + bob_pk = Key.from_jwk(bob_sk.get_jwk_public()) + bob_key = AskarKey(bob_sk, BOB_KID) + bob_priv_key = AskarSecretKey(bob_sk, BOB_KID) + alice_key = AskarKey(alice_sk, ALICE_KID) + alice_priv_key = AskarSecretKey(alice_sk, ALICE_KID) + enc_message = await crypto.ecdh_es_encrypt([bob_key], MESSAGE) + plaintext = await crypto.ecdh_es_decrypt(enc_message, bob_priv_key) + assert plaintext == MESSAGE diff --git a/tests/crypto/test_askar_x_authlib.py b/tests/crypto/test_askar_x_authlib.py index 344fae4..b367b2e 100644 --- a/tests/crypto/test_askar_x_authlib.py +++ b/tests/crypto/test_askar_x_authlib.py @@ -63,7 +63,7 @@ def authlib(): @pytest.mark.asyncio -async def test_compat( +async def test_compat_ecdh_1pu( askar: AskarCryptoService, authlib: AuthlibCryptoService, alice: tuple[AskarSecretKey, AuthlibKey], @@ -77,11 +77,46 @@ async def test_compat( bob_sk, bob_pk = bob to_alice = b"Dear alice, please decrypt this" - enc_message = await authlib.ecdh_1pu_encrypt([alice_pk], bob_sk, to_alice) - plaintext = await askar.ecdh_1pu_decrypt(enc_message, alice_sk, bob_pk) + alice_enc_message = await authlib.ecdh_1pu_encrypt([alice_pk], bob_sk, to_alice) + print(alice_enc_message) + + plaintext = await askar.ecdh_1pu_decrypt(alice_enc_message, alice_sk, bob_pk) + assert plaintext == to_alice + + to_bob = b"Dear bob, please decrypt this" + bob_enc_message = await askar.ecdh_1pu_encrypt([bob_pk], alice_sk, to_bob) + + print(bob_enc_message) + + plaintext = await authlib.ecdh_1pu_decrypt(bob_enc_message, bob_sk, alice_pk) + assert plaintext == to_bob + + +@pytest.mark.asyncio +async def test_compat_ecdh_es( + askar: AskarCryptoService, + authlib: AuthlibCryptoService, + alice: tuple[AskarSecretKey, AuthlibKey], + bob: tuple[AuthlibSecretKey, AskarKey], +): + """Test compabibility between Askar and Authlib. + + Alice uses Askar, Bob uses Authlib. + """ + alice_sk, alice_pk = alice + bob_sk, bob_pk = bob + + to_alice = b"Dear alice, please decrypt this" + alice_enc_message = await authlib.ecdh_es_encrypt([alice_pk], to_alice) + print(alice_enc_message) + + plaintext = await askar.ecdh_es_decrypt(alice_enc_message, alice_sk) assert plaintext == to_alice to_bob = b"Dear bob, please decrypt this" - enc_message = await askar.ecdh_1pu_encrypt([bob_pk], alice_sk, to_bob) - plaintext = await authlib.ecdh_1pu_decrypt(enc_message, bob_sk, alice_pk) + bob_enc_message = await askar.ecdh_es_encrypt([bob_pk], to_bob) + + print(bob_enc_message) + + plaintext = await authlib.ecdh_es_decrypt(bob_enc_message, bob_sk) assert plaintext == to_bob diff --git a/tests/crypto/test_authlib.py b/tests/crypto/test_authlib.py index 47bd005..dab3028 100644 --- a/tests/crypto/test_authlib.py +++ b/tests/crypto/test_authlib.py @@ -47,3 +47,23 @@ async def test_1pu_round_trip(crypto: AuthlibCryptoService): plaintext = await crypto.ecdh_1pu_decrypt(enc_message, bob_priv_key, alice_key) assert plaintext == MESSAGE + + +@pytest.mark.asyncio +async def test_es_round_trip(crypto: AuthlibCryptoService): + """Test ECDH-ES round trip.""" + alice_sk = OKPKey.generate_key("X25519", is_private=True) + alice_pk = alice_sk.get_public_key() + bob_sk = OKPKey.generate_key("X25519", is_private=True) + bob_pk = bob_sk.get_public_key() + + bob_key = AuthlibKey(bob_sk, BOB_KID) + bob_priv_key = AuthlibSecretKey(bob_sk, BOB_KID) + + alice_key = AuthlibKey(alice_sk, ALICE_KID) + alice_priv_key = AuthlibSecretKey(alice_sk, ALICE_KID) + + enc_message = await crypto.ecdh_es_encrypt([bob_key], MESSAGE) + + plaintext = await crypto.ecdh_es_decrypt(enc_message, bob_priv_key) + assert plaintext == MESSAGE From 5cd165ab9e600f34b4749b4c4753af0fdc7fe6ca Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 13 Nov 2023 10:53:29 -0500 Subject: [PATCH 5/7] fix: bad import; enable TID ruff checks Signed-off-by: Daniel Bluhm --- didcomm_messaging/crypto/backend/askar.py | 3 +-- didcomm_messaging/crypto/backend/basic.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/didcomm_messaging/crypto/backend/askar.py b/didcomm_messaging/crypto/backend/askar.py index 69a34be..15b5930 100644 --- a/didcomm_messaging/crypto/backend/askar.py +++ b/didcomm_messaging/crypto/backend/askar.py @@ -13,10 +13,9 @@ SecretKey, SecretsManager, ) +from didcomm_messaging.crypto.jwe import JweBuilder, JweEnvelope, JweRecipient, b64url from didcomm_messaging.multiformats import multibase, multicodec -from ..jwe import JweBuilder, JweEnvelope, JweRecipient, b64url - try: from aries_askar import Key, ecdh, AskarError, KeyAlg, Store except ImportError: diff --git a/didcomm_messaging/crypto/backend/basic.py b/didcomm_messaging/crypto/backend/basic.py index 8ca714a..c69d923 100644 --- a/didcomm_messaging/crypto/backend/basic.py +++ b/didcomm_messaging/crypto/backend/basic.py @@ -1,7 +1,7 @@ """Basic Crypto Implementations.""" from typing import Optional -from .base import S, SecretsManager +from didcomm_messaging.crypto.base import S, SecretsManager class InMemorySecretsManager(SecretsManager[S]): diff --git a/pyproject.toml b/pyproject.toml index dc0ae36..7127be2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ build-backend = "pdm.backend" addopts = "--doctest-glob README.md --ruff" [tool.ruff] -select = ["E", "F", "C", "D"] +select = ["E", "F", "C", "D", "TID"] ignore = [ # Google Python Doc Style From 979c6aa7c63bd24dcbeaa48d6dacc467fe8b2468 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 13 Nov 2023 11:00:26 -0500 Subject: [PATCH 6/7] test: packaging service Failing at the moment Signed-off-by: Daniel Bluhm --- tests/test_packaging.py | 69 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/test_packaging.py diff --git a/tests/test_packaging.py b/tests/test_packaging.py new file mode 100644 index 0000000..bb5d6fa --- /dev/null +++ b/tests/test_packaging.py @@ -0,0 +1,69 @@ +"""Test PackagingService.""" +import pytest + +from aries_askar import Key, KeyAlg +from didcomm_messaging.crypto.backend.askar import AskarCryptoService, AskarSecretKey +from didcomm_messaging.crypto.backend.basic import InMemorySecretsManager +from didcomm_messaging.packaging import PackagingService +from didcomm_messaging.multiformats import multibase +from didcomm_messaging.multiformats import multicodec +from didcomm_messaging.resolver.peer import Peer2, Peer4 +from didcomm_messaging.resolver import PrefixResolver +from did_peer_2 import KeySpec, generate + + +@pytest.fixture +def secrets(): + """Fixture for secrets.""" + yield InMemorySecretsManager() + + +@pytest.fixture +def crypto(): + """Fixture for crypto.""" + yield AskarCryptoService() + + +@pytest.fixture +def packaging(secrets, crypto): + """Fixture for packaging.""" + yield PackagingService( + PrefixResolver({"did:peer:2": Peer2(), "did:peer:4": Peer4()}), crypto, secrets + ) + + +# TODO More thorough tests +@pytest.mark.asyncio +async def test_packer_basic( + secrets: InMemorySecretsManager, + crypto: AskarCryptoService, + packaging: PackagingService, +): + """Test basic packaging. + + This is a happy path test. + """ + verkey = Key.generate(KeyAlg.ED25519) + xkey = Key.generate(KeyAlg.X25519) + did = generate( + [ + KeySpec.verification( + multibase.encode( + multicodec.wrap("ed25519-pub", verkey.get_public_bytes()), + "base58btc", + ) + ), + KeySpec.key_agreement( + multibase.encode( + multicodec.wrap("x25519-pub", xkey.get_public_bytes()), "base58btc" + ) + ), + ], + [], + ) + await secrets.add_secret(AskarSecretKey(verkey, f"{did}#key-1")) + await secrets.add_secret(AskarSecretKey(xkey, f"{did}#key-2")) + message = b"hello world" + packed = await packaging.pack(message, [did], did) + unpacked = await packaging.unpack(packed) + assert unpacked == message From 44f7472ab0fe289847949431a1bd64b2aad2db64 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 13 Nov 2023 11:09:32 -0500 Subject: [PATCH 7/7] fix: check against bytes of message Signed-off-by: Daniel Bluhm --- pdm.lock | 51 ++++++++++++++++++++++++++++++++++++++++- tests/test_packaging.py | 3 +-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/pdm.lock b/pdm.lock index 93f89fe..e8efded 100644 --- a/pdm.lock +++ b/pdm.lock @@ -2,11 +2,25 @@ # It is not intended for manual editing. [metadata] -groups = ["default", "authlib", "dev"] +groups = ["default", "askar", "authlib", "dev", "did_peer"] strategy = ["cross_platform"] lock_version = "4.4" content_hash = "sha256:457084dc08847eebe96ff14040e06a7e7674753c45fa2d27578a6d4d9626a0f6" +[[package]] +name = "aries-askar" +version = "0.2.9" +requires_python = ">=3.6.3" +summary = "UNKNOWN" +dependencies = [ + "cached-property~=1.5", +] +files = [ + {file = "aries_askar-0.2.9-py3-none-macosx_10_9_universal2.whl", hash = "sha256:e8231845ab5eee3be08070c3d005a1be22da2c3fdf84b6adc06e97ee47abcd26"}, + {file = "aries_askar-0.2.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:73a4b9384b762423da563ebbe72abc8edf4c11416150148b7b758afd98ef7758"}, + {file = "aries_askar-0.2.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9084f10f193a331275c6af72235ecc816063dafe71dc3eca5c920bf30ff069d1"}, +] + [[package]] name = "authlib" version = "1.2.1" @@ -60,6 +74,15 @@ files = [ {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, ] +[[package]] +name = "cached-property" +version = "1.5.2" +summary = "A decorator for caching properties in classes." +files = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] + [[package]] name = "cffi" version = "1.16.0" @@ -182,6 +205,32 @@ files = [ {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, ] +[[package]] +name = "did-peer-2" +version = "0.1.2" +requires_python = ">=3.9" +summary = "An implementation of did:peer:2" +dependencies = [ + "base58>=2.1.1", +] +files = [ + {file = "did_peer_2-0.1.2-py3-none-any.whl", hash = "sha256:d5908cda2d52b7c34428a421044507d7847fd79b78dc8360441c408f4507d612"}, + {file = "did_peer_2-0.1.2.tar.gz", hash = "sha256:af8623f62022732e9fadc0289dfb886fd8267767251c4fa0b63694ecd29a7086"}, +] + +[[package]] +name = "did-peer-4" +version = "0.1.3" +requires_python = ">=3.9" +summary = "An implementation of did:peer:4" +dependencies = [ + "base58>=2.1.1", +] +files = [ + {file = "did_peer_4-0.1.3-py3-none-any.whl", hash = "sha256:2e9a62b52d06c96dfa25449616190384ecea4b6c3261be7d7df82635f1e63b6d"}, + {file = "did_peer_4-0.1.3.tar.gz", hash = "sha256:9b82535c4abee528ab66998b2ab876a2c9c60eb4410aeebde86c3ef98c59205c"}, +] + [[package]] name = "distlib" version = "0.3.7" diff --git a/tests/test_packaging.py b/tests/test_packaging.py index bb5d6fa..60a8ff0 100644 --- a/tests/test_packaging.py +++ b/tests/test_packaging.py @@ -36,7 +36,6 @@ def packaging(secrets, crypto): @pytest.mark.asyncio async def test_packer_basic( secrets: InMemorySecretsManager, - crypto: AskarCryptoService, packaging: PackagingService, ): """Test basic packaging. @@ -65,5 +64,5 @@ async def test_packer_basic( await secrets.add_secret(AskarSecretKey(xkey, f"{did}#key-2")) message = b"hello world" packed = await packaging.pack(message, [did], did) - unpacked = await packaging.unpack(packed) + unpacked, meta = await packaging.unpack(packed) assert unpacked == message