@blamejs/pki 0.5.31 → 0.5.32

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/README.md +2 -2
  3. package/lib/acme.js +347 -90
  4. package/lib/asn1-der.js +93 -33
  5. package/lib/cmc-build.js +19 -8
  6. package/lib/cmc-verify.js +4 -4
  7. package/lib/cmp-build.js +36 -6
  8. package/lib/cmp-session.js +5 -5
  9. package/lib/cmp-verify.js +86 -16
  10. package/lib/cms-compress.js +2 -2
  11. package/lib/cms-decrypt.js +3 -3
  12. package/lib/cms-encrypt.js +1 -1
  13. package/lib/cms-sign.js +7 -7
  14. package/lib/cms-verify.js +9 -4
  15. package/lib/crl-sign.js +4 -4
  16. package/lib/crmf-sign.js +2 -1
  17. package/lib/csr-sign.js +1 -4
  18. package/lib/ct.js +8 -4
  19. package/lib/est.js +234 -34
  20. package/lib/framework-error.js +20 -6
  21. package/lib/guard-bytes.js +24 -3
  22. package/lib/http-digest.js +69 -16
  23. package/lib/http-retry-after.js +68 -24
  24. package/lib/http-transport.js +60 -6
  25. package/lib/inspect.js +13 -2
  26. package/lib/key.js +2 -2
  27. package/lib/lint.js +1 -1
  28. package/lib/mime.js +27 -4
  29. package/lib/ocsp.js +7 -7
  30. package/lib/oid.js +16 -0
  31. package/lib/path-validate.js +76 -18
  32. package/lib/pbes2.js +2 -2
  33. package/lib/pkcs12-build.js +2 -2
  34. package/lib/pki-build.js +6 -6
  35. package/lib/rfc3339.js +75 -17
  36. package/lib/schema-c509.js +36 -9
  37. package/lib/schema-cmc.js +3 -3
  38. package/lib/schema-pkix.js +174 -25
  39. package/lib/schema-tsp.js +10 -1
  40. package/lib/sign-scheme.js +1 -1
  41. package/lib/sigstore.js +52 -9
  42. package/lib/smime.js +86 -6
  43. package/lib/trust.js +114 -14
  44. package/lib/tsp-sign.js +39 -27
  45. package/lib/validator-tpm.js +2 -2
  46. package/lib/webauthn-mds.js +24 -6
  47. package/lib/webauthn.js +3 -3
  48. package/lib/webcrypto.js +1 -1
  49. package/lib/x509-sign.js +1 -1
  50. package/package.json +1 -1
  51. package/sbom.cdx.json +6 -6
package/lib/asn1-der.js CHANGED
@@ -38,6 +38,7 @@ var intrinsic = require("./guard-intrinsic");
38
38
  // The list test at each builder door decides whether a caller's argument is refused. Read at the
39
39
  // moment it runs, a replacement answering true admits a non-list and the door it guards is gone.
40
40
  var _isArray = intrinsic.isArray;
41
+ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
41
42
 
42
43
  var Asn1Error = frameworkError.Asn1Error;
43
44
  var OidError = frameworkError.OidError;
@@ -626,7 +627,7 @@ function _decodeVisible(buf) {
626
627
  // PrintableString is restricted to A-Z a-z 0-9 space and ' ( ) + , - . / : = ?
627
628
  function _decodePrintable(buf) {
628
629
  var s = buf.toString("latin1");
629
- if (!PRINTABLE_RE.test(s)) throw new Asn1Error("asn1/bad-printable-string", "PrintableString has characters outside the restricted set");
630
+ if (!isPrintableString(s)) throw new Asn1Error("asn1/bad-printable-string", "PrintableString has characters outside the restricted set");
630
631
  return s;
631
632
  }
632
633
 
@@ -644,7 +645,7 @@ function readNumericString(node) {
644
645
  _expectUniversal(node, TAGS.NUMERIC_STRING, "readNumericString");
645
646
  _expectPrimitive(node, "readNumericString");
646
647
  var s = node.content.toString("latin1");
647
- if (!NUMERIC_RE.test(s)) throw new Asn1Error("asn1/bad-numeric-string", "NumericString has characters outside the digits-and-space set");
648
+ if (!_isNumericString(s)) throw new Asn1Error("asn1/bad-numeric-string", "NumericString has characters outside the digits-and-space set");
648
649
  return s;
649
650
  }
650
651
 
@@ -702,12 +703,54 @@ function _decodeUtf32be(buf) {
702
703
  return out;
703
704
  }
704
705
 
705
- var UTC_RE = /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z$/;
706
- var GEN_RE = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z$/;
707
- // The X.690 sec. 11.7 DER fractional-seconds GeneralizedTime profile: seconds present,
708
- // a `.` decimal separator (never `,`), a non-empty fraction, Z-terminated. The
709
- // trailing-zeros rule (sec. 11.7.3) is enforced separately. Used by RFC 3161 genTime.
710
- var GEN_FRAC_RE = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d+)Z$/;
706
+ // Exactly `count` ASCII digits at `start`, as an integer, or null on any non-digit or short read.
707
+ // Character-scanned so no regex match protocol -- a live, replaceable `exec` -- stands between the
708
+ // codec and the time field it slices out.
709
+ function _digitsToInt(s, start, count) {
710
+ if (start + count > s.length) return null;
711
+ var v = 0;
712
+ for (var i = 0; i < count; i++) {
713
+ var c = _charCodeAt(s, start + i);
714
+ if (c < 48 || c > 57) return null;
715
+ v = v * 10 + (c - 48);
716
+ }
717
+ return v;
718
+ }
719
+
720
+ // The fixed DER time layout, scanned into its components, or null on any format violation:
721
+ // yearLen digits (2 for UTCTime YY, 4 for GeneralizedTime YYYY), then MM DD HH MM SS, then --
722
+ // when allowFrac -- the X.690 sec. 11.7 fractional-seconds profile (a "." decimal separator, never
723
+ // ",", and a non-empty fraction), then a terminating "Z" as the final character with nothing after.
724
+ // `ms` is the first three fraction digits right-zero-padded (matching (frac + "000").slice(0,3));
725
+ // `fracLastIsZero` carries the sec. 11.7.3 trailing-zero check the caller enforces.
726
+ function _scanTimeFields(s, yearLen, allowFrac) {
727
+ var pos = 0;
728
+ var year = _digitsToInt(s, pos, yearLen); if (year === null) return null; pos += yearLen;
729
+ var month = _digitsToInt(s, pos, 2); if (month === null) return null; pos += 2;
730
+ var day = _digitsToInt(s, pos, 2); if (day === null) return null; pos += 2;
731
+ var hour = _digitsToInt(s, pos, 2); if (hour === null) return null; pos += 2;
732
+ var min = _digitsToInt(s, pos, 2); if (min === null) return null; pos += 2;
733
+ var sec = _digitsToInt(s, pos, 2); if (sec === null) return null; pos += 2;
734
+ var hasFrac = false, ms = 0, fracLastIsZero = false;
735
+ if (allowFrac) {
736
+ if (_charCodeAt(s, pos) !== 46) return null; // "."
737
+ pos += 1;
738
+ var f = [0, 0, 0], fi = 0;
739
+ while (pos < s.length) {
740
+ var c = _charCodeAt(s, pos);
741
+ if (c < 48 || c > 57) break;
742
+ if (fi < 3) f[fi] = c - 48;
743
+ fi += 1; pos += 1;
744
+ }
745
+ if (fi === 0) return null; // a non-empty fraction is required
746
+ hasFrac = true;
747
+ fracLastIsZero = _charCodeAt(s, pos - 1) === 48;
748
+ ms = f[0] * 100 + f[1] * 10 + f[2];
749
+ }
750
+ if (pos !== s.length - 1 || _charCodeAt(s, pos) !== 90) return null; // trailing "Z", nothing after
751
+ return { year: year, month: month, day: day, hour: hour, min: min, sec: sec,
752
+ hasFrac: hasFrac, ms: ms, fracLastIsZero: fracLastIsZero };
753
+ }
711
754
 
