@blamejs/pki 0.1.23 → 0.1.25
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.
- package/CHANGELOG.md +28 -1
- package/README.md +5 -1
- package/index.js +18 -0
- package/lib/acme.js +1159 -0
- package/lib/constants.js +10 -0
- package/lib/est.js +717 -0
- package/lib/framework-error.js +35 -0
- package/lib/jose.js +616 -0
- package/lib/oid.js +8 -2
- package/lib/schema-all.js +16 -0
- package/lib/schema-csr.js +17 -3
- package/lib/schema-csrattrs.js +371 -0
- package/lib/schema-pkix.js +45 -5
- package/lib/webcrypto.js +5 -4
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/oid.js
CHANGED
|
@@ -74,7 +74,8 @@ var FAMILIES = {
|
|
|
74
74
|
// RFC 5755 attribute-certificate id-pe extensions: ac-auditIdentity sec. 4.3.1,
|
|
75
75
|
// aaControls sec. 7.4, ac-proxying sec. 7.2).
|
|
76
76
|
pkixAccess: { base: [1, 3, 6, 1, 5, 5, 7, 1], of: {
|
|
77
|
-
authorityInfoAccess: 1, acAuditIdentity: 4, aaControls: 6, acProxying: 10
|
|
77
|
+
authorityInfoAccess: 1, acAuditIdentity: 4, aaControls: 6, acProxying: 10,
|
|
78
|
+
acmeIdentifier: 31 } },
|
|
78
79
|
|
|
79
80
|
// id-aca -- RFC 5755 attribute-certificate attribute types on the id-pkix 10 arc:
|
|
80
81
|
// authenticationInfo sec. 4.4.1 .. group sec. 4.4.4, plus encAttrs sec. 7.1 (the encrypted-
|
|
@@ -211,10 +212,15 @@ var FAMILIES = {
|
|
|
211
212
|
// signingCertificateV2 (ESSCertIDv2) carries a non-SHA-1 cert hash (RFC 5816 sec. 2.2.1).
|
|
212
213
|
// The RFC 2634 originals (receiptRequest .. contentReference) are name-only: they
|
|
213
214
|
// resolve to a name so an unsupported-attribute diagnostic is legible, not decoded.
|
|
215
|
+
// decryptKeyID / asymmDecryptKeyID name the out-of-band key-encryption key a
|
|
216
|
+
// /serverkeygen client references (RFC 4108 sec. 2.2.5 / RFC 7030 sec. 4.4.1);
|
|
217
|
+
// certificationRequestInfoTemplate / extensionReqTemplate are the RFC 9908
|
|
218
|
+
// CSR-attributes template carriers.
|
|
214
219
|
smimeAa: { base: [1, 2, 840, 113549, 1, 9, 16, 2], of: {
|
|
215
220
|
receiptRequest: 1, eSSSecurityLabel: 2, mlExpansionHistory: 3, contentHints: 4,
|
|
216
221
|
msgSigDigest: 5, contentIdentifier: 7, equivalentLabels: 9, contentReference: 10,
|
|
217
|
-
signingCertificate: 12, timeStampToken: 14, signingCertificateV2: 47
|
|
222
|
+
signingCertificate: 12, timeStampToken: 14, decryptKeyID: 37, signingCertificateV2: 47,
|
|
223
|
+
asymmDecryptKeyID: 54, certificationRequestInfoTemplate: 61, extensionReqTemplate: 62 } },
|
|
218
224
|
|
|
219
225
|
// ANSI X9.62 EC public key, named curve, and ECDSA signatures.
|
|
220
226
|
ansiX962: { base: [1, 2, 840, 10045], of: {
|
package/lib/schema-all.js
CHANGED
|
@@ -42,6 +42,7 @@ var ocsp = require("./schema-ocsp");
|
|
|
42
42
|
var tsp = require("./schema-tsp");
|
|
43
43
|
var crmf = require("./schema-crmf");
|
|
44
44
|
var cmp = require("./schema-cmp");
|
|
45
|
+
var csrattrs = require("./schema-csrattrs");
|
|
45
46
|
var attrcert = require("./schema-attrcert");
|
|
46
47
|
// smime is a COMPANION decoder for CMS signed-attribute values, NOT a top-level
|
|
47
48
|
// format -- it has no self-describing DER root (an ESS attribute value is a bare
|
|
@@ -122,6 +123,20 @@ var FORMATS = [
|
|
|
122
123
|
detect: cmp.matches,
|
|
123
124
|
parse: function (input) { return cmp.parse(input); },
|
|
124
125
|
},
|
|
126
|
+
{
|
|
127
|
+
// CsrAttrs ::= SEQUENCE SIZE (0..MAX) OF AttrOrOID (RFC 8951 sec. 3.5) -- a
|
|
128
|
+
// SEQUENCE whose every child is a bare universal OID or an Attribute (a
|
|
129
|
+
// universal SEQUENCE of exactly 2: OID + SET). A 1-2-element all-Attribute
|
|
130
|
+
// CsrAttrs satisfies the permissive ocsp-request probe below (SEQUENCE +
|
|
131
|
+
// constructed first child), and the empty 30 00 is a CsrAttrs no other root
|
|
132
|
+
// accepts -- so csrattrs is the strict refinement and MUST sit ahead of
|
|
133
|
+
// ocsp-request, the same resolution tsp / cmp use. Disjoint from cmp (whose
|
|
134
|
+
// body child is context-[0..26], never a universal OID/Attribute).
|
|
135
|
+
name: "csrattrs",
|
|
136
|
+
module: csrattrs,
|
|
137
|
+
detect: csrattrs.matches,
|
|
138
|
+
parse: function (input) { return csrattrs.parse(input); },
|
|
139
|
+
},
|
|
125
140
|
{
|
|
126
141
|
// OCSPRequest ::= SEQUENCE { tbsRequest SEQUENCE, optionalSignature [0] EXPLICIT
|
|
127
142
|
// OPTIONAL } -- a SEQUENCE of 1-2 whose first child is the tbsRequest SEQUENCE.
|
|
@@ -289,6 +304,7 @@ module.exports = {
|
|
|
289
304
|
tsp: { parse: tsp.parse, parseTstInfo: tsp.parseTstInfo, parseToken: tsp.parseToken, pemDecode: tsp.pemDecode, pemEncode: tsp.pemEncode },
|
|
290
305
|
crmf: { parse: crmf.parse, pemDecode: crmf.pemDecode, pemEncode: crmf.pemEncode },
|
|
291
306
|
cmp: { parse: cmp.parse, pemDecode: cmp.pemDecode, pemEncode: cmp.pemEncode },
|
|
307
|
+
csrattrs: { parse: csrattrs.parse },
|
|
292
308
|
attrcert: { parse: attrcert.parse, pemDecode: attrcert.pemDecode, pemEncode: attrcert.pemEncode },
|
|
293
309
|
smime: { parseSigningCertificate: smime.parseSigningCertificate, parseSigningCertificateV2: smime.parseSigningCertificateV2, parseSmimeCapabilities: smime.parseSmimeCapabilities, decodeAttribute: smime.decodeAttribute },
|
|
294
310
|
all: all,
|
package/lib/schema-csr.js
CHANGED
|
@@ -75,7 +75,11 @@ var DIRECTORY_STRING_TAGS = [
|
|
|
75
75
|
];
|
|
76
76
|
var RECOGNIZED_ATTRIBUTE_VALUE = {};
|
|
77
77
|
RECOGNIZED_ATTRIBUTE_VALUE[oid.byName("extensionRequest")] = function (node, ctx) {
|
|
78
|
-
|
|
78
|
+
// Surface the requested extensions decoded (same {oid,name,critical,value}
|
|
79
|
+
// shape as a certificate's extensions) so a CA pipeline -- and the EST
|
|
80
|
+
// re-enroll guard -- reads them off the attribute instead of re-walking the
|
|
81
|
+
// raw Extensions bytes. Returned as an enrichment merged onto the attribute.
|
|
82
|
+
try { return { extensions: schema.walk(EXTENSIONS, node, ctx).result }; }
|
|
79
83
|
catch (e) {
|
|
80
84
|
throw ctx.E("csr/bad-attribute-value",
|
|
81
85
|
"extensionRequest value must be a well-formed Extensions SEQUENCE (RFC 2985 sec. 5.4.2): " + ((e && e.message) || String(e)), e);
|
|
@@ -115,7 +119,8 @@ var CERTIFICATION_REQUEST_INFO = schema.seq([
|
|
|
115
119
|
if (valueItems.length !== 1) {
|
|
116
120
|
throw ctx.E("csr/bad-attribute-value", (a.name || a.type) + " is a SINGLE VALUE attribute (RFC 2985 sec. 5.4)");
|
|
117
121
|
}
|
|
118
|
-
checkValue(valueItems[0].node, ctx);
|
|
122
|
+
var enriched = checkValue(valueItems[0].node, ctx);
|
|
123
|
+
if (enriched) { Object.keys(enriched).forEach(function (k) { a[k] = enriched[k]; }); }
|
|
119
124
|
}
|
|
120
125
|
return a;
|
|
121
126
|
});
|
|
@@ -165,7 +170,10 @@ var CERTIFICATION_REQUEST = pkix.signedEnvelope(NS, CERTIFICATION_REQUEST_INFO,
|
|
|
165
170
|
* Every field is validated on the way in; a malformed CertificationRequest /
|
|
166
171
|
* CertificationRequestInfo throws a typed `CsrError` (`csr/*`) and a leaf-level
|
|
167
172
|
* codec fault surfaces as `asn1/*`. Attribute values are returned as raw DER
|
|
168
|
-
* buffers so an unrecognized attribute type never fails the parse
|
|
173
|
+
* buffers so an unrecognized attribute type never fails the parse; the
|
|
174
|
+
* `extensionRequest` attribute additionally carries its requested extensions
|
|
175
|
+
* decoded on `.extensions` (the `{ oid, name, critical, value }` shape a
|
|
176
|
+
* certificate's extensions use).
|
|
169
177
|
*
|
|
170
178
|
* @example
|
|
171
179
|
* var csr = pki.schema.csr.parse(der);
|
|
@@ -231,4 +239,10 @@ module.exports = {
|
|
|
231
239
|
pemDecode: pemDecode,
|
|
232
240
|
pemEncode: pemEncode,
|
|
233
241
|
matches: matches,
|
|
242
|
+
// The top schema, exposed (not on the curated pki.schema.csr surface) so a
|
|
243
|
+
// composer can drive the encode direction -- schema.encode(schema, v) -> a
|
|
244
|
+
// CertificationRequest -- and prove the round-trip. The EST builder uses it
|
|
245
|
+
// to re-encode a CSR with added attributes (the crmf certReqMessagesSchema
|
|
246
|
+
// precedent).
|
|
247
|
+
certificationRequestSchema: CERTIFICATION_REQUEST,
|
|
234
248
|
};
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* @module pki.schema.csrattrs
|
|
6
|
+
* @nav Schema
|
|
7
|
+
* @title CSR Attributes
|
|
8
|
+
* @order 185
|
|
9
|
+
* @slug csrattrs
|
|
10
|
+
*
|
|
11
|
+
* @intro
|
|
12
|
+
* The EST CSR Attributes wire format per RFC 8951 sec. 3.5 (the RFC 7030
|
|
13
|
+
* sec. 4.5.2 ASN.1 was syntactically broken, erratum 4384) with the RFC 9908
|
|
14
|
+
* template structures. `parse` turns a DER `Buffer` into
|
|
15
|
+
* `{ items }` -- `CsrAttrs ::= SEQUENCE SIZE (0..MAX) OF AttrOrOID`, where
|
|
16
|
+
* `AttrOrOID ::= CHOICE { oid OBJECT IDENTIFIER, attribute Attribute }`
|
|
17
|
+
* disambiguates on the universal tag. An empty SEQUENCE is a COMPLETE valid
|
|
18
|
+
* document ("no additional information desired"). Each item surfaces its
|
|
19
|
+
* `kind` (`"oid"` / `"attribute"`), the `oid` (dotted) and registry `name`;
|
|
20
|
+
* an attribute keeps its raw `values` and, for the three types RFC 9908
|
|
21
|
+
* gives meaning, a decoded view: `id-ExtensionReq` -> `extensions`, the
|
|
22
|
+
* `ecPublicKey` / `rsaEncryption` key-type conventions -> `curve` / `keySize`,
|
|
23
|
+
* and `id-aa-certificationRequestInfoTemplate` -> a fully-decoded `template`.
|
|
24
|
+
*
|
|
25
|
+
* Structure is strict-DER throughout, but UNKNOWN OIDs and attribute types
|
|
26
|
+
* are TOLERATED -- surfaced raw, never a parse fault ("the client MUST ignore
|
|
27
|
+
* any OID or attribute it does not recognize", RFC 8951 sec. 4.5.2). The
|
|
28
|
+
* RFC 9908 semantic MUSTs that ARE enforced fail closed with a typed
|
|
29
|
+
* `csrattrs/*` code: at most one `id-ExtensionReq` attribute, its values a SET
|
|
30
|
+
* of exactly one `Extensions`, template version v1(0), and a template's inner
|
|
31
|
+
* attributes carrying at most one `id-aa-extensionReqTemplate` and never both
|
|
32
|
+
* extension-request kinds. Template PRIORITY (a client that understands the
|
|
33
|
+
* template ignores the legacy elements, RFC 9908 sec. 4) is a `pki.est` builder
|
|
34
|
+
* rule, not a parse rejection -- parse surfaces everything.
|
|
35
|
+
*
|
|
36
|
+
* There is NO `pemDecode` / `pemEncode`: no RFC 7468 label exists for
|
|
37
|
+
* CsrAttrs; the wire encoding is bare RFC 4648 base64 handled by the EST
|
|
38
|
+
* transfer codec (`pki.est.transferDecode`), not a PEM envelope.
|
|
39
|
+
*
|
|
40
|
+
* @card
|
|
41
|
+
* Parse DER EST CSR Attributes (RFC 8951 / RFC 9908) into ordered items --
|
|
42
|
+
* bare OIDs, attributes with raw values, decoded extension requests / key-type
|
|
43
|
+
* conventions / request-info templates; unknown types tolerated, fail-closed.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
var asn1 = require("./asn1-der");
|
|
47
|
+
var schema = require("./schema-engine");
|
|
48
|
+
var pkix = require("./schema-pkix");
|
|
49
|
+
var oid = require("./oid");
|
|
50
|
+
var frameworkError = require("./framework-error");
|
|
51
|
+
|
|
52
|
+
var CsrattrsError = frameworkError.CsrattrsError;
|
|
53
|
+
|
|
54
|
+
var NS = pkix.makeNS("csrattrs", CsrattrsError, oid);
|
|
55
|
+
var T = asn1.TAGS;
|
|
56
|
+
|
|
57
|
+
// The base Attribute is SET SIZE(1..MAX); ATTRIBUTE_KEYTYPE lowers the floor to 0
|
|
58
|
+
// ONLY for the ecPublicKey / rsaEncryption key-type hints, whose empty values SET
|
|
59
|
+
// means "any key of this type" (RFC 9908 sec. 3.2). Every other attribute -- and
|
|
60
|
+
// every template inner attribute -- keeps the non-empty floor, so an empty
|
|
61
|
+
// challengePassword or unknown attribute still fails closed.
|
|
62
|
+
// ATTRIBUTE keeps the base SET SIZE(1..MAX); ATTRIBUTE_OPTIONAL_VALUES lowers the
|
|
63
|
+
// floor to 0. Only the recognized NON-KEY attributes that require a value
|
|
64
|
+
// (id-ExtensionReq, the request-info template) use the strict form; a key-type
|
|
65
|
+
// hint's empty SET means "any key of this type" (RFC 9908 sec. 3.2), and an
|
|
66
|
+
// UNKNOWN attribute (a key type this build does not know yet, or any other OID) is
|
|
67
|
+
// TOLERATED with an empty SET too -- RFC 8951 ignores what it does not recognize,
|
|
68
|
+
// so an unfamiliar OID must not fail the whole response.
|
|
69
|
+
var ATTRIBUTE = pkix.attribute(NS);
|
|
70
|
+
var ATTRIBUTE_OPTIONAL_VALUES = pkix.attribute(NS, { minValues: 0 });
|
|
71
|
+
var EXTENSIONS = pkix.extensions(NS);
|
|
72
|
+
var ALGORITHM_IDENTIFIER = pkix.algorithmIdentifier(NS);
|
|
73
|
+
|
|
74
|
+
// The RFC 9908 semantically-meaningful attribute OIDs (registry, never literals).
|
|
75
|
+
var OID_EXTENSION_REQUEST = oid.byName("extensionRequest"); // id-ExtensionReq
|
|
76
|
+
var OID_EC_PUBLIC_KEY = oid.byName("ecPublicKey");
|
|
77
|
+
var OID_RSA_ENCRYPTION = oid.byName("rsaEncryption");
|
|
78
|
+
var OID_TEMPLATE = oid.byName("certificationRequestInfoTemplate");
|
|
79
|
+
var OID_EXT_REQ_TEMPLATE = oid.byName("extensionReqTemplate");
|
|
80
|
+
|
|
81
|
+
// The recognized non-key attributes that MUST carry a value (empty SET rejected).
|
|
82
|
+
var STRICT_VALUES_OIDS = new Set([OID_EXTENSION_REQUEST, OID_TEMPLATE]);
|
|
83
|
+
|
|
84
|
+
// The recognized public-key type OIDs -- used to FLAG a parsed attribute isKeyType
|
|
85
|
+
// for the enroll planner (rsaEncryption / ecPublicKey are only the RFC 9908 sec. 3.2
|
|
86
|
+
// examples; a no-parameter key like Ed25519 or an ML-DSA / SLH-DSA / ML-KEM type is
|
|
87
|
+
// equally valid). Built by registered NAME (a typo fails at load), never literals.
|
|
88
|
+
var KEY_TYPE_OIDS = new Set([
|
|
89
|
+
"rsaEncryption", "ecPublicKey", "Ed25519", "Ed448", "X25519", "X448",
|
|
90
|
+
"id-ml-dsa-44", "id-ml-dsa-65", "id-ml-dsa-87",
|
|
91
|
+
"id-ml-kem-512", "id-ml-kem-768", "id-ml-kem-1024",
|
|
92
|
+
"id-slh-dsa-sha2-128s", "id-slh-dsa-sha2-128f", "id-slh-dsa-sha2-192s",
|
|
93
|
+
"id-slh-dsa-sha2-192f", "id-slh-dsa-sha2-256s", "id-slh-dsa-sha2-256f",
|
|
94
|
+
"id-slh-dsa-shake-128s", "id-slh-dsa-shake-128f", "id-slh-dsa-shake-192s",
|
|
95
|
+
"id-slh-dsa-shake-192f", "id-slh-dsa-shake-256s", "id-slh-dsa-shake-256f",
|
|
96
|
+
].map(function (nm) {
|
|
97
|
+
var d = oid.byName(nm);
|
|
98
|
+
if (typeof d !== "string") throw new Error("csrattrs key-type seed " + JSON.stringify(nm) + " is not a registered OID name");
|
|
99
|
+
return d;
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
// ---- RFC 9908 sec. 3.4 CertificationRequestInfoTemplate ------------------
|
|
103
|
+
|
|
104
|
+
// version MUST be v1(0). A dedicated leaf (not pkix.versionReader) so the reject
|
|
105
|
+
// carries the template-specific code the RFC 9908 sec. 3.4 MUST names.
|
|
106
|
+
var TEMPLATE_VERSION = schema.decode(function (n, ctx) {
|
|
107
|
+
var v = asn1.read.integer(n);
|
|
108
|
+
if (v !== 0n) throw ctx.E("csrattrs/bad-template-version", "CertificationRequestInfoTemplate version must be v1(0) (RFC 9908 sec. 3.4)");
|
|
109
|
+
return 0;
|
|
110
|
+
}, function () { return asn1.build.integer(0n); });
|
|
111
|
+
|
|
112
|
+
// SingleAttributeTemplate ::= SEQUENCE { type OID, value ANY OPTIONAL } -- a
|
|
113
|
+
// value-absent element means "the client fills this in" (RFC 9908 sec. 3.4). The
|
|
114
|
+
// trailing OPTIONAL ANY is the algorithmIdentifier-parameters shape.
|
|
115
|
+
var SINGLE_ATTR_TEMPLATE = schema.seq([
|
|
116
|
+
schema.field("type", schema.oidLeaf()),
|
|
117
|
+
schema.optional("value", schema.any(), { whenAny: true }),
|
|
118
|
+
], {
|
|
119
|
+
assert: "sequence", arity: { min: 1, max: 2 }, code: "csrattrs/bad-name-template", what: "SingleAttributeTemplate",
|
|
120
|
+
build: function (m, ctx) {
|
|
121
|
+
return { type: m.fields.type.value, name: ctx.oid.name(m.fields.type.value) || null,
|
|
122
|
+
value: m.fields.value.present ? m.fields.value.node.bytes : null };
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// RelativeDistinguishedNameTemplate ::= SET SIZE (1..MAX) OF SingleAttributeTemplate.
|
|
127
|
+
var RDN_TEMPLATE = schema.setOf(SINGLE_ATTR_TEMPLATE, {
|
|
128
|
+
assert: "set", min: 1, code: "csrattrs/bad-name-template", what: "RelativeDistinguishedNameTemplate",
|
|
129
|
+
build: function (m) { return m.items.map(function (it) { return it.value.result; }); },
|
|
130
|
+
});
|
|
131
|
+
// NameTemplate ::= RDNSequence (a SEQUENCE OF RDN-template SETs) -> array of RDNs,
|
|
132
|
+
// each an array of { type, name, value } (value null = "the client fills it in").
|
|
133
|
+
var NAME_TEMPLATE = schema.seqOf(RDN_TEMPLATE, {
|
|
134
|
+
assert: "sequence", code: "csrattrs/bad-name-template", what: "NameTemplate",
|
|
135
|
+
build: function (m) { return m.items.map(function (it) { return it.value.result; }); },
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// SubjectPublicKeyInfoTemplate ::= SEQUENCE { algorithm AlgorithmIdentifier,
|
|
139
|
+
// subjectPublicKey BIT STRING OPTIONAL } -- the key is OPTIONAL (present ONLY
|
|
140
|
+
// as an RSA modulus-size placeholder), so pkix.spki (key required, arity 2)
|
|
141
|
+
// cannot be reused.
|
|
142
|
+
var SPKI_TEMPLATE = schema.seq([
|
|
143
|
+
schema.field("algorithm", ALGORITHM_IDENTIFIER),
|
|
144
|
+
schema.optional("placeholderKey", schema.bitString(), { whenUniversal: [T.BIT_STRING] }),
|
|
145
|
+
], {
|
|
146
|
+
// subjectPKInfo is [0] IMPLICIT (RFC 9908 sec. 3.4) -- the context tag replaces
|
|
147
|
+
// the universal SEQUENCE tag, so the node is context-[0] constructed and its
|
|
148
|
+
// children are read directly (the pwri keyDerivationAlgorithm [0] precedent).
|
|
149
|
+
assert: "implicit", implicitTag: 0, arity: { min: 1, max: 2 }, code: "csrattrs/bad-spki-template", what: "SubjectPublicKeyInfoTemplate",
|
|
150
|
+
build: function (m) {
|
|
151
|
+
return { algorithm: m.fields.algorithm.value.result,
|
|
152
|
+
placeholderKey: m.fields.placeholderKey.present ? m.fields.placeholderKey.value.bytes : null };
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// ExtensionTemplate ::= SEQUENCE { extnID OID, critical BOOLEAN DEFAULT FALSE,
|
|
157
|
+
// extnValue OCTET STRING OPTIONAL } -- extnValue absent means "client supplies";
|
|
158
|
+
// an explicit critical FALSE is non-DER (BOOLEAN DEFAULT FALSE). Not reusable
|
|
159
|
+
// from pkix.extension (whose extnValue is required), so the DER-critical rule
|
|
160
|
+
// is applied here directly.
|
|
161
|
+
var EXTENSION_TEMPLATE = schema.decode(function (n, ctx) {
|
|
162
|
+
if (!schema.isUniversal(n, T.SEQUENCE) || !n.children || n.children.length < 1 || n.children.length > 3) {
|
|
163
|
+
throw ctx.E("csrattrs/bad-extension-template", "ExtensionTemplate must be a SEQUENCE of {extnID, critical?, extnValue?}");
|
|
164
|
+
}
|
|
165
|
+
var k = n.children, i = 1;
|
|
166
|
+
var extnID = asn1.read.oid(k[0]);
|
|
167
|
+
var critical = false, value = null;
|
|
168
|
+
if (i < k.length && schema.isUniversal(k[i], T.BOOLEAN)) {
|
|
169
|
+
critical = asn1.read.boolean(k[i]);
|
|
170
|
+
if (critical === false) throw ctx.E("csrattrs/bad-extension-template", "an explicit critical FALSE must be omitted (BOOLEAN DEFAULT FALSE)");
|
|
171
|
+
i += 1;
|
|
172
|
+
}
|
|
173
|
+
if (i < k.length) { value = asn1.read.octetString(k[i]); i += 1; }
|
|
174
|
+
if (i !== k.length) throw ctx.E("csrattrs/bad-extension-template", "ExtensionTemplate has an unexpected trailing element");
|
|
175
|
+
return { oid: extnID, name: ctx.oid.name(extnID) || null, critical: critical, value: value };
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// ExtensionReqTemplate ::= ExtensionTemplates ::= SEQUENCE SIZE (1..MAX) OF
|
|
179
|
+
// ExtensionTemplate (RFC 9908 sec. 3.4) -- the id-aa-extensionReqTemplate value
|
|
180
|
+
// is a SEQUENCE OF ExtensionTemplate, not a single one.
|
|
181
|
+
var EXTENSION_REQ_TEMPLATE = schema.seqOf(EXTENSION_TEMPLATE, {
|
|
182
|
+
assert: "sequence", min: 1, code: "csrattrs/bad-extension-template", what: "ExtensionReqTemplate",
|
|
183
|
+
build: function (m) { return m.items.map(function (it) { return it.value; }); },
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// CertificationRequestInfoTemplate ::= SEQUENCE { version, subject NameTemplate
|
|
187
|
+
// OPTIONAL, subjectPKInfo [0] IMPLICIT OPTIONAL, attributes [1] IMPLICIT
|
|
188
|
+
// Attributes }. attributes is present (may be an empty SET). The RFC 9908
|
|
189
|
+
// sec. 3.4 inner-attribute MUSTs (<=1 extensionReqTemplate; not both
|
|
190
|
+
// extension-request kinds; a values SET of exactly one ExtensionTemplate) are
|
|
191
|
+
// enforced in the build.
|
|
192
|
+
var CERT_REQ_INFO_TEMPLATE = schema.seq([
|
|
193
|
+
schema.field("version", TEMPLATE_VERSION),
|
|
194
|
+
schema.optional("subject", NAME_TEMPLATE, { whenUniversal: [T.SEQUENCE] }),
|
|
195
|
+
schema.optional("subjectPKInfo", SPKI_TEMPLATE, { tag: 0 }),
|
|
196
|
+
schema.field("attributes", schema.implicitSetOf(1, ATTRIBUTE, { min: 0, code: "csrattrs/bad-template-attrs", what: "template attributes" })),
|
|
197
|
+
], {
|
|
198
|
+
assert: "sequence", code: "csrattrs/bad-template", what: "CertificationRequestInfoTemplate",
|
|
199
|
+
build: function (m, ctx) {
|
|
200
|
+
var attributes = m.fields.attributes.value.items.map(function (it) { return it.value.result; });
|
|
201
|
+
var extReqTemplates = 0, extReqs = 0, hasExtReq = false, extensionTemplates = [], extensions = null;
|
|
202
|
+
for (var i = 0; i < attributes.length; i++) {
|
|
203
|
+
var a = attributes[i];
|
|
204
|
+
if (a.type === OID_EXTENSION_REQUEST) {
|
|
205
|
+
hasExtReq = true;
|
|
206
|
+
extReqs += 1;
|
|
207
|
+
// A template id-ExtensionReq carries one Extensions value (RFC 9908 sec. 3.2);
|
|
208
|
+
// decode it so a client reads the requirements off .extensions instead of
|
|
209
|
+
// re-parsing raw bytes (the top-level extensionRequest exposes it the same way).
|
|
210
|
+
if (a.values.length !== 1) throw NS.E("csrattrs/bad-template-attrs", "a template id-ExtensionReq values must be a SET of exactly one Extensions (RFC 9908 sec. 3.2)");
|
|
211
|
+
extensions = schema.walk(EXTENSIONS, asn1.decode(a.values[0]), ctx).result;
|
|
212
|
+
}
|
|
213
|
+
if (a.type === OID_EXT_REQ_TEMPLATE) {
|
|
214
|
+
extReqTemplates += 1;
|
|
215
|
+
// The attribute's values SET holds exactly one ExtensionReqTemplate
|
|
216
|
+
// (RFC 9908 sec. 3.4); that value is itself a SEQUENCE OF ExtensionTemplate.
|
|
217
|
+
if (a.values.length !== 1) throw NS.E("csrattrs/bad-template-attrs", "an id-aa-extensionReqTemplate values must be a SET of exactly one ExtensionReqTemplate (RFC 9908 sec. 3.4)");
|
|
218
|
+
extensionTemplates = extensionTemplates.concat(schema.walk(EXTENSION_REQ_TEMPLATE, asn1.decode(a.values[0]), ctx).result);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (extReqs > 1) throw NS.E("csrattrs/bad-template-attrs", "a template must not carry more than one id-ExtensionReq (RFC 9908 sec. 3.2)");
|
|
222
|
+
if (extReqTemplates > 1) throw NS.E("csrattrs/bad-template-attrs", "a template must not carry more than one id-aa-extensionReqTemplate (RFC 9908 sec. 3.4)");
|
|
223
|
+
if (extReqTemplates >= 1 && hasExtReq) throw NS.E("csrattrs/bad-template-attrs", "a template must not carry both id-ExtensionReq and id-aa-extensionReqTemplate (RFC 9908 sec. 3.4)");
|
|
224
|
+
return {
|
|
225
|
+
version: m.fields.version.value,
|
|
226
|
+
subject: m.fields.subject.present ? m.fields.subject.value.result : null,
|
|
227
|
+
subjectPKInfo: m.fields.subjectPKInfo.present ? m.fields.subjectPKInfo.value.result : null,
|
|
228
|
+
attributes: attributes,
|
|
229
|
+
extensions: extensions,
|
|
230
|
+
extensionTemplates: extensionTemplates,
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ---- AttrOrOID + the semantic enrichment ---------------------------------
|
|
236
|
+
|
|
237
|
+
// Decode the three RFC 9908-meaningful attribute types onto an already-walked
|
|
238
|
+
// Attribute { type, name, values }. Unknown types keep only their raw values.
|
|
239
|
+
// A recognized public-key type is flagged isKeyType so a consumer (the pki.est
|
|
240
|
+
// enroll planner) treats every accepted key type uniformly, not just RSA/EC.
|
|
241
|
+
function _enrichAttribute(item, ctx) {
|
|
242
|
+
if (KEY_TYPE_OIDS.has(item.oid)) item.isKeyType = true;
|
|
243
|
+
if (item.oid === OID_EXTENSION_REQUEST) {
|
|
244
|
+
if (item.values.length !== 1) throw ctx.E("csrattrs/bad-extension-req", "an id-ExtensionReq attribute values must be a SET of exactly one Extensions (RFC 9908 sec. 3.2)");
|
|
245
|
+
item.extensions = schema.walk(EXTENSIONS, asn1.decode(item.values[0]), ctx).result;
|
|
246
|
+
} else if (item.oid === OID_EC_PUBLIC_KEY) {
|
|
247
|
+
// A key-type hint's values SET is empty ("any curve") or a singleton naming
|
|
248
|
+
// ONE curve (RFC 9908 sec. 3.2); a multi-valued SET is ambiguous -- fail
|
|
249
|
+
// closed rather than letting DER ordering pick the advertised constraint.
|
|
250
|
+
if (item.values.length > 1) throw ctx.E("csrattrs/bad-key-type-attr", "an ecPublicKey attribute values must be empty or a SET of exactly one named-curve OBJECT IDENTIFIER (RFC 9908 sec. 3.2)");
|
|
251
|
+
if (item.values.length === 1) {
|
|
252
|
+
try { item.curve = asn1.read.oid(asn1.decode(item.values[0])); }
|
|
253
|
+
catch (e) { throw ctx.E("csrattrs/bad-key-type-attr", "an ecPublicKey attribute value must be a named-curve OBJECT IDENTIFIER (RFC 9908 sec. 3.2)", e); }
|
|
254
|
+
}
|
|
255
|
+
} else if (item.oid === OID_RSA_ENCRYPTION) {
|
|
256
|
+
// Empty ("any size") or a singleton naming ONE key size; reject a multi-valued SET.
|
|
257
|
+
if (item.values.length > 1) throw ctx.E("csrattrs/bad-key-type-attr", "an rsaEncryption attribute values must be empty or a SET of exactly one INTEGER key size (RFC 9908 sec. 3.2)");
|
|
258
|
+
if (item.values.length === 1) {
|
|
259
|
+
var sz;
|
|
260
|
+
try { sz = asn1.read.integer(asn1.decode(item.values[0])); }
|
|
261
|
+
catch (e) { throw ctx.E("csrattrs/bad-key-type-attr", "an rsaEncryption attribute value must be an INTEGER key size (RFC 9908 sec. 3.2)", e); }
|
|
262
|
+
// Bound before narrowing to a Number: an RSA modulus-size hint of 1..65536
|
|
263
|
+
// bits covers every real key; a value past 2^53 would round silently.
|
|
264
|
+
if (sz < 1n || sz > 65536n) throw ctx.E("csrattrs/bad-key-type-attr", "the rsaEncryption key size " + sz + " is out of range (1..65536 bits)");
|
|
265
|
+
item.keySize = Number(sz);
|
|
266
|
+
}
|
|
267
|
+
} else if (item.oid === OID_TEMPLATE) {
|
|
268
|
+
if (item.values.length !== 1) throw ctx.E("csrattrs/bad-template", "a certificationRequestInfoTemplate attribute values must be a SET of exactly one template (RFC 9908 sec. 3.4)");
|
|
269
|
+
item.template = schema.walk(CERT_REQ_INFO_TEMPLATE, asn1.decode(item.values[0]), ctx).result;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// AttrOrOID ::= CHOICE { oid OBJECT IDENTIFIER, attribute Attribute }. The two
|
|
274
|
+
// arms are the two universal tags; anything else is not an AttrOrOID. A decode
|
|
275
|
+
// leaf (not schema.choice) so the enrichment + the paired write live together.
|
|
276
|
+
var ATTR_OR_OID = schema.decode(function (n, ctx) {
|
|
277
|
+
if (schema.isUniversal(n, T.OBJECT_IDENTIFIER) && !n.constructed) {
|
|
278
|
+
var o = asn1.read.oid(n);
|
|
279
|
+
return { kind: "oid", oid: o, name: ctx.oid.name(o) || null };
|
|
280
|
+
}
|
|
281
|
+
if (schema.isUniversal(n, T.SEQUENCE) && n.children) {
|
|
282
|
+
// Peek the type OID to choose the values-arity rule: only a recognized non-key
|
|
283
|
+
// attribute (id-ExtensionReq / template) requires SET SIZE(1..MAX); a key-type
|
|
284
|
+
// hint OR an unknown OID MAY carry an empty SET (RFC 9908 sec. 3.2 / RFC 8951).
|
|
285
|
+
var typeOid = (n.children.length >= 1 && schema.isUniversal(n.children[0], T.OBJECT_IDENTIFIER) && !n.children[0].constructed) ? asn1.read.oid(n.children[0]) : null;
|
|
286
|
+
var attrSchema = STRICT_VALUES_OIDS.has(typeOid) ? ATTRIBUTE : ATTRIBUTE_OPTIONAL_VALUES;
|
|
287
|
+
var a = schema.walk(attrSchema, n, ctx).result; // { type, name, values:[bytes] }
|
|
288
|
+
var item = { kind: "attribute", oid: a.type, name: a.name, values: a.values };
|
|
289
|
+
_enrichAttribute(item, ctx);
|
|
290
|
+
return item;
|
|
291
|
+
}
|
|
292
|
+
throw ctx.E("csrattrs/bad-attr-or-oid", "each CsrAttrs element must be an OBJECT IDENTIFIER or an Attribute SEQUENCE (RFC 8951 sec. 3.5)");
|
|
293
|
+
}, function (item) {
|
|
294
|
+
// Paired encoder (one structure, both directions): reconstruct from the arm's
|
|
295
|
+
// identifying fields + raw values -- the typed enrichment is decode-only.
|
|
296
|
+
if (item.kind === "oid") return asn1.build.oid(item.oid);
|
|
297
|
+
return asn1.build.sequence([asn1.build.oid(item.oid), asn1.build.set(item.values)]);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// CsrAttrs ::= SEQUENCE SIZE (0..MAX) OF AttrOrOID. min:0 -- an empty SEQUENCE is
|
|
301
|
+
// a complete valid document. The cross-element MUSTs (RFC 9908 sec. 3.2 single
|
|
302
|
+
// id-ExtensionReq; sec. 3.4 single template) are enforced in the build.
|
|
303
|
+
var CSR_ATTRS = schema.seqOf(ATTR_OR_OID, {
|
|
304
|
+
assert: "sequence", code: "csrattrs/not-csrattrs", what: "CsrAttrs",
|
|
305
|
+
build: function (m) {
|
|
306
|
+
var items = m.items.map(function (it) { return it.value; });
|
|
307
|
+
var extReq = 0, tpl = 0;
|
|
308
|
+
for (var i = 0; i < items.length; i++) {
|
|
309
|
+
if (items[i].kind !== "attribute") continue;
|
|
310
|
+
if (items[i].oid === OID_EXTENSION_REQUEST) extReq += 1;
|
|
311
|
+
if (items[i].oid === OID_TEMPLATE) tpl += 1;
|
|
312
|
+
}
|
|
313
|
+
if (extReq > 1) throw NS.E("csrattrs/duplicate-extension-req", "at most one id-ExtensionReq attribute is permitted in CsrAttrs (RFC 9908 sec. 3.2)");
|
|
314
|
+
if (tpl > 1) throw NS.E("csrattrs/duplicate-template", "at most one certificationRequestInfoTemplate attribute is permitted in CsrAttrs (RFC 9908 sec. 3.4)");
|
|
315
|
+
return { items: items };
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* @primitive pki.schema.csrattrs.parse
|
|
321
|
+
* @signature pki.schema.csrattrs.parse(der) -> { items }
|
|
322
|
+
* @since 0.1.24
|
|
323
|
+
* @status experimental
|
|
324
|
+
* @spec RFC 8951, RFC 9908, RFC 7030
|
|
325
|
+
* @related pki.schema.parse, pki.est.buildEnrollAttributes
|
|
326
|
+
*
|
|
327
|
+
* Parse a DER `Buffer` of EST CSR Attributes (`CsrAttrs`, RFC 8951 sec. 3.5) into
|
|
328
|
+
* `{ items }`. Each item is `{ kind, oid, name }` -- `kind` `"oid"` for a bare
|
|
329
|
+
* OID, `"attribute"` for an `Attribute`, which adds raw `values` plus, for the
|
|
330
|
+
* RFC 9908 meaningful types, `extensions` (id-ExtensionReq), `curve` / `keySize`
|
|
331
|
+
* (EC / RSA key-type conventions), or `template` (the request-info template). An
|
|
332
|
+
* empty `SEQUENCE` yields `{ items: [] }` ("no additional information desired").
|
|
333
|
+
* Unknown OIDs / attribute types are tolerated (surfaced raw); a malformed
|
|
334
|
+
* structure or an RFC 9908 semantic violation throws a typed `CsrattrsError`
|
|
335
|
+
* (`csrattrs/*`) and a leaf-level codec fault surfaces as `asn1/*`.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* var a = pki.schema.csrattrs.parse(der);
|
|
339
|
+
* a.items[0].kind; // -> "oid" | "attribute"
|
|
340
|
+
*/
|
|
341
|
+
var parse = pkix.makeParser({ pemLabel: null, PemError: frameworkError.PemError, ErrorClass: CsrattrsError, prefix: "csrattrs", what: "CsrAttrs", topSchema: CSR_ATTRS, ns: NS });
|
|
342
|
+
|
|
343
|
+
// matches(root): a CsrAttrs is a universal SEQUENCE whose children are each a
|
|
344
|
+
// bare universal OID or an Attribute (a universal SEQUENCE of exactly 2 whose
|
|
345
|
+
// first child is an OID and second a SET). An empty SEQUENCE (30 00) is a valid
|
|
346
|
+
// CsrAttrs and no other registered document root accepts it. Registered BEFORE
|
|
347
|
+
// ocsp-request (a permissive superset probe) as the strict refinement, the same
|
|
348
|
+
// resolution tsp / crmf already use.
|
|
349
|
+
function matches(root) {
|
|
350
|
+
var k = pkix.rootSequenceChildren(root, 0);
|
|
351
|
+
if (!k) return false;
|
|
352
|
+
if (k.length === 0) return true;
|
|
353
|
+
for (var i = 0; i < k.length; i++) {
|
|
354
|
+
var c = k[i];
|
|
355
|
+
// bare-OID arm: a primitive universal OBJECT IDENTIFIER.
|
|
356
|
+
if (schema.isUniversal(c, T.OBJECT_IDENTIFIER) && !c.constructed) continue;
|
|
357
|
+
// Attribute arm: a universal SEQUENCE of exactly { OID, SET }.
|
|
358
|
+
if (!schema.isUniversal(c, T.SEQUENCE) || !c.children || c.children.length !== 2) return false;
|
|
359
|
+
if (!schema.isUniversal(c.children[0], T.OBJECT_IDENTIFIER)) return false;
|
|
360
|
+
if (!(schema.isUniversal(c.children[1], T.SET) && c.children[1].children)) return false;
|
|
361
|
+
}
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
module.exports = {
|
|
366
|
+
parse: parse,
|
|
367
|
+
matches: matches,
|
|
368
|
+
// The top schema, exposed (not on the curated pki.schema.csrattrs surface) so a
|
|
369
|
+
// composer can drive the encode direction and prove the round-trip.
|
|
370
|
+
csrAttrsSchema: CSR_ATTRS,
|
|
371
|
+
};
|
package/lib/schema-pkix.js
CHANGED
|
@@ -56,6 +56,38 @@ function pemDecode(text, label, PemError) {
|
|
|
56
56
|
return der;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// pemDecodeAll(text, label, PemError): decode EVERY PEM block under the STRICT
|
|
60
|
+
// RFC 7468 profile for application/pem-certificate-chain (RFC 8555 sec. 9.1) --
|
|
61
|
+
// each block must carry `label` (default CERTIFICATE), explanatory text MUST NOT
|
|
62
|
+
// appear before/between/after blocks (errata 5983: certchain = stricttextualmsg
|
|
63
|
+
// *(eol stricttextualmsg)), and at least one block is required. Every block's
|
|
64
|
+
// base64 body is canonical (the pemDecode rule); returns an array of DER buffers.
|
|
65
|
+
function pemDecodeAll(text, label, PemError) {
|
|
66
|
+
label = label || "CERTIFICATE";
|
|
67
|
+
if (Buffer.isBuffer(text)) {
|
|
68
|
+
if (text.length > constants.LIMITS.PEM_MAX_BYTES) throw new PemError("pem/too-large", "PEM input exceeds size cap");
|
|
69
|
+
text = text.toString("latin1");
|
|
70
|
+
}
|
|
71
|
+
if (typeof text !== "string") throw new PemError("pem/bad-input", "pemDecodeAll expects a string or Buffer");
|
|
72
|
+
if (text.length > constants.LIMITS.PEM_MAX_BYTES) throw new PemError("pem/too-large", "PEM input exceeds size cap");
|
|
73
|
+
var re = /-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----/g;
|
|
74
|
+
var blocks = [];
|
|
75
|
+
var lastEnd = 0, m;
|
|
76
|
+
while ((m = re.exec(text)) !== null) {
|
|
77
|
+
if (/\S/.test(text.slice(lastEnd, m.index))) throw new PemError("pem/explanatory-text", "explanatory text is not permitted around PEM blocks (RFC 8555 sec. 9.1)");
|
|
78
|
+
if (m[1] !== label) throw new PemError("pem/label-mismatch", "expected " + JSON.stringify(label) + " block, got " + JSON.stringify(m[1]));
|
|
79
|
+
var b64 = m[2].replace(/[\r\n\t ]+/g, "");
|
|
80
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(b64) || b64.length % 4 !== 0) throw new PemError("pem/bad-base64", "PEM body is not canonical base64 (RFC 4648 sec. 3.5)");
|
|
81
|
+
var der = Buffer.from(b64, "base64");
|
|
82
|
+
if (der.toString("base64") !== b64) throw new PemError("pem/bad-base64", "PEM base64 body is not canonical (RFC 4648 sec. 3.5)");
|
|
83
|
+
blocks.push(der);
|
|
84
|
+
lastEnd = re.lastIndex;
|
|
85
|
+
}
|
|
86
|
+
if (blocks.length === 0) throw new PemError("pem/no-block", "no PEM block found");
|
|
87
|
+
if (/\S/.test(text.slice(lastEnd))) throw new PemError("pem/explanatory-text", "explanatory text is not permitted after the PEM chain (RFC 8555 sec. 9.1)");
|
|
88
|
+
return blocks;
|
|
89
|
+
}
|
|
90
|
+
|
|
59
91
|
// The label must be re-readable by this module's own PEM_RE (uppercase
|
|
60
92
|
// alphanumeric words separated by single spaces, a subset of the RFC 7468
|
|
61
93
|
// sec. 3 label grammar). Anything else (lowercase, a leading/trailing space,
|
|
@@ -340,7 +372,10 @@ function name(ns, opts) {
|
|
|
340
372
|
rdns.push(atvs);
|
|
341
373
|
parts.push(atvParts.join("+"));
|
|
342
374
|
});
|
|
343
|
-
|
|
375
|
+
// `bytes` is the raw Name DER (the universal SEQUENCE TLV, recovered from the
|
|
376
|
+
// wire node even in IMPLICIT [tag] mode) -- a byte-exact comparison a consumer
|
|
377
|
+
// needs when a rendered DN is not enough (the EST re-enroll subject MUST).
|
|
378
|
+
return { rdns: rdns, dn: parts.join(", "), bytes: opts.implicitTag != null ? asn1.sequenceTlv(m.node) : m.node.bytes };
|
|
344
379
|
}
|
|
345
380
|
if (opts.implicitTag != null) {
|
|
346
381
|
return schema.implicitSeqOf(opts.implicitTag, relativeDistinguishedName(ns), {
|
|
@@ -913,13 +948,17 @@ function spki(ns, opts) {
|
|
|
913
948
|
|
|
914
949
|
// Attribute ::= SEQUENCE { type OBJECT IDENTIFIER, values SET OF AttributeValue }.
|
|
915
950
|
// AttributeValue is ANY, kept as raw DER (node.bytes) -- an unrecognized attribute
|
|
916
|
-
// type never fails the parse. values is SET SIZE (1..MAX): an empty SET
|
|
917
|
-
// rejected; there is NO SET-OF uniqueness. Shared by the CSR (requested
|
|
951
|
+
// type never fails the parse. values is SET SIZE (1..MAX) by default: an empty SET
|
|
952
|
+
// is rejected; there is NO SET-OF uniqueness. Shared by the CSR (requested
|
|
918
953
|
// attributes, RFC 2986 sec. 4.1) and PKCS#8 (private-key attributes, RFC 5958 sec. 2).
|
|
919
|
-
|
|
954
|
+
// opts.minValues (default 1) lowers the floor to 0 for the EST CsrAttrs key-type
|
|
955
|
+
// hints, whose empty values SET means "any key of this type" (RFC 9908 sec. 3.2).
|
|
956
|
+
function attribute(ns, opts) {
|
|
957
|
+
opts = opts || {};
|
|
958
|
+
var minValues = opts.minValues === undefined ? 1 : opts.minValues;
|
|
920
959
|
return schema.seq([
|
|
921
960
|
schema.field("type", schema.oidLeaf()),
|
|
922
|
-
schema.field("values", schema.setOf(schema.any(), { assert: "set", min:
|
|
961
|
+
schema.field("values", schema.setOf(schema.any(), { assert: "set", min: minValues, max: constants.LIMITS.ATTRIBUTE_MAX_VALUES,
|
|
923
962
|
code: ns.prefix + "/bad-attribute-values", what: "attribute values" })),
|
|
924
963
|
], {
|
|
925
964
|
assert: "sequence", arity: { exact: 2 }, code: ns.prefix + "/bad-attribute", what: "Attribute",
|
|
@@ -1015,6 +1054,7 @@ function signedEnvelope(ns, tbsSchema, opts) {
|
|
|
1015
1054
|
|
|
1016
1055
|
module.exports = {
|
|
1017
1056
|
pemDecode: pemDecode,
|
|
1057
|
+
pemDecodeAll: pemDecodeAll,
|
|
1018
1058
|
pemEncode: pemEncode,
|
|
1019
1059
|
coerceToDer: coerceToDer,
|
|
1020
1060
|
decodeRoot: decodeRoot,
|
package/lib/webcrypto.js
CHANGED
|
@@ -339,10 +339,11 @@ SubtleCrypto.prototype.verify = async function verify(algorithm, key, signature,
|
|
|
339
339
|
hm.update(buf);
|
|
340
340
|
// Verify must RESOLVE false for any invalid signature, including one of
|
|
341
341
|
// the wrong length. timingSafeEqual throws RangeError on a length
|
|
342
|
-
// mismatch, so gate on length first
|
|
343
|
-
//
|
|
344
|
-
|
|
345
|
-
|
|
342
|
+
// mismatch, so gate on the (public) length first, then compare the bytes
|
|
343
|
+
// in constant time. The length check leaks nothing a constant-time compare
|
|
344
|
+
// would protect; the secret-dependent byte comparison is timingSafeEqual.
|
|
345
|
+
var digest = hm.digest();
|
|
346
|
+
return digest.length === sig.length && nodeCrypto.timingSafeEqual(digest, sig);
|
|
346
347
|
}
|
|
347
348
|
throw new WebCryptoError("webcrypto/not-supported", "verify: unsupported algorithm " + JSON.stringify(name));
|
|
348
349
|
};
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:fa2f7437-a88e-47ad-959f-8d019edc06e0",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-07-
|
|
8
|
+
"timestamp": "2026-07-10T18:29:08.522Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.1.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.1.25",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.1.
|
|
25
|
+
"version": "0.1.25",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.1.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.1.25",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.1.
|
|
57
|
+
"ref": "@blamejs/pki@0.1.25",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|