Skip to content

Commit ef221c0

Browse files
(fix) Properly use VM key when signing [SD-]JWT openwallet-foundation#3892
Signed-off-by: George Mulhearn <[email protected]>
1 parent 902e218 commit ef221c0

File tree

2 files changed

+31
-73
lines changed

2 files changed

+31
-73
lines changed

acapy_agent/wallet/jwt.py

Lines changed: 24 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@
22

33
import json
44
import logging
5-
from typing import Any, Mapping, Optional, Tuple
5+
from typing import Any, Mapping, Optional
66

77
from marshmallow import fields
8-
from pydid import DIDUrl, Resource, VerificationMethod
9-
from pydid.verification_method import Ed25519VerificationKey2018, Multikey
108

11-
from acapy_agent.wallet.keys.manager import key_type_from_multikey, multikey_to_verkey
9+
from acapy_agent.wallet.keys.manager import (
10+
MultikeyManager,
11+
key_type_from_multikey,
12+
multikey_to_verkey,
13+
)
1214

1315
from ..core.profile import Profile
14-
from ..messaging.jsonld.error import BadJWSHeaderError, InvalidVerificationMethod
16+
from ..messaging.jsonld.error import BadJWSHeaderError
1517
from ..messaging.models.base import BaseModel, BaseModelSchema
16-
from ..resolver.did_resolver import DIDResolver
1718
from .base import BaseWallet
1819
from .default_verification_key_strategy import BaseVerificationKeyStrategy
19-
from .key_type import ED25519, KeyType
2020
from .util import b64_to_bytes, bytes_to_b64
2121

