Skip to content

Commit

Permalink
feat: add stubbed didcomm messaging and routing services
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Bluhm <[email protected]>
  • Loading branch information
dbluhm committed Nov 2, 2023
1 parent ef06475 commit 7823ed2
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 6 deletions.
85 changes: 84 additions & 1 deletion didcomm_messaging/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,84 @@
"""DIDComm Messaging implementation using Aries Askar."""
"""DIDComm Messaging."""
from dataclasses import dataclass
import json
from typing import Optional

from pydid.service import DIDCommV2Service

from didcomm_messaging.crypto import CryptoService, SecretsManager
from didcomm_messaging.packaging import PackagingService
from didcomm_messaging.resolver import DIDResolver
from didcomm_messaging.routing import RoutingService


@dataclass
class PackResult:
"""Result of packing a message."""

message: bytes
target: str


@dataclass
class UnpackResult:
"""Result of unpacking a message."""

message: dict
encrytped: bool
authenticated: bool
recipient_kid: str
sender_kid: Optional[str] = None


class DIDCommMessaging:
"""Main entrypoint for DIDComm Messaging."""

def __init__(
self,
crypto: CryptoService,
secrets: SecretsManager,
resolver: DIDResolver,
packaging: PackagingService,
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.
This is a very simple implementation that just returns the first one.
"""
if isinstance(service.service_endpoint, list):
service_endpoint = service.service_endpoint[0]
else:
service_endpoint = service.service_endpoint

return service_endpoint.uri

async def pack(self, 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
)

forward, service = await self.routing.prepare_forward(to, encoded_message)
return PackResult(forward, self.service_to_target(service))

async def unpack(self, encoded_message: bytes, **options) -> UnpackResult:
"""Unpack a message."""
unpacked, metadata = await self.packaging.unpack(encoded_message, **options)
message = json.loads(unpacked.decode())
return UnpackResult(
message,
encrytped=bool(metadata.method),
authenticated=bool(metadata.sender_kid),
recipient_kid=metadata.recip_key.kid,
sender_kid=metadata.sender_kid,
)
18 changes: 13 additions & 5 deletions didcomm_messaging/packaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


from dataclasses import dataclass
from typing import Generic, Literal, Optional, Sequence, Union
from typing import Generic, Literal, Optional, Sequence, Tuple, Union

from pydid import DIDUrl, VerificationMethod
from didcomm_messaging.crypto import P, S, CryptoService, SecretsManager
Expand Down Expand Up @@ -84,12 +84,17 @@ 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]) -> bytes:
async def unpack(
self, enc_message: Union[str, bytes]
) -> Tuple[bytes, PackedMessageMetadata]:
"""Unpack a DIDComm message."""
metadata = await self.extract_packed_message_metadata(enc_message)

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

if not metadata.sender_kid:
raise PackagingServiceError("Missing sender key ID")
Expand All @@ -99,8 +104,11 @@ async def unpack(self, enc_message: Union[str, bytes]) -> bytes:
)
sender_key = self.crypto.verification_method_to_public_key(sender_vm)

return await self.crypto.ecdh_1pu_decrypt(
enc_message, metadata.recip_key, sender_key
return (
await self.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:
Expand Down
54 changes: 54 additions & 0 deletions didcomm_messaging/routing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""RoutingService interface."""

from typing import Tuple
from pydid.service import DIDCommV2Service
from didcomm_messaging.packaging import PackagingService
from didcomm_messaging.resolver import DIDResolver


class RoutingServiceError(Exception):
"""Raised when an error occurs in the RoutingService."""


class RoutingService:
"""RoutingService."""

def __init__(self, packaging: PackagingService, resolver: DIDResolver):
"""Initialize the RoutingService."""
self.packaging = packaging
self.resolver = resolver

async def _resolve_service(self, to: str) -> DIDCommV2Service:
"""Resolve the service endpoint for a given DID."""
doc = await self.resolver.resolve_and_parse(to)
if not doc.service:
raise RoutingServiceError(f"No service endpoint found for {to}")

first_didcomm_service = next(
(
service
for service in doc.service
if isinstance(service, DIDCommV2Service)
),
None,
)
if not first_didcomm_service:
raise RoutingServiceError(f"No DIDCommV2 service endpoint found for {to}")

return first_didcomm_service

async def prepare_forward(
self, to: str, encoded_message: bytes
) -> Tuple[bytes, DIDCommV2Service]:
"""Prepare a forward message, if necessary.
Args:
to (str): The recipient of the message. This will be a DID.
encoded_message (bytes): The encoded message.
Returns:
The encoded message, and the service endpoint to forward to.
"""
service = await self._resolve_service(to)
# TODO Do the stuff
return encoded_message, service

0 comments on commit 7823ed2

Please sign in to comment.