-
Notifications
You must be signed in to change notification settings - Fork 515
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add specific route to create did key #3168
Changes from all commits
29c7a87
006c815
c36974c
aeecdec
17ea779
5c1d5de
3d0c1d0
0b55237
4b32ff8
fbef29d
09eb919
14ca735
c6d74b0
6cd5c34
43643db
022063c
209cde6
2fe35dc
66f0ab1
1f0ea9b
3e9d329
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from .did_key import DIDKey | ||
|
||
|
||
class DidOperationError(Exception): | ||
"""Generic DID operation Error.""" | ||
|
||
|
||
__all__ = [ | ||
"DIDKey", | ||
"DidOperationError", | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
"""DID Management Routes.""" | ||
|
||
from aiohttp import web | ||
from aiohttp_apispec import docs, request_schema, response_schema | ||
from marshmallow.exceptions import ValidationError | ||
|
||
from ..wallet.key_type import ED25519 | ||
from ..admin.decorators.auth import tenant_authentication | ||
from .web_requests import ( | ||
DIDKeyRegistrationRequest, | ||
DIDKeyRegistrationResponse, | ||
DIDKeyBindingRequest, | ||
DIDKeyBindingResponse, | ||
) | ||
from . import DIDKey, DidOperationError | ||
|
||
KEY_MAPPINGS = {"ed25519": ED25519} | ||
|
||
|
||
@docs(tags=["did"], summary="Create DID Key") | ||
@request_schema(DIDKeyRegistrationRequest()) | ||
@response_schema(DIDKeyRegistrationResponse(), 201, description="Create new DID key") | ||
@tenant_authentication | ||
async def create_did_key(request): | ||
"""Request handler for registering a Key DID. | ||
|
||
Args: | ||
request: aiohttp request object | ||
|
||
""" | ||
try: | ||
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) | ||
|
||
|
||
async def register(app: web.Application): | ||
"""Register routes.""" | ||
|
||
app.add_routes( | ||
[ | ||
web.post("/did/key/create", create_did_key), | ||
web.post("/did/key/bind", bind_did_key), | ||
] | ||
) | ||
|
||
|
||
def post_process_routes(app: web.Application): | ||
"""Amend swagger API.""" | ||
# Add top-level tags description | ||
if "tags" not in app._state["swagger_dict"]: | ||
app._state["swagger_dict"]["tags"] = [] | ||
app._state["swagger_dict"]["tags"].append( | ||
{ | ||
"name": "did", | ||
"description": "Endpoints for managing dids", | ||
"externalDocs": { | ||
"description": "Specification", | ||
"url": "https://www.w3.org/TR/did-core/", | ||
}, | ||
} | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from unittest import IsolatedAsyncioTestCase | ||
from ..did_key import DIDKey | ||
from ...core.in_memory import InMemoryProfile | ||
from ...wallet.did_method import DIDMethods | ||
from ...wallet.key_type import ED25519 | ||
|
||
|
||
class TestDIDKeyOperations(IsolatedAsyncioTestCase): | ||
test_seed = "00000000000000000000000000000000" | ||
did = "did:key:z6MkgKA7yrw5kYSiDuQFcye4bMaJpcfHFry3Bx45pdWh3s8i" | ||
kid = "did:key:z6MkgKA7yrw5kYSiDuQFcye4bMaJpcfHFry3Bx45pdWh3s8i#z6MkgKA7yrw5kYSiDuQFcye4bMaJpcfHFry3Bx45pdWh3s8i" | ||
new_kid = "did:web:example.com#key-01" | ||
multikey = "z6MkgKA7yrw5kYSiDuQFcye4bMaJpcfHFry3Bx45pdWh3s8i" | ||
profile = InMemoryProfile.test_profile({}, {DIDMethods: DIDMethods()}) | ||
|
||
async def test_create_ed25519_did_key(self): | ||
results = await DIDKey().create( | ||
key_type=ED25519, profile=self.profile, seed=self.test_seed | ||
) | ||
assert results["did"] == self.did | ||
assert results["multikey"] == self.multikey | ||
assert results["verificationMethod"] == self.kid | ||
|
||
async def test_bind_did_key(self): | ||
results = await DIDKey().create( | ||
key_type=ED25519, profile=self.profile, seed=self.test_seed | ||
) | ||
results = await DIDKey().bind( | ||
profile=self.profile, did=results["did"], kid=self.new_kid | ||
) | ||
assert results["did"] == self.did | ||
assert results["multikey"] == self.multikey | ||
assert results["verificationMethod"] == self.new_kid |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm really not a fan of having to pull up yet another file to find the expected inputs to the handlers. I'd strongly recommend keeping this in the same file as the handlers. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
"""DID routes web requests schemas.""" | ||
|
||
from marshmallow import fields, Schema | ||
|
||
|
||
class DIDKeyRegistrationRequest(Schema): | ||
"""Request schema for creating a dids.""" | ||
|
||
type = fields.Str( | ||
default="ed25519", | ||
required=False, | ||
metadata={ | ||
"description": "Key Type", | ||
"example": "ed25519", | ||
}, | ||
) | ||
|
||
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 DIDKeyBindingResponse(Schema): | ||
"""Response schema for binding a kid to a did.""" | ||
|
||
did = fields.Str() | ||
multikey = fields.Str() | ||
verificationMethod = fields.Str() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. kid in docstring but missing from method parameters |
||
|
||
Returns: | ||
A `DIDInfo` instance representing the created DID | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same |
||
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, | ||
} | ||
Comment on lines
+301
to
+307
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you give some background to this addition? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The askar wallet and in memory wallet both have the function create local did. In the askar wallet, it also inserts a key through that function: If I had a wallet type of askar, I could assign a kid to this keypair, however with the in memory wallet it wouldn't be able to find the key_pair from the verkey when calling the assign_kid function because it only created the did. Both the askar wallet and in memory wallet are classes derived from the BaseWallet, but when creating a did through the AskarWallet, it also creates a keypair as with the in memory wallet it only creates the did. |
||
return DIDInfo( | ||
did=did, | ||
verkey=verkey_enc, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This key "binding" concept feels like a hack. I understand the intent is to enable ACA-Py to sign things with a DID that it doesn't know how to use natively. I think there are better paths to accomplish this but even if we did have a mechanism for creating a key and associating it with a DID, why would we take the intermediate step of representing it as a did:key first?