@blamejs/pki 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -97,6 +97,12 @@ var EXT_BY_INT = {
97
97
  29: _name("freshestCRL"),
98
98
  30: _name("inhibitAnyPolicy"),
99
99
  31: _name("subjectInfoAccess"),
100
+ // RFC 3779 resource-delegation extensions, and their RFC 8360 "v2" twins, which the draft
101
+ // encodes "exactly like" the originals -- same two codecs, four registry rows (sec. 8.8).
102
+ 32: _name("ipAddrBlocks"),
103
+ 33: _name("autonomousSysIds"),
104
+ 34: _name("ipAddrBlocksV2"),
105
+ 35: _name("autonomousSysIdsV2"),
100
106
  36: _name("ocspNoCheck"),
101
107
  38: _name("tlsFeature"),
102
108
  };
@@ -108,6 +114,7 @@ var EXT_COMPACT = {
108
114
  subjectAltName: 1, issuerAltName: 1, nameConstraints: 1, cRLDistributionPoints: 1,
109
115
  freshestCRL: 1, authorityInfoAccess: 1, subjectInfoAccess: 1, certificatePolicies: 1,
110
116
  policyMappings: 1, policyConstraints: 1, subjectDirectoryAttributes: 1,
117
+ ipAddrBlocks: 1, autonomousSysIds: 1, ipAddrBlocksV2: 1, autonomousSysIdsV2: 1,
111
118
  };
112
119
  // sec. 8.12 Extended Key Usages registry (C509 int -> registered id-kp purpose name). A KeyPurposeId
113
120
  // outside this set encodes as an unwrapped ~oid; a C509 int outside it fails closed on decode.
@@ -555,6 +562,332 @@ function _ncIpFromDer(buf) {
555
562
  return Buffer.concat([buf.subarray(0, addrLen), Buffer.from([prefixLen])]);
556
563
  }
557
564
 
