Skip to content

Fix encoding/decoding of multi-byte first OID subidentifier#1148

Open
spokodev wants to merge 1 commit into
digitalbazaar:mainfrom
spokodev:fix/asn1-multibyte-first-oid-subidentifier
Open

Fix encoding/decoding of multi-byte first OID subidentifier#1148
spokodev wants to merge 1 commit into
digitalbazaar:mainfrom
spokodev:fix/asn1-multibyte-first-oid-subidentifier

Conversation

@spokodev

Copy link
Copy Markdown

Problem

asn1.oidToDer / asn1.derToOid mis-handle the first OID subidentifier when it spans more than one byte. The first subidentifier encodes the first two arcs as 40 * arc1 + arc2; per X.690 §8.19 it is a base-128 multi-byte value like every other subidentifier, so for arc1 === 2 (where arc2 is unbounded) it routinely exceeds one byte. Both directions corrupt such OIDs:

const forge = require('node-forge');
forge.asn1.oidToDer('2.999.3').toHex();                  // '43703'     wrong (X.690: '883703')
forge.asn1.derToOid(forge.util.hexToBytes('883703'));   // '3.16.55.3' wrong (X.690: '2.999.3')
forge.asn1.oidToDer('2.100').toHex();                   // 'b4'        wrong (X.690: '8134')
  • Encode writes 40*arc1 + arc2 with a single putByte, truncating values ≥ 256 and never setting the continuation bit (so values in 128–255 produce a byte a decoder reads as "more bytes follow").
  • Decode reads exactly one byte and splits it, ignoring the continuation bit, so a multi-byte first subidentifier is shredded into bogus arcs (and can emit a structurally impossible root arc of 3).

This is not limited to the 2.999 example arc: registered arcs that appear in real certificates are affected, e.g. 2.41.x (biometrics, ISO/IEC JTC 1 SC 37), 2.49.x (NATO), 2.51.x (GS1). Any DER carrying such an OID is written wrong and read wrong.

Fix

  • oidToDer: feed the first subidentifier through the existing base-128 emitter instead of a raw putByte.
  • derToOid: accumulate the first subidentifier across continuation bytes like every other subidentifier, then split it (value < 80 → ⌊value/40⌋.value%40, else 2.(value−80)).

Verification

  • New round-trip tests for a multi-byte first subidentifier (2.999.3883703, 2.1008134); they fail on main and pass with the fix.
  • Output cross-checked against OpenSSL asn1parse and an independent X.690 §8.19 implementation across an OID fuzz (first-arc sweeps + random wide arcs): 0 mismatches, and 0 changes to previously-correct OIDs.
  • The existing asn1, oids, x509, pem suites pass unchanged.

The pre-existing >32-bit-arc limitation (the 0xffffffff guard / TODO) is unchanged and out of scope.

`oidToDer` wrote the first subidentifier (40 * arc1 + arc2) as a single
raw byte, and `derToOid` read it back as a single byte. Per X.690 8.19
the first subidentifier is a base-128 multi-byte value like every other
arc, so it can exceed one byte whenever arc1 is 2 (arc2 is then
unbounded). Both directions silently corrupted such OIDs:

  oidToDer('2.999.3')                  // '43703'     (should be '883703')
  derToOid(hexToBytes('883703'))       // '3.16.55.3' (should be '2.999.3')

This affects registered arcs that appear in real certificates, e.g.
2.41.x (biometrics), 2.49.x (NATO), 2.51.x (GS1).

Encode the first subidentifier through the existing base-128 loop, and
on decode accumulate it across continuation bytes before splitting it
into the first two arcs. Output verified against OpenSSL `asn1parse` and
the X.690 8.19 rule across an OID fuzz.
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