2222
LOGGER = logging.getLogger(__name__)
@@ -64,19 +64,18 @@ async def jwt_sign(
6464
verification_method = await verkey_strat.get_verification_method_id_for_did(
6565
did, profile
6666
)
67-
else:
68-
# We look up keys by did for now
69-
did = DIDUrl.parse(verification_method).did
70-
if not did:
71-
raise ValueError("DID URL must be absolute")
7267

7368
async with profile.session() as session:
7469
wallet = session.inject(BaseWallet)
75-
did_info = await wallet.get_local_did(did_lookup_name(did))
70+
key_manager = MultikeyManager(session)
71+
key_info = await key_manager.resolve_and_bind_kid(verification_method)
72+
multikey = key_info["multikey"]
73+
key_type = key_type_from_multikey(multikey)
74+
public_key_base58 = multikey_to_verkey(multikey)
7675

77-
header_alg = did_info.key_type.jws_algorithm
76+
header_alg = key_type.jws_algorithm
7877
if not header_alg:
79-
raise ValueError(f"DID key type '{did_info.key_type}' cannot be used for JWS")
78+
raise ValueError(f"DID key type '{key_type}' cannot be used for JWS")
8079

8180
if not headers.get("typ", None):
8281
headers["typ"] = "JWT"
@@ -88,9 +87,9 @@ async def jwt_sign(
8887
encoded_headers = dict_to_b64(headers)
8988
encoded_payload = dict_to_b64(payload)
9089

91-
LOGGER.info(f"jwt sign: {did}")
90+
LOGGER.info(f"jwt sign: {verification_method}")
9291
sig_bytes = await wallet.sign_message(
93-
f"{encoded_headers}.{encoded_payload}".encode(), did_info.verkey
92+
f"{encoded_headers}.{encoded_payload}".encode(), public_key_base58
9493
)
9594

9695
sig = bytes_to_b64(sig_bytes, urlsafe=True, pad=False)
@@ -138,38 +137,6 @@ class Meta:
138137
error = fields.Str(required=False, metadata={"description": "Error text"})
139138

140139

141-
async def resolve_public_key_by_kid_for_verify(
142-
profile: Profile, kid: str
143-
) -> Tuple[str, KeyType]:
144-
"""Resolve public key verkey (base58 public key) and key type from a kid."""
145-
resolver = profile.inject(DIDResolver)
146-
vmethod: Resource = await resolver.dereference(
147-
profile,
148-
kid,
149-
)
150-
151-
if not isinstance(vmethod, VerificationMethod):
152-
raise InvalidVerificationMethod(
153-
"Dereferenced resource is not a verification method"
154-
)
155-
156-
if isinstance(vmethod, Ed25519VerificationKey2018):
157-
verkey = vmethod.public_key_base58
158-
ktyp = ED25519
159-
return (verkey, ktyp)
160-
161-
if isinstance(vmethod, Multikey):
162-
multikey = vmethod.public_key_multibase
163-
verkey = multikey_to_verkey(multikey)
164-
ktyp = key_type_from_multikey(multikey=multikey)
165-
return (verkey, ktyp)
166-
167-
# unsupported
168-
raise InvalidVerificationMethod(
169-
f"Dereferenced method {type(vmethod).__name__} is not supported"
170-
)
171-
172-
173140
async def jwt_verify(profile: Profile, jwt: str) -> JWTVerifyResult:
174141
"""Verify a JWT and return the headers and payload."""
175142
encoded_headers, encoded_payload, encoded_signature = jwt.split(".", 3)
@@ -189,15 +156,19 @@ async def jwt_verify(profile: Profile, jwt: str) -> JWTVerifyResult:
189156
decoded_signature = b64_to_bytes(encoded_signature, urlsafe=True)
190157

191158
async with profile.session() as session:
192-
(verkey, ktyp) = await resolve_public_key_by_kid_for_verify(
193-
profile, verification_method
159+
key_manager = MultikeyManager(session)
160+
multikey = await key_manager.resolve_multikey_from_verification_method_id(
161+
verification_method
194162
)
163+
key_type = key_type_from_multikey(multikey)
164+
public_key_base58 = multikey_to_verkey(multikey)
165+
195166
wallet = session.inject(BaseWallet)
196167
valid = await wallet.verify_message(
197168
f"{encoded_headers}.{encoded_payload}".encode(),
198169
decoded_signature,
199-
from_verkey=verkey,
200-
key_type=ktyp,
170+
from_verkey=public_key_base58,
171+
key_type=key_type,
201172
)
202173

203174
return JWTVerifyResult(headers, payload, valid, verification_method)

acapy_agent/wallet/tests/test_jwt.py

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
import pytest
55

6+
from acapy_agent.resolver.default.key import KeyDIDResolver
7+
68
from ...resolver.did_resolver import DIDResolver
79
from ...resolver.tests.test_did_resolver import MockResolver
810
from ...utils.testing import create_test_profile
@@ -13,7 +15,7 @@
1315
BaseVerificationKeyStrategy,
1416
DefaultVerificationKeyStrategy,
1517
)
16-
from ..jwt import jwt_sign, jwt_verify, resolve_public_key_by_kid_for_verify
18+
from ..jwt import jwt_sign, jwt_verify
1719

1820

1921
class TestJWT(IsolatedAsyncioTestCase):
@@ -92,6 +94,9 @@ async def asyncSetUp(self):
9294
BaseVerificationKeyStrategy, DefaultVerificationKeyStrategy()
9395
)
9496
self.profile.context.injector.bind_instance(KeyTypes, KeyTypes())
97+
self.profile.context.injector.bind_instance(
98+
DIDResolver, DIDResolver([KeyDIDResolver()])
99+
)
95100

96101
async def setUpTestingDid(self, key_type: KeyType) -> Tuple[str, str]:
97102
async with self.profile.session() as session:
@@ -164,7 +169,7 @@ async def test_sign_x_invalid_verification_method(self):
164169
verification_method = "did:key:zzzzgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL"
165170
with pytest.raises(Exception) as e_info:
166171
await jwt_sign(self.profile, headers, payload, did, verification_method)
167-
assert "Unknown DID" in str(e_info)
172+
assert "DIDNotFound" in str(e_info)
168173

169174
async def test_verify_x_invalid_signed(self):
170175
for key_type in [ED25519, P256]:
@@ -182,21 +187,3 @@ async def test_verify_x_invalid_signed(self):
182187

183188
with pytest.raises(Exception):
184189
await jwt_verify(self.profile, signed)
185-
186-
async def test_resolve_public_key_by_kid_for_verify_ed25519(self):
187-
(_, kid) = await self.setUpTestingDid(ED25519)
188-
(key_bs58, key_type) = await resolve_public_key_by_kid_for_verify(
189-
self.profile, kid
190-
)
191-
192-
assert key_bs58 == "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx"
193-
assert key_type == ED25519
194-
195-
async def test_resolve_public_key_by_kid_for_verify_p256(self):
196-
(_, kid) = await self.setUpTestingDid(P256)
197-
(key_bs58, key_type) = await resolve_public_key_by_kid_for_verify(
198-
self.profile, kid
199-
)
200-
201-
assert key_bs58 == "tYbR5egjfja9D5ix1jjYGqfh5QPu73RcZ7UjQUXtargj"
202-
assert key_type == P256

0 commit comments

Comments
 (0)