565
+ // ---- RFC 3779 IPAddrBlocks / ASIdentifiers (draft sec. 3.3, ext ints 32-35) -----------------
566
+ //
567
+ // An RFC 3779 IPAddress is a BIT STRING whose unused-bit count carries the prefix length, so the
568
+ // draft maps it to the byte sequence `unusedBits || value`, which "preserves the exact information
569
+ // contained in the ASN.1 BIT STRING" -- lossless even for a prefix ending in zero bits. Per
570
+ // IPAddressFamily the draft then picks ONE of two forms and makes the choice a SHALL: if any of the
571
+ // family's byte sequences exceeds 8 octets the whole family uses the bytes form, otherwise the int
572
+ // form. Accepting the wrong one would give one DER two CBOR encodings, so decode enforces it.
573
+ //
574
+ // The int form is the big-endian integer of `(unusedBits + 1) || value` -- the +1 guarantees a
575
+ // non-zero leading octet, which is what makes the minimal big-endian representation unambiguous --
576
+ // and every IPAddress after the first is stored as the DIFFERENCE from its predecessor. The chain
577
+ // runs flat over each address in order (a range contributes min then max) and RESETS at each
578
+ // family, so a family always opens with an absolute value.
579
+ //
580
+ // These integers reach 2^64-1: an IPv6 /48 already exceeds 2^53, and the draft's own A.5 vector
581
+ // lands there. Everything below is BigInt end to end and bounds through guard.range.uint64 (the
582
+ // BigInt-preserving guard) -- never the uint31 counter bound, which would reject a valid prefix.
583
+
584
+ // An asn1 node exposes tagClass + tagNumber (never a combined `.tag`), so a universal-type test
585
+ // has to check both -- a context-tagged [16] must not read as a SEQUENCE.
586
+ function _isUniversal(n, tagNumber) { return !!n && n.tagClass === "universal" && n.tagNumber === tagNumber; }
587
+
588
+ // `unusedBits || value` -> the (unusedBits+1)||value integer. Returns null if the sequence cannot
589
+ // be one (empty, or an unused-bit count DER would not accept).
590
+ function _ipSeqToInt(seq) {
591
+ if (!seq.length || seq[0] > 7) return null;
592
+ // One-shot base-256 parse of (unusedBits+1)||value. A per-byte shift-accumulate is quadratic in
593
+ // the operand width, so the toolkit builds a BigInt from its hex in a single call instead.
594
+ var head = Buffer.from([seq[0] + 1]);
595
+ return BigInt("0x" + Buffer.concat([head, seq.subarray(1)]).toString("hex"));
596
+ }
597
+ // The inverse: the minimal big-endian octets of `n`, with the leading octet decremented back to the
598
+ // unused-bit count. Returns null when `n` cannot be an encoded IPAddress -- a leading octet outside
599
+ // 1..8 is not a DER unused-bit count, and past 8 octets the family was required to use the bytes form.
600
+ function _ipIntToSeq(n) {
601
+ if (n < 1n) return null;
602
+ var out = [];
603
+ for (var v = n; v > 0n; v >>= 8n) out.unshift(Number(v & 0xffn));
604
+ if (out.length > 9 || out[0] < 1 || out[0] > 8) return null;
605
+ out[0] -= 1;
606
+ return Buffer.from(out);
607
+ }
608
+ // The address width in octets of an address family, from its AFI (RFC 3779 sec. 2.2.3.3). Only a
609
+ // family whose width is known can have its canonical form checked, so an unrecognized AFI yields
610
+ // null and the caller declines to compact rather than compacting something it cannot verify.
611
+ var _IP_WIDTH = { 1: 4, 2: 16 };
612
+
613
+ // The lowest address an `unusedBits || value` sequence denotes, as `width` big-endian octets. DER
614
+ // forces the unused bits to zero, so the value IS its own lowest address once zero-extended.
615
+ function _ipLow(seq, width) {
616
+ var v = Buffer.alloc(width);
617
+ seq.subarray(1).copy(v);
618
+ return v;
619
+ }
620
+ // The highest address it denotes: the same prefix with every host bit set (RFC 3779 sec. 2.2.3.8 --
621
+ // a range's max is likewise the prefix with its trailing bits taken as ones).
622
+ function _ipHigh(seq, width) {
623
+ var v = _ipLow(seq, width);
624
+ var bits = (seq.length - 1) * 8 - seq[0];
625
+ for (var i = bits; i < width * 8; i++) v[i >> 3] |= 0x80 >> (i & 7);
626
+ return v;
627
+ }
628
+ // Big-endian octet-string compare, and "is `b` the immediate successor of `a`" -- the test that
629
+ // distinguishes a legal gap from a contiguous pair the RFC requires be merged.
630
+ function _ipOctCmp(a, b) {
631
+ for (var i = 0; i < a.length; i++) { if (a[i] !== b[i]) return a[i] - b[i]; }
632
+ return 0;
633
+ }
634
+ function _ipIsSuccessor(a, b) {
635
+ var carry = 1, inc = Buffer.from(a);
636
+ for (var i = inc.length - 1; i >= 0 && carry; i--) { var s = inc[i] + carry; inc[i] = s & 0xff; carry = s >> 8; }
637
+ if (carry) return false; // `a` was already the maximum address
638
+ return _ipOctCmp(inc, b) === 0;
639
+ }
640
+
641
+ // RFC 3779 sec. 2.2.3.7: "any range of addresses that can be encoded as a prefix MUST be encoded
642
+ // using an IPAddress element", with the choice fixed by the spec's own pseudocode -- let N be the
643
+ // count of matching leading bits of the low and high addresses; if every remaining bit of the low
644
+ // is zero AND every remaining bit of the high is one, the span IS the N-bit prefix and the range
645
+ // form is forbidden. Two encodings of one address span would otherwise both be legal.
646
+ function _ipRangeIsPrefix(lo, hi) {
647
+ var bits = lo.length * 8, n = 0;
648
+ while (n < bits) {
649
+ var byteAt = n >> 3, mask = 0x80 >> (n & 7);
650
+ if ((lo[byteAt] & mask) !== (hi[byteAt] & mask)) break;
651
+ n++;
652
+ }
653
+ for (var i = n; i < bits; i++) {
654
+ var bt = i >> 3, mk = 0x80 >> (i & 7);
655
+ if ((lo[bt] & mk) !== 0) return false; // a low-address host bit is set
656
+ if ((hi[bt] & mk) === 0) return false; // a high-address host bit is clear
657
+ }
658
+ return true;
659
+ }
660
+
661
+ // RFC 3779 sec. 2.2.3.3 orders the families themselves: "There MUST be only one IPAddressFamily
662
+ // SEQUENCE per unique combination of AFI and SAFI. Each SEQUENCE MUST be ordered by ascending
663
+ // addressFamily values (treating the octets as unsigned quantities). An addressFamily without a
664
+ // SAFI MUST precede one that contains an SAFI." A plain unsigned octet-string compare gives all
665
+ // three at once, because a two-octet family is a PREFIX of the three-octet one sharing its AFI and
666
+ // a prefix sorts first. Returns < 0, 0 or > 0; 0 means the same AFI/SAFI appeared twice.
667
+ function _famOctCmp(a, b) {
668
+ var n = Math.min(a.length, b.length);
669
+ for (var i = 0; i < n; i++) { if (a[i] !== b[i]) return a[i] - b[i]; }
670
+ return a.length - b.length;
671
+ }
672
+
673
+ // Is `unusedBits || value` a sequence DER would accept as a BIT STRING? The declared unused low
674
+ // bits MUST be zero (RFC 3779 sec. 2.2.3.8 restates the DER rule), and a value with no octets can
675
+ // only declare zero unused bits. Checked HERE rather than left to the BIT STRING builder, because
676
+ // the builder's fault is an asn1/* error surfacing out of a CBOR-layer decode -- a caller handed a
677
+ // malformed compact value should see this module's own verdict, not the ASN.1 layer's.
678
+ function _ipSeqBitsClear(seq) {
679
+ var unused = seq[0], value = seq.subarray(1);
680
+ if (!value.length) return unused === 0;
681
+ if (unused === 0) return true;
682
+ return (value[value.length - 1] & ((1 << unused) - 1)) === 0;
683
+ }
684
+
685
+ // RFC 3779 sec. 2.2.3.6 fixes the canonical form of an address list: entries sorted on
686
+ // `<lowest address> | <prefix length>` (which is neither the DER byte order nor the compact integer
687
+ // order -- the RFC warns about the first and the second sorts the same wrong way), no pair
688
+ // overlapping, and any contiguous pair combined into one entry. All three bind together: a list
689
+ // violating any of them is not the one canonical encoding of its address set, so this codec
690
+ // declines to compact it and the extension keeps its original bytes.
691
+ // Two comparisons carry all three rules. `cur.lo > prev.hi` is the no-overlap rule AND the sort
692
+ // rule at once: entries are already known to have lo <= hi, so an entry that started at or before
693
+ // its predecessor's low address would also start at or before its high one. A separate ascending
694
+ // test would therefore be a branch nothing can reach.
695
+ function _ipRangesCanonical(bounds) {
696
+ for (var i = 1; i < bounds.length; i++) {
697
+ var prev = bounds[i - 1], cur = bounds[i];
698
+ if (_ipOctCmp(cur.lo, prev.hi) <= 0) return false; // out of order, or overlapping
699
+ if (_ipIsSuccessor(prev.hi, cur.lo)) return false; // contiguous: MUST have been merged
700
+ }
701
+ return true;
702
+ }
703
+
704
+ // One family's IntIPAddressChoice / IPAddressChoice -> the DER IPAddressOrRange SEQUENCE list.
705
+ // The int form stores each address after the first as a DIFFERENCE from its predecessor, flat over
706
+ // every address in order (a range contributes min then max); the chain resets at each family, which
707
+ // is why `prev` starts null here rather than being threaded across families. The reconstructed
708
+ // ABSOLUTE is what gets bounded -- the delta itself is an arbitrary CBOR int and bounding it would
709
+ // miss a chain that walks out of range in steps.
710
+ function _ipChoiceToDer(items, afi) {
711
+ var out = [], prev = null, sawBytes = false, sawInt = false, seqs = [];
712
+ // An address may not be wider than its family: RFC 3779 sec. 2.2.3.8 sizes an IPAddress by the
713
+ // family, so a 5-octet address under AFI 1 is not an IPv4 address at all. Without this, a native
714
+ // C509 would reconstruct into a DER carrying an over-wide address -- one the RFC forbids and an
715
+ // independent validator refuses -- from CBOR this codec had accepted. The mirror check lives on
716
+ // the encode side; both directions have to hold or the pair is not a bijection.
717
+ // Without the family's address width none of the RFC 3779 rules below can be evaluated: the
718
+ // width bound has nothing to compare against, and the low/high bounds that drive the order,
719
+ // overlap, adjacency and endpoint checks cannot be computed at all. Accepting such a family
720
+ // would therefore wave every one of those checks through -- so a family this codec cannot
721
+ // measure is refused outright, matching the encode side, which declines to compact it. An
722
+ // `inherit` family is unaffected: it carries no addresses and never reaches here.
723
+ var width = _IP_WIDTH[afi];
724
+ if (!width) throw _err("c509/bad-extensions", "address family " + afi + " has no known address width, so its addresses cannot be checked (RFC 3779 sec. 2.2.3.3)");
725
+ function widthOk(seq) { return seq.length - 1 <= width; }
726
+ function absolute(node) {
727
+ var seq;
728
+ if (node.majorType === 2) { // bytes form: the sequence verbatim
729
+ sawBytes = true;
730
+ if (node.content.length === 0) throw _err("c509/bad-extensions", "an IPAddress byte sequence must be non-empty");
731
+ if (node.content[0] > 7) throw _err("c509/bad-extensions", "an IPAddress unused-bit count must be 0..7 (DER)");
732
+ seq = node.content;
733
+ } else {
734
+ sawInt = true;
735
+ var d = _cborIntVal(node, "an IPAddress");
736
+ var abs = prev === null ? d : prev + d;
737
+ prev = abs;
738
+ // Bound the reconstructed absolute through the BigInt-preserving guard: this domain reaches
739
+ // 2^64-1, so narrowing to Number would corrupt the value being guarded.
740
+ guard.range.uint64(abs, _err, "c509/bad-extensions", "an IPAddress");
741
+ seq = _ipIntToSeq(abs);
742
+ if (!seq) throw _err("c509/bad-extensions", "an IPAddress integer does not encode a DER BIT STRING (sec. 3.3)");
743
+ }
744
+ if (!widthOk(seq)) {
745
+ throw _err("c509/bad-extensions", "an IPAddress is wider than address family " + afi + " permits (RFC 3779 sec. 2.2.3.8)");
746
+ }
747
+ if (!_ipSeqBitsClear(seq)) {
748
+ throw _err("c509/bad-extensions", "an IPAddress must leave its declared unused bits zero (RFC 3779 sec. 2.2.3.8)");
749
+ }
750
+ return seq;
751
+ }
752
+ var bounds = [];
753
+ for (var i = 0; i < items.length; i++) {
754
+ var it = items[i];
755
+ if (it.majorType === 4) { // [min, max] -> an addressRange
756
+ if (!it.children || it.children.length !== 2) throw _err("c509/bad-extensions", "an IPAddress range must be exactly [min, max]");
757
+ var lo = absolute(it.children[0]), hi = absolute(it.children[1]);
758
+ seqs.push(lo); seqs.push(hi);
759
+ var rlo = _ipLow(lo, width), rhi = _ipHigh(hi, width);
760
+ {
761
+ // A span expressible as a prefix MUST use the prefix form (RFC 3779 sec. 2.2.3.7), or one
762
+ // address span would have two legal encodings.
763
+ if (_ipRangeIsPrefix(rlo, rhi)) throw _err("c509/bad-extensions", "an address range that is exactly a prefix must use the prefix form (RFC 3779 sec. 2.2.3.7)");
764
+ bounds.push({ lo: rlo, hi: rhi });
765
+ }
766
+ out.push(b.sequence([b.bitString(lo.subarray(1), lo[0]), b.bitString(hi.subarray(1), hi[0])]));
767
+ } else { // a single addressPrefix
768
+ var pfx = absolute(it);
769
+ seqs.push(pfx);
770
+ bounds.push({ lo: _ipLow(pfx, width), hi: _ipHigh(pfx, width) });
771
+ out.push(b.bitString(pfx.subarray(1), pfx[0]));
772
+ }
773
+ }
774
+ // The form choice is a SHALL, so the wrong one would give one DER two CBOR encodings. A family
775
+ // mixing the two arms is not a choice either admits; a bytes-form family whose every sequence
776
+ // fits 8 octets was required to use the int form.
777
+ if (sawBytes && sawInt) throw _err("c509/bad-extensions", "an IPAddressFamily must use one address form throughout (sec. 3.3)");
778
+ if (sawBytes && !seqs.some(function (s) { return s.length > 8; })) {
779
+ throw _err("c509/bad-extensions", "an IPAddressFamily whose addresses all fit 8 octets must use the integer form (sec. 3.3)");
780
+ }
781
+ // The SAME RFC 3779 sec. 2.2.3.6 canonical form the encode side requires, enforced here too.
782
+ // draft sec. 3.3 says "The limitations specified in [RFC3779] apply here as well", so a compact
783
+ // list that is unsorted, overlapping, or unmerged is malformed C509 -- and reconstructing it
784
+ // would emit a certificate an independent validator refuses, from CBOR this codec had accepted.
785
+ // Both directions must hold or the pair is not a bijection.
786
+ for (var r = 0; r < bounds.length; r++) {
787
+ if (_ipOctCmp(bounds[r].lo, bounds[r].hi) > 0) throw _err("c509/bad-extensions", "an IPAddress range must not end below its start (RFC 3779 sec. 2.2.3.9)");
788
+ }
789
+ if (!_ipRangesCanonical(bounds)) {
790
+ throw _err("c509/bad-extensions", "an IPAddressFamily must be sorted, non-overlapping and maximally merged (RFC 3779 sec. 2.2.3.6)");
791
+ }
792
+ return out;
793
+ }
794
+
795
+ // DER IPAddressChoice (a SEQUENCE of BIT STRING / SEQUENCE-of-two-BIT-STRING) -> the compact CBOR
796
+ // array. The form is chosen per family and is a SHALL: the bytes form applies to the WHOLE family
797
+ // as soon as any one sequence exceeds 8 octets, otherwise every member takes the delta-coded int
798
+ // form. Returns null on any shape the compact form cannot carry exactly.
799
+ function _ipChoiceFromDer(ch, afi) {
800
+ if (!_isUniversal(ch, asn1.TAGS.SEQUENCE) || !ch.children || ch.children.length === 0) return null;
801
+ // Collect each address as its `unusedBits || value` sequence, keeping the range grouping.
802
+ var groups = [], flat = [];
803
+ for (var i = 0; i < ch.children.length; i++) {
804
+ var el = ch.children[i], pair;
805
+ if (_isUniversal(el, asn1.TAGS.BIT_STRING)) {
806
+ var bs = asn1.read.bitString(el);
807
+ pair = [Buffer.concat([Buffer.from([bs.unusedBits]), bs.bytes])];
808
+ } else if (_isUniversal(el, asn1.TAGS.SEQUENCE) && el.children && el.children.length === 2 &&
809
+ _isUniversal(el.children[0], asn1.TAGS.BIT_STRING) && _isUniversal(el.children[1], asn1.TAGS.BIT_STRING)) {
810
+ var lo = asn1.read.bitString(el.children[0]), hi = asn1.read.bitString(el.children[1]);
811
+ pair = [Buffer.concat([Buffer.from([lo.unusedBits]), lo.bytes]),
812
+ Buffer.concat([Buffer.from([hi.unusedBits]), hi.bytes])];
813
+ } else return null;
814
+ groups.push(pair);
815
+ for (var k = 0; k < pair.length; k++) flat.push(pair[k]);
816
+ }
817
+ // The list must be in RFC 3779 sec. 2.2.3.6 canonical form -- sorted, non-overlapping, and with
818
+ // every contiguous pair already merged. A list that is not is not the one canonical encoding of
819
+ // its address set, so it is NOT re-encoded into a conforming one: this returns null and the
820
+ // extension rides the byte-string form, keeping its exact bytes and leaving the defect visible to
821
+ // a validator. Checking overlap and adjacency needs the family's address width, so a family whose
822
+ // AFI this codec does not know declines too, rather than compacting what it cannot verify.
823
+ // Defense-in-depth, and deliberately explicit: without the width the bound arithmetic below would
824
+ // fault and the caller's catch would produce the same fallback, so this line changes no observable
825
+ // verdict and no vector can isolate it. It stays because a fallback that depends on an exception
826
+ // being raised somewhere downstream is one refactor away from becoming an accepted value.
827
+ var width = _IP_WIDTH[afi];
828
+ if (!width) return null;
829
+ var bounds = [];
830
+ for (var g = 0; g < groups.length; g++) {
831
+ var bg = groups[g];
832
+ if (bg[0].length - 1 > width || bg[bg.length - 1].length - 1 > width) return null; // longer than the family's addresses
833
+ var blo = _ipLow(bg[0], width), bhi = _ipHigh(bg[bg.length - 1], width);
834
+ // The mirror of the decode-side rule: a range that is exactly a prefix had to be written as one.
835
+ // No vector can isolate this line today, because the encode path's round-trip self-verify
836
+ // re-parses what it produced and the decode rule rejects it there, yielding the same fallback.
837
+ // It stays explicit because that makes encode's correctness depend on decode continuing to
838
+ // throw -- soften the decode rule and this path would silently start compacting again.
839
+ if (bg.length === 2 && _ipRangeIsPrefix(blo, bhi)) return null;
840
+ bounds.push({ lo: blo, hi: bhi });
841
+ }
842
+ // A range's own endpoints must ascend too, which a single-entry list has no chance to violate.
843
+ for (var r = 0; r < bounds.length; r++) {
844
+ if (_ipOctCmp(bounds[r].lo, bounds[r].hi) > 0) return null;
845
+ }
846
+ if (!_ipRangesCanonical(bounds)) return null;
847
+ var useBytes = flat.some(function (s) { return s.length > 8; });
848
+ var out = [];
849
+ if (useBytes) {
850
+ groups.forEach(function (p) {
851
+ out.push(p.length === 1 ? cbor.build.byteString(p[0])
852
+ : cbor.build.array([cbor.build.byteString(p[0]), cbor.build.byteString(p[1])]));
853
+ });
854
+ return cbor.build.array(out);
855
+ }
856
+ var prev = null;
857
+ function delta(seq) {
858
+ var n = _ipSeqToInt(seq);
859
+ if (n === null) return null;
860
+ var d = prev === null ? n : n - prev;
861
+ prev = n;
862
+ return cbor.build.int(d);
863
+ }
864
+ for (var gi = 0; gi < groups.length; gi++) {
865
+ var grp = groups[gi];
866
+ if (grp.length === 1) {
867
+ var one = delta(grp[0]);
868
+ if (!one) return null;
869
+ out.push(one);
870
+ } else {
871
+ var dlo = delta(grp[0]);
872
+ var dhi = dlo ? delta(grp[1]) : null;
873
+ if (!dlo || !dhi) return null;
874
+ out.push(cbor.build.array([dlo, dhi]));
875
+ }
876
+ }
877
+ return cbor.build.array(out);
878
+ }
879
+
880
+ // One ASId in the delta chain -> its absolute value, bounded to the RFC 3779 32-bit ASId domain.
881
+ // Safe to narrow here (2^32-1 < 2^53), unlike the IP domain.
882
+ function _asDelta(node, prev) {
883
+ var d = _cborIntVal(node, "an ASIdentifier");
884
+ var abs = prev === null ? d : prev + d;
885
+ // Bound only -- the narrowed Number the guard returns is deliberately discarded, because the
886
+ // chain's next step adds a BigInt delta to this value and mixing the two throws.
887
+ guard.range.int(abs, 0n, 4294967295n, _err, "c509/bad-extensions", "an ASIdentifier");
888
+ return abs;
889
+ }
890
+
558
891
  // GeneralSubtrees = [ + GeneralName ] (the flat int/value array) <-> the concatenated GeneralSubtree
