{"version":3,"file":"JwsLinkedDataSignature.mjs","names":[],"sources":["../../../../../src/modules/vc/linked-data-proofs/signature-suites/JwsLinkedDataSignature.ts"],"sourcesContent":["/*!\n * Copyright (c) 2020-2021 Digital Bazaar, Inc. All rights reserved.\n */\n\nimport { CredoError } from '../../../../error'\nimport { JsonEncoder, TypedArrayEncoder } from '../../../../utils'\nimport { isKnownJwaSignatureAlgorithm, PublicJwk } from '../../../kms'\nimport { suites } from '../adapters/jsonld-signatures-adapter'\nimport type { LdKeyPair } from '../models/LdKeyPair'\nimport type { DocumentLoader, Proof, VerificationMethod } from '../proof-ops/jsonldUtil'\n\nconst LinkedDataSignature = suites.LinkedDataSignature\nexport interface JwsLinkedDataSignatureOptions {\n  type: string\n  algorithm: string\n  LDKeyClass?: typeof LdKeyPair\n  key?: LdKeyPair\n  proof?: Proof\n  date?: string\n  contextUrl: string\n  useNativeCanonize?: boolean\n}\n\nexport class JwsLinkedDataSignature extends LinkedDataSignature {\n  /**\n   * @param options - Options hashmap.\n   * @param options.type - Provided by subclass.\n   * @param options.alg - JWS alg provided by subclass.\n   * @param [options.LDKeyClass] - Provided by subclass or subclass\n   *   overrides `getVerificationMethod`.\n   *\n   * Either a `key` OR at least one of `signer`/`verifier` is required.\n   *\n   * @param [options.key] - An optional key object (containing an\n   *   `id` property, and either `signer` or `verifier`, depending on the\n   *   intended operation. Useful for when the application is managing keys\n   *   itself (when using a KMS, you never have access to the private key,\n   *   and so should use the `signer` param instead).\n   *\n   * Advanced optional parameters and overrides.\n   *\n   * @param [options.proof] - A JSON-LD document with options to use\n   *   for the `proof` node. Any other custom fields can be provided here\n   *   using a context different from `security-v2`.\n   * @param [options.date] - Signing date to use if not passed.\n   * @param options.contextUrl - JSON-LD context url that corresponds\n   *   to this signature suite. Used for enforcing suite context during the\n   *   `sign()` operation.\n   * @param [options.useNativeCanonize] - Whether to use a native\n   *   canonize algorithm.\n   */\n  public constructor(options: JwsLinkedDataSignatureOptions) {\n    super({\n      type: options.type,\n      LDKeyClass: options.LDKeyClass,\n      contextUrl: options.contextUrl,\n      key: options.key,\n      signer: undefined,\n      verifier: undefined,\n      proof: options.proof,\n      date: options.date,\n      useNativeCanonize: options.useNativeCanonize,\n    })\n    this.alg = options.algorithm\n  }\n\n  /**\n   * @param options - Options hashmap.\n   * @param options.verifyData - The data to sign.\n   * @param options.proof - A JSON-LD document with options to use\n   *   for the `proof` node. Any other custom fields can be provided here\n   *   using a context different from `security-v2`.\n   *\n   * @returns The proof containing the signature value.\n   */\n  public async sign(options: { verifyData: Uint8Array; proof: Proof }) {\n    if (!(this.signer && typeof this.signer.sign === 'function')) {\n      throw new Error('A signer API has not been specified.')\n    }\n\n    // Ensure the key signs with the same algorithm as declared in the JWS header\n    if (this.key?.publicJwk instanceof PublicJwk) {\n      if (!isKnownJwaSignatureAlgorithm(this.alg)) {\n        throw new CredoError(`Unsupported JWA signature algorithm '${this.alg}' for signature suite ${this.type}`)\n      }\n      this.key.publicJwk.alg = this.alg\n    }\n\n    // JWS header\n    const header = {\n      alg: this.alg,\n      b64: false,\n      crit: ['b64'],\n    }\n\n    /*\n    +-------+-----------------------------------------------------------+\n    | \"b64\" | JWS Signing Input Formula                                 |\n    +-------+-----------------------------------------------------------+\n    | true  | ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' ||     |\n    |       | BASE64URL(JWS Payload))                                   |\n    |       |                                                           |\n    | false | ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.') ||    |\n    |       | JWS Payload                                               |\n    +-------+-----------------------------------------------------------+\n    */\n\n    // create JWS data and sign\n    const encodedHeader = JsonEncoder.toBase64Url(header)\n\n    const data = _createJws({ encodedHeader, verifyData: options.verifyData })\n\n    const signature = await this.signer.sign({ data })\n\n    // create detached content signature\n    const encodedSignature = TypedArrayEncoder.toBase64Url(signature)\n    options.proof.jws = `${encodedHeader}..${encodedSignature}`\n    return options.proof\n  }\n\n  /**\n   * @param options - Options hashmap.\n   * @param options.verifyData - The data to verify.\n   * @param options.verificationMethod - A verification method.\n   * @param options.proof - The proof to be verified.\n   *\n   * @returns Resolves with the verification result.\n   */\n  public async verifySignature(options: {\n    verifyData: Uint8Array\n    verificationMethod: VerificationMethod\n    proof: Proof\n  }) {\n    if (!(options.proof.jws && typeof options.proof.jws === 'string' && options.proof.jws.includes('.'))) {\n      throw new TypeError('The proof does not include a valid \"jws\" property.')\n    }\n    // add payload into detached content signature\n    const [encodedHeader /*payload*/, , encodedSignature] = options.proof.jws.split('.')\n\n    // biome-ignore lint/suspicious/noImplicitAnyLet: no explanation\n    let header\n    try {\n      header = JsonEncoder.fromBase64Url(encodedHeader)\n    } catch (e) {\n      throw new Error(`Could not parse JWS header; ${e}`)\n    }\n    if (!(header && typeof header === 'object')) {\n      throw new Error('Invalid JWS header.')\n    }\n\n    // confirm header matches all expectations\n    if (\n      !(\n        header.alg === this.alg &&\n        header.b64 === false &&\n        Array.isArray(header.crit) &&\n        header.crit.length === 1 &&\n        header.crit[0] === 'b64' &&\n        Object.keys(header).length === 3\n      )\n    ) {\n      throw new Error(`Invalid JWS header parameters for ${this.type}.`)\n    }\n\n    // do signature verification\n    const signature = TypedArrayEncoder.fromBase64Url(encodedSignature)\n\n    const data = _createJws({ encodedHeader, verifyData: options.verifyData })\n\n    let { verifier } = this\n    if (!verifier) {\n      const key = await this.LDKeyClass.from(options.verificationMethod)\n\n      // Ensure verification uses the alg of the JWS header (checked above to match the suite algorithm)\n      if (key.publicJwk instanceof PublicJwk) {\n        if (!isKnownJwaSignatureAlgorithm(header.alg)) {\n          throw new CredoError(`Unsupported JWA signature algorithm '${header.alg}' for signature suite ${this.type}`)\n        }\n        key.publicJwk.alg = header.alg\n      }\n\n      verifier = key.verifier()\n    }\n    return verifier.verify({ data, signature })\n  }\n\n  public async getVerificationMethod(options: { proof: Proof; documentLoader?: DocumentLoader }) {\n    if (this.key) {\n      // This happens most often during sign() operations. For verify(),\n      // the expectation is that the verification method will be fetched\n      // by the documentLoader (below), not provided as a `key` parameter.\n      return this.key.export({ publicKey: true })\n    }\n\n    let { verificationMethod } = options.proof\n\n    if (typeof verificationMethod === 'object' && verificationMethod !== null) {\n      verificationMethod = verificationMethod.id\n    }\n\n    if (!verificationMethod) {\n      throw new Error('No \"verificationMethod\" found in proof.')\n    }\n\n    if (!options.documentLoader) {\n      throw new CredoError('Missing custom document loader. This is required for resolving verification methods.')\n    }\n\n    const { document } = await options.documentLoader(verificationMethod)\n\n    verificationMethod = typeof document === 'string' ? JSON.parse(document) : document\n\n    await this.assertVerificationMethod(verificationMethod)\n    return verificationMethod\n  }\n\n  /**\n   * Checks whether a given proof exists in the document.\n   *\n   * @param options - Options hashmap.\n   * @param options.proof - A proof.\n   * @param options.document - A JSON-LD document.\n   * @param options.purpose - A jsonld-signatures ProofPurpose\n   *  instance (e.g. AssertionProofPurpose, AuthenticationProofPurpose, etc).\n   * @param options.documentLoader  - A secure document loader (it is\n   *   recommended to use one that provides static known documents, instead of\n   *   fetching from the web) for returning contexts, controller documents,\n   *   keys, and other relevant URLs needed for the proof.\n   *\n   * @returns Whether a match for the proof was found.\n   */\n  public async matchProof(options: {\n    proof: Proof\n    document: VerificationMethod\n    // biome-ignore lint/suspicious/noExplicitAny: no explanation\n    purpose: any\n    documentLoader?: DocumentLoader\n  }) {\n    const proofMatches = await super.matchProof({\n      proof: options.proof,\n      document: options.document,\n      purpose: options.purpose,\n      documentLoader: options.documentLoader,\n    })\n    if (!proofMatches) {\n      return false\n    }\n    // NOTE: When subclassing this suite: Extending suites will need to check\n\n    if (!this.key) {\n      // no key specified, so assume this suite matches and it can be retrieved\n      return true\n    }\n\n    const { verificationMethod } = options.proof\n\n    // only match if the key specified matches the one in the proof\n    if (typeof verificationMethod === 'object') {\n      return verificationMethod.id === this.key.id\n    }\n    return verificationMethod === this.key.id\n  }\n}\n\n/**\n * Creates the bytes ready for signing.\n *\n * @param {object} options -  Options hashmap.\n * @param {string} options.encodedHeader - A base64url encoded JWT header.\n * @param {Uint8Array} options.verifyData - Payload to sign/verify.\n * @returns {Uint8Array} A combined byte array for signing.\n */\nfunction _createJws(options: { encodedHeader: string; verifyData: Uint8Array }): Uint8Array {\n  const encodedHeaderBytes = TypedArrayEncoder.fromUtf8String(`${options.encodedHeader}.`)\n\n  // concatenate the two uint8arrays\n  const data = new Uint8Array(encodedHeaderBytes.length + options.verifyData.length)\n  data.set(encodedHeaderBytes, 0)\n  data.set(options.verifyData, encodedHeaderBytes.length)\n  return data\n}\n"],"mappings":"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"}