52 lines
2.8 KiB
Markdown
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).
|