Skip to content

Commit e2f55f2

Browse files
committed
key loader: did: jwk: Ditch multibase did keys
Signed-off-by: John Andersen <[email protected]>
1 parent 0c722b8 commit e2f55f2

7 files changed

+61
-255
lines changed

environment.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,3 @@ dependencies:
4040
- PyJWT==2.8.0
4141
- werkzeug==2.2.2
4242
- cwt==2.7.1
43-
- py-multibase==1.0.3
44-
- py-multicodec==0.2.1

scitt_emulator/create_statement.py

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright (c) SCITT Authors
22
# Licensed under the MIT License.
3+
import base64
34
import pathlib
45
import argparse
56
from typing import Union, Optional
@@ -12,17 +13,10 @@
1213
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
1314
from cryptography.hazmat.primitives.serialization import load_pem_private_key
1415

15-
# NOTE These are unmaintained but the
16-
# https://github.com/hashberg-io/multiformats stuff and base58 modules don't
17-
# produce the same results:
18-
# https://grotto-networking.com/blog/posts/DID_Key.html#bug-in-multibase-library
19-
import multibase
20-
import multicodec
21-
2216
# TODO jwcrypto is LGPLv3, is there another option with a permissive licence?
2317
import jwcrypto.jwk
2418

25-
from scitt_emulator.did_helpers import DID_KEY_METHOD, MULTICODEC_HEX_P384_PUBLIC_KEY
19+
from scitt_emulator.did_helpers import DID_JWK_METHOD
2620

2721

