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

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-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

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