diff --git a/aries_cloudagent/did/did_key.py b/aries_cloudagent/did/did_key.py index a871bf8423..76e08cbc64 100644 --- a/aries_cloudagent/did/did_key.py +++ b/aries_cloudagent/did/did_key.py @@ -29,11 +29,14 @@ def __init__(self, public_key: bytes = None, key_type: KeyType = None) -> None: self._key_type = key_type @classmethod - async def register(self, key_type: KeyType, profile: Profile): - """Register a new key DID. + async def create( + self, key_type: KeyType, profile: Profile, kid: str = None, seed: str = None + ): + """Create a new key DID. Args: key_type: The key type to use for the DID + kid: An optional verification method to associate with the DID profile: The profile to use for storing the DID keypair Returns: @@ -45,8 +48,40 @@ async def register(self, key_type: KeyType, profile: Profile): """ async with profile.session() as session: wallet = session.inject(BaseWallet) - info = await wallet.create_local_did(method=KEY, key_type=key_type) - return info.did + did_info = await wallet.create_local_did( + method=KEY, key_type=key_type, seed=seed + ) + kid = kid if kid else f"{did_info.did}#" + did_info.did.split(":")[-1] + await wallet.assign_kid_to_key(verkey=did_info.verkey, kid=kid) + await wallet.get_key_by_kid(kid=kid) + return { + "did": did_info.did, + "verificationMethod": kid, + "multikey": did_info.did.split(":")[-1], + } + + @classmethod + async def bind(self, profile: Profile, did: str, kid: str): + """Create a new key DID. + + Args: + key_type: The key type to use for the DID + kid: An optional verification method to associate with the DID + profile: The profile to use for storing the DID keypair + + Returns: + A string representing the created DID + + Raises: + DidOperationError: If the an error occures during did registration + + """ + async with profile.session() as session: + wallet = session.inject(BaseWallet) + did_info = await wallet.get_local_did(did=did) + await wallet.assign_kid_to_key(verkey=did_info.verkey, kid=kid) + await wallet.get_key_by_kid(kid=kid) + return {"did": did, "verificationMethod": kid, "multikey": did.split(":")[-1]} @classmethod def from_public_key(cls, public_key: bytes, key_type: KeyType) -> "DIDKey": diff --git a/aries_cloudagent/did/routes.py b/aries_cloudagent/did/routes.py index e7c6f7588a..d43a1115b9 100644 --- a/aries_cloudagent/did/routes.py +++ b/aries_cloudagent/did/routes.py @@ -10,29 +10,63 @@ from .web_requests import ( DIDKeyRegistrationRequest, DIDKeyRegistrationResponse, + DIDKeyBindingRequest, + DIDKeyBindingResponse, ) from . import DIDKey, DidOperationError KEY_MAPPINGS = {"ed25519": ED25519} -@docs(tags=["did"], summary="Register Key DID") +@docs(tags=["did"], summary="Create DID Key") @request_schema(DIDKeyRegistrationRequest()) -@response_schema(DIDKeyRegistrationResponse(), 201, description="Register new DID key") +@response_schema(DIDKeyRegistrationResponse(), 201, description="Create new DID key") @tenant_authentication -async def register_did_key(request: web.BaseRequest): +async def create_did_key(request): """Request handler for registering a Key DID. Args: request: aiohttp request object """ - body = await request.json() - context: AdminRequestContext = request["context"] try: - key_type = body["key_type"] - did_doc = await DIDKey().register(KEY_MAPPINGS[key_type], context.profile) - return web.json_response({"didDocument": did_doc}, status=201) + return web.json_response( + await DIDKey().create( + profile=request["context"].profile, + key_type=KEY_MAPPINGS[ + request["data"]["type"] if "type" in request["data"] else "ed25519" + ], + kid=request["data"]["kid"] if "kid" in request["data"] else None, + seed=request["data"]["seed"] if "seed" in request["data"] else None, + ), + status=201, + ) + except (KeyError, ValidationError, DidOperationError) as err: + return web.json_response({"message": str(err)}, status=400) + + +@docs(tags=["did"], summary="Bind DID Key") +@request_schema(DIDKeyBindingRequest()) +@response_schema( + DIDKeyBindingResponse(), 201, description="Bind existing DID key to new KID" +) +@tenant_authentication +async def bind_did_key(request): + """Request handler for binding a Key DID. + + Args: + request: aiohttp request object + + """ + try: + return web.json_response( + await DIDKey().bind( + profile=request["context"].profile, + did=request["data"]["did"], + kid=request["data"]["kid"], + ), + status=200, + ) except (KeyError, ValidationError, DidOperationError) as err: return web.json_response({"message": str(err)}, status=400) @@ -42,7 +76,8 @@ async def register(app: web.Application): app.add_routes( [ - web.post("/did/key", register_did_key), + web.post("/did/key/create", create_did_key), + web.post("/did/key/bind", bind_did_key), ] ) diff --git a/aries_cloudagent/did/web_requests.py b/aries_cloudagent/did/web_requests.py index 9240ed06b8..a4be22e43c 100644 --- a/aries_cloudagent/did/web_requests.py +++ b/aries_cloudagent/did/web_requests.py @@ -1,13 +1,12 @@ """DID routes web requests schemas.""" -from marshmallow import fields -from ..messaging.models.openapi import OpenAPISchema +from marshmallow import fields, Schema -class DIDKeyRegistrationRequest(OpenAPISchema): - """Request schema for registering key dids.""" +class DIDKeyRegistrationRequest(Schema): + """Request schema for creating a dids.""" - key_type = fields.Str( + type = fields.Str( default="ed25519", required=False, metadata={ @@ -16,8 +15,58 @@ class DIDKeyRegistrationRequest(OpenAPISchema): }, ) + seed = fields.Str( + default=None, + required=False, + metadata={ + "description": "Seed", + "example": "00000000000000000000000000000000", + }, + ) + + kid = fields.Str( + default=None, + required=False, + metadata={ + "description": "Verification Method", + "example": "did:web:example.com#key-01", + }, + ) + + +class DIDKeyRegistrationResponse(Schema): + """Response schema for creating a did.""" + + did = fields.Str() + multikey = fields.Str() + verificationMethod = fields.Str() + + +class DIDKeyBindingRequest(Schema): + """Request schema for binding a kid to a did.""" + + did = fields.Str( + default=None, + required=True, + metadata={ + "description": "DID", + "example": "did:key:z6MkgKA7yrw5kYSiDuQFcye4bMaJpcfHFry3Bx45pdWh3s8i", + }, + ) + + kid = fields.Str( + default=None, + required=True, + metadata={ + "description": "Verification Method", + "example": "did:web:example.com#key-02", + }, + ) + -class DIDKeyRegistrationResponse(OpenAPISchema): - """Response schema for registering web dids.""" +class DIDKeyBindingResponse(Schema): + """Response schema for binding a kid to a did.""" - did_document = fields.Dict() + did = fields.Str() + multikey = fields.Str() + verificationMethod = fields.Str() diff --git a/aries_cloudagent/wallet/askar.py b/aries_cloudagent/wallet/askar.py index b885843b3b..942f5b1f2a 100644 --- a/aries_cloudagent/wallet/askar.py +++ b/aries_cloudagent/wallet/askar.py @@ -236,6 +236,7 @@ async def create_local_did( seed: Optional seed to use for DID did: The DID to use metadata: Metadata to store with DID + kid: Optional key identifier Returns: A `DIDInfo` instance representing the created DID diff --git a/aries_cloudagent/wallet/in_memory.py b/aries_cloudagent/wallet/in_memory.py index bc6f40bc97..5eefe504c0 100644 --- a/aries_cloudagent/wallet/in_memory.py +++ b/aries_cloudagent/wallet/in_memory.py @@ -263,6 +263,7 @@ async def create_local_did( key_type: The key type to use for the DID seed: Optional seed to use for DID did: The DID to use + kid: Optional kid to assign to the DID metadata: Metadata to store with DID Returns: @@ -297,6 +298,13 @@ async def create_local_did( "key_type": key_type, "method": method, } + self.profile.keys[verkey_enc] = { + "seed": seed, + "secret": secret, + "verkey": verkey_enc, + "metadata": metadata.copy() if metadata else {}, + "key_type": key_type, + } return DIDInfo( did=did, verkey=verkey_enc,