Skip to content

fix: harden GeneralizedTime to whole seconds in cert/CRL generators (#122) - #142

Open
crossi-dev wants to merge 1 commit into
PeculiarVentures:masterfrom
crossi-dev:fix/122-generalizedtime-no-fractional
Open

fix: harden GeneralizedTime to whole seconds in cert/CRL generators (#122)#142
crossi-dev wants to merge 1 commit into
PeculiarVentures:masterfrom
crossi-dev:fix/122-generalizedtime-no-fractional

Conversation

@crossi-dev

Copy link
Copy Markdown

Re #122

You noted the fix landed in @peculiar/asn1-x509 but that x509 itself hadn't been hardened yet — so this does two things:

1. Bump the floor. @peculiar/asn1-x509 ^2.6.0^2.6.1, so a fresh install can't resolve a pre-fix version of the dependency.

2. Defense-in-depth in the generators. Truncate notBefore/notAfter (and the CRL's thisUpdate/nextUpdate/revocationDate) to whole seconds before building the ASN.1 Validity:

const truncate = (d: Date): Date => new Date(Math.floor(d.getTime() / 1000) * 1000);

Per RFC 5280 §4.1.2.5 (GeneralizedTime MUST NOT carry fractional seconds), so x509 emits compliant DER regardless of which asn1-x509 actually resolves, or whether a caller passes a Date with non-zero ms. The caller's Date objects aren't mutated.

Why it matters

A notAfter after 2049 is encoded as GeneralizedTime; with non-zero ms the old path produced 20700101000000.500Z, which OpenSSL rejects (error 14). Fresh installs already dodge this via the dep, but the x509 layer itself stays correct now too.

Verified

16 files, 291 passed, 0 failures

New #122 regression test: notAfter in 2070 with ms=500 → asserts no GeneralizedTime byte contains ., decoded ms is 0, and the caller's Date is untouched.


I bumped the dep floor to ^2.6.1 rather than pinning — want it tighter, or is the caret range what you'd prefer here? Either's a one-liner.

(I'm with Choreless — we fix and ship for small teams — but no strings, x509 is just a lib I lean on.) — Charles

…5280

RFC 5280 §4.1.2.5.2 mandates that GeneralizedTime values in X.509
certificates MUST NOT include fractional seconds. When a caller passes
a Date with non-zero milliseconds and notAfter falls after 2049 (the
GeneralizedTime boundary), asn1js encodes the milliseconds as a
fractional suffix (e.g. 20700101000000.500Z), which OpenSSL rejects
with 'format error in certificate notAfter field'.

Apply truncation to whole seconds at the x509 layer in both
X509CertificateGenerator.create() and X509CrlGenerator.create()
before constructing ASN.1 Validity/Time objects. Truncation uses
Math.floor(d.getTime()/1000)*1000 so the caller's Date is never mutated.

Also bump the @peculiar/asn1-x509 minimum from ^2.6.0 to ^2.6.1, the
version where the same fix was applied at the dependency level (defense
in depth so either layer is sufficient on its own).

Closes PeculiarVentures#122
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant