From 5a7d564399a61732921056841f4da63ec63469ea Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 11 May 2023 10:52:19 +0200 Subject: [PATCH 1/7] feat: support more key types in jws service Signed-off-by: Timo Glastra --- .../src/wallet/__tests__/AskarWallet.test.ts | 2 +- packages/core/src/crypto/Jwk.ts | 73 --------- packages/core/src/crypto/JwkTypes.ts | 139 ------------------ packages/core/src/crypto/JwsService.ts | 116 ++++++++++----- packages/core/src/crypto/JwsTypes.ts | 20 +++ packages/core/src/crypto/Key.ts | 14 +- .../core/src/crypto/__tests__/Jwk.test.ts | 97 ------------ .../src/crypto/__tests__/JwsService.test.ts | 87 ++++++++--- .../__tests__/__fixtures__/didJwsz6Mkf.ts | 2 +- .../__tests__/__fixtures__/didJwsz6Mkv.ts | 10 +- .../__tests__/__fixtures__/didJwszDnaey.ts | 26 ++++ packages/core/src/crypto/index.ts | 3 +- packages/core/src/crypto/jose/jwa/alg.ts | 38 +++++ packages/core/src/crypto/jose/jwa/crv.ts | 7 + packages/core/src/crypto/jose/jwa/index.ts | 3 + packages/core/src/crypto/jose/jwa/kty.ts | 6 + .../core/src/crypto/jose/jwk/Ed25519Jwk.ts | 87 +++++++++++ packages/core/src/crypto/jose/jwk/Jwk.ts | 40 +++++ packages/core/src/crypto/jose/jwk/P_256Jwk.ts | 102 +++++++++++++ packages/core/src/crypto/jose/jwk/P_384Jwk.ts | 102 +++++++++++++ packages/core/src/crypto/jose/jwk/P_521Jwk.ts | 102 +++++++++++++ .../core/src/crypto/jose/jwk/X25519Jwk.ts | 91 ++++++++++++ .../jose/jwk/__tests__/Ed25519Jwk.test.ts | 36 +++++ .../jose/jwk/__tests__/P_256Jwk.test.ts | 52 +++++++ .../jose/jwk/__tests__/P_384Jwk.test.ts | 51 +++++++ .../jose/jwk/__tests__/P_521Jwk.test.ts | 51 +++++++ .../jose/jwk/__tests__/X25519Jwk.test.ts | 36 +++++ .../jwk/ecCompression.ts} | 2 +- packages/core/src/crypto/jose/jwk/index.ts | 7 + .../core/src/crypto/jose/jwk/transform.ts | 38 +++++ packages/core/src/crypto/jose/jwk/validate.ts | 25 ++++ packages/core/src/crypto/keyUtils.ts | 38 ++++- .../connections/DidExchangeProtocol.ts | 6 +- .../src/modules/dids/domain/DidDocument.ts | 3 +- .../dids/domain/key-type/bls12381g1.ts | 2 +- .../dids/domain/key-type/bls12381g1g2.ts | 2 +- .../dids/domain/key-type/bls12381g2.ts | 2 +- .../modules/dids/domain/key-type/ed25519.ts | 3 +- .../dids/domain/key-type/keyDidJsonWebKey.ts | 4 +- .../dids/domain/key-type/keyDidMapping.ts | 7 +- .../modules/dids/domain/key-type/x25519.ts | 2 +- .../src/modules/dids/domain/keyDidDocument.ts | 3 +- .../verificationMethod/JsonWebKey2020.ts | 11 +- .../verificationMethod/VerificationMethod.ts | 6 +- .../src/modules/dids/methods/jwk/DidJwk.ts | 24 +-- .../dids/methods/jwk/JwkDidRegistrar.ts | 4 +- .../dids/methods/jwk/__tests__/DidJwk.test.ts | 7 +- .../jwk/__tests__/JwkDidRegistrar.test.ts | 17 +-- .../__fixtures__/p256DidJwkEyJjcnYi0i.ts | 18 +-- .../dids/methods/jwk/didJwkDidDocument.ts | 7 +- .../peer/createPeerDidDocumentFromServices.ts | 3 +- .../dids/methods/peer/peerDidNumAlgo0.ts | 2 +- .../dids/methods/peer/peerDidNumAlgo2.ts | 2 +- .../src/OpenId4VcClientService.ts | 11 +- 54 files changed, 1189 insertions(+), 460 deletions(-) delete mode 100644 packages/core/src/crypto/Jwk.ts delete mode 100644 packages/core/src/crypto/JwkTypes.ts delete mode 100644 packages/core/src/crypto/__tests__/Jwk.test.ts create mode 100644 packages/core/src/crypto/__tests__/__fixtures__/didJwszDnaey.ts create mode 100644 packages/core/src/crypto/jose/jwa/alg.ts create mode 100644 packages/core/src/crypto/jose/jwa/crv.ts create mode 100644 packages/core/src/crypto/jose/jwa/index.ts create mode 100644 packages/core/src/crypto/jose/jwa/kty.ts create mode 100644 packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts create mode 100644 packages/core/src/crypto/jose/jwk/Jwk.ts create mode 100644 packages/core/src/crypto/jose/jwk/P_256Jwk.ts create mode 100644 packages/core/src/crypto/jose/jwk/P_384Jwk.ts create mode 100644 packages/core/src/crypto/jose/jwk/P_521Jwk.ts create mode 100644 packages/core/src/crypto/jose/jwk/X25519Jwk.ts create mode 100644 packages/core/src/crypto/jose/jwk/__tests__/Ed25519Jwk.test.ts create mode 100644 packages/core/src/crypto/jose/jwk/__tests__/P_256Jwk.test.ts create mode 100644 packages/core/src/crypto/jose/jwk/__tests__/P_384Jwk.test.ts create mode 100644 packages/core/src/crypto/jose/jwk/__tests__/P_521Jwk.test.ts create mode 100644 packages/core/src/crypto/jose/jwk/__tests__/X25519Jwk.test.ts rename packages/core/src/crypto/{EcCompression.ts => jose/jwk/ecCompression.ts} (98%) create mode 100644 packages/core/src/crypto/jose/jwk/index.ts create mode 100644 packages/core/src/crypto/jose/jwk/transform.ts create mode 100644 packages/core/src/crypto/jose/jwk/validate.ts diff --git a/packages/askar/src/wallet/__tests__/AskarWallet.test.ts b/packages/askar/src/wallet/__tests__/AskarWallet.test.ts index 00eb26f77e..469d1349b6 100644 --- a/packages/askar/src/wallet/__tests__/AskarWallet.test.ts +++ b/packages/askar/src/wallet/__tests__/AskarWallet.test.ts @@ -100,7 +100,7 @@ describeRunInNodeVersion([18], 'AskarWallet basic operations', () => { }) }) - test('Create P-256 keypair', async () => { + test('Create P256 keypair', async () => { await expect( askarWallet.createKey({ seed: Buffer.concat([seed, seed]), keyType: KeyType.P256 }) ).resolves.toMatchObject({ diff --git a/packages/core/src/crypto/Jwk.ts b/packages/core/src/crypto/Jwk.ts deleted file mode 100644 index 2c8d49d243..0000000000 --- a/packages/core/src/crypto/Jwk.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { - Ed25519JwkPublicKey, - Jwk, - P256JwkPublicKey, - P384JwkPublicKey, - P521JwkPublicKey, - X25519JwkPublicKey, -} from './JwkTypes' -import type { Key } from './Key' - -import { TypedArrayEncoder, Buffer } from '../utils' - -import { compress, expand } from './EcCompression' -import { - jwkCurveToKeyTypeMapping, - keyTypeToJwkCurveMapping, - isEd25519JwkPublicKey, - isX25519JwkPublicKey, - isP256JwkPublicKey, - isP384JwkPublicKey, - isP521JwkPublicKey, -} from './JwkTypes' -import { KeyType } from './KeyType' - -export function getKeyDataFromJwk(jwk: Jwk): { keyType: KeyType; publicKey: Uint8Array } { - // ed25519, x25519 - if (isEd25519JwkPublicKey(jwk) || isX25519JwkPublicKey(jwk)) { - return { - publicKey: TypedArrayEncoder.fromBase64(jwk.x), - keyType: jwkCurveToKeyTypeMapping[jwk.crv], - } - } - - // p-256, p-384, p-521 - if (isP256JwkPublicKey(jwk) || isP384JwkPublicKey(jwk) || isP521JwkPublicKey(jwk)) { - // TODO: do we want to use the compressed key in the Key instance? - const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(jwk.x), TypedArrayEncoder.fromBase64(jwk.y)]) - const compressedPublicKey = compress(publicKeyBuffer) - - return { - publicKey: compressedPublicKey, - keyType: jwkCurveToKeyTypeMapping[jwk.crv], - } - } - - throw new Error(`Unsupported JWK kty '${jwk.kty}' and crv '${jwk.crv}'`) -} - -export function getJwkFromKey(key: Key): Jwk { - if (key.keyType === KeyType.Ed25519 || key.keyType === KeyType.X25519) { - return { - kty: 'OKP', - crv: keyTypeToJwkCurveMapping[key.keyType], - x: TypedArrayEncoder.toBase64URL(key.publicKey), - } satisfies Ed25519JwkPublicKey | X25519JwkPublicKey - } - - if (key.keyType === KeyType.P256 || key.keyType === KeyType.P384 || key.keyType === KeyType.P521) { - const crv = keyTypeToJwkCurveMapping[key.keyType] - const expanded = expand(key.publicKey, crv) - const x = expanded.slice(0, expanded.length / 2) - const y = expanded.slice(expanded.length / 2) - - return { - kty: 'EC', - crv, - x: TypedArrayEncoder.toBase64URL(x), - y: TypedArrayEncoder.toBase64URL(y), - } satisfies P256JwkPublicKey | P384JwkPublicKey | P521JwkPublicKey - } - - throw new Error(`Cannot encode Key as JWK. Unsupported key type '${key.keyType}'`) -} diff --git a/packages/core/src/crypto/JwkTypes.ts b/packages/core/src/crypto/JwkTypes.ts deleted file mode 100644 index 560b2c5e1d..0000000000 --- a/packages/core/src/crypto/JwkTypes.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { KeyType } from './KeyType' - -export type JwkCurve = 'Ed25519' | 'X25519' | 'P-256' | 'P-384' | 'P-521' | 'Bls12381G1' | 'Bls12381G2' - -export interface Jwk { - kty: 'EC' | 'OKP' - crv: JwkCurve - x: string - y?: string - use?: 'sig' | 'enc' -} - -export interface Ed25519JwkPublicKey extends Jwk { - kty: 'OKP' - crv: 'Ed25519' - x: string - y?: never - use?: 'sig' -} - -export interface X25519JwkPublicKey extends Jwk { - kty: 'OKP' - crv: 'X25519' - x: string - y?: never - use?: 'enc' -} - -export interface P256JwkPublicKey extends Jwk { - kty: 'EC' - crv: 'P-256' - x: string - y: string - use?: 'sig' | 'enc' -} - -export interface P384JwkPublicKey extends Jwk { - kty: 'EC' - crv: 'P-384' - x: string - y: string - use?: 'sig' | 'enc' -} - -export interface P521JwkPublicKey extends Jwk { - kty: 'EC' - crv: 'P-521' - x: string - y: string - use?: 'sig' | 'enc' -} - -export function isEd25519JwkPublicKey(jwk: Jwk): jwk is Ed25519JwkPublicKey { - return jwk.kty === 'OKP' && jwk.crv === 'Ed25519' && jwk.x !== undefined && (!jwk.use || jwk.use === 'sig') -} - -export function isX25519JwkPublicKey(jwk: Jwk): jwk is X25519JwkPublicKey { - return jwk.kty === 'OKP' && jwk.crv === 'X25519' && jwk.x !== undefined && (!jwk.use || jwk.use === 'enc') -} - -export function isP256JwkPublicKey(jwk: Jwk): jwk is P256JwkPublicKey { - return ( - jwk.kty === 'EC' && - jwk.crv === 'P-256' && - jwk.x !== undefined && - jwk.y !== undefined && - (!jwk.use || ['sig', 'enc'].includes(jwk.use)) - ) -} - -export function isP384JwkPublicKey(jwk: Jwk): jwk is P384JwkPublicKey { - return ( - jwk.kty === 'EC' && - jwk.crv === 'P-384' && - jwk.x !== undefined && - jwk.y !== undefined && - (!jwk.use || ['sig', 'enc'].includes(jwk.use)) - ) -} - -export function isP521JwkPublicKey(jwk: Jwk): jwk is P521JwkPublicKey { - return ( - jwk.kty === 'EC' && - jwk.crv === 'P-521' && - jwk.x !== undefined && - jwk.y !== undefined && - (!jwk.use || ['sig', 'enc'].includes(jwk.use)) - ) -} - -export const jwkCurveToKeyTypeMapping = { - Ed25519: KeyType.Ed25519, - X25519: KeyType.X25519, - 'P-256': KeyType.P256, - 'P-384': KeyType.P384, - 'P-521': KeyType.P521, - Bls12381G1: KeyType.Bls12381g1, - Bls12381G2: KeyType.Bls12381g2, -} as const - -export const keyTypeToJwkCurveMapping = { - [KeyType.Ed25519]: 'Ed25519', - [KeyType.X25519]: 'X25519', - [KeyType.P256]: 'P-256', - [KeyType.P384]: 'P-384', - [KeyType.P521]: 'P-521', - [KeyType.Bls12381g1]: 'Bls12381G1', - [KeyType.Bls12381g2]: 'Bls12381G2', -} as const - -const keyTypeSigningSupportedMapping = { - [KeyType.Ed25519]: true, - [KeyType.X25519]: false, - [KeyType.P256]: true, - [KeyType.P384]: true, - [KeyType.P521]: true, - [KeyType.Bls12381g1]: true, - [KeyType.Bls12381g2]: true, - [KeyType.Bls12381g1g2]: true, -} as const - -const keyTypeEncryptionSupportedMapping = { - [KeyType.Ed25519]: false, - [KeyType.X25519]: true, - [KeyType.P256]: true, - [KeyType.P384]: true, - [KeyType.P521]: true, - [KeyType.Bls12381g1]: false, - [KeyType.Bls12381g2]: false, - [KeyType.Bls12381g1g2]: false, -} as const - -export function isSigningSupportedForKeyType(keyType: KeyType): boolean { - return keyTypeSigningSupportedMapping[keyType] -} - -export function isEncryptionSupportedForKeyType(keyType: KeyType): boolean { - return keyTypeEncryptionSupportedMapping[keyType] -} diff --git a/packages/core/src/crypto/JwsService.ts b/packages/core/src/crypto/JwsService.ts index e81be473b8..851582e85e 100644 --- a/packages/core/src/crypto/JwsService.ts +++ b/packages/core/src/crypto/JwsService.ts @@ -1,31 +1,40 @@ -import type { Jwk } from './JwkTypes' -import type { Jws, JwsGeneralFormat } from './JwsTypes' +import type { Jws, JwsGeneralFormat, JwsProtectedHeader, JwsProtectedHeaderOptions } from './JwsTypes' +import type { Key } from './Key' +import type { Jwk } from './jose/jwk' import type { AgentContext } from '../agent' import type { Buffer } from '../utils' import { AriesFrameworkError } from '../error' +import { getKeyFromVerificationMethod } from '../modules/dids/domain/key-type/keyDidMapping' +import { DidKey } from '../modules/dids/methods/key/DidKey' +import { DidResolverService } from '../modules/dids/services/DidResolverService' import { injectable } from '../plugins' import { JsonEncoder, TypedArrayEncoder } from '../utils' import { WalletError } from '../wallet/error' -import { Key } from './Key' -import { KeyType } from './KeyType' - -// TODO: support more key types, more generic jws format -const JWS_KEY_TYPE = 'OKP' -const JWS_CURVE = 'Ed25519' -const JWS_ALG = 'EdDSA' +import { getJwkFromJson, getJwkFromKey } from './jose/jwk' @injectable() export class JwsService { - public static supportedKeyTypes = [KeyType.Ed25519] - private async createJwsBase(agentContext: AgentContext, options: CreateJwsBaseOptions) { - if (!JwsService.supportedKeyTypes.includes(options.key.keyType)) { + const { jwk, alg } = options.protectedHeaderOptions + const keyJwk = getJwkFromKey(options.key) + + // Make sure the options.key and jwk from protectedHeader are the same. + if (jwk && (jwk.key.keyType !== options.key.keyType || !jwk.key.publicKey.equals(options.key.publicKey))) { + throw new AriesFrameworkError(`Protected header JWK does not match key for signing.`) + } + + // Validate the options.key used for signing against the jws options + // We use keyJwk instead of jwk, as the user could also use kid instead of jwk + if (keyJwk && !keyJwk.supportsSignatureAlgorithm(alg)) { throw new AriesFrameworkError( - `Only ${JwsService.supportedKeyTypes.join(',')} key type(s) supported for creating JWS` + `alg '${alg}' is not a valid JWA signature algorithm for this jwk. Supported algorithms are ${keyJwk.supportedSignatureAlgorithms.join( + ', ' + )}` ) } + const base64Payload = TypedArrayEncoder.toBase64URL(options.payload) const base64UrlProtectedHeader = JsonEncoder.toBase64URL(this.buildProtected(options.protectedHeaderOptions)) @@ -88,25 +97,25 @@ export class JwsService { const signerKeys: Key[] = [] for (const jws of signatures) { - const protectedJson = JsonEncoder.fromBase64(jws.protected) - - const isValidKeyType = protectedJson?.jwk?.kty === JWS_KEY_TYPE - const isValidCurve = protectedJson?.jwk?.crv === JWS_CURVE - const isValidAlg = protectedJson?.alg === JWS_ALG - - if (!isValidKeyType || !isValidCurve || !isValidAlg) { - throw new AriesFrameworkError('Invalid protected header') + const protectedJson: JwsProtectedHeader = JsonEncoder.fromBase64(jws.protected) + + const jwk = await this.jwkFromProtectedHeader(agentContext, protectedJson) + if (!jwk.supportsSignatureAlgorithm(protectedJson.alg)) { + throw new AriesFrameworkError( + `alg '${ + protectedJson.alg + }' is not a valid JWA signature algorithm for this jwk. Supported algorithms are ${jwk.supportedSignatureAlgorithms.join( + ', ' + )}` + ) } const data = TypedArrayEncoder.fromString(`${jws.protected}.${base64Payload}`) const signature = TypedArrayEncoder.fromBase64(jws.signature) - - const publicKey = TypedArrayEncoder.fromBase64(protectedJson?.jwk?.x) - const key = Key.fromPublicKey(publicKey, KeyType.Ed25519) - signerKeys.push(key) + signerKeys.push(jwk.key) try { - const isValid = await agentContext.wallet.verify({ key, data, signature }) + const isValid = await agentContext.wallet.verify({ key: jwk.key, data, signature }) if (!isValid) { return { @@ -128,10 +137,10 @@ export class JwsService { } } - return { isValid: true, signerKeys: signerKeys } + return { isValid: true, signerKeys } } - private buildProtected(options: ProtectedHeaderOptions) { + private buildProtected(options: JwsProtectedHeaderOptions) { if (!options.jwk && !options.kid) { throw new AriesFrameworkError('Both JWK and kid are undefined. Please provide one or the other.') } @@ -140,22 +149,58 @@ export class JwsService { } return { + ...options, alg: options.alg, - jwk: options.jwk, + jwk: options.jwk?.toJson(), kid: options.kid, } } + + private async jwkFromProtectedHeader(agentContext: AgentContext, protectedHeader: JwsProtectedHeader): Promise { + if (protectedHeader.jwk && protectedHeader.kid) { + throw new AriesFrameworkError( + 'Both JWK and kid are defined in the protected header. Only one of the two is allowed.' + ) + } + + // Jwk + if (protectedHeader.jwk) { + return getJwkFromJson(protectedHeader.jwk) + } + + // Kid + if (protectedHeader.kid) { + if (!protectedHeader.kid.startsWith('did:')) { + throw new AriesFrameworkError( + `Only DIDs are supported as the 'kid' parameter for JWS. '${protectedHeader.kid}' is not a did.` + ) + } + + const didResolver = agentContext.dependencyManager.resolve(DidResolverService) + const didDocument = await didResolver.resolveDidDocument(agentContext, protectedHeader.kid) + + // This is a special case for Aries RFC 0017 signed attachments. It allows a did:key without a keyId to be used kid + // https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0017-attachments/README.md#signing-attachments + if (protectedHeader.kid.startsWith('did:key:') && !protectedHeader.kid.includes('#')) { + return getJwkFromKey(DidKey.fromDid(protectedHeader.kid).key) + } + + // TODO: allowedPurposes + return getJwkFromKey(getKeyFromVerificationMethod(didDocument.dereferenceKey(protectedHeader.kid))) + } + + throw new AriesFrameworkError('Both JWK and kid are undefined. Protected header must contain one of the two.') + } } export interface CreateJwsOptions { key: Key payload: Buffer header: Record - protectedHeaderOptions: ProtectedHeaderOptions + protectedHeaderOptions: JwsProtectedHeaderOptions } type CreateJwsBaseOptions = Omit - type CreateCompactJwsOptions = Omit export interface VerifyJwsOptions { @@ -167,12 +212,3 @@ export interface VerifyJwsResult { isValid: boolean signerKeys: Key[] } - -export type kid = string - -export interface ProtectedHeaderOptions { - alg: string - jwk?: Jwk - kid?: kid - [key: string]: any -} diff --git a/packages/core/src/crypto/JwsTypes.ts b/packages/core/src/crypto/JwsTypes.ts index 6f3b65d9eb..4e32bec36e 100644 --- a/packages/core/src/crypto/JwsTypes.ts +++ b/packages/core/src/crypto/JwsTypes.ts @@ -1,3 +1,23 @@ +import type { JwaSignatureAlgorithm } from './jose/jwa' +import type { Jwk } from './jose/jwk' +import type { JwkJson } from './jose/jwk/Jwk' + +export type Kid = string + +export interface JwsProtectedHeaderOptions { + alg: JwaSignatureAlgorithm | string + kid?: Kid + jwk?: Jwk + [key: string]: unknown +} + +export interface JwsProtectedHeader { + alg: JwaSignatureAlgorithm | string + kid?: Kid + jwk?: JwkJson + [key: string]: unknown +} + export interface JwsGeneralFormat { header: Record signature: string diff --git a/packages/core/src/crypto/Key.ts b/packages/core/src/crypto/Key.ts index 85a56dafbd..2ebe3651f2 100644 --- a/packages/core/src/crypto/Key.ts +++ b/packages/core/src/crypto/Key.ts @@ -1,10 +1,8 @@ -import type { Jwk } from './JwkTypes' import type { KeyType } from './KeyType' import { Buffer, MultiBaseEncoder, TypedArrayEncoder, VarintEncoder } from '../utils' -import { getJwkFromKey, getKeyDataFromJwk } from './Jwk' -import { isEncryptionSupportedForKeyType, isSigningSupportedForKeyType } from './JwkTypes' +import { isEncryptionSupportedForKeyType, isSigningSupportedForKeyType } from './keyUtils' import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeyType } from './multiCodecKey' export class Key { @@ -61,14 +59,4 @@ export class Key { public get supportsSigning() { return isSigningSupportedForKeyType(this.keyType) } - - public toJwk(): Jwk { - return getJwkFromKey(this) - } - - public static fromJwk(jwk: Jwk) { - const { keyType, publicKey } = getKeyDataFromJwk(jwk) - - return Key.fromPublicKey(publicKey, keyType) - } } diff --git a/packages/core/src/crypto/__tests__/Jwk.test.ts b/packages/core/src/crypto/__tests__/Jwk.test.ts deleted file mode 100644 index 0cbe81255e..0000000000 --- a/packages/core/src/crypto/__tests__/Jwk.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { - Ed25519JwkPublicKey, - P256JwkPublicKey, - P384JwkPublicKey, - P521JwkPublicKey, - X25519JwkPublicKey, -} from '../JwkTypes' - -import { getJwkFromKey, getKeyDataFromJwk } from '../Jwk' -import { Key } from '../Key' -import { KeyType } from '../KeyType' - -describe('jwk', () => { - it('Ed25519', () => { - const fingerprint = 'z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp' - const jwk = { - kty: 'OKP', - crv: 'Ed25519', - x: 'O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik', - } satisfies Ed25519JwkPublicKey - - const { keyType, publicKey } = getKeyDataFromJwk(jwk) - expect(keyType).toEqual(KeyType.Ed25519) - expect(Key.fromPublicKey(publicKey, KeyType.Ed25519).fingerprint).toEqual(fingerprint) - - const actualJwk = getJwkFromKey(Key.fromFingerprint(fingerprint)) - expect(actualJwk).toEqual(jwk) - }) - - it('X25519', () => { - const fingerprint = 'z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW' - const jwk = { - kty: 'OKP', - crv: 'X25519', - x: 'W_Vcc7guviK-gPNDBmevVw-uJVamQV5rMNQGUwCqlH0', - } satisfies X25519JwkPublicKey - - const { keyType, publicKey } = getKeyDataFromJwk(jwk) - expect(keyType).toEqual(KeyType.X25519) - expect(Key.fromPublicKey(publicKey, KeyType.X25519).fingerprint).toEqual(fingerprint) - - const actualJwk = getJwkFromKey(Key.fromFingerprint(fingerprint)) - expect(actualJwk).toEqual(jwk) - }) - - it('P-256', () => { - const fingerprint = 'zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv' - const jwk = { - kty: 'EC', - crv: 'P-256', - x: 'igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns', - y: 'efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM', - } satisfies P256JwkPublicKey - - const { keyType, publicKey } = getKeyDataFromJwk(jwk) - expect(keyType).toEqual(KeyType.P256) - expect(Key.fromPublicKey(publicKey, KeyType.P256).fingerprint).toEqual(fingerprint) - - const actualJwk = getJwkFromKey(Key.fromFingerprint(fingerprint)) - expect(actualJwk).toEqual(jwk) - }) - - it('P-384', () => { - const fingerprint = 'z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9' - const jwk = { - kty: 'EC', - crv: 'P-384', - x: 'lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc', - y: 'y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv', - } satisfies P384JwkPublicKey - - const { keyType, publicKey } = getKeyDataFromJwk(jwk) - expect(keyType).toEqual(KeyType.P384) - expect(Key.fromPublicKey(publicKey, KeyType.P384).fingerprint).toEqual(fingerprint) - - const actualJwk = getJwkFromKey(Key.fromFingerprint(fingerprint)) - expect(actualJwk).toEqual(jwk) - }) - - it('P-521', () => { - const fingerprint = - 'z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7' - const jwk = { - kty: 'EC', - crv: 'P-521', - x: 'ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS', - y: 'AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC', - } satisfies P521JwkPublicKey - - const { keyType, publicKey } = getKeyDataFromJwk(jwk) - expect(keyType).toEqual(KeyType.P521) - expect(Key.fromPublicKey(publicKey, KeyType.P521).fingerprint).toEqual(fingerprint) - - const actualJwk = getJwkFromKey(Key.fromFingerprint(fingerprint)) - expect(actualJwk).toEqual(jwk) - }) -}) diff --git a/packages/core/src/crypto/__tests__/JwsService.test.ts b/packages/core/src/crypto/__tests__/JwsService.test.ts index 15dc242f9b..fd266e0abb 100644 --- a/packages/core/src/crypto/__tests__/JwsService.test.ts +++ b/packages/core/src/crypto/__tests__/JwsService.test.ts @@ -1,28 +1,35 @@ import type { AgentContext } from '../../agent' import type { Key, Wallet } from '@aries-framework/core' -import { IndySdkWallet } from '../../../../indy-sdk/src' -import { indySdk } from '../../../../indy-sdk/tests/setupIndySdkModule' -import { getAgentConfig, getAgentContext } from '../../../tests/helpers' +// eslint-disable-next-line import/no-extraneous-dependencies +import '@hyperledger/aries-askar-nodejs' + +import { describeRunInNodeVersion } from '../../../../../tests/runInVersion' +import { AskarWallet } from '../../../../askar/src' +import { agentDependencies, getAgentConfig, getAgentContext } from '../../../tests/helpers' import { DidKey } from '../../modules/dids' import { Buffer, JsonEncoder, TypedArrayEncoder } from '../../utils' import { JwsService } from '../JwsService' import { KeyType } from '../KeyType' +import { JwaSignatureAlgorithm } from '../jose/jwa' +import { getJwkFromKey } from '../jose/jwk' import { SigningProviderRegistry } from '../signing-provider' import * as didJwsz6Mkf from './__fixtures__/didJwsz6Mkf' import * as didJwsz6Mkv from './__fixtures__/didJwsz6Mkv' +import * as didJwszDnaey from './__fixtures__/didJwszDnaey' -describe('JwsService', () => { +describeRunInNodeVersion([18], 'JwsService', () => { let wallet: Wallet let agentContext: AgentContext let jwsService: JwsService let didJwsz6MkfKey: Key let didJwsz6MkvKey: Key + let didJwszDnaeyKey: Key + beforeAll(async () => { const config = getAgentConfig('JwsService') - // TODO: update to InMemoryWallet - wallet = new IndySdkWallet(indySdk, config.logger, new SigningProviderRegistry([])) + wallet = new AskarWallet(config.logger, new agentDependencies.FileSystem(), new SigningProviderRegistry([])) agentContext = getAgentContext({ wallet, }) @@ -33,33 +40,71 @@ describe('JwsService', () => { privateKey: TypedArrayEncoder.fromString(didJwsz6Mkf.SEED), keyType: KeyType.Ed25519, }) + didJwsz6MkvKey = await wallet.createKey({ privateKey: TypedArrayEncoder.fromString(didJwsz6Mkv.SEED), keyType: KeyType.Ed25519, }) + + didJwszDnaeyKey = await wallet.createKey({ + privateKey: TypedArrayEncoder.fromString(didJwszDnaey.SEED), + keyType: KeyType.P256, + }) }) afterAll(async () => { await wallet.delete() }) - describe('createJws', () => { - it('creates a jws for the payload with the key associated with the verkey', async () => { - const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON) - const kid = new DidKey(didJwsz6MkfKey).did + it('creates a jws for the payload using Ed25519 key', async () => { + const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON) + const kid = new DidKey(didJwsz6MkfKey).did + + const jws = await jwsService.createJws(agentContext, { + payload, + key: didJwsz6MkfKey, + header: { kid }, + protectedHeaderOptions: { + alg: JwaSignatureAlgorithm.EdDSA, + jwk: getJwkFromKey(didJwsz6MkfKey), + }, + }) - const jws = await jwsService.createJws(agentContext, { - payload, - key: didJwsz6MkfKey, - header: { kid }, - protectedHeaderOptions: { - alg: 'EdDSA', - jwk: didJwsz6MkfKey.toJwk(), - }, - }) + expect(jws).toEqual(didJwsz6Mkf.JWS_JSON) + }) + + it('creates and verify a jws using ES256 alg and P-256 kty', async () => { + const payload = JsonEncoder.toBuffer(didJwszDnaey.DATA_JSON) + const kid = new DidKey(didJwszDnaeyKey).did + + const jws = await jwsService.createJws(agentContext, { + payload, + key: didJwszDnaeyKey, + header: { kid }, + protectedHeaderOptions: { + alg: JwaSignatureAlgorithm.ES256, + jwk: getJwkFromKey(didJwszDnaeyKey), + }, + }) - expect(jws).toEqual(didJwsz6Mkf.JWS_JSON) + expect(jws).toEqual(didJwszDnaey.JWS_JSON) + }) + + it('creates a compact jws', async () => { + const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON) + + const jws = await jwsService.createJwsCompact(agentContext, { + payload, + key: didJwsz6MkfKey, + protectedHeaderOptions: { + alg: JwaSignatureAlgorithm.EdDSA, + jwk: getJwkFromKey(didJwsz6MkfKey), + }, }) + + expect(jws).toEqual( + `${didJwsz6Mkf.JWS_JSON.protected}.${TypedArrayEncoder.toBase64URL(payload)}.${didJwsz6Mkf.JWS_JSON.signature}` + ) }) describe('verifyJws', () => { @@ -75,7 +120,7 @@ describe('JwsService', () => { expect(signerKeys).toEqual([didJwsz6MkfKey]) }) - it('returns all verkeys that signed the jws', async () => { + it('returns all keys that signed the jws', async () => { const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON) const { isValid, signerKeys } = await jwsService.verifyJws(agentContext, { diff --git a/packages/core/src/crypto/__tests__/__fixtures__/didJwsz6Mkf.ts b/packages/core/src/crypto/__tests__/__fixtures__/didJwsz6Mkf.ts index 8524f12301..9ab465d022 100644 --- a/packages/core/src/crypto/__tests__/__fixtures__/didJwsz6Mkf.ts +++ b/packages/core/src/crypto/__tests__/__fixtures__/didJwsz6Mkf.ts @@ -1,5 +1,5 @@ export const SEED = '00000000000000000000000000000My2' -export const VERKEY = 'kqa2HyagzfMAq42H5f9u3UMwnSBPQx2QfrSyXbUPxMn' +export const PUBLIC_KEY_BASE58 = 'kqa2HyagzfMAq42H5f9u3UMwnSBPQx2QfrSyXbUPxMn' export const DATA_JSON = { did: 'did', diff --git a/packages/core/src/crypto/__tests__/__fixtures__/didJwsz6Mkv.ts b/packages/core/src/crypto/__tests__/__fixtures__/didJwsz6Mkv.ts index fe31ea8808..5e064848b5 100644 --- a/packages/core/src/crypto/__tests__/__fixtures__/didJwsz6Mkv.ts +++ b/packages/core/src/crypto/__tests__/__fixtures__/didJwsz6Mkv.ts @@ -1,5 +1,5 @@ export const SEED = '00000000000000000000000000000My1' -export const VERKEY = 'GjZWsBLgZCR18aL468JAT7w9CZRiBnpxUPPgyQxh4voa' +export const PUBLIC_KEY_BASE58 = 'GjZWsBLgZCR18aL468JAT7w9CZRiBnpxUPPgyQxh4voa' export const DATA_JSON = { did: 'did', @@ -19,10 +19,8 @@ export const DATA_JSON = { } export const JWS_JSON = { - header: { - kid: 'did:key:z6MkvBpZTRb7tjuUF5AkmhG1JDV928hZbg5KAQJcogvhz9ax', - }, protected: - 'eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3ZCcFpUUmI3dGp1VUY1QWttaEcxSkRWOTI4aFpiZzVLQVFKY29ndmh6OWF4IiwiandrIjp7Imt0eSI6Ik9LUCIsImNydiI6IkVkMjU1MTkiLCJ4IjoiNmNaMmJaS21LaVVpRjlNTEtDVjhJSVlJRXNPTEhzSkc1cUJKOVNyUVlCayIsImtpZCI6ImRpZDprZXk6ejZNa3ZCcFpUUmI3dGp1VUY1QWttaEcxSkRWOTI4aFpiZzVLQVFKY29ndmh6OWF4In19', - signature: 'eA3MPRpSTt5NR8EZkDNb849E9qfrlUm8-StWPA4kMp-qcH7oEc2-1En4fgpz_IWinEbVxCLbmKhWNyaTAuHNAg', + 'eyJhbGciOiJFZERTQSIsImp3ayI6eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6IjZjWjJiWkttS2lVaUY5TUxLQ1Y4SUlZSUVzT0xIc0pHNXFCSjlTclFZQmsifX0', + signature: 'Js_ibaz24b4GRikbGPeLvRe5FyrcVR2aNVZSs26CLl3DCMJdPqUNRxVDNOD-dBnLs0HyTh6_mX9cG9vWEimtBA', + header: { kid: 'did:key:z6MkvBpZTRb7tjuUF5AkmhG1JDV928hZbg5KAQJcogvhz9ax' }, } diff --git a/packages/core/src/crypto/__tests__/__fixtures__/didJwszDnaey.ts b/packages/core/src/crypto/__tests__/__fixtures__/didJwszDnaey.ts new file mode 100644 index 0000000000..ac85c645bc --- /dev/null +++ b/packages/core/src/crypto/__tests__/__fixtures__/didJwszDnaey.ts @@ -0,0 +1,26 @@ +export const SEED = '00000000000000000000000000000My3' +export const PUBLIC_KEY_BASE58 = '2ARvZ9WjdavGb3db6i1TR3bNW8QxqfG9YPHAJJXCsRj2t' + +export const DATA_JSON = { + did: 'did', + did_doc: { + '@context': 'https://w3id.org/did/v1', + service: [ + { + id: 'did:example:123456789abcdefghi#did-communication', + type: 'did-communication', + priority: 0, + recipientKeys: ['someVerkey'], + routingKeys: [], + serviceEndpoint: 'https://agent.example.com/', + }, + ], + }, +} + +export const JWS_JSON = { + protected: + 'eyJhbGciOiJFUzI1NiIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IjZlR0VlUTdwZDB6UXZMdjdneERaN3FKSHpfY2gwWjlzM2JhUFBodmw0QlUiLCJ5IjoiTU8tS25aeUJ4bWo3THVxTU9yV0lNOG1SSzJrSWhXdF9LZF8yN2RvNXRmVSJ9fQ', + signature: '3L6N8rPDpxQ6nBWqyoLIcy_82HRWcNs_foPRnByErtJMAuTCm0fBN_-27xa9FBr-zh6Kumk8pOovXYP8kJrA3g', + header: { kid: 'did:key:zDnaeyQFrnYZJKPp3fnukaXZnhunkBE5yRdfgL8TjsLnnoW5z' }, +} diff --git a/packages/core/src/crypto/index.ts b/packages/core/src/crypto/index.ts index 45f8e62972..ae6002a5cb 100644 --- a/packages/core/src/crypto/index.ts +++ b/packages/core/src/crypto/index.ts @@ -1,4 +1,3 @@ -export { Jwk } from './JwkTypes' export { JwsService } from './JwsService' export * from './jwtUtils' @@ -7,4 +6,6 @@ export * from './keyUtils' export { KeyType } from './KeyType' export { Key } from './Key' +export { Jwk } from './jose/jwk' + export * from './signing-provider' diff --git a/packages/core/src/crypto/jose/jwa/alg.ts b/packages/core/src/crypto/jose/jwa/alg.ts new file mode 100644 index 0000000000..5913830b2e --- /dev/null +++ b/packages/core/src/crypto/jose/jwa/alg.ts @@ -0,0 +1,38 @@ +export enum JwaSignatureAlgorithm { + HS256 = 'HS256', + HS384 = 'HS384', + HS512 = 'HS512', + RS256 = 'RS256', + RS384 = 'RS384', + RS512 = 'RS512', + ES256 = 'ES256', + ES384 = 'ES384', + ES512 = 'ES512', + PS256 = 'PS256', + PS384 = 'PS384', + PS512 = 'PS512', + EdDSA = 'EdDSA', + none = 'none', +} + +export enum JwaEncryptionAlgorithm { + RSA1_5 = 'RSA1_5', + RSA_OAEP = 'RSA-OAEP', + RSA_OAEP_256 = 'RSA-OAEP-256', + A128KW = 'A128KW', + A192KW = 'A192KW', + A256KW = 'A256KW', + dir = 'dir', + ECDH_ES = 'ECDH-ES', + ECDH_ES_A128KW = 'ECDH-ES+A128KW', + ECDH_ES_A192KW = 'ECDH-ES+A192KW', + ECDH_ES_A256KW = 'ECDH-ES+A256KW', + A128GCMKW = 'A128GCMKW', + A192GCMKW = 'A192GCMKW', + A256GCMKW = 'A256GCMKW', + PBES2_HS256_A128KW = 'PBES2-HS256+A128KW', + PBES2_HS384_A192KW = 'PBES2-HS384+A192KW', + PBES2_HS512_A256KW = 'PBES2-HS512+A256KW', +} + +export type JwaAlgorithm = JwaSignatureAlgorithm | JwaEncryptionAlgorithm diff --git a/packages/core/src/crypto/jose/jwa/crv.ts b/packages/core/src/crypto/jose/jwa/crv.ts new file mode 100644 index 0000000000..098e009453 --- /dev/null +++ b/packages/core/src/crypto/jose/jwa/crv.ts @@ -0,0 +1,7 @@ +export enum JwaCurve { + P_256 = 'P-256', + P_384 = 'P-384', + P_521 = 'P-521', + Ed25519 = 'Ed25519', + X25519 = 'X25519', +} diff --git a/packages/core/src/crypto/jose/jwa/index.ts b/packages/core/src/crypto/jose/jwa/index.ts new file mode 100644 index 0000000000..9aa115a084 --- /dev/null +++ b/packages/core/src/crypto/jose/jwa/index.ts @@ -0,0 +1,3 @@ +export { JwaAlgorithm, JwaEncryptionAlgorithm, JwaSignatureAlgorithm } from './alg' +export { JwaKeyType } from './kty' +export { JwaCurve } from './crv' diff --git a/packages/core/src/crypto/jose/jwa/kty.ts b/packages/core/src/crypto/jose/jwa/kty.ts new file mode 100644 index 0000000000..0601fb7b02 --- /dev/null +++ b/packages/core/src/crypto/jose/jwa/kty.ts @@ -0,0 +1,6 @@ +export enum JwaKeyType { + EC = 'EC', + RSA = 'RSA', + oct = 'oct', + OKP = 'OKP', +} diff --git a/packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts b/packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts new file mode 100644 index 0000000000..b7bd33eb39 --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts @@ -0,0 +1,87 @@ +import type { JwkJson } from './Jwk' +import type { Buffer } from '../../../utils' + +import { TypedArrayEncoder } from '../../../utils' +import { KeyType } from '../../KeyType' +import { JwaCurve, JwaKeyType } from '../jwa' +import { JwaSignatureAlgorithm } from '../jwa/alg' + +import { Jwk } from './Jwk' +import { hasKty, hasCrv, hasX, hasValidUse } from './validate' + +export class Ed25519Jwk extends Jwk { + public readonly x: string + + public constructor({ x }: { x: string }) { + super() + + this.x = x + } + + public get kty() { + return JwaKeyType.OKP as const + } + + public get crv() { + return JwaCurve.Ed25519 as const + } + + public get keyType() { + return KeyType.Ed25519 + } + + public get publicKey() { + return TypedArrayEncoder.fromBase64(this.x) + } + + public get supportedEncryptionAlgorithms() { + return [] + } + + public get supportedSignatureAlgorithms() { + return [JwaSignatureAlgorithm.EdDSA] + } + + public toJson() { + return { + ...super.toJson(), + crv: this.crv, + x: this.x, + } as Ed25519JwkJson + } + + public static fromJson(jwkJson: JwkJson) { + if (!isValidEd25519JwkPublicKey(jwkJson)) { + throw new Error("Invalid 'Ed25519' JWK.") + } + + return new Ed25519Jwk({ + x: jwkJson.x, + }) + } + + public static fromPublicKey(publicKey: Buffer) { + return new Ed25519Jwk({ + x: TypedArrayEncoder.toBase64URL(publicKey), + }) + } +} + +export interface Ed25519JwkJson extends JwkJson { + kty: JwaKeyType.OKP + crv: JwaCurve.Ed25519 + x: string + use?: 'sig' +} + +function isValidEd25519JwkPublicKey(jwk: JwkJson): jwk is Ed25519JwkJson { + return ( + hasKty(jwk, JwaKeyType.OKP) && + hasCrv(jwk, JwaCurve.Ed25519) && + hasX(jwk) && + hasValidUse(jwk, { + supportsEncrypting: false, + supportsSigning: true, + }) + ) +} diff --git a/packages/core/src/crypto/jose/jwk/Jwk.ts b/packages/core/src/crypto/jose/jwk/Jwk.ts new file mode 100644 index 0000000000..709dd2a3f9 --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/Jwk.ts @@ -0,0 +1,40 @@ +import type { Buffer } from '../../../utils' +import type { KeyType } from '../../KeyType' +import type { JwaKeyType, JwaEncryptionAlgorithm, JwaSignatureAlgorithm } from '../jwa' + +import { Key } from '../../Key' + +export interface JwkJson { + kty: string + use?: string + [key: string]: unknown +} + +export abstract class Jwk { + public abstract publicKey: Buffer + public abstract keyType: KeyType + public abstract supportedSignatureAlgorithms: JwaSignatureAlgorithm[] + public abstract supportedEncryptionAlgorithms: JwaEncryptionAlgorithm[] + + public abstract kty: JwaKeyType + public use?: string + + public toJson(): JwkJson { + return { + kty: this.kty, + use: this.use, + } + } + + public get key() { + return new Key(this.publicKey, this.keyType) + } + + public supportsSignatureAlgorithm(algorithm: JwaSignatureAlgorithm | string) { + return this.supportedSignatureAlgorithms.includes(algorithm as JwaSignatureAlgorithm) + } + + public supportsEncryptionAlgorithm(algorithm: JwaEncryptionAlgorithm | string) { + return this.supportedEncryptionAlgorithms.includes(algorithm as JwaEncryptionAlgorithm) + } +} diff --git a/packages/core/src/crypto/jose/jwk/P_256Jwk.ts b/packages/core/src/crypto/jose/jwk/P_256Jwk.ts new file mode 100644 index 0000000000..7b6eb09deb --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/P_256Jwk.ts @@ -0,0 +1,102 @@ +import type { JwkJson } from './Jwk' + +import { TypedArrayEncoder, Buffer } from '../../../utils' +import { KeyType } from '../../KeyType' +import { JwaCurve, JwaKeyType } from '../jwa' +import { JwaSignatureAlgorithm } from '../jwa/alg' + +import { Jwk } from './Jwk' +import { compress, expand } from './ecCompression' +import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' + +export class P_256Jwk extends Jwk { + public readonly x: string + public readonly y: string + + public constructor({ x, y }: { x: string; y: string }) { + super() + + this.x = x + this.y = y + } + + public get kty() { + return JwaKeyType.EC as const + } + + public get crv() { + return JwaCurve.P_256 as const + } + + public get keyType() { + return KeyType.P256 + } + + // NOTE: this is the compressed variant. We should add support for the uncompressed variant. + public get publicKey() { + const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(this.x), TypedArrayEncoder.fromBase64(this.y)]) + const compressedPublicKey = compress(publicKeyBuffer) + + return Buffer.from(compressedPublicKey) + } + + public get supportedEncryptionAlgorithms() { + return [] + } + + public get supportedSignatureAlgorithms() { + return [JwaSignatureAlgorithm.ES256] + } + + public toJson() { + return { + ...super.toJson(), + crv: this.crv, + x: this.x, + y: this.y, + } as P256JwkJson + } + + public static fromJson(jwkJson: JwkJson) { + if (!isValidP256JwkPublicKey(jwkJson)) { + throw new Error("Invalid 'P-256' JWK.") + } + + return new P_256Jwk({ + x: jwkJson.x, + y: jwkJson.y, + }) + } + + public static fromPublicKey(publicKey: Buffer) { + const expanded = expand(publicKey, JwaCurve.P_256) + const x = expanded.slice(0, expanded.length / 2) + const y = expanded.slice(expanded.length / 2) + + return new P_256Jwk({ + x: TypedArrayEncoder.toBase64URL(x), + y: TypedArrayEncoder.toBase64URL(y), + }) + } +} + +export interface P256JwkJson extends JwkJson { + kty: JwaKeyType.EC + crv: JwaCurve.P_256 + x: string + y: string + use?: 'sig' | 'enc' +} + +export function isValidP256JwkPublicKey(jwk: JwkJson): jwk is P256JwkJson { + return ( + hasKty(jwk, JwaKeyType.EC) && + hasCrv(jwk, JwaCurve.P_256) && + hasX(jwk) && + hasY(jwk) && + hasValidUse(jwk, { + supportsEncrypting: true, + supportsSigning: true, + }) + ) +} diff --git a/packages/core/src/crypto/jose/jwk/P_384Jwk.ts b/packages/core/src/crypto/jose/jwk/P_384Jwk.ts new file mode 100644 index 0000000000..9ce5b121d6 --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/P_384Jwk.ts @@ -0,0 +1,102 @@ +import type { JwkJson } from './Jwk' + +import { TypedArrayEncoder, Buffer } from '../../../utils' +import { KeyType } from '../../KeyType' +import { JwaCurve, JwaKeyType } from '../jwa' +import { JwaSignatureAlgorithm } from '../jwa/alg' + +import { Jwk } from './Jwk' +import { compress, expand } from './ecCompression' +import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' + +export class P_384Jwk extends Jwk { + public readonly x: string + public readonly y: string + + public constructor({ x, y }: { x: string; y: string }) { + super() + + this.x = x + this.y = y + } + + public get kty() { + return JwaKeyType.EC as const + } + + public get crv() { + return JwaCurve.P_384 as const + } + + public get keyType() { + return KeyType.P384 + } + + // NOTE: this is the compressed variant. We should add support for the uncompressed variant. + public get publicKey() { + const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(this.x), TypedArrayEncoder.fromBase64(this.y)]) + const compressedPublicKey = compress(publicKeyBuffer) + + return Buffer.from(compressedPublicKey) + } + + public get supportedEncryptionAlgorithms() { + return [] + } + + public get supportedSignatureAlgorithms() { + return [JwaSignatureAlgorithm.ES384] + } + + public toJson() { + return { + ...super.toJson(), + crv: this.crv, + x: this.x, + y: this.y, + } as P384JwkJson + } + + public static fromJson(jwk: JwkJson) { + if (!isValidP384JwkPublicKey(jwk)) { + throw new Error("Invalid 'P-384' JWK.") + } + + return new P_384Jwk({ + x: jwk.x, + y: jwk.y, + }) + } + + public static fromPublicKey(publicKey: Buffer) { + const expanded = expand(publicKey, JwaCurve.P_384) + const x = expanded.slice(0, expanded.length / 2) + const y = expanded.slice(expanded.length / 2) + + return new P_384Jwk({ + x: TypedArrayEncoder.toBase64URL(x), + y: TypedArrayEncoder.toBase64URL(y), + }) + } +} + +export interface P384JwkJson extends JwkJson { + kty: JwaKeyType.EC + crv: JwaCurve.P_384 + x: string + y: string + use?: 'sig' | 'enc' +} + +export function isValidP384JwkPublicKey(jwk: JwkJson): jwk is P384JwkJson { + return ( + hasKty(jwk, JwaKeyType.EC) && + hasCrv(jwk, JwaCurve.P_384) && + hasX(jwk) && + hasY(jwk) && + hasValidUse(jwk, { + supportsEncrypting: true, + supportsSigning: true, + }) + ) +} diff --git a/packages/core/src/crypto/jose/jwk/P_521Jwk.ts b/packages/core/src/crypto/jose/jwk/P_521Jwk.ts new file mode 100644 index 0000000000..b9bcd092de --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/P_521Jwk.ts @@ -0,0 +1,102 @@ +import type { JwkJson } from './Jwk' + +import { TypedArrayEncoder, Buffer } from '../../../utils' +import { KeyType } from '../../KeyType' +import { JwaCurve, JwaKeyType } from '../jwa' +import { JwaSignatureAlgorithm } from '../jwa/alg' + +import { Jwk } from './Jwk' +import { compress, expand } from './ecCompression' +import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' + +export class P_521Jwk extends Jwk { + public readonly x: string + public readonly y: string + + public constructor({ x, y }: { x: string; y: string }) { + super() + + this.x = x + this.y = y + } + + public get kty() { + return JwaKeyType.EC as const + } + + public get crv() { + return JwaCurve.P_521 as const + } + + public get keyType() { + return KeyType.P521 + } + + // NOTE: this is the compressed variant. We should add support for the uncompressed variant. + public get publicKey() { + const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(this.x), TypedArrayEncoder.fromBase64(this.y)]) + const compressedPublicKey = compress(publicKeyBuffer) + + return Buffer.from(compressedPublicKey) + } + + public get supportedEncryptionAlgorithms() { + return [] + } + + public get supportedSignatureAlgorithms() { + return [JwaSignatureAlgorithm.ES512] + } + + public toJson() { + return { + ...super.toJson(), + crv: this.crv, + x: this.x, + y: this.y, + } as P521JwkJson + } + + public static fromJson(jwk: JwkJson) { + if (!isValidP521JwkPublicKey(jwk)) { + throw new Error("Invalid 'P-521' JWK.") + } + + return new P_521Jwk({ + x: jwk.x, + y: jwk.y, + }) + } + + public static fromPublicKey(publicKey: Buffer) { + const expanded = expand(publicKey, JwaCurve.P_521) + const x = expanded.slice(0, expanded.length / 2) + const y = expanded.slice(expanded.length / 2) + + return new P_521Jwk({ + x: TypedArrayEncoder.toBase64URL(x), + y: TypedArrayEncoder.toBase64URL(y), + }) + } +} + +export interface P521JwkJson extends JwkJson { + kty: JwaKeyType.EC + crv: JwaCurve.P_521 + x: string + y: string + use?: 'sig' | 'enc' +} + +export function isValidP521JwkPublicKey(jwk: JwkJson): jwk is P521JwkJson { + return ( + hasKty(jwk, JwaKeyType.EC) && + hasCrv(jwk, JwaCurve.P_521) && + hasX(jwk) && + hasY(jwk) && + hasValidUse(jwk, { + supportsEncrypting: true, + supportsSigning: true, + }) + ) +} diff --git a/packages/core/src/crypto/jose/jwk/X25519Jwk.ts b/packages/core/src/crypto/jose/jwk/X25519Jwk.ts new file mode 100644 index 0000000000..2a3ce9d968 --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/X25519Jwk.ts @@ -0,0 +1,91 @@ +import type { JwkJson } from './Jwk' +import type { Buffer } from '../../../utils' + +import { TypedArrayEncoder } from '../../../utils' +import { KeyType } from '../../KeyType' +import { JwaCurve, JwaKeyType, JwaEncryptionAlgorithm } from '../jwa' + +import { Jwk } from './Jwk' +import { hasCrv, hasKty, hasValidUse, hasX } from './validate' + +export class X25519Jwk extends Jwk { + public readonly x: string + + public constructor({ x }: { x: string }) { + super() + + this.x = x + } + + public get kty() { + return JwaKeyType.OKP as const + } + + public get crv() { + return JwaCurve.X25519 as const + } + + public get keyType() { + return KeyType.X25519 + } + + public get publicKey() { + return TypedArrayEncoder.fromBase64(this.x) + } + + public get supportedEncryptionAlgorithms() { + return [ + JwaEncryptionAlgorithm.ECDH_ES_A128KW, + JwaEncryptionAlgorithm.ECDH_ES_A192KW, + JwaEncryptionAlgorithm.ECDH_ES_A256KW, + JwaEncryptionAlgorithm.ECDH_ES, + ] + } + + public get supportedSignatureAlgorithms() { + return [] + } + + public toJson() { + return { + ...super.toJson(), + crv: this.crv, + x: this.x, + } as X25519JwkJson + } + + public static fromJson(jwk: JwkJson) { + if (!isValidX25519JwkPublicKey(jwk)) { + throw new Error("Invalid 'X25519' JWK.") + } + + return new X25519Jwk({ + x: jwk.x, + }) + } + + public static fromPublicKey(publicKey: Buffer) { + return new X25519Jwk({ + x: TypedArrayEncoder.toBase64URL(publicKey), + }) + } +} + +export interface X25519JwkJson extends JwkJson { + kty: JwaKeyType.OKP + crv: JwaCurve.X25519 + x: string + use?: 'enc' +} + +function isValidX25519JwkPublicKey(jwk: JwkJson): jwk is X25519JwkJson { + return ( + hasKty(jwk, JwaKeyType.OKP) && + hasCrv(jwk, JwaCurve.X25519) && + hasX(jwk) && + hasValidUse(jwk, { + supportsEncrypting: true, + supportsSigning: false, + }) + ) +} diff --git a/packages/core/src/crypto/jose/jwk/__tests__/Ed25519Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/Ed25519Jwk.test.ts new file mode 100644 index 0000000000..a2f07ecc7f --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/__tests__/Ed25519Jwk.test.ts @@ -0,0 +1,36 @@ +import { TypedArrayEncoder } from '../../../../utils' +import { KeyType } from '../../../KeyType' +import { Ed25519Jwk } from '../Ed25519Jwk' + +const jwkJson = { + kty: 'OKP', + crv: 'Ed25519', + x: 'O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik', +} + +describe('Ed25519JWk', () => { + test('has correct properties', () => { + const jwk = new Ed25519Jwk({ x: jwkJson.x }) + + expect(jwk.kty).toEqual('OKP') + expect(jwk.crv).toEqual('Ed25519') + expect(jwk.keyType).toEqual(KeyType.Ed25519) + expect(jwk.publicKey).toEqual(TypedArrayEncoder.fromBase64(jwkJson.x)) + expect(jwk.supportedEncryptionAlgorithms).toEqual([]) + expect(jwk.supportedSignatureAlgorithms).toEqual(['EdDSA']) + expect(jwk.key.keyType).toEqual(KeyType.Ed25519) + expect(jwk.toJson()).toEqual(jwkJson) + }) + + test('fromJson', () => { + const jwk = Ed25519Jwk.fromJson(jwkJson) + expect(jwk.x).toEqual(jwkJson.x) + + expect(() => Ed25519Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'Ed25519' JWK.") + }) + + test('fromPublicKey', () => { + const jwk = Ed25519Jwk.fromPublicKey(TypedArrayEncoder.fromBase64(jwkJson.x)) + expect(jwk.x).toEqual(jwkJson.x) + }) +}) diff --git a/packages/core/src/crypto/jose/jwk/__tests__/P_256Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/P_256Jwk.test.ts new file mode 100644 index 0000000000..fe941e8855 --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/__tests__/P_256Jwk.test.ts @@ -0,0 +1,52 @@ +import { TypedArrayEncoder, Buffer } from '../../../../utils' +import { KeyType } from '../../../KeyType' +import { P_256Jwk } from '../P_256Jwk' +import { compress } from '../ecCompression' + +const jwkJson = { + kty: 'EC', + crv: 'P-256', + x: 'igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns', + y: 'efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM', +} + +describe('P_256JWk', () => { + test('has correct properties', () => { + const jwk = new P_256Jwk({ x: jwkJson.x, y: jwkJson.y }) + + expect(jwk.kty).toEqual('EC') + expect(jwk.crv).toEqual('P-256') + expect(jwk.keyType).toEqual(KeyType.P256) + + const publicKeyBuffer = Buffer.concat([ + TypedArrayEncoder.fromBase64(jwkJson.x), + TypedArrayEncoder.fromBase64(jwkJson.y), + ]) + const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) + expect(jwk.publicKey).toEqual(compressedPublicKey) + expect(jwk.supportedEncryptionAlgorithms).toEqual([]) + expect(jwk.supportedSignatureAlgorithms).toEqual(['ES256']) + expect(jwk.key.keyType).toEqual(KeyType.P256) + expect(jwk.toJson()).toEqual(jwkJson) + }) + + test('fromJson', () => { + const jwk = P_256Jwk.fromJson(jwkJson) + expect(jwk.x).toEqual(jwkJson.x) + expect(jwk.y).toEqual(jwkJson.y) + + expect(() => P_256Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'P-256' JWK.") + }) + + test('fromPublicKey', () => { + const publicKeyBuffer = Buffer.concat([ + TypedArrayEncoder.fromBase64(jwkJson.x), + TypedArrayEncoder.fromBase64(jwkJson.y), + ]) + const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) + + const jwk = P_256Jwk.fromPublicKey(compressedPublicKey) + expect(jwk.x).toEqual(jwkJson.x) + expect(jwk.y).toEqual(jwkJson.y) + }) +}) diff --git a/packages/core/src/crypto/jose/jwk/__tests__/P_384Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/P_384Jwk.test.ts new file mode 100644 index 0000000000..5fb93b4cb1 --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/__tests__/P_384Jwk.test.ts @@ -0,0 +1,51 @@ +import { TypedArrayEncoder, Buffer } from '../../../../utils' +import { KeyType } from '../../../KeyType' +import { P_384Jwk } from '../P_384Jwk' +import { compress } from '../ecCompression' + +const jwkJson = { + kty: 'EC', + crv: 'P-384', + x: 'lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc', + y: 'y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv', +} + +describe('P_384JWk', () => { + test('has correct properties', () => { + const jwk = new P_384Jwk({ x: jwkJson.x, y: jwkJson.y }) + + expect(jwk.kty).toEqual('EC') + expect(jwk.crv).toEqual('P-384') + expect(jwk.keyType).toEqual(KeyType.P384) + const publicKeyBuffer = Buffer.concat([ + TypedArrayEncoder.fromBase64(jwkJson.x), + TypedArrayEncoder.fromBase64(jwkJson.y), + ]) + const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) + expect(jwk.publicKey).toEqual(compressedPublicKey) + expect(jwk.supportedEncryptionAlgorithms).toEqual([]) + expect(jwk.supportedSignatureAlgorithms).toEqual(['ES384']) + expect(jwk.key.keyType).toEqual(KeyType.P384) + expect(jwk.toJson()).toEqual(jwkJson) + }) + + test('fromJson', () => { + const jwk = P_384Jwk.fromJson(jwkJson) + expect(jwk.x).toEqual(jwkJson.x) + expect(jwk.y).toEqual(jwkJson.y) + + expect(() => P_384Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'P-384' JWK.") + }) + + test('fromPublicKey', () => { + const publicKeyBuffer = Buffer.concat([ + TypedArrayEncoder.fromBase64(jwkJson.x), + TypedArrayEncoder.fromBase64(jwkJson.y), + ]) + const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) + + const jwk = P_384Jwk.fromPublicKey(compressedPublicKey) + expect(jwk.x).toEqual(jwkJson.x) + expect(jwk.y).toEqual(jwkJson.y) + }) +}) diff --git a/packages/core/src/crypto/jose/jwk/__tests__/P_521Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/P_521Jwk.test.ts new file mode 100644 index 0000000000..6e2fad628a --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/__tests__/P_521Jwk.test.ts @@ -0,0 +1,51 @@ +import { TypedArrayEncoder, Buffer } from '../../../../utils' +import { KeyType } from '../../../KeyType' +import { P_521Jwk } from '../P_521Jwk' +import { compress } from '../ecCompression' + +const jwkJson = { + kty: 'EC', + crv: 'P-521', + x: 'ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS', + y: 'AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC', +} + +describe('P_521JWk', () => { + test('has correct properties', () => { + const jwk = new P_521Jwk({ x: jwkJson.x, y: jwkJson.y }) + + expect(jwk.kty).toEqual('EC') + expect(jwk.crv).toEqual('P-521') + expect(jwk.keyType).toEqual(KeyType.P521) + const publicKeyBuffer = Buffer.concat([ + TypedArrayEncoder.fromBase64(jwkJson.x), + TypedArrayEncoder.fromBase64(jwkJson.y), + ]) + const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) + expect(jwk.publicKey).toEqual(compressedPublicKey) + expect(jwk.supportedEncryptionAlgorithms).toEqual([]) + expect(jwk.supportedSignatureAlgorithms).toEqual(['ES512']) + expect(jwk.key.keyType).toEqual(KeyType.P521) + expect(jwk.toJson()).toEqual(jwkJson) + }) + + test('fromJson', () => { + const jwk = P_521Jwk.fromJson(jwkJson) + expect(jwk.x).toEqual(jwkJson.x) + expect(jwk.y).toEqual(jwkJson.y) + + expect(() => P_521Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'P-521' JWK.") + }) + + test('fromPublicKey', () => { + const publicKeyBuffer = Buffer.concat([ + TypedArrayEncoder.fromBase64(jwkJson.x), + TypedArrayEncoder.fromBase64(jwkJson.y), + ]) + const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) + + const jwk = P_521Jwk.fromPublicKey(compressedPublicKey) + expect(jwk.x).toEqual(jwkJson.x) + expect(jwk.y).toEqual(jwkJson.y) + }) +}) diff --git a/packages/core/src/crypto/jose/jwk/__tests__/X25519Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/X25519Jwk.test.ts new file mode 100644 index 0000000000..138e3f59b4 --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/__tests__/X25519Jwk.test.ts @@ -0,0 +1,36 @@ +import { TypedArrayEncoder } from '../../../../utils' +import { KeyType } from '../../../KeyType' +import { X25519Jwk } from '../X25519Jwk' + +const jwkJson = { + kty: 'OKP', + crv: 'X25519', + x: 'W_Vcc7guviK-gPNDBmevVw-uJVamQV5rMNQGUwCqlH0', +} + +describe('X25519JWk', () => { + test('has correct properties', () => { + const jwk = new X25519Jwk({ x: jwkJson.x }) + + expect(jwk.kty).toEqual('OKP') + expect(jwk.crv).toEqual('X25519') + expect(jwk.keyType).toEqual(KeyType.X25519) + expect(jwk.publicKey).toEqual(TypedArrayEncoder.fromBase64(jwkJson.x)) + expect(jwk.supportedEncryptionAlgorithms).toEqual(['ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW', 'ECDH-ES']) + expect(jwk.supportedSignatureAlgorithms).toEqual([]) + expect(jwk.key.keyType).toEqual(KeyType.X25519) + expect(jwk.toJson()).toEqual(jwkJson) + }) + + test('fromJson', () => { + const jwk = X25519Jwk.fromJson(jwkJson) + expect(jwk.x).toEqual(jwkJson.x) + + expect(() => X25519Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'X25519' JWK.") + }) + + test('fromPublicKey', () => { + const jwk = X25519Jwk.fromPublicKey(TypedArrayEncoder.fromBase64(jwkJson.x)) + expect(jwk.x).toEqual(jwkJson.x) + }) +}) diff --git a/packages/core/src/crypto/EcCompression.ts b/packages/core/src/crypto/jose/jwk/ecCompression.ts similarity index 98% rename from packages/core/src/crypto/EcCompression.ts rename to packages/core/src/crypto/jose/jwk/ecCompression.ts index 42cbd30398..cfde82d11d 100644 --- a/packages/core/src/crypto/EcCompression.ts +++ b/packages/core/src/crypto/jose/jwk/ecCompression.ts @@ -5,7 +5,7 @@ // native BigInteger is only supported in React Native 0.70+, so we use big-integer for now. import bigInt from 'big-integer' -import { Buffer } from '../utils/buffer' +import { Buffer } from '../../../utils/buffer' const curveToPointLength = { 'P-256': 64, diff --git a/packages/core/src/crypto/jose/jwk/index.ts b/packages/core/src/crypto/jose/jwk/index.ts new file mode 100644 index 0000000000..ead8c51a0d --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/index.ts @@ -0,0 +1,7 @@ +export { getJwkFromJson, getJwkFromKey } from './transform' +export { Ed25519Jwk } from './Ed25519Jwk' +export { X25519Jwk } from './X25519Jwk' +export { P_256Jwk } from './P_256Jwk' +export { P_384Jwk } from './P_384Jwk' +export { P_521Jwk } from './P_521Jwk' +export { Jwk } from './Jwk' diff --git a/packages/core/src/crypto/jose/jwk/transform.ts b/packages/core/src/crypto/jose/jwk/transform.ts new file mode 100644 index 0000000000..fd6c133736 --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/transform.ts @@ -0,0 +1,38 @@ +import type { JwkJson, Jwk } from './Jwk' +import type { Key } from '../../Key' + +import { KeyType } from '../../KeyType' +import { JwaCurve, JwaKeyType } from '../jwa' + +import { Ed25519Jwk } from './Ed25519Jwk' +import { P_256Jwk } from './P_256Jwk' +import { P_384Jwk } from './P_384Jwk' +import { P_521Jwk } from './P_521Jwk' +import { X25519Jwk } from './X25519Jwk' +import { hasCrv } from './validate' + +export function getJwkFromJson(jwkJson: JwkJson): Jwk { + if (jwkJson.kty === JwaKeyType.OKP) { + if (hasCrv(jwkJson, JwaCurve.Ed25519)) return Ed25519Jwk.fromJson(jwkJson) + if (hasCrv(jwkJson, JwaCurve.X25519)) return X25519Jwk.fromJson(jwkJson) + } + + if (jwkJson.kty === JwaKeyType.EC) { + if (hasCrv(jwkJson, JwaCurve.P_256)) return P_256Jwk.fromJson(jwkJson) + if (hasCrv(jwkJson, JwaCurve.P_384)) return P_384Jwk.fromJson(jwkJson) + if (hasCrv(jwkJson, JwaCurve.P_521)) return P_521Jwk.fromJson(jwkJson) + } + + throw new Error(`Cannot create JWK from JSON. Unsupported JWK with kty '${jwkJson.kty}'.`) +} + +export function getJwkFromKey(key: Key) { + if (key.keyType === KeyType.Ed25519) return Ed25519Jwk.fromPublicKey(key.publicKey) + if (key.keyType === KeyType.X25519) return X25519Jwk.fromPublicKey(key.publicKey) + + if (key.keyType === KeyType.P256) return P_256Jwk.fromPublicKey(key.publicKey) + if (key.keyType === KeyType.P384) return P_384Jwk.fromPublicKey(key.publicKey) + if (key.keyType === KeyType.P521) return P_521Jwk.fromPublicKey(key.publicKey) + + throw new Error(`Cannot create JWK from key. Unsupported key with type '${key.keyType}'.`) +} diff --git a/packages/core/src/crypto/jose/jwk/validate.ts b/packages/core/src/crypto/jose/jwk/validate.ts new file mode 100644 index 0000000000..b4e922c323 --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/validate.ts @@ -0,0 +1,25 @@ +import type { JwkJson } from './Jwk' +import type { JwaCurve, JwaKeyType } from '../jwa' + +export function hasCrv(jwk: JwkJson, crv: JwaCurve): jwk is JwkJson & { crv: JwaCurve } { + return 'crv' in jwk && (!crv || jwk.crv === crv) +} + +export function hasKty(jwk: JwkJson, kty: JwaKeyType) { + return 'kty' in jwk && jwk.kty === kty +} + +export function hasX(jwk: JwkJson): jwk is JwkJson & { x: string } { + return 'x' in jwk && jwk.x !== undefined +} + +export function hasY(jwk: JwkJson): jwk is JwkJson & { y: string } { + return 'y' in jwk && jwk.y !== undefined +} + +export function hasValidUse( + jwk: JwkJson, + { supportsSigning, supportsEncrypting }: { supportsSigning: boolean; supportsEncrypting: boolean } +) { + return jwk.use === undefined || (supportsSigning && jwk.use === 'sig') || (supportsEncrypting && jwk.use === 'enc') +} diff --git a/packages/core/src/crypto/keyUtils.ts b/packages/core/src/crypto/keyUtils.ts index d772c63e92..7b667f7aa7 100644 --- a/packages/core/src/crypto/keyUtils.ts +++ b/packages/core/src/crypto/keyUtils.ts @@ -3,7 +3,7 @@ import { Buffer } from '../utils' import { KeyType } from './KeyType' export function isValidSeed(seed: Buffer, keyType: KeyType): boolean { - const minimumSeedLength: Record = { + const minimumSeedLength = { [KeyType.Ed25519]: 32, [KeyType.X25519]: 32, [KeyType.Bls12381g1]: 32, @@ -12,13 +12,13 @@ export function isValidSeed(seed: Buffer, keyType: KeyType): boolean { [KeyType.P256]: 64, [KeyType.P384]: 64, [KeyType.P521]: 64, - } + } as const return Buffer.isBuffer(seed) && seed.length >= minimumSeedLength[keyType] } export function isValidPrivateKey(privateKey: Buffer, keyType: KeyType): boolean { - const privateKeyLength: Record = { + const privateKeyLength = { [KeyType.Ed25519]: 32, [KeyType.X25519]: 32, [KeyType.Bls12381g1]: 32, @@ -27,7 +27,37 @@ export function isValidPrivateKey(privateKey: Buffer, keyType: KeyType): boolean [KeyType.P256]: 32, [KeyType.P384]: 48, [KeyType.P521]: 66, - } + } as const return Buffer.isBuffer(privateKey) && privateKey.length === privateKeyLength[keyType] } + +export function isSigningSupportedForKeyType(keyType: KeyType): boolean { + const keyTypeSigningSupportedMapping = { + [KeyType.Ed25519]: true, + [KeyType.X25519]: false, + [KeyType.P256]: true, + [KeyType.P384]: true, + [KeyType.P521]: true, + [KeyType.Bls12381g1]: true, + [KeyType.Bls12381g2]: true, + [KeyType.Bls12381g1g2]: true, + } as const + + return keyTypeSigningSupportedMapping[keyType] +} + +export function isEncryptionSupportedForKeyType(keyType: KeyType): boolean { + const keyTypeEncryptionSupportedMapping = { + [KeyType.Ed25519]: false, + [KeyType.X25519]: true, + [KeyType.P256]: true, + [KeyType.P384]: true, + [KeyType.P521]: true, + [KeyType.Bls12381g1]: false, + [KeyType.Bls12381g2]: false, + [KeyType.Bls12381g1g2]: false, + } as const + + return keyTypeEncryptionSupportedMapping[keyType] +} diff --git a/packages/core/src/modules/connections/DidExchangeProtocol.ts b/packages/core/src/modules/connections/DidExchangeProtocol.ts index e7f56e23ec..b22bb487d7 100644 --- a/packages/core/src/modules/connections/DidExchangeProtocol.ts +++ b/packages/core/src/modules/connections/DidExchangeProtocol.ts @@ -10,6 +10,8 @@ import type { OutOfBandRecord } from '../oob/repository' import { InjectionSymbols } from '../../constants' import { Key, KeyType } from '../../crypto' import { JwsService } from '../../crypto/JwsService' +import { JwaSignatureAlgorithm } from '../../crypto/jose/jwa' +import { getJwkFromKey } from '../../crypto/jose/jwk' import { Attachment, AttachmentData } from '../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../error' import { Logger } from '../../logger' @@ -475,8 +477,8 @@ export class DidExchangeProtocol { kid, }, protectedHeaderOptions: { - alg: 'EdDSA', - jwk: key.toJwk(), + alg: JwaSignatureAlgorithm.EdDSA, + jwk: getJwkFromKey(key), }, }) didDocAttach.addJws(jws) diff --git a/packages/core/src/modules/dids/domain/DidDocument.ts b/packages/core/src/modules/dids/domain/DidDocument.ts index b4d4525e36..994ab81f53 100644 --- a/packages/core/src/modules/dids/domain/DidDocument.ts +++ b/packages/core/src/modules/dids/domain/DidDocument.ts @@ -3,7 +3,8 @@ import type { DidDocumentService } from './service' import { Expose, Type } from 'class-transformer' import { IsArray, IsOptional, IsString, ValidateNested } from 'class-validator' -import { KeyType, Key } from '../../../crypto' +import { Key } from '../../../crypto/Key' +import { KeyType } from '../../../crypto/KeyType' import { JsonTransformer } from '../../../utils/JsonTransformer' import { IsStringOrStringArray } from '../../../utils/transformers' diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts index aee5774210..f6c7f61bb3 100644 --- a/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts @@ -1,8 +1,8 @@ import type { KeyDidMapping } from './keyDidMapping' import type { VerificationMethod } from '../verificationMethod' -import { KeyType } from '../../../../crypto' import { Key } from '../../../../crypto/Key' +import { KeyType } from '../../../../crypto/KeyType' const VERIFICATION_METHOD_TYPE_BLS12381G1_KEY_2020 = 'Bls12381G1Key2020' diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g1g2.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g1g2.ts index 55b0d8c949..360d22a8ab 100644 --- a/packages/core/src/modules/dids/domain/key-type/bls12381g1g2.ts +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g1g2.ts @@ -1,7 +1,7 @@ import type { KeyDidMapping } from './keyDidMapping' -import { KeyType } from '../../../../crypto' import { Key } from '../../../../crypto/Key' +import { KeyType } from '../../../../crypto/KeyType' import { getBls12381g1VerificationMethod } from './bls12381g1' import { getBls12381g2VerificationMethod } from './bls12381g2' diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts index e980f9d142..2715a470bd 100644 --- a/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts @@ -1,8 +1,8 @@ import type { KeyDidMapping } from './keyDidMapping' import type { VerificationMethod } from '../verificationMethod' -import { KeyType } from '../../../../crypto' import { Key } from '../../../../crypto/Key' +import { KeyType } from '../../../../crypto/KeyType' export const VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020 = 'Bls12381G2Key2020' diff --git a/packages/core/src/modules/dids/domain/key-type/ed25519.ts b/packages/core/src/modules/dids/domain/key-type/ed25519.ts index 4d96a43e6c..3a370ee4ac 100644 --- a/packages/core/src/modules/dids/domain/key-type/ed25519.ts +++ b/packages/core/src/modules/dids/domain/key-type/ed25519.ts @@ -3,7 +3,8 @@ import type { VerificationMethod } from '../verificationMethod' import { convertPublicKeyToX25519 } from '@stablelib/ed25519' -import { Key, KeyType } from '../../../../crypto' +import { Key } from '../../../../crypto/Key' +import { KeyType } from '../../../../crypto/KeyType' export const VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018 = 'Ed25519VerificationKey2018' export const VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020 = 'Ed25519VerificationKey2020' diff --git a/packages/core/src/modules/dids/domain/key-type/keyDidJsonWebKey.ts b/packages/core/src/modules/dids/domain/key-type/keyDidJsonWebKey.ts index 3dd7c19d05..ec5e179d0b 100644 --- a/packages/core/src/modules/dids/domain/key-type/keyDidJsonWebKey.ts +++ b/packages/core/src/modules/dids/domain/key-type/keyDidJsonWebKey.ts @@ -1,7 +1,7 @@ import type { KeyDidMapping } from './keyDidMapping' import type { VerificationMethod } from '../verificationMethod' -import { Key } from '../../../../crypto' +import { getJwkFromJson } from '../../../../crypto/jose/jwk' import { AriesFrameworkError } from '../../../../error' import { getJsonWebKey2020VerificationMethod } from '../verificationMethod' import { VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020, isJsonWebKey2020 } from '../verificationMethod/JsonWebKey2020' @@ -15,6 +15,6 @@ export const keyDidJsonWebKey: KeyDidMapping = { throw new AriesFrameworkError('Invalid verification method passed') } - return Key.fromJwk(verificationMethod.publicKeyJwk) + return getJwkFromJson(verificationMethod.publicKeyJwk).key }, } diff --git a/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts b/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts index 1449892e9b..7a07f6cce7 100644 --- a/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts +++ b/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts @@ -1,7 +1,8 @@ +import type { Key } from '../../../../crypto/Key' import type { VerificationMethod } from '../verificationMethod' -import { KeyType } from '../../../../crypto' -import { Key } from '../../../../crypto/Key' +import { KeyType } from '../../../../crypto/KeyType' +import { getJwkFromJson } from '../../../../crypto/jose/jwk' import { AriesFrameworkError } from '../../../../error' import { isJsonWebKey2020, VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020 } from '../verificationMethod/JsonWebKey2020' @@ -76,7 +77,7 @@ export function getKeyFromVerificationMethod(verificationMethod: VerificationMet ) } - return Key.fromJwk(verificationMethod.publicKeyJwk) + return getJwkFromJson(verificationMethod.publicKeyJwk).key } const keyDid = verificationMethodKeyDidMapping[verificationMethod.type] diff --git a/packages/core/src/modules/dids/domain/key-type/x25519.ts b/packages/core/src/modules/dids/domain/key-type/x25519.ts index 399029928e..c49335b3bf 100644 --- a/packages/core/src/modules/dids/domain/key-type/x25519.ts +++ b/packages/core/src/modules/dids/domain/key-type/x25519.ts @@ -1,8 +1,8 @@ import type { KeyDidMapping } from './keyDidMapping' import type { VerificationMethod } from '../verificationMethod' -import { KeyType } from '../../../../crypto' import { Key } from '../../../../crypto/Key' +import { KeyType } from '../../../../crypto/KeyType' const VERIFICATION_METHOD_TYPE_X25519_KEY_AGREEMENT_KEY_2019 = 'X25519KeyAgreementKey2019' diff --git a/packages/core/src/modules/dids/domain/keyDidDocument.ts b/packages/core/src/modules/dids/domain/keyDidDocument.ts index af97546202..c32d7e2e2a 100644 --- a/packages/core/src/modules/dids/domain/keyDidDocument.ts +++ b/packages/core/src/modules/dids/domain/keyDidDocument.ts @@ -1,7 +1,8 @@ import type { DidDocument } from './DidDocument' import type { VerificationMethod } from './verificationMethod/VerificationMethod' -import { KeyType, Key } from '../../../crypto' +import { Key } from '../../../crypto/Key' +import { KeyType } from '../../../crypto/KeyType' import { AriesFrameworkError } from '../../../error' import { SECURITY_CONTEXT_BBS_URL, SECURITY_JWS_CONTEXT_URL, SECURITY_X25519_CONTEXT_URL } from '../../vc/constants' import { ED25519_SUITE_CONTEXT_URL_2018 } from '../../vc/signature-suites/ed25519/constants' diff --git a/packages/core/src/modules/dids/domain/verificationMethod/JsonWebKey2020.ts b/packages/core/src/modules/dids/domain/verificationMethod/JsonWebKey2020.ts index a007f1d1fc..b4c4eed311 100644 --- a/packages/core/src/modules/dids/domain/verificationMethod/JsonWebKey2020.ts +++ b/packages/core/src/modules/dids/domain/verificationMethod/JsonWebKey2020.ts @@ -1,11 +1,12 @@ import type { VerificationMethod } from './VerificationMethod' -import type { Jwk } from '../../../../crypto' +import type { Key } from '../../../../crypto/Key' +import type { JwkJson } from '../../../../crypto/jose/jwk/Jwk' -import { Key } from '../../../../crypto' +import { getJwkFromKey } from '../../../../crypto/jose/jwk' export const VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020 = 'JsonWebKey2020' -type JwkOrKey = { jwk: Jwk; key?: never } | { key: Key; jwk?: never } +type JwkOrKey = { jwk: JwkJson; key?: never } | { key: Key; jwk?: never } type GetJsonWebKey2020VerificationMethodOptions = { did: string @@ -19,7 +20,7 @@ export function getJsonWebKey2020VerificationMethod({ verificationMethodId, }: GetJsonWebKey2020VerificationMethodOptions) { if (!verificationMethodId) { - const k = key ?? Key.fromJwk(jwk) + const k = key ?? jwk.key verificationMethodId = `${did}#${k.fingerprint}` } @@ -27,7 +28,7 @@ export function getJsonWebKey2020VerificationMethod({ id: verificationMethodId, type: VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020, controller: did, - publicKeyJwk: jwk ?? key.toJwk(), + publicKeyJwk: jwk ?? getJwkFromKey(key).toJson(), } } diff --git a/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethod.ts b/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethod.ts index 632596fd57..b520a0955c 100644 --- a/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethod.ts +++ b/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethod.ts @@ -1,4 +1,4 @@ -import type { Jwk } from '../../../../crypto' +import type { JwkJson } from '../../../../crypto/jose/jwk/Jwk' import { IsString, IsOptional } from 'class-validator' @@ -8,7 +8,7 @@ export interface VerificationMethodOptions { controller: string publicKeyBase58?: string publicKeyBase64?: string - publicKeyJwk?: Jwk + publicKeyJwk?: JwkJson publicKeyHex?: string publicKeyMultibase?: string publicKeyPem?: string @@ -51,7 +51,7 @@ export class VerificationMethod { public publicKeyBase64?: string // TODO: validation of JWK - public publicKeyJwk?: Jwk + public publicKeyJwk?: JwkJson @IsOptional() @IsString() diff --git a/packages/core/src/modules/dids/methods/jwk/DidJwk.ts b/packages/core/src/modules/dids/methods/jwk/DidJwk.ts index f83b956e24..fe0df0056b 100644 --- a/packages/core/src/modules/dids/methods/jwk/DidJwk.ts +++ b/packages/core/src/modules/dids/methods/jwk/DidJwk.ts @@ -1,6 +1,6 @@ import type { Jwk } from '../../../../crypto' -import { Key } from '../../../../crypto/Key' +import { getJwkFromJson } from '../../../../crypto/jose/jwk' import { JsonEncoder } from '../../../../utils' import { parseDid } from '../../domain/parse' @@ -22,33 +22,37 @@ export class DidJwk { } public static fromDid(did: string) { - // We create a `Key` instance form the jwk, as that validates the jwk const parsed = parseDid(did) - const jwk = JsonEncoder.fromBase64(parsed.id) as Jwk - Key.fromJwk(jwk) + const jwkJson = JsonEncoder.fromBase64(parsed.id) + // This validates the jwk + getJwkFromJson(jwkJson) return new DidJwk(did) } public static fromJwk(jwk: Jwk) { - // We create a `Key` instance form the jwk, as that validates the jwk - Key.fromJwk(jwk) - const did = `did:jwk:${JsonEncoder.toBase64URL(jwk)}` + const did = `did:jwk:${JsonEncoder.toBase64URL(jwk.toJson())}` return new DidJwk(did) } public get key() { - return Key.fromJwk(this.jwk) + return this.jwk.key } public get jwk() { - const parsed = parseDid(this.did) - const jwk = JsonEncoder.fromBase64(parsed.id) as Jwk + const jwk = getJwkFromJson(this.jwkJson) return jwk } + public get jwkJson() { + const parsed = parseDid(this.did) + const jwkJson = JsonEncoder.fromBase64(parsed.id) + + return jwkJson + } + public get didDocument() { return getDidJwkDocument(this) } diff --git a/packages/core/src/modules/dids/methods/jwk/JwkDidRegistrar.ts b/packages/core/src/modules/dids/methods/jwk/JwkDidRegistrar.ts index c5fc9b01e0..10991418ac 100644 --- a/packages/core/src/modules/dids/methods/jwk/JwkDidRegistrar.ts +++ b/packages/core/src/modules/dids/methods/jwk/JwkDidRegistrar.ts @@ -4,6 +4,7 @@ import type { Buffer } from '../../../../utils' import type { DidRegistrar } from '../../domain/DidRegistrar' import type { DidCreateOptions, DidCreateResult, DidDeactivateResult, DidUpdateResult } from '../../types' +import { getJwkFromKey } from '../../../../crypto/jose/jwk' import { DidDocumentRole } from '../../domain/DidDocumentRole' import { DidRepository, DidRecord } from '../../repository' @@ -37,7 +38,8 @@ export class JwkDidRegistrar implements DidRegistrar { privateKey, }) - const didJwk = DidJwk.fromJwk(key.toJwk()) + const jwk = getJwkFromKey(key) + const didJwk = DidJwk.fromJwk(jwk) // Save the did so we know we created it and can issue with it const didRecord = new DidRecord({ diff --git a/packages/core/src/modules/dids/methods/jwk/__tests__/DidJwk.test.ts b/packages/core/src/modules/dids/methods/jwk/__tests__/DidJwk.test.ts index 406abe86a8..036e0c940d 100644 --- a/packages/core/src/modules/dids/methods/jwk/__tests__/DidJwk.test.ts +++ b/packages/core/src/modules/dids/methods/jwk/__tests__/DidJwk.test.ts @@ -1,3 +1,4 @@ +import { getJwkFromJson } from '../../../../../crypto/jose/jwk' import { DidJwk } from '../DidJwk' import { p256DidJwkEyJjcnYi0iFixture } from './__fixtures__/p256DidJwkEyJjcnYi0i' @@ -8,14 +9,14 @@ describe('DidJwk', () => { const documentTypes = [p256DidJwkEyJjcnYi0iFixture, x25519DidJwkEyJrdHkiOiJFixture] for (const documentType of documentTypes) { - const didKey = DidJwk.fromDid(documentType.id) + const didJwk = DidJwk.fromDid(documentType.id) - expect(didKey.didDocument.toJSON()).toMatchObject(documentType) + expect(didJwk.didDocument.toJSON()).toMatchObject(documentType) } }) it('creates a DidJwk instance from a jwk instance', async () => { - const didJwk = DidJwk.fromJwk(p256DidJwkEyJjcnYi0iFixture.verificationMethod[0].publicKeyJwk) + const didJwk = DidJwk.fromJwk(getJwkFromJson(p256DidJwkEyJjcnYi0iFixture.verificationMethod[0].publicKeyJwk)) expect(didJwk.did).toBe(p256DidJwkEyJjcnYi0iFixture.id) expect(didJwk.didDocument.toJSON()).toMatchObject(p256DidJwkEyJjcnYi0iFixture) diff --git a/packages/core/src/modules/dids/methods/jwk/__tests__/JwkDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/jwk/__tests__/JwkDidRegistrar.test.ts index dc4f246b99..da69ad0234 100644 --- a/packages/core/src/modules/dids/methods/jwk/__tests__/JwkDidRegistrar.test.ts +++ b/packages/core/src/modules/dids/methods/jwk/__tests__/JwkDidRegistrar.test.ts @@ -2,7 +2,7 @@ import type { Wallet } from '../../../../../wallet' import { getAgentContext, mockFunction } from '../../../../../../tests/helpers' import { KeyType } from '../../../../../crypto' -import { Key } from '../../../../../crypto/Key' +import { getJwkFromJson } from '../../../../../crypto/jose/jwk' import { TypedArrayEncoder } from '../../../../../utils' import { JsonTransformer } from '../../../../../utils/JsonTransformer' import { WalletError } from '../../../../../wallet/error' @@ -13,15 +13,14 @@ import { JwkDidRegistrar } from '../JwkDidRegistrar' jest.mock('../../../repository/DidRepository') const DidRepositoryMock = DidRepository as jest.Mock +const jwk = getJwkFromJson({ + crv: 'P-256', + kty: 'EC', + x: 'acbIQiuMs3i8_uszEjJ2tpTtRM4EU3yz91PH6CdH2V0', + y: '_KcyLj9vWMptnmKtm46GqDz8wf74I5LKgrl2GzH3nSE', +}) const walletMock = { - createKey: jest.fn(() => - Key.fromJwk({ - crv: 'P-256', - kty: 'EC', - x: 'acbIQiuMs3i8_uszEjJ2tpTtRM4EU3yz91PH6CdH2V0', - y: '_KcyLj9vWMptnmKtm46GqDz8wf74I5LKgrl2GzH3nSE', - }) - ), + createKey: jest.fn(() => jwk.key), } as unknown as Wallet const didRepositoryMock = new DidRepositoryMock() diff --git a/packages/core/src/modules/dids/methods/jwk/__tests__/__fixtures__/p256DidJwkEyJjcnYi0i.ts b/packages/core/src/modules/dids/methods/jwk/__tests__/__fixtures__/p256DidJwkEyJjcnYi0i.ts index d086154f38..f042e88502 100644 --- a/packages/core/src/modules/dids/methods/jwk/__tests__/__fixtures__/p256DidJwkEyJjcnYi0i.ts +++ b/packages/core/src/modules/dids/methods/jwk/__tests__/__fixtures__/p256DidJwkEyJjcnYi0i.ts @@ -1,33 +1,33 @@ export const p256DidJwkEyJjcnYi0iFixture = { '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], - id: 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9', + id: 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9', verificationMethod: [ { - id: 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + id: 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', type: 'JsonWebKey2020', controller: - 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9', + 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9', publicKeyJwk: { - crv: 'P-256', kty: 'EC', + crv: 'P-256', x: 'acbIQiuMs3i8_uszEjJ2tpTtRM4EU3yz91PH6CdH2V0', y: '_KcyLj9vWMptnmKtm46GqDz8wf74I5LKgrl2GzH3nSE', }, }, ], assertionMethod: [ - 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', ], authentication: [ - 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', ], capabilityInvocation: [ - 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', ], capabilityDelegation: [ - 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', ], keyAgreement: [ - 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', ], } as const diff --git a/packages/core/src/modules/dids/methods/jwk/didJwkDidDocument.ts b/packages/core/src/modules/dids/methods/jwk/didJwkDidDocument.ts index 3906929375..ae91db8891 100644 --- a/packages/core/src/modules/dids/methods/jwk/didJwkDidDocument.ts +++ b/packages/core/src/modules/dids/methods/jwk/didJwkDidDocument.ts @@ -1,17 +1,22 @@ import type { DidJwk } from './DidJwk' import { AriesFrameworkError } from '../../../../error' +import { JsonEncoder } from '../../../../utils' import { SECURITY_JWS_CONTEXT_URL } from '../../../vc/constants' import { getJsonWebKey2020VerificationMethod, DidDocumentBuilder } from '../../domain' +import { parseDid } from '../../domain/parse' export function getDidJwkDocument(didJwk: DidJwk) { if (!didJwk.allowsEncrypting && !didJwk.allowsSigning) { throw new AriesFrameworkError('At least one of allowsSigning or allowsEncrypting must be enabled') } + const parsed = parseDid(didJwk.did) + const jwkJson = JsonEncoder.fromBase64(parsed.id) + const verificationMethod = getJsonWebKey2020VerificationMethod({ did: didJwk.did, - jwk: didJwk.jwk, + jwk: jwkJson, verificationMethodId: `${didJwk.did}#0`, }) diff --git a/packages/core/src/modules/dids/methods/peer/createPeerDidDocumentFromServices.ts b/packages/core/src/modules/dids/methods/peer/createPeerDidDocumentFromServices.ts index 184e678d76..6d67b49e22 100644 --- a/packages/core/src/modules/dids/methods/peer/createPeerDidDocumentFromServices.ts +++ b/packages/core/src/modules/dids/methods/peer/createPeerDidDocumentFromServices.ts @@ -2,7 +2,8 @@ import type { ResolvedDidCommService } from '../../../didcomm' import { convertPublicKeyToX25519 } from '@stablelib/ed25519' -import { KeyType, Key } from '../../../../crypto' +import { Key } from '../../../../crypto/Key' +import { KeyType } from '../../../../crypto/KeyType' import { AriesFrameworkError } from '../../../../error' import { uuid } from '../../../../utils/uuid' import { DidDocumentBuilder } from '../../domain/DidDocumentBuilder' diff --git a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo0.ts b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo0.ts index 934156d5d8..7f1831d1d6 100644 --- a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo0.ts +++ b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo0.ts @@ -1,4 +1,4 @@ -import { Key } from '../../../../crypto' +import { Key } from '../../../../crypto/Key' import { getDidDocumentForKey } from '../../domain/keyDidDocument' import { parseDid } from '../../domain/parse' diff --git a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts index eee20933ed..752a01fb08 100644 --- a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts +++ b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts @@ -2,7 +2,7 @@ import type { JsonObject } from '../../../../types' import type { OutOfBandDidCommService } from '../../../oob/domain/OutOfBandDidCommService' import type { DidDocument, VerificationMethod } from '../../domain' -import { Key } from '../../../../crypto' +import { Key } from '../../../../crypto/Key' import { JsonEncoder, JsonTransformer } from '../../../../utils' import { DidCommV1Service, DidDocumentService } from '../../domain' import { DidDocumentBuilder } from '../../domain/DidDocumentBuilder' diff --git a/packages/openid4vc-client/src/OpenId4VcClientService.ts b/packages/openid4vc-client/src/OpenId4VcClientService.ts index 629e1d589f..7f65dad450 100644 --- a/packages/openid4vc-client/src/OpenId4VcClientService.ts +++ b/packages/openid4vc-client/src/OpenId4VcClientService.ts @@ -124,7 +124,7 @@ export class OpenId4VcClientService { if (jwtKeyAlgMapping[jwt.header.alg].includes(key.keyType)) { throw new AriesFrameworkError( - `The retreived key's type does't match the JWT algorithm. Key type: ${key.keyType}, JWT algorithm: ${jwt.header.alg}` + `The retrieved key's type does't match the JWT algorithm. Key type: ${key.keyType}, JWT algorithm: ${jwt.header.alg}` ) } @@ -257,11 +257,12 @@ export class OpenId4VcClientService { this.logger.debug('Full server metadata', serverMetadata) - if (accessToken.scope) { - for (const credentialType of accessToken.scope.split(' ')) { - this.assertCredentialHasFormat(credentialFormat, credentialType, serverMetadata) - } + if (!accessToken.scope) { + throw new AriesFrameworkError( + "Access token response doesn't contain a scope. Only scoped issuer URIs are supported at this time." + ) } + this.assertCredentialHasFormat(credentialFormat, accessToken.scope, serverMetadata) // proof of possession const callbacks = this.getSignCallback(agentContext) From 0e4f920d2d05372b53668ec920083e7d4f406fc3 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 11 May 2023 10:55:57 +0200 Subject: [PATCH 2/7] fix: type issue Signed-off-by: Timo Glastra --- .../modules/dids/domain/verificationMethod/JsonWebKey2020.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/modules/dids/domain/verificationMethod/JsonWebKey2020.ts b/packages/core/src/modules/dids/domain/verificationMethod/JsonWebKey2020.ts index b4c4eed311..a752fd2173 100644 --- a/packages/core/src/modules/dids/domain/verificationMethod/JsonWebKey2020.ts +++ b/packages/core/src/modules/dids/domain/verificationMethod/JsonWebKey2020.ts @@ -2,7 +2,7 @@ import type { VerificationMethod } from './VerificationMethod' import type { Key } from '../../../../crypto/Key' import type { JwkJson } from '../../../../crypto/jose/jwk/Jwk' -import { getJwkFromKey } from '../../../../crypto/jose/jwk' +import { getJwkFromJson, getJwkFromKey } from '../../../../crypto/jose/jwk' export const VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020 = 'JsonWebKey2020' @@ -20,7 +20,7 @@ export function getJsonWebKey2020VerificationMethod({ verificationMethodId, }: GetJsonWebKey2020VerificationMethodOptions) { if (!verificationMethodId) { - const k = key ?? jwk.key + const k = key ?? getJwkFromJson(jwk).key verificationMethodId = `${did}#${k.fingerprint}` } From df3251123db86c3c7b1f23d005c7fd3e29ae4b8a Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 11 May 2023 10:56:50 +0200 Subject: [PATCH 3/7] chore: minor tweak Signed-off-by: Timo Glastra --- packages/askar/src/wallet/__tests__/AskarWallet.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/askar/src/wallet/__tests__/AskarWallet.test.ts b/packages/askar/src/wallet/__tests__/AskarWallet.test.ts index 469d1349b6..00eb26f77e 100644 --- a/packages/askar/src/wallet/__tests__/AskarWallet.test.ts +++ b/packages/askar/src/wallet/__tests__/AskarWallet.test.ts @@ -100,7 +100,7 @@ describeRunInNodeVersion([18], 'AskarWallet basic operations', () => { }) }) - test('Create P256 keypair', async () => { + test('Create P-256 keypair', async () => { await expect( askarWallet.createKey({ seed: Buffer.concat([seed, seed]), keyType: KeyType.P256 }) ).resolves.toMatchObject({ From 94ea1733dbc3960e3ca4e3f624bea3ce8c8d2040 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 11 May 2023 10:58:15 +0200 Subject: [PATCH 4/7] revert revert Signed-off-by: Timo Glastra --- packages/openid4vc-client/src/OpenId4VcClientService.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/openid4vc-client/src/OpenId4VcClientService.ts b/packages/openid4vc-client/src/OpenId4VcClientService.ts index 7f65dad450..f90c17abbd 100644 --- a/packages/openid4vc-client/src/OpenId4VcClientService.ts +++ b/packages/openid4vc-client/src/OpenId4VcClientService.ts @@ -257,12 +257,11 @@ export class OpenId4VcClientService { this.logger.debug('Full server metadata', serverMetadata) - if (!accessToken.scope) { - throw new AriesFrameworkError( - "Access token response doesn't contain a scope. Only scoped issuer URIs are supported at this time." - ) + if (accessToken.scope) { + for (const credentialType of accessToken.scope.split(' ')) { + this.assertCredentialHasFormat(credentialFormat, credentialType, serverMetadata) + } } - this.assertCredentialHasFormat(credentialFormat, accessToken.scope, serverMetadata) // proof of possession const callbacks = this.getSignCallback(agentContext) From 4004d2b4d0240defa20bdcc3881fce9bc147a8cb Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 11 May 2023 11:00:49 +0200 Subject: [PATCH 5/7] small tweak Signed-off-by: Timo Glastra --- packages/core/src/crypto/jose/jwk/validate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/crypto/jose/jwk/validate.ts b/packages/core/src/crypto/jose/jwk/validate.ts index b4e922c323..50c39c338b 100644 --- a/packages/core/src/crypto/jose/jwk/validate.ts +++ b/packages/core/src/crypto/jose/jwk/validate.ts @@ -2,7 +2,7 @@ import type { JwkJson } from './Jwk' import type { JwaCurve, JwaKeyType } from '../jwa' export function hasCrv(jwk: JwkJson, crv: JwaCurve): jwk is JwkJson & { crv: JwaCurve } { - return 'crv' in jwk && (!crv || jwk.crv === crv) + return 'crv' in jwk && jwk.crv === crv } export function hasKty(jwk: JwkJson, kty: JwaKeyType) { From 8829207787b0763ecb3a3527f5959b22786ef58a Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Mon, 15 May 2023 10:54:13 +0200 Subject: [PATCH 6/7] chore: address feedback Signed-off-by: Timo Glastra --- packages/core/package.json | 5 +++-- packages/core/src/crypto/JwsService.ts | 11 +++++----- .../src/crypto/__tests__/JwsService.test.ts | 4 +--- packages/core/src/crypto/jose/jwa/crv.ts | 6 +++--- .../jose/jwk/{P_256Jwk.ts => P256Jwk.ts} | 21 ++++++++++++------- .../jose/jwk/{P_384Jwk.ts => P384Jwk.ts} | 21 ++++++++++++------- .../jose/jwk/{P_521Jwk.ts => P521Jwk.ts} | 21 ++++++++++++------- .../jose/jwk/__tests__/P_256Jwk.test.ts | 10 ++++----- .../jose/jwk/__tests__/P_384Jwk.test.ts | 10 ++++----- .../jose/jwk/__tests__/P_521Jwk.test.ts | 10 ++++----- packages/core/src/crypto/jose/jwk/index.ts | 6 +++--- .../core/src/crypto/jose/jwk/transform.ts | 18 ++++++++-------- .../connections/DidExchangeProtocol.ts | 5 +++-- packages/core/src/modules/dids/helpers.ts | 7 +++---- .../oob/domain/OutOfBandDidCommService.ts | 3 ++- .../modules/vc/__tests__/documentLoader.ts | 3 ++- .../modules/vc/libraries/documentLoader.ts | 3 ++- packages/core/src/utils/did.ts | 11 ++++++++++ packages/core/src/utils/index.ts | 1 + packages/core/tests/setup.ts | 1 + 20 files changed, 104 insertions(+), 73 deletions(-) rename packages/core/src/crypto/jose/jwk/{P_256Jwk.ts => P256Jwk.ts} (83%) rename packages/core/src/crypto/jose/jwk/{P_384Jwk.ts => P384Jwk.ts} (83%) rename packages/core/src/crypto/jose/jwk/{P_521Jwk.ts => P521Jwk.ts} (83%) diff --git a/packages/core/package.json b/packages/core/package.json index 9eb5d56a3f..20064f5efb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -30,7 +30,6 @@ "@stablelib/ed25519": "^1.0.2", "@stablelib/random": "^1.0.1", "@stablelib/sha256": "^1.0.1", - "node-fetch": "^2.6.1", "@types/ws": "^8.5.4", "abort-controller": "^3.0.0", "big-integer": "^1.6.51", @@ -42,6 +41,7 @@ "lru_map": "^0.4.1", "luxon": "^3.3.0", "make-error": "^1.3.6", + "node-fetch": "^2.6.1", "object-inspect": "^1.10.3", "query-string": "^7.0.1", "reflect-metadata": "^0.1.13", @@ -52,13 +52,14 @@ "web-did-resolver": "^2.0.21" }, "devDependencies": { + "@hyperledger/aries-askar-nodejs": "^0.1.0-dev.8", "@types/events": "^3.0.0", "@types/luxon": "^3.2.0", + "@types/node-fetch": "2.6.2", "@types/object-inspect": "^1.8.0", "@types/uuid": "^9.0.1", "@types/varint": "^6.0.0", "nock": "^13.3.0", - "@types/node-fetch": "2.6.2", "rimraf": "^4.4.0", "tslog": "^4.8.2", "typescript": "~4.9.5" diff --git a/packages/core/src/crypto/JwsService.ts b/packages/core/src/crypto/JwsService.ts index 851582e85e..077c381230 100644 --- a/packages/core/src/crypto/JwsService.ts +++ b/packages/core/src/crypto/JwsService.ts @@ -9,7 +9,7 @@ import { getKeyFromVerificationMethod } from '../modules/dids/domain/key-type/ke import { DidKey } from '../modules/dids/methods/key/DidKey' import { DidResolverService } from '../modules/dids/services/DidResolverService' import { injectable } from '../plugins' -import { JsonEncoder, TypedArrayEncoder } from '../utils' +import { isDid, JsonEncoder, TypedArrayEncoder } from '../utils' import { WalletError } from '../wallet/error' import { getJwkFromJson, getJwkFromKey } from './jose/jwk' @@ -170,7 +170,7 @@ export class JwsService { // Kid if (protectedHeader.kid) { - if (!protectedHeader.kid.startsWith('did:')) { + if (!isDid(protectedHeader.kid)) { throw new AriesFrameworkError( `Only DIDs are supported as the 'kid' parameter for JWS. '${protectedHeader.kid}' is not a did.` ) @@ -181,12 +181,13 @@ export class JwsService { // This is a special case for Aries RFC 0017 signed attachments. It allows a did:key without a keyId to be used kid // https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0017-attachments/README.md#signing-attachments - if (protectedHeader.kid.startsWith('did:key:') && !protectedHeader.kid.includes('#')) { + if (isDid(protectedHeader.kid, 'key') && !protectedHeader.kid.includes('#')) { return getJwkFromKey(DidKey.fromDid(protectedHeader.kid).key) } - // TODO: allowedPurposes - return getJwkFromKey(getKeyFromVerificationMethod(didDocument.dereferenceKey(protectedHeader.kid))) + return getJwkFromKey( + getKeyFromVerificationMethod(didDocument.dereferenceKey(protectedHeader.kid, ['authentication'])) + ) } throw new AriesFrameworkError('Both JWK and kid are undefined. Protected header must contain one of the two.') diff --git a/packages/core/src/crypto/__tests__/JwsService.test.ts b/packages/core/src/crypto/__tests__/JwsService.test.ts index fd266e0abb..294a475b65 100644 --- a/packages/core/src/crypto/__tests__/JwsService.test.ts +++ b/packages/core/src/crypto/__tests__/JwsService.test.ts @@ -1,9 +1,6 @@ import type { AgentContext } from '../../agent' import type { Key, Wallet } from '@aries-framework/core' -// eslint-disable-next-line import/no-extraneous-dependencies -import '@hyperledger/aries-askar-nodejs' - import { describeRunInNodeVersion } from '../../../../../tests/runInVersion' import { AskarWallet } from '../../../../askar/src' import { agentDependencies, getAgentConfig, getAgentContext } from '../../../tests/helpers' @@ -19,6 +16,7 @@ import * as didJwsz6Mkf from './__fixtures__/didJwsz6Mkf' import * as didJwsz6Mkv from './__fixtures__/didJwsz6Mkv' import * as didJwszDnaey from './__fixtures__/didJwszDnaey' +// Only runs in Node18 because test uses Askar, which doesn't work well in Node16 describeRunInNodeVersion([18], 'JwsService', () => { let wallet: Wallet let agentContext: AgentContext diff --git a/packages/core/src/crypto/jose/jwa/crv.ts b/packages/core/src/crypto/jose/jwa/crv.ts index 098e009453..bdf2c4a010 100644 --- a/packages/core/src/crypto/jose/jwa/crv.ts +++ b/packages/core/src/crypto/jose/jwa/crv.ts @@ -1,7 +1,7 @@ export enum JwaCurve { - P_256 = 'P-256', - P_384 = 'P-384', - P_521 = 'P-521', + P256 = 'P-256', + P384 = 'P-384', + P521 = 'P-521', Ed25519 = 'Ed25519', X25519 = 'X25519', } diff --git a/packages/core/src/crypto/jose/jwk/P_256Jwk.ts b/packages/core/src/crypto/jose/jwk/P256Jwk.ts similarity index 83% rename from packages/core/src/crypto/jose/jwk/P_256Jwk.ts rename to packages/core/src/crypto/jose/jwk/P256Jwk.ts index 7b6eb09deb..e8f074a663 100644 --- a/packages/core/src/crypto/jose/jwk/P_256Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/P256Jwk.ts @@ -9,7 +9,7 @@ import { Jwk } from './Jwk' import { compress, expand } from './ecCompression' import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' -export class P_256Jwk extends Jwk { +export class P256Jwk extends Jwk { public readonly x: string public readonly y: string @@ -25,14 +25,19 @@ export class P_256Jwk extends Jwk { } public get crv() { - return JwaCurve.P_256 as const + return JwaCurve.P256 as const } public get keyType() { return KeyType.P256 } - // NOTE: this is the compressed variant. We should add support for the uncompressed variant. + /** + * Returns the public key of the P-256 JWK. + * + * NOTE: this is the compressed variant. We still need to add support for the + * uncompressed variant. + */ public get publicKey() { const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(this.x), TypedArrayEncoder.fromBase64(this.y)]) const compressedPublicKey = compress(publicKeyBuffer) @@ -62,18 +67,18 @@ export class P_256Jwk extends Jwk { throw new Error("Invalid 'P-256' JWK.") } - return new P_256Jwk({ + return new P256Jwk({ x: jwkJson.x, y: jwkJson.y, }) } public static fromPublicKey(publicKey: Buffer) { - const expanded = expand(publicKey, JwaCurve.P_256) + const expanded = expand(publicKey, JwaCurve.P256) const x = expanded.slice(0, expanded.length / 2) const y = expanded.slice(expanded.length / 2) - return new P_256Jwk({ + return new P256Jwk({ x: TypedArrayEncoder.toBase64URL(x), y: TypedArrayEncoder.toBase64URL(y), }) @@ -82,7 +87,7 @@ export class P_256Jwk extends Jwk { export interface P256JwkJson extends JwkJson { kty: JwaKeyType.EC - crv: JwaCurve.P_256 + crv: JwaCurve.P256 x: string y: string use?: 'sig' | 'enc' @@ -91,7 +96,7 @@ export interface P256JwkJson extends JwkJson { export function isValidP256JwkPublicKey(jwk: JwkJson): jwk is P256JwkJson { return ( hasKty(jwk, JwaKeyType.EC) && - hasCrv(jwk, JwaCurve.P_256) && + hasCrv(jwk, JwaCurve.P256) && hasX(jwk) && hasY(jwk) && hasValidUse(jwk, { diff --git a/packages/core/src/crypto/jose/jwk/P_384Jwk.ts b/packages/core/src/crypto/jose/jwk/P384Jwk.ts similarity index 83% rename from packages/core/src/crypto/jose/jwk/P_384Jwk.ts rename to packages/core/src/crypto/jose/jwk/P384Jwk.ts index 9ce5b121d6..9fb74f4269 100644 --- a/packages/core/src/crypto/jose/jwk/P_384Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/P384Jwk.ts @@ -9,7 +9,7 @@ import { Jwk } from './Jwk' import { compress, expand } from './ecCompression' import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' -export class P_384Jwk extends Jwk { +export class P384Jwk extends Jwk { public readonly x: string public readonly y: string @@ -25,14 +25,19 @@ export class P_384Jwk extends Jwk { } public get crv() { - return JwaCurve.P_384 as const + return JwaCurve.P384 as const } public get keyType() { return KeyType.P384 } - // NOTE: this is the compressed variant. We should add support for the uncompressed variant. + /** + * Returns the public key of the P-384 JWK. + * + * NOTE: this is the compressed variant. We still need to add support for the + * uncompressed variant. + */ public get publicKey() { const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(this.x), TypedArrayEncoder.fromBase64(this.y)]) const compressedPublicKey = compress(publicKeyBuffer) @@ -62,18 +67,18 @@ export class P_384Jwk extends Jwk { throw new Error("Invalid 'P-384' JWK.") } - return new P_384Jwk({ + return new P384Jwk({ x: jwk.x, y: jwk.y, }) } public static fromPublicKey(publicKey: Buffer) { - const expanded = expand(publicKey, JwaCurve.P_384) + const expanded = expand(publicKey, JwaCurve.P384) const x = expanded.slice(0, expanded.length / 2) const y = expanded.slice(expanded.length / 2) - return new P_384Jwk({ + return new P384Jwk({ x: TypedArrayEncoder.toBase64URL(x), y: TypedArrayEncoder.toBase64URL(y), }) @@ -82,7 +87,7 @@ export class P_384Jwk extends Jwk { export interface P384JwkJson extends JwkJson { kty: JwaKeyType.EC - crv: JwaCurve.P_384 + crv: JwaCurve.P384 x: string y: string use?: 'sig' | 'enc' @@ -91,7 +96,7 @@ export interface P384JwkJson extends JwkJson { export function isValidP384JwkPublicKey(jwk: JwkJson): jwk is P384JwkJson { return ( hasKty(jwk, JwaKeyType.EC) && - hasCrv(jwk, JwaCurve.P_384) && + hasCrv(jwk, JwaCurve.P384) && hasX(jwk) && hasY(jwk) && hasValidUse(jwk, { diff --git a/packages/core/src/crypto/jose/jwk/P_521Jwk.ts b/packages/core/src/crypto/jose/jwk/P521Jwk.ts similarity index 83% rename from packages/core/src/crypto/jose/jwk/P_521Jwk.ts rename to packages/core/src/crypto/jose/jwk/P521Jwk.ts index b9bcd092de..a6a4709da8 100644 --- a/packages/core/src/crypto/jose/jwk/P_521Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/P521Jwk.ts @@ -9,7 +9,7 @@ import { Jwk } from './Jwk' import { compress, expand } from './ecCompression' import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' -export class P_521Jwk extends Jwk { +export class P521Jwk extends Jwk { public readonly x: string public readonly y: string @@ -25,14 +25,19 @@ export class P_521Jwk extends Jwk { } public get crv() { - return JwaCurve.P_521 as const + return JwaCurve.P521 as const } public get keyType() { return KeyType.P521 } - // NOTE: this is the compressed variant. We should add support for the uncompressed variant. + /** + * Returns the public key of the P-521 JWK. + * + * NOTE: this is the compressed variant. We still need to add support for the + * uncompressed variant. + */ public get publicKey() { const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(this.x), TypedArrayEncoder.fromBase64(this.y)]) const compressedPublicKey = compress(publicKeyBuffer) @@ -62,18 +67,18 @@ export class P_521Jwk extends Jwk { throw new Error("Invalid 'P-521' JWK.") } - return new P_521Jwk({ + return new P521Jwk({ x: jwk.x, y: jwk.y, }) } public static fromPublicKey(publicKey: Buffer) { - const expanded = expand(publicKey, JwaCurve.P_521) + const expanded = expand(publicKey, JwaCurve.P521) const x = expanded.slice(0, expanded.length / 2) const y = expanded.slice(expanded.length / 2) - return new P_521Jwk({ + return new P521Jwk({ x: TypedArrayEncoder.toBase64URL(x), y: TypedArrayEncoder.toBase64URL(y), }) @@ -82,7 +87,7 @@ export class P_521Jwk extends Jwk { export interface P521JwkJson extends JwkJson { kty: JwaKeyType.EC - crv: JwaCurve.P_521 + crv: JwaCurve.P521 x: string y: string use?: 'sig' | 'enc' @@ -91,7 +96,7 @@ export interface P521JwkJson extends JwkJson { export function isValidP521JwkPublicKey(jwk: JwkJson): jwk is P521JwkJson { return ( hasKty(jwk, JwaKeyType.EC) && - hasCrv(jwk, JwaCurve.P_521) && + hasCrv(jwk, JwaCurve.P521) && hasX(jwk) && hasY(jwk) && hasValidUse(jwk, { diff --git a/packages/core/src/crypto/jose/jwk/__tests__/P_256Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/P_256Jwk.test.ts index fe941e8855..1250d031d9 100644 --- a/packages/core/src/crypto/jose/jwk/__tests__/P_256Jwk.test.ts +++ b/packages/core/src/crypto/jose/jwk/__tests__/P_256Jwk.test.ts @@ -1,6 +1,6 @@ import { TypedArrayEncoder, Buffer } from '../../../../utils' import { KeyType } from '../../../KeyType' -import { P_256Jwk } from '../P_256Jwk' +import { P256Jwk } from '../P256Jwk' import { compress } from '../ecCompression' const jwkJson = { @@ -12,7 +12,7 @@ const jwkJson = { describe('P_256JWk', () => { test('has correct properties', () => { - const jwk = new P_256Jwk({ x: jwkJson.x, y: jwkJson.y }) + const jwk = new P256Jwk({ x: jwkJson.x, y: jwkJson.y }) expect(jwk.kty).toEqual('EC') expect(jwk.crv).toEqual('P-256') @@ -31,11 +31,11 @@ describe('P_256JWk', () => { }) test('fromJson', () => { - const jwk = P_256Jwk.fromJson(jwkJson) + const jwk = P256Jwk.fromJson(jwkJson) expect(jwk.x).toEqual(jwkJson.x) expect(jwk.y).toEqual(jwkJson.y) - expect(() => P_256Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'P-256' JWK.") + expect(() => P256Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'P-256' JWK.") }) test('fromPublicKey', () => { @@ -45,7 +45,7 @@ describe('P_256JWk', () => { ]) const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) - const jwk = P_256Jwk.fromPublicKey(compressedPublicKey) + const jwk = P256Jwk.fromPublicKey(compressedPublicKey) expect(jwk.x).toEqual(jwkJson.x) expect(jwk.y).toEqual(jwkJson.y) }) diff --git a/packages/core/src/crypto/jose/jwk/__tests__/P_384Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/P_384Jwk.test.ts index 5fb93b4cb1..0f409ed878 100644 --- a/packages/core/src/crypto/jose/jwk/__tests__/P_384Jwk.test.ts +++ b/packages/core/src/crypto/jose/jwk/__tests__/P_384Jwk.test.ts @@ -1,6 +1,6 @@ import { TypedArrayEncoder, Buffer } from '../../../../utils' import { KeyType } from '../../../KeyType' -import { P_384Jwk } from '../P_384Jwk' +import { P384Jwk } from '../P384Jwk' import { compress } from '../ecCompression' const jwkJson = { @@ -12,7 +12,7 @@ const jwkJson = { describe('P_384JWk', () => { test('has correct properties', () => { - const jwk = new P_384Jwk({ x: jwkJson.x, y: jwkJson.y }) + const jwk = new P384Jwk({ x: jwkJson.x, y: jwkJson.y }) expect(jwk.kty).toEqual('EC') expect(jwk.crv).toEqual('P-384') @@ -30,11 +30,11 @@ describe('P_384JWk', () => { }) test('fromJson', () => { - const jwk = P_384Jwk.fromJson(jwkJson) + const jwk = P384Jwk.fromJson(jwkJson) expect(jwk.x).toEqual(jwkJson.x) expect(jwk.y).toEqual(jwkJson.y) - expect(() => P_384Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'P-384' JWK.") + expect(() => P384Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'P-384' JWK.") }) test('fromPublicKey', () => { @@ -44,7 +44,7 @@ describe('P_384JWk', () => { ]) const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) - const jwk = P_384Jwk.fromPublicKey(compressedPublicKey) + const jwk = P384Jwk.fromPublicKey(compressedPublicKey) expect(jwk.x).toEqual(jwkJson.x) expect(jwk.y).toEqual(jwkJson.y) }) diff --git a/packages/core/src/crypto/jose/jwk/__tests__/P_521Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/P_521Jwk.test.ts index 6e2fad628a..662fb5ee7b 100644 --- a/packages/core/src/crypto/jose/jwk/__tests__/P_521Jwk.test.ts +++ b/packages/core/src/crypto/jose/jwk/__tests__/P_521Jwk.test.ts @@ -1,6 +1,6 @@ import { TypedArrayEncoder, Buffer } from '../../../../utils' import { KeyType } from '../../../KeyType' -import { P_521Jwk } from '../P_521Jwk' +import { P521Jwk } from '../P521Jwk' import { compress } from '../ecCompression' const jwkJson = { @@ -12,7 +12,7 @@ const jwkJson = { describe('P_521JWk', () => { test('has correct properties', () => { - const jwk = new P_521Jwk({ x: jwkJson.x, y: jwkJson.y }) + const jwk = new P521Jwk({ x: jwkJson.x, y: jwkJson.y }) expect(jwk.kty).toEqual('EC') expect(jwk.crv).toEqual('P-521') @@ -30,11 +30,11 @@ describe('P_521JWk', () => { }) test('fromJson', () => { - const jwk = P_521Jwk.fromJson(jwkJson) + const jwk = P521Jwk.fromJson(jwkJson) expect(jwk.x).toEqual(jwkJson.x) expect(jwk.y).toEqual(jwkJson.y) - expect(() => P_521Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'P-521' JWK.") + expect(() => P521Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'P-521' JWK.") }) test('fromPublicKey', () => { @@ -44,7 +44,7 @@ describe('P_521JWk', () => { ]) const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) - const jwk = P_521Jwk.fromPublicKey(compressedPublicKey) + const jwk = P521Jwk.fromPublicKey(compressedPublicKey) expect(jwk.x).toEqual(jwkJson.x) expect(jwk.y).toEqual(jwkJson.y) }) diff --git a/packages/core/src/crypto/jose/jwk/index.ts b/packages/core/src/crypto/jose/jwk/index.ts index ead8c51a0d..1e4f9e0f24 100644 --- a/packages/core/src/crypto/jose/jwk/index.ts +++ b/packages/core/src/crypto/jose/jwk/index.ts @@ -1,7 +1,7 @@ export { getJwkFromJson, getJwkFromKey } from './transform' export { Ed25519Jwk } from './Ed25519Jwk' export { X25519Jwk } from './X25519Jwk' -export { P_256Jwk } from './P_256Jwk' -export { P_384Jwk } from './P_384Jwk' -export { P_521Jwk } from './P_521Jwk' +export { P256Jwk } from './P256Jwk' +export { P384Jwk } from './P384Jwk' +export { P521Jwk } from './P521Jwk' export { Jwk } from './Jwk' diff --git a/packages/core/src/crypto/jose/jwk/transform.ts b/packages/core/src/crypto/jose/jwk/transform.ts index fd6c133736..69f1726e0e 100644 --- a/packages/core/src/crypto/jose/jwk/transform.ts +++ b/packages/core/src/crypto/jose/jwk/transform.ts @@ -5,9 +5,9 @@ import { KeyType } from '../../KeyType' import { JwaCurve, JwaKeyType } from '../jwa' import { Ed25519Jwk } from './Ed25519Jwk' -import { P_256Jwk } from './P_256Jwk' -import { P_384Jwk } from './P_384Jwk' -import { P_521Jwk } from './P_521Jwk' +import { P256Jwk } from './P256Jwk' +import { P384Jwk } from './P384Jwk' +import { P521Jwk } from './P521Jwk' import { X25519Jwk } from './X25519Jwk' import { hasCrv } from './validate' @@ -18,9 +18,9 @@ export function getJwkFromJson(jwkJson: JwkJson): Jwk { } if (jwkJson.kty === JwaKeyType.EC) { - if (hasCrv(jwkJson, JwaCurve.P_256)) return P_256Jwk.fromJson(jwkJson) - if (hasCrv(jwkJson, JwaCurve.P_384)) return P_384Jwk.fromJson(jwkJson) - if (hasCrv(jwkJson, JwaCurve.P_521)) return P_521Jwk.fromJson(jwkJson) + if (hasCrv(jwkJson, JwaCurve.P256)) return P256Jwk.fromJson(jwkJson) + if (hasCrv(jwkJson, JwaCurve.P384)) return P384Jwk.fromJson(jwkJson) + if (hasCrv(jwkJson, JwaCurve.P521)) return P521Jwk.fromJson(jwkJson) } throw new Error(`Cannot create JWK from JSON. Unsupported JWK with kty '${jwkJson.kty}'.`) @@ -30,9 +30,9 @@ export function getJwkFromKey(key: Key) { if (key.keyType === KeyType.Ed25519) return Ed25519Jwk.fromPublicKey(key.publicKey) if (key.keyType === KeyType.X25519) return X25519Jwk.fromPublicKey(key.publicKey) - if (key.keyType === KeyType.P256) return P_256Jwk.fromPublicKey(key.publicKey) - if (key.keyType === KeyType.P384) return P_384Jwk.fromPublicKey(key.publicKey) - if (key.keyType === KeyType.P521) return P_521Jwk.fromPublicKey(key.publicKey) + if (key.keyType === KeyType.P256) return P256Jwk.fromPublicKey(key.publicKey) + if (key.keyType === KeyType.P384) return P384Jwk.fromPublicKey(key.publicKey) + if (key.keyType === KeyType.P521) return P521Jwk.fromPublicKey(key.publicKey) throw new Error(`Cannot create JWK from key. Unsupported key with type '${key.keyType}'.`) } diff --git a/packages/core/src/modules/connections/DidExchangeProtocol.ts b/packages/core/src/modules/connections/DidExchangeProtocol.ts index b22bb487d7..26dcfe42ab 100644 --- a/packages/core/src/modules/connections/DidExchangeProtocol.ts +++ b/packages/core/src/modules/connections/DidExchangeProtocol.ts @@ -16,6 +16,7 @@ import { Attachment, AttachmentData } from '../../decorators/attachment/Attachme import { AriesFrameworkError } from '../../error' import { Logger } from '../../logger' import { inject, injectable } from '../../plugins' +import { isDid } from '../../utils' import { JsonEncoder } from '../../utils/JsonEncoder' import { JsonTransformer } from '../../utils/JsonTransformer' import { @@ -163,7 +164,7 @@ export class DidExchangeProtocol { } // If the responder wishes to continue the exchange, they will persist the received information in their wallet. - if (!message.did.startsWith('did:peer:')) { + if (!isDid(message.did, 'peer')) { throw new DidExchangeProblemReportError( `Message contains unsupported did ${message.did}. Supported dids are [did:peer]`, { @@ -304,7 +305,7 @@ export class DidExchangeProtocol { }) } - if (!message.did.startsWith('did:peer:')) { + if (!isDid(message.did, 'peer')) { throw new DidExchangeProblemReportError( `Message contains unsupported did ${message.did}. Supported dids are [did:peer]`, { diff --git a/packages/core/src/modules/dids/helpers.ts b/packages/core/src/modules/dids/helpers.ts index 6170707c27..5e02316db5 100644 --- a/packages/core/src/modules/dids/helpers.ts +++ b/packages/core/src/modules/dids/helpers.ts @@ -1,9 +1,10 @@ import { KeyType, Key } from '../../crypto' +import { isDid } from '../../utils' import { DidKey } from './methods/key' export function isDidKey(key: string) { - return key.startsWith('did:key') + return isDid(key, 'key') } export function didKeyToVerkey(key: string) { @@ -15,9 +16,7 @@ export function didKeyToVerkey(key: string) { } export function verkeyToDidKey(key: string) { - if (isDidKey(key)) { - return key - } + if (isDidKey(key)) return key const publicKeyBase58 = key const ed25519Key = Key.fromPublicKeyBase58(publicKeyBase58, KeyType.Ed25519) const didKey = new DidKey(ed25519Key) diff --git a/packages/core/src/modules/oob/domain/OutOfBandDidCommService.ts b/packages/core/src/modules/oob/domain/OutOfBandDidCommService.ts index f351520fba..ecfd87c4d3 100644 --- a/packages/core/src/modules/oob/domain/OutOfBandDidCommService.ts +++ b/packages/core/src/modules/oob/domain/OutOfBandDidCommService.ts @@ -2,6 +2,7 @@ import type { ValidationOptions } from 'class-validator' import { ArrayNotEmpty, buildMessage, IsOptional, isString, IsString, ValidateBy } from 'class-validator' +import { isDid } from '../../../utils' import { DidDocumentService } from '../../dids' export class OutOfBandDidCommService extends DidDocumentService { @@ -44,7 +45,7 @@ function IsDidKeyString(validationOptions?: ValidationOptions): PropertyDecorato { name: 'isDidKeyString', validator: { - validate: (value): boolean => isString(value) && value.startsWith('did:key:'), + validate: (value): boolean => isString(value) && isDid(value, 'key'), defaultMessage: buildMessage( (eachPrefix) => eachPrefix + '$property must be a did:key string', validationOptions diff --git a/packages/core/src/modules/vc/__tests__/documentLoader.ts b/packages/core/src/modules/vc/__tests__/documentLoader.ts index adf72dba7f..134fcda3ad 100644 --- a/packages/core/src/modules/vc/__tests__/documentLoader.ts +++ b/packages/core/src/modules/vc/__tests__/documentLoader.ts @@ -2,6 +2,7 @@ import type { AgentContext } from '../../../agent/context/AgentContext' import type { JsonObject } from '../../../types' import type { DocumentLoaderResult } from '../libraries/jsonld' +import { isDid } from '../../../utils' import jsonld from '../libraries/jsonld' import { @@ -127,7 +128,7 @@ async function _customDocumentLoader(url: string): Promise throw new Error(`Document not found: ${url}`) } - if (url.startsWith('did:')) { + if (isDid(url)) { result = await jsonld.frame( result, { diff --git a/packages/core/src/modules/vc/libraries/documentLoader.ts b/packages/core/src/modules/vc/libraries/documentLoader.ts index 50fcde95d0..dfb7ee7925 100644 --- a/packages/core/src/modules/vc/libraries/documentLoader.ts +++ b/packages/core/src/modules/vc/libraries/documentLoader.ts @@ -2,6 +2,7 @@ import type { DocumentLoader } from './jsonld' import type { AgentContext } from '../../../agent/context/AgentContext' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' +import { isDid } from '../../../utils' import { DidResolverService } from '../../dids' import jsonld from './jsonld' @@ -13,7 +14,7 @@ export function defaultDocumentLoader(agentContext: AgentContext): DocumentLoade const didResolver = agentContext.dependencyManager.resolve(DidResolverService) async function loader(url: string) { - if (url.startsWith('did:')) { + if (isDid(url)) { const result = await didResolver.resolve(agentContext, url) if (result.didResolutionMetadata.error || !result.didDocument) { diff --git a/packages/core/src/utils/did.ts b/packages/core/src/utils/did.ts index d21405252d..8f64e8ac83 100644 --- a/packages/core/src/utils/did.ts +++ b/packages/core/src/utils/did.ts @@ -7,3 +7,14 @@ export function indyDidFromPublicKeyBase58(publicKeyBase58: string): string { return did } + +/** + * Checks whether `potentialDid` is a valid DID. You can optionally provide a `method` to + * check whether the did is for that specific method. + * + * Note: the check in this method is very simple and just check whether the did starts with + * `did:` or `did::`. It does not do an advanced regex check on the did. + */ +export function isDid(potentialDid: string, method?: string) { + return method ? potentialDid.startsWith(`did:${method}:`) : potentialDid.startsWith('did:') +} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index a432b6bc6c..774666eec5 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -12,3 +12,4 @@ export * from './type' export * from './deepEquality' export * from './objectEquality' export * from './MessageValidator' +export * from './did' diff --git a/packages/core/tests/setup.ts b/packages/core/tests/setup.ts index a2ba1429d2..1ba81132c7 100644 --- a/packages/core/tests/setup.ts +++ b/packages/core/tests/setup.ts @@ -1,4 +1,5 @@ import 'reflect-metadata' +import '@hyperledger/aries-askar-nodejs' import type { ConnectionRecord } from '../src/modules/connections/repository/ConnectionRecord' From ae3b3707ddba08b0e9072a9c65549ca9f70d5112 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 16 May 2023 16:01:14 +0200 Subject: [PATCH 7/7] chore: address feedback Signed-off-by: Timo Glastra --- packages/core/src/crypto/jose/jwa/alg.ts | 24 +++++++++---------- packages/core/src/crypto/jose/jwk/Jwk.ts | 10 +++++++- .../core/src/crypto/jose/jwk/X25519Jwk.ts | 8 +++---- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/packages/core/src/crypto/jose/jwa/alg.ts b/packages/core/src/crypto/jose/jwa/alg.ts index 5913830b2e..ea2ba50c8d 100644 --- a/packages/core/src/crypto/jose/jwa/alg.ts +++ b/packages/core/src/crypto/jose/jwa/alg.ts @@ -12,27 +12,27 @@ export enum JwaSignatureAlgorithm { PS384 = 'PS384', PS512 = 'PS512', EdDSA = 'EdDSA', - none = 'none', + None = 'none', } export enum JwaEncryptionAlgorithm { - RSA1_5 = 'RSA1_5', - RSA_OAEP = 'RSA-OAEP', - RSA_OAEP_256 = 'RSA-OAEP-256', + RSA15 = 'RSA1_5', + RSAOAEP = 'RSA-OAEP', + RSAOAEP256 = 'RSA-OAEP-256', A128KW = 'A128KW', A192KW = 'A192KW', A256KW = 'A256KW', - dir = 'dir', - ECDH_ES = 'ECDH-ES', - ECDH_ES_A128KW = 'ECDH-ES+A128KW', - ECDH_ES_A192KW = 'ECDH-ES+A192KW', - ECDH_ES_A256KW = 'ECDH-ES+A256KW', + Dir = 'dir', + ECDHES = 'ECDH-ES', + ECDHESA128KW = 'ECDH-ES+A128KW', + ECDHESA192KW = 'ECDH-ES+A192KW', + ECDHESA256KW = 'ECDH-ES+A256KW', A128GCMKW = 'A128GCMKW', A192GCMKW = 'A192GCMKW', A256GCMKW = 'A256GCMKW', - PBES2_HS256_A128KW = 'PBES2-HS256+A128KW', - PBES2_HS384_A192KW = 'PBES2-HS384+A192KW', - PBES2_HS512_A256KW = 'PBES2-HS512+A256KW', + PBES2HS256A128KW = 'PBES2-HS256+A128KW', + PBES2HS384A192KW = 'PBES2-HS384+A192KW', + PBES2HS512A256KW = 'PBES2-HS512+A256KW', } export type JwaAlgorithm = JwaSignatureAlgorithm | JwaEncryptionAlgorithm diff --git a/packages/core/src/crypto/jose/jwk/Jwk.ts b/packages/core/src/crypto/jose/jwk/Jwk.ts index 709dd2a3f9..595a1dc9a3 100644 --- a/packages/core/src/crypto/jose/jwk/Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/Jwk.ts @@ -12,10 +12,18 @@ export interface JwkJson { export abstract class Jwk { public abstract publicKey: Buffer - public abstract keyType: KeyType public abstract supportedSignatureAlgorithms: JwaSignatureAlgorithm[] public abstract supportedEncryptionAlgorithms: JwaEncryptionAlgorithm[] + /** + * keyType as used by the rest of the framework, can be used in the + * `Wallet`, `Key` and other classes. + */ + public abstract keyType: KeyType + + /** + * key type as defined in [JWA Specification](https://tools.ietf.org/html/rfc7518#section-6.1) + */ public abstract kty: JwaKeyType public use?: string diff --git a/packages/core/src/crypto/jose/jwk/X25519Jwk.ts b/packages/core/src/crypto/jose/jwk/X25519Jwk.ts index 2a3ce9d968..ceb7784592 100644 --- a/packages/core/src/crypto/jose/jwk/X25519Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/X25519Jwk.ts @@ -35,10 +35,10 @@ export class X25519Jwk extends Jwk { public get supportedEncryptionAlgorithms() { return [ - JwaEncryptionAlgorithm.ECDH_ES_A128KW, - JwaEncryptionAlgorithm.ECDH_ES_A192KW, - JwaEncryptionAlgorithm.ECDH_ES_A256KW, - JwaEncryptionAlgorithm.ECDH_ES, + JwaEncryptionAlgorithm.ECDHESA128KW, + JwaEncryptionAlgorithm.ECDHESA192KW, + JwaEncryptionAlgorithm.ECDHESA256KW, + JwaEncryptionAlgorithm.ECDHES, ] }