Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 91 additions & 22 deletions didcomm_messaging/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,9 @@ class UnpackResult:
sender_kid: Optional[str] = None


class DIDCommMessaging(Generic[P, S]):
class DIDCommMessagingService(Generic[P, S]):
"""Main entrypoint for DIDComm Messaging."""

def __init__(
self,
crypto: CryptoService[P, S],
secrets: SecretsManager[S],
resolver: DIDResolver,
packaging: PackagingService[P, S],
routing: RoutingService,
):
"""Initialize the DIDComm Messaging service."""
self.crypto = crypto
self.secrets = secrets
self.resolver = resolver
self.packaging = packaging
self.routing = routing

def service_to_target(self, service: DIDCommV2Service) -> str:
"""Convert a service to a target uri.

Expand All @@ -76,20 +61,49 @@ def service_to_target(self, service: DIDCommV2Service) -> str:

return service_endpoint.uri

async def pack(self, message: dict, to: str, frm: Optional[str] = None, **options):
async def pack(
self,
crypto: CryptoService[P, S],
resolver: DIDResolver,
secrets: SecretsManager[S],
packaging: PackagingService[P, S],
routing: RoutingService,
message: dict,
to: str,
frm: Optional[str] = None,
**options,
):
"""Pack a message."""
# TODO crypto layer permits packing to multiple recipients; should we as well?

encoded_message = await self.packaging.pack(
json.dumps(message).encode(), [to], frm, **options
encoded_message = await packaging.pack(
crypto,
resolver,
secrets,
json.dumps(message).encode(),
[to],
frm,
**options,
)

forward, services = await self.routing.prepare_forward(to, encoded_message)
forward, services = await routing.prepare_forward(
crypto, packaging, resolver, secrets, to, encoded_message
)
return PackResult(forward, services)

async def unpack(self, encoded_message: bytes, **options) -> UnpackResult:
async def unpack(
self,
crypto: CryptoService[P, S],
resolver: DIDResolver,
secrets: SecretsManager[S],
packaging: PackagingService[P, S],
encoded_message: bytes,
**options,
) -> UnpackResult:
"""Unpack a message."""
unpacked, metadata = await self.packaging.unpack(encoded_message, **options)
unpacked, metadata = await packaging.unpack(
crypto, resolver, secrets, encoded_message, **options
)
message = json.loads(unpacked.decode())
return UnpackResult(
message,
Expand All @@ -98,3 +112,58 @@ async def unpack(self, encoded_message: bytes, **options) -> UnpackResult:
recipient_kid=metadata.recip_key.kid,
sender_kid=metadata.sender_kid,
)


class DIDCommMessaging(Generic[P, S]):
"""Main entrypoint for DIDComm Messaging."""

def __init__(
self,
crypto: CryptoService[P, S],
secrets: SecretsManager[S],
resolver: DIDResolver,
packaging: PackagingService[P, S],
routing: RoutingService,
):
"""Initialize the DIDComm Messaging service."""
self.crypto = crypto
self.secrets = secrets
self.resolver = resolver
self.packaging = packaging
self.routing = routing
self.dmp = DIDCommMessagingService()

async def pack(
self,
message: dict,
to: str,
frm: Optional[str] = None,
**options,
) -> PackResult:
"""Pack a message."""
return await self.dmp.pack(
self.crypto,
self.resolver,
self.secrets,
self.packaging,
self.routing,
message,
to,
frm,
**options,
)

async def unpack(
self,
encoded_message: bytes,
**options,
) -> UnpackResult:
"""Unpack a message."""
return await self.dmp.unpack(
self.crypto,
self.resolver,
self.secrets,
self.packaging,
encoded_message,
**options,
)
67 changes: 32 additions & 35 deletions didcomm_messaging/packaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,8 @@ class PackagingServiceError(Exception):
class PackagingService(Generic[P, S]):
"""DIDComm Messaging interface."""

def __init__(
self,
resolver: DIDResolver,
crypto: CryptoService[P, S],
secrets: SecretsManager[S],
):
"""Initialize the KMS."""
self.resolver = resolver
self.crypto = crypto
self.secrets = secrets

async def extract_packed_message_metadata( # noqa: C901
self, enc_message: Union[str, bytes]
self, enc_message: Union[str, bytes], secrets: SecretsManager[S]
) -> PackedMessageMetadata:
"""Extract metadata from a packed DIDComm message."""
try:
Expand All @@ -61,7 +50,7 @@ async def extract_packed_message_metadata( # noqa: C901
sender_kid = None
recip_key = None
for kid in wrapper.recipient_key_ids:
recip_key = await self.secrets.get_secret_by_kid(kid)
recip_key = await secrets.get_secret_by_kid(kid)
if recip_key:
break

Expand Down Expand Up @@ -97,40 +86,42 @@ async def extract_packed_message_metadata( # noqa: C901
return PackedMessageMetadata(wrapper, method, recip_key, sender_kid)

async def unpack(
self, enc_message: Union[str, bytes]
self,
crypto: CryptoService[P, S],
resolver: DIDResolver,
secrets: SecretsManager[S],
enc_message: Union[str, bytes],
) -> Tuple[bytes, PackedMessageMetadata]:
"""Unpack a DIDComm message."""
metadata = await self.extract_packed_message_metadata(enc_message)
metadata = await self.extract_packed_message_metadata(enc_message, secrets)

if metadata.method == "ECDH-ES":
return (
await self.crypto.ecdh_es_decrypt(enc_message, metadata.recip_key),
await crypto.ecdh_es_decrypt(enc_message, metadata.recip_key),
metadata,
)

if not metadata.sender_kid:
raise PackagingServiceError("Missing sender key ID")

sender_vm = await self.resolver.resolve_and_dereference_verification_method(
sender_vm = await resolver.resolve_and_dereference_verification_method(
metadata.sender_kid
)
sender_key = self.crypto.verification_method_to_public_key(sender_vm)
sender_key = crypto.verification_method_to_public_key(sender_vm)

return (
await self.crypto.ecdh_1pu_decrypt(
enc_message, metadata.recip_key, sender_key
),
await crypto.ecdh_1pu_decrypt(enc_message, metadata.recip_key, sender_key),
metadata,
)

async def recip_for_kid_or_default_for_did(self, kid_or_did: str) -> P:
async def recip_for_kid_or_default_for_did(
self, crypto: CryptoService[P, S], resolver: DIDResolver, kid_or_did: str
) -> P:
"""Resolve a verification method for a kid or return default recip."""
if "#" in kid_or_did:
vm = await self.resolver.resolve_and_dereference_verification_method(
kid_or_did
)
vm = await resolver.resolve_and_dereference_verification_method(kid_or_did)
else:
doc = await self.resolver.resolve_and_parse(kid_or_did)
doc = await resolver.resolve_and_parse(kid_or_did)
if not doc.key_agreement:
raise PackagingServiceError(
"No key agreement methods found; cannot determine recipient"
Expand All @@ -146,14 +137,14 @@ async def recip_for_kid_or_default_for_did(self, kid_or_did: str) -> P:
else:
vm = default

return self.crypto.verification_method_to_public_key(vm)
return crypto.verification_method_to_public_key(vm)

async def default_sender_kid_for_did(self, did: str) -> str:
async def default_sender_kid_for_did(self, resolver: DIDResolver, did: str) -> str:
"""Determine the kid of the default sender key for a DID."""
if "#" in did:
return did

doc = await self.resolver.resolve_and_parse(did)
doc = await resolver.resolve_and_parse(did)
if not doc.key_agreement:
raise PackagingServiceError(
"No key agreement methods found; cannot determine recipient"
Expand All @@ -175,21 +166,27 @@ async def default_sender_kid_for_did(self, did: str) -> str:

async def pack(
self,
crypto: CryptoService[P, S],
resolver: DIDResolver,
secrets: SecretsManager[S],
message: bytes,
to: Sequence[str],
frm: Optional[str] = None,
**options,
):
"""Pack a DIDComm message."""
recip_keys = [await self.recip_for_kid_or_default_for_did(kid) for kid in to]
sender_kid = await self.default_sender_kid_for_did(frm) if frm else None
sender_key = (
await self.secrets.get_secret_by_kid(sender_kid) if sender_kid else None
recip_keys = [
await self.recip_for_kid_or_default_for_did(crypto, resolver, kid)
for kid in to
]
sender_kid = (
await self.default_sender_kid_for_did(resolver, frm) if frm else None
)
sender_key = await secrets.get_secret_by_kid(sender_kid) if sender_kid else None
if frm and not sender_key:
raise PackagingServiceError("No sender key found")

if sender_key:
return await self.crypto.ecdh_1pu_encrypt(recip_keys, sender_key, message)
return await crypto.ecdh_1pu_encrypt(recip_keys, sender_key, message)
else:
return await self.crypto.ecdh_es_encrypt(recip_keys, message)
return await crypto.ecdh_es_encrypt(recip_keys, message)
Loading