559
892
  // SEQUENCEs (RFC 5280 sec. 4.2.1.10: SEQUENCE { base, minimum [0] DEFAULT 0, maximum [1] OPTIONAL }); the
560
893
  // C509 profile omits minimum/maximum, so each GeneralSubtree is base-only.
@@ -799,6 +1132,82 @@ function _extValueToDer(name, node, isNative) {
799
1132
  if (node.majorType === 3) return b.sequence([b.sequence([b.explicit(0, b.contextConstructed(0, b.contextPrimitive(6, _ia5Bytes(node, 6))))])]);
800
1133
  if (node.majorType !== 4 || !node.children || node.children.length < 1) throw _err("c509/bad-extensions", "a " + name + " value must be a CBOR array of DistributionPoints or a bare URI text (sec. 3.3)");
801
1134
  return b.sequence(node.children.map(function (dp) { return _dpToDer(dp, isNative); }));
1135
+ // IPAddrBlocks (and its RFC 8360 v2 twin): a FLAT array of (AFI, SAFI, choice) triples --
1136
+ // IPAddressFamily is a parenthesized CDDL group, so it splices rather than nesting.
1137
+ case "ipAddrBlocks":
1138
+ case "ipAddrBlocksV2": {
1139
+ if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "an IPAddrBlocks value must be a CBOR array");
1140
+ var ipKids = node.children;
1141
+ if (ipKids.length === 0 || ipKids.length % 3 !== 0) throw _err("c509/bad-extensions", "an IPAddrBlocks array must be non-empty (AFI, SAFI, addresses) triples (sec. 3.3)");
1142
+ var families = [], prevFam = null;
1143
+ for (var fi = 0; fi + 2 < ipKids.length; fi += 3) {
1144
+ var afi = _cborUint(ipKids[fi], "an IPAddrBlocks AFI");
1145
+ if (afi > 0xffffn) throw _err("c509/bad-extensions", "an IPAddrBlocks AFI must fit two octets (RFC 3779 sec. 2.2.3.3)");
1146
+ var safiNode = ipKids[fi + 1], famBytes = [Number(afi >> 8n) & 0xff, Number(afi & 0xffn)];
1147
+ if (!_isCborNull(safiNode)) {
1148
+ var safi = _cborUint(safiNode, "an IPAddrBlocks SAFI");
1149
+ if (safi > 0xffn) throw _err("c509/bad-extensions", "an IPAddrBlocks SAFI must fit one octet (RFC 3779 sec. 2.2.3.3)");
1150
+ famBytes.push(Number(safi));
1151
+ }
1152
+ // The families themselves are ordered and unique (RFC 3779 sec. 2.2.3.3), the same way the
1153
+ // addresses inside one are. Without this a value could name AFI 2 before AFI 1, or repeat a
1154
+ // family, and reconstruct a certificate an independent validator refuses.
1155
+ var famOct = Buffer.from(famBytes);
1156
+ if (prevFam !== null && _famOctCmp(prevFam, famOct) >= 0) {
1157
+ throw _err("c509/bad-extensions", "IPAddrBlocks address families must be unique and in ascending addressFamily order (RFC 3779 sec. 2.2.3.3)");
1158
+ }
1159
+ prevFam = famOct;
1160
+ var choice = ipKids[fi + 2], famFields = [b.octetString(famOct)];
1161
+ if (_isCborNull(choice)) { // null -> inherit
1162
+ famFields.push(b.nullValue());
1163
+ } else {
1164
+ if (choice.majorType !== 4 || !choice.children || choice.children.length === 0) {
1165
+ throw _err("c509/bad-extensions", "an IPAddrBlocks address choice must be null (inherit) or a non-empty CBOR array");
1166
+ }
1167
+ famFields.push(b.sequence(_ipChoiceToDer(choice.children, Number(afi))));
1168
+ }
1169
+ families.push(b.sequence(famFields));
1170
+ }
1171
+ return b.sequence(families);
1172
+ }
1173
+ // ASIdentifiers (and its v2 twin): null = inherit, else a flat array of uint / [min,max].
1174
+ // Only the asnum field is representable -- a present rdi has no compact form (sec. 3.3), so a
1175
+ // certificate carrying one rides the ~oid byte-string form and never reaches this arm.
1176
+ case "autonomousSysIds":
1177
+ case "autonomousSysIdsV2": {
1178
+ if (_isCborNull(node)) return b.sequence([b.explicit(0, b.nullValue())]); // asnum inherit
1179
+ if (node.majorType !== 4 || !node.children || node.children.length === 0) {
1180
+ throw _err("c509/bad-extensions", "an ASIdentifiers value must be null (inherit) or a non-empty CBOR array");
1181
+ }
1182
+ // The deltas are `uint` in the draft's CDDL precisely because RFC 3779 sec. 3.2.3.4 sorts AS
1183
+ // ids by increasing value: a negative delta would walk the chain backwards. That section also
1184
+ // forbids a pair overlapping and requires a contiguous series to be one range -- the same
1185
+ // three rules the encode side applies, tracked here across members through `asPrevHigh`.
1186
+ // A negative delta needs no separate test: on the first entry it drives the absolute below
1187
+ // zero and the range guard refuses it, and on any later entry it lands at or below the
1188
+ // previous high, which the canonical test already refuses. A separate uint check would be a
1189
+ // branch nothing can reach.
1190
+ var asKids = node.children, asDers = [], asPrev = null, asPrevHigh = null;
1191
+ for (var asi2 = 0; asi2 < asKids.length; asi2++) {
1192
+ var it = asKids[asi2];
1193
+ if (it.majorType === 4) { // [min, max] -> ASRange
1194
+ if (!it.children || it.children.length !== 2) throw _err("c509/bad-extensions", "an ASIdentifiers range must be exactly [min, max]");
1195
+ var amin = _asDelta(it.children[0], asPrev), amax = _asDelta(it.children[1], amin);
1196
+ if (amax <= amin) throw _err("c509/bad-extensions", "an ASIdentifiers range must be ascending (RFC 3779 sec. 3.2.3.6)");
1197
+ if (asPrevHigh !== null && amin <= asPrevHigh + 1n) throw _err("c509/bad-extensions", "ASIdentifiers must be sorted, non-overlapping and maximally merged (RFC 3779 sec. 3.2.3.4)");
1198
+ asDers.push(b.sequence([b.integer(amin), b.integer(amax)]));
1199
+ asPrev = amax;
1200
+ asPrevHigh = amax;
1201
+ } else { // uint -> ASId
1202
+ var aid = _asDelta(it, asPrev);
1203
+ if (asPrevHigh !== null && aid <= asPrevHigh + 1n) throw _err("c509/bad-extensions", "ASIdentifiers must be sorted, non-overlapping and maximally merged (RFC 3779 sec. 3.2.3.4)");
1204
+ asDers.push(b.integer(aid));
1205
+ asPrev = aid;
1206
+ asPrevHigh = aid;
1207
+ }
1208
+ }
1209
+ return b.sequence([b.explicit(0, b.sequence(asDers))]);
1210
+ }
802
1211
  case "certificatePolicies": { // [ pid, [ *(qid, qtext) ], ... ] -> SEQUENCE OF PolicyInformation
803
1212
  if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "a certificatePolicies value must be a CBOR array");
