{"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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"}