@blamejs/pki 0.6.13 → 0.6.15

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 (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +1 -1
  3. package/index.js +1 -1
  4. package/lib/acme.js +59 -815
  5. package/lib/asn1-der.js +7 -245
  6. package/lib/attrcert-sign.js +12 -260
  7. package/lib/attrcert-verify.js +1 -7
  8. package/lib/byte-reader.js +0 -8
  9. package/lib/byte-writer.js +0 -11
  10. package/lib/cbor-det.js +0 -54
  11. package/lib/cmc-build.js +15 -357
  12. package/lib/cmc-verify.js +6 -239
  13. package/lib/cmp-build.js +41 -317
  14. package/lib/cmp-session.js +73 -760
  15. package/lib/cmp-verify.js +32 -488
  16. package/lib/cms-compress.js +3 -32
  17. package/lib/cms-decrypt.js +29 -294
  18. package/lib/cms-encrypt.js +14 -234
  19. package/lib/cms-sign.js +29 -324
  20. package/lib/cms-verify.js +9 -500
  21. package/lib/composite-kem.js +2 -102
  22. package/lib/composite-sig.js +1 -77
  23. package/lib/constants.js +0 -249
  24. package/lib/crl-sign.js +9 -237
  25. package/lib/crl-verify.js +1 -10
  26. package/lib/crmf-sign.js +19 -198
  27. package/lib/crmf-verify.js +1 -12
  28. package/lib/csr-sign.js +3 -34
  29. package/lib/csr-verify.js +1 -12
  30. package/lib/ct.js +28 -253
  31. package/lib/edwards-point.js +10 -44
  32. package/lib/est.js +28 -764
  33. package/lib/framework-error.js +4 -182
  34. package/lib/guard-all.js +1 -58
  35. package/lib/guard-async.js +1 -36
  36. package/lib/guard-bytes.js +6 -650
  37. package/lib/guard-compress.js +5 -102
  38. package/lib/guard-crypto.js +1 -40
  39. package/lib/guard-der.js +2 -93
  40. package/lib/guard-encoding.js +2 -60
  41. package/lib/guard-header.js +1 -36
  42. package/lib/guard-identifier.js +7 -531
  43. package/lib/guard-intrinsic.js +1 -150
  44. package/lib/guard-json.js +10 -73
  45. package/lib/guard-limits.js +1 -77
  46. package/lib/guard-list.js +1 -49
  47. package/lib/guard-name.js +3 -209
  48. package/lib/guard-parsed.js +7 -335
  49. package/lib/guard-range.js +1 -71
  50. package/lib/guard-secret.js +1 -102
  51. package/lib/guard-text.js +1 -64
  52. package/lib/guard-time.js +1 -97
  53. package/lib/hpke.js +3 -105
  54. package/lib/http-digest.js +14 -142
  55. package/lib/http-retry-after.js +1 -47
  56. package/lib/http-transport.js +21 -131
  57. package/lib/inspect.js +30 -284
  58. package/lib/ip-utils.js +6 -47
  59. package/lib/jose.js +7 -142
  60. package/lib/key.js +2 -67
  61. package/lib/lint.js +9 -194
  62. package/lib/merkle.js +1 -27
  63. package/lib/mime.js +9 -82
  64. package/lib/ocsp-request-verify.js +1 -14
  65. package/lib/ocsp-verify.js +1 -82
  66. package/lib/ocsp.js +11 -169
  67. package/lib/oid.js +5 -337
  68. package/lib/path-validate.js +103 -1492
  69. package/lib/pbes2.js +2 -89
  70. package/lib/pkcs12-build.js +31 -379
  71. package/lib/pki-build.js +64 -290
  72. package/lib/rc2.js +2 -18
  73. package/lib/rfc3339.js +4 -27
  74. package/lib/scep.js +59 -233
  75. package/lib/schema-all.js +0 -122
  76. package/lib/schema-attrcert.js +4 -146
  77. package/lib/schema-c509.js +153 -802
  78. package/lib/schema-cmc.js +5 -220
  79. package/lib/schema-cmp.js +2 -223
  80. package/lib/schema-cms.js +13 -427
  81. package/lib/schema-crl.js +5 -68
  82. package/lib/schema-crmf.js +6 -167
  83. package/lib/schema-csr.js +1 -54
  84. package/lib/schema-csrattrs.js +2 -87
  85. package/lib/schema-engine.js +0 -152
  86. package/lib/schema-ocsp.js +0 -117
  87. package/lib/schema-pkcs12.js +2 -93
  88. package/lib/schema-pkcs8.js +3 -35
  89. package/lib/schema-pkix.js +24 -655
  90. package/lib/schema-smime.js +0 -54
  91. package/lib/schema-tsp.js +3 -99
  92. package/lib/schema-x509.js +4 -63
  93. package/lib/shbs.js +11 -95
  94. package/lib/sign-scheme.js +2 -97
  95. package/lib/sigstore.js +9 -169
  96. package/lib/sleep.js +2 -8
  97. package/lib/smime.js +15 -408
  98. package/lib/tls-cert-compress.js +0 -62
  99. package/lib/trust.js +19 -185
  100. package/lib/tsp-sign.js +16 -270
  101. package/lib/validator-all.js +1 -30
  102. package/lib/validator-attcert.js +2 -46
  103. package/lib/validator-cose.js +2 -153
  104. package/lib/validator-keydesc.js +1 -37
  105. package/lib/validator-sig.js +1 -46
  106. package/lib/validator-tls.js +1 -88
  107. package/lib/validator-tpm.js +19 -122
  108. package/lib/verify-seam.js +1 -24
  109. package/lib/webauthn-mds.js +3 -400
  110. package/lib/webauthn.js +12 -833
  111. package/lib/webcrypto.js +5 -485
  112. package/lib/x509-sign.js +4 -108
  113. package/package.json +1 -1
  114. package/sbom.cdx.json +6 -6
@@ -1,85 +1,21 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
- //
5
- // @internal -- no operator-facing namespace. The documented surface is the codec whose
6
- // TLS handshake handling composes this validator (pki.tls).
7
- //
8
- // validator-tls -- the SINGLE home for the framing conformance of the TLS handshake
9
- // structures that carry certificates: the RFC 8879 sec. 4 CompressedCertificate and the
10
- // RFC 8446 sec. 4.4.2 Certificate message it decompresses to. Sibling to the guard family:
11
- // a validator owns a decoded TYPE's COMPLETE framing rule set once, so the vector bounds,
12
- // the minimum sizes and the trailing-byte rejection cannot drift as consumers are added.
13
- //
14
- // Every field is unsigned big-endian, packed with no padding, in the TLS presentation
15
- // language: `uintN` is a fixed-width integer and `opaque<min..max>` is a length prefix wide
16
- // enough for `max` followed by exactly that many bytes. The caller supplies its typed error
17
- // constructor E (a validator takes a class, `new E(code, msg)`, where the guard family
18
- // takes a factory) plus the `codes` its domain uses for each fault.
19
- //
20
- // Rule set (verbatim against RFC 8879 sec. 4 and RFC 8446 sec. 4.4.2):
21
- // - compressedCertificate: uint16 algorithm, uint24 uncompressed_length, then
22
- // opaque compressed_certificate_message<1..2^24-1>, where the minimum of 1 makes an empty
23
- // compressed body a framing violation, which matters because the three decompressors do
24
- // not agree on what empty input means. Bytes after the vector are rejected: one chain
25
- // must have exactly one encoding.
26
- // - certificateMessage: opaque certificate_request_context<0..2^8-1>, then
27
- // CertificateEntry certificate_list<0..2^24-1>, each entry an opaque<1..2^24-1> payload
28
- // (cert_data, or a SubjectPublicKeyInfo under RFC 7250 RawPublicKey) plus
29
- // Extension extensions<0..2^16-1>. Bytes after the list are rejected. A zero-length
30
- // entry payload is a framing violation, not a blank certificate.
31
- //
32
- // This validator decodes and bounds STRUCTURE only. Which algorithms are acceptable, what
33
- // the output cap is, and whether the recovered length matches its declaration are the
34
- // codec's policy and stay with the codec.
35
- //
36
- // The RFC 8446 sec. 4.4.2 / sec. 4.2 rules that bind a Certificate-message DECODER, and where
37
- // each one lands, enumerated so the ones deliberately left unenforced are a recorded decision
38
- // and not an oversight:
39
- // ENFORCED here:
40
- // - RawPublicKey carries at most one CertificateEntry (sec. 4.4.2).
41
- // - No extension type appears twice in one extension block (sec. 4.2).
42
- // - Every vector's framing, its declared minimum, and no trailing bytes at either level.
43
- // Not enforced, deliberately:
44
- // - "The sender's certificate MUST come in the first CertificateEntry" is about which
45
- // certificate a consumer treats as the leaf, not about framing. Entries are surfaced in
46
- // wire order and never reordered, so the caller sees exactly what arrived, and the same
47
- // section tells implementations to tolerate extraneous certificates and arbitrary
48
- // orderings beyond the first, so refusing any order here would be wrong.
49
- // - "The server's certificate_list MUST always be non-empty" is conditioned on the sender's
50
- // role: a client legitimately sends an empty list when it has no certificate to offer.
51
- // This decoder has no role, so an empty list decodes to zero entries and the caller, which
52
- // does know the role, decides.
53
- // - Extension correspondence ("extensions MUST correspond to ones from the client") and any
54
- // extension's own contents (a status_request body being a CertificateStatus) need the
55
- // negotiation state and the extension's semantics. Neither exists here: extension values
56
- // are surfaced raw.
57
- // - The OpenPGP certificate type is unreachable, not checked: the only accepted
58
- // types are X509 and RawPublicKey, and anything else is refused at the entry point.
4
+ // @internal
59
5
 
60
6
  var C = require("./constants");
61
7
  var ByteReader = require("./byte-reader");
62
8
 
63
- // The widest an opaque<..2^24-1> vector can be, and the width of the length prefix that
64
- // frames it. Both come straight from the TLS presentation language.
65
9
  var MAX_VECTOR_24 = 0xffffff;
66
10
  var MAX_VECTOR_16 = 0xffff;
67
11
 
68
- // RFC 8446 sec. 4.4.2 frames a Certificate message as a 1-byte-prefixed request context plus
69
- // a 3-byte-prefixed entry list, so the smallest well-formed message is 4 bytes.
70
12
  var MIN_CERT_MSG_BYTES = 4;
71
13
 
72
- // compressedCertificate(view, E, codes) -> { algorithm, uncompressedLength, body }.
73
- // Decode the RFC 8879 sec. 4 framing. `codes` names { truncated, framing, trailing }.
74
14
  // @enforced-by behavioral -- a packed TLS presentation-language decode has no rename-proof
75
- // code shape; the framing rules are pinned by the RED conformance vectors that drive the
76
- // shipped consumer (pki.tls.decompressCertificate) on each malformed shape.
77
15
  function compressedCertificate(view, E, codes) {
78
16
  var r = new ByteReader(view, 0, view.length, E, codes.truncated);
79
17
  var algorithm = r.u16(codes.truncated);
80
18
  var uncompressedLength = r.u24(codes.truncated);
81
- // opaque compressed_certificate_message<1..2^24-1>, minimum 1, so an empty body is a
82
- // framing violation and not something a decompressor gets to interpret.
83
19
  var body = r.vector(3, 1, MAX_VECTOR_24, codes.framing);
84
20
  if (!r.atEnd()) {
85
21
  throw new E(codes.trailing, "the CompressedCertificate carries " + r.remaining() +
@@ -88,25 +24,13 @@ function compressedCertificate(view, E, codes) {
88
24
  return { algorithm: algorithm, uncompressedLength: uncompressedLength, body: Buffer.from(body) };
89
25
  }
90
26
 
91
- // The extensions vector of a CertificateEntry, decoded to its records. RFC 8446 sec. 4.2 makes an
92
- // Extension a uint16 type plus a uint16-prefixed value, so the vector is a whole number of them and
93
- // the smallest is 4 bytes. Walking it is what keeps a malformed Certificate message from passing as
94
- // structurally valid: a one-byte vector cannot be an Extension, and accepting the bytes opaquely
95
- // would report it as well-formed. Each record's value is surfaced raw: this decodes the framing,
96
- // never the extension's own contents.
97
27
  function _extensionRecords(view, E, codes) {
98
28
  var r = new ByteReader(view, 0, view.length, E, codes.truncated);
99
29
  var out = [];
100
30
  var seen = Object.create(null);
101
31
  while (!r.atEnd()) {
102
- // Each iteration consumes at least 4 bytes and every read is bounds-checked, so the walk
103
- // always terminates or faults.
104
32
  var type = r.u16(codes.framing);
105
33
  var data = r.vector(2, 0, MAX_VECTOR_16, codes.framing);
106
- // RFC 8446 sec. 4.2: "There MUST NOT be more than one extension of the same type in a given
107
- // extension block." Left unenforced, one type could appear twice and a consumer reading only
108
- // the first (or only the last) would act on a different value than another implementation --
109
- // the same one-structure-two-meanings ambiguity a duplicate DER SET member creates.
110
34
  if (seen[type]) {
111
35
  throw new E(codes.framing, "the extension block carries more than one extension of type " +
112
36
  type + " (RFC 8446 sec. 4.2)");
@@ -117,14 +41,7 @@ function _extensionRecords(view, E, codes) {
117
41
  return out;
118
42
  }
119
43
 
120
- // certificateMessage(view, E, codes, certificateType) -> { certificateRequestContext, entries }.
121
- // Decode the RFC 8446 sec. 4.4.2 Certificate message. `codes` names { truncated, framing,
122
- // trailing }. `certificateType` is "X509" or "RawPublicKey" and is DECLARED by the caller --
123
- // it is negotiated by a separate extension (RFC 7250) and is not present in these bytes, so
124
- // it can never be inferred from them.
125
44
  // @enforced-by behavioral -- a packed TLS presentation-language decode has no rename-proof
126
- // code shape; the framing rules are pinned by the RED conformance vectors that drive the
127
- // shipped consumer (pki.tls.parseCertificateMessage) on each malformed shape.
128
45
  function certificateMessage(view, E, codes, certificateType) {
129
46
  var r = new ByteReader(view, 0, view.length, E, codes.truncated);
130
47
  var ctxLen = r.u8(codes.truncated);
@@ -135,10 +52,6 @@ function certificateMessage(view, E, codes, certificateType) {
135
52
  throw new E(codes.trailing, "the Certificate message carries " + r.remaining() +
136
53
  " byte(s) after the certificate list");
137
54
  }
138
- // RFC 8446 sec. 4.4.2: under a negotiated RawPublicKey certificate type the list "MUST contain
139
- // no more than one CertificateEntry". Otherwise the count is bounded because the byte ceiling
140
- // does not bound it -- the smallest legal entry is 6 bytes, so a message well inside the
141
- // framing limit can declare millions, each costing far more heap than wire.
142
55
  var maxEntries = certificateType === "RawPublicKey" ? 1 : C.LIMITS.TLS_CERT_MAX_ENTRIES;
143
56
  var entries = [];
144
57
  while (!list.atEnd()) {
@@ -1,30 +1,7 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
- //
5
- // @internal -- no operator-facing namespace. The documented surface is the verifier whose
6
- // tpm handling composes this validator (pki.webauthn).
7
- //
8
- // validator-tpm -- the SINGLE home for the TPM 2.0 structure conformance a WebAuthn tpm
9
- // attestation carries: the TPMT_PUBLIC (pubArea) and TPMS_ATTEST (certInfo) decode, plus the
10
- // pubArea-key == credential-key binding (WebAuthn sec. 8.3 + TCG TPM 2.0 Part 2). Sibling to
11
- // the guard family: a validator owns a decoded TYPE's COMPLETE conformance rule set once, so
12
- // the strict TPM2B sizing / algorithm-union walking / trailing-byte rejection cannot drift.
13
- //
14
- // Every field is unsigned big-endian, packed with no padding; every TPM2B_* is a UINT16 size
15
- // followed by exactly `size` bytes. The bare certInfo and pubArea carry NO outer TPM2B size
16
- // prefix. The caller supplies its typed error CONSTRUCTOR E + a structural `code` (a
17
- // malformed structure is bad input) and, for the key binding, a `mismatchCode`.
18
- //
19
- // Rule set (gap-checked verbatim against WebAuthn sec. 8.3 + TCG TPM 2.0 Part 2 sec. 10.12 /
20
- // 12.2):
21
- // - parsePubArea: decode type/nameAlg, walk parameters per the algorithm union, read the
22
- // public key in `unique`; reject trailing bytes (they perturb the TPM Name hash).
23
- // - parseCertInfo: magic == TPM_GENERATED_VALUE, type == TPM_ST_ATTEST_CERTIFY; read
24
- // extraData + attested.name; reject trailing bytes past the attested structure.
25
- // - pubKeyEqualsCose: the pubArea key (EC curve+x+y, or RSA modulus+exponent) equals the
26
- // credential COSE key, compared as unsigned magnitudes (a TPM2B may differ from a COSE
27
- // coordinate by a leading 0x00), the RSA exponent over its full UINT32 width.
4
+ // @internal
28
5
 
29
6
  var ByteReader = require("./byte-reader");
30
7
  var guard = require("./guard-all");
@@ -33,11 +10,8 @@ var TPM_GENERATED_VALUE = 0xff544347;
33
10
  var TPM_ST_ATTEST_CERTIFY = 0x8017;
34
11
  var TPM_ALG = { RSA: 0x0001, SHA1: 0x0004, SHA256: 0x000b, SHA384: 0x000c, SHA512: 0x000d, NULL: 0x0010, ECC: 0x0023 };
35
12
  var TPM_ALG_HASH = {}; TPM_ALG_HASH[TPM_ALG.SHA1] = "sha1"; TPM_ALG_HASH[TPM_ALG.SHA256] = "sha256"; TPM_ALG_HASH[TPM_ALG.SHA384] = "sha384"; TPM_ALG_HASH[TPM_ALG.SHA512] = "sha512";
36
- // TPM_ECC_CURVE -> COSE crv (the codepoints differ; this is a mapping, not equality).
37
13
  var TPM_CURVE_TO_COSE = {}; TPM_CURVE_TO_COSE[0x0003] = 1; TPM_CURVE_TO_COSE[0x0004] = 2; TPM_CURVE_TO_COSE[0x0005] = 3;
38
14
 
39
- // Unsigned big-endian magnitude compare: strip leading zero octets, then byte-equal. TPM2B
40
- // buffers and COSE fixed-length coordinates can differ by a leading 0x00.
41
15
  function _ucmp(a, b) {
42
16
  function strip(x) { var i = 0; while (i < x.length - 1 && x[i] === 0) i++; return x.subarray(i); }
43
17
  return Buffer.compare(strip(a), strip(b)) === 0;
@@ -46,21 +20,9 @@ function _uintBytes(n) {
46
20
  var hex = n.toString(16); if (hex.length % 2) hex = "0" + hex;
47
21
  var b = Buffer.from(hex, "hex"); var i = 0; while (i < b.length - 1 && b[i] === 0) i++; return b.subarray(i);
48
22
  }
49
- // TPMT_SYM_DEF_OBJECT: algorithm UINT16; when not NULL, keyBits+mode follow. An attestation
50
- // (restricted signing) key is NULL, so the non-NULL arm is defensive.
51
23
  function _symDef(r) { if (r.u16() !== TPM_ALG.NULL) { r.u16(); r.u16(); } }
52
- // TPMT_*_SCHEME / TPMT_KDF_SCHEME: scheme UINT16; when not NULL, a TPMS_SCHEME_HASH (a single
53
- // UINT16 hashAlg) follows for the signing/kdf schemes in scope.
54
24
  function _scheme(r) { if (r.u16() !== TPM_ALG.NULL) { r.u16(); } }
55
25
 
56
- // TPMA_OBJECT bit positions (TPM 2.0 Part 2 sec. 8.3.2, Table 33). A data row set, not a switch:
57
- // a bit index absent here is reserved by definition, and the reserved mask below is its complement
58
- // over the positions Table 33 marks "shall be zero" (bits 0, 3, 15:12 and 31:20).
59
- // Null-prototype: every one of these tables is indexed by a CALLER-SUPPLIED name, and an inherited
60
- // member ("constructor", "toString", "__proto__") would resolve to a truthy non-value. In a lookup
61
- // that decides whether a name is known, that reads as "known" and the caller's policy silently
62
- // applies nothing at all -- a fail-open. Applied to all three tables here, not only the one where
63
- // it was noticed.
64
26
  var TPMA_OBJECT_BITS = Object.assign(Object.create(null), {
65
27
  fixedTPM: 1, stClear: 2, fixedParent: 4, sensitiveDataOrigin: 5, userWithAuth: 6,
66
28
  adminWithPolicy: 7, firmwareLimited: 8, svnLimited: 9, noDA: 10,
@@ -68,78 +30,60 @@ var TPMA_OBJECT_BITS = Object.assign(Object.create(null), {
68
30
  });
69
31
  var TPMA_OBJECT_RESERVED = 0xfff0f009;
70
32
 
71
- // _decodeAttributes(oa) -> the named booleans of a TPMA_OBJECT word.
72
33
  // @enforced-by behavioral -- a bit-position table lookup has no rename-proof code shape; the RED
73
- // vectors (each named bit read off a real attestation, and the sign-bit case) are the guard.
74
34
  function _decodeAttributes(oa) {
75
35
  var out = {};
76
36
  Object.keys(TPMA_OBJECT_BITS).forEach(function (n) { out[n] = ((oa >>> TPMA_OBJECT_BITS[n]) & 1) === 1; });
77
37
  return out;
78
38
  }
79
39
 
80
- // parsePubArea(buf, E, code) -> the decoded TPMT_PUBLIC (WebAuthn 8.3 item 17-20).
81
40
  // @enforced-by behavioral -- a packed TPM structure decode has no rename-proof code shape;
82
- // the RED vectors (trailing bytes, an unsupported type, a truncated TPM2B) are the guard.
83
41
  function parsePubArea(buf, E, code) {
84
42
  var r = new ByteReader(buf, 0, buf.length, E, code);
85
43
  var type = r.u16(), nameAlg = r.u16();
86
- // objectAttributes (TPM 2.0 Part 2 sec. 8.3.2 Table 33) and authPolicy (Table 87) are SURFACED,
87
- // not gated: WebAuthn sec. 8.3 constrains only the `parameters` and `unique` fields of pubArea,
88
- // so enforcing these would invent a conformance rule. A relying party that wants them applies its
89
- // own policy, which it cannot do to a field it never sees. The bytes are trustworthy by the time
90
- // a caller reads them -- pubArea is hashed into certInfo.attested.name, which the AIK signature
91
- // covers, and both are verified before the result is built.
92
44
  var objectAttributes = r.u32();
93
- var authPolicy = r.vector(2, 0, null); // a zero-length digest IS the Empty Policy, not "absent"
45
+ var authPolicy = r.vector(2, 0, null);
94
46
  var pub = { type: type, nameAlg: nameAlg, nameAlgBytes: buf.subarray(2, 4),
95
47
  objectAttributes: objectAttributes >>> 0, attributes: _decodeAttributes(objectAttributes),
96
48
  authPolicy: authPolicy };
97
49
  if (type === TPM_ALG.RSA) {
98
- _symDef(r); // symmetric TPMT_SYM_DEF_OBJECT
99
- _scheme(r); // scheme TPMT_RSA_SCHEME
100
- r.u16(); // keyBits
101
- var exp = r.u32(); // exponent (0 => default 65537)
50
+ _symDef(r);
51
+ _scheme(r);
52
+ r.u16();
53
+ var exp = r.u32();
102
54
  pub.exponent = exp === 0 ? 65537 : exp;
103
- pub.rsa = r.vector(2, 0, null); // unique TPM2B_PUBLIC_KEY_RSA (the modulus)
55
+ pub.rsa = r.vector(2, 0, null);
104
56
  } else if (type === TPM_ALG.ECC) {
105
- _symDef(r); // symmetric
106
- _scheme(r); // scheme TPMT_ECC_SCHEME
107
- pub.curveId = r.u16(); // curveID TPMI_ECC_CURVE
108
- _scheme(r); // kdf TPMT_KDF_SCHEME
109
- pub.x = r.vector(2, 0, null); // unique.ecc.x TPM2B_ECC_PARAMETER
110
- pub.y = r.vector(2, 0, null); // unique.ecc.y TPM2B_ECC_PARAMETER
57
+ _symDef(r);
58
+ _scheme(r);
59
+ pub.curveId = r.u16();
60
+ _scheme(r);
61
+ pub.x = r.vector(2, 0, null);
62
+ pub.y = r.vector(2, 0, null);
111
63
  } else {
112
64
  throw new E(code, "unsupported TPMT_PUBLIC type 0x" + type.toString(16));
113
65
  }
114
- // TPMT_PUBLIC ends with `unique`; trailing bytes mean a malformed pubArea (and would
115
- // perturb the TPM Name hash), so the decode fails closed on them.
116
66
  if (!r.atEnd()) throw new E(code, "pubArea has trailing bytes after the unique field (WebAuthn 8.3)");
117
67
  return pub;
118
68
  }
119
69
 
120
- // parseCertInfo(buf, E, code) -> the decoded + magic/type-validated TPMS_ATTEST
121
- // { extraData, attestedName } (WebAuthn 8.3 item 13-15).
122
70
  // @enforced-by behavioral -- a packed TPM structure decode has no rename-proof code shape;
123
- // the RED vectors (wrong magic, wrong type, trailing bytes) are the guard.
124
71
  function parseCertInfo(buf, E, code) {
125
72
  var r = new ByteReader(buf, 0, buf.length, E, code);
126
73
  var magic = r.u32(), type = r.u16();
127
74
  if (magic !== TPM_GENERATED_VALUE) throw new E(code, "certInfo magic is not TPM_GENERATED_VALUE (WebAuthn 8.3)");
128
75
  if (type !== TPM_ST_ATTEST_CERTIFY) throw new E(code, "certInfo type is not TPM_ST_ATTEST_CERTIFY (WebAuthn 8.3)");
129
- r.vector(2, 0, null); // qualifiedSigner TPM2B_NAME
130
- var extraData = r.vector(2, 0, null); // extraData TPM2B_DATA
131
- r.u64(); r.u32(); r.u32(); r.u8(); // clockInfo TPMS_CLOCK_INFO (17 bytes)
132
- r.u64(); // firmwareVersion
133
- var name = r.vector(2, 0, null); // attested.name TPM2B_NAME (nameAlg||H)
134
- r.vector(2, 0, null); // attested.qualifiedName TPM2B_NAME
76
+ r.vector(2, 0, null);
77
+ var extraData = r.vector(2, 0, null);
78
+ r.u64(); r.u32(); r.u32(); r.u8();
79
+ r.u64();
80
+ var name = r.vector(2, 0, null);
81
+ r.vector(2, 0, null);
135
82
  if (!r.atEnd()) throw new E(code, "certInfo has trailing bytes after the attested structure (WebAuthn 8.3)");
136
83
  return { extraData: extraData, attestedName: name };
137
84
  }
138
85
 
139
- // pubKeyEqualsCose(pub, cose, E, mismatchCode, code) -- the pubArea public key MUST equal the
140
- // credential COSE key (WebAuthn 8.3 item 22).
141
86
  // @enforced-by behavioral -- a decoded-key equality check has no rename-proof code shape; the
142
- // RED vectors (a mismatched EC curve/coordinate, a mismatched RSA modulus/exponent) are the guard.
143
87
  function pubKeyEqualsCose(pub, cose, E, mismatchCode, code) {
144
88
  if (pub.type === TPM_ALG.ECC) {
145
89
  if (cose.kty !== 2 || TPM_CURVE_TO_COSE[pub.curveId] !== cose.crv || !_ucmp(pub.x, cose.x || Buffer.alloc(0)) || !_ucmp(pub.y, cose.y || Buffer.alloc(0))) {
@@ -148,9 +92,6 @@ function pubKeyEqualsCose(pub, cose, E, mismatchCode, code) {
148
92
  return;
149
93
  }
150
94
  if (pub.type === TPM_ALG.RSA) {
151
- // Compare the exponent as an unsigned integer over its full width (a UINT32 up to
152
- // 0xFFFFFFFF); a fixed 3-byte re-encode would silently truncate an exponent > 0xFFFFFF
153
- // and let a mismatched key pass (WebAuthn 8.3 item 22).
154
95
  var e = _uintBytes(pub.exponent >>> 0);
155
96
  if (cose.kty !== 3 || !_ucmp(pub.rsa, cose.n || Buffer.alloc(0)) || !_ucmp(e, cose.e || Buffer.alloc(0))) {
156
97
  throw new E(mismatchCode, "the TPM pubArea RSA key does not equal the credential public key");
@@ -160,21 +101,13 @@ function pubKeyEqualsCose(pub, cose, E, mismatchCode, code) {
160
101
  throw new E(code, "unsupported TPM pubArea key type");
161
102
  }
162
103
 
163
- // The one defined preset: the six TPMA_OBJECT bits every genuine attestation observed sets the same
164
- // way. `decrypt`, `noDA` and `userWithAuth` are deliberately absent: they differ across real
165
- // Windows Hello statements, so requiring any of them rejects working hardware. A second preset
166
- // would be policy invention; a caller who wants different bits spells them out.
167
104
  var TPM_POLICY_PROFILES = Object.assign(Object.create(null), {
168
105
  "hardware-bound": { fixedTPM: true, fixedParent: true, sensitiveDataOrigin: true, sign: true, restricted: false, x509sign: false },
169
106
  });
170
107
  var _TPM_POLICY_KEYS = Object.assign(Object.create(null), { profile: 1, objectAttributes: 1, reservedBitsClear: 1, consistency: 1, authPolicy: 1 });
171
108
  var _AUTH_POLICY_KEYS = Object.assign(Object.create(null), { present: 1, allow: 1 });
172
109
 
173
- // Config-time validation of opts.tpmPolicy: a typo must never silently disable a check, so an
174
- // unknown key or attribute name throws at the boundary.
175
110
  // @enforced-by behavioral -- an opts-shape validator has no rename-proof code shape; the RED
176
- // vectors (unknown key, unknown profile, unknown attribute, non-boolean value, and the
177
- // sensitiveDataOrigin-without-fixedTPM rule) are the guard.
178
111
  function normalizeObjectAttributePolicy(policy, E, code) {
179
112
  if (policy === undefined) return null;
180
113
  if (!policy || typeof policy !== "object" || Array.isArray(policy)) throw new E(code, "opts.tpmPolicy must be an object");
@@ -185,7 +118,6 @@ function normalizeObjectAttributePolicy(policy, E, code) {
185
118
  if (!preset) throw new E(code, "opts.tpmPolicy.profile " + JSON.stringify(policy.profile) + " is not a defined profile");
186
119
  Object.keys(preset).forEach(function (n) { want[n] = preset[n]; });
187
120
  }
188
- // An explicit map layers OVER the preset, so a caller can keep the profile and override one bit.
189
121
  var explicit = policy.objectAttributes;
190
122
  if (explicit !== undefined) {
191
123
  if (!explicit || typeof explicit !== "object" || Array.isArray(explicit)) throw new E(code, "opts.tpmPolicy.objectAttributes must be an object");
@@ -195,9 +127,6 @@ function normalizeObjectAttributePolicy(policy, E, code) {
195
127
  want[n] = explicit[n];
196
128
  });
197
129
  }
198
- // sec. 8.3.3.5 NOTE 1: sensitiveDataOrigin only asserts the TPM generated the key when fixedTPM
199
- // is also SET; otherwise the object could have been imported. Demanding it alone asserts
200
- // nothing, and a caller who thinks otherwise should hear it at config time.
201
130
  if (want.sensitiveDataOrigin === true && want.fixedTPM !== true) {
202
131
  throw new E(code, "opts.tpmPolicy requires sensitiveDataOrigin without fixedTPM; on its own it does not establish that the TPM generated the key (TPM 2.0 Part 2 sec. 8.3.3.5)");
203
132
  }
@@ -208,23 +137,10 @@ function normalizeObjectAttributePolicy(policy, E, code) {
208
137
  var allow = null;
209
138
  if (ap !== undefined) {
210
139
  if (!ap || typeof ap !== "object" || Array.isArray(ap)) throw new E(code, "opts.tpmPolicy.authPolicy must be an object");
211
- // The nested keys are enumerated for the same reason the top-level ones are: a misspelled
212
- // `alow` would leave the allow-list unset, and the assertion would then impose no digest
213
- // restriction at all -- the caller's policy silently doing nothing.
214
140
  guard.identifier.assertKnownKeys(ap, _AUTH_POLICY_KEYS, function (c, m) { return new E(c, m); }, code, "opts.tpmPolicy.authPolicy has an unknown key ");
215
141
  if (ap.present !== undefined && typeof ap.present !== "boolean") throw new E(code, "opts.tpmPolicy.authPolicy.present must be a boolean");
216
142
  if (ap.allow !== undefined) {
217
143
  if (!Array.isArray(ap.allow)) throw new E(code, "opts.tpmPolicy.authPolicy.allow must be an array");
218
- // Every entry is decoded here, at config time, so the comparison only ever sees bytes.
219
- // Node's hex decoder is permissive: it stops at the first character that is not a hex
220
- // digit, so "<digest>zz" decodes back to the digest and a non-string decodes to an empty
221
- // buffer. Either could match a key this policy was written to exclude, including the
222
- // Empty Policy. An entry that is not a Buffer or a canonical even-length hex string is a
223
- // caller error; it fails here, before it can become a digest nobody intended.
224
- // The decoding runs through guard.encoding.hex, which owns the alphabet, the even-length
225
- // rule and the canonical round-trip. Those three checks were once written out here by hand
226
- // and now live in one place. It also decodes, so the hex path cannot validate one string
227
- // and decode another.
228
144
  allow = ap.allow.map(function (entry, i) {
229
145
  if (guard.bytes.isByteSource(entry)) return guard.bytes.snapshotSource(entry, E, code, "opts.tpmPolicy.authPolicy.allow[" + i + "]");
230
146
  var label = "opts.tpmPolicy.authPolicy.allow[" + i + "]";
@@ -241,41 +157,23 @@ function normalizeObjectAttributePolicy(policy, E, code) {
241
157
  authPolicy: ap ? { present: ap.present === true, allow: allow } : null };
242
158
  }
243
159
 
244
- // Apply a normalized policy to a parsed pubArea. Nothing here is a WebAuthn sec. 8.3 requirement,
245
- // since that section constrains only pubArea's `parameters` and `unique` fields. Every rule runs
246
- // only because a caller asked for it by name.
247
160
  // @enforced-by behavioral -- an opt-gated policy gate has no rename-proof code shape, and there is
248
- // no general vector shape to detect: the RED vectors (each required bit in both directions, the
249
- // reserved-bit mask across the sign bit, the restricted sign+decrypt combination, the over-long
250
- // digest, the Empty Policy, and the allow-list miss) are the guard.
251
161
  function assertObjectAttributePolicy(pub, policy, E, policyCode, structuralCode) {
252
162
  if (!policy) return;
253
163
  var oa = pub.objectAttributes >>> 0;
254
- // Bit 31 lies inside the reserved mask, so the AND must be coerced back to unsigned: a bare
255
- // `oa & 0xfff0f009` is a signed int32, and a negative result would not read as "a bit is set".
256
164
  if (policy.reservedBitsClear && ((oa & TPMA_OBJECT_RESERVED) >>> 0) !== 0) {
257
165
  throw new E(structuralCode, "the TPMT_PUBLIC objectAttributes sets a reserved bit (TPM 2.0 Part 2 sec. 8.3.2 Table 33: shall be zero)");
258
166
  }
259
167
  if (policy.consistency) {
260
- // sec. 8.3.3.11, verbatim: "This attribute shall not be SET in any object that has fixedTPM
261
- // SET." A key that cannot leave its TPM cannot be duplicated, so requiring its duplication be
262
- // encrypted describes an object that cannot exist.
263
168
  if (pub.attributes.encryptedDuplication && pub.attributes.fixedTPM) {
264
169
  throw new E(structuralCode, "the TPMT_PUBLIC objectAttributes sets encryptedDuplication on an object with fixedTPM SET (TPM 2.0 Part 2 sec. 8.3.3.11)");
265
170
  }
266
- // sec. 8.3.3.16/.17: a restricted key is a TPM-internal key, and the two use bits are a choice
267
- // -- a restricted object that both signs and decrypts is not a defined combination.
268
171
  if (pub.attributes.sign && pub.attributes.decrypt && pub.attributes.restricted) {
269
172
  throw new E(structuralCode, "the TPMT_PUBLIC objectAttributes sets restricted with both sign and decrypt (TPM 2.0 Part 2 sec. 8.3.3)");
270
173
  }
271
- // A TPM2B_DIGEST carries at most a SHA-512 digest.
272
174
  if (pub.authPolicy.length > 64) {
273
175
  throw new E(structuralCode, "the TPMT_PUBLIC authPolicy is " + pub.authPolicy.length + " octets, above the 64-octet TPM2B_DIGEST maximum");
274
176
  }
275
- // The remaining sec. 8.3.3 "shall" statements are all parent-relative. fixedTPM, stClear and
276
- // encryptedDuplication are each constrained against the value the object's parent carries. An
277
- // attestation presents one public area and no parent, so a verifier cannot evaluate them at
278
- // all; they are not omitted by oversight, they are unverifiable from what is on the wire.
279
177
  }
280
178
  Object.keys(policy.objectAttributes).forEach(function (name) {
281
179
  var want = policy.objectAttributes[name];
@@ -290,7 +188,6 @@ function assertObjectAttributePolicy(pub, policy, E, policyCode, structuralCode)
290
188
  throw new E(policyCode, "the TPM credential key carries the Empty Policy; opts.tpmPolicy.authPolicy.present requires a policy digest");
291
189
  }
292
190
  if (ap.allow) {
293
- // Every entry was decoded and validated at config time, so this compares digests only.
294
191
  var got = pub.authPolicy;
295
192
  var ok = ap.allow.some(function (want) {
296
193
  return want.length === got.length && guard.crypto.constantTimeEqual(want, got);
@@ -1,29 +1,10 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
- //
5
- // @internal -- the injected-signature-engine seam, once.
6
- //
7
- // A CRL, a PKCS#10 certification request and an RFC 4211 CertReqMsg all verify an untrusted
8
- // signature through the same path-validation engine, which carries the algorithm-confusion, EdDSA
9
- // low-order and composite gates. None of them may reach that engine through pki.path's public
10
- // surface, so each holds a slot that path-validate fills at its module load. Each was writing the
11
- // same slot, the same wiring assertion and the same octet-alignment refusal. That shape lives here
12
- // now. A format module supplies what is its own: an error class, a code, and the preimage bytes.
13
- //
14
- // The producing side of each format asks a different question, through pki-build's
15
- // assertSignatureVerifies: whether the signature it has just made verifies under a key the caller
16
- // already controls. That check waives the EdDSA low-order-point gate for that reason, and is a
17
- // separate mechanism from this one.
4
+ // @internal
18
5
 
19
6
  var guard = require("./guard-all");
20
7
 
21
- // makeSeam(name, ErrorClass, code) -> { setEngine, verify }.
22
- //
23
- // `verify(sigAlg, signature, spkiBytes, preimage)` takes the signature as the parsed BIT STRING
24
- // ({ unusedBits, bytes }), because the octet-alignment refusal is part of the contract: a signature
25
- // carrying unused bits is malformed for every algorithm these formats admit, and a caller that had
26
- // already discarded `unusedBits` could not be held to it.
27
8
  function makeSeam(name, ErrorClass, code) {
28
9
  var engine = null;
29
10
 
@@ -31,10 +12,6 @@ function makeSeam(name, ErrorClass, code) {
31
12
  setEngine: function (verifyWithSpki) { engine = verifyWithSpki; },
32
13
 
33
14
  verify: function (sigAlg, signature, spkiBytes, preimage) {
34
- // Each format's producing verb requires path-validate for the injection side-effect, so the
35
- // engine is set on every route that reaches here through a public verb. A future internal
36
- // consumer that required a seam without it would otherwise call null and surface a raw
37
- // TypeError out of a verify, which names neither the defect nor a verdict.
38
15
  if (engine == null) {
39
16
  throw new ErrorClass(code, "the " + name + " signature engine is not initialized (require pki before use)");
40
17
  }