@blamejs/pki 0.4.14 → 0.5.0

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 (60) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/MIGRATING.md +2 -2
  3. package/README.md +142 -137
  4. package/index.js +4 -0
  5. package/lib/acme.js +73 -1
  6. package/lib/asn1-der.js +2 -0
  7. package/lib/attrcert-sign.js +4 -0
  8. package/lib/cbor-det.js +32 -16
  9. package/lib/cmc-build.js +880 -0
  10. package/lib/cmc-verify.js +657 -0
  11. package/lib/cmp-build.js +8 -7
  12. package/lib/cmp-verify.js +11 -1
  13. package/lib/cms-sign.js +170 -8
  14. package/lib/cms-verify.js +80 -14
  15. package/lib/crl-sign.js +22 -0
  16. package/lib/crmf-sign.js +5 -2
  17. package/lib/csr-sign.js +3 -0
  18. package/lib/ct.js +72 -0
  19. package/lib/est.js +828 -32
  20. package/lib/framework-error.js +13 -0
  21. package/lib/guard-bytes.js +37 -1
  22. package/lib/guard-range.js +23 -1
  23. package/lib/http-transport.js +9 -3
  24. package/lib/inspect.js +28 -5
  25. package/lib/jose.js +15 -0
  26. package/lib/lint.js +4 -0
  27. package/lib/merkle.js +5 -5
  28. package/lib/ocsp.js +139 -11
  29. package/lib/oid.js +69 -1
  30. package/lib/path-validate.js +438 -100
  31. package/lib/pkcs12-build.js +12 -0
  32. package/lib/schema-all.js +19 -1
  33. package/lib/schema-attrcert.js +27 -0
  34. package/lib/schema-c509.js +6 -0
  35. package/lib/schema-cmc.js +791 -0
  36. package/lib/schema-cmp.js +25 -0
  37. package/lib/schema-cms.js +17 -1
  38. package/lib/schema-crl.js +23 -1
  39. package/lib/schema-crmf.js +13 -0
  40. package/lib/schema-csr.js +11 -0
  41. package/lib/schema-csrattrs.js +6 -0
  42. package/lib/schema-engine.js +6 -2
  43. package/lib/schema-ocsp.js +41 -0
  44. package/lib/schema-pkcs12.js +16 -0
  45. package/lib/schema-pkcs8.js +8 -0
  46. package/lib/schema-pkix.js +6 -0
  47. package/lib/schema-smime.js +4 -4
  48. package/lib/schema-tsp.js +32 -1
  49. package/lib/schema-x509.js +14 -1
  50. package/lib/shbs.js +12 -4
  51. package/lib/sigstore.js +4 -0
  52. package/lib/smime.js +28 -7
  53. package/lib/tls-cert-compress.js +15 -3
  54. package/lib/trust.js +27 -4
  55. package/lib/tsp-sign.js +41 -6
  56. package/lib/vendor/README.md +19 -19
  57. package/lib/webauthn.js +895 -26
  58. package/lib/x509-sign.js +3 -0
  59. package/package.json +1 -1
  60. package/sbom.cdx.json +6 -6
