CT
parseSctList decodes the SignedCertificateTimestampList an X.509 certificate (or an OCSP response) carries in the SCT extension into its individual signed certificate timestamps.
The SCT payload is encoded in the TLS presentation language (RFC 8446 sec. 3 / RFC 5246 sec. 4 conventions) -- positional, tag-less, fixed-width big-endian integers and length-prefixed opaque vectors -- NOT ASN.1/DER. So this module owns a bounded big-endian TLS-struct reader rather than composing the DER schema engine; the only ASN.1 surface is the sec. 3.3 double wrap (the extension value is a DER OCTET STRING whose content is another DER OCTET STRING whose content is the TLS list -- the certificate/OCSP layer peels the outer, this module peels the inner).
Structure is decoded, crypto is surfaced RAW: each SCT surfaces its logId (32 raw bytes -- SHA-256 of the log's SPKI, never recomputed), the exact timestamp as a BigInt, the raw extensions, the named-but-not-interpreted hashAlg/sigAlg code points, and the raw signature. The parser NEVER verifies a signature, recomputes a LogID, or trusts a log -- a verifier composes webcrypto over reconstructSignedData(...), the exact digitally-signed preimage. DER-only carrier, fail-closed.
pki.ct.parseSctList
pki.ct.parseSctList(extValue) -> { scts, unknownScts, all }
Parse the value of an RFC 6962 SCT-list extension (the raw extnValue content an x509.parse / OCSP extension already surfaces) into { scts, unknownScts, all }. Each entry of scts is a fully decoded v1 SCT: version (0), logId (32-byte Buffer) + logIdHex, timestamp (BigInt, exact) + timestampMs (Number or null above 2^53) + timestampDate, extensions (raw Buffer), hashAlg / sigAlg (1-byte code points) + a named signatureAlgorithm, the raw signature Buffer, and rawSct (the full SerializedSCT body). A SerializedSCT whose version this parser does not define is preserved OPAQUE in unknownScts as { version, rawSct } rather than failing the list -- RFC 6962 sec. 3.3 frames each SerializedSCT with its own length so unknown versions are skippable (forward compatibility). all lists every SerializedSCT (known and unknown) in the exact wire order, so encodeSctList(all) reproduces the list byte-identically even when the two kinds are interleaved.
The extension value is a DER OCTET STRING wrapping the TLS-encoded list (RFC 6962 sec. 3.3 double wrap); everything below that peel is TLS presentation language, decoded with a bounded cursor. Structure is decoded, crypto is surfaced RAW -- the signature is never verified and the LogID never recomputed.
Throws CtError with a stable ct/* code on any malformed input (a bad inner DER wrap is ct/bad-der with the asn1/* fault as .cause), never a raw TypeError.
Example
var cert = pki.schema.x509.parse(pem);
var sctOid = pki.oid.byName("signedCertificateTimestampList");
var ext = (cert.extensions || []).find(function (e) { return e.oid === sctOid; });
if (ext) {
var list = pki.ct.parseSctList(ext.value);
list.scts[0].logIdHex; // the log's key id
list.scts[0].timestamp; // exact BigInt ms since epoch
}
References
pki.ct.reconstructSignedData
pki.ct.reconstructSignedData(entry, sct) -> Buffer
Rebuild the exact digitally-signed preimage bytes an external verifier hashes to check an SCT's signature (RFC 6962 sec. 3.2), for a parsed sct. entry selects the log-entry arm: - { entryType: 0, leafCert: <DER Buffer> } -- an SCT delivered over TLS / OCSP, signed over x509_entry(0) with the leaf certificate. - { entryType: 1, tbsCertificate: <DER Buffer>, issuerKeyHash: <32B> } -- an SCT EMBEDDED in a certificate, signed over precert_entry(1) with the issuer key hash + the precertificate TBS (the TBS with only the SCT extension removed). issuerKeyHash is SHA-256 of the issuer's SPKI DER.
The preimage reuses the parsed SCT's raw extensions byte-for-byte and re-emits the fixed-width scalars canonically. This never verifies anything -- a verifier hashes the returned bytes and checks the signature with the log's public key (compose webcrypto). Throws CtError (ct/bad-entry-type, ct/bad-issuer-key-hash, ct/bad-tbs-length) on a malformed entry, and ct/bad-input / ct/bad-extensions on an sct whose timestamp or extensions exceed their RFC 6962 3.2 wire ranges (uint64 / opaque<0..2^16-1>).
Example
var sct = pki.ct.parseSctList(sctExtValue).scts[0];
var preimage = pki.ct.reconstructSignedData({ entryType: 0, leafCert: der }, sct);
// hash `preimage` + verify against the log's public key at the verify layer
References
- spec RFC 6962
pki.ct.verifySct
pki.ct.verifySct(entry, sct, logPublicKey) -> Promise<boolean>
Verify a Signed Certificate Timestamp's signature against a Certificate Transparency log's public key (RFC 6962 sec. 3.2). entry is the log entry the SCT covers ({ entryType: 0, leafCert } or { entryType: 1, tbsCertificate, issuerKeyHash }, as for reconstructSignedData), sct a decoded v1 SCT from parseSctList().scts[], and logPublicKey the log's SubjectPublicKeyInfo (DER Buffer). Reconstructs the exact signed data, imports the log key, and verifies the SCT signature -- an ECDSA signature is routed through the strict DER ECDSA-Sig-Value conformance gate before conversion to the raw r||s WebCrypto expects, an RSA signature verifies directly.
Resolves true on a valid signature and false on a cryptographic mismatch (a false verdict is a verdict). Throws a typed CtError on structural failure -- a malformed entry/SCT, an unusable log key, or an unsupported hash/signature algorithm.
Example
async function example() {
var sct = pki.ct.parseSctList(sctExtValue).scts[0];
// Resolve the CT log's DER SubjectPublicKeyInfo from a trusted log list, keyed by log id.
var logKeysByLogId = {}; // { sct.logIdHex: <SPKI Buffer>, ... }
var logKey = logKeysByLogId[sct.logIdHex];
var ok = await pki.ct.verifySct({ entryType: 0, leafCert: certDer }, sct, logKey);
}
example();
References
- spec RFC 6962
- defends
sct-signature-forgery (CWE-347)
pki.ct.encodeSctList
pki.ct.encodeSctList(scts) -> Buffer
Build the value of an RFC 6962 SCT-list extension from an array of SCTs -- the exact inverse of parseSctList, such that parseSctList(encodeSctList(list.all)) round-trips to identical bytes. Each element is either a decoded v1 SCT (the shape parseSctList().scts[] or signSct returns: version 0, 32-byte logId, timestamp BigInt, raw extensions, hashAlg / sigAlg code points, raw signature) -- rebuilt from its fields in the RFC 6962 sec. 3.2 field order -- or an opaque non-v1 entry ({ version, rawSct }) whose rawSct is re-emitted verbatim (forward compatibility, sec. 3.3). Pass parseSctList().all (not .scts) to preserve the exact wire order and every unknown-version entry.
Returns the DER OCTET STRING-wrapped TLS SignedCertificateTimestampList (the same extnValue content parseSctList consumes). The list must be non-empty and stays within the parser's SCT_MAX_COUNT element cap and the RFC 6962 sec. 3.3 65535-byte list-body cap so encode cannot emit what parse would reject. Throws a typed CtError (ct/empty-list, ct/bad-input, ct/too-large, ct/too-many-scts) on malformed input.
Example
var list = pki.ct.parseSctList(sctExtValue);
var reEncoded = pki.ct.encodeSctList(list.all); // byte-identical to sctExtValue
References
pki.ct.signSct
pki.ct.signSct(entry, logKey, opts?) -> Promise<sct>
Perform a Certificate Transparency log's signing step (RFC 6962 sec. 3.2): rebuild the exact digitally-signed preimage over entry (via reconstructSignedData, the SAME builder the verifier hashes), sign it with the log's private key, and return a fully-formed v1 SCT that verifySct accepts against the log's public key. entry is the log entry the SCT covers ({ entryType: 0, leafCert } or { entryType: 1, tbsCertificate, issuerKeyHash }, as for reconstructSignedData); logKey is the log's private key (PKCS#8 DER Buffer, PEM string, or a node KeyObject).
The log-key profile is RFC 6962 sec. 2.1.4: ECDSA NIST P-256 (sigAlg 3) or RSA >= 2048 (sigAlg 1), SHA-256 only -- an unsupported key fails closed ct/unsupported-algorithm. The logId is derived as SHA-256 of the log SPKI (sec. 3.4); a supplied opts.logId must match. The returned SCT is the parseSctList/verifySct shape and composes with encodeSctList.
Options
logId assert the derived LogID equals this 32-byte value (fail closed on mismatch).
Example
async function example() {
var sct = await pki.ct.signSct({ entryType: 0, leafCert: der }, signerKeyPkcs8);
var ext = pki.ct.encodeSctList([sct]);
}
example();
References
- spec RFC 6962
pki.ct.parseLogList
pki.ct.parseLogList(json, opts?) -> { logs, byLogId, version, timestamp }
Ingest a Certificate Transparency log-list JSON document (the log_list.json browsers consume) into a set of constraint-carrying trusted logs, keyed by log-id. json is a Buffer or string -- the caller supplies the already-fetched, already-authenticated bytes (offline, no network fetch). Parsing routes through the bounded, duplicate-member-rejecting JSON reader; for each log it base64-decodes the key to its DER SubjectPublicKeyInfo, validates it as a well-formed on-profile key, **recomputes** SHA-256(SPKI) and fail-closed **requires** it equal the stated log_id (RFC 6962 sec. 3.2 -- a log whose stated id disagrees with its key is refused as ct/log-id-mismatch), and decodes the state (exactly one of pending/qualified/usable/readonly/retired/rejected) and temporal_interval. Returns { logs, byLogId, version, timestamp } where each log is { logId, logIdHex, key, description, url, mmd, operator, state: { name, since, trusted, conditional }, temporalInterval, trusted }, byLogId is a null-proto { logIdHex: log } map, version is the document's version string (or null), and timestamp is the parsed log_list_timestamp Date (or null when absent/unparseable -- the staleness surface, read leniently, never a throw). Every malformed / oversized / mis-bound input is a typed CtError.
Example
var logList = pki.ct.parseLogList(logListJsonBytes);
logList.logs[0].trusted; // was the first log trusted (usable/qualified/readonly)?
References
- spec RFC 6962
pki.ct.verifySctWithLogList
pki.ct.verifySctWithLogList(entry, sct, logList, opts?) -> Promise<boolean>
Resolve the trusted CT log for an SCT and verify it in one step. logList is a parseLogList result; the log is resolved by sct.logIdHex (an unknown log is ct/log-not-found). The log's **state** gates trust (usable/qualified/readonly proceed; a retired log proceeds only for an SCT timestamped before its retirement instant; pending/rejected are ct/log-untrusted); its **temporal_interval** gates the covered certificate (the cert's notAfter -- from entry.leafCert when entryType is 0, or opts.certNotAfter -- must fall in [start_inclusive, end_exclusive), and a windowed log with no resolvable notAfter is ct/temporal-interval, never silently skipped). Then the crypto is delegated to the shipped verifySct(entry, sct, log.key) (which independently re-checks logId == SHA-256(key)). Resolves true for a valid signature from a trusted, in-window log; false on a cryptographic mismatch (a verdict); throws a typed CtError on any structural / trust failure.
Options
certNotAfter A `Date` -- the covered certificate's notAfter for the temporal-interval gate (required for a precert entry).
Example
async function example() {
var ok = await pki.ct.verifySctWithLogList(sctEntry, embeddedSct, logList);
}
example();
References
- spec RFC 6962
pki.ct.verifyLogListSignature
pki.ct.verifyLogListSignature(json, signature, publicKey) -> Promise<boolean>
Verify the detached signature published alongside the Certificate Transparency log list (the log_list.sig over log_list.json). json is the RAW log-list bytes (a Buffer, or the fetched text as a string -- verified byte-for-byte, never re-serialized), signature is the detached signature, and publicKey is the caller-PINNED signer SubjectPublicKeyInfo (DER; there is no baked-in key). The scheme is RSASSA-PKCS1-v1.5 with SHA-256 over an RSA key (the deployed scheme; an EC P-256 / ECDSA-SHA-256 arm is accepted for future-proofing). Resolves true for a valid signature, false on a cryptographic mismatch (a verdict). Fail-closed forgery defenses throw before any verify: an RSA public exponent below 3 or even (ct/bad-input), a sub-2048-bit RSA key or an unsupported key type / curve (ct/unsupported-algorithm), a non-conformant ECDSA DER Sig-Value (ct/bad-signature); a structural evaluation failure is ct/verify-error. Offline: the caller fetches and pins; the toolkit only verifies.
Example
async function example() {
var ok = await pki.ct.verifyLogListSignature(logListJsonBytes, logListSig, googleSignerSpki);
}
example();
References
pki.ct.fetchLogList
pki.ct.fetchLogList(opts) -> Promise<{ logs, byLogId, version, timestamp, raw, status, contentType, tls }>
Fetch the Certificate Transparency log list live and return the trusted-log set ONLY after the detached signature verifies against the caller-pinned distributor key. It GETs opts.url (the log_list.json) and the detached opts.sigUrl (the log_list.sig, by default opts.url with a .json path suffix rewritten to .sig) over the shared, fail-closed pki.transport (or an injected opts.transport), then verifies the detached signature over the RAW fetched JSON bytes against opts.signerKey and only on a strict true verdict ingests the SAME bytes through parseLogList -- so the client never parses, reads, caches, or surfaces any field of an unverified document (verify-before-parse). No baked-in vendor URL and no baked-in key: the caller pins both out-of-band. Trust is EXPLICIT -- an opts.tls.anchors set or an opts.tls.useSystemStore opt-in, rejectUnauthorized always on. The returned timestamp is surfaced (never policed) so the caller enforces its own freshness policy; chaining a resolved log to an SCT is the caller's verifySctWithLogList step. Every fetch / verify / parse failure is a typed CtError.
Options
requireJsonContentType OPTIONAL boolean, default false -- opt in to a strict `ct/bad-content-type` gate on the JSON GET.
Example
async function example() {
// a live distributor uses the default pki.transport.https; here an injected transport returns the pair
var r = await pki.ct.fetchLogList({ url: "https://ct.example/log_list.json", signerKey: googleSignerSpki,
transport: function (req) {
var isSig = /\.sig$/.test(req.url);
return Promise.resolve({ status: 200, headers: { "content-type": isSig ? "application/octet-stream" : "application/json" }, body: isSig ? logListSig : logListJsonBytes });
} });
r.logs[0] && r.logs[0].trusted; // the verified, trusted-log set (the detached signature checked first)
}
example();
References
- spec RFC 6962