@gldywn/sct.js
v2.0.0
Published
Verification of Signed Certificate Timestamps (SCTs) for Certificate Transparency in JavaScript
Readme
SCT.js
SCT.js is a Certificate Transparency SCT verifier in TypeScript for Node.js.
It uses the built-in node:crypto module for all cryptographic operations.
Status
- The library is stable and ready for production use.
- Supports both X.509 and Pre-certificate SCT entries.
- The test suite for X.509 entries is ported from the original Rust library. It has been augmented with a comprehensive, generated test suite for Pre-certificate entries, covering all success and error cases for multiple signature algorithms (ECDSA P-256/P-384, RSA 2048/3072/4096).
How SCTs are Acquired
Signed Certificate Timestamps (SCTs) are the core of Certificate Transparency, but they can be delivered to a client in several ways. This library is responsible for the verification of an SCT, regardless of how it was acquired. It is the responsibility of the client application to obtain the certificate and its associated SCTs.
There are three primary methods for delivering SCTs:
- Embedded in the Certificate: The SCT is included directly in the final X.509 certificate as an extension (
1.3.6.1.4.1.11129.2.4.2). This is the most common method. - Via a TLS Extension: The SCT is delivered during the TLS handshake via the
signed_certificate_timestampextension. This is useful for certificates issued before the log was trusted, or when the CA is not participating in CT. - Via OCSP Stapling: The SCT is included in a stapled OCSP response, which is sent by the server during the TLS handshake.
This library can verify SCTs from any of these sources, provided you can supply the SCT data and the certificate or pre-certificate data it corresponds to.
Installation
npm install @gldywn/sct.jsUsage
The library supports two primary verification scenarios: for SCTs embedded in a pre-certificate (most common, found in the final certificate) and for SCTs delivered via TLS extension or OCSP, which are verified against the final X.509 certificate.
Verifying a Pre-certificate SCT
This is the most common use case, typically used to verify an SCT that was embedded directly into a certificate during its issuance. To do this, you need both the final certificate (leafCert) and the certificate of its issuer (issuerCert). The reconstructPrecert function is used to create the data structure that the SCT was originally signed against.
For a complete, runnable example, see examples/verify-precert.ts.
import { verifySct, reconstructPrecert, ENTRY_TYPE, Log } from '@gldywn/sct.js';
try {
// The leaf certificate (end-entity) and the issuer certificate both DER-encoded X.509
const leafCert = readTestData('leaf_google-cert.bin', 'precert');
const issuerCert = readTestData('issuer_google-cert.bin', 'precert');
// The SCT embedded within the leaf certificate (extracted from the certificate itself)
const precertSct = readTestData('google-sct0.bin', 'precert');
// Helper function to reconstruct the precertificate data structure to verify the SCT against
const precert = reconstructPrecert(leafCert, issuerCert);
// A list of trusted logs
const trustedLogs: Log[] = [
{
description: 'Fetched Google Test Log',
key: getTrustedLogKey(),
id: createHash('sha256')
.update(getTrustedLogKey().export({ type: 'spki', format: 'der' }))
.digest(),
url: 'test.com',
operated_by: 'Test',
max_merge_delay: 0,
status: 'usable',
},
];
const { log, sct } = verifySct(precertSct, precert, ENTRY_TYPE.PRECERT_ENTRY, Date.now(), trustedLogs);
console.log(`Pre-cert SCT verification successful!`);
console.log(` Log ID: ${log.id.toString('hex')}`);
console.log(` Log: ${log.description}`);
console.log(` Timestamp: ${new Date(Number(sct.timestamp)).toISOString()}`);
} catch (error) {
console.error('Error while verifying pre-certificate SCT:', error);
}Verifying an X.509 SCT (from TLS Extension / OCSP)
This method is used when the SCT is delivered separately from the certificate, such as in a signed_certificate_timestamp TLS extension or a stapled OCSP response. In this case, the SCT is verified against the final, DER-encoded X.509 certificate directly, and no reconstruction is needed.
For a complete, runnable example, see examples/verify-x509.ts.
import { verifySct, ENTRY_TYPE, Log } from '@gldywn/sct.js';
try {
// The leaf certificate (end-entity), DER-encoded X.509
const certificate = readTestData('google-cert.bin', 'x509');
// The SCT, obtained either from the "signed_certificate_timestamp" TLS extension or OCSP response
const sct = readTestData('google-sct0.bin', 'x509');
// A list of trusted logs
const trustedLogs: Log[] = [
{
description: "Google 'Pilot' log",
key: jwkFromRawEcdsa(readTestData('google-pilot-pubkey.bin', 'x509')),
id: Buffer.from('a4b90990b418581487bb13a2cc67700a3c359804f91bdfb8e377cd0ec80ddc10', 'hex'),
url: 'ct.googleapis.com/pilot/',
operated_by: 'Google',
max_merge_delay: 86400,
status: 'usable',
},
];
const { log, sct: parsedSct } = verifySct(sct, certificate, ENTRY_TYPE.X509_ENTRY, Date.now(), trustedLogs);
console.log(`X.509 SCT verification successful!`);
console.log(` Log ID: ${log.id.toString('hex')}`);
console.log(` Log: ${log.description}`);
console.log(` Timestamp: ${new Date(Number(parsedSct.timestamp)).toISOString()}`);
} catch (error) {
console.error('Error while verifying X.509 SCT:', error);
}Test
This project includes a comprehensive test suite to ensure correctness and stability.
Updating Test Data
The repository includes pre-generated test data. To update the fixtures for pre-certificate tests, run:
npm run test:setupThis command performs the following steps:
- Generates test keys: Creates new key pairs for all supported algorithms.
- Generates pre-cert data: Builds a fresh suite of mock SCTs and test cases.
- Fetches real-world data: Downloads the latest test certificates from Google.
Running Tests
To run the complete test suite:
npm testLicense
SCT.js is distributed under the MIT license.
Browser Compatibility
This library is currently intended for Node.js environments only.
It relies on Node.js built-in modules like crypto and Buffer that are not available in web browsers. While it is theoretically possible to use this library in a browser by using polyfills (such as crypto-browserify and buffer), this is not officially supported or tested at this time. Future versions may include a browser-compatible bundle.
Acknowledgements
This library is a full-featured TypeScript port of the excellent rustls/sct.rs library. It is designed to be a 1:1 functional equivalent and includes a faithful port of the original's comprehensive test suite to ensure correctness.
