{"version":3,"file":"JwsLinkedDataSignature.mjs","names":[],"sources":["../../../../../src/modules/vc/data-integrity/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 type { DocumentLoader, Proof, VerificationMethod } from '../jsonldUtil'\nimport { suites } from '../libraries/jsonld-signatures'\nimport type { LdKeyPair } from '../models/LdKeyPair'\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    // 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      ) &&\n      Object.keys(header).length === 3\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      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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"}