@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
JavaScript
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