@dwn-protocol/id-sdk
Version:
SDK for accessing the features and capabilities
948 lines (819 loc) • 30.1 kB
text/typescript
import { sha256 } from '@noble/hashes/sha256';
import { Convert, Multicodec, MulticodecCode, MulticodecDefinition, removeUndefinedProperties } from '../common/index.js';
import type { IDCrypto } from './types/iddwn-crypto.js';
import { keyToMultibaseId } from './utils.js';
import { CryptoKey } from './algorithms-api/index.js';
import { Ed25519, Secp256k1, X25519 } from './crypto-primitives/index.js';
/**
* JSON Web Key Operations
*
* decrypt : Decrypt content and validate decryption, if applicable
* deriveBits : Derive bits not to be used as a key
* deriveKey : Derive key
* encrypt : Encrypt content
* sign : Compute digital signature or MAC
* unwrapKey : Decrypt key and validate decryption, if applicable
* verify : Verify digital signature or MAC
* wrapKey : Encrypt key
*/
export type JwkOperation = IDCrypto.KeyUsage[] | string[];
/**
* JSON Web Key Use
*
* sig : Digital Signature or MAC
* enc : Encryption
*/
export type JwkUse = 'sig' | 'enc' | string;
/**
* JSON Web Key Types
*/
export type JwkType =
/**
* Elliptic Curve
* Used with Elliptic Curve Digital Signature Algorithm (ECDSA) and Elliptic
* Curve Diffie-Hellman (ECDH), including secp256k1, P-256, P-384, and P-521.
*/
| 'EC'
/**
* RSA
* Widely used for encryption and digital signatures. RSA keys are used in
* various algorithms like RS256, RS384, RS512, etc.
*/
| 'RSA'
/**
* Octet sequence
* Used with symmetric signing (e.g., HMAC HS256, HS512, etc.) and
* symmetric encryption (e.g., A256CBC-HS512, A256GCM, etc.) algorithms.
*/
| 'oct'
/**
* Octet string key pairs (OKP)
* A type of public key that is used with algorithms such as EdDSA (Ed25519 and
* Ed448 curves) and ECDH (X25519 and X448 curves).
*/
| 'OKP'
/**
* JSON Web Key Elliptic Curve
*/
export type JwkNamedCurves =
// P-256 Curve
| 'P-256'
// P-384 Curve
| 'P-384'
// P-521 Curve
| 'P-521'
// Ed25519 signature algorithm key pairs
| 'Ed25519'
// Ed448 signature algorithm key pairs
| 'Ed448'
// X25519 function key pairs
| 'X25519'
// X448 function key pairs
| 'X448'
// SECG secp256k1 curve
| 'secp256k1';
/**
* JSON Web Key Parameters
*/
// Used with any "kty" (key type) value.
export type JwkParamsAnyKeyType = {
// The algorithm intended for use with the key
alg?: string;
// Extractable
ext?: 'true' | 'false';
// Key Operations
key_ops?: JwkOperation;
// Key ID
kid?: string;
// Key Type
kty: JwkType;
// Public Key Use
use?: JwkUse;
// X.509 Certificate Chain
x5c?: string;
// X.509 Certificate SHA-1 Thumbprint
x5t?: string;
// X.509 Certificate SHA-256 Thumbprint
'x5t#S256'?: string;
// X.509 URL
x5u?: string;
}
// Used with "EC" (elliptic curve) public keys.
export type JwkParamsEcPublic = Omit<JwkParamsAnyKeyType, 'alg' | 'kty'> & {
/**
* The algorithm intended for use with the key.
* ES256 : ECDSA using P-256 and SHA-256
* ES256K : ECDSA using secp256k1 curve and SHA-256
* ES384 : ECDSA using P-384 and SHA-384
* ES512 : ECDSA using P-521 and SHA-512
*/
alg?: 'ES256' | 'ES256K' | 'ES384' | 'ES512';
/**
* Elliptic Curve key pair.
*/
kty: 'EC';
/**
* The cryptographic curve used with the key.
* MUST be present for all EC public keys.
*/
crv: 'secp256k1' | 'P-256' | 'P-384' | 'P-521';
/**
* The x-coordinate for the Elliptic Curve point.
* Represented as the base64url encoding of the octet string
* representation of the coordinate.
* MUST be present for all EC public keys
*/
x: string;
/**
* The y-coordinate for the Elliptic Curve point.
* Represented as the base64url encoding of the octet string
* representation of the coordinate.
* MUST be present only for secp256k1 public keys.
*/
y?: string;
}
// Used with "EC" (elliptic curve) private keys.
export type JwkParamsEcPrivate = JwkParamsEcPublic & {
/**
* The d-coordinate for the Elliptic Curve point.
* Represented as the base64url encoding of the octet string
* representation of the coordinate.
* MUST be present for all EC private keys.
*/
d: string;
}
// Used with "OKP" (octet key pair) public keys.
export type JwkParamsOkpPublic =
Omit<JwkParamsAnyKeyType, 'kty' | 'alg' | 'crv'> &
Pick<JwkParamsEcPublic, 'x'> & {
/**
* The algorithm intended for use with the key.
* EdDSA: Edwards Curve Digital Signature Algorithm
*/
alg?: 'EdDSA';
/**
* The cryptographic curve used with the key.
* MUST be present for all OKP public keys.
*/
crv: 'Ed25519' | 'Ed448' | 'X25519' | 'X448';
/**
* Key type
* OKP (Octet Key Pair) is defined for public key algorithms that use octet
* strings as private and public keys.
*/
kty: 'OKP';
}
// Used with "OKP" (octet key pair) private keys.
export type JwkParamsOkpPrivate = JwkParamsOkpPublic & {
/**
* The d-coordinate for the Edwards Curve point.
* Represented as the base64url encoding of the octet string
* representation of the coordinate.
* MUST be present for all EC private keys.
*/
d: string;
};
// Used with "oct" (octet sequence) private keys.
export type JwkParamsOctPrivate = Omit<JwkParamsAnyKeyType, 'alg' | 'kty'> & {
/**
* The algorithm intended for use with the key.
* Used with symmetric signing (e.g., HMAC HS256, etc.) and
* symmetric encryption (e.g., A256GCM, etc.) algorithms.
*/
alg?:
// AES CBC using 128-bit key
| 'A128CBC'
// AES CBC using 192-bit key
| 'A192CBC'
// AES CBC using 256-bit key
| 'A256CBC'
// AES CTR using 128-bit key
| 'A128CTR'
// AES CTR using 192-bit key
| 'A192CTR'
// AES CTR using 256-bit key
| 'A256CTR'
// AES GCM using a 128-bit key
| 'A128GCM'
// AES GCM using a 192-bit key
| 'A192GCM'
// AES GCM using a 256-bit key
| 'A256GCM'
// HMAC using SHA-256
| 'HS256'
// HMAC using SHA-384
| 'HS384'
// HMAC using SHA-512
| 'HS512'
/**
* The "k" (key value) parameter contains the value of the symmetric
* (or other single-valued) key. It is represented as the base64url
* encoding of the octet sequence containing the key value.
*/
k: string;
/**
* Key type
* oct (Octet Sequence) is defined for symmetric encryption and
* symmetric signature algorithms.
*/
kty: 'oct';
}
// Used with "RSA" public keys.
export type JwkParamsRsaPublic = Omit<JwkParamsAnyKeyType, 'kty'> & {
// Public exponent for RSA
e: string;
/**
* Key type
* RSA is widely used for encryption and digital signatures.
*/
kty: 'RSA';
// Modulus for RSA
n: string;
};
// Used with "RSA" private keys.
export type JwkParamsRsaPrivate = JwkParamsRsaPublic & {
// Private exponent for RSA
d: string;
// First prime factor for RSA
p?: string;
// Second prime factor for RSA
q?: string;
// First factor's CRT exponent for RSA
dp?: string;
// Second factor's CRT exponent for RSA
dq?: string;
// First CRT coefficient for RSA
qi?: string;
// Other primes information (optional in RFC 7518)
oth?: {
r: string;
d: string;
t: string;
}[];
};
export type PublicKeyJwk = JwkParamsEcPublic | JwkParamsOkpPublic | JwkParamsRsaPublic;
export type PrivateKeyJwk = JwkParamsEcPrivate | JwkParamsOkpPrivate | JwkParamsOctPrivate | JwkParamsRsaPrivate;
export type JwkKeyPair = {
publicKeyJwk: PublicKeyJwk;
privateKeyJwk: PrivateKeyJwk;
}
export type JsonWebKey = PrivateKeyJwk | PublicKeyJwk;
export interface JoseHeaderParams {
// Content Type
cty?: string;
// JWK Set URL
jku?: string;
// JSON Web Key
jwk?: PublicKeyJwk;
// Key ID
kid?: string;
// Type
typ?: string;
// X.509 Certificate Chain
x5c?: string[];
// X.509 Certificate SHA-1 Thumbprint
x5t?: string;
// X.509 URL
x5u?: string;
}
export interface JwsHeaderParams extends JoseHeaderParams {
alg:
// Edwards curve digital signature algorithm (e.g., Ed25519)
| 'EdDSA'
// ECDSA using P-256 and SHA-256
| 'ES256'
// ECDSA using secp256k1 curve and SHA-256
| 'ES256K'
// ECDSA using P-384 and SHA-384
| 'ES384'
// ECDSA using P-521 and SHA-512
| 'ES512'
// HMAC using SHA-256
| 'HS256'
// HMAC using SHA-384
| 'HS384'
// HMAC using SHA-512
| 'HS512';
// Indicates that extensions to JOSE RFCs are being used
// that MUST be understood and processed.
crit?: string[]
// Additional Public or Private Header Parameter names.
[key: string]: unknown
}
export interface JweHeaderParams extends JoseHeaderParams {
alg:
// AES Key Wrap with default initial value using 128-bit key
| 'A128KW'
// AES Key Wrap with default initial value using 192-bit key
| 'A192KW'
// AES Key Wrap with default initial value using 256-bit key
| 'A256KW'
// Direct use of a shared symmetric key as the CEK
| 'dir'
// Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF
| 'ECDH-ES'
// ECDH-ES using Concat KDF and CEK wrapped with "A128KW"
| 'ECDH-ES+A128KW'
// ECDH-ES using Concat KDF and CEK wrapped with "A192KW"
| 'ECDH-ES+A192KW'
// ECDH-ES using Concat KDF and CEK wrapped with "A256KW"
| 'ECDH-ES+A256KW'
// Key wrapping with AES GCM using 128-bit key
| 'A128GCMKW'
// Key wrapping with AES GCM using 192-bit key
| 'A192GCMKW'
// Key wrapping with AES GCM using 256-bit key
| 'A256GCMKW'
// PBES2 with HMAC SHA-256 and "A128KW" wrapping
| 'PBES2-HS256+A128KW'
// PBES2 with HMAC SHA-384 and "A192KW" wrapping
| 'PBES2-HS384+A192KW'
// PBES2 with HMAC SHA-512 and "A256KW" wrapping
| 'PBES2-HS512+A256KW'
// PBES2 with HMAC SHA-512 and "XC20PKW" wrapping
| 'PBES2-HS512+XC20PKW';
apu?: Uint8Array;
apv?: Uint8Array;
// Indicates that extensions to JOSE RFCs are being used
// that MUST be understood and processed.
crit?: string[]
/**
* Cryptographic Algorithms for Content Encryption
* JWE uses cryptographic algorithms to encrypt and integrity-protect the
* plaintext and to integrity-protect the Additional Authenticated Data.
*/
enc:
// AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm,
// as defined in RFC 7518, Section 5.2.3
| 'A128CBC-HS256'
// AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm,
// as defined in RFC 7518, Section 5.2.4
| 'A192CBC-HS384'
// AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm,
// as defined in RFC 7518, Section 5.2.5
| 'A256CBC-HS512'
// AES GCM using 128-bit key
| 'A128GCM'
// AES GCM using 192-bit key
| 'A192GCM'
// AES GCM using 256-bit key
| 'A256GCM'
// XChaCha20-Poly1305 authenticated encryption algorithm
| 'XC20P';
epk?: Uint8Array;
iv?: Uint8Array;
p2c?: number;
p2s?: string;
// Additional Public or Private Header Parameter names.
[key: string]: unknown
}
const joseToWebCryptoMapping: { [key: string]: IDCrypto.GenerateKeyOptions } = {
'Ed25519' : { name: 'EdDSA', namedCurve: 'Ed25519' },
'Ed448' : { name: 'EdDSA', namedCurve: 'Ed448' },
'X25519' : { name: 'ECDH', namedCurve: 'X25519' },
'secp256k1:ES256K' : { name: 'ECDSA', namedCurve: 'secp256k1' },
'secp256k1' : { name: 'ECDH', namedCurve: 'secp256k1' },
'P-256' : { name: 'ECDSA', namedCurve: 'P-256' },
'P-384' : { name: 'ECDSA', namedCurve: 'P-384' },
'P-521' : { name: 'ECDSA', namedCurve: 'P-521' },
'A128CBC' : { name: 'AES-CBC', length: 128 },
'A192CBC' : { name: 'AES-CBC', length: 192 },
'A256CBC' : { name: 'AES-CBC', length: 256 },
'A128CTR' : { name: 'AES-CTR', length: 128 },
'A192CTR' : { name: 'AES-CTR', length: 192 },
'A256CTR' : { name: 'AES-CTR', length: 256 },
'A128GCM' : { name: 'AES-GCM', length: 128 },
'A192GCM' : { name: 'AES-GCM', length: 192 },
'A256GCM' : { name: 'AES-GCM', length: 256 },
'HS256' : { name: 'HMAC', hash: { name: 'SHA-256' } },
'HS384' : { name: 'HMAC', hash: { name: 'SHA-384' } },
'HS512' : { name: 'HMAC', hash: { name: 'SHA-512' } },
};
const webCryptoToJoseMapping: { [key: string]: Partial<JsonWebKey> } = {
'EdDSA:Ed25519' : { alg: 'EdDSA', crv: 'Ed25519', kty: 'OKP' },
'EdDSA:Ed448' : { alg: 'EdDSA', crv: 'Ed448', kty: 'OKP' },
'ECDH:X25519' : { crv: 'X25519', kty: 'OKP' },
'ECDSA:secp256k1' : { alg: 'ES256K', crv: 'secp256k1', kty: 'EC' },
'ECDH:secp256k1' : { crv: 'secp256k1', kty: 'EC' },
'ECDSA:P-256' : { alg: 'ES256', crv: 'P-256', kty: 'EC' },
'ECDSA:P-384' : { alg: 'ES384', crv: 'P-384', kty: 'EC' },
'ECDSA:P-521' : { alg: 'ES512', crv: 'P-521', kty: 'EC' },
'AES-CBC:128' : { alg: 'A128CBC', kty: 'oct' },
'AES-CBC:192' : { alg: 'A192CBC', kty: 'oct' },
'AES-CBC:256' : { alg: 'A256CBC', kty: 'oct' },
'AES-CTR:128' : { alg: 'A128CTR', kty: 'oct' },
'AES-CTR:192' : { alg: 'A192CTR', kty: 'oct' },
'AES-CTR:256' : { alg: 'A256CTR', kty: 'oct' },
'AES-GCM:128' : { alg: 'A128GCM', kty: 'oct' },
'AES-GCM:192' : { alg: 'A192GCM', kty: 'oct' },
'AES-GCM:256' : { alg: 'A256GCM', kty: 'oct' },
'HMAC:SHA-256' : { alg: 'HS256', kty: 'oct' },
'HMAC:SHA-384' : { alg: 'HS384', kty: 'oct' },
'HMAC:SHA-512' : { alg: 'HS512', kty: 'oct' },
};
const multicodecToJoseMapping: { [key: string]: JsonWebKey } = {
'ed25519-pub' : { alg: 'EdDSA', crv: 'Ed25519', kty: 'OKP', x: '' },
'ed25519-priv' : { alg: 'EdDSA', crv: 'Ed25519', kty: 'OKP', x: '', d: '' },
'secp256k1-pub' : { alg: 'ES256K', crv: 'secp256k1', kty: 'EC', x: '', y: ''},
'secp256k1-priv' : { alg: 'ES256K', crv: 'secp256k1', kty: 'EC', x: '', y: '', d: '' },
'x25519-pub' : { crv: 'X25519', kty: 'OKP', x: '' },
'x25519-priv' : { crv: 'X25519', kty: 'OKP', x: '', d: '' },
};
const joseToMulticodecMapping: { [key: string]: string } = {
'Ed25519:public' : 'ed25519-pub',
'Ed25519:private' : 'ed25519-priv',
'secp256k1:public' : 'secp256k1-pub',
'secp256k1:private' : 'secp256k1-priv',
'X25519:public' : 'x25519-pub',
'X25519:private' : 'x25519-priv',
};
export class Jose {
public static async cryptoKeyToJwk(options: {
key: IDCrypto.CryptoKey,
}): Promise<JsonWebKey> {
const { algorithm, extractable, material, type, usages } = options.key;
// Translate WebCrypto algorithm to JOSE format.
let jsonWebKey = Jose.webCryptoToJose(algorithm) as JsonWebKey;
// Set extractable parameter.
jsonWebKey.ext = extractable ? 'true' : 'false';
// Set key use parameter.
jsonWebKey.key_ops = usages;
jsonWebKey = await Jose.keyToJwk({
keyMaterial : material,
keyType : type,
...jsonWebKey
});
return { ...jsonWebKey };
}
public static async cryptoKeyToJwkPair(options: {
keyPair: IDCrypto.CryptoKeyPair,
}): Promise<JwkKeyPair> {
const { keyPair } = options;
// Convert public and private keys into JSON Web Key format.
const privateKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.privateKey }) as PrivateKeyJwk;
const publicKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.publicKey }) as PublicKeyJwk;
// Assemble as a JWK key pair
const jwkKeyPair: JwkKeyPair = { privateKeyJwk, publicKeyJwk };
return { ...jwkKeyPair };
}
public static async joseToMulticodec(options: {
key: JsonWebKey
}): Promise<MulticodecDefinition<MulticodecCode>> {
const jsonWebKey = options.key;
const params: string[] = [];
if ('crv' in jsonWebKey) {
params.push(jsonWebKey.crv);
if ('d' in jsonWebKey) {
params.push('private');
} else {
params.push('public');
}
}
const lookupKey = params.join(':');
const name = joseToMulticodecMapping[lookupKey];
if (name === undefined) {
throw new Error(`Unsupported JOSE to Multicodec conversion: '${lookupKey}'`);
}
const code = Multicodec.getCodeFromName({ name });
return { code, name };
}
public static joseToWebCrypto(options:
Partial<JsonWebKey>
): IDCrypto.GenerateKeyOptions {
const params: string[] = [];
/**
* All Elliptic Curve (EC) and Octet Key Pair (OKP) JSON Web Keys
* set a value for the "crv" parameter.
*/
if ('crv' in options && options.crv) {
params.push(options.crv);
// Special case for secp256k1. If alg is "ES256K", then ECDSA. Else ECDH.
if (options.crv === 'secp256k1' && options.alg === 'ES256K') {
params.push(options.alg);
}
/**
* All Octet Sequence (oct) JSON Web Keys omit "crv" and
* set a value for the "alg" parameter.
*/
} else if (options.alg !== undefined) {
params.push(options.alg);
} else {
throw new TypeError(`One or more parameters missing: 'alg' or 'crv'`);
}
const lookupKey = params.join(':');
const webCrypto = joseToWebCryptoMapping[lookupKey];
if (webCrypto === undefined) {
throw new Error(`Unsupported JOSE to WebCrypto conversion: '${lookupKey}'`);
}
return { ...webCrypto };
}
/**
* Computes the thumbprint of a JSON Web Key (JWK) using the method
* specified in RFC 7638. This function accepts RSA, EC, OKP, and oct keys
* and returns the thumbprint as a base64url encoded SHA-256 hash of the
* JWK's required members, serialized and sorted lexicographically.
*
* Purpose:
* - Uniquely Identifying Keys: The thumbprint allows the unique
* identification of a specific JWK within a set of JWKs. It provides a
* deterministic way to generate a value that can be used as a key
* identifier (kid) or to match a specific key.
*
* - Simplifying Key Management: In systems where multiple keys are used,
* managing and identifying individual keys can become complex. The
* thumbprint method simplifies this by creating a standardized, unique
* identifier for each key.
*
* - Enabling Interoperability: By standardizing the method to compute a
* thumbprint, different systems can compute the same thumbprint value for
* a given JWK. This enables interoperability among systems that use JWKs.
*
* - Secure Comparison: The thumbprint provides a way to securely compare
* JWKs to determine if they are equivalent.
*
* @param jwk - The JSON Web Key for which the thumbprint will be computed.
* This must be an RSA, EC, OKP, or oct key.
* @returns The thumbprint as a base64url encoded string.
* @throws {Error} Throws an error if the provided key type is unsupported.
*
* @example
* const jwk: PublicKeyJwk = {
* 'kty': 'EC',
* 'crv': 'secp256k1',
* 'x': '61iPYuGefxotzBdQZtDvv6cWHZmXrTTscY-u7Y2pFZc',
* 'y': '88nPCVLfrAY9i-wg5ORcwVbHWC_tbeAd1JE2e0co0lU'
* };
*
* const thumbprint = jwkThumbprint(jwk);
* console.log(`JWK thumbprint: ${thumbprint}`);
*
* @see {@link https://datatracker.ietf.org/doc/html/rfc7638 | RFC7638} for
* the specification of JWK thumbprint computation.
*/
public static async jwkThumbprint(options: {
key: JsonWebKey
}): Promise<string> {
const { key } = options;
/** Step 1 - Normalization: The JWK is normalized to include only specific
* members and in lexicographic order.
*/
const keyType = key.kty;
let normalizedJwk: Partial<JsonWebKey>;
if (keyType === 'EC') {
normalizedJwk = { crv: key.crv, kty: key.kty, x: key.x, y: key.y };
} else if (keyType === 'oct') {
normalizedJwk = { k: key.k, kty: key.kty };
} else if (keyType === 'OKP') {
normalizedJwk = { crv: key.crv, kty: key.kty, x: key.x };
} else if (keyType === 'RSA') {
normalizedJwk = { e: key.e, kty: key.kty, n: key.n };
} else {
throw new Error(`Unsupported key type: ${keyType}`);
}
removeUndefinedProperties(normalizedJwk);
/** Step 2 - Serialization: The normalized JWK is serialized to a UTF-8
* representation of its JSON encoding. */
const serializedJwk = Jose.canonicalize(normalizedJwk);
/** Step 3 - Digest Calculation: A cryptographic hash function
* (SHA-256 is recommended) is applied to the serialized JWK,
* resulting in the thumbprint. */
const utf8Bytes = Convert.string(serializedJwk).toUint8Array();
const digest = sha256(utf8Bytes);
// Encode as Base64Url.
const thumbprint = Convert.uint8Array(digest).toBase64Url();
return thumbprint;
}
public static async jwkToCryptoKey(options: {
key: JsonWebKey
}): Promise<IDCrypto.CryptoKey> {
const jsonWebKey = options.key;
const { keyMaterial, keyType } = await Jose.jwkToKey({ key: jsonWebKey });
// Translate JOSE format to WebCrypto algorithm.
let algorithm = Jose.joseToWebCrypto(jsonWebKey) as IDCrypto.GenerateKeyOptions;
// Set extractable parameter.
let extractable: boolean;
if ('ext' in jsonWebKey && jsonWebKey.ext !== undefined) {
extractable = jsonWebKey.ext === 'true' ? true : false;
} else {
throw new Error(`Conversion from JWK to CryptoKey failed. Required parameter missing: 'ext'`);
}
// Set key use parameter.
let keyUsage: IDCrypto.KeyUsage[];
if ('key_ops' in jsonWebKey && jsonWebKey.key_ops !== undefined) {
keyUsage = jsonWebKey.key_ops as IDCrypto.KeyUsage[];
} else {
throw new Error(`Conversion from JWK to CryptoKey failed. Required parameter missing: 'key_ops'`);
}
const cryptoKey = new CryptoKey(
algorithm,
extractable,
keyMaterial,
keyType,
keyUsage
);
return cryptoKey;
}
public static async jwkToKey(options: {
key: JsonWebKey
}): Promise<{ keyMaterial: Uint8Array, keyType: IDCrypto.KeyType }> {
const jsonWebKey = options.key;
let keyMaterial: Uint8Array;
let keyType: IDCrypto.KeyType;
// Asymmetric private key ("EC" or "OKP" - Curve25519 or SECG curves).
if ('d' in jsonWebKey) {
keyMaterial = Convert.base64Url(jsonWebKey.d).toUint8Array();
keyType = 'private';
}
// Asymmetric public key ("EC" - secp256k1, secp256r1, secp384r1, secp521r1).
else if ('y' in jsonWebKey && jsonWebKey.y) {
const prefix = new Uint8Array([0x04]); // Designates an uncompressed key.
const x = Convert.base64Url(jsonWebKey.x).toUint8Array();
const y = Convert.base64Url(jsonWebKey.y).toUint8Array();
const publicKey = new Uint8Array([...prefix, ...x, ...y]);
keyMaterial = publicKey;
keyType = 'public';
}
// Asymmetric public key ("OKP" - Ed25519, X25519).
else if ('x' in jsonWebKey) {
keyMaterial = Convert.base64Url(jsonWebKey.x).toUint8Array();
keyType = 'public';
}
// Symmetric encryption or signature key ("oct" - AES, HMAC)
else if ('k' in jsonWebKey) {
keyMaterial = Convert.base64Url(jsonWebKey.k).toUint8Array();
keyType = 'private';
}
else {
throw new Error('Jose: Unknown JSON Web Key format.');
}
return { keyMaterial, keyType };
}
/**
* Note: All secp public keys are converted to compressed point encoding
* before the multibase identifier is computed.
*
* Per {@link https://github.com/multiformats/multicodec/blob/master/table.csv | Multicodec table}:
* public keys for Elliptic Curve cryptography algorithms (e.g., secp256k1,
* secp256k1r1, secp384r1, etc.) are always represented with compressed point
* encoding (e.g., secp256k1-pub, p256-pub, p384-pub, etc.).
*
* Per {@link https://datatracker.ietf.org/doc/html/rfc8812#name-jose-and-cose-secp256k1-cur | RFC 8812}:
* "As a compressed point encoding representation is not defined for JWK
* elliptic curve points, the uncompressed point encoding defined there
* MUST be used. The x and y values represented MUST both be exactly
* 256 bits, with any leading zeros preserved.
*
*/
public static async jwkToMultibaseId(options: {
key: JsonWebKey
}): Promise<string> {
const jsonWebKey = options.key;
// Convert the algorithm into Multicodec name format.
const { name: multicodecName } = await Jose.joseToMulticodec({ key: jsonWebKey });
// Decode the key as a raw binary data from the JWK.
let { keyMaterial } = await Jose.jwkToKey({ key: jsonWebKey });
// Convert secp256k1 public keys to compressed format.
if ('crv' in jsonWebKey && !('d' in jsonWebKey)) {
switch (jsonWebKey.crv) {
case 'secp256k1': {
keyMaterial = await Secp256k1.convertPublicKey({
publicKey : keyMaterial,
compressedPublicKey : true
});
break;
}
}
}
// Compute the multibase identifier based on the provided key.
const multibaseId = keyToMultibaseId({ key: keyMaterial, multicodecName });
return multibaseId;
}
public static async keyToJwk(options:
Partial<JsonWebKey> & {
keyMaterial: Uint8Array,
keyType: IDCrypto.KeyType,
}): Promise<JsonWebKey> {
const { keyMaterial, keyType, ...jsonWebKeyOptions } = options;
let jsonWebKey = { ...jsonWebKeyOptions } as JsonWebKey;
/**
* All Elliptic Curve (EC) and Octet Key Pair (OKP) keys
* specify a "crv" (named curve) parameter.
*/
if ('crv' in jsonWebKey) {
switch (jsonWebKey.crv) {
case 'Ed25519': {
const publicKey = (keyType === 'private')
? await Ed25519.getPublicKey({ privateKey: keyMaterial })
: keyMaterial;
jsonWebKey.x = Convert.uint8Array(publicKey).toBase64Url();
jsonWebKey.kty ??= 'OKP';
break;
}
case 'X25519': {
const publicKey = (keyType === 'private')
? await X25519.getPublicKey({ privateKey: keyMaterial })
: keyMaterial;
jsonWebKey.x = Convert.uint8Array(publicKey).toBase64Url();
jsonWebKey.kty ??= 'OKP';
break;
}
case 'secp256k1': {
const points = await Secp256k1.getCurvePoints({ key: keyMaterial });
jsonWebKey.x = Convert.uint8Array(points.x).toBase64Url();
jsonWebKey.y = Convert.uint8Array(points.y).toBase64Url();
jsonWebKey.kty ??= 'EC';
break;
}
default: {
throw new Error(`Unsupported key to JWK conversion: ${jsonWebKey.crv}`);
}
}
if (keyType === 'private') {
jsonWebKey = {
d: Convert.uint8Array(keyMaterial).toBase64Url(),
...jsonWebKey
};
}
}
/**
* All Octet Sequence (oct) symmetric encryption and signature keys
* specify only an "alg" parameter.
*/
if (!('crv' in jsonWebKey) && jsonWebKey.kty === 'oct') {
jsonWebKey.k = Convert.uint8Array(keyMaterial).toBase64Url();
}
return { ...jsonWebKey };
}
public static async multicodecToJose(options: {
code?: MulticodecCode,
name?: string
}): Promise<JsonWebKey> {
let { code, name } = options;
// Either code or name must be specified, but not both.
if (!(name ? !code : code)) {
throw new Error(`Either 'name' or 'code' must be defined, but not both.`);
}
// If name is undefined, lookup by code.
name = (name === undefined ) ? Multicodec.getNameFromCode({ code: code! }) : name;
const lookupKey: any = name;
const jose = multicodecToJoseMapping[lookupKey];
if (jose === undefined) {
throw new Error(`Unsupported Multicodec to JOSE conversion: '${options.name}'`);
}
return { ...jose };
}
public static webCryptoToJose(options:
IDCrypto.Algorithm | IDCrypto.GenerateKeyOptions
): Partial<JsonWebKey> {
const params: string[] = [];
/**
* All WebCrypto algorithms have the "named" parameter.
*/
params.push(options.name);
/**
* All Elliptic Curve (EC) WebCrypto algorithms
* set a value for the "namedCurve" parameter.
*/
if ('namedCurve' in options) {
params.push(options.namedCurve);
/**
* All symmetric encryption (AES) WebCrypto algorithms
* set a value for the "length" parameter.
*/
} else if ('length' in options && options.length !== undefined) {
params.push(options.length.toString());
/**
* All symmetric signature (HMAC) WebCrypto algorithms
* set a value for the "hash" parameter.
*/
} else if ('hash' in options) {
params.push(options.hash.name);
} else {
throw new TypeError(`One or more parameters missing: 'name', 'namedCurve', 'length', or 'hash'`);
}
const lookupKey = params.join(':');
const jose = webCryptoToJoseMapping[lookupKey];
if (jose === undefined) {
throw new Error(`Unsupported WebCrypto to JOSE conversion: '${lookupKey}'`);
}
return { ...jose };
}
private static canonicalize(obj: { [key: string]: any }): string {
const sortedKeys = Object.keys(obj).sort();
const sortedObj = sortedKeys.reduce<{ [key: string]: any }>((acc, key) => {
acc[key] = obj[key];
return acc;
}, {});
return JSON.stringify(sortedObj);
}
}
type Constructable = new (...args: any[]) => object;
export function CryptoKeyToJwkMixin<T extends Constructable>(Base: T) {
return class extends Base {
public async toJwk(): Promise<JsonWebKey> {
const jwk = Jose.cryptoKeyToJwk({ key: (this as unknown) as CryptoKey });
return jwk;
}
};
}
export const CryptoKeyWithJwk = CryptoKeyToJwkMixin(CryptoKey);