804
1213
  var cpKids = node.children;
@@ -960,6 +1369,73 @@ function _extValueFromDer(name, der) {
960
1369
  if (dpResults.length === 1 && dpResults[0].oneUri != null && dpResults[0].noReasons && dpResults[0].noIssuer) return cbor.build.textString(dpResults[0].oneUri);
961
1370
  return cbor.build.array(dpResults.map(function (r) { return r.triple; }));
962
1371
  }
1372
+ // IPAddrBlocks -> the flat (AFI, SAFI, choice) array. Any shape the compact form cannot
1373
+ // represent exactly returns null, so the extension rides the ~oid byte-string form intact.
1374
+ case "ipAddrBlocks":
1375
+ case "ipAddrBlocksV2": {
1376
+ if (!_isUniversal(node, asn1.TAGS.SEQUENCE) || !node.children || node.children.length === 0) return null;
1377
+ var ipOut = [], prevFamOct = null;
1378
+ for (var ifi = 0; ifi < node.children.length; ifi++) {
1379
+ var fam = node.children[ifi];
1380
+ if (!_isUniversal(fam, asn1.TAGS.SEQUENCE) || !fam.children || fam.children.length !== 2) return null;
1381
+ var famOct = asn1.read.octetString(fam.children[0]);
1382
+ if (famOct.length !== 2 && famOct.length !== 3) return null; // OCTET STRING (SIZE (2..3))
1383
+ // Families ordered and unique (RFC 3779 sec. 2.2.3.3) -- the mirror of the decode side.
1384
+ if (prevFamOct !== null && _famOctCmp(prevFamOct, famOct) >= 0) return null;
1385
+ prevFamOct = famOct;
1386
+ var afiVal = (famOct[0] << 8) | famOct[1];
1387
+ ipOut.push(cbor.build.uint(BigInt(afiVal)));
1388
+ ipOut.push(famOct.length === 3 ? cbor.build.uint(BigInt(famOct[2])) : cbor.build.nullValue());
1389
+ var ch = fam.children[1];
1390
+ if (_isUniversal(ch, asn1.TAGS.NULL)) { ipOut.push(cbor.build.nullValue()); continue; } // inherit
1391
+ var chOut = _ipChoiceFromDer(ch, afiVal);
1392
+ if (!chOut) return null;
1393
+ ipOut.push(chOut);
1394
+ }
1395
+ return cbor.build.array(ipOut);
1396
+ }
1397
+ // ASIdentifiers -> null (inherit) or the flat delta array. Only asnum is representable: a
1398
+ // present rdi has no compact form (sec. 3.3), so such a certificate returns null here.
1399
+ case "autonomousSysIds":
1400
+ case "autonomousSysIdsV2": {
1401
+ if (!_isUniversal(node, asn1.TAGS.SEQUENCE) || !node.children || node.children.length !== 1) return null;
1402
+ var asnum = node.children[0];
1403
+ if (asnum.tagClass !== "context" || asnum.tagNumber !== 0 || !asnum.children || asnum.children.length !== 1) return null;
1404
+ var inner = asnum.children[0];
1405
+ if (_isUniversal(inner, asn1.TAGS.NULL)) return cbor.build.nullValue(); // asnum inherit
1406
+ if (!_isUniversal(inner, asn1.TAGS.SEQUENCE) || !inner.children || inner.children.length === 0) return null;
1407
+ // RFC 3779 sec. 3.2.3.4 fixes the canonical form the same way sec. 2.2.3.6 does for addresses:
1408
+ // sorted by increasing value, no pair overlapping, and any contiguous series already merged
1409
+ // into one range. `asPrevHigh` carries the previous entry's upper bound so all three hold
1410
+ // ACROSS members -- checking only within a range would let a descending or adjacent pair
1411
+ // through. A list that is not canonical is left uncompacted with its bytes intact.
1412
+ var asOut = [], asPrevOut = null, asPrevHigh = null;
1413
+ for (var aoi = 0; aoi < inner.children.length; aoi++) {
1414
+ var el = inner.children[aoi], elLo, elHi;
1415
+ if (_isUniversal(el, asn1.TAGS.INTEGER)) {
1416
+ elLo = asn1.read.integer(el);
1417
+ elHi = elLo;
1418
+ if (elLo < 0n || elLo > 4294967295n) return null;
1419
+ } else if (_isUniversal(el, asn1.TAGS.SEQUENCE) && el.children && el.children.length === 2) {
1420
+ elLo = asn1.read.integer(el.children[0]);
1421
+ elHi = asn1.read.integer(el.children[1]);
1422
+ if (elLo < 0n || elHi > 4294967295n || elHi <= elLo) return null;
1423
+ } else return null;
1424
+ if (asPrevHigh !== null && elLo <= asPrevHigh + 1n) return null; // descending, overlapping, or contiguous
1425
+ if (el.tagNumber === asn1.TAGS.INTEGER) {
1426
+ asOut.push(cbor.build.int(asPrevOut === null ? elLo : elLo - asPrevOut));
1427
+ asPrevOut = elLo;
1428
+ } else {
1429
+ asOut.push(cbor.build.array([
1430
+ cbor.build.int(asPrevOut === null ? elLo : elLo - asPrevOut),
1431
+ cbor.build.int(elHi - elLo),
1432
+ ]));
1433
+ asPrevOut = elHi;
1434
+ }
1435
+ asPrevHigh = elHi;
1436
+ }
1437
+ return cbor.build.array(asOut);
1438
+ }
963
1439
  case "certificatePolicies": { // SEQUENCE OF PolicyInformation -> [ pid, [ *(qid, qtext) ], ... ]
964
1440
  if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1) return null;
