Skip to content

Commit

Permalink
merge
Browse files Browse the repository at this point in the history
  • Loading branch information
nitro-neal committed Feb 29, 2024
2 parents 0bcbc08 + ea9c48a commit 71d3a77
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 60 deletions.
1 change: 1 addition & 0 deletions packages/credentials/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './jwt.js';
export * from './presentation-exchange.js';
export * from './verifiable-credential.js';
export * from './verifiable-presentation.js';
export * as utils from './utils.js';
6 changes: 3 additions & 3 deletions packages/credentials/src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
} from '@sphereon/ssi-types';

import {
DEFAULT_CONTEXT,
DEFAULT_VC_CONTEXT,
DEFAULT_VC_TYPE,
VerifiableCredential
} from './verifiable-credential.js';
Expand All @@ -23,8 +23,8 @@ export class SsiValidator {

static validateContext(value: ICredentialContextType | ICredentialContextType[]): void {
const input = this.asArray(value);
if (input.length < 1 || input.indexOf(DEFAULT_CONTEXT) === -1) {
throw new Error(`@context is missing default context "${DEFAULT_CONTEXT}"`);
if (input.length < 1 || input.indexOf(DEFAULT_VC_CONTEXT) === -1) {
throw new Error(`@context is missing default context "${DEFAULT_VC_CONTEXT}"`);
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/credentials/src/verifiable-credential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Jwt } from './jwt.js';
import { SsiValidator } from './validators.js';
import { getCurrentXmlSchema112Timestamp, getXmlSchema112Timestamp } from './utils.js';

export const DEFAULT_CONTEXT = 'https://www.w3.org/2018/credentials/v1';
export const DEFAULT_VC_CONTEXT = 'https://www.w3.org/2018/credentials/v1';
export const DEFAULT_VC_TYPE = 'VerifiableCredential';

/**
Expand Down Expand Up @@ -153,7 +153,7 @@ export class VerifiableCredential {
};

const vcDataModel: VcDataModel = {
'@context' : [DEFAULT_CONTEXT],
'@context' : [DEFAULT_VC_CONTEXT],
type : Array.isArray(type)
? [DEFAULT_VC_TYPE, ...type]
: (type ? [DEFAULT_VC_TYPE, type] : [DEFAULT_VC_TYPE]),
Expand Down
6 changes: 3 additions & 3 deletions packages/credentials/src/verifiable-presentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { utils as cryptoUtils } from '@web5/crypto';

import { Jwt } from './jwt.js';
import { SsiValidator } from './validators.js';
import { VerifiableCredential } from './verifiable-credential.js';

export const DEFAULT_CONTEXT = 'https://www.w3.org/2018/credentials/v1';
import { VerifiableCredential, DEFAULT_VC_CONTEXT } from './verifiable-credential.js';

export const DEFAULT_VP_TYPE = 'VerifiablePresentation';

/**
Expand Down Expand Up @@ -136,7 +136,7 @@ export class VerifiablePresentation {
}

const vpDataModel: VpDataModel = {
'@context' : [DEFAULT_CONTEXT],
'@context' : [DEFAULT_VC_CONTEXT],
type : Array.isArray(type)
? [DEFAULT_VP_TYPE, ...type]
: (type ? [DEFAULT_VP_TYPE, type] : [DEFAULT_VP_TYPE]),
Expand Down
6 changes: 3 additions & 3 deletions packages/credentials/tests/ssi-validator.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { expect } from 'chai';

import { SsiValidator } from '../src/validators.js';
import { DEFAULT_CONTEXT, DEFAULT_VC_TYPE } from '../src/verifiable-credential.js';
import { DEFAULT_VC_CONTEXT, DEFAULT_VC_TYPE } from '../src/verifiable-credential.js';

describe('SsiValidator', () => {

describe('validateContext', () => {
it('should throw an error if the default context is missing', () => {
expect(() => SsiValidator.validateContext(['http://example.com'])).throw(`@context is missing default context "${DEFAULT_CONTEXT}"`);
expect(() => SsiValidator.validateContext(['http://example.com'])).throw(`@context is missing default context "${DEFAULT_VC_CONTEXT}"`);
});

it('should not throw an error if the default context is present', () => {
expect(() => SsiValidator.validateContext([DEFAULT_CONTEXT, 'http://example.com'])).not.throw();
expect(() => SsiValidator.validateContext([DEFAULT_VC_CONTEXT, 'http://example.com'])).not.throw();
});
});

Expand Down
54 changes: 54 additions & 0 deletions packages/crypto/src/utils.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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<string, string> = {
'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.
*
Expand Down
45 changes: 45 additions & 0 deletions packages/crypto/tests/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
Expand Down
71 changes: 22 additions & 49 deletions packages/dids/src/bearer-did.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<string, string> = {
'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}`);
}
}

0 comments on commit 71d3a77

Please sign in to comment.