@dwn-protocol/id-sdk
Version:
SDK for accessing the features and capabilities
446 lines • 20.2 kB
JavaScript
;
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CryptoKeyWithJwk = exports.CryptoKeyToJwkMixin = exports.Jose = void 0;
const sha256_1 = require("@noble/hashes/sha256");
const index_js_1 = require("../common/index.js");
const utils_js_1 = require("./utils.js");
const index_js_2 = require("./algorithms-api/index.js");
const index_js_3 = require("./crypto-primitives/index.js");
const joseToWebCryptoMapping = {
'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 = {
'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 = {
'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 = {
'Ed25519:public': 'ed25519-pub',
'Ed25519:private': 'ed25519-priv',
'secp256k1:public': 'secp256k1-pub',
'secp256k1:private': 'secp256k1-priv',
'X25519:public': 'x25519-pub',
'X25519:private': 'x25519-priv',
};
class Jose {
static async cryptoKeyToJwk(options) {
const { algorithm, extractable, material, type, usages } = options.key;
// Translate WebCrypto algorithm to JOSE format.
let jsonWebKey = Jose.webCryptoToJose(algorithm);
// Set extractable parameter.
jsonWebKey.ext = extractable ? 'true' : 'false';
// Set key use parameter.
jsonWebKey.key_ops = usages;
jsonWebKey = await Jose.keyToJwk(Object.assign({ keyMaterial: material, keyType: type }, jsonWebKey));
return Object.assign({}, jsonWebKey);
}
static async cryptoKeyToJwkPair(options) {
const { keyPair } = options;
// Convert public and private keys into JSON Web Key format.
const privateKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.privateKey });
const publicKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.publicKey });
// Assemble as a JWK key pair
const jwkKeyPair = { privateKeyJwk, publicKeyJwk };
return Object.assign({}, jwkKeyPair);
}
static async joseToMulticodec(options) {
const jsonWebKey = options.key;
const params = [];
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 = index_js_1.Multicodec.getCodeFromName({ name });
return { code, name };
}
static joseToWebCrypto(options) {
const params = [];
/**
* 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 Object.assign({}, 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.
*/
static async jwkThumbprint(options) {
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;
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}`);
}
(0, index_js_1.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 = index_js_1.Convert.string(serializedJwk).toUint8Array();
const digest = (0, sha256_1.sha256)(utf8Bytes);
// Encode as Base64Url.
const thumbprint = index_js_1.Convert.uint8Array(digest).toBase64Url();
return thumbprint;
}
static async jwkToCryptoKey(options) {
const jsonWebKey = options.key;
const { keyMaterial, keyType } = await Jose.jwkToKey({ key: jsonWebKey });
// Translate JOSE format to WebCrypto algorithm.
let algorithm = Jose.joseToWebCrypto(jsonWebKey);
// Set extractable parameter.
let extractable;
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;
if ('key_ops' in jsonWebKey && jsonWebKey.key_ops !== undefined) {
keyUsage = jsonWebKey.key_ops;
}
else {
throw new Error(`Conversion from JWK to CryptoKey failed. Required parameter missing: 'key_ops'`);
}
const cryptoKey = new index_js_2.CryptoKey(algorithm, extractable, keyMaterial, keyType, keyUsage);
return cryptoKey;
}
static async jwkToKey(options) {
const jsonWebKey = options.key;
let keyMaterial;
let keyType;
// Asymmetric private key ("EC" or "OKP" - Curve25519 or SECG curves).
if ('d' in jsonWebKey) {
keyMaterial = index_js_1.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 = index_js_1.Convert.base64Url(jsonWebKey.x).toUint8Array();
const y = index_js_1.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 = index_js_1.Convert.base64Url(jsonWebKey.x).toUint8Array();
keyType = 'public';
}
// Symmetric encryption or signature key ("oct" - AES, HMAC)
else if ('k' in jsonWebKey) {
keyMaterial = index_js_1.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.
*
*/
static async jwkToMultibaseId(options) {
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 index_js_3.Secp256k1.convertPublicKey({
publicKey: keyMaterial,
compressedPublicKey: true
});
break;
}
}
}
// Compute the multibase identifier based on the provided key.
const multibaseId = (0, utils_js_1.keyToMultibaseId)({ key: keyMaterial, multicodecName });
return multibaseId;
}
static async keyToJwk(options) {
var _a, _b, _c;
const { keyMaterial, keyType } = options, jsonWebKeyOptions = __rest(options, ["keyMaterial", "keyType"]);
let jsonWebKey = Object.assign({}, jsonWebKeyOptions);
/**
* 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 index_js_3.Ed25519.getPublicKey({ privateKey: keyMaterial })
: keyMaterial;
jsonWebKey.x = index_js_1.Convert.uint8Array(publicKey).toBase64Url();
(_a = jsonWebKey.kty) !== null && _a !== void 0 ? _a : (jsonWebKey.kty = 'OKP');
break;
}
case 'X25519': {
const publicKey = (keyType === 'private')
? await index_js_3.X25519.getPublicKey({ privateKey: keyMaterial })
: keyMaterial;
jsonWebKey.x = index_js_1.Convert.uint8Array(publicKey).toBase64Url();
(_b = jsonWebKey.kty) !== null && _b !== void 0 ? _b : (jsonWebKey.kty = 'OKP');
break;
}
case 'secp256k1': {
const points = await index_js_3.Secp256k1.getCurvePoints({ key: keyMaterial });
jsonWebKey.x = index_js_1.Convert.uint8Array(points.x).toBase64Url();
jsonWebKey.y = index_js_1.Convert.uint8Array(points.y).toBase64Url();
(_c = jsonWebKey.kty) !== null && _c !== void 0 ? _c : (jsonWebKey.kty = 'EC');
break;
}
default: {
throw new Error(`Unsupported key to JWK conversion: ${jsonWebKey.crv}`);
}
}
if (keyType === 'private') {
jsonWebKey = Object.assign({ d: index_js_1.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 = index_js_1.Convert.uint8Array(keyMaterial).toBase64Url();
}
return Object.assign({}, jsonWebKey);
}
static async multicodecToJose(options) {
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) ? index_js_1.Multicodec.getNameFromCode({ code: code }) : name;
const lookupKey = name;
const jose = multicodecToJoseMapping[lookupKey];
if (jose === undefined) {
throw new Error(`Unsupported Multicodec to JOSE conversion: '${options.name}'`);
}
return Object.assign({}, jose);
}
static webCryptoToJose(options) {
const params = [];
/**
* 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 Object.assign({}, jose);
}
static canonicalize(obj) {
const sortedKeys = Object.keys(obj).sort();
const sortedObj = sortedKeys.reduce((acc, key) => {
acc[key] = obj[key];
return acc;
}, {});
return JSON.stringify(sortedObj);
}
}
exports.Jose = Jose;
function CryptoKeyToJwkMixin(Base) {
return class extends Base {
async toJwk() {
const jwk = Jose.cryptoKeyToJwk({ key: this });
return jwk;
}
};
}
exports.CryptoKeyToJwkMixin = CryptoKeyToJwkMixin;
exports.CryptoKeyWithJwk = CryptoKeyToJwkMixin(index_js_2.CryptoKey);
//# sourceMappingURL=jose.js.map