@@ -0,0 +1,791 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.schema.cmc
6
+ * @nav Schema
7
+ * @title CMC
8
+ * @order 185
9
+ * @slug cmc
10
+ *
11
+ * @intro
12
+ * RFC 5272 CMC message-layer decoding, as updated by RFC 6402. A Full PKI
13
+ * Request (`PKIData`) and a Full PKI Response (`PKIResponse`) both ride inside
14
+ * a CMS SignedData (or AuthenticatedData) as the encapsulated content, named by
15
+ * the eContentType `id-cct-PKIData` / `id-cct-PKIResponse` -- so `parse` peels
16
+ * the CMS layer, dispatches on that content type, and decodes the message.
17
+ *
18
+ * Every body part carries a `BodyPartID`, and the identifier MUST be unique
19
+ * across the WHOLE message rather than within one sequence; 0 is reserved as
20
+ * the reference to the enclosing PKIData and is never an element's own
21
+ * identity. Controls are surfaced as an ordered list with their values RAW: an
22
+ * unrecognized control is DATA, not a fault, because the rule that a server
23
+ * fails an unrecognized control binds the server, not the client reading a
24
+ * response. The status controls (`CMCStatusInfo` v1 and `CMCStatusInfoV2`) are
25
+ * collected in wire order -- a response may carry several verdicts, and the
26
+ * ABSENCE of one means success.
27
+ *
28
+ * The `reqSequence` byte range is surfaced exactly as it appeared on the wire,
29
+ * tag and length included, because the Identity Proof V2 witness is computed
30
+ * over those bytes and a re-serialization would silently differ.
31
+ *
32
+ * @spec RFC 5272, RFC 5273, RFC 5274, RFC 6402
33
+ * @card
34
+ * Decode RFC 5272 CMC Full PKI Requests and Responses inside CMS -- controls
35
+ * with raw values, tagged requests (PKCS#10 / CRMF / other), ordered status
36
+ * verdicts with the RFC 6402 two-module OtherStatusInfo ambiguity resolved,
37
+ * whole-message body-part identity, raw reqSequence bytes, fail-closed.
38
+ */
39
+
40
+ var asn1 = require("./asn1-der");
41
+ var schema = require("./schema-engine");
42
+ var pkix = require("./schema-pkix");
43
+ var oid = require("./oid");
44
+ var cms = require("./schema-cms");
45
+ var guard = require("./guard-all");
46
+ var frameworkError = require("./framework-error");
47
+
48
+ var CmcError = frameworkError.CmcError;
49
+
50
+ var NS = pkix.makeNS("cmc", CmcError, oid);
51
+ var TAGS = asn1.TAGS;
52
+
53
+ function E(code, message, cause) { return new CmcError(code, message, cause); }
54
+ function O(name) { return oid.byName(name); }
55
+
56
+ // Registry constants -- never a dotted literal in a format module.
57
+ var OID_PKI_DATA = O("id-cct-PKIData");
58
+ var OID_PKI_RESPONSE = O("id-cct-PKIResponse");
59
+ var OID_STATUS_INFO = O("id-cmc-statusInfo");
60
+ var OID_STATUS_INFO_V2 = O("id-cmc-statusInfoV2");
61
+ var OID_RESPONSE_BODY = O("id-cmc-responseBody");
62
+ var OID_RA_IDENTITY_WITNESS = O("id-cmc-raIdentityWitness");
63
+
64
+ // BodyPartID ::= INTEGER (0..4294967295) -- RFC 5272 sec. 3.2.2.
65
+ var BODY_PART_MAX = 4294967295n;
66
+
67
+ // CMCStatus ::= INTEGER { success(0), failed(2), pending(3), noSupport(4),
68
+ // confirmRequired(5), popRequired(6), partial(7) } -- RFC 5272 sec. 6.1.3.
69
+ // 1 is RESERVED: it is neither a success nor a failure, so it is refused rather
70
+ // than folded into either.
71
+ var STATUS_BY_VALUE = {
72
+ 0: "success", 2: "failed", 3: "pending", 4: "noSupport",
73
+ 5: "confirmRequired", 6: "popRequired", 7: "partial",
74
+ };
75
+
76
+ // CMCFailInfo ::= INTEGER { ... } -- RFC 5272 sec. 6.1.4. Values outside this set
77
+ // are surfaced numerically rather than refused: sec. 6.1.4 reserves 1000..1999
78
+ // for private extended reasons, so an unknown number is legal data.
79
+ var FAIL_INFO_BY_VALUE = {
80
+ 0: "badAlg", 1: "badMessageCheck", 2: "badRequest", 3: "badTime", 4: "badCertId",
81
+ 5: "unsupportedExt", 6: "mustArchiveKeys", 7: "badIdentity", 8: "popRequired",
82
+ 9: "popFailed", 10: "noKeyReuse", 11: "internalCAError", 12: "tryLater",
83
+ 13: "authDataFail",
84
+ };
85
+
86
+ // The CMS content types a Full PKI Request / Response may ride in -- RFC 5272
87
+ // sec. 3.2 ("encapsulated in either a SignedData or an AuthenticatedData") and
88
+ // sec. 4.2, which says the same of the response.
89
+ // The names are the OID REGISTRY's, read off `oid.name(...)` rather than the
90
+ // spec's prose: RFC 5652 calls the type AuthenticatedData, the registry row is
91
+ // `authData`, and hard-coding the prose spelling would reject every conforming
92
+ // AuthenticatedData response while looking correct.
93
+ var CMC_CARRIERS = { signedData: 1, authData: 1 };
94
+
95
+ // Control PLACEMENT -- RFC 6402 sec. 2.5 / 2.6 give two MUSTs about where a
96
+ // control may appear. An OID-keyed table states them once, in the shape of the
97
+ // shipped CMS attribute-placement registry, so a new placement rule is a row.
98
+ var CONTROL_PLACEMENT = {};
99
+ CONTROL_PLACEMENT[OID_RESPONSE_BODY] = "pkiResponse"; // sec. 2.6: never in a PKIData
100
+ CONTROL_PLACEMENT[OID_RA_IDENTITY_WITNESS] = "pkiData"; // sec. 2.5: never in a PKIResponse
101
+
102
+ // ---- leaves ----------------------------------------------------------
103
+
104
+ // A BodyPartID read + bounded + narrowed atomically. `guard.range.int` takes
105
+ // BigInt bounds and returns a Number only when the value is inside them, so an
106
+ // out-of-range identifier can never reach the caller as a silently-truncated int.
107
+ function readBodyPartId(node, label) {
108
+ var v = asn1.read.integer(node);
109
+ return guard.range.int(v, 0n, BODY_PART_MAX, E, "cmc/bad-body-part-id", label || "a BodyPartID");
110
+ }
111
+
112
+ // The identity of an element, where 0 is reserved (RFC 5272 sec. 3.2.2: "The
113
+ // bodyPartID value of 0 is reserved for use as the reference to the current
114
+ // PKIData object"). A REFERENCE may be 0; an identity may not.
115
+ function readBodyPartIdentity(node) {
116
+ var id = readBodyPartId(node, "a body part identity");
117
+ if (id === 0) {
118
+ throw E("cmc/reserved-body-part-id",
119
+ "bodyPartID 0 is reserved as the reference to the current PKIData and cannot identify a body part (RFC 5272 sec. 3.2.2)");
120
+ }
121
+ return id;
122
+ }
123
+
124
+ // ---- controls --------------------------------------------------------
125
+
126
+ // TaggedAttribute ::= SEQUENCE { bodyPartID BodyPartID, attrType OBJECT
127
+ // IDENTIFIER, attrValues SET OF AttributeValue } -- RFC 5272 sec. 3.2.1.1.
128
+ // attrValues are surfaced RAW: this decoder does not know every control, and
129
+ // PD7's "fail on an unrecognized control" binds the SERVER, not a client reading
130
+ // a response, so an unknown value is data to hand on rather than a fault.
131
+ //
132
+ // The placement rule is NOT checked here: it depends on which message the control
133
+ // sits in, which the enclosing walk knows and this shape does not.
134
+ var TAGGED_ATTRIBUTE = schema.seq([
135
+ schema.field("bodyPartID", schema.decode(readBodyPartIdentity)),
136
+ schema.field("attrType", schema.oidLeaf()),
137
+ schema.field("attrValues", schema.setOf(schema.any(),
138
+ { code: "cmc/bad-control", what: "TaggedAttribute attrValues", build: rawList })),
139
+ ], {
140
+ assert: "sequence", arity: { exact: 3 }, code: "cmc/bad-control", what: "a TaggedAttribute",
141
+ build: function (m) {
142
+ var attrType = m.fields.attrType.value;
143
+ return {
144
+ bodyPartID: m.fields.bodyPartID.value,
145
+ attrType: attrType,
146
+ attrName: oid.name(attrType) || null,
147
+ values: m.fields.attrValues.value.result,
148
+ };
149
+ },
150
+ });
151
+
152
+ // RFC 6402 sec. 2.5 / 2.6 give two MUSTs about WHERE a control may appear, and
153
+ // both are one-directional. Applied against the enclosing message kind.
154
+ function assertControlPlacement(control, where) {
155
+ var placement = CONTROL_PLACEMENT[control.attrType];
156
+ if (placement && placement !== where) {
157
+ throw E("cmc/control-misplaced",
158
+ "the control " + (control.attrName || control.attrType) + " may appear only in the control sequence of a " +
159
+ placement + " (RFC 6402 sec. 2.5 / 2.6)");
160
+ }
161
+ }
162
+
163
+ // ---- status controls -------------------------------------------------
164
+
165
+ // BodyPartReference ::= CHOICE { bodyPartID BodyPartID, bodyPartPath BodyPartPath }
166
+ // -- an INTEGER and a SEQUENCE, so the arms are tag-disjoint. BodyPartPath is
167
+ // SEQUENCE SIZE(1..MAX), so an empty one is malformed rather than "no path".
168
+ function parseBodyPartReference(node) {
169
+ if (node.tagClass === "universal" && node.tagNumber === TAGS.INTEGER) {
170
+ return { bodyPartID: readBodyPartId(node), bodyPartPath: null };
171
+ }
172
+ if (node.tagClass === "universal" && node.tagNumber === TAGS.SEQUENCE && node.constructed) {
173
+ if (node.children.length < 1) {
174
+ throw E("cmc/bad-body-part-path", "a BodyPartPath is SEQUENCE SIZE(1..MAX) and cannot be empty (RFC 5272 sec. 3.2.2)");
175
+ }
176
+ return { bodyPartID: null, bodyPartPath: node.children.map(function (c) { return readBodyPartId(c); }) };
177
+ }
178
+ throw E("cmc/bad-status-info", "a BodyPartReference must be a BodyPartID INTEGER or a BodyPartPath SEQUENCE");
179
+ }
180
+
181
+ /**
182
+ * OtherStatusInfo ::= CHOICE { failInfo CMCFailInfo, pendInfo PendInfo,
183
+ * extendedFailInfo SEQUENCE { ... } }
184
+ *
185
+ * The load-bearing ambiguity. RFC 5272 App A and RFC 6402 App A.1 (the 1988
186
+ * module) leave `extendedFailInfo` UNTAGGED, so it collides with `pendInfo` --
187
+ * both are a bare SEQUENCE. RFC 6402 App A.2 (the 2008 module) tags it `[1]`.
188
+ * Both encodings occur on the wire, so both are decoded:
189
+ *
190
+ * universal INTEGER -> failInfo
191
+ * context [1] -> extendedFailInfo (2008 module)
192
+ * universal SEQUENCE -> disambiguate on the FIRST child's tag:
193
+ * OCTET STRING -> pendInfo (PendInfo ::= SEQUENCE
194
+ * { pendToken OCTET STRING, pendTime GeneralizedTime })
195
+ * OBJECT IDENTIFIER -> extendedFailInfo
196
+ * anything else -> refuse
197
+ *
198
+ * The final arm is the point: a shape that matches neither rule is REFUSED, not
199
+ * guessed at. Guessing here would attach a failure reason, or a pending token,
200
+ * that the responder never sent.
201
+ */
202
+ function parseOtherStatusInfo(node, version) {
203
+ // The extendedFailInfo arm belongs to CMCStatusInfoV2 alone. RFC 5272 sec. 6.1.1
204
+ // gives the v1 otherInfo exactly two arms -- `CHOICE { failInfo CMCFailInfo,
205
+ // pendInfo PendInfo }` -- so accepting the third here would read a failure
206
+ // verdict out of a v1 control that cannot legally express one, and the
207
+ // disambiguation below would be answering a question v1 never asks.
208
+ var extendedAllowed = version !== 1;
209
+ if (node.tagClass === "universal" && node.tagNumber === TAGS.INTEGER) {
210
+ var v = asn1.read.integer(node);
211
+ var n = guard.range.int(v, 0n, 0xFFFFFFFFn, E, "cmc/bad-status-info", "a CMCFailInfo");
212
+ return { failInfo: n, failInfoName: FAIL_INFO_BY_VALUE[n] || null, pendInfo: null, extendedFailInfo: null };
213
+ }
214
+ if (node.tagClass === "context" && node.tagNumber === 1) {
215
+ if (!extendedAllowed) {
216
+ throw E("cmc/bad-status-info",
217
+ "a v1 CMCStatusInfo otherInfo is failInfo or pendInfo only; the [1] extendedFailInfo arm is CMCStatusInfoV2's (RFC 5272 sec. 6.1.1)");
218
+ }
219
+ // IMPLICIT, so the [1] REPLACES the SEQUENCE tag and its children are the
220
+ // failInfoOID / failInfoValue directly -- there is no inner SEQUENCE to
221
+ // unwrap. Both RFC 6402 modules are IMPLICIT: A.1 (rfc6402.txt:805) and A.2
222
+ // (rfc6402.txt:1314) each open `DEFINITIONS IMPLICIT TAGS ::=`, and the
223
+ // EXPLICIT-override rule does not apply here because the tagged type is a
224
+ // plain SEQUENCE rather than a CHOICE or an open type. Reading this arm as
225
+ // EXPLICIT would reject every conforming 2008-module response.
226
+ return { failInfo: null, failInfoName: null, pendInfo: null, extendedFailInfo: readExtendedFailInfo(node.children, "[1] extendedFailInfo") };
227
+ }
228
+ if (node.tagClass === "universal" && node.tagNumber === TAGS.SEQUENCE && node.constructed) {
229
+ var first = node.children[0];
230
+ if (!first) throw E("cmc/ambiguous-status-info", "an empty OtherStatusInfo SEQUENCE matches neither pendInfo nor extendedFailInfo");
231
+ if (first.tagClass === "universal" && first.tagNumber === TAGS.OCTET_STRING) {
232
+ if (node.children.length !== 2) throw E("cmc/bad-status-info", "PendInfo must be { pendToken, pendTime }");
233
+ // pendTime is a GeneralizedTime specifically, so assert the tag rather than
234
+ // accept either time form -- a UTCTime here is a different encoding, and
235
+ // reading it anyway would let a two-digit year through the retry clock.
236
+ var timeNode = node.children[1];
237
+ if (timeNode.tagClass !== "universal" || timeNode.tagNumber !== TAGS.GENERALIZED_TIME) {
238
+ throw E("cmc/bad-status-info", "PendInfo pendTime must be a GeneralizedTime (RFC 5272 sec. 6.1.1)");
239
+ }
240
+ return {
241
+ failInfo: null, failInfoName: null, extendedFailInfo: null,
242
+ pendInfo: { pendToken: asn1.read.octetString(first), pendTime: asn1.read.time(timeNode) },
243
+ };
244
+ }
245
+ if (first.tagClass === "universal" && first.tagNumber === TAGS.OBJECT_IDENTIFIER) {
246
+ if (!extendedAllowed) {
247
+ throw E("cmc/bad-status-info",
248
+ "a v1 CMCStatusInfo otherInfo SEQUENCE is pendInfo; this one leads with an OBJECT IDENTIFIER, which is the extendedFailInfo shape CMCStatusInfoV2 introduced (RFC 5272 sec. 6.1.1)");
249
+ }
250
+ return { failInfo: null, failInfoName: null, pendInfo: null, extendedFailInfo: readExtendedFailInfo(node.children, "extendedFailInfo") };
251
+ }
252
+ throw E("cmc/ambiguous-status-info",
253
+ "an untagged OtherStatusInfo SEQUENCE is pendInfo or extendedFailInfo depending on its first element, and this one is neither an OCTET STRING nor an OBJECT IDENTIFIER (RFC 5272 App A vs RFC 6402 App A.1/A.2)");
254
+ }
255
+ throw E("cmc/bad-status-info", "OtherStatusInfo must be a CMCFailInfo INTEGER, a PendInfo SEQUENCE, or an extendedFailInfo");
256
+ }
257
+
258
+ // ExtendedFailInfo ::= SEQUENCE { failInfoOID OBJECT IDENTIFIER, failInfoValue ANY }
259
+ // The OID is surfaced raw and NEVER mapped onto a CMCFailInfo: RFC 5272 sec.
260
+ // 6.1.1 makes internalCAError only a MAY for an unrecognized extended error, and
261
+ // synthesizing it would report a reason the responder did not send.
262
+ function readExtendedFailInfo(children, what) {
263
+ if (!children || children.length !== 2) {
264
+ throw E("cmc/bad-status-info", what + " must be SEQUENCE { failInfoOID, failInfoValue }");
265
+ }
266
+ return { failInfoOID: asn1.read.oid(children[0]), failInfoValueBytes: children[1].bytes };
267
+ }
268
+
269
+ // CMCStatusInfoV2 ::= SEQUENCE { cMCStatus, bodyList SEQUENCE SIZE(1..MAX) OF
270
+ // BodyPartReference, statusString UTF8String OPTIONAL, otherInfo OPTIONAL }
271
+ // The v1 CMCStatusInfo is the same shape with a SEQUENCE OF BodyPartID bodyList
272
+ // and no extendedFailInfo arm; both are decoded into one surface so a caller
273
+ // reads one ordered list of verdicts (RFC 5272 sec. 6.1: a client MUST cope with
274
+ // several status controls).
275
+ function parseStatusInfo(valueDer, version, bodyPartID) {
276
+ var root = asn1.decode(valueDer);
277
+ if (root.tagClass !== "universal" || root.tagNumber !== TAGS.SEQUENCE || !root.constructed) {
278
+ throw E("cmc/bad-status-info", "a CMCStatusInfo value must be a SEQUENCE");
279
+ }
280
+ var kids = root.children;
281
+ if (kids.length < 2) throw E("cmc/bad-status-info", "a CMCStatusInfo needs at least cMCStatus and bodyList");
282
+
283
+ var statusValue = asn1.read.integer(kids[0]);
284
+ var statusNum = guard.range.int(statusValue, 0n, 0xFFFFFFFFn, E, "cmc/bad-status", "a CMCStatus");
285
+ var statusName = STATUS_BY_VALUE[statusNum];
286
+ if (!statusName) {
287
+ // 1 is reserved and everything above 7 is unassigned. An unknown status is
288
+ // not a verdict in either direction, so it is refused rather than mapped.
289
+ throw E("cmc/bad-status", "unknown CMCStatus " + statusNum + " (1 is reserved; RFC 5272 sec. 6.1.3)");
290
+ }
291
+
292
+ var listNode = kids[1];
293
+ if (listNode.tagClass !== "universal" || listNode.tagNumber !== TAGS.SEQUENCE || !listNode.constructed) {
294
+ throw E("cmc/bad-status-info", "the CMCStatusInfo bodyList must be a SEQUENCE");
295
+ }
296
+ if (listNode.children.length < 1) {
297
+ throw E("cmc/bad-status-info", "the CMCStatusInfo bodyList is SEQUENCE SIZE(1..MAX) and cannot be empty (RFC 5272 sec. 6.1.1)");
298
+ }
299
+ var bodyList = listNode.children.map(function (c) {
300
+ return version === 2 ? parseBodyPartReference(c) : { bodyPartID: readBodyPartId(c), bodyPartPath: null };
301
+ });
302
+
303
+ // The two OPTIONAL tail fields are ORDERED by the ASN.1: statusString precedes
304
+ // otherInfo. Tracking only "have I seen a statusString yet" would accept them
305
+ // reversed -- a second encoding of a structure DER admits exactly one encoding
306
+ // of -- so the walk refuses anything once otherInfo has been consumed.
307
+ var statusString = null, other = null, sawOther = false;
308
+ for (var i = 2; i < kids.length; i++) {
309
+ var k = kids[i];
310
+ if (k.tagClass === "universal" && k.tagNumber === TAGS.UTF8_STRING && statusString === null && !sawOther) {
311
+ // read.string, not a per-type reader: the codec exposes one strict text reader
312
+ // for the DirectoryString family and there is no `read.utf8`. The tag is
313
+ // already asserted UTF8String above, so this decodes exactly that.
314
+ statusString = asn1.read.string(k);
315
+ continue;
316
+ }
317
+ if (sawOther) {
318
+ throw E("cmc/bad-status-info",
319
+ "a CMCStatusInfo carries at most one OtherStatusInfo, and the OPTIONAL statusString precedes it (RFC 5272 sec. 6.1.1)");
320
+ }
321
+ other = parseOtherStatusInfo(k, version);
322
+ sawOther = true;
323
+ }
324
+
325
+ var out = {
326
+ version: version, bodyPartID: bodyPartID, status: statusName, statusValue: statusNum,
327
+ bodyList: bodyList, statusString: statusString,
328
+ failInfo: other ? other.failInfo : null,
329
+ failInfoName: other ? other.failInfoName : null,
330
+ pendInfo: other ? other.pendInfo : null,
331
+ extendedFailInfo: other ? other.extendedFailInfo : null,
332
+ };
333
+ _assertStatusCoherent(out);
334
+ return out;
335
+ }
336
+
337
+ // The cross-field coupling (RFC 5272 sec. 6.1.1 / 6.1.2): failInfo and
338
+ // extendedFailInfo are "present only if cMCStatus contains the value failed",
339
+ // and pendInfo "MUST be populated for a cMCStatus value of pending or partial".
340
+ // A message whose status and detail disagree is refused rather than reconciled --
341
+ // the two halves would otherwise let a caller read a verdict neither field states.
342
+ function _assertStatusCoherent(s) {
343
+ var isFailed = s.status === "failed";
344
+ var isPending = s.status === "pending" || s.status === "partial";
345
+ if ((s.failInfo !== null || s.extendedFailInfo !== null) && !isFailed) {
346
+ throw E("cmc/status-info-mismatch",
347
+ "a failInfo / extendedFailInfo is present only when cMCStatus is failed, got " + s.status + " (RFC 5272 sec. 6.1.1)");
348
+ }
349
+ if (s.pendInfo !== null && !isPending) {
350
+ throw E("cmc/status-info-mismatch",
351
+ "a pendInfo is present only when cMCStatus is pending or partial, got " + s.status + " (RFC 5272 sec. 6.1.1)");
352
+ }
353
+ if (isPending && s.pendInfo === null) {
354
+ throw E("cmc/status-info-mismatch",
355
+ "the pendInfo field MUST be populated for a cMCStatus of " + s.status + " (RFC 5272 sec. 6.1.2)");
356
+ }
357
+ }
358
+
359
+ // ---- tagged requests -------------------------------------------------
360
+
361
+ // TaggedRequest ::= CHOICE { tcr [0] TaggedCertificationRequest,
362
+ // crm [1] CertReqMsg,
363
+ // orm [2] SEQUENCE { bodyPartID, requestMessageType,
364
+ // requestMessageValue } }
365
+ // The CMC module is IMPLICIT TAGS, so each context tag REPLACES the arm's own
366
+ // universal tag -- the [0] node's direct children are the TaggedCertificationRequest
367
+ // fields, NOT a nested SEQUENCE. An EXPLICIT encoding is therefore a different
368
+ // (and invalid) message, which is what C5 pins.
369
+ // An IMPLICIT context-tagged constructed node re-encoded under the universal
370
+ // SEQUENCE tag it stands in for. IMPLICIT tagging REPLACES the tag and leaves the
371
+ // content untouched, so restoring it is exactly a re-headering of the same bytes.
372
+ function _asUniversalSequence(node) {
373
+ var headerLen = node.header.end - node.header.start;
374
+ return asn1.encode(0x00, true, TAGS.SEQUENCE, node.bytes.subarray(headerLen));
375
+ }
376
+
377
+ function parseTaggedRequest(node) {
378
+ if (node.tagClass !== "context" || !node.constructed) {
379
+ throw E("cmc/bad-tagged-request", "a TaggedRequest must be a context-tagged constructed CHOICE arm (RFC 5272 sec. 3.2.1.2)");
380
+ }
381
+ if (node.tagNumber === 0) {
382
+ // TaggedCertificationRequest ::= SEQUENCE { bodyPartID, certificationRequest }
383
+ if (node.children.length !== 2) {
384
+ throw E("cmc/bad-tagged-request", "a tcr arm is [0] IMPLICIT { bodyPartID, certificationRequest }; the module is IMPLICIT TAGS, so an EXPLICIT wrapper is not this arm");
385
+ }
386
+ var csrNode = node.children[1];
387
+ if (csrNode.tagClass !== "universal" || csrNode.tagNumber !== TAGS.SEQUENCE || !csrNode.constructed) {
388
+ throw E("cmc/bad-tagged-request", "the tcr certificationRequest must be a CertificationRequest SEQUENCE");
389
+ }
390
+ return {
391
+ arm: "tcr", bodyPartID: readBodyPartIdentity(node.children[0]),
392
+ certificationRequestBytes: csrNode.bytes, certReqMsgBytes: null,
393
+ requestMessageType: null, requestMessageValueBytes: null,
394
+ };
395
+ }
396
+ if (node.tagNumber === 1) {
397
+ // crm [1] IMPLICIT CertReqMsg.
398
+ //
399
+ // This arm still has a body part IDENTITY, and it is not a separate field:
400
+ // RFC 5272 sec. 3.2.2 says "Body part identifiers are encoded in the
401
+ // certReqIds field for CertReqMsg objects (in a TaggedRequest) or in the
402
+ // bodyPartID field of the other objects", and sec. 6.5.2 repeats it
403
+ // ("... either the bodyPartID of a TaggedCertificationRequest (PKCS #10) or
404
+ // the certReqId of the CertRequest within a CertReqMsg (CRMF)"). So the
405
+ // certReqId is read here and carried into the SAME whole-message uniqueness
406
+ // check every other body part is bound by -- returning null would silently
407
+ // exempt this arm from it.
408
+ //
409
+ // CertReqMsg ::= SEQUENCE { certReq CertRequest, popo OPTIONAL, regInfo OPTIONAL }
410
+ // CertRequest ::= SEQUENCE { certReqId INTEGER, certTemplate CertTemplate, ... }
411
+ //
412
+ // Only the identity is decoded here; the CertReqMsg's own conformance is
413
+ // pki.schema.crmf's to enforce, and duplicating it would fork the rules.
414
+ var certReq = node.children[0];
415
+ if (!certReq || certReq.tagClass !== "universal" || certReq.tagNumber !== TAGS.SEQUENCE || !certReq.constructed) {
416
+ throw E("cmc/bad-tagged-request", "a crm arm is [1] IMPLICIT CertReqMsg, whose first element is a CertRequest SEQUENCE");
417
+ }
418
+ if (!certReq.children.length) {
419
+ throw E("cmc/bad-tagged-request", "a CertRequest must lead with its certReqId INTEGER (RFC 4211 sec. 5)");
420
+ }
421
+ return {
422
+ arm: "crm", bodyPartID: readBodyPartIdentity(certReq.children[0]),
423
+ certificationRequestBytes: null,
424
+ // Re-tagged from the IMPLICIT [1] back to the universal SEQUENCE the CRMF
425
+ // parser expects: handing back the context-tagged bytes would produce a
426
+ // structure no CertReqMsg reader accepts.
427
+ certReqMsgBytes: _asUniversalSequence(node),
428
+ requestMessageType: null, requestMessageValueBytes: null,
429
+ };
430
+ }
431
+ if (node.tagNumber === 2) {
432
+ if (node.children.length !== 3) {
433
+ throw E("cmc/bad-tagged-request", "an orm arm is [2] IMPLICIT { bodyPartID, requestMessageType, requestMessageValue }");
434
+ }
435
+ return {
436
+ arm: "orm", bodyPartID: readBodyPartIdentity(node.children[0]),
437
+ certificationRequestBytes: null, certReqMsgBytes: null,
438
+ requestMessageType: asn1.read.oid(node.children[1]),
439
+ requestMessageValueBytes: node.children[2].bytes,
440
+ };
441
+ }
442
+ throw E("cmc/bad-tagged-request", "unknown TaggedRequest alternative [" + node.tagNumber + "] (RFC 5272 sec. 3.2.1.2)");
443
+ }
444
+
445
+ // ---- body-part identity ----------------------------------------------
446
+
447
+ // RFC 5272 sec. 3.2.2: "The body part identifier MUST be unique within a single
448
+ // PKIData or PKIResponse." The scope is the WHOLE message -- a checker that ran
449
+ // per-sequence would accept a control and a request that collide, which is
450
+ // exactly the shape B2 pins.
451
+ function assertUniqueBodyPartIds(ids) {
452
+ var seen = Object.create(null);
453
+ for (var i = 0; i < ids.length; i++) {
454
+ if (ids[i] === null) continue;
455
+ if (seen[ids[i]]) {
456
+ throw E("cmc/duplicate-body-part-id",
457
+ "bodyPartID " + ids[i] + " appears more than once; it MUST be unique within a single PKIData or PKIResponse (RFC 5272 sec. 3.2.2)");
458
+ }
459
+ seen[ids[i]] = true;
460
+ }
461
+ }
462
+
463
+ // ---- the two message bodies ------------------------------------------
464
+
465
+ // The two message bodies as engine schemas. Declaring them means the engine owns
466
+ // the positional reads, the field arity and the SET-OF ordering -- the bug class
467
+ // a hand-rolled cursor keeps reintroducing -- while the rules that genuinely span
468
+ // fields (whole-message body-part uniqueness, control placement, the status
469
+ // cross-checks) run in the build / the caller, which is where they can see
470
+ // everything they need.
471
+ // A repeat's walk result is the match object `{ node, items: [{ node, value }] }`,
472
+ // so each of these carries a `build` that flattens it to the plain list the
473
+ // enclosing build wants. The three differ by what an item's `value` IS, which is
474
+ // decided by the item schema:
475
+ // - a `seq` item walks to its own MATCH, so its build output is at .value.result
476
+ // - a `decode` leaf walks straight to whatever its function returned
477
+ // - an `any` item walks to the node, whose .bytes is the raw TLV
478
+ function builtList(match) { return match.items.map(function (i) { return i.value.result; }); }
479
+ function decodedList(match) { return match.items.map(function (i) { return i.value; }); }
480
+ function rawList(match) { return match.items.map(function (i) { return i.node.bytes; }); }
481
+
482
+ var CONTROL_SEQUENCE = schema.seqOf(TAGGED_ATTRIBUTE,
483
+ { code: "cmc/bad-control", what: "a control sequence", build: builtList });
484
+ var REQ_SEQUENCE = schema.seqOf(schema.decode(parseTaggedRequest),
485
+ { code: "cmc/bad-tagged-request", what: "a reqSequence", build: decodedList });
486
+
487
+ // TaggedContentInfo ::= SEQUENCE { bodyPartID, contentInfo } (RFC 5272 sec.
488
+ // 3.2.1.3) and OtherMsg ::= SEQUENCE { bodyPartID, otherMsgType, otherMsgValue }
489
+ // (sec. 3.2.1.4). Neither payload is interpreted here -- the ContentInfo is a
490
+ // CMS object the caller decodes, the OtherMsg value is by definition arbitrary --
491
+ // but BOTH carry a body part identity, and sec. 3.2.2's uniqueness rule covers
492
+ // "each element of a PKIData or PKIResponse". Decoding just the identifier is
493
+ // what lets that rule cover them without this layer claiming to understand the
494
+ // payload.
495
+ var TAGGED_CONTENT_INFO = schema.seq([
496
+ schema.field("bodyPartID", schema.decode(readBodyPartIdentity)),
497
+ schema.field("contentInfo", schema.any()),
498
+ ], {
499
+ assert: "sequence", arity: { exact: 2 }, code: "cmc/bad-cms-sequence", what: "a TaggedContentInfo",
500
+ build: function (m) {
501
+ // The payload stays RAW -- this layer does not decode a content type it may
502
+ // not know -- but it must still BE a ContentInfo: SEQUENCE { contentType
503
+ // OBJECT IDENTIFIER, content [0] EXPLICIT ANY OPTIONAL } (RFC 5652 sec. 3).
504
+ // Surfacing an INTEGER, or anything else that cannot be one, as a content
505
+ // info would hand the caller a "CMS message" no CMS reader will accept, which
506
+ // is not the same thing as a content type this decoder happens not to handle.
507
+ var ci = m.fields.contentInfo.value;
508
+ var kids = ci.children || [];
509
+ var lead = kids[0];
510
+ if (!(ci.tagClass === "universal" && ci.tagNumber === TAGS.SEQUENCE && ci.constructed) || !lead) {
511
+ throw E("cmc/bad-cms-sequence",
512
+ "a TaggedContentInfo carries a CMS ContentInfo, which leads with its contentType OBJECT IDENTIFIER (RFC 5652 sec. 3)");
513
+ }
514
+ // READ the contentType rather than inspecting its tag. An OBJECT IDENTIFIER
515
+ // TLV can carry content no OID reader will accept -- empty, or a subidentifier
516
+ // padded to a non-minimal encoding -- and a tag check waves both through. The
517
+ // toolkit's own CMS parser refuses them, so accepting them here would let the
518
+ // builder sign a "CMS message" pki.schema.cms.parse will not read back. The
519
+ // VALUE is still not interpreted: this layer only establishes that there is a
520
+ // content type to interpret.
521
+ try { asn1.read.oid(lead); }
522
+ catch (e) {
523
+ throw E("cmc/bad-cms-sequence",
524
+ "a TaggedContentInfo carries a CMS ContentInfo, whose contentType must be a readable OBJECT IDENTIFIER (RFC 5652 sec. 3)", e);
525
+ }
526
+ // The WHOLE envelope, not just its first field: content is `[0] EXPLICIT ANY
527
+ // DEFINED BY contentType OPTIONAL`, so at most one more element and it is that
528
+ // tag. An INTEGER sitting where the content belongs, or a third field, is not
529
+ // a ContentInfo any CMS reader will take -- and stopping at the leading OID
530
+ // would surface it as one.
531
+ if (kids.length > 2) {
532
+ throw E("cmc/bad-cms-sequence",
533
+ "a CMS ContentInfo is { contentType, [0] EXPLICIT content OPTIONAL }; this carries " +
534
+ kids.length + " fields (RFC 5652 sec. 3)");
535
+ }
536
+ if (kids.length === 2) {
537
+ var content = kids[1];
538
+ if (content.tagClass !== "context" || content.tagNumber !== 0 || !content.constructed) {
539
+ throw E("cmc/bad-cms-sequence",
540
+ "a CMS ContentInfo's second field is the [0] EXPLICIT content (RFC 5652 sec. 3)");
541
+ }
542
+ // EXPLICIT wraps EXACTLY ONE value (X.690 sec. 8.14). Checking the tag alone
543
+ // would let an EMPTY wrapper, or one holding several values, read as a content
544
+ // info -- shapes pki.schema.cms.parse refuses as cms/not-a-content-info, so the
545
+ // builder's readback would pass a message its own CMS reader will not accept.
546
+ if (!content.children || content.children.length !== 1) {
547
+ throw E("cmc/bad-cms-sequence",
548
+ "a ContentInfo's [0] EXPLICIT content wraps exactly one value, got " +
549
+ ((content.children && content.children.length) || 0) + " (X.690 sec. 8.14)");
550
+ }
551
+ }
552
+ return { bodyPartID: m.fields.bodyPartID.value, contentInfoBytes: ci.bytes };
553
+ },
554
+ });
555
+
556
+ var OTHER_MSG = schema.seq([
557
+ schema.field("bodyPartID", schema.decode(readBodyPartIdentity)),
558
+ schema.field("otherMsgType", schema.oidLeaf()),
559
+ schema.field("otherMsgValue", schema.any()),
560
+ ], {
561
+ assert: "sequence", arity: { exact: 3 }, code: "cmc/bad-other-msg", what: "an OtherMsg",
562
+ build: function (m) {
563
+ return {
564
+ bodyPartID: m.fields.bodyPartID.value,
565
+ otherMsgType: m.fields.otherMsgType.value,
566
+ otherMsgValueBytes: m.fields.otherMsgValue.value.bytes,
567
+ };
568
+ },
569
+ });
570
+
571
+ // PKIData ::= SEQUENCE { controlSequence, reqSequence, cmsSequence,
572
+ // otherMsgSequence } -- RFC 5272 sec. 3.2.1. All FOUR are mandatory though each
573
+ // may be empty, so `arity: { exact: 4 }` is the rule, not a convenience.
574
+ var PKI_DATA = schema.seq([
575
+ schema.field("controlSequence", CONTROL_SEQUENCE),
576
+ schema.field("reqSequence", REQ_SEQUENCE),
577
+ schema.field("cmsSequence", schema.seqOf(TAGGED_CONTENT_INFO,
578
+ { code: "cmc/bad-cms-sequence", what: "the PKIData cmsSequence", build: builtList })),
579
+ schema.field("otherMsgSequence", schema.seqOf(OTHER_MSG,
580
+ { code: "cmc/bad-other-msg", what: "the PKIData otherMsgSequence", build: builtList })),
581
+ ], {
582
+ assert: "sequence", arity: { exact: 4 }, code: "cmc/bad-pkidata",
583
+ what: "a PKIData (SEQUENCE { controlSequence, reqSequence, cmsSequence, otherMsgSequence }, all four mandatory though each may be empty -- RFC 5272 sec. 3.2.1)",
584
+ build: function (m) {
585
+ return {
586
+ kind: "pkiData",
587
+ controls: m.fields.controlSequence.value.result,
588
+ requests: m.fields.reqSequence.value.result,
589
+ cmsSequence: m.fields.cmsSequence.value.result,
590
+ otherMsgs: m.fields.otherMsgSequence.value.result,
591
+ // The raw reqSequence TLV, tag and length included: the Identity Proof V2
592
+ // witness is computed over exactly these bytes "encoded exactly as it
593
+ // appears in the Full PKI Request" (RFC 5272 sec. 6.2.1), so it is taken
594
+ // off the matched NODE rather than re-encoded from the decoded requests.
595
+ reqSequenceBytes: m.fields.reqSequence.node.bytes,
596
+ };
597
+ },
598
+ });
599
+
600
+ // PKIResponse ::= SEQUENCE { controlSequence, cmsSequence, otherMsgSequence } --
601
+ // RFC 5272 sec. 4.2.1. THREE fields: there is no reqSequence in a response.
602
+ var PKI_RESPONSE = schema.seq([
603
+ schema.field("controlSequence", CONTROL_SEQUENCE),
604
+ schema.field("cmsSequence", schema.seqOf(TAGGED_CONTENT_INFO,
605
+ { code: "cmc/bad-cms-sequence", what: "the PKIResponse cmsSequence", build: builtList })),
606
+ schema.field("otherMsgSequence", schema.seqOf(OTHER_MSG,
607
+ { code: "cmc/bad-other-msg", what: "the PKIResponse otherMsgSequence", build: builtList })),
608
+ ], {
609
+ assert: "sequence", arity: { exact: 3 }, code: "cmc/bad-pkiresponse",
610
+ what: "a PKIResponse (SEQUENCE { controlSequence, cmsSequence, otherMsgSequence } -- three fields, all mandatory though each may be empty -- RFC 5272 sec. 4.2.1)",
611
+ build: function (m) {
612
+ return {
613
+ kind: "pkiResponse",
614
+ controls: m.fields.controlSequence.value.result,
615
+ requests: [],
616
+ cmsSequence: m.fields.cmsSequence.value.result,
617
+ otherMsgs: m.fields.otherMsgSequence.value.result,
618
+ reqSequenceBytes: null,
619
+ };
620
+ },
621
+ });
622
+
623
+ // Collect the status controls out of an already-parsed control list, in wire
624
+ // order. Absence is NOT an error: RFC 5272 sec. 6.1.2 says "If no status exists
625
+ // for a Simple or Full PKI Request, then the value of success is assumed", so a
626
+ // response with no status control is a successful one.
627
+ function _collectStatuses(controls) {
628
+ var out = [];
629
+ for (var i = 0; i < controls.length; i++) {
630
+ var c = controls[i];
631
+ var version = c.attrType === OID_STATUS_INFO_V2 ? 2 : (c.attrType === OID_STATUS_INFO ? 1 : 0);
632
+ if (!version) continue;
633
+ if (c.values.length !== 1) {
634
+ throw E("cmc/bad-status-info", "a CMC status control carries exactly one AttributeValue, got " + c.values.length);
635
+ }
636
+ out.push(parseStatusInfo(c.values[0], version, c.bodyPartID));
637
+ }
638
+ return out;
639
+ }
640
+
641
+ // The cross-cutting rules that a per-field walk cannot see: where each control is
642
+ // allowed to appear, which of them are status verdicts, which are unrecognized
643
+ // (surfaced, never a fault), and the whole-message body-part uniqueness.
644
+ function _finishBody(body, where) {
645
+ for (var i = 0; i < body.controls.length; i++) assertControlPlacement(body.controls[i], where);
646
+ body.statuses = _collectStatuses(body.controls);
647
+ body.unhandled = body.controls.filter(function (c) {
648
+ return c.attrType !== OID_STATUS_INFO && c.attrType !== OID_STATUS_INFO_V2 && c.attrName === null;
649
+ });
650
+ // RFC 5272 sec. 3.2.2 scopes uniqueness to "each element of a PKIData or
651
+ // PKIResponse", and FOUR element kinds carry an identity. Enumerated here in
652
+ // one place so the rule cannot hold for some of them and quietly lapse for the
653
+ // rest: controls (sec. 3.2.1.1), requests (sec. 3.2.1.2 -- the tcr/orm
654
+ // bodyPartID and the crm arm's certReqId), content infos (sec. 3.2.1.3) and
655
+ // other messages (sec. 3.2.1.4).
656
+ var ids = [];
657
+ function collect(list) { for (var j = 0; j < list.length; j++) ids.push(list[j].bodyPartID); }
658
+ collect(body.controls);
659
+ collect(body.requests);
660
+ collect(body.cmsSequence);
661
+ collect(body.otherMsgs);
662
+ assertUniqueBodyPartIds(ids);
663
+ return body;
664
+ }
665
+
666
+ /**
667
+ * @primitive pki.schema.cmc.parsePkiData
668
+ * @signature pki.schema.cmc.parsePkiData(der) -> parsed
669
+ * @since 0.4.16
670
+ * @status experimental
671
+ * @spec RFC 5272 sec. 3.2.1
672
+ * @related pki.schema.cmc.parse, pki.schema.cmc.parsePkiResponse
673
+ *
674
+ * Decode a bare `PKIData` body (the encapsulated content of a Full PKI Request),
675
+ * without the CMS layer. All FOUR sequences are mandatory though each may be
676
+ * empty, so a message that simply omits a trailing empty one is malformed rather
677
+ * than shorthand. `reqSequenceBytes` is the raw `reqSequence` TLV, tag and length
678
+ * included, because the Identity Proof V2 witness is computed over exactly those
679
+ * bytes (RFC 5272 sec. 6.2.1).
680
+ *
681
+ * @example
682
+ * var b = pki.asn1.build;
683
+ * var der = b.sequence([b.sequence([]), b.sequence([]), b.sequence([]), b.sequence([])]);
684
+ * var d = pki.schema.cmc.parsePkiData(der);
685
+ * d.controls.length; // 0 -- present and empty, which is legal
686
+ */
687
+ function parsePkiData(input) {
688
+ var root = asn1.decode(Buffer.isBuffer(input) ? input : Buffer.from(input));
689
+ var body = schema.walk(PKI_DATA, root, NS).result;
690
+ body.bytes = root.bytes;
691
+ return _finishBody(body, "pkiData");
692
+ }
693
+
694
+ /**
695
+ * @primitive pki.schema.cmc.parsePkiResponse
696
+ * @signature pki.schema.cmc.parsePkiResponse(der) -> parsed
697
+ * @since 0.4.16
698
+ * @status experimental
699
+ * @spec RFC 5272 sec. 4.2.1
700
+ * @related pki.schema.cmc.parse, pki.schema.cmc.parsePkiData
701
+ *
702
+ * Decode a bare `PKIResponse` body. A PKIResponse has THREE sequences -- there is
703
+ * no `reqSequence` -- and all three are mandatory though each may be empty. The
704
+ * status controls are collected in wire order; a response may legitimately carry
705
+ * several, and carrying NONE means success (RFC 5272 sec. 6.1.2).
706
+ *
707
+ * @example
708
+ * var b = pki.asn1.build;
709
+ * var der = b.sequence([b.sequence([]), b.sequence([]), b.sequence([])]);
710
+ * var r = pki.schema.cmc.parsePkiResponse(der);
711
+ * r.statuses.length; // 0 -- no status control means success is assumed
712
+ */
713
+ function parsePkiResponse(input) {
714
+ var root = asn1.decode(Buffer.isBuffer(input) ? input : Buffer.from(input));
715
+ var body = schema.walk(PKI_RESPONSE, root, NS).result;
716
+ body.bytes = root.bytes;
717
+ return _finishBody(body, "pkiResponse");
718
+ }
719
+
720
+ /**
721
+ * @primitive pki.schema.cmc.parse
722
+ * @signature pki.schema.cmc.parse(input) -> parsed
723
+ * @since 0.4.16
724
+ * @status experimental
725
+ * @spec RFC 5272, RFC 6402
726
+ * @defends cmc-status-confusion (CWE-20)
727
+ * @related pki.schema.cmc.parsePkiData, pki.schema.cmc.parsePkiResponse, pki.cms.verify
728
+ *
729
+ * Decode a CMC Full PKI Request or Full PKI Response from its CMS carrier. `input`
730
+ * is DER, a PEM `CMS` block, or an already-parsed `pki.schema.cms` object. The CMS
731
+ * layer is peeled, the encapsulated content type selects the body
732
+ * (`id-cct-PKIData` -> a request, `id-cct-PKIResponse` -> a response), and any
733
+ * other content type is refused as not-CMC rather than guessed at.
734
+ *
735
+ * The parsed CMS is returned on `cms` so a caller can verify the signature -- this
736
+ * decoder never does: reading a message and trusting it are separate steps, and
737
+ * RFC 5272 sec. 3.2.1.3.4 makes the signature check the caller's obligation.
738
+ *
739
+ * @example
740
+ * var b = pki.asn1.build;
741
+ * var body = b.sequence([b.sequence([]), b.sequence([]), b.sequence([]), b.sequence([])]);
742
+ * var encap = b.sequence([b.oid(pki.oid.byName("id-cct-PKIData")), b.explicit(0, b.octetString(body))]);
743
+ * var sd = b.sequence([b.integer(3n), b.set([]), encap, b.set([])]);
744
+ * var der = b.sequence([b.oid("1.2.840.113549.1.7.2"), b.explicit(0, sd)]);
745
+ * pki.schema.cmc.parse(der).kind; // "pkiData"
746
+ */
747
+ function parse(input) {
748
+ // Bytes or an already-parsed CMS? Decided by a POSITIVE marker of the parsed
749
+ // shape, never by "it is an object" -- a Uint8Array is an object too, so the
750
+ // looser test routes perfectly good DER to the parsed branch, where it has no
751
+ // content type and is refused as not-CMC while the identical Buffer parses.
752
+ // encapContentInfo is the field this function goes on to read, so requiring it
753
+ // is the same question the code below already asks.
754
+ var parsedCms = (input && typeof input === "object" && !ArrayBuffer.isView(input) &&
755
+ !(input instanceof ArrayBuffer) && input.encapContentInfo != null)
756
+ ? input : cms.parse(input);
757
+ // The CARRIER is part of the identity, not just the encapsulated content type.
758
+ // RFC 5272 sec. 3.2 / 4.2 admit a SignedData or an AuthenticatedData; other CMS
759
+ // types also expose an encapContentInfo (CompressedData does), and dispatching
760
+ // on the inner OID alone would accept an unsigned, unauthenticated wrapper as a
761
+ // Full PKI message.
762
+ if (CMC_CARRIERS[parsedCms.contentTypeName] !== 1) {
763
+ throw E("cmc/not-cmc",
764
+ "a Full PKI Request / Response is carried in a CMS SignedData or AuthenticatedData, got " +
765
+ (parsedCms.contentTypeName || "an unnamed content type") + " (RFC 5272 sec. 3.2 / 4.2)");
766
+ }
767
+ var encap = parsedCms.encapContentInfo;
768
+ if (!encap) throw E("cmc/not-cmc", "a CMC message is carried in a CMS SignedData or AuthenticatedData");
769
+ var eContentType = encap.eContentType;
770
+ if (eContentType !== OID_PKI_DATA && eContentType !== OID_PKI_RESPONSE) {
771
+ throw E("cmc/not-cmc",
772
+ "the encapsulated content type is " + (oid.name(eContentType) || eContentType) +
773
+ ", not id-cct-PKIData or id-cct-PKIResponse (RFC 5272 sec. 3.2 / 4.2)");
774
+ }
775
+ if (encap.eContent == null) {
776
+ throw E("cmc/no-content",
777
+ "a Full PKI Request / Response carries its body as the encapsulated content; this SignedData is detached (RFC 5272 sec. 3.2)");
778
+ }
779
+ var body = eContentType === OID_PKI_DATA ? parsePkiData(encap.eContent) : parsePkiResponse(encap.eContent);
780
+ body.cms = parsedCms;
781
+ body.eContentType = eContentType;
782
+ return body;
783
+ }
784
+
785
+ module.exports = {
786
+ parse: parse,
787
+ parsePkiData: parsePkiData,
788
+ parsePkiResponse: parsePkiResponse,
789
+ STATUS_BY_VALUE: STATUS_BY_VALUE,
790
+ FAIL_INFO_BY_VALUE: FAIL_INFO_BY_VALUE,
791
+ };