UNPKG

@credo-ts/core

Version:

<p align="center"> <br /> <img alt="Credo Logo" src="https://github.com/openwallet-foundation/credo-ts/blob/c7886cb8377ceb8ee4efe8d264211e561a75072d/images/credo-logo.png" height="250px" /> </p> <h1 align="center"><b>Credo - Core</b></h1

1 lines • 17.9 kB
{"version":3,"file":"JwsService.mjs","names":[],"sources":["../../src/crypto/JwsService.ts"],"sourcesContent":["import type { AgentContext } from '../agent'\nimport { CredoError } from '../error'\nimport {\n assertJwkAsymmetric,\n asymmetricPublicJwkMatches,\n getJwkHumanDescription,\n KeyManagementApi,\n KeyManagementError,\n type KnownJwaSignatureAlgorithm,\n PublicJwk,\n} from '../modules/kms'\nimport { isKnownJwaSignatureAlgorithm } from '../modules/kms/jwk/jwa'\nimport { type EncodedX509Certificate, X509ModuleConfig } from '../modules/x509'\nimport { X509Service } from './../modules/x509/X509Service'\nimport { injectable } from '../plugins'\nimport { isJsonObject } from '../types'\nimport { JsonEncoder, TypedArrayEncoder } from '../utils'\nimport type { JwsSigner, JwsSignerWithJwk } from './JwsSigner'\nimport type {\n Jws,\n JwsDetachedFormat,\n JwsFlattenedFormat,\n JwsGeneralFormat,\n JwsProtectedHeaderOptions,\n} from './JwsTypes'\nimport { JWS_COMPACT_FORMAT_MATCHER } from './JwsTypes'\nimport { JwtPayload } from './jose/jwt'\n\n@injectable()\nexport class JwsService {\n private async createJwsBase(agentContext: AgentContext, options: CreateJwsBaseOptions) {\n const { jwk, alg, x5c } = options.protectedHeaderOptions\n\n const kms = agentContext.dependencyManager.resolve(KeyManagementApi)\n\n const key = await kms.getPublicKey({ keyId: options.keyId })\n assertJwkAsymmetric(key)\n\n const publicJwk = PublicJwk.fromPublicJwk(key)\n\n // Make sure the options.x5c and x5c from protectedHeader are the same.\n if (x5c) {\n const certificate = X509Service.getLeafCertificate(agentContext, {\n certificateChain: x5c,\n })\n\n if (!asymmetricPublicJwkMatches(certificate.publicJwk.toJson(), key)) {\n throw new CredoError('Protected header x5c does not match key for signing.')\n }\n }\n\n const jwkInstance = jwk instanceof PublicJwk ? jwk : jwk ? PublicJwk.fromUnknown(jwk) : undefined\n // Make sure the options.key and jwk from protectedHeader are the same.\n if (jwkInstance && !asymmetricPublicJwkMatches(jwkInstance.toJson(), key)) {\n throw new CredoError('Protected header JWK does not match key for signing.')\n }\n\n // Validate the options.key used for signing against the jws options\n if (!publicJwk.supportedSignatureAlgorithms.includes(alg)) {\n throw new CredoError(\n `alg '${alg}' is not a valid JWA signature algorithm for this jwk with ${publicJwk.jwkTypeHumanDescription}. Supported algorithms are ${publicJwk.supportedSignatureAlgorithms.join(\n ', '\n )}`\n )\n }\n\n const payload =\n options.payload instanceof JwtPayload ? JsonEncoder.toUint8Array(options.payload.toJson()) : options.payload\n\n const base64Payload = TypedArrayEncoder.toBase64Url(payload)\n const base64UrlProtectedHeader = JsonEncoder.toBase64Url(this.buildProtected(options.protectedHeaderOptions))\n\n const signResult = await kms.sign({\n algorithm: alg,\n data: TypedArrayEncoder.fromUtf8String(`${base64UrlProtectedHeader}.${base64Payload}`),\n keyId: options.keyId,\n })\n const signature = TypedArrayEncoder.toBase64Url(signResult.signature)\n\n return {\n base64Payload,\n base64UrlProtectedHeader,\n signature,\n }\n }\n\n public async createJws(\n agentContext: AgentContext,\n { payload, keyId, header, protectedHeaderOptions }: CreateJwsOptions\n ): Promise<JwsGeneralFormat> {\n const { base64UrlProtectedHeader, signature, base64Payload } = await this.createJwsBase(agentContext, {\n payload,\n keyId,\n protectedHeaderOptions,\n })\n\n return {\n protected: base64UrlProtectedHeader,\n signature,\n header,\n payload: base64Payload,\n }\n }\n\n /**\n * @see {@link https://www.rfc-editor.org/rfc/rfc7515#section-3.1}\n * */\n public async createJwsCompact(\n agentContext: AgentContext,\n { payload, keyId, protectedHeaderOptions }: CreateCompactJwsOptions\n ): Promise<string> {\n const { base64Payload, base64UrlProtectedHeader, signature } = await this.createJwsBase(agentContext, {\n payload,\n keyId,\n protectedHeaderOptions,\n })\n return `${base64UrlProtectedHeader}.${base64Payload}.${signature}`\n }\n\n /**\n * Verify a JWS\n */\n public async verifyJws(\n agentContext: AgentContext,\n {\n jws,\n resolveJwsSigner,\n trustedCertificates,\n jwsSigner: expectedJwsSigner,\n allowedJwsSignerMethods = ['did', 'jwk', 'x5c'],\n }: VerifyJwsOptions\n ): Promise<VerifyJwsResult> {\n let signatures: JwsDetachedFormat[] = []\n let payload: string\n\n if (expectedJwsSigner && !allowedJwsSignerMethods.includes(expectedJwsSigner.method)) {\n throw new CredoError(\n `jwsSigner provided with method '${\n expectedJwsSigner.method\n }', but allowed jws signer methods are ${allowedJwsSignerMethods.join(', ')}.`\n )\n }\n\n if (typeof jws === 'string') {\n if (!JWS_COMPACT_FORMAT_MATCHER.test(jws)) throw new CredoError(`Invalid JWS compact format for value '${jws}'.`)\n\n const [protectedHeader, _payload, signature] = jws.split('.')\n\n payload = _payload\n signatures.push({\n header: {},\n protected: protectedHeader,\n signature,\n })\n } else if ('signatures' in jws) {\n signatures = jws.signatures\n payload = jws.payload\n } else {\n signatures.push(jws)\n payload = jws.payload\n }\n\n if (signatures.length === 0) {\n throw new CredoError('Unable to verify JWS, no signatures present in JWS.')\n }\n\n const jwsFlattened = {\n signatures,\n payload,\n } satisfies JwsFlattenedFormat\n\n const jwsSigners: JwsSignerWithJwk[] = []\n for (const jws of signatures) {\n const protectedJson = JsonEncoder.fromBase64Url(jws.protected)\n\n if (!isJsonObject(protectedJson)) {\n throw new CredoError('Unable to verify JWS, protected header is not a valid JSON object.')\n }\n\n if (!protectedJson.alg || typeof protectedJson.alg !== 'string') {\n throw new CredoError('Unable to verify JWS, protected header alg is not provided or not a string.')\n }\n\n const jwsSigner =\n expectedJwsSigner ??\n (await this.jwsSignerFromJws(agentContext, {\n jws,\n payload,\n protectedHeader: {\n ...protectedJson,\n alg: protectedJson.alg,\n },\n allowedJwsSignerMethods,\n resolveJwsSigner,\n }))\n\n await this.verifyJwsSigner(agentContext, {\n jwsSigner,\n trustedCertificates,\n })\n\n if (!jwsSigner.jwk.supportedSignatureAlgorithms.includes(protectedJson.alg as KnownJwaSignatureAlgorithm)) {\n throw new CredoError(\n `alg '${protectedJson.alg}' is not a valid JWA signature algorithm for this jwk ${getJwkHumanDescription(jwsSigner.jwk.toJson())}. Supported algorithms are ${jwsSigner.jwk.supportedSignatureAlgorithms.join(', ')}`\n )\n }\n\n const data = TypedArrayEncoder.fromUtf8String(`${jws.protected}.${payload}`)\n const signature = TypedArrayEncoder.fromBase64Url(jws.signature)\n jwsSigners.push(jwsSigner)\n\n const kms = agentContext.dependencyManager.resolve(KeyManagementApi)\n\n try {\n const { verified } = await kms.verify({\n key: {\n publicJwk: jwsSigner.jwk.toJson(),\n },\n data,\n signature,\n algorithm: protectedJson.alg as KnownJwaSignatureAlgorithm,\n })\n\n if (!verified) {\n return {\n isValid: false,\n jwsSigners: [],\n jws: jwsFlattened,\n }\n }\n } catch (error) {\n // WalletError probably means signature verification failed. Would be useful to add\n // more specific error type in kms.verify method\n if (error instanceof KeyManagementError) {\n return {\n isValid: false,\n jwsSigners: [],\n jws: jwsFlattened,\n }\n }\n\n throw error\n }\n }\n\n return { isValid: true, jwsSigners, jws: jwsFlattened }\n }\n\n private buildProtected(options: JwsProtectedHeaderOptions) {\n return {\n ...options,\n alg: options.alg,\n jwk: options.jwk instanceof PublicJwk ? options.jwk.toJson() : options.jwk,\n kid: options.kid,\n }\n }\n\n private async verifyJwsSigner(\n agentContext: AgentContext,\n options: {\n jwsSigner: JwsSignerWithJwk\n trustedCertificates?: EncodedX509Certificate[]\n }\n ) {\n const { jwsSigner } = options\n\n if (jwsSigner.method === 'x5c') {\n const trustedCertificatesFromConfig =\n agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates ?? []\n const trustedCertificates = options.trustedCertificates ?? trustedCertificatesFromConfig\n if (trustedCertificates.length === 0) {\n throw new CredoError(\n `trustedCertificates is required when the JWS protected header contains an 'x5c' property.`\n )\n }\n\n await X509Service.validateCertificateChain(agentContext, {\n certificateChain: jwsSigner.x5c,\n trustedCertificates,\n })\n }\n }\n\n private async jwsSignerFromJws(\n agentContext: AgentContext,\n options: {\n jws: JwsDetachedFormat\n allowedJwsSignerMethods: JwsSigner['method'][]\n protectedHeader: { alg: string; [key: string]: unknown }\n payload: string\n resolveJwsSigner?: JwsSignerResolver\n }\n ): Promise<JwsSignerWithJwk> {\n const { protectedHeader, resolveJwsSigner, jws, payload, allowedJwsSignerMethods } = options\n\n const alg = protectedHeader.alg\n if (!isKnownJwaSignatureAlgorithm(alg)) {\n throw new CredoError(`Unsupported JWA signature algorithm '${protectedHeader.alg}'`)\n }\n\n if (protectedHeader.x5c && allowedJwsSignerMethods.includes('x5c')) {\n if (\n !Array.isArray(protectedHeader.x5c) ||\n protectedHeader.x5c.some((certificate) => typeof certificate !== 'string')\n ) {\n throw new CredoError('x5c header is not a valid JSON array of strings.')\n }\n\n const certificate = X509Service.getLeafCertificate(agentContext, {\n certificateChain: protectedHeader.x5c,\n })\n return {\n method: 'x5c',\n jwk: certificate.publicJwk,\n x5c: protectedHeader.x5c,\n }\n }\n\n // Jwk\n if (protectedHeader.jwk && allowedJwsSignerMethods.includes('jwk')) {\n if (!isJsonObject(protectedHeader.jwk)) throw new CredoError('JWK is not a valid JSON object.')\n\n const protectedJwk = PublicJwk.fromUnknown(protectedHeader.jwk)\n\n return {\n method: 'jwk',\n jwk: protectedJwk,\n }\n }\n\n if (!resolveJwsSigner) {\n throw new CredoError(`resolveJwsSigner is required for resolving jws signers other than 'jwk' and 'x5c'.`)\n }\n\n try {\n const jwsSigner = await resolveJwsSigner({\n jws,\n protectedHeader: {\n ...protectedHeader,\n alg,\n },\n payload,\n })\n\n if (!allowedJwsSignerMethods.includes(jwsSigner.method)) {\n throw new CredoError(\n `resolveJwsSigner returned jws signer with method '${\n jwsSigner.method\n }', but allowed jws signer methods are ${allowedJwsSignerMethods.join(', ')}.`\n )\n }\n\n return jwsSigner\n } catch (error) {\n throw new CredoError(`Error when resolving jws signer for jws in resolveJwsSigner. ${error.message}`, {\n cause: error,\n })\n }\n }\n}\n\nexport interface CreateJwsOptions {\n payload: Uint8Array | JwtPayload\n keyId: string\n header: Record<string, unknown>\n protectedHeaderOptions: JwsProtectedHeaderOptions\n}\n\ntype CreateJwsBaseOptions = Omit<CreateJwsOptions, 'header'>\ntype CreateCompactJwsOptions = Omit<CreateJwsOptions, 'header'>\n\nexport interface VerifyJwsOptions {\n jws: Jws\n\n /**\n * The expected signer of the JWS. If provided the signer won't be dynamically\n * detected based on the values in the JWS.\n */\n jwsSigner?: JwsSignerWithJwk\n\n /**\n * Allowed jws signer methods when dynamically inferring the jws signer method.\n */\n allowedJwsSignerMethods?: JwsSigner['method'][]\n\n /*\n * Method that should return the JWS signer was used\n * to sign the JWS.\n *\n * This method is called by the JWS Service when it could not determine the public key.\n *\n * Currently the JWS Service can only determine the public key if the JWS protected header\n * contains a `jwk` or `x5c` property. In all other cases, it's up to the caller to resolve the public\n * key based on the JWS.\n *\n * A common use case is the `kid` property in the JWS protected header. Or determining the key\n * base on the `iss` property in the JWT payload.\n */\n resolveJwsSigner?: JwsSignerResolver\n\n trustedCertificates?: EncodedX509Certificate[]\n}\n\nexport type JwsSignerResolver = (options: {\n jws: JwsDetachedFormat\n payload: string\n protectedHeader: {\n alg: KnownJwaSignatureAlgorithm\n jwk?: string\n kid?: string\n [key: string]: unknown\n }\n}) => Promise<JwsSignerWithJwk> | JwsSignerWithJwk\n\nexport interface VerifyJwsResult {\n isValid: boolean\n jwsSigners: JwsSignerWithJwk[]\n\n jws: JwsFlattenedFormat\n}\n"],"mappings":"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"}