965
1441
  var cpOut = [];
@@ -1879,6 +2355,14 @@ function _compressEcPoint(point, coordLen) {
1879
2355
  _derToType3 = function (input, opts) {
1880
2356
  var c;
1881
2357
  try { c = x509.parse(input); } catch (e) { throw _err("c509/bad-input", "the input is not a valid X.509 certificate", e); }
2358
+ // Both C509 certificate types are defined over X.509 v3 (draft-ietf-cose-cbor-encoded-cert
2359
+ // sec. 1), and the encoding carries no version field -- reconstruction always emits v3. So a v1
2360
+ // or v2 certificate is outside the format, not a codec limitation, and saying so here keeps it
2361
+ // from falling through to the byte-compare below, whose "does not reconstruct byte-for-byte"
2362
+ // reads as a defect in this encoder rather than a certificate the format does not cover.
2363
+ // (A v3 certificate with the extensions field OMITTED is fully supported: sec. 3.1.10 encodes
2364
+ // an omitted 'extensions' field as an empty CBOR array.)
2365
+ if (c.version !== 3) throw _err("c509/non-invertible", "C509 covers X.509 v3 certificates; got v" + c.version);
1882
2366
  if (!/^ecdsa/i.test(c.signatureAlgorithm.name || "")) throw _err("c509/non-invertible", "type-3 C509 encoding covers only ECDSA-signed certificates; got " + (c.signatureAlgorithm.name || "an unregistered algorithm"));
1883
2367
  if (c.subjectPublicKeyInfo.algorithm.name !== "ecPublicKey") throw _err("c509/non-invertible", "type-3 C509 encoding covers only EC (ecPublicKey) certificates in v1; got " + (c.subjectPublicKeyInfo.algorithm.name || "an unregistered algorithm"));
1884
2368
  var curveOid = asn1.read.oid(asn1.decode(c.subjectPublicKeyInfo.algorithm.parameters));