# `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-`Z` zone designators rejected. - 50/49 split — `YY < 50` resolves to `20YY`, `YY >= 50` resolves to `19YY`. - 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 ```ts 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 ```ts 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 `encodeTime` is 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-`Z` zone designators are rejected — both RFC 5280 sections require UTC. - Component-range checks happen after `Date.UTC` so leap-year arithmetic is consistent with JavaScript's `Date` implementation (e.g. February 29 on non-leap years).