Files
skills/docs/security-stack/security/x509/src/asn1/time.md
2026-05-29 08:20:56 +01:00

52 lines
2.8 KiB
Markdown

# `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).