2822
@pycose.headers.CoseHeaderAttribute.register_attribute()
@@ -102,22 +96,9 @@ def create_claim(
10296
cwt_cose_key_to_cose_key = cwt_cose_key.to_dict()
10397
sign1_message_key = pycose.keys.ec2.EC2Key.from_dict(cwt_cose_key_to_cose_key)
10498

105-
# If issuer was not given used did:key of public key
99+
# If issuer was not given used did:jwk of public key
106100
if issuer is None:
107-
multicodec_prefix_p_384 = "p384-pub"
108-
multicodec.constants.NAME_TABLE[multicodec_prefix_p_384] = MULTICODEC_HEX_P384_PUBLIC_KEY
109-
issuer = (
110-
DID_KEY_METHOD
111-
+ multibase.encode(
112-
"base58btc",
113-
multicodec.add_prefix(
114-
multicodec_prefix_p_384,
115-
load_pem_private_key(key_as_pem_bytes, password=None)
116-
.public_key()
117-
.public_bytes(Encoding.X962, PublicFormat.CompressedPoint),
118-
),
119-
).decode()
120-
)
101+
issuer = DID_JWK_METHOD + base64.urlsafe_b64encode(key.export_public().encode()).decode()
121102

122103
# CWT_Claims (label: 14 pending [CWT_CLAIM_COSE]): A CWT representing
123104
# the Issuer (iss) making the statement, and the Subject (sub) to

scitt_emulator/did_helpers.py

Lines changed: 3 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import os
2-
import sys
3-
import inspect
42
import urllib.parse
5-
from typing import Optional, Callable, Dict, Tuple, Union
3+
from typing import Optional
64

7-
import multibase
8-
import multicodec
9-
import cryptography.hazmat.primitives.asymmetric.ec
5+
6+
DID_JWK_METHOD = "did:jwk:"
107

118

129
def did_web_to_url(
@@ -22,172 +19,3 @@ def did_web_to_url(
2219
*[urllib.parse.unquote(i) for i in did_web_string.split(":")[2:]],
2320
]
2421
)
25-
26-
27-
class DIDKeyInvalidPublicKeyLengthError(ValueError):
28-
"""
29-
If the byte length of rawPublicKeyBytes does not match the expected public
30-
key length for the associated multicodecValue, an invalidPublicKeyLength
31-
error MUST be raised.
32-
"""
33-
34-
35-
class DIDKeyDecoderNotFoundError(NotImplementedError):
36-
"""
37-
Raised when we don't have a function implemented to decode the given key
38-
"""
39-
40-
41-
class DIDKeyDecoderError(Exception):
42-
"""
43-
Raised when we failed to decode a key from a did:key DID method
44-
"""
45-
46-
47-
class DIDKeyInvalidPublicKeyError(DIDKeyDecoderError):
48-
"""
49-
Raised when the raw bytes of a key are invalid during decode
50-
"""
51-
52-
53-
DID_KEY_METHOD = "did:key:"
54-
55-
56-
def did_key_decode_public_key(multibase_value: str) -> Tuple[bytes, bytes]:
57-
# 3.1.2.3
58-
# Decode multibaseValue using the base58-btc multibase alphabet and set
59-
# multicodecValue to the multicodec header for the decoded value.
60-
multibase_value_decoded = multibase.decode(multibase_value)
61-
# Implementers are cautioned to ensure that the multicodecValue is set to
62-
# the result after performing varint decoding.
63-
multicodec_value = multicodec.extract_prefix(multibase_value_decoded)
64-
# Set the rawPublicKeyBytes to the bytes remaining after the multicodec
65-
# header.
66-
raw_public_key_bytes = multicodec.remove_prefix(multibase_value_decoded)
67-
# Return multicodecValue and rawPublicKeyBytes as the decodedPublicKey.
68-
return multicodec_value, raw_public_key_bytes
69-
70-
71-
class _MULTICODEC_VALUE_NOT_FOUND_IN_TABLE:
72-
pass
73-
74-
75-
MULTICODEC_VALUE_NOT_FOUND_IN_TABLE = _MULTICODEC_VALUE_NOT_FOUND_IN_TABLE()
76-
77-
# Multicodec hexadecimal value, public key, byte length, Description
78-
MULTICODEC_HEX_SECP256K1_PUBLIC_KEY = 0xE7
79-
MULTICODEC_HEX_X25519_PUBLIC_KEY = 0xEC
80-
MULTICODEC_HEX_ED25519_PUBLIC_KEY = 0xED
81-
MULTICODEC_HEX_P256_PUBLIC_KEY = 0x1200
82-
MULTICODEC_HEX_P384_PUBLIC_KEY = 0x1201
83-
MULTICODEC_HEX_P521_PUBLIC_KEY = 0x1202
84-
MULTICODEC_HEX_RSA_PUBLIC_KEY = 0x1205
85-
86-
MULTICODEC_VALUE_TABLE = {
87-
MULTICODEC_HEX_SECP256K1_PUBLIC_KEY: 33, # secp256k1-pub - Secp256k1 public key (compressed)
88-
MULTICODEC_HEX_X25519_PUBLIC_KEY: 32, # x25519-pub - Curve25519 public key
89-
MULTICODEC_HEX_ED25519_PUBLIC_KEY: 32, # ed25519-pub - Ed25519 public key
90-
MULTICODEC_HEX_P256_PUBLIC_KEY: 33, # p256-pub - P-256 public key (compressed)
91-
MULTICODEC_HEX_P384_PUBLIC_KEY: 49, # p384-pub - P-384 public key (compressed)
92-
MULTICODEC_HEX_P521_PUBLIC_KEY: None, # p521-pub - P-521 public key (compressed)
93-
MULTICODEC_HEX_RSA_PUBLIC_KEY: None, # rsa-pub - RSA public key. DER-encoded ASN.1 type RSAPublicKey according to IETF RFC 8017 (PKCS #1)
94-
}
95-
96-
97-
def did_key_signature_method_creation(
98-
multibase_value: hex,
99-
raw_public_key_bytes: bytes,
100-
) -> Union[cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey]:
101-
# 3.1.2 https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm
102-
# Initialize verificationMethod to an empty object.
103-
verification_method = {}
104-
105-
# Set multicodecValue and rawPublicKeyBytes to the result of passing
106-
# multibaseValue and options to § 3.1.3 Decode Public Key Algorithm.
107-
# Ensure the proper key length of rawPublicKeyBytes based on the
108-
# multicodecValue table
109-
public_key_length_MUST_be = MULTICODEC_VALUE_TABLE.get(
110-
multibase_value, MULTICODEC_VALUE_NOT_FOUND_IN_TABLE
111-
)
112-
if public_key_length_MUST_be is MULTICODEC_VALUE_NOT_FOUND_IN_TABLE:
113-
raise DIDKeyDecoderNotFoundError(
114-
f"multibase_value {multibase_value!r} not in MULTICODEC_VALUE_NOT_FOUND_IN_TABLE {MULTICODEC_VALUE_NOT_FOUND_IN_TABLE!r}"
115-
)
116-
117-
# If the byte length of rawPublicKeyBytes does not match the expected public
118-
# key length for the associated multicodecValue, an invalidPublicKeyLength
119-
# error MUST be raised.
120-
if public_key_length_MUST_be is not None and public_key_length_MUST_be != len(
121-
raw_public_key_bytes
122-
):
123-
raise DIDKeyInvalidPublicKeyLengthError(
124-
f"public_key_length_MUST_be: {public_key_length_MUST_be } != len(raw_public_key_bytes): {len(raw_public_key_bytes)}"
125-
)
126-
127-
# Ensure the rawPublicKeyBytes are a proper encoding of the public key type
128-
# as specified by the multicodecValue. This validation is often done by a
129-
# cryptographic library when importing the public key by, for example,
130-
# ensuring that an Elliptic Curve public key is a specific coordinate that
131-
# exists on the elliptic curve. If an invalid public key value is detected,
132-
# an invalidPublicKey error MUST be raised.
133-
#
134-
# SPEC ISSUE: Request for feedback on implementability: It is not clear if
135-
# this particular check is implementable across all public key types. The
136-
# group is accepting feedback on the implementability of this particular
137-
# feature.
138-
try:
139-
if multibase_value in (
140-
MULTICODEC_HEX_P256_PUBLIC_KEY,
141-
MULTICODEC_HEX_P384_PUBLIC_KEY,
142-
MULTICODEC_HEX_P521_PUBLIC_KEY,
143-
):
144-
public_key = cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.from_encoded_point(
145-
cryptography.hazmat.primitives.asymmetric.ec.SECP384R1(),
146-
raw_public_key_bytes,
147-
)
148-
else:
149-
raise DIDKeyDecoderNotFoundError(
150-
f"No importer for multibase_value {multibase_value!r}"
151-
)
152-
except Exception as e:
153-
raise DIDKeyInvalidPublicKeyError(
154-
f"invalid raw_public_key_bytes: {raw_public_key_bytes!r}"
155-
) from e
156-
157-
return public_key
158-
159-
160-
def did_key_to_cryptography_key(
161-
did_key: str,
162-
) -> Union[cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey]:
163-
"""
164-
References
165-
166-
- https://w3c-ccg.github.io/did-method-key/#p-384
167-
- RFC7515: JSON Web Key (JWK): https://www.rfc-editor.org/rfc/rfc7517
168-
- RFC8037: CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JSON Object Signing and Encryption (JOSE): https://www.rfc-editor.org/rfc/rfc8037
169-
170-
Examples
171-
172-
- P-384: https://github.com/w3c-ccg/did-method-key/blob/f5abee840c31e92cd1ac11737e0b62103ab99d21/test-vectors/nist-curves.json#L112-L166
173-
174-
>>> did_key_to_cryptography_key("did:key:invalid")
175-
Traceback (most recent call last):
176-
DIDKeyDecoderNotFoundError: ...
177-
>>> public_key = did_key_to_cryptography_key("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9")
178-
>>> public_key.__class__
179-
<class 'cryptography.hazmat.backends.openssl.ec._EllipticCurvePublicKey'>
180-
"""
181-
try:
182-
multibase_value, raw_public_key_bytes = did_key_decode_public_key(
183-
did_key.replace(DID_KEY_METHOD, "", 1)
184-
)
185-
except Exception as e:
186-
raise DIDKeyDecoderNotFoundError(did_key) from e
187-
188-
try:
189-
return did_key_signature_method_creation(multibase_value, raw_public_key_bytes)
190-
except Exception as e:
191-
raise DIDKeyDecoderError(did_key) from e
192-
193-
raise DIDKeyDecoderNotFoundError(did_key)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import base64
2+
from typing import List, Tuple
3+
4+
import cwt
5+
import cwt.algs.ec2
6+
import pycose
7+
import pycose.keys.ec2
8+
import cryptography.hazmat.primitives.asymmetric.ec
9+
from cryptography.hazmat.primitives import serialization
10+
11+
import jwcrypto.jwk
12+
13+
from scitt_emulator.did_helpers import DID_JWK_METHOD
14+
from scitt_emulator.key_helper_dataclasses import VerificationKey
15+
16+
17+
CONTENT_TYPE = "application/did+jwk"
18+
19+
20+
def key_loader_format_did_jwk(
21+
unverified_issuer: str,
22+
) -> List[VerificationKey]:
23+
if not unverified_issuer.startswith(DID_JWK_METHOD):
24+
return []
25+
key = jwcrypto.jwk.JWK.from_json(
26+
base64.urlsafe_b64decode(unverified_issuer[len(DID_JWK_METHOD):]).decode()
27+
)
28+
return [
29+
VerificationKey(
30+
transforms=[key],
31+
original=key,
32+
original_content_type=CONTENT_TYPE,
33+
original_bytes=unverified_issuer.encode("utf-8"),
34+
original_bytes_encoding="utf-8",
35+
usable=False,
36+
cwt=None,
37+
cose=None,
38+
)
39+
]

scitt_emulator/key_loader_format_did_key.py

Lines changed: 0 additions & 51 deletions
This file was deleted.

scitt_emulator/key_loader_format_url_referencing_ssh_authorized_keys.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,16 @@ def key_loader_format_url_referencing_ssh_authorized_keys(
5252
)
5353

5454
return keys
55+
56+
57+
def transform_key_instance_cryptography_ecc_public_to_jwcrypto_jwk(
58+
key: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey,
59+
) -> jwcrypto.jwk.JWK:
60+
if not isinstance(key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey):
61+
raise TypeError(key)
62+
return jwcrypto.jwk.JWK.from_pem(
63+
key.public_bytes(
64+
encoding=serialization.Encoding.PEM,
65+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
66+
)
67+
)

setup.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
'scitt-emulator=scitt_emulator.cli:main'
1313
],
1414
'scitt_emulator.verify_signature.key_loaders': [
15-
'did_key=scitt_emulator.key_loader_format_did_key:key_loader_format_did_key',
15+
'did_jwk=scitt_emulator.key_loader_format_did_jwk:key_loader_format_did_jwk',
1616
'url_referencing_oidc_issuer=scitt_emulator.key_loader_format_url_referencing_oidc_issuer:key_loader_format_url_referencing_oidc_issuer',
1717
'url_referencing_ssh_authorized_keys=scitt_emulator.key_loader_format_url_referencing_ssh_authorized_keys:key_loader_format_url_referencing_ssh_authorized_keys',
1818
],
1919
'scitt_emulator.key_helpers.transforms_key_instances': [
2020
'transform_key_instance_cwt_cose_ec2_to_pycose_ec2=scitt_emulator.key_transforms:transform_key_instance_cwt_cose_ec2_to_pycose_ec2',
21-
'transform_key_instance_cryptography_ecc_public_to_jwcrypto_jwk=scitt_emulator:key_loader_format_did_key.transform_key_instance_cryptography_ecc_public_to_jwcrypto_jwk',
2221
'transform_key_instance_jwcrypto_jwk_to_cwt_cose=scitt_emulator.key_loader_format_url_referencing_oidc_issuer:transform_key_instance_jwcrypto_jwk_to_cwt_cose',
22+
'transform_key_instance_cryptography_ecc_public_to_jwcrypto_jwk=scitt_emulator:key_loader_format_url_referencing_ssh_authorized_keys.transform_key_instance_cryptography_ecc_public_to_jwcrypto_jwk',
2323
],
2424
'scitt_emulator.key_helpers.verification_key_to_object': [
2525
'to_object_oidc_issuer=scitt_emulator.key_loader_format_url_referencing_oidc_issuer:to_object_oidc_issuer',
@@ -30,8 +30,6 @@
3030
"cryptography",
3131
"cbor2",
3232
"cwt",
33-
"py-multicodec",
34-
"py-multibase",
3533
"jwcrypto",
3634
"pycose",
3735
"httpx",

0 commit comments

Comments
 (0)