diff --git a/packages/crypto/src/utils.ts b/packages/crypto/src/utils.ts index 3dce127de..a51c8f2c0 100644 --- a/packages/crypto/src/utils.ts +++ b/packages/crypto/src/utils.ts @@ -1,3 +1,5 @@ +import type { Jwk } from './jose/jwk.js'; + import { crypto } from '@noble/hashes/crypto'; import { randomBytes as nobleRandomBytes } from '@noble/hashes/utils'; @@ -63,6 +65,58 @@ export function checkValidProperty(params: { } } +/** + * Determines the JOSE algorithm identifier of the digital signature algorithm based on the `alg` or + * `crv` property of a {@link Jwk | JWK}. + * + * If the `alg` property is present, its value takes precedence and is returned. Otherwise, the + * `crv` property is used to determine the algorithm. + * + * @see {@link https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms | JOSE Algorithms} + * @see {@link https://datatracker.ietf.org/doc/draft-ietf-jose-fully-specified-algorithms/ | Fully-Specified Algorithms for JOSE and COSE} + * + * @example + * ```ts + * const publicKey: Jwk = { + * "kty": "OKP", + * "crv": "Ed25519", + * "x": "FEJG7OakZi500EydXxuE8uMc8uaAzEJkmQeG8khXANw" + * } + * const algorithm = getJoseSignatureAlgorithmFromPublicKey(publicKey); + * console.log(algorithm); // Output: "EdDSA" + * ``` + * + * @param publicKey - A JWK containing the `alg` and/or `crv` properties. + * @returns The name of the algorithm associated with the key. + * @throws Error if the algorithm cannot be determined from the provided input. + */ +export function getJoseSignatureAlgorithmFromPublicKey(publicKey: Jwk): string { + const curveToJoseAlgorithm: Record = { + 'Ed25519' : 'EdDSA', + 'P-256' : 'ES256', + 'P-384' : 'ES384', + 'P-521' : 'ES512', + 'secp256k1' : 'ES256K', + }; + + // If the key contains an `alg` property that matches a JOSE registered algorithm identifier, + // return its value. + if (publicKey.alg && Object.values(curveToJoseAlgorithm).includes(publicKey.alg)) { + return publicKey.alg; + } + + // If the key contains a `crv` property, return the corresponding algorithm. + if (publicKey.crv && Object.keys(curveToJoseAlgorithm).includes(publicKey.crv)) { + return curveToJoseAlgorithm[publicKey.crv]; + } + + throw new Error( + `Unable to determine algorithm based on provided input: alg=${publicKey.alg}, crv=${publicKey.crv}. ` + + `Supported 'alg' values: ${Object.values(curveToJoseAlgorithm).join(', ')}. ` + + `Supported 'crv' values: ${Object.keys(curveToJoseAlgorithm).join(', ')}.` + ); +} + /** * Checks if the Web Crypto API is supported in the current runtime environment. * diff --git a/packages/crypto/tests/utils.spec.ts b/packages/crypto/tests/utils.spec.ts index 2bdc3e555..c2d4fdfdd 100644 --- a/packages/crypto/tests/utils.spec.ts +++ b/packages/crypto/tests/utils.spec.ts @@ -1,12 +1,15 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; +import type { Jwk } from '../src/jose/jwk.js'; + import { randomUuid, randomBytes, checkValidProperty, isWebCryptoSupported, checkRequiredProperty, + getJoseSignatureAlgorithmFromPublicKey, } from '../src/utils.js'; // TODO: Remove this polyfill once Node.js v18 is no longer supported by @web5/crypto. @@ -65,6 +68,48 @@ describe('Crypto Utils', () => { }); }); + describe('getJoseSignatureAlgorithmFromPublicKey()', () => { + it('returns the algorithm specified by the alg property regardless of the crv property', () => { + const publicKey: Jwk = { kty: 'OKP', alg: 'EdDSA', crv: 'P-256' }; + expect(getJoseSignatureAlgorithmFromPublicKey(publicKey)).to.equal('EdDSA'); + }); + + it('returns the correct algorithm for Ed25519 curve', () => { + const publicKey: Jwk = { kty: 'OKP', crv: 'Ed25519' }; + expect(getJoseSignatureAlgorithmFromPublicKey(publicKey)).to.equal('EdDSA'); + }); + + it('returns the correct algorithm for P-256 curve', () => { + const publicKey: Jwk = { kty: 'EC', crv: 'P-256' }; + expect(getJoseSignatureAlgorithmFromPublicKey(publicKey)).to.equal('ES256'); + }); + + it('returns the correct algorithm for P-384 curve', () => { + const publicKey: Jwk = { kty: 'EC', crv: 'P-384' }; + expect(getJoseSignatureAlgorithmFromPublicKey(publicKey)).to.equal('ES384'); + }); + + it('returns the correct algorithm for P-521 curve', () => { + const publicKey: Jwk = { kty: 'EC', crv: 'P-521' }; + expect(getJoseSignatureAlgorithmFromPublicKey(publicKey)).to.equal('ES512'); + }); + + it('throws an error for unsupported algorithms', () => { + const publicKey: Jwk = { kty: 'EC', alg: 'UnsupportedAlgorithm' }; + expect(() => getJoseSignatureAlgorithmFromPublicKey(publicKey)).to.throw(); + }); + + it('throws an error for unsupported curves', () => { + const publicKey: Jwk = { kty: 'EC', crv: 'UnsupportedCurve' }; + expect(() => getJoseSignatureAlgorithmFromPublicKey(publicKey)).to.throw(); + }); + + it('throws an error when neither alg nor crv is provided', () => { + const publicKey: Jwk = { kty: 'EC' }; + expect(() => getJoseSignatureAlgorithmFromPublicKey(publicKey)).to.throw(); + }); + }); + describe('isWebCryptoSupported()', () => { afterEach(() => { // Restore the original state after each test diff --git a/packages/dids/src/bearer-did.ts b/packages/dids/src/bearer-did.ts index c9b718b22..f8f312031 100644 --- a/packages/dids/src/bearer-did.ts +++ b/packages/dids/src/bearer-did.ts @@ -1,4 +1,16 @@ -import { LocalKeyManager, type CryptoApi, type EnclosedSignParams, type EnclosedVerifyParams, type Jwk, type KeyIdentifier, type KeyImporterExporter, type KmsExportKeyParams, type KmsImportKeyParams, type Signer } from '@web5/crypto'; +import type { + Jwk, + Signer, + CryptoApi, + KeyIdentifier, + EnclosedSignParams, + KmsExportKeyParams, + KmsImportKeyParams, + KeyImporterExporter, + EnclosedVerifyParams, +} from '@web5/crypto'; + +import { LocalKeyManager, utils as cryptoUtils } from '@web5/crypto'; import type { DidDocument } from './types/did-core.js'; import type { DidMetadata, PortableDid } from './types/portable-did.js'; @@ -88,13 +100,15 @@ export class BearerDid { * `PortableDid` structure. * * @remarks - * This method requires that the DID's key manager supports the `exportKey` operation. If the DID - * document does not contain any verification methods, or if the key manager does not support key - * export, an error is thrown. + * If the DID's key manager does not allow private keys to be exported, the `PortableDid` returned + * will not contain a `privateKeys` property. This enables the importing and exporting DIDs that + * use the same underlying KMS even if the KMS does not support exporting private keys. Examples + * include hardware security modules (HSMs) and cloud-based KMS services like AWS KMS. * - * The resulting `PortableDid` will contain the same number of verification methods as the DID - * document, each with its associated public and private keys and the purposes for which the key - * can be used. + * If the DID's key manager does support exporting private keys, the resulting `PortableDid` will + * include a `privateKeys` property which contains the same number of entries as there are + * verification methods as the DID document, each with its associated private key and the + * purpose(s) for which the key can be used (e.g., `authentication`, `assertionMethod`, etc.). * * @example * ```ts @@ -179,7 +193,7 @@ export class BearerDid { const keyManager = this.keyManager; // Determine the signing algorithm. - const algorithm = BearerDid.getAlgorithmFromPublicKey(publicKey); + const algorithm = cryptoUtils.getJoseSignatureAlgorithmFromPublicKey(publicKey); return { algorithm : algorithm, @@ -263,45 +277,4 @@ export class BearerDid { return did; } - - /** - * Determines the name of the algorithm based on the key's curve property. - * - * @remarks - * This method facilitates the identification of the correct algorithm for cryptographic - * operations based on the `alg` or `crv` properties of a {@link Jwk | JWK}. - * - * @example - * ```ts - * const publicKey = { ... }; // Public key in JWK format - * const algorithm = BearerDid.getAlgorithmFromPublicKey({ key: publicKey }); - * ``` - * - * @param publicKey - A JWK containing the `alg` and/or `crv` properties. - * - * @returns The name of the algorithm associated with the key. - * - * @throws Error if the algorithm cannot be determined from the provided input. - */ - private static getAlgorithmFromPublicKey(publicKey: Jwk): string { - const registeredSigningAlgorithms: Record = { - 'Ed25519' : 'EdDSA', - 'P-256' : 'ES256', - 'P-384' : 'ES384', - 'P-521' : 'ES512', - 'secp256k1' : 'ES256K', - }; - - // If the key contains an `alg` property, return its value. - if (publicKey.alg) { - return publicKey.alg; - } - - // If the key contains a `crv` property, return the corresponding algorithm. - if (publicKey.crv && Object.keys(registeredSigningAlgorithms).includes(publicKey.crv)) { - return registeredSigningAlgorithms[publicKey.crv]; - } - - throw new Error(`Unable to determine algorithm based on provided input: alg=${publicKey.alg}, crv=${publicKey.crv}`); - } } \ No newline at end of file