Skip to content

Commit

Permalink
fix: multiple fixes after more testing
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Bluhm <[email protected]>
  • Loading branch information
dbluhm committed Jun 6, 2024
1 parent 9b68316 commit 2a136b7
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 110 deletions.
11 changes: 6 additions & 5 deletions didcomm_messaging/crypto/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ def key_bytes_from_verification_method(cls, vm: VerificationMethod) -> bytes:
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")
if vm.type != "Multikey":
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:
Expand Down
2 changes: 1 addition & 1 deletion didcomm_messaging/crypto/jwe.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ def _deserialize(cls, parsed: Mapping[str, Any]) -> "JweEnvelope": # noqa: C901
inst = cls(
recipients=recipients,
protected=protected,
protected_b64=protected_b64,
protected_b64=protected_b64.encode(),
unprotected=unprotected,
ciphertext=ciphertext,
iv=iv,
Expand Down
81 changes: 62 additions & 19 deletions didcomm_messaging/legacy/messaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
import json
from typing import Generic, Optional, Sequence, Union

import base58
from pydantic import AnyUrl
from pydid import VerificationMethod
from pydid.service import DIDCommV1Service

from didcomm_messaging.crypto import SecretsManager, P, S
from didcomm_messaging.crypto import P, S, SecretsManager
from didcomm_messaging.legacy.base import LegacyCryptoService
from didcomm_messaging.legacy.packaging import LegacyPackagingService
from didcomm_messaging.multiformats import multibase, multicodec
from didcomm_messaging.resolver import DIDResolver


Expand Down Expand Up @@ -57,6 +59,13 @@ class Target:
class LegacyDIDCommMessagingService(Generic[P, S]):
"""Main entrypoint for DIDComm Messaging."""

def multikey_to_kid(self, multikey: str) -> str:
"""Return a kid from a multikey."""
codec, data = multicodec.unwrap(multibase.decode(multikey))
if codec != multicodec.multicodec("ed25519-pub"):
raise LegacyDIDCommMessagingError("DIDComm v1 requires ed25519 keys")
return base58.b58encode(data).decode()

async def did_to_target(
self, crypto: LegacyCryptoService[P, S], resolver: DIDResolver, did: str
) -> Target:
Expand All @@ -72,27 +81,55 @@ async def did_to_target(
target = services[0]

recipient_keys = [
crypto.verification_method_to_public_key(
doc.dereference_as(VerificationMethod, recip)
).kid
self.multikey_to_kid(
crypto.verification_method_to_public_key(
doc.dereference_as(VerificationMethod, recip)
).multikey
)
for recip in target.recipient_keys
]
routing_keys = [
crypto.verification_method_to_public_key(
doc.dereference_as(VerificationMethod, routing_key)
).kid
self.multikey_to_kid(
crypto.verification_method_to_public_key(
doc.dereference_as(VerificationMethod, routing_key)
).multikey
)
for routing_key in target.routing_keys
]
endpoint = target.service_endpoint
if isinstance(endpoint, AnyUrl):
endpoint = str(endpoint)
if not endpoint.startswith("http") or not endpoint.startswith("ws"):
if not endpoint.startswith("http") and not endpoint.startswith("ws"):
raise LegacyDIDCommMessagingError(
f"Unable to send message to endpoint {endpoint}"
)

return Target(recipient_keys, routing_keys, endpoint)

async def from_did_to_kid(
self, crypto: LegacyCryptoService[P, S], resolver: DIDResolver, did: str
) -> str:
"""Resolve our DID to a kid to be used by crypto layers."""
doc = await resolver.resolve_and_parse(did)
services = [
service
for service in doc.service or []
if isinstance(service, DIDCommV1Service)
]
if not services:
raise LegacyDIDCommMessagingError(f"Unable to send message to DID {did}")
target = services[0]

recipient_keys = [
self.multikey_to_kid(
crypto.verification_method_to_public_key(
doc.dereference_as(VerificationMethod, recip)
).multikey
)
for recip in target.recipient_keys
]
return recipient_keys[0]

def forward_wrap(self, to: str, msg: str) -> bytes:
"""Wrap a message in a forward."""
forward = {
Expand All @@ -109,7 +146,7 @@ async def pack(
secrets: SecretsManager[S],
packaging: LegacyPackagingService[P, S],
message: Union[dict, str, bytes],
to: str,
to: Union[str, Target],
frm: Optional[str] = None,
**options,
):
Expand All @@ -121,9 +158,10 @@ async def pack(
secrets: secrets manager to use to look up private key material
packaging: packaging service
routing: routing service
message: to send
to: recipient of the message, expressed as a DID
frm: the sender of the message, expressed as a DID
message: to send; must be str, bytes, or json serializable dict
to: recipient of the message, expressed as a DID or Target
frm: the sender of the message, expressed as a DID or kid (base58 encoded
public key)
options: arbitrary values to pass to the packaging service
Returns:
Expand All @@ -139,7 +177,13 @@ async def pack(
else:
raise TypeError("message must be bytes, str, or dict")

target = await self.did_to_target(crypto, resolver, to)
if isinstance(to, str):
target = await self.did_to_target(crypto, resolver, to)
else:
target = to

if frm and frm.startswith("did:"):
frm = await self.from_did_to_kid(crypto, resolver, frm) if frm else None

encoded_message = await packaging.pack(
crypto,
Expand Down Expand Up @@ -202,18 +246,17 @@ def __init__(
async def pack(
self,
message: Union[dict, str, bytes],
to: str,
to: Union[str, Target],
frm: Optional[str] = None,
**options,
) -> LegacyPackResult:
"""Pack a message.
Args:
message: to send
to: recipient of the message, expressed as a KID which is a Base58
encoded Ed25519 public key
frm: the sender of the message, expressed as a KID which is a Base58
encoded Ed25519 public key
message: to send; must be str, bytes, or json serializable dict
to: recipient of the message, expressed as a DID or Target
frm: the sender of the message, expressed as a DID or kid (base58 encoded
public key)
options: arbitrary values to pass to the packaging service
Returns:
Expand Down
6 changes: 3 additions & 3 deletions didcomm_messaging/legacy/packaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ def extract_pack_recipients(self, recipients: Sequence[JweRecipient]):
ValueError: If the recipients block is malformed
"""
result = {}
seen_recips = []
for recip in recipients:
recip_vk_b58 = recip.header.get("kid")
if not recip_vk_b58:
raise ValueError("Blank recipient key")
if recip_vk_b58 in result:
if recip_vk_b58 in seen_recips:
raise ValueError("Duplicate recipient key")
seen_recips.append(recip_vk_b58)

sender_b64 = recip.header.get("sender")
enc_sender = self.b64url.decode(sender_b64) if sender_b64 else None
Expand All @@ -50,7 +51,6 @@ def extract_pack_recipients(self, recipients: Sequence[JweRecipient]):
nonce = self.b64url.decode(nonce_b64) if nonce_b64 else None

yield RecipData(recip_vk_b58, enc_sender, nonce, recip.encrypted_key)
return result

async def extract_packed_message_metadata(
self, secrets: SecretsManager[S], wrapper: JweEnvelope
Expand Down
6 changes: 4 additions & 2 deletions didcomm_messaging/resolver/peer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Peer2(DIDResolver):

async def is_resolvable(self, did: str) -> bool:
"""Check to see if a DID is resolvable."""
return peer_2_pattern.match(did)
return bool(peer_2_pattern.match(did))

async def resolve(self, did: str) -> dict:
"""Resolve a did:peer:2 DID."""
Expand All @@ -32,7 +32,9 @@ class Peer4(DIDResolver):

async def is_resolvable(self, did: str) -> bool:
"""Check to see if a DID is resolvable."""
return peer_4_pattern_short.match(did) or peer_4_pattern_long.match(did)
return bool(peer_4_pattern_short.match(did)) or bool(
peer_4_pattern_long.match(did)
)

async def resolve(self, did: str) -> dict:
"""Resolve a did:peer:4 DID."""
Expand Down
98 changes: 74 additions & 24 deletions tests/legacy/conftest.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
from aries_askar import Key, KeyAlg, Store
import base58
from did_peer_4.input_doc import KeySpec, input_doc_from_keys_and_services
import did_peer_4
import pytest
import pytest_asyncio
import base58

from didcomm_messaging.crypto.backend.askar import AskarSecretsManager
from didcomm_messaging.crypto.backend.askar import AskarKey, AskarSecretsManager
from didcomm_messaging.legacy.askar import AskarLegacyCryptoService, AskarSecretKey
from didcomm_messaging.legacy.nacl import InMemSecretsManager, NaclLegacyCryptoService

from aries_askar import Key, KeyAlg, Store

from didcomm_messaging.legacy.messaging import LegacyDIDCommMessaging
from didcomm_messaging.legacy.nacl import (
EdPublicKey,
InMemSecretsManager,
KeyPair,
NaclLegacyCryptoService,
)
from didcomm_messaging.legacy.packaging import LegacyPackagingService
from didcomm_messaging.resolver import DIDResolver
from didcomm_messaging.resolver.peer import Peer4


@pytest.fixture
Expand Down Expand Up @@ -38,38 +46,80 @@ async def askar_secrets(store: Store):


@pytest.fixture
def alice(nacl_secrets: InMemSecretsManager):
"""Generate alice's keys."""
yield nacl_secrets.create()
def packer():
yield LegacyPackagingService()


@pytest.fixture
def bob(nacl_secrets: InMemSecretsManager):
"""Generate bob's keys."""
yield nacl_secrets.create()
def resolver():
yield Peer4()


@pytest.fixture
def packer():
yield LegacyPackagingService()

def alice(
nacl: NaclLegacyCryptoService,
nacl_secrets: InMemSecretsManager,
packer: LegacyPackagingService,
resolver: DIDResolver,
):
yield LegacyDIDCommMessaging(nacl, nacl_secrets, resolver, packer)

@pytest_asyncio.fixture
async def askarlice(store: Store):
"""Generate alice's keys."""

key = Key.generate(KeyAlg.ED25519)
kid = base58.b58encode(key.get_public_bytes()).decode()
async with store.session() as session:
await session.insert_key(kid, key)
return AskarSecretKey(key, kid)
@pytest.fixture
def bob(
askar: AskarLegacyCryptoService,
askar_secrets: AskarSecretsManager,
resolver: DIDResolver,
packer: LegacyPackagingService,
):
yield LegacyDIDCommMessaging(askar, askar_secrets, resolver, packer)


@pytest_asyncio.fixture
async def bobskar(store: Store):
async def bob_key(store: Store):
"""Generate bob's keys."""
key = Key.generate(KeyAlg.ED25519)
kid = base58.b58encode(key.get_public_bytes()).decode()
async with store.session() as session:
await session.insert_key(kid, key)
return AskarSecretKey(key, kid)


@pytest.fixture
def alice_key(nacl_secrets: InMemSecretsManager):
"""Generate alice's keys."""
yield nacl_secrets.create()


@pytest.fixture
def alice_did(alice_key: KeyPair):
alice_pub = EdPublicKey(alice_key.verkey)
input_doc = input_doc_from_keys_and_services(
[KeySpec(alice_pub.multikey, relationships=["authentication"])],
[
{
"id": "#didcomm",
"type": "did-communication",
"recipientKeys": ["#key-0"],
"serviceEndpoint": "https://example.com",
}
],
)
return did_peer_4.encode(input_doc, validate=True)


@pytest.fixture
def bob_did(bob_key: AskarSecretKey):
bob_pub = AskarKey(bob_key.key, bob_key.kid)
input_doc = input_doc_from_keys_and_services(
[KeySpec(bob_pub.multikey, relationships=["authentication"])],
[
{
"id": "#didcomm",
"type": "did-communication",
"recipientKeys": ["#key-0"],
"serviceEndpoint": "https://example.com",
}
],
)
return did_peer_4.encode(input_doc, validate=True)
13 changes: 13 additions & 0 deletions tests/legacy/test_crypto.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
"""Test Pack and Unpack."""

import pytest
from didcomm_messaging.legacy import crypto
from didcomm_messaging.legacy.nacl import KeyPair


@pytest.fixture
def alice():
"""Generate alice's keys."""
yield KeyPair(*crypto.create_keypair())


@pytest.fixture
def bob():
"""Generate bob's keys."""
yield KeyPair(*crypto.create_keypair())


def test_pack_unpack_auth(alice: KeyPair, bob: KeyPair):
"""Test the pack-unpack loop with authcrypt."""
msg = "hello world"
Expand Down
Loading

0 comments on commit 2a136b7

Please sign in to comment.