{"version":3,"file":"utils.mjs","names":[],"sources":["../../../src/modules/sd-jwt-vc/utils.ts"],"sourcesContent":["import type { Signer, Verifier } from '@sd-jwt/core'\nimport { AgentContext } from '../../agent'\nimport type { HashName } from '../../crypto'\nimport { CredoError } from '../../error'\nimport { TypedArrayEncoder } from '../../utils'\nimport { DidResolverService, DidsApi, getPublicJwkFromVerificationMethod, parseDid } from '../dids'\nimport { isKnownJwaSignatureAlgorithm, type Jwk, KeyManagementApi, PublicJwk } from '../kms'\nimport { X509Certificate } from '../x509/X509Certificate'\nimport { SdJwtVcError } from './SdJwtVcError'\nimport type { SdJwtVcHolderBinding, SdJwtVcIssuer } from './SdJwtVcOptions'\n\nexport async function resolveSigningPublicJwkFromDidUrl(agentContext: AgentContext, didUrl: string) {\n  const dids = agentContext.dependencyManager.resolve(DidsApi)\n\n  const { publicJwk } = await dids.resolveVerificationMethodFromCreatedDidRecord(didUrl)\n  return publicJwk\n}\n\nexport async function resolveDidUrl(agentContext: AgentContext, didUrl: string) {\n  const didResolver = agentContext.dependencyManager.resolve(DidResolverService)\n  const didDocument = await didResolver.resolveDidDocument(agentContext, didUrl)\n\n  return {\n    verificationMethod: didDocument.dereferenceKey(didUrl, ['assertionMethod']),\n    didDocument,\n  }\n}\n\nexport async function extractKeyFromHolderBinding(\n  agentContext: AgentContext,\n  holder: SdJwtVcHolderBinding,\n  { forSigning = false, jwkKeyId }: { forSigning?: boolean; jwkKeyId?: string } = {}\n) {\n  if (holder.method === 'did') {\n    const parsedDid = parseDid(holder.didUrl)\n    if (!parsedDid.fragment) {\n      throw new CredoError(`didUrl '${holder.didUrl}' does not contain a '#'. Unable to derive key from did document`)\n    }\n\n    let publicJwk: PublicJwk\n    if (forSigning) {\n      publicJwk = await resolveSigningPublicJwkFromDidUrl(agentContext, holder.didUrl)\n    } else {\n      const { verificationMethod } = await resolveDidUrl(agentContext, holder.didUrl)\n      publicJwk = getPublicJwkFromVerificationMethod(verificationMethod)\n    }\n\n    const supportedSignatureAlgorithms = publicJwk.supportedSignatureAlgorithms\n    if (supportedSignatureAlgorithms.length === 0) {\n      throw new CredoError(`No supported JWA signature algorithms found for key ${publicJwk.jwkTypeHumanDescription}`)\n    }\n    const alg = supportedSignatureAlgorithms[0]\n\n    return {\n      alg,\n      publicJwk,\n      cnf: {\n        // We need to include the whole didUrl here, otherwise the verifier\n        // won't know which did it is associated with\n        kid: holder.didUrl,\n      },\n    }\n  }\n  if (holder.method === 'jwk') {\n    const publicJwk = holder.jwk\n    const alg = publicJwk.supportedSignatureAlgorithms[0]\n\n    if (forSigning) {\n      publicJwk.keyId = jwkKeyId ?? publicJwk.legacyKeyId\n    }\n\n    return {\n      alg,\n      publicJwk,\n      cnf: {\n        jwk: publicJwk.toJson(),\n      },\n    }\n  }\n\n  throw new CredoError(\"Unsupported credential holder binding. Only 'did' and 'jwk' are supported at the moment.\")\n}\n\n/**\n * Set the `alg` from a JWT header on the public jwk, so signing and verification use the\n * algorithm of the JWT rather than the first supported signature algorithm of the key.\n *\n * Throws an error if the alg is not a known JWA signature algorithm, or if the alg is not\n * supported by the jwk.\n */\nexport function setJwkAlgFromJwtHeader(publicJwk: PublicJwk, jwtHeaderAlg: unknown) {\n  if (typeof jwtHeaderAlg !== 'string' || !isKnownJwaSignatureAlgorithm(jwtHeaderAlg)) {\n    throw new CredoError(`Expected JWT header 'alg' to be a known JWA signature algorithm, found '${jwtHeaderAlg}'`)\n  }\n\n  publicJwk.alg = jwtHeaderAlg\n}\n\n/**\n * Hashing algorithms that can be used for the disclosure digests of an SD-JWT, and are placed\n * in the `_sd_alg` claim.\n *\n * `sha-1` is omitted, as SD-JWT requires a hash algorithm that is considered secure at the\n * time of issuance.\n *\n * @see https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-22.html#name-hash-function-claim\n */\nexport const sdJwtHashingAlgorithms: Array<Exclude<HashName, 'sha-1'>> = ['sha-256', 'sha-384', 'sha-512']\n\n/**\n * Get the hashing algorithm to use for the disclosure digests of an SD-JWT, defaulting to `sha-256`.\n *\n * The sign options already exclude insecure hashing algorithms on a type level, but this is\n * enforced at runtime as well, as the value can originate from JavaScript callers or dynamic input.\n */\nexport function getSdJwtHashingAlgorithm(hashingAlgorithm?: HashName): Exclude<HashName, 'sha-1'> {\n  const hashAlg = hashingAlgorithm ?? 'sha-256'\n\n  if (!sdJwtHashingAlgorithms.includes(hashAlg as Exclude<HashName, 'sha-1'>)) {\n    throw new CredoError(\n      `Unsupported hashing algorithm '${hashAlg}' for the disclosure digests of an SD-JWT. Supported hashing algorithms are ${sdJwtHashingAlgorithms.join(', ')}`\n    )\n  }\n\n  return hashAlg as Exclude<HashName, 'sha-1'>\n}\n\nexport function getSdJwtSigner(agentContext: AgentContext, key: PublicJwk): Signer {\n  const kms = agentContext.resolve(KeyManagementApi)\n\n  return async (input: string) => {\n    const result = await kms.sign({\n      keyId: key.keyId,\n      data: TypedArrayEncoder.fromUtf8String(input),\n      algorithm: key.signatureAlgorithm,\n    })\n\n    return TypedArrayEncoder.toBase64Url(result.signature)\n  }\n}\n\nexport function getSdJwtVerifier(agentContext: AgentContext, key: PublicJwk): Verifier {\n  const kms = agentContext.resolve(KeyManagementApi)\n\n  return async (message: string, signatureBase64Url: string) => {\n    const result = await kms.verify({\n      signature: TypedArrayEncoder.fromBase64Url(signatureBase64Url),\n      key: {\n        publicJwk: key.toJson(),\n      },\n      data: TypedArrayEncoder.fromUtf8String(message),\n      algorithm: key.signatureAlgorithm,\n    })\n\n    return result.verified\n  }\n}\n\nexport interface CnfPayload {\n  jwk?: Jwk\n  kid?: string\n}\n\nexport function parseHolderBindingFromCredential(payload?: Record<string, unknown>): SdJwtVcHolderBinding | null {\n  if (!payload) {\n    throw new CredoError('Unable to extract payload from SD-JWT VC')\n  }\n\n  if (!payload.cnf) {\n    return null\n  }\n  const cnf: CnfPayload = payload.cnf\n\n  if (cnf.jwk) {\n    return {\n      method: 'jwk',\n      jwk: PublicJwk.fromUnknown(cnf.jwk),\n    }\n  }\n  if (cnf.kid) {\n    if (!cnf.kid.startsWith('did:') || !cnf.kid.includes('#')) {\n      throw new CredoError('Invalid holder kid for did. Only absolute KIDs for cnf are supported')\n    }\n    return {\n      method: 'did',\n      didUrl: cnf.kid,\n    }\n  }\n\n  throw new CredoError(\"Unsupported credential holder binding. Only 'did' and 'jwk' are supported at the moment.\")\n}\n\nexport function parseIssuerFromCredential(\n  header?: Record<string, unknown>,\n  payload?: Record<string, unknown>\n): SdJwtVcIssuer {\n  if (!payload || !header) throw new SdJwtVcError('SD-JWT is missing payload')\n  const iss = payload.iss as string | undefined\n\n  if (header?.x5c) {\n    if (!Array.isArray(header.x5c)) {\n      throw new SdJwtVcError('Invalid x5c header in credential. Not an array.')\n    }\n    if (header.x5c.length === 0) {\n      throw new SdJwtVcError('Invalid x5c header in credential. Empty array.')\n    }\n    if (header.x5c.some((x5c) => typeof x5c !== 'string')) {\n      throw new SdJwtVcError('Invalid x5c header in credential. Not an array of strings.')\n    }\n\n    const certificateChain = header.x5c.map((cert) => X509Certificate.fromEncodedCertificate(cert))\n\n    return {\n      method: 'x5c',\n      x5c: certificateChain,\n      issuer: iss,\n    }\n  }\n\n  if (iss?.startsWith('did:')) {\n    // If `did` is used, we require a relative KID to be present to identify\n    // the key used by issuer to sign the sd-jwt-vc\n    if (!header.kid) {\n      throw new SdJwtVcError('Credential does not contain a kid in the header')\n    }\n\n    const issuerKid = header.kid as string\n\n    let didUrl: string\n    if (issuerKid.startsWith('#')) {\n      didUrl = `${iss}${issuerKid}`\n    } else if (issuerKid.startsWith('did:')) {\n      const didFromKid = parseDid(issuerKid)\n      if (didFromKid.did !== iss) {\n        throw new SdJwtVcError(\n          `kid in header is an absolute DID URL, but the did (${didFromKid.did}) does not match with the 'iss' did (${iss})`\n        )\n      }\n\n      didUrl = issuerKid\n    } else {\n      throw new SdJwtVcError(\n        'Invalid issuer kid for did. Only absolute or relative (starting with #) did urls are supported.'\n      )\n    }\n\n    return {\n      method: 'did',\n      didUrl,\n    }\n  }\n\n  throw new SdJwtVcError('Unsupported signing method for SD-JWT VC. Only did and x5c are supported at the moment.')\n}\n"],"mappings":"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"}