712
755
  // readTime(node, opts?) -- opts.allowFractional enables the RFC 3161 / X.690 sec. 11.7
713
756
  // fractional-seconds GeneralizedTime profile. Fractional is OFF by default because
@@ -718,39 +761,37 @@ function readTime(node, opts) {
718
761
  if (node.tagClass !== "universal") throw new Asn1Error("asn1/expected-time", "readTime: not a universal time type");
719
762
  _expectPrimitive(node, "readTime");
720
763
  var s = node.content.toString("latin1");
721
- var m, year;
764
+ var t, year;
722
765
  if (node.tagNumber === TAGS.UTC_TIME) {
723
- m = UTC_RE.exec(s);
724
- if (!m) throw new Asn1Error("asn1/bad-utctime", "UTCTime must be YYMMDDHHMMSSZ, got " + JSON.stringify(s));
725
- year = parseInt(m[1], 10);
766
+ t = _scanTimeFields(s, 2, false);
767
+ if (!t) throw new Asn1Error("asn1/bad-utctime", "UTCTime must be YYMMDDHHMMSSZ, got " + JSON.stringify(s));
768
+ year = t.year;
726
769
  // RFC 5280 sec. 4.1.2.5.1 -- YY < 50 => 20YY, else 19YY.
727
770
  year += (year < 50) ? 2000 : 1900;
728
771
  } else if (node.tagNumber === TAGS.GENERALIZED_TIME) {
729
- m = GEN_RE.exec(s);
730
- if (!m) {
772
+ t = _scanTimeFields(s, 4, false);
773
+ if (!t) {
731
774
  if (!(opts && opts.allowFractional)) throw new Asn1Error("asn1/bad-generalizedtime", "GeneralizedTime must be YYYYMMDDHHMMSSZ, got " + JSON.stringify(s));
732
- m = GEN_FRAC_RE.exec(s);
733
- if (!m) throw new Asn1Error("asn1/bad-generalizedtime", "GeneralizedTime must be YYYYMMDDHHMMSS[.fraction]Z, got " + JSON.stringify(s));
775
+ t = _scanTimeFields(s, 4, true);
776
+ if (!t) throw new Asn1Error("asn1/bad-generalizedtime", "GeneralizedTime must be YYYYMMDDHHMMSS[.fraction]Z, got " + JSON.stringify(s));
734
777
  // X.690 sec. 11.7.3 -- the fractional part MUST NOT end in a zero (and is non-empty
735
- // by the regex). ".5Z" is valid; ".50Z" and ".500Z" are non-canonical. X.690
778
+ // by the scan). ".5Z" is valid; ".50Z" and ".500Z" are non-canonical. X.690
736
779
  // sec. 11.7 places NO cap on the fraction length (RFC 3161 sec. 2.4.2 gives the 5-digit
737
780
  // example 19990609001326.34352Z), so a fraction of any length is accepted; the
738
781
  // returned Date is millisecond-precision, and a caller needing the exact fraction
739
782
  // reads it from node.content (the raw bytes, surfaced losslessly like the OID /
740
783
  // serial paths).
741
- if (m[7].charAt(m[7].length - 1) === "0") throw new Asn1Error("asn1/bad-generalizedtime", "GeneralizedTime fraction must not have trailing zeros, got " + JSON.stringify(s));
784
+ if (t.fracLastIsZero) throw new Asn1Error("asn1/bad-generalizedtime", "GeneralizedTime fraction must not have trailing zeros, got " + JSON.stringify(s));
742
785
  }
743
- year = parseInt(m[1], 10);
786
+ year = t.year;
744
787
  } else {
745
788
  throw new Asn1Error("asn1/expected-time", "readTime: tag " + node.tagNumber + " is not a time type");
746
789
  }
747
- // The month is capture group 2 in UTC_RE, GEN_RE, and GEN_FRAC_RE alike
748
- // (the groups diverge only at 1, the year, and 7, the fraction).
749
- var month = parseInt(m[2], 10);
750
- var day = parseInt(m[3], 10);
751
- var hour = parseInt(m[4], 10);
752
- var min = parseInt(m[5], 10);
753
- var sec = parseInt(m[6], 10);
790
+ var month = t.month;
791
+ var day = t.day;
792
+ var hour = t.hour;
793
+ var min = t.min;
794
+ var sec = t.sec;
754
795
  // Build from the LITERAL year via setUTCFullYear: the Date.UTC() / new Date()
755
796
  // constructors remap a year in 0..99 to 1900..1999, which would corrupt a
756
797
  // GeneralizedTime year below 100 (0099 -> 1999). setUTCFullYear(year, ...)
@@ -758,11 +799,11 @@ function readTime(node, opts) {
758
799
  // below 100 and proleptic leap years are handled correctly.
759
800
  // A fractional GeneralizedTime carries sub-second precision; surface it on the
760
801
  // Date at millisecond resolution (the first three fraction digits, zero-padded).
761
- var ms = (node.tagNumber === TAGS.GENERALIZED_TIME && m[7]) ? parseInt((m[7] + "000").slice(0, 3), 10) : 0;
802
+ var ms = (node.tagNumber === TAGS.GENERALIZED_TIME && t.hasFrac) ? t.ms : 0;
762
803
  var d = new Date(0);
763
804
  d.setUTCFullYear(year, month - 1, day);
764
805
  d.setUTCHours(hour, min, sec, ms);
765
- // Coverage residual -- regex-validated fields are finite integers and Date rolls over (never NaN); out-of-range is caught by the component round-trip check below.
806
+ // Coverage residual -- scan-validated fields are finite integers and Date rolls over (never NaN); out-of-range is caught by the component round-trip check below.
766
807
  if (isNaN(guard.time.instantOf(d))) throw new Asn1Error("asn1/bad-time", "unparseable time " + JSON.stringify(s));
767
808
  // Date/setUTC* silently roll over out-of-range fields (Feb 30 -> Mar 2,
768
809
  // month 13 -> next Jan, hour 25 -> +1 day). Reject unless every component
@@ -895,8 +936,26 @@ function encodeOidContent(dotted) {
895
936
  return Buffer.from(out);
896
937
  }
897
938
 
898
- var PRINTABLE_RE = /^[A-Za-z0-9 '()+,\-./:=?]*$/;
899
- var NUMERIC_RE = /^[0-9 ]*$/;
939
+ // PrintableString (X.680): A-Z a-z 0-9 space and ' ( ) + , - . / : = ? -- checked by character scan (no
940
+ // regex). NumericString: digits and space only.
941
+ function _isPrintableChar(c) {
942
+ if ((c >= 65 && c <= 90) || (c >= 97 && c <= 122) || (c >= 48 && c <= 57)) return true; // A-Z a-z 0-9
943
+ switch (c) {
944
+ case 32: case 39: case 40: case 41: case 43: case 44: case 45: case 46: case 47: // space ' ( ) + , - . /
945
+ case 58: case 61: case 63: // : = ?
946
+ return true;
947
+ default: return false;
948
+ }
949
+ }
950
+ function isPrintableString(s) {
951
+ if (typeof s !== "string") return false;
952
+ for (var i = 0; i < s.length; i += 1) if (!_isPrintableChar(_charCodeAt(s, i))) return false;
953
+ return true;
954
+ }
955
+ function _isNumericString(s) {
956
+ for (var i = 0, c; i < s.length; i += 1) { c = _charCodeAt(s, i); if (!((c >= 48 && c <= 57) || c === 32)) return false; }
957
+ return true;
958
+ }
900
959
 
901
960
  function _fmtTwo(n) { return (n < 10 ? "0" : "") + n; }
902
961
 
@@ -989,13 +1048,13 @@ var build = {
989
1048
  // would silently pass a non-IA5 character through as a different one.
990
1049
  s = String(s);
991
1050
  for (var i = 0; i < s.length; i++) {
992
- if (s.charCodeAt(i) > 0x7F) throw new Asn1Error("asn1/bad-ia5-string", "IA5String requires 7-bit ASCII");
1051
+ if (_charCodeAt(s, i) > 0x7F) throw new Asn1Error("asn1/bad-ia5-string", "IA5String requires 7-bit ASCII");
993
1052
  }
994
1053
  return _universal(TAGS.IA5_STRING, false, Buffer.from(s, "latin1"));
995
1054
  },
996
1055
  printable: function (s) {
997
1056
  s = String(s);
998
- if (!PRINTABLE_RE.test(s)) throw new Asn1Error("asn1/bad-printable-string", "value has characters outside the PrintableString set");
1057
+ if (!isPrintableString(s)) throw new Asn1Error("asn1/bad-printable-string", "value has characters outside the PrintableString set");
999
1058
  return _universal(TAGS.PRINTABLE_STRING, false, Buffer.from(s, "latin1"));
1000
1059
  },
1001
1060
  bmpString: function (s) {
@@ -1005,7 +1064,7 @@ var build = {
1005
1064
  s = String(s);
1006
1065
  var out = Buffer.alloc(s.length * 2);
1007
1066
  for (var i = 0; i < s.length; i++) {
1008
- var u = s.charCodeAt(i);
1067
+ var u = _charCodeAt(s, i);
1009
1068
  if (u >= 0xD800 && u <= 0xDFFF) throw new Asn1Error("asn1/bad-bmp-string", "BMPString cannot encode a surrogate code point (non-BMP characters are unsupported)");
1010
1069
  out[i * 2] = (u >> 8) & 0xFF;
1011
1070
  out[i * 2 + 1] = u & 0xFF;
@@ -1074,6 +1133,7 @@ function _asBufferArray(arr, who) {
1074
1133
  module.exports = {
1075
1134
  TAGS: TAGS,
1076
1135
  decode: decode,
1136
+ isPrintableString: isPrintableString,
1077
1137
  // `encode` is the documented public name for the low-level TLV encoder;
1078
1138
  // `encodeTLV` remains as the descriptive alias.
1079
1139
  encode: encodeTLV,
package/lib/cmc-build.js CHANGED
@@ -369,12 +369,12 @@ function _assertNoDuplicateBinding(callerControls, spec) {
369
369
  function _asBigInt(v, what) { return guard.range.authoredInteger(v, E, "cmc/bad-input", what); }
370
370
 
371
371
  function _der(v, what) {
372
- if (Buffer.isBuffer(v) || v instanceof Uint8Array) return guard.bytes.snapshot(v, CmcError, "cmc/bad-input", what);
372
+ if (guard.bytes.isByteSource(v)) return guard.bytes.snapshotSource(v, CmcError, "cmc/bad-input", what);
373
373
  throw E("cmc/bad-input", what + " must be DER bytes");
374
374
  }
375
375
  function _oidOf(v) {
376
376
  if (typeof v !== "string") throw E("cmc/bad-input", "an OID must be a name or a dotted string");
377
- return /^\d+(\.\d+)+$/.test(v) ? v : O(v);
377
+ return oid.isDottedDecimal(v) ? v : O(v);
378
378
  }
379
379
 
380
380
  // ---- controls --------------------------------------------------------
@@ -725,13 +725,24 @@ function _copySigners(signer, owned) {
725
725
  // replace or zeroize while cms.sign reads them in a later turn. A CryptoKey is
726
726
  // an opaque handle and is passed through as-is.
727
727
  function copyValue(v, isSecret) {
728
- if (Buffer.isBuffer(v) || v instanceof Uint8Array) return _own(Buffer.from(v), isSecret);
728
+ if (!isSecret) {
729
+ // A public signer field (cert / spki / keyIdentifier DER): any BufferSource is
730
+ // snapshotted to a private Buffer so a caller cannot mutate it in a later turn;
731
+ // an ArrayBuffer or DataView is accepted, not silently turned into an empty object.
732
+ // A non-byte descriptor or scalar (a parsed certificate, a CryptoKey handle) passes
733
+ // through untouched.
734
+ if (guard.bytes.isByteSource(v)) return guard.bytes.snapshotSource(v, CmcError, "cmc/bad-input", "a signer field");
735
+ return v;
736
+ }
737
+ // The private key stays on the ownership contract: a Buffer is copied, a Uint8Array is
738
+ // copied, and every copy is collected so it can be zeroized once signing settles.
739
+ if (Buffer.isBuffer(v) || v instanceof Uint8Array) return _own(Buffer.from(v), true); // allow:byte-source-narrow -- private key bytes (Buffer borrowed / Uint8Array copied+wiped)
729
740
  if (!v || typeof v !== "object" || typeof v.type === "string") return v; // CryptoKey / scalar
730
741
  var c = {}, ck;
731
742
  for (ck in v) {
732
743
  if (!_hasOwn(v, ck)) continue;
733
744
  var cv = v[ck];
734
- c[ck] = (Buffer.isBuffer(cv) || cv instanceof Uint8Array) ? _own(Buffer.from(cv), isSecret) : cv;
745
+ c[ck] = (Buffer.isBuffer(cv) || cv instanceof Uint8Array) ? _own(Buffer.from(cv), true) : cv; // allow:byte-source-narrow -- composite key component
735
746
  }
736
747
  return c;
737
748
  }
@@ -787,15 +798,15 @@ function _assertKeyOnlySigner(signer, requests) {
787
798
  // The sec. 3.2a/3.2c binding for a single key-only signer.
788
799
  function _assertKeyOnlySignerBinding(so, requests) {
789
800
  var id = so.keyIdentifier;
790
- var idBytes = (Buffer.isBuffer(id) || id instanceof Uint8Array) ? Buffer.from(id) : null;
791
- if (!idBytes) {
801
+ if (!guard.bytes.isByteSource(id)) {
792
802
  throw E("cmc/bad-signer",
793
803
  "a key-only signer must name the Subject Key Identifier its certification request declares (RFC 5272 sec. 3.2)");
794
804
  }
795
- var signerSpki = (Buffer.isBuffer(so.spki) || so.spki instanceof Uint8Array) ? Buffer.from(so.spki) : null;
796
- if (!signerSpki) {
805
+ var idBytes = guard.bytes.snapshotSource(id, CmcError, "cmc/bad-input", "the signer's keyIdentifier");
806
+ if (!guard.bytes.isByteSource(so.spki)) {
797
807
  throw E("cmc/bad-signer", "a key-only signer must carry its spki as DER bytes (RFC 5272 sec. 3.2)");
798
808
  }
809
+ var signerSpki = guard.bytes.snapshotSource(so.spki, CmcError, "cmc/bad-input", "the signer's spki");
799
810
  var declaredSki = false, sawRequest = false;
800
811
  for (var i = 0; i < requests.length; i++) {
801
812
  var req = requests[i];
package/lib/cmc-verify.js CHANGED
@@ -126,7 +126,7 @@ function _assertBound(body, sent) {
126
126
  function _carries(v) {
127
127
  if (v == null) return false;
128
128
  if (typeof v === "string") return v.length > 0;
129
- if (Buffer.isBuffer(v) || v instanceof Uint8Array) return v.length > 0;
129
+ if (Buffer.isBuffer(v) || v instanceof Uint8Array) return v.length > 0; // allow:byte-source-narrow -- sent.senderNonce / dataReturn are already normalized to a Buffer by _snapshotSent; this is a length test on a settled value, not an input door
130
130
  return true; // an integer or other scalar identifier is its own value
131
131
  }
132
132
  var bound = {
@@ -405,7 +405,7 @@ function _snapshotIfBytes(input) {
405
405
  // BufferSource, so an ArrayBuffer or a DataView reaches the decoder too -- and
406
406
  // leaving those aliased would reopen the window for exactly the inputs that came
407
407
  // in by the wider door.
408
- if (ArrayBuffer.isView(input) || input instanceof ArrayBuffer) {
408
+ if (guard.bytes.isByteSource(input)) {
409
409
  return guard.bytes.snapshotSource(input, CmcError, "cmc/bad-input", "pki.cmc.verify");
410
410
  }
411
411
  return input;
@@ -516,8 +516,8 @@ function _assertAuthentic(body, sent, responseBytes) {
516
516
  // the signers and the certificate bag in the gap would get one message's
517
517
  // verdict reported beside another message's verified signature. There are no
518
518
  // bytes to pin, so the honest answer is that nothing was authenticated.
519
- var haveBytes = Buffer.isBuffer(responseBytes) || responseBytes instanceof Uint8Array ||
520
- typeof responseBytes === "string";
519
+ var haveBytes = typeof responseBytes === "string" ||
520
+ Buffer.isBuffer(responseBytes) || responseBytes instanceof Uint8Array; // allow:byte-source-narrow -- responseBytes is _snapshotIfBytes's return: any BufferSource is already a Buffer here, so this is Buffer-or-string by construction
521
521
  // The two accepted carriers are authenticated DIFFERENTLY, so the rule has to
522
522
  // be written for both: a SignedData by its SignerInfo signatures, an
523
523
  // AuthenticatedData by a MAC over a key this layer never holds. Checking only
package/lib/cmp-build.js CHANGED
@@ -44,6 +44,10 @@ var _hasOwn = intrinsic.hasOwn;
44
44
  // message: each decides what reaches the wire, so a replaced built-in must not get a say.
45
45
  var _isArray = intrinsic.isArray;
46
46
  var _isBuffer = intrinsic.isBuffer;
47
+ var _charAt = intrinsic.uncurry(String.prototype.charAt);
48
+ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
49
+ var _strSlice = intrinsic.uncurry(String.prototype.slice);
50
+ var _strIndexOf = intrinsic.uncurry(String.prototype.indexOf);
47
51
  var _bufferConcat = intrinsic.bufferConcat;
48
52
  var _map = intrinsic.map;
49
53
  var _keys = intrinsic.keys;
@@ -892,7 +896,7 @@ var KNOWN_TRANSFER_OPTS = { transport: 1, tls: 1, headers: 1, timeout: 1, maxRes
892
896
  // view reads as zero-length) as cmp/bad-input rather than silently POSTing an empty body in its place.
893
897
  function _cmpMessageDer(input) {
894
898
  var der;
895
- if (Buffer.isBuffer(input) || input instanceof Uint8Array) der = guard.bytes.view(input, CmpError, "cmp/bad-input", "the CMP message DER");
899
+ if (guard.bytes.isByteSource(input)) der = guard.bytes.source(input, CmpError, "cmp/bad-input", "the CMP message DER");
896
900
  else if (typeof input === "string") {
897
901
  // Normalize a PEM decode failure (no block, wrong armor label, malformed base64) to the stable
898
902
  // cmp/bad-input at this input boundary, rather than leaking the PEM layer's PemError to the caller.
@@ -991,8 +995,8 @@ function _transfer(url, message, opts) {
991
995
  // from an injected transport) is re-viewed through the byte guard, as the request path does; only a
992
996
  // genuine string body falls back to a UTF-8 encode (the width a real socket already counts) so the size
993
997
  // cap and the parser see the same bytes -- a typed array is not stringified to "48,130,..." garbage.
994
- var body = (Buffer.isBuffer(res.body) || res.body instanceof Uint8Array)
995
- ? guard.bytes.view(res.body, CmpError, "cmp/bad-response", "the CMP response body")
998
+ var body = guard.bytes.isByteSource(res.body)
999
+ ? guard.bytes.source(res.body, CmpError, "cmp/bad-response", "the CMP response body")
996
1000
  : Buffer.from(String(res.body == null ? "" : res.body), "utf8");
997
1001
  if (body.length > maxResponseBytes) throw _err("cmp/response-too-large", "the response body (" + body.length + " bytes) exceeds the " + maxResponseBytes + "-byte cap");
998
1002
  return _classifyCmpResponse(res.status, h, body, res.tls || null);
@@ -1001,9 +1005,17 @@ function _transfer(url, message, opts) {
1001
1005
 
1002
1006
  // A single safe path segment for a well-known URI: no separator, dot-segment, or reserved/space char that
1003
1007
  // would retarget the resource; percent-encoded on the way out. Fails closed (guards never guess a value).
1008
+ // /[/?#\s\\]/ : does `s` contain a path separator, query/fragment marker, whitespace, or a backslash?
1009
+ function _hasUnsafeSegChar(s) {
1010
+ for (var i = 0; i < s.length; i++) {
1011
+ var c = _charCodeAt(s, i);
1012
+ if (c === 0x2f || c === 0x3f || c === 0x23 || c === 0x5c || pkix.isJsWhitespace(c)) return true;
1013
+ }
1014
+ return false;
1015
+ }
1004
1016
  function _wellKnownSeg(v, name) {
1005
1017
  var s = String(v);
1006
- if (s === "" || s === "." || s === ".." || /[/?#\s\\]/.test(s)) throw _err("cmp/bad-url", "the CMP " + name + " must be a single safe path segment (no '/', dot-segment, or reserved char): " + JSON.stringify(s));
1018
+ if (s === "" || s === "." || s === ".." || _hasUnsafeSegChar(s)) throw _err("cmp/bad-url", "the CMP " + name + " must be a single safe path segment (no '/', dot-segment, or reserved char): " + JSON.stringify(s));
1007
1019
  // encodeURIComponent throws a raw URIError (no .code) on an unpaired UTF-16 surrogate; normalize it to the
1008
1020
  // helper's cmp/bad-url contract so a malformed-config segment never escapes as an untyped error.
1009
1021
  try { return encodeURIComponent(s); }
@@ -1012,6 +1024,24 @@ function _wellKnownSeg(v, name) {
1012
1024
 
1013
1025
  var KNOWN_WELLKNOWN_OPTS = { label: 1, operation: 1 };
1014
1026
 
1027
+ // ---- URL raw-path helpers by character scan (no regex) --------------------------------------------
1028
+ function _isAsciiLetter(ch) { return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z"); }
1029
+ // The "scheme://authority" prefix removed (matching /^[a-z]+:\/\/[^/?#]*/i: a leading letter run,
1030
+ // "://", then a run of characters other than "/", "?" or "#"), else the string unchanged.
1031
+ function _stripSchemeAuthority(s) {
1032
+ var p = 0, n = s.length;
1033
+ while (p < n && _isAsciiLetter(_charAt(s, p))) p += 1;
1034
+ if (p === 0 || _strSlice(s, p, p + 3) !== "://") return s;
1035
+ p += 3;
1036
+ while (p < n) { var ch = _charAt(s, p); if (ch === "/" || ch === "?" || ch === "#") break; p += 1; }
1037
+ return _strSlice(s, p);
1038
+ }
1039
+ // Everything before the first "?" or "#" (matching split(/[?#]/)[0]).
1040
+ function _beforeQueryOrFragment(s) {
1041
+ for (var i = 0; i < s.length; i++) { var ch = _charAt(s, i); if (ch === "?" || ch === "#") return _strSlice(s, 0, i); }
1042
+ return s;
1043
+ }
1044
+
1015
1045
  function wellKnownUrl(base, opts) {
1016
1046
  opts = opts || {};
1017
1047
  // Reject an unknown option key (a typo like { lable } / { operaton }), as build/transfer do -- a silently
@@ -1030,13 +1060,13 @@ function wellKnownUrl(base, opts) {
1030
1060
  // A backslash has no place in an http(s) authority: WHATWG rewrites it to a path separator (so
1031
1061
  // `https://ca.example\tenant` is really the path `/tenant`), but the raw-path regex below excludes only
1032
1062
  // `/?#` and would swallow it as authority, slipping a supplied path past the check. Reject it outright.
1033
- if (/\\/.test(String(base))) throw _err("cmp/bad-url", "the CMP base URL must not contain a backslash (WHATWG rewrites it to a path separator): " + JSON.stringify(String(base)));
1063
+ if (_strIndexOf(String(base), "\\") !== -1) throw _err("cmp/bad-url", "the CMP base URL must not contain a backslash (WHATWG rewrites it to a path separator): " + JSON.stringify(String(base)));
1034
1064
  // An RFC 8615 well-known URI is AUTHORITY-ROOTED (/.well-known/...), so a base carrying ANY path is
1035
1065
  // rejected fail-closed -- an operator whose CMP endpoint sits under a path passes that full operation URL
1036
1066
  // to transfer() directly. Inspect the RAW path in the source string, NOT url.pathname: WHATWG normalizes
1037
1067
  // dot-segments (`/tenant/..`, `/%2e`) to "/" before the parse, which would slip a supplied path past a
1038
1068
  // pathname check and silently target the authority-root profile instead of reporting the misconfiguration.
1039
- var rawBasePath = String(base).replace(/^[a-z]+:\/\/[^/?#]*/i, "").split(/[?#]/)[0];
1069
+ var rawBasePath = _beforeQueryOrFragment(_stripSchemeAuthority(String(base)));
1040
1070
  if (rawBasePath !== "" && rawBasePath !== "/") throw _err("cmp/bad-url", "an RFC 8615 well-known URI is authority-rooted; the CMP base URL must have no path component (pass a full operation URL to transfer instead): " + JSON.stringify(String(base)));
1041
1071
  var segs = [".well-known", "cmp"];
1042
1072
  if (opts.label != null) { segs.push("p"); segs.push(_wellKnownSeg(opts.label, "label")); }
@@ -538,12 +538,12 @@ function session(opts) {
538
538
  // return value as its contract -- using it as a predicate throws that contract away.
539
539
  _expectedSenderCert = (_engine && _engine.coerceCert) ? _engine.coerceCert(_es) : _es;
540
540
  }
541
- else if (Buffer.isBuffer(_es) || _es instanceof Uint8Array) { _expectedSenderDer = Buffer.from(_es); _expectedSenderCert = x509.parse(_expectedSenderDer); }
541
+ else if (guard.bytes.isByteSource(_es)) { _expectedSenderDer = guard.bytes.snapshotSource(_es, CmpError, "cmp/bad-input", "opts.expectedSender"); _expectedSenderCert = x509.parse(_expectedSenderDer); }
542
542
  else if (typeof _es === "string") { _expectedSenderDer = x509.pemDecode(_es); _expectedSenderCert = x509.parse(_expectedSenderDer); }
543
- else { throw _err("cmp/bad-input", "opts.expectedSender must be a certificate DER Buffer / PEM string / parsed certificate"); }
543
+ else { throw _err("cmp/bad-input", "opts.expectedSender must be a certificate DER BufferSource / PEM string / parsed certificate"); }
544
544
  } catch (e) {
545
545
  if (e.isCmpError) throw e;
546
- throw _err("cmp/bad-input", "opts.expectedSender must be the CA signer certificate (DER Buffer / PEM / parsed) so every signed response can be bound to it -- " + ((e && e.message) || e), e);
546
+ throw _err("cmp/bad-input", "opts.expectedSender must be the CA signer certificate (DER BufferSource / PEM / parsed) so every signed response can be bound to it -- " + ((e && e.message) || e), e);
547
547
  }
548
548
  }
549
549
  // Reject a DISTINCT intermediate pool exceeding the per-flavor cap (the candidate ceiling MINUS the room reserved
@@ -1095,7 +1095,7 @@ function session(opts) {
1095
1095
  // build path validates the key separately) or the CSR is unparseable (which fails at the build boundary).
1096
1096
  function _extractRequestedSpki(arm, armSpec) {
1097
1097
  if (arm === "p10cr") {
1098
- if (!Buffer.isBuffer(armSpec) && !(armSpec instanceof Uint8Array)) return null;
1098
+ if (!guard.bytes.isByteSource(armSpec)) return null;
1099
1099
  try { return csr.parse(armSpec).subjectPublicKeyInfo.bytes; }
1100
1100
  catch (_e) { /* allow:swallow-unverified an unparseable p10cr CSR fails closed at the cmp.build boundary; the key-match is simply not applied to a request that never sends */ return null; }
1101
1101
  }
@@ -1103,7 +1103,7 @@ function session(opts) {
1103
1103
  // SNAPSHOT: this is held in session state across the transport round trip and then compared
1104
1104
  // against the issued certificate's key, so an alias would let the caller rewrite the key the
1105
1105
  // response is checked against after the request went out.
1106
- if (Buffer.isBuffer(pk) || pk instanceof Uint8Array) return guard.bytes.snapshot(pk, CmpError, "cmp/bad-input", "the certTemplate publicKey");
1106
+ if (guard.bytes.isByteSource(pk)) return guard.bytes.snapshotSource(pk, CmpError, "cmp/bad-input", "the certTemplate publicKey");
1107
1107
  return null;
1108
1108
  }
1109
1109
 
package/lib/cmp-verify.js CHANGED
@@ -63,9 +63,6 @@ var _isFinite = intrinsic.isFinite;
63
63
  // comparing is no longer the one the certificate carries.
64
64
  var _strSlice = intrinsic.uncurry(String.prototype.slice);
65
65
  var _strLastIndexOf = intrinsic.uncurry(String.prototype.lastIndexOf);
66
- var _strSearch = intrinsic.uncurry(String.prototype.search);
67
- var _reTest = intrinsic.uncurry(RegExp.prototype.test);
68
- var _reExec = intrinsic.uncurry(RegExp.prototype.exec);
69
66
  var _isArray = Array.isArray;
70
67
  var asn1 = require("./asn1-der");
71
68
  var oid = require("./oid");
@@ -181,8 +178,8 @@ function _coerce(message) {
181
178
  // buffer. (A PEM string is immutable and base64-decodes into fresh bytes, so it needs no copy.)
182
179
  // The slot, not the prototype: this decides whether the message is privately copied before it is
183
180
  // parsed, and `instanceof` consults `Uint8Array[Symbol.hasInstance]` at call time.
184
- if (_isBuffer(message) || util.types.isUint8Array(message)) return cmp.parse(_bufferFrom(message));
185
- return cmp.parse(message); // a PEM CMP string (throws cmp/* on malformed input)
181
+ if (guard.bytes.isByteSource(message)) return cmp.parse(guard.bytes.snapshotSource(message, CmpError, "cmp/bad-input", "the CMP message"));
182
+ return cmp.parse(message); // a PEM CMP string (immutable; base64-decodes into fresh bytes, so it needs no copy; throws cmp/* on malformed input)
186
183
  }
187
184
 
188
185
  // Rebuild the PKIMessage DER from a parsed object's raw slices: SEQUENCE { header, body, protection [0]
@@ -200,11 +197,12 @@ function _reassemble(m) {
200
197
  return b.sequence(kids);
201
198
  }
202
199
 
203
- // A certificate DER from a Buffer/Uint8Array (detached-view-safe) or a PEM string.
200
+ // A certificate DER from any BufferSource (Buffer / typed array / DataView / ArrayBuffer,
201
+ // detached-view-safe) or a PEM string.
204
202
  function _certDer(cert, what) {
205
- if (_isBuffer(cert) || util.types.isUint8Array(cert)) return guard.bytes.view(cert, CmpError, "cmp/bad-input", what);
203
+ if (guard.bytes.isByteSource(cert)) return guard.bytes.source(cert, CmpError, "cmp/bad-input", what);
206
204
  if (typeof cert === "string") { try { return x509.pemDecode(cert); } catch (e) { throw _err("cmp/bad-input", what + " PEM could not be decoded", e); } }
207
- throw _err("cmp/bad-input", what + " must be a certificate DER Buffer/Uint8Array or PEM string");
205
+ throw _err("cmp/bad-input", what + " must be a certificate DER BufferSource or PEM string");
208
206
  }
209
207
 
210
208
  // The subjectKeyIdentifier extension value of a parsed certificate, or null when absent/undecodable.
@@ -320,7 +318,7 @@ function _mailboxSplit(s) {
320
318
  // An unquoted (Dot-string) Local-part is atext atoms joined by single dots (RFC 5321 sec. 4.1.2): every
321
319
  // character must be an atext octet or a "." with no empty atom / leading-trailing dot. A malformed atom
322
320
  // character (e.g. a space in "user name") makes the whole address malformed -> byte-exact comparison.
323
- if (_charAt(s, 0) !== "\"" && (local.length === 0 || _charAt(local, 0) === "." || _charAt(local, local.length - 1) === "." || _strIndexOf(local, "..") !== -1 || !_reTest(/^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+$/, local))) return -1;
321
+ if (_charAt(s, 0) !== "\"" && (local.length === 0 || _charAt(local, 0) === "." || _charAt(local, local.length - 1) === "." || _strIndexOf(local, "..") !== -1 || !_isEmailLocalPart(local))) return -1;
324
322
  return sep;
325
323
  }
326
324
 
@@ -343,6 +341,78 @@ function _rfc822Equal(a, b) {
343
341
  // about which characters case-fold.
344
342
  var _lowerAsciiDomain = guard.name.lowerAscii;
345
343
 
344
+ // ---- URI / email grammar predicates by character scan (no regex) ----------------------------------
345
+ // The non-alphanumeric members of the RFC 5321 atext set and the RFC 3986 URI character set (the
346
+ // A-Za-z0-9 range is checked directly), so charset membership is an indexOf rather than a live regex
347
+ // match protocol.
348
+ var _EMAIL_LOCAL_SPECIALS = "!#$%&'*+/=?^_`{|}~.-";
349
+ var _URI_SPECIALS = "._~:/?#@!$&'()*+,;=%[]-";
350
+ function _isAsciiAlnum(ch) { return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z") || (ch >= "0" && ch <= "9"); }
351
+ function _isHexDigit(ch) { return (ch >= "0" && ch <= "9") || (ch >= "A" && ch <= "F") || (ch >= "a" && ch <= "f"); }
352
+
353
+ // A non-empty RFC 5321 Dot-string atext run (matching /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+$/).
354
+ function _isEmailLocalPart(s) {
355
+ if (s.length === 0) return false;
356
+ for (var i = 0; i < s.length; i++) {
357
+ var ch = _charAt(s, i);
358
+ if (!(_isAsciiAlnum(ch) || _strIndexOf(_EMAIL_LOCAL_SPECIALS, ch) !== -1)) return false;
359
+ }
360
+ return true;
361
+ }
362
+
363
+ // Every character an RFC 3986 URI character (matching /^[A-Za-z0-9._~:/?#@!$&'()*+,;=%[\]-]*$/; empty is valid).
364
+ function _isAllUriChars(s) {
365
+ for (var i = 0; i < s.length; i++) {
366
+ var ch = _charAt(s, i);
367
+ if (!(_isAsciiAlnum(ch) || _strIndexOf(_URI_SPECIALS, ch) !== -1)) return false;
368
+ }
369
+ return true;
370
+ }
371
+
372
+ // A "%" not introducing a valid %HH pct-encoding (matching /%(?![0-9A-Fa-f]{2})/): true when malformed.
373
+ function _hasInvalidPercent(u) {
374
+ for (var i = 0; i < u.length; i++) {
375
+ if (_charAt(u, i) === "%" && !(_isHexDigit(_charAt(u, i + 1)) && _isHexDigit(_charAt(u, i + 2)))) return true;
376
+ }
377
+ return false;
378
+ }
379
+
380
+ // A scheme + rest split (matching /^([a-zA-Z][a-zA-Z0-9+.-]*):(.*)$/): a leading letter, then
381
+ // [a-zA-Z0-9+.-]*, then ":"; returns { scheme, rest } or null. The URI charset gate runs first, so `u`
382
+ // carries no CR/LF here and the [^\n] semantics of "." never apply.
383
+ function _splitScheme(u) {
384
+ if (u.length === 0) return null;
385
+ var first = _charAt(u, 0);
386
+ if (!((first >= "A" && first <= "Z") || (first >= "a" && first <= "z"))) return null;
387
+ var p = 1;
388
+ while (p < u.length) {
389
+ var ch = _charAt(u, p);
390
+ if (!(_isAsciiAlnum(ch) || ch === "+" || ch === "." || ch === "-")) break;
391
+ p += 1;
392
+ }
393
+ if (_charAt(u, p) !== ":") return null;
394
+ return { scheme: _strSlice(u, 0, p), rest: _strSlice(u, p + 1) };
395
+ }
396
+
397
+ // The index of the first "/", "?" or "#" (the authority terminator), or -1 (matching search(/[/?#]/)).
398
+ function _firstAuthorityDelim(s) {
399
+ for (var i = 0; i < s.length; i++) {
400
+ var ch = _charAt(s, i);
401
+ if (ch === "/" || ch === "?" || ch === "#") return i;
402
+ }
403
+ return -1;
404
+ }
405
+
406
+ // A ":" followed by zero-or-more digits, whole string (matching /^:[0-9]*$/).
407
+ function _isColonPort(s) {
408
+ if (s.length === 0 || _charAt(s, 0) !== ":") return false;
409
+ for (var i = 1; i < s.length; i++) {
410
+ var ch = _charAt(s, i);
411
+ if (!(ch >= "0" && ch <= "9")) return false;
412
+ }
413
+ return true;
414
+ }
415
+
346
416
  // RFC 5280 sec. 4.2.1.6 / RFC 3986 sec. 6.2.2.1 URI comparison: the scheme and the authority host are
347
417
  // case-insensitive; every other component (userinfo, port, path, query, fragment) is byte-exact. Return
348
418
  // null for anything that is not a well-formed absolute URI so the caller falls back to exact-DER (fail-closed
@@ -351,17 +421,17 @@ function _normalizeUri(u) {
351
421
  // RFC 3986 syntax: every character must be a URI character and every "%" must introduce a valid pct-encoding
352
422
  // (%HH). A URI with an out-of-charset byte or a malformed percent-escape ("%zz") in any component (path,
353
423
  // query, fragment, userinfo) is not normalized; return null so it is compared byte-exact.
354
- if (!_reTest(/^[A-Za-z0-9._~:/?#@!$&'()*+,;=%[\]-]*$/, u) || _reTest(/%(?![0-9A-Fa-f]{2})/, u)) return null;
355
- var m = _reExec(/^([a-zA-Z][a-zA-Z0-9+.-]*):(.*)$/, u); // scheme ":" rest (RFC 3986 sec. 3.1)
424
+ if (!_isAllUriChars(u) || _hasInvalidPercent(u)) return null;
425
+ var m = _splitScheme(u); // scheme ":" rest (RFC 3986 sec. 3.1)
356
426
  if (!m) return null;
357
- var scheme = _toLowerCase(m[1]);
358
- var rest = m[2];
427
+ var scheme = _toLowerCase(m.scheme);
428
+ var rest = m.rest;
359
429
  // A URI SAN identity is host-based (RFC 5280 sec. 4.2.1.6): an authority-free URI (no "//", e.g. a URN) has
360
430
  // no host to anchor the comparison, so compare it byte-exact and never case-fold its scheme; the shared
361
431
  // GeneralName parser only enforces printable IA5, not a present authority.
362
432
  if (_strSlice(rest, 0, 2) !== "//") return null;
363
433
  var body = _strSlice(rest, 2);
364
- var cut = _strSearch(body, /[/?#]/); // authority ends at the first "/", "?" or "#" (RFC 3986 sec. 3.2)
434
+ var cut = _firstAuthorityDelim(body); // authority ends at the first "/", "?" or "#" (RFC 3986 sec. 3.2)
365
435
  var split = cut < 0 ? body.length : cut;
366
436
  var authority = _strSlice(body, 0, split), tail = _strSlice(body, split); // tail: path/query/fragment -- exact
367
437
  // A raw "@" is not permitted inside userinfo (RFC 3986 sec. 3.2.1); more than one "@" is a MALFORMED
@@ -385,7 +455,7 @@ function _normalizeUri(u) {
385
455
  // The port must be numeric (RFC 3986 sec. 3.2.3 port = *DIGIT). A non-numeric port is a malformed authority:
386
456
  // return null so the caller compares the raw values exactly and never case-folds the host of an input the
387
457
  // normalizer cannot confidently parse (which would bind two byte-distinct malformed authorities).
388
- if (port !== "" && !_reTest(/^:[0-9]*$/, port)) return null;
458
+ if (port !== "" && !_isColonPort(port)) return null;
389
459
  // The authority host must be a well-formed FQDN or IP literal (RFC 5280 sec. 4.2.1.6 requires a URI SAN
390
460
  // authority to carry an FQDN/IP host): an empty, malformed ("Victim..COM"), or bad IPv6-literal host is
391
461
  // compared byte-exact and never case-folded, since the GeneralName parser only enforces printable IA5.
@@ -742,7 +812,7 @@ function _certList(v) {
742
812
  var out = [];
743
813
  for (var i = 0, n = arr.length; i < n; i++) {
744
814
  var c = arr[i];
745
- _append(out, (_isBuffer(c) || util.types.isUint8Array(c) || typeof c === "string")
815
+ _append(out, (guard.bytes.isByteSource(c) || typeof c === "string")
746
816
  ? _certDer(c, "a trust anchor / intermediate") : c);
747
817
  }
748
818
  return out;
@@ -37,7 +37,7 @@ var NULL_PARAMS_DER = Buffer.from([0x05, 0x00]);
37
37
  // (default id-data), eContent = the RFC 1950 ZLIB stream }.
38
38
  async function compress(content, opts) {
39
39
  opts = opts || {};
40
- var raw = guard.bytes.view(content, CmsError, "cms/bad-input", "content");
40
+ var raw = guard.bytes.source(content, CmsError, "cms/bad-input", "content");
41
41
  var level = opts.level;
42
42
  if (level !== undefined && (typeof level !== "number" || !isFinite(level) || Math.floor(level) !== level)) throw _err("cms/bad-input", "opts.level must be an integer");
43
43
  var ctName = opts.contentType || "data";
@@ -86,7 +86,7 @@ async function decompress(input, opts) {
86
86
  }
87
87
 
88
88
  function _toDer(input) {
89
- if (Buffer.isBuffer(input) || input instanceof Uint8Array) return guard.bytes.snapshot(input, CmsError, "cms/bad-input", "input");
89
+ if (guard.bytes.isByteSource(input)) return guard.bytes.snapshotSource(input, CmsError, "cms/bad-input", "input");
90
90
  if (typeof input === "string") { try { return schemaCms.pemDecode(input); } catch (e) { throw _err("cms/bad-input", "the CMS PEM could not be decoded", e); } }
91
91
  throw _err("cms/bad-input", "input must be a DER Buffer, Uint8Array, or PEM string");
92
92
  }