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

154 lines (153 loc) • 6.72 kB
import { TypedArrayEncoder } from "../../../utils/TypedArrayEncoder.mjs"; import { Hasher } from "../../../crypto/hashes/Hasher.mjs"; import "../../../utils/index.mjs"; import { normalizeSerialNumber } from "./serialNumber.mjs"; import { z as z$1 } from "zod"; //#region src/modules/x509/utils/crlSummary.ts /** * Plain-JSON summary of a verified CRL, cached so that subsequent revocation checks do not need to * re-parse the (potentially multi-MB) CRL. Must remain JSON-serializable so it survives * storage-backed cache implementations. */ const zX509CrlSummary = z$1.object({ /** SHA-256 (hex) of the DER-encoded subject name of the issuer certificate */ issuerNameSha256: z$1.string(), /** RFC 7638 JWK thumbprint (hex) of the issuer certificate's public key */ issuerPublicJwkThumbprint: z$1.string(), /** `thisUpdate` as epoch milliseconds */ thisUpdate: z$1.number(), /** `nextUpdate` as epoch milliseconds, absent when the CRL has none */ nextUpdate: z$1.optional(z$1.number()), /** SHA-256 (hex) of the DER-encoded CRL bytes, used to revalidate a stale summary without re-parsing */ crlSha256: z$1.string(), /** Epoch milliseconds after which the summary must be revalidated against freshly fetched CRL bytes */ staleAt: z$1.number(), deltaCrlIndicator: z$1.optional(z$1.number()), issuingDistributionPoint: z$1.optional(z$1.object({ fullName: z$1.array(z$1.string()), onlyContainsUserCerts: z$1.boolean(), onlyContainsCACerts: z$1.boolean(), onlySomeReasons: z$1.optional(z$1.array(z$1.number())), indirectCRL: z$1.boolean(), onlyContainsAttributeCerts: z$1.boolean() })), criticalExtensionIds: z$1.array(z$1.string()), /** Original serial number strings as parsed from the CRL (not normalized) */ serialNumbers: z$1.array(z$1.string()), /** Revocation date per entry as epoch milliseconds */ revocationDates: z$1.array(z$1.number()), /** Revocation reason per entry, `null` when absent (`undefined` is not JSON-safe in arrays) */ reasons: z$1.array(z$1.nullable(z$1.number())) }).refine((summary) => summary.serialNumbers.length === summary.revocationDates.length && summary.serialNumbers.length === summary.reasons.length); /** * Guard for cached summaries. Anything that does not match the expected shape (older formats, * corrupted values) is treated as a cache miss by callers, never as an error, so this must not * throw. Callers must keep using the original object rather than zod's parsed copy: cloning would * copy the potentially huge entry arrays on every cache read and break the revoked-index * memoization, which is keyed on object identity. */ function isX509CrlSummary(value) { return zX509CrlSummary.safeParse(value).success; } function computeIssuerNameSha256(issuerCertificate) { return TypedArrayEncoder.toHex(Hasher.hash(issuerCertificate.subjectNameBytes, "SHA-256")); } function computeIssuerPublicJwkThumbprint(issuerCertificate) { return TypedArrayEncoder.toHex(issuerCertificate.publicJwk.getJwkThumbprint()); } function computeCrlSha256(crlBytes) { return TypedArrayEncoder.toHex(Hasher.hash(crlBytes, "SHA-256")); } /** * Derive the cacheable summary of a verified CRL. * * `staleAt` is caching policy (freshness deadline derived from the verification date and the * configured expiry), so it is provided by the caller rather than derived from the CRL. * * May throw for malformed CRLs (e.g. duplicate extensions, via the delta/issuing-distribution-point * getters); callers must then skip caching the summary so such CRLs keep their existing behavior. */ function buildCrlSummary(crl, issuerCertificate, staleAt) { const serialNumbers = []; const revocationDates = []; const reasons = []; for (const entry of crl.revokedCertificates) { serialNumbers.push(entry.serialNumber); revocationDates.push(entry.revocationDate.getTime()); reasons.push(entry.reason ?? null); } const issuingDistributionPoint = crl.issuingDistributionPoint; return { issuerNameSha256: computeIssuerNameSha256(issuerCertificate), issuerPublicJwkThumbprint: computeIssuerPublicJwkThumbprint(issuerCertificate), thisUpdate: crl.thisUpdate.getTime(), nextUpdate: crl.nextUpdate?.getTime(), crlSha256: computeCrlSha256(crl.rawCertificateRevocationList), staleAt, deltaCrlIndicator: crl.deltaCrlIndicator, issuingDistributionPoint: issuingDistributionPoint ? { fullName: issuingDistributionPoint.fullName, onlyContainsUserCerts: issuingDistributionPoint.onlyContainsUserCerts, onlyContainsCACerts: issuingDistributionPoint.onlyContainsCACerts, onlySomeReasons: issuingDistributionPoint.onlySomeReasons, indirectCRL: issuingDistributionPoint.indirectCRL, onlyContainsAttributeCerts: issuingDistributionPoint.onlyContainsAttributeCerts } : void 0, criticalExtensionIds: crl.criticalExtensionIds, serialNumbers, revocationDates, reasons }; } const revokedIndexBySummary = /* @__PURE__ */ new WeakMap(); /** * {@link VerifiedCrl} view over a cached {@link X509CrlSummary}. Read-only: the summary may be a * shared reference owned by an in-memory cache and must never be mutated. */ var CrlSummaryVerifiedCrl = class { constructor(summary) { this.summary = summary; } get deltaCrlIndicator() { return this.summary.deltaCrlIndicator; } get issuingDistributionPoint() { const idp = this.summary.issuingDistributionPoint; if (!idp) return void 0; return { fullName: [...idp.fullName], onlyContainsUserCerts: idp.onlyContainsUserCerts, onlyContainsCACerts: idp.onlyContainsCACerts, onlySomeReasons: idp.onlySomeReasons ? [...idp.onlySomeReasons] : void 0, indirectCRL: idp.indirectCRL, onlyContainsAttributeCerts: idp.onlyContainsAttributeCerts }; } get criticalExtensionIds() { return [...this.summary.criticalExtensionIds]; } findRevoked(certificate) { const index = this.revokedIndex.get(normalizeSerialNumber(certificate.data.serialNumber)); if (index === void 0) return null; return { serialNumber: this.summary.serialNumbers[index], revocationDate: new Date(this.summary.revocationDates[index]), reason: this.summary.reasons[index] ?? void 0 }; } get revokedIndex() { let index = revokedIndexBySummary.get(this.summary); if (!index) { index = /* @__PURE__ */ new Map(); for (let i = 0; i < this.summary.serialNumbers.length; i++) { const normalized = normalizeSerialNumber(this.summary.serialNumbers[i]); if (!index.has(normalized)) index.set(normalized, i); } revokedIndexBySummary.set(this.summary, index); } return index; } }; //#endregion export { CrlSummaryVerifiedCrl, buildCrlSummary, computeCrlSha256, computeIssuerNameSha256, computeIssuerPublicJwkThumbprint, isX509CrlSummary }; //# sourceMappingURL=crlSummary.mjs.map