2.8 KiB
2.8 KiB
asn1/time — UTCTime and GeneralizedTime codec
Purpose
Decodes and encodes the two time formats RFC 5280 paragraph 4.1.2.5 permits inside a certificate's Validity field: UTCTime (YYMMDDHHMMSSZ, two-digit-year 50/49 split per RFC 5280 paragraph 4.1.2.5.1) and GeneralizedTime (YYYYMMDDHHMMSSZ, mandatory Z per RFC 5280 paragraph 4.1.2.5.2). Returns and accepts epoch-ms so the validity-period check in validate-chain can compare against any injectable Clock. The encoder switches to GeneralizedTime when the timestamp is at or after 2050-01-01T00:00:00Z per the RFC's hard cutoff.
Features
- Strict shape regex per format — fractional seconds and non-
Zzone designators rejected. - 50/49 split —
YY < 50resolves to20YY,YY >= 50resolves to19YY. - Component-range validation — month 1-12, day 1-31, hour 0-23, minute 0-59, second 0-59.
- Encoder picks GeneralizedTime when timestamp lies outside the UTCTime-safe
[1950-01-01, 2050-01-01)window. decodeTime(tagNumber, body)dispatcher consumes whichever choice the wire carries.
Usage: Basic Example
import { decodeUtcTime, decodeGeneralizedTime, encodeTime } from "@suaveplan/x509/asn1";
const t = decodeUtcTime(new TextEncoder().encode("250101000000Z")); // 2025-01-01T00:00:00Z
const { tagNumber, body } = encodeTime(Date.UTC(2024, 0, 15)); // UTCTime
const { tagNumber: t2 } = encodeTime(Date.UTC(2055, 0, 15)); // GeneralizedTime
Usage: Advanced Example
import { decodeAsn1, decodeTime } from "@suaveplan/x509/asn1";
// Inside a Validity SEQUENCE, choose between UTCTime and GeneralizedTime
// based on the observed tag:
const child = decodeAsn1(timeBytes);
if (!("content" in child)) throw new Error("expected primitive Time");
const notBefore = decodeTime(child.tag.tagNumber, child.content);
API Reference
decodeUtcTime(body: Uint8Array): number— strict UTCTime decode to epoch-ms.decodeGeneralizedTime(body: Uint8Array): number— strict GeneralizedTime decode to epoch-ms.decodeTime(tagNumber: number, body: Uint8Array): number— universal-tag dispatcher.encodeTime(epochMs: number): { tagNumber, body }— RFC 5280 paragraph 4.1.2.5 compliant encoder picking UTCTime or GeneralizedTime.
Implementation Notes
- The 2050 boundary on
encodeTimeis the cutoff specified by RFC 5280 paragraph 4.1.2.5.1 — UTCTime cannot represent dates after 2050. - Pre-1950 dates also switch to GeneralizedTime — UTCTime's 50/49 split cannot represent them.
- Fractional seconds in GeneralizedTime are rejected — RFC 5280 paragraph 4.1.2.5.2 forbids them.
- Non-
Zzone designators are rejected — both RFC 5280 sections require UTC. - Component-range checks happen after
Date.UTCso leap-year arithmetic is consistent with JavaScript'sDateimplementation (e.g. February 29 on non-leap years).