@blamejs/pki 0.3.1 → 0.3.3

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/lib/cms-sign.js CHANGED
@@ -22,6 +22,7 @@ var frameworkError = require("./framework-error");
22
22
 
23
23
  var signScheme = require("./sign-scheme");
24
24
  var guard = require("./guard-all");
25
+ var pkiBuild = require("./pki-build");
25
26
  var CmsError = frameworkError.CmsError;
26
27
  var b = asn1.build;
27
28
  function _err(code, message, cause) { return new CmsError(code, message, cause); }
@@ -47,14 +48,6 @@ function _digest(digestName, content) {
47
48
  }
48
49
 
49
50
 
50
- // The raw issuer Name TLV from a parsed certificate (byte-identical to the cert, so the sid the
51
- // verifier canonically compares matches exactly). The issuer is the Name after the optional
52
- // version [0] and the serial + signature AlgorithmIdentifier in the tbsCertificate.
53
- function _issuerBytes(cert) {
54
- var tbs = asn1.decode(cert.tbsBytes);
55
- var hasVersion = tbs.children[0].tagClass === "context" && tbs.children[0].tagNumber === 0;
56
- return tbs.children[hasVersion ? 3 : 2].bytes;
57
- }
58
51
  // The cert's subjectKeyIdentifier extension value (the raw key id), or throws.
