Skip to content

Commit

Permalink
Merge pull request #31 from Indicio-tech/refactor/flexible-services
Browse files Browse the repository at this point in the history
refactor: make services more flexible
  • Loading branch information
Frostyfrog authored Apr 22, 2024
2 parents 3fc3e98 + 3f9a6e4 commit f8ecfd6
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 82 deletions.
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

0 comments on commit f8ecfd6

Please sign in to comment.