Skip to content

Commit

Permalink
jwt signing
Browse files Browse the repository at this point in the history
Signed-off-by: George Mulhearn <[email protected]>
  • Loading branch information
gmulhearn-anonyome committed Jan 14, 2025
1 parent a9862d2 commit 460c6b0
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 32 deletions.
26 changes: 26 additions & 0 deletions acapy_agent/did/did_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
BLS12381G1G2,
BLS12381G2,
ED25519,
P256,
X25519,
KeyType,
KeyTypes,
Expand Down Expand Up @@ -275,6 +276,30 @@ def construct_did_key_ed25519(did_key: "DIDKey") -> dict:

return did_doc

def construct_did_key_p256(did_key: "DIDKey") -> dict:
"""Construct P256 did:key.
Args:
did_key (DIDKey): did key instance to parse p256 did:key document from
Returns:
dict: The p256 did:key did document
"""

did_doc = construct_did_signature_key_base(
id=did_key.did,
key_id=did_key.key_id,
verification_method={
"id": did_key.key_id,
"type": "Multikey",
"controller": did_key.did,
"publicKeyMultibase": did_key.fingerprint,
},
)

return did_doc


def construct_did_signature_key_base(*, id: str, key_id: str, verification_method: dict):
"""Create base did key structure to use for most signature keys.
Expand All @@ -298,6 +323,7 @@ def construct_did_signature_key_base(*, id: str, key_id: str, verification_metho
DID_KEY_RESOLVERS = {
ED25519: construct_did_key_ed25519,
X25519: construct_did_key_x25519,
P256: construct_did_key_p256,
BLS12381G2: construct_did_key_bls12381g2,
BLS12381G1: construct_did_key_bls12381g1,
BLS12381G1G2: construct_did_key_bls12381g1g2,
Expand Down
3 changes: 1 addition & 2 deletions acapy_agent/messaging/jsonld/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
from .credential import sign_credential, verify_credential
from .error import BaseJSONLDMessagingError

SUPPORTED_VERIFICATION_METHOD_TYPES = (Ed25519VerificationKey2018,)

SUPPORTED_VERIFICATION_METHOD_TYPES = (Ed25519VerificationKey2018)

class SignatureOptionsSchema(Schema):
"""Schema for LD signature options."""
Expand Down
53 changes: 36 additions & 17 deletions acapy_agent/wallet/jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import json
import logging
from typing import Any, Mapping, Optional
from typing import Any, Mapping, Optional, Tuple

from marshmallow import fields
from pydid import DIDUrl, Resource, VerificationMethod
from pydid.verification_method import Ed25519VerificationKey2018, Multikey

from acapy_agent.wallet.keys.manager import key_type_from_multikey, multikey_to_verkey

from ..core.profile import Profile
from ..messaging.jsonld.error import BadJWSHeaderError, InvalidVerificationMethod
Expand All @@ -14,7 +17,7 @@
from ..resolver.did_resolver import DIDResolver
from .base import BaseWallet
from .default_verification_key_strategy import BaseVerificationKeyStrategy
from .key_type import ED25519
from .key_type import ED25519, KeyType, KeyTypes
from .util import b64_to_bytes, bytes_to_b64

LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -66,12 +69,20 @@ async def jwt_sign(
did = DIDUrl.parse(verification_method).did
if not did:
raise ValueError("DID URL must be absolute")

async with profile.session() as session:
wallet = session.inject(BaseWallet)
did_info = await wallet.get_local_did(did_lookup_name(did))

header_alg = did_info.key_type.jws_algorithm
if not header_alg:
raise ValueError("DID key type cannot be used for JWS")

if not headers.get("typ", None):
headers["typ"] = "JWT"
headers = {
**headers,
"alg": "EdDSA",
"alg": header_alg,
"kid": verification_method,
}
encoded_headers = dict_to_b64(headers)
Expand Down Expand Up @@ -130,7 +141,7 @@ class Meta:
error = fields.Str(required=False, metadata={"description": "Error text"})


async def resolve_public_key_by_kid_for_verify(profile: Profile, kid: str) -> str:
async def resolve_public_key_by_kid_for_verify(profile: Profile, kid: str) -> Tuple[str, KeyType]:
"""Resolve public key material from a kid."""
resolver = profile.inject(DIDResolver)
vmethod: Resource = await resolver.dereference(
Expand All @@ -142,34 +153,42 @@ async def resolve_public_key_by_kid_for_verify(profile: Profile, kid: str) -> st
raise InvalidVerificationMethod(
"Dereferenced resource is not a verification method"
)

if not isinstance(vmethod, SUPPORTED_VERIFICATION_METHOD_TYPES):
raise InvalidVerificationMethod(
f"Dereferenced method {type(vmethod).__name__} is not supported"
)

return vmethod.material


if isinstance(vmethod, Ed25519VerificationKey2018):
verkey = vmethod.public_key_base58
ktyp = ED25519
return (verkey, ktyp)

if isinstance(vmethod, Multikey):
multikey = vmethod.public_key_multibase
verkey = multikey_to_verkey(multikey)
ktyp = key_type_from_multikey(multikey=multikey)
return (verkey, ktyp)

# unsupported
raise InvalidVerificationMethod(
f"Dereferenced method {type(vmethod).__name__} is not supported"
)

async def jwt_verify(profile: Profile, jwt: str) -> JWTVerifyResult:
"""Verify a JWT and return the headers and payload."""
encoded_headers, encoded_payload, encoded_signature = jwt.split(".", 3)
headers = b64_to_dict(encoded_headers)
if "alg" not in headers or headers["alg"] != "EdDSA" or "kid" not in headers:
raise BadJWSHeaderError("Invalid JWS header parameters for Ed25519Signature2018.")
if "alg" not in headers or (headers["alg"] != "EdDSA" and headers["alg"] != "ES256") or "kid" not in headers:
raise BadJWSHeaderError("Invalid JWS header parameters")

payload = b64_to_dict(encoded_payload)
verification_method = headers["kid"]
decoded_signature = b64_to_bytes(encoded_signature, urlsafe=True)

async with profile.session() as session:
verkey = await resolve_public_key_by_kid_for_verify(profile, verification_method)
(verkey, ktyp) = await resolve_public_key_by_kid_for_verify(profile, verification_method)
wallet = session.inject(BaseWallet)
valid = await wallet.verify_message(
f"{encoded_headers}.{encoded_payload}".encode(),
decoded_signature,
verkey,
ED25519,
from_verkey=verkey,
key_type=ktyp,
)

return JWTVerifyResult(headers, payload, valid, verification_method)
22 changes: 14 additions & 8 deletions acapy_agent/wallet/key_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
class KeyType:
"""Key Type class."""

def __init__(self, key_type: str, multicodec_name: str, multicodec_prefix: bytes):
def __init__(self, key_type: str, multicodec_name: str, multicodec_prefix: bytes, jws_alg: Optional[str]):
"""Construct key type."""
self._type: str = key_type
self._name: str = multicodec_name
self._prefix: bytes = multicodec_prefix
self._jws_alg: bytes = jws_alg

@property
def key_type(self) -> str:
Expand All @@ -26,18 +27,23 @@ def multicodec_name(self) -> str:
def multicodec_prefix(self) -> bytes:
"""Get key type multicodec prefix."""
return self._prefix

@property
def jws_algorithm(self) -> Optional[str]:
"""Get key type JWS Algorithm (used in the JOSE header)"""
return self._jws_alg



# NOTE: the py_multicodec library is outdated. We use hardcoded prefixes here
# until this PR gets released: https://github.com/multiformats/py-multicodec/pull/14
# multicodec is also not used now, but may be used again if py_multicodec is updated
ED25519: KeyType = KeyType("ed25519", "ed25519-pub", b"\xed\x01")
X25519: KeyType = KeyType("x25519", "x25519-pub", b"\xec\x01")
P256: KeyType = KeyType("p256", "p256-pub", b"\x80\x24")
BLS12381G1: KeyType = KeyType("bls12381g1", "bls12_381-g1-pub", b"\xea\x01")
BLS12381G2: KeyType = KeyType("bls12381g2", "bls12_381-g2-pub", b"\xeb\x01")
BLS12381G1G2: KeyType = KeyType("bls12381g1g2", "bls12_381-g1g2-pub", b"\xee\x01")

ED25519: KeyType = KeyType("ed25519", "ed25519-pub", b"\xed\x01", "EdDSA")
X25519: KeyType = KeyType("x25519", "x25519-pub", b"\xec\x01", None)
P256: KeyType = KeyType("p256", "p256-pub", b"\x80\x24", "ES256")
BLS12381G1: KeyType = KeyType("bls12381g1", "bls12_381-g1-pub", b"\xea\x01", None)
BLS12381G2: KeyType = KeyType("bls12381g2", "bls12_381-g2-pub", b"\xeb\x01", None)
BLS12381G1G2: KeyType = KeyType("bls12381g1g2", "bls12_381-g1g2-pub", b"\xee\x01", None)

class KeyTypes:
"""KeyType class specifying key types with multicodec name."""
Expand Down
10 changes: 5 additions & 5 deletions acapy_agent/wallet/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ class DIDListQueryStringSchema(OpenAPISchema):
)
key_type = fields.Str(
required=False,
validate=validate.OneOf([ED25519.key_type, BLS12381G2.key_type]),
validate=validate.OneOf([ED25519.key_type, BLS12381G2.key_type, P256.key_type]),
metadata={"example": ED25519.key_type, "description": "Key type to query for."},
)

Expand Down Expand Up @@ -1076,7 +1076,7 @@ async def wallet_set_did_endpoint(request: web.BaseRequest):
return web.json_response({"txn": transaction.serialize()})


@docs(tags=["wallet"], summary="Create a EdDSA jws using did keys with a given payload")
@docs(tags=["wallet"], summary="Create a jws using did keys with a given payload")
@request_schema(JWSCreateSchema)
@response_schema(WalletModuleResponseSchema(), description="")
@tenant_authentication
Expand Down Expand Up @@ -1115,7 +1115,7 @@ async def wallet_jwt_sign(request: web.BaseRequest):


@docs(
tags=["wallet"], summary="Create a EdDSA sd-jws using did keys with a given payload"
tags=["wallet"], summary="Create an sd-jws using did keys with a given payload"
)
@request_schema(SDJWSCreateSchema)
@response_schema(WalletModuleResponseSchema(), description="")
Expand Down Expand Up @@ -1158,7 +1158,7 @@ async def wallet_sd_jwt_sign(request: web.BaseRequest):
return web.json_response(sd_jws)


@docs(tags=["wallet"], summary="Verify a EdDSA jws using did keys with a given JWS")
@docs(tags=["wallet"], summary="Verify a jws using did keys with a given JWS")
@request_schema(JWSVerifySchema())
@response_schema(JWSVerifyResponseSchema(), 200, description="")
@tenant_authentication
Expand Down Expand Up @@ -1200,7 +1200,7 @@ async def wallet_jwt_verify(request: web.BaseRequest):

@docs(
tags=["wallet"],
summary="Verify a EdDSA sd-jws using did keys with a given SD-JWS with "
summary="Verify an sd-jws using did keys with a given SD-JWS with "
"optional key binding",
)
@request_schema(SDJWSVerifySchema())
Expand Down

0 comments on commit 460c6b0

Please sign in to comment.