59
52
  function _skiValue(cert) {
60
53
  var ext = (cert.extensions || []).filter(function (e) { return e.oid === OID_SKI; })[0];
@@ -75,7 +68,7 @@ function _buildSignerInfo(signer, content, eContentType, opts) {
75
68
  var useSki = opts.sid === "ski";
76
69
  var sid = useSki
77
70
  ? b.contextPrimitive(0, _skiValue(cert)) // [0] IMPLICIT SubjectKeyIdentifier
78
- : b.sequence([b.raw(_issuerBytes(cert)), b.integer(cert.serialNumber)]); // IssuerAndSerialNumber
71
+ : b.sequence([b.raw(pkiBuild.tbsNameField(cert, "issuer")), b.integer(cert.serialNumber)]); // IssuerAndSerialNumber
79
72
  var version = useSki ? 3 : 1;
80
73
 
81
74
  return Promise.resolve().then(function () {
@@ -0,0 +1,275 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+
5
+ /**
6
+ * @module pki.crmf
7
+ * @nav Signing
8
+ * @title Certificate request messages
9
+ * @intro The RFC 4211 certificate-request-message producing side. `pki.crmf.build` assembles a
10
+ * `CertReqMessages` -- one or more `CertReqMsg`, each a `CertRequest` (a `CertTemplate` of the requested
11
+ * certificate fields plus optional controls) paired with a proof of possession. The common proof is a
12
+ * `POPOSigningKey` signature over the `CertRequest`, made with the private half of the key being
13
+ * certified (the requester proves possession, exactly as a PKCS#10 CSR does). The message drops into a
14
+ * CMP (RFC 9810) or EST enrollment body. Parsing lives at `pki.schema.crmf.parse`.
15
+ * @spec RFC 4211
16
+ * @card Build a CRMF CertReqMessages with a signature proof of possession.
17
+ */
18
+ //
19
+ // RFC 4211 App. B / RFC 5912 sec. 10 are DEFINITIONS IMPLICIT TAGS: a CertTemplate [0]..[9] field tag
20
+ // REPLACES the base universal tag (built via asn1.build.implicit, preserving the primitive/constructed
21
+ // bit); the shipped parser encodes issuer [3] / subject [5] Name IMPLICITLY (the dominant CMP/EST wire
22
+ // form), so the builder does too. The two EXPLICIT exceptions are the OptionalValidity notBefore [0] /
23
+ // notAfter [1] Time (a genuine UTCTime/GeneralizedTime CHOICE). The signature scheme resolves from the
24
+ // requested publicKey through the shared sign-scheme registry; the Name / extension / SPKI encoders and
25
+ // the post-sign self-check are the shared lib/pki-build primitives, bound to the crmf namespace.
26
+
27
+ var asn1 = require("./asn1-der");
28
+ var oid = require("./oid");
29
+ var crmf = require("./schema-crmf");
30
+ var signScheme = require("./sign-scheme");
31
+ var pkix = require("./schema-pkix");
32
+ var pkiBuild = require("./pki-build");
33
+ var frameworkError = require("./framework-error");
34
+
35
+ var CrmfError = frameworkError.CrmfError;
36
+ var b = asn1.build;
37
+ function _err(code, message, cause) { return new CrmfError(code, message, cause); }
38
+ function _signE(kind, message, cause) { return new CrmfError("crmf/" + kind, message, cause); }
39
+ function O(n) { return oid.byName(n); }
40
+
41
+ var NS = pkix.makeNS("crmf", CrmfError, oid);
42
+ var EXT_DECODERS = pkix.certExtensionDecoders(NS).byOid;
43
+ var _b = pkiBuild.makeBuilder({
44
+ ErrorClass: CrmfError, prefix: "crmf", O: O, NS: NS,
45
+ NAME_SCHEMA: pkix.name(NS), SPKI_SCHEMA: pkix.spki(NS), EXT_DECODERS: EXT_DECODERS,
46
+ });
47
+
48
+ var KNOWN_SPEC_KEYS = { certReqId: 1, certTemplate: 1, controls: 1, regInfo: 1, pop: 1 };
49
+ var KNOWN_TEMPLATE_KEYS = { version: 1, subject: 1, publicKey: 1, validity: 1, extensions: 1, issuer: 1 };
50
+ // The controls (RFC 4211 sec. 6) and regInfo (sec. 7) are DISJOINT AttributeTypeAndValue namespaces --
51
+ // regToken/authenticator/oldCertID/protocolEncrKey are controls; utf8Pairs is regInfo -- so the object
52
+ // form validates each key against its own field's registry (a control name is not a valid regInfo, and
53
+ // vice versa). The key name equals the registered OID name; complex values (pkiPublicationInfo, certReq)
54
+ // ride the pre-encoded escape hatch. Each entry is the value encoder for that OID.
55
+ function _ctlUtf8(v) { return b.utf8(String(v)); }
56
+ function _ctlSpki(v) { var k = _b.reqDer(v, "protocolEncrKey (an SPKI DER)"); _b.assertValidSpki(k, "protocolEncrKey"); return b.raw(k); }
57
+ var CONTROL_VALUE = { regToken: _ctlUtf8, authenticator: _ctlUtf8, oldCertID: function (v) { return _encodeCertId(v); }, protocolEncrKey: _ctlSpki };
58
+ var REGINFO_VALUE = { utf8Pairs: _ctlUtf8 };
59
+
60
+ // ---- CertTemplate + POP structural encoders (byte-exact inverses of schema-crmf.js) ----
61
+
62
+ // OptionalValidity [4] IMPLICIT SEQUENCE { notBefore [0] EXPLICIT Time, notAfter [1] EXPLICIT Time } --
63
+ // at least one present (RFC 4211 sec. 5). Time is a CHOICE so [0]/[1] are EXPLICIT.
64
+ function _encodeOptionalValidity(validity) {
65
+ if (!validity || typeof validity !== "object" || Buffer.isBuffer(validity)) throw _err("crmf/bad-validity", "validity must be an object { notBefore?, notAfter? }");
66
+ var nb = validity.notBefore, na = validity.notAfter;
67
+ if (nb == null && na == null) throw _err("crmf/bad-validity", "validity must contain notBefore or notAfter (RFC 4211 sec. 5)");
68
+ var parts = [];
69
+ // timeDer validates each instant (guard.time.assertValid throws on an Invalid Date) BEFORE the
70
+ // inverted-window comparison, so getTime() below cannot be NaN.
71
+ if (nb != null) parts.push(b.explicit(0, _b.timeDer(nb, "validity notBefore")));
72
+ if (na != null) parts.push(b.explicit(1, _b.timeDer(na, "validity notAfter")));
73
+ // allow:nan-date-comparison-unguarded -- both instants passed timeDer's guard.time.assertValid above.
74
+ if (nb != null && na != null && nb.getTime() > na.getTime()) throw _err("crmf/bad-validity", "notBefore must not be after notAfter");
75
+ return b.implicit(4, b.sequence(parts));
76
+ }
77
+ // CertTemplate ::= SEQUENCE { [0..9] all IMPLICIT OPTIONAL }. A REQUEST omits serialNumber [1] / signingAlg
78
+ // [2] / issuerUID [7] / subjectUID [8] (CA-assigned or deprecated, RFC 4211 sec. 5) -- the builder never
79
+ // emits them. Fields are emitted in ascending tag order.
80
+ function _encodeCertTemplate(tpl) {
81
+ if (!tpl || typeof tpl !== "object" || Buffer.isBuffer(tpl)) throw _err("crmf/bad-cert-template", "certTemplate must be an object");
82
+ Object.keys(tpl).forEach(function (k) { if (!KNOWN_TEMPLATE_KEYS[k]) throw _err("crmf/bad-input", "unknown certTemplate field " + JSON.stringify(k)); });
83
+ var fields = [];
84
+ if (tpl.version != null) {
85
+ if (tpl.version !== 2) throw _err("crmf/bad-version", "certTemplate version MUST be 2 (v3) if supplied (RFC 4211 sec. 5)");
86
+ fields.push(b.implicit(0, b.integer(2n))); // version [0]
87
+ }
88
+ // issuer [3] / subject [5] are EXPLICIT: Name is a CHOICE, and X.680 sec. 31.2.7 forces a context tag on
89
+ // a CHOICE to EXPLICIT even under the module's IMPLICIT TAGS default (the [3]/[5] wraps the RDNSequence
90
+ // SEQUENCE, it does not replace its tag). The parser accepts both forms; this is the conformant one.
91
+ if (tpl.issuer != null) fields.push(b.explicit(3, _b.encodeName(tpl.issuer))); // issuer [3] EXPLICIT
92
+ if (tpl.validity != null) fields.push(_encodeOptionalValidity(tpl.validity)); // validity [4] IMPLICIT
93
+ if (tpl.subject != null) fields.push(b.explicit(5, _b.encodeName(tpl.subject))); // subject [5] EXPLICIT
94
+ var spki = null;
95
+ if (tpl.publicKey != null) {
96
+ spki = _b.reqDer(tpl.publicKey, "certTemplate.publicKey (the SPKI DER of the requested key)");
97
+ _b.assertValidSpki(spki, "certTemplate.publicKey");
98
+ fields.push(b.implicit(6, spki)); // publicKey [6]
99
+ }
100
+ if (tpl.extensions != null) fields.push(b.implicit(9, _b.requestedExtensions(tpl.extensions, spki))); // extensions [9]
101
+ return { der: b.sequence(fields), spki: spki, complete: tpl.subject != null && tpl.publicKey != null };
102
+ }
103
+
104
+ // Controls / regInfo ::= SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue { type OID, value ANY }. Object
105
+ // form maps recognized names to typed value encoders; any other (or a pre-encoded AttributeTypeAndValue
106
+ // DER array) rides the escape hatch, shape-validated.
107
+ function _buildAttrTypeAndValues(spec, code, label, valueMap) {
108
+ if (Array.isArray(spec)) {
109
+ if (!spec.length) throw _err(code, label + " must carry at least one entry");
110
+ var seenA = {};
111
+ return b.sequence(spec.map(function (e, i) {
112
+ var der = _b.reqDer(e, label + " [" + i + "]");
113
+ var n;
114
+ try { n = asn1.decode(der); } catch (err) { throw _err("crmf/bad-input", "pre-encoded " + label + " [" + i + "] is not valid DER", err); }
115
+ if (n.tagNumber !== asn1.TAGS.SEQUENCE || n.tagClass !== "universal" || !n.children || n.children.length !== 2) throw _err("crmf/bad-input", "pre-encoded " + label + " [" + i + "] must be a SEQUENCE { type OID, value }");
116
+ var t;
117
+ try { t = asn1.read.oid(n.children[0]); } catch (err) { throw _err("crmf/bad-input", "pre-encoded " + label + " [" + i + "] type is not an OBJECT IDENTIFIER", err); }
118
+ if (seenA[t]) throw _err(code, "duplicate " + label + " type " + (oid.name(t) || t));
119
+ seenA[t] = true;
120
+ return b.raw(der);
121
+ }));
122
+ }
123
+ if (!spec || typeof spec !== "object") throw _err("crmf/bad-input", label + " must be an object or an array of pre-encoded AttributeTypeAndValue DER");
124
+ var out = [], seen = {};
125
+ Object.keys(spec).forEach(function (k) {
126
+ var enc = valueMap[k];
127
+ if (!enc) throw _err("crmf/bad-input", "unknown " + label + " " + JSON.stringify(k) + "; pass a pre-encoded AttributeTypeAndValue DER via the array form for a " + label + " entry outside " + Object.keys(valueMap).join("/"));
128
+ var typeOid = O(k);
129
+ if (seen[typeOid]) throw _err(code, "duplicate " + label + " type " + k);
130
+ seen[typeOid] = true;
131
+ out.push(b.sequence([b.oid(typeOid), enc(spec[k])]));
132
+ });
133
+ if (!out.length) throw _err(code, label + " must carry at least one entry");
134
+ return b.sequence(out);
135
+ }
136
+ // certReqId is a signed INTEGER, value UNCONSTRAINED (the RFC 9483 -1 sentinel and 0 are both legal). A
137
+ // number must be a safe integer (a fractional or > 2^53 number loses precision through BigInt); a BigInt
138
+ // or a decimal / 0x-hex string carries an arbitrary value.
139
+ function _certReqId(v) {
140
+ if (v == null) return 0n;
141
+ if (typeof v === "bigint") return v;
142
+ if (typeof v === "number") { if (!Number.isSafeInteger(v)) throw _err("crmf/bad-input", "certReqId number must be a safe integer (pass a BigInt or a string for a larger value)"); return BigInt(v); }
143
+ if (typeof v === "string") { try { return BigInt(v); } catch (e) { throw _err("crmf/bad-input", "certReqId string must be a decimal or 0x-hex integer", e); } }
144
+ throw _err("crmf/bad-input", "certReqId must be a BigInt, a safe integer, or a string");
145
+ }
146
+ // CertId ::= SEQUENCE { issuer GeneralName, serialNumber INTEGER } (RFC 4211 sec. 6.4, oldCertID value).
147
+ function _encodeCertId(id) {
148
+ if (!id || typeof id !== "object" || id.issuer == null || id.serialNumber == null) throw _err("crmf/bad-input", "oldCertID must be { issuer: GeneralName, serialNumber }");
149
+ return b.sequence([_b.encodeGeneralName(id.issuer), _b.serialInteger(id.serialNumber)]);
150
+ }
151
+
152
+ // ProofOfPossession. The signature arm (the default when a key is given): when the template carries BOTH
153
+ // subject and publicKey (complete), sign the CertRequest DER and OMIT poposkInput; otherwise build a
154
+ // POPOSigningKeyInput (authInfo sender [0] GeneralName + the requested publicKey), sign its SEQUENCE, and
155
+ // carry it as poposkInput [0] (RFC 4211 sec. 4.1). raVerified is emitted only on an explicit opt-in.
156
+ function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
157
+ if (pop != null && (typeof pop !== "object" || Buffer.isBuffer(pop))) throw _err("crmf/bad-input", "spec.pop must be an object (e.g. { type: 'signature' } or { type: 'raVerified', raVerified: true })");
158
+ var mode = (pop && pop.type) || (signingKey != null ? "signature" : null);
159
+ if (mode == null) return null; // no POP requested and no key -> omit popo (an RA supplies it out of band)
160
+ if (mode === "raVerified") {
161
+ if (!(pop && pop.raVerified === true)) throw _err("crmf/bad-popo", "raVerified must be explicitly opted into (pop: { type: 'raVerified', raVerified: true }) -- a requester does not normally assert it (RFC 4211 sec. 4)");
162
+ return b.implicit(0, b.nullValue()); // raVerified [0] IMPLICIT NULL
163
+ }
164
+ if (mode !== "signature") throw _err("crmf/bad-popo", "unsupported proof-of-possession type " + JSON.stringify(mode) + " (supported: 'signature', 'raVerified')");
165
+ if (signingKey == null) throw _err("crmf/bad-input", "a signature proof of possession requires the requester's private key");
166
+ if (template.spki == null) throw _err("crmf/bad-input", "a signature proof of possession requires certTemplate.publicKey");
167
+ var scheme = signScheme.resolveSignScheme(_b.certLikeFromSpki(template.spki), { combinedRsaSig: true, pss: opts.pss, digestAlgorithm: opts.digestAlgorithm }, true, _signE);
168
+ var signedRegion, poposkInputField = null;
169
+ if (template.complete) {
170
+ signedRegion = certReqDer; // sec. 4.1: complete template -> sign the CertRequest, poposkInput omitted
171
+ } else {
172
+ // POPOSigningKeyInput ::= SEQUENCE { authInfo CHOICE { sender [0] GeneralName, publicKeyMAC }, publicKey }.
173
+ var sender = pop && pop.sender;
174
+ if (sender == null) throw _err("crmf/bad-popo", "an incomplete template (missing subject or publicKey) requires pop.sender (a GeneralName) for the POPOSigningKeyInput authInfo (RFC 4211 sec. 4.1)");
175
+ var poposkSeq = b.sequence([b.explicit(0, _b.encodeGeneralName(sender)), b.raw(template.spki)]);
176
+ signedRegion = poposkSeq;
177
+ poposkInputField = b.implicit(0, poposkSeq); // poposkInput [0] IMPLICIT POPOSigningKeyInput
178
+ }
179
+ return signScheme.signOverTbs(scheme, signingKey, signedRegion, _signE).then(function (sig) {
180
+ return Promise.resolve(_b.assertSignatureVerifies(signedRegion, sig, template.spki, scheme)).then(function () {
181
+ var popoChildren = [];
182
+ if (poposkInputField) popoChildren.push(poposkInputField);
183
+ popoChildren.push(scheme.sigAlgId);
184
+ popoChildren.push(b.bitString(sig, 0));
185
+ return b.implicit(1, b.sequence(popoChildren)); // signature [1] IMPLICIT POPOSigningKey
186
+ });
187
+ }, function (e) {
188
+ if (e instanceof CrmfError) throw e;
189
+ throw _err("crmf/bad-input", "signing the proof of possession failed -- the key does not match the requested public key or is invalid", e);
190
+ });
191
+ }
192
+
193
+ /**
194
+ * @primitive pki.crmf.build
195
+ * @signature pki.crmf.build(spec, key?, opts?) -> Promise<Buffer|string>
196
+ * @since 0.3.3
197
+ * @status experimental
198
+ * @spec RFC 4211
199
+ * @defends forged-certificate-request (CWE-347)
200
+ * @related pki.schema.crmf.parse, pki.csr.sign
201
+ *
202
+ * Build and DER-encode an RFC 4211 `CertReqMessages`. `spec` describes one certificate request message (or
203
+ * pass `spec.messages` -- an array of specs -- for a batch): `certReqId` (an integer, default 0; the RFC
204
+ * 9483 `-1` sentinel is allowed), `certTemplate` (the requested certificate fields -- `subject`, `publicKey`
205
+ * (the SPKI DER of the key being certified), `validity` ({ notBefore, notAfter } Dates), `extensions` (an
206
+ * object of subjectAltName / keyUsage / extendedKeyUsage / basicConstraints / certificatePolicies /
207
+ * subjectKeyIdentifier, or pre-encoded Extension DER), and an optional `version` (2)), optional `controls`
208
+ * and `regInfo` (an object of regToken / authenticator / utf8Pairs / oldCertID / protocolEncrKey, or
209
+ * pre-encoded AttributeTypeAndValue DER), and an optional `pop` selector. `key` (or `{ key }`) is the
210
+ * REQUESTER's private key -- the private half of `certTemplate.publicKey`; the message carries a
211
+ * `POPOSigningKey` proof of possession signed with it (verified before the message is returned), exactly
212
+ * as a PKCS#10 CSR proves possession. The signature algorithm is resolved from the requested public key
213
+ * (RSA PKCS#1 v1.5 / PSS, ECDSA, EdDSA, ML-DSA, SLH-DSA, or a composite arm). `key` is optional -- omit it
214
+ * for a `raVerified` proof (opt in with `pop: { type: 'raVerified', raVerified: true }`). Returns DER, or a
215
+ * PEM block with `opts.pem` (the label is required). Malformed input throws a typed `CrmfError`.
216
+ * Certificate-request-message parsing is `pki.schema.crmf.parse`.
217
+ *
218
+ * @opts
219
+ * - `pem` (string) -- return a PEM block with this label instead of DER (e.g. "CERTIFICATE REQUEST MESSAGE").
220
+ * - `pss` (boolean) -- sign an RSA key with RSASSA-PSS rather than PKCS#1 v1.5.
221
+ * - `digestAlgorithm` (string) -- override the message digest where the algorithm permits a choice.
222
+ * @example
223
+ * var msg = await pki.crmf.build(
224
+ * { certReqId: 0, certTemplate: { subject: "device-42", publicKey: signerSpki } },
225
+ * { key: signerKeyPkcs8 });
226
+ * pki.schema.crmf.parse(msg).messages[0].certReq.certTemplate.subject.dn; // "CN=device-42"
227
+ */
228
+ function build(spec, key, opts) {
229
+ return Promise.resolve().then(function () { return _build(spec, key, opts); });
230
+ }
231
+
232
+ function _buildCertReqMsg(spec, key, opts) {
233
+ if (!spec || typeof spec !== "object" || Buffer.isBuffer(spec)) throw _err("crmf/bad-input", "each certificate-request-message spec must be an object");
234
+ Object.keys(spec).forEach(function (k) { if (!KNOWN_SPEC_KEYS[k]) throw _err("crmf/bad-input", "unknown spec field " + JSON.stringify(k)); });
235
+ if (spec.certTemplate == null) throw _err("crmf/bad-input", "spec.certTemplate is required");
236
+
237
+ var signingKey = (key && typeof key === "object" && !Buffer.isBuffer(key) && !(key instanceof Uint8Array) && key.type == null && "key" in key) ? key.key : key;
238
+ var template = _encodeCertTemplate(spec.certTemplate);
239
+ var certReqChildren = [b.integer(_certReqId(spec.certReqId)), template.der];
240
+ if (spec.controls != null) certReqChildren.push(_buildAttrTypeAndValues(spec.controls, "crmf/bad-controls", "controls", CONTROL_VALUE));
241
+ var certReqDer = b.sequence(certReqChildren);
242
+
243
+ return Promise.resolve(_buildProofOfPossession(spec.pop, certReqDer, template, signingKey, opts)).then(function (popoDer) {
244
+ var msgChildren = [certReqDer];
245
+ if (popoDer) msgChildren.push(popoDer);
246
+ if (spec.regInfo != null) msgChildren.push(_buildAttrTypeAndValues(spec.regInfo, "crmf/bad-reg-info", "regInfo", REGINFO_VALUE));
247
+ return b.sequence(msgChildren); // CertReqMsg
248
+ });
249
+ }
250
+
251
+ function _build(spec, key, opts) {
252
+ opts = opts || {};
253
+ if (!spec || typeof spec !== "object" || Buffer.isBuffer(spec)) throw _err("crmf/bad-input", "the certificate-request-message spec must be an object");
254
+ var specs;
255
+ if (spec.messages != null) {
256
+ // Batch form: the envelope carries ONLY `messages` -- reject a stray field so a request spec written at
257
+ // the wrong nesting level (e.g. certTemplate alongside messages) is not silently dropped.
258
+ if (!Array.isArray(spec.messages)) throw _err("crmf/bad-input", "spec.messages must be an array of certificate-request-message specs");
259
+ Object.keys(spec).forEach(function (k) { if (k !== "messages") throw _err("crmf/bad-input", "unknown batch-envelope field " + JSON.stringify(k) + " -- a batch spec carries only 'messages'"); });
260
+ specs = spec.messages;
261
+ } else {
262
+ specs = [spec];
263
+ }
264
+ if (!specs.length) throw _err("crmf/bad-input", "at least one certificate request message is required (RFC 4211 sec. 3)");
265
+ return Promise.all(specs.map(function (s) { return _buildCertReqMsg(s, key, opts); })).then(function (msgs) {
266
+ var der = b.sequence(msgs); // CertReqMessages ::= SEQUENCE SIZE(1..MAX) OF CertReqMsg
267
+ if (opts.pem != null) {
268
+ if (typeof opts.pem !== "string" || !opts.pem) throw _err("crmf/bad-input", "opts.pem must be a non-empty PEM label string");
269
+ return crmf.pemEncode(der, opts.pem);
270
+ }
271
+ return der;
272
+ });
273
+ }
274
+
275
+ module.exports = { build: build };
package/lib/csr-sign.js CHANGED
@@ -42,12 +42,6 @@ var _b = pkiBuild.makeBuilder({
42
42
  NAME_SCHEMA: pkix.name(NS), SPKI_SCHEMA: pkix.spki(NS), EXT_DECODERS: EXT_DECODERS,
43
43
  });
44
44
 
45
- // The requested-extension object keys (a CSR REQUESTS extensions; it does NOT enforce issuance policy,
46
- // so there are no CA cross-field gates and no authorityKeyIdentifier auto-derivation).
47
- var KNOWN_REQ_EXT_KEYS = {
48
- subjectAltName: 1, keyUsage: 1, keyUsageCritical: 1, extendedKeyUsage: 1, extendedKeyUsageCritical: 1,
49
- basicConstraints: 1, certificatePolicies: 1, certificatePoliciesCritical: 1, subjectKeyIdentifier: 1,
50
- };
51
45
  // challengePassword DirectoryString charset (PrintableString set); anything else uses UTF8String.
52
46
  var PRINTABLE_RE = /^[A-Za-z0-9 '()+,\-./:=?]*$/;
53
47
 
@@ -57,43 +51,9 @@ function _implicitSetOf0(members) {
57
51
  return b.contextConstructed(0, Buffer.concat(members.slice().sort(Buffer.compare)));
58
52
  }
59
53
 
60
- // The requested v3 Extensions (a bare SEQUENCE OF Extension, RFC 2985 sec. 5.4.2) from the object form,
61
- // or an array of pre-encoded Extension DER. Reuses the shared extension-value encoders; no CA gates.
62
- function _buildRequestedExtensions(extSpec, subjectSpki) {
63
- if (Array.isArray(extSpec)) {
64
- if (!extSpec.length) throw _err("csr/bad-input", "extensionRequest must carry at least one extension");
65
- var seen = {};
66
- return b.sequence(extSpec.map(function (e, i) {
67
- var der = _b.reqDer(e, "extension");
68
- _b.assertValidExtension(der, i);
69
- var n = asn1.decode(der);
70
- var extnId = asn1.read.oid(n.children[0]);
71
- if (seen[extnId]) throw _err("csr/bad-input", "duplicate extension " + extnId + " in extensionRequest (RFC 5280 sec. 4.2)");
72
- seen[extnId] = true;
73
- // Fully validate a RECOGNIZED extension via its real RFC 5280 sec. 4.2.1 value decoder (a malformed
74
- // value fails closed with the decoder's typed code); an unrecognized extension stays opaque.
75
- var dec = EXT_DECODERS[extnId];
76
- if (dec) {
77
- try { dec(asn1.read.octetString(n.children[n.children.length - 1])); }
78
- catch (err) { if (err instanceof CsrError) throw err; throw _err("csr/bad-input", "pre-encoded " + (oid.name(extnId) || extnId) + " extension value is malformed", err); }
79
- }
80
- return b.raw(der);
81
- }));
82
- }
83
- if (!extSpec || typeof extSpec !== "object") throw _err("csr/bad-input", "extensionRequest must be an object or an array of pre-encoded Extension DER");
84
- Object.keys(extSpec).forEach(function (k) {
85
- if (!KNOWN_REQ_EXT_KEYS[k]) throw _err("csr/bad-input", "unknown extensionRequest extension " + JSON.stringify(k) + "; pass a pre-encoded Extension DER via the array form for a custom extension");
86
- });
87
- var out = [];
88
- if (extSpec.subjectKeyIdentifier != null) out.push(_b.ext(O("subjectKeyIdentifier"), false, _b.extSki(_b.skiKeyId(extSpec.subjectKeyIdentifier, subjectSpki))));
89
- if (extSpec.keyUsage != null) out.push(_b.ext(O("keyUsage"), extSpec.keyUsageCritical !== false, _b.extKeyUsage(extSpec.keyUsage)));
90
- if (extSpec.extendedKeyUsage != null) out.push(_b.ext(O("extKeyUsage"), !!extSpec.extendedKeyUsageCritical, _b.extExtKeyUsage(extSpec.extendedKeyUsage)));
91
- if (extSpec.basicConstraints != null) { _b.validateBcSpec(extSpec.basicConstraints); out.push(_b.ext(O("basicConstraints"), extSpec.basicConstraints.critical !== false, _b.extBasicConstraints(extSpec.basicConstraints))); }
92
- if (extSpec.subjectAltName != null) out.push(_b.ext(O("subjectAltName"), false, _b.extSan(extSpec.subjectAltName)));
93
- if (extSpec.certificatePolicies != null) out.push(_b.ext(O("certificatePolicies"), !!extSpec.certificatePoliciesCritical, _b.extCertPolicies(extSpec.certificatePolicies)));
94
- if (!out.length) throw _err("csr/bad-input", "extensionRequest must request at least one extension");
95
- return b.sequence(out);
96
- }
54
+ // The requested v3 Extensions (a bare SEQUENCE OF Extension, RFC 2985 sec. 5.4.2) -- the shared builder
55
+ // primitive (a CSR REQUESTS extensions; no CA gates), object form or a pre-encoded Extension DER array.
56
+ function _buildRequestedExtensions(extSpec, subjectSpki) { return _b.requestedExtensions(extSpec, subjectSpki); }
97
57
 
98
58
  // challengePassword ::= DirectoryString bounded 1..255 (RFC 2985 sec. 5.4.1). PrintableString when the
99
59
  // value is in that charset, else UTF8String; never a Teletex/BMP/Universal string for a new value.
package/lib/pki-build.js CHANGED
@@ -13,6 +13,8 @@
13
13
  var asn1 = require("./asn1-der");
14
14
  var schema = require("./schema-engine");
15
15
  var compositeSig = require("./composite-sig");
16
+ var guard = require("./guard-all");
17
+ var oid = require("./oid");
16
18
  var nodeCrypto = require("crypto");
17
19
 
18
20
  var b = asn1.build;
@@ -31,6 +33,17 @@ function makeBuilder(ctx) {
31
33
  var NAME_SCHEMA = ctx.NAME_SCHEMA, SPKI_SCHEMA = ctx.SPKI_SCHEMA;
32
34
  function E(kind, message, cause) { return new ErrorClass(ctx.prefix + "/" + kind, message, cause); }
33
35
  function code(kind) { return ctx.prefix + "/" + kind; }
36
+ // A full-code error factory (guard.* helpers invoke E as `E(fullCode, msg)`, not `E(kind, msg)`).
37
+ function rawErr(fullCode, message, cause) { return new ErrorClass(fullCode, message, cause); }
38
+
39
+ // RFC 5280 sec. 4.1.2.5 -- UTCTime for 1950..2049, GeneralizedTime otherwise (UTCTime's two-digit year
40
+ // only represents that window). The shared cutover so a certificate / CRMF / any validity encoder cannot
41
+ // drift on the boundary. `which` labels the instant in a validation error.
42
+ function timeDer(date, which) {
43
+ guard.time.assertValid(date, rawErr, code("bad-input"), which);
44
+ var y = date.getUTCFullYear();
45
+ return (y >= 1950 && y <= 2049) ? b.utcTime(date) : b.generalizedTime(date);
46
+ }
34
47
 
35
48
  // ---- distinguished name encoding (RFC 5280 sec. 4.1.2.4) ----
36
49
  // countryName is a PrintableString SIZE(2), emailAddress an IA5String; every other new-name attribute
@@ -144,10 +157,18 @@ function makeBuilder(ctx) {
144
157
  }
145
158
  function extSki(keyid) { return b.octetString(keyid); }
146
159
  function extAki(keyid) { return b.sequence([b.contextPrimitive(0, keyid)]); } // keyIdentifier [0] IMPLICIT OCTET STRING
147
- function extSan(entries) {
148
- if (!Array.isArray(entries) || !entries.length) throw E("bad-input", "subjectAltName must carry at least one GeneralName");
149
- return b.sequence(entries.map(encodeGeneralName));
160
+ // GeneralNames ::= SEQUENCE SIZE(1..MAX) OF GeneralName. With no implicitTag it is a universal SEQUENCE
161
+ // (subjectAltName / issuerAltName / IssuerSerial.issuer). With an implicitTag it is a context-constructed
162
+ // [n] whose tag REPLACES the SEQUENCE tag (RFC 5755 IMPLICIT TAGS -- Holder.entityName [1],
163
+ // RoleSyntax.roleAuthority [0], IetfAttrSyntax.policyAuthority [0]); the [n] node's children ARE the
164
+ // GeneralName members, no inner SEQUENCE wrapper.
165
+ function encodeGeneralNames(entries, implicitTag) {
166
+ if (!Array.isArray(entries) || !entries.length) throw E("bad-input", "a GeneralNames must carry at least one GeneralName");
167
+ var members = entries.map(encodeGeneralName);
168
+ if (implicitTag == null) return b.sequence(members);
169
+ return b.contextConstructed(implicitTag, Buffer.concat(members));
150
170
  }
171
+ function extSan(entries) { return encodeGeneralNames(entries); }
151
172
  function extCertPolicies(names) {
152
173
  if (!Array.isArray(names) || !names.length) throw E("bad-input", "certificatePolicies must list at least one policy OID");
153
174
  var seen = {};
@@ -218,6 +239,76 @@ function makeBuilder(ctx) {
218
239
  var last = n.children[n.children.length - 1];
219
240
  if (last.tagNumber !== asn1.TAGS.OCTET_STRING || last.tagClass !== "universal") throw E("bad-input", "pre-encoded extension [" + idx + "] extnValue must be an OCTET STRING");
220
241
  }
242
+ // The requested v3 Extensions (a bare SEQUENCE OF Extension) from an object of the recognized keys, or
243
+ // an array of pre-encoded Extension DER (shape- + recognized-value-validated). Shared by every enrollment
244
+ // request that carries requested extensions (a PKCS#10 extensionRequest attribute, a CRMF CertTemplate
245
+ // extensions [9]); a request REQUESTS extensions, so there are no CA cross-field gates. `spki` feeds the
246
+ // subjectKeyIdentifier auto-derive.
247
+ var REQ_EXT_KEYS = {
248
+ subjectAltName: 1, keyUsage: 1, keyUsageCritical: 1, extendedKeyUsage: 1, extendedKeyUsageCritical: 1,
249
+ basicConstraints: 1, certificatePolicies: 1, certificatePoliciesCritical: 1, subjectKeyIdentifier: 1,
250
+ };
251
+ function requestedExtensions(extSpec, spki) {
252
+ var EXT_DECODERS = ctx.EXT_DECODERS;
253
+ if (Array.isArray(extSpec)) {
254
+ if (!extSpec.length) throw E("bad-input", "the requested extensions list must carry at least one extension");
255
+ var seen = {};
256
+ return b.sequence(extSpec.map(function (e, i) {
257
+ var der = reqDer(e, "extension");
258
+ assertValidExtension(der, i);
259
+ var n = asn1.decode(der);
260
+ var extnId = asn1.read.oid(n.children[0]);
261
+ if (seen[extnId]) throw E("bad-input", "duplicate requested extension " + extnId + " (RFC 5280 sec. 4.2)");
262
+ seen[extnId] = true;
263
+ var dec = EXT_DECODERS && EXT_DECODERS[extnId];
264
+ if (dec) {
265
+ try { dec(asn1.read.octetString(n.children[n.children.length - 1])); }
266
+ catch (err) { if (err instanceof ErrorClass) throw err; throw E("bad-input", "pre-encoded " + (oid.name(extnId) || extnId) + " extension value is malformed", err); }
267
+ }
268
+ return b.raw(der);
269
+ }));
270
+ }
271
+ if (!extSpec || typeof extSpec !== "object") throw E("bad-input", "requested extensions must be an object or an array of pre-encoded Extension DER");
272
+ Object.keys(extSpec).forEach(function (k) {
273
+ if (!REQ_EXT_KEYS[k]) throw E("bad-input", "unknown requested extension " + JSON.stringify(k) + "; pass a pre-encoded Extension DER via the array form for a custom extension");
274
+ });
275
+ var out = [];
276
+ if (extSpec.subjectKeyIdentifier != null) {
277
+ // Auto-deriving the SKI (subjectKeyIdentifier: true) hashes the subject public key, so it needs one;
278
+ // reject the request when no public key is available (supply a Buffer key id instead).
279
+ if (extSpec.subjectKeyIdentifier === true && spki == null) throw E("bad-input", "subjectKeyIdentifier auto-derive (true) requires the public key -- supply a Buffer key id, or include the public key");
280
+ out.push(ext(O("subjectKeyIdentifier"), false, extSki(skiKeyId(extSpec.subjectKeyIdentifier, spki))));
281
+ }
282
+ if (extSpec.keyUsage != null) out.push(ext(O("keyUsage"), extSpec.keyUsageCritical !== false, extKeyUsage(extSpec.keyUsage)));
283
+ if (extSpec.extendedKeyUsage != null) out.push(ext(O("extKeyUsage"), !!extSpec.extendedKeyUsageCritical, extExtKeyUsage(extSpec.extendedKeyUsage)));
284
+ if (extSpec.basicConstraints != null) { validateBcSpec(extSpec.basicConstraints); out.push(ext(O("basicConstraints"), extSpec.basicConstraints.critical !== false, extBasicConstraints(extSpec.basicConstraints))); }
285
+ if (extSpec.subjectAltName != null) out.push(ext(O("subjectAltName"), false, extSan(extSpec.subjectAltName)));
286
+ if (extSpec.certificatePolicies != null) out.push(ext(O("certificatePolicies"), !!extSpec.certificatePoliciesCritical, extCertPolicies(extSpec.certificatePolicies)));
287
+ if (!out.length) throw E("bad-input", "the requested extensions object must request at least one extension");
288
+ return b.sequence(out);
289
+ }
290
+
291
+ // A positive, non-zero INTEGER of at most 20 content octets (RFC 5280 sec. 4.1.2.2 / RFC 5755 sec.
292
+ // 4.2.5 -- the identical serial rule for a certificate and an attribute certificate). A random 20-octet
293
+ // positive serial is generated when none is supplied. Accepts a BigInt, a safe integer, a decimal /
294
+ // 0x-hex string, or a raw magnitude Buffer.
295
+ function serialInteger(serial) {
296
+ var v;
297
+ if (serial == null) {
298
+ var rnd = nodeCrypto.randomBytes(20);
299
+ rnd[0] &= 0x7f; // keep the top bit clear so the magnitude stays <= 20 octets and positive
300
+ if (rnd[0] === 0) rnd[0] = 0x01; // never all-zero leading -> non-zero and no redundant sign octet
301
+ v = BigInt("0x" + rnd.toString("hex"));
302
+ } else if (typeof serial === "bigint") { v = serial; }
303
+ else if (typeof serial === "number") { if (!Number.isSafeInteger(serial)) throw E("bad-serial", "serialNumber number must be a safe integer (pass a BigInt, hex string, or Buffer for a value above 2^53-1)"); v = BigInt(serial); }
304
+ else if (typeof serial === "string") { try { v = BigInt(serial); } catch (e) { throw E("bad-serial", "serialNumber string must be a decimal or 0x-hex integer", e); } }
305
+ else if (Buffer.isBuffer(serial)) { v = serial.length ? BigInt("0x" + serial.toString("hex")) : 0n; }
306
+ else { throw E("bad-serial", "serialNumber must be a BigInt, integer, hex string, or Buffer"); }
307
+ if (v <= 0n) throw E("bad-serial", "serialNumber must be a positive integer (RFC 5280 sec. 4.1.2.2)");
308
+ var tlv = b.integer(v);
309
+ if (asn1.decode(tlv).content.length > 20) throw E("bad-serial", "serialNumber must not exceed 20 octets (RFC 5280 sec. 4.1.2.2)");
310
+ return tlv;
311
+ }
221
312
  // Synthesize the parsed-cert shape resolveSignScheme reads (subjectPublicKeyInfo.algorithm) from a raw SPKI.
222
313
  function certLikeFromSpki(spkiDer) {
223
314
  var spki = asn1.decode(spkiDer);
@@ -258,6 +349,8 @@ function makeBuilder(ctx) {
258
349
  return {
259
350
  E: E, code: code, KU_BIT: KU_BIT,
260
351
  encodeName: encodeName, isEmptyName: isEmptyName, encodeGeneralName: encodeGeneralName,
352
+ encodeGeneralNames: encodeGeneralNames, serialInteger: serialInteger, timeDer: timeDer,
353
+ requestedExtensions: requestedExtensions,
261
354
  extKeyUsage: extKeyUsage, extExtKeyUsage: extExtKeyUsage, validateBcSpec: validateBcSpec,
262
355
  extBasicConstraints: extBasicConstraints, pathLen: pathLen, extSki: extSki, extAki: extAki,
263
356
  extSan: extSan, extCertPolicies: extCertPolicies, ext: ext,
@@ -267,4 +360,14 @@ function makeBuilder(ctx) {
267
360
  };
268
361
  }
269
362
 
270
- module.exports = { makeBuilder: makeBuilder, KU_BIT: KU_BIT };
363
+ // The raw issuer / subject Name TLV of a parsed X.509 certificate (byte-identical). tbs field layout:
364
+ // [version?] serial(0) signature(1) issuer(2) validity(3) subject(4). Used where a producer must chain a
365
+ // name to an existing certificate exactly -- an issued certificate's issuer, a CMS SignerInfo issuer, an
366
+ // attribute certificate's issuerName / holder baseCertificateID.
367
+ function tbsNameField(cert, which) {
368
+ var tbs = asn1.decode(cert.tbsBytes);
369
+ var hasVersion = tbs.children[0].tagClass === "context" && tbs.children[0].tagNumber === 0;
370
+ return tbs.children[(hasVersion ? 1 : 0) + (which === "subject" ? 4 : 2)].bytes;
371
+ }
372
+
373
+ module.exports = { makeBuilder: makeBuilder, KU_BIT: KU_BIT, tbsNameField: tbsNameField };
@@ -666,6 +666,21 @@ _AC_EXT_DECODERS[_Oav("noRevAvail")] = _noRevAvail;
666
666
  _AC_EXT_DECODERS[_Oav("aaControls")] = _aaControls;
667
667
  _AC_EXT_DECODERS[_Oav("acProxying")] = _acProxying;
668
668
 
669
+ // @internal -- validate a pre-encoded AC attribute value / AC extension value against the SAME decoder
670
+ // the parser runs, so the attrcert-sign escape hatches (a caller's pre-encoded Attribute / Extension DER)
671
+ // cannot embed a structurally-malformed recognized value the strict parser would then reject. `type` /
672
+ // `extnOid` are dotted-OID strings; `valueDer` / `extnValueDer` are the raw AttributeValue / extnValue
673
+ // content Buffers (exactly what the parse loop passes). An unrecognized type returns null (stays opaque,
674
+ // no validation); a recognized-but-malformed value throws the frozen attrcert/bad-* code.
675
+ function validateAttributeValue(type, valueDer) {
676
+ var dec = _ATTR_VALUE_DECODERS[type];
677
+ return dec ? dec(valueDer) : null;
678
+ }
679
+ function validateExtensionValue(extnOid, extnValueDer) {
680
+ var dec = _AC_EXT_DECODERS[extnOid];
681
+ return dec ? dec(extnValueDer) : null;
682
+ }
683
+
669
684
  module.exports = {
670
685
  parse: parse,
671
686
  parseV1: parseV1,
@@ -673,4 +688,6 @@ module.exports = {
673
688
  pemEncode: pemEncode,
674
689
  matches: matches,
675
690
  matchesV1: matchesV1,
691
+ validateAttributeValue: validateAttributeValue,
692
+ validateExtensionValue: validateExtensionValue,
676
693
  };
package/lib/x509-sign.js CHANGED
@@ -21,7 +21,6 @@
21
21
  // cms/tsp/ocsp producing pattern); the strict schema-x509 decoder round-trips it, and that round-trip
22
22
  // -- plus OpenSSL interop -- is the divergence guard.
23
23
 
24
- var nodeCrypto = require("crypto");
25
24
  var asn1 = require("./asn1-der");
26
25
  var oid = require("./oid");
27
26
  var x509 = require("./schema-x509");
@@ -69,7 +68,7 @@ var _encodeName = _b.encodeName, _isEmptyName = _b.isEmptyName, _reqDer = _b.req
69
68
  _ext = _b.ext, _extBasicConstraints = _b.extBasicConstraints, _validateBcSpec = _b.validateBcSpec,
70
69
  _extKeyUsage = _b.extKeyUsage, _extExtKeyUsage = _b.extExtKeyUsage, _extSki = _b.extSki,
71
70
  _extAki = _b.extAki, _extSan = _b.extSan, _extCertPolicies = _b.extCertPolicies,
72
- _skiKeyId = _b.skiKeyId, _spkiKeyId = _b.spkiKeyId;
71
+ _skiKeyId = _b.skiKeyId, _spkiKeyId = _b.spkiKeyId, _serialInteger = _b.serialInteger;
73
72
 
74
73
  // ---- issuer-side extension helpers (subjectKeyIdentifier lookup + authorityKeyIdentifier) ----
75
74
 
@@ -166,41 +165,9 @@ function _buildExtensions(extSpec, ctx) {
166
165
 
167
166
  // ---- serial + validity + key plumbing --------------------------------------
168
167
 
169
- // RFC 5280 sec. 4.1.2.2 -- a positive, non-zero INTEGER of at most 20 content octets. A random
170
- // 20-octet positive serial is generated when none is supplied.
171
- function _serialInteger(serial) {
172
- var v;
173
- if (serial == null) {
174
- var rnd = nodeCrypto.randomBytes(20);
175
- rnd[0] &= 0x7f; // keep the top bit clear so the magnitude stays <= 20 octets and positive
176
- if (rnd[0] === 0) rnd[0] = 0x01; // never all-zero leading -> non-zero and no redundant sign octet
177
- v = BigInt("0x" + rnd.toString("hex"));
178
- } else if (typeof serial === "bigint") { v = serial; }
179
- else if (typeof serial === "number") { if (!Number.isInteger(serial)) throw _err("x509/bad-serial", "serialNumber must be an integer"); v = BigInt(serial); }
180
- else if (typeof serial === "string") { try { v = BigInt(serial); } catch (e) { throw _err("x509/bad-serial", "serialNumber string must be a decimal or 0x-hex integer", e); } }
181
- else if (Buffer.isBuffer(serial)) { v = serial.length ? BigInt("0x" + serial.toString("hex")) : 0n; }
182
- else { throw _err("x509/bad-serial", "serialNumber must be a BigInt, integer, hex string, or Buffer"); }
183
- if (v <= 0n) throw _err("x509/bad-serial", "serialNumber must be a positive integer (RFC 5280 sec. 4.1.2.2)");
184
- var tlv = b.integer(v);
185
- if (asn1.decode(tlv).content.length > 20) throw _err("x509/bad-serial", "serialNumber must not exceed 20 octets (RFC 5280 sec. 4.1.2.2)");
186
- return tlv;
187
- }
188
- // RFC 5280 sec. 4.1.2.5 -- UTCTime for 1950..2049, GeneralizedTime otherwise. UTCTime's two-digit year
189
- // only represents 1950..2049 (sec. 4.1.2.5.1), so a pre-1950 date MUST use GeneralizedTime (the same
190
- // arm as a date from 2050 on); using UTCTime for a pre-1950 year would be unrepresentable.
191
- function _timeDer(date, which) {
192
- guard.time.assertValid(date, _err, "x509/bad-input", "certificate " + which);
193
- var y = date.getUTCFullYear();
194
- return (y >= 1950 && y <= 2049) ? b.utcTime(date) : b.generalizedTime(date);
195
- }
196
- // The raw subject Name TLV of a parsed CA certificate (byte-identical, so the child issuer chains to
197
- // the CA subject exactly). The subject follows the optional version [0], serial, signature, issuer,
198
- // and validity in the tbsCertificate.
199
- function _caSubjectBytes(caCert) {
200
- var tbs = asn1.decode(caCert.tbsBytes);
201
- var hasVersion = tbs.children[0].tagClass === "context" && tbs.children[0].tagNumber === 0;
202
- return tbs.children[(hasVersion ? 1 : 0) + 4].bytes; // [version?] serial(0) sig(1) issuer(2) validity(3) SUBJECT(4)
203
- }
168
+ // RFC 5280 sec. 4.1.2.5 UTCTime/GeneralizedTime cutover -- the shared builder primitive (pki-build), a
169
+ // thin wrapper here so the certificate validity label reads "certificate notBefore/notAfter".
170
+ function _timeDer(date, which) { return _b.timeDer(date, "certificate " + which); }
204
171
  // A supplied issuer certificate MUST be a CA that may sign certificates: basicConstraints present AND
205
172
  // critical AND cA=TRUE (RFC 5280 sec. 4.2.1.9), and -- when a keyUsage extension is present -- the
206
173
  // keyCertSign bit (sec. 4.2.1.3). Refuse a non-CA issuer rather than mint a certificate that will not
@@ -316,7 +283,7 @@ function _sign(spec, issuer, opts) {
316
283
  issuerCert = (Buffer.isBuffer(issuer.cert) || typeof issuer.cert === "string") ? x509.parse(issuer.cert) : issuer.cert;
317
284
  if (!issuerCert || !issuerCert.tbsBytes) throw _err("x509/bad-input", "issuer.cert must be a certificate DER/PEM or a parsed certificate");
318
285
  issuerPathLen = _assertIssuerIsCa(issuerCert);
319
- issuerDer = _caSubjectBytes(issuerCert);
286
+ issuerDer = pkiBuild.tbsNameField(issuerCert, "subject");
320
287
  issuerSpki = issuerCert.subjectPublicKeyInfo.bytes;
321
288
  } else {
322
289
  issuerDer = _encodeName(issuer.name == null ? [] : issuer.name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",