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