@blamejs/pki 0.3.0 → 0.3.2

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/x509-sign.js CHANGED
@@ -21,16 +21,15 @@
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");
28
27
  var signScheme = require("./sign-scheme");
29
- var compositeSig = require("./composite-sig");
30
28
  var guard = require("./guard-all");
31
29
  var frameworkError = require("./framework-error");
32
30
  var schema = require("./schema-engine");
33
31
  var pkix = require("./schema-pkix");
32
+ var pkiBuild = require("./pki-build");
34
33
 
35
34
  var CertificateError = frameworkError.CertificateError;
36
35
  // The x509 schema namespace + Name parser -- the SAME RDNSequence parser pki.schema.x509.parse uses, so
@@ -59,189 +58,25 @@ var KNOWN_EXT_KEYS = {
59
58
  certificatePolicies: 1, certificatePoliciesCritical: 1,
60
59
  };
61
60
 
62
- // KeyUsage named-bit positions (RFC 5280 sec. 4.2.1.3); contentCommitment is the RFC 5280 rename of
63
- // the X.509 nonRepudiation bit (1).
64
- var KU_BIT = {
65
- digitalSignature: 0, nonRepudiation: 1, contentCommitment: 1, keyEncipherment: 2,
66
- dataEncipherment: 3, keyAgreement: 4, keyCertSign: 5, cRLSign: 6, encipherOnly: 7, decipherOnly: 8,
67
- };
61
+ // The shared PKIX producing primitives (lib/pki-build.js), bound to the x509 namespace so they keep the
62
+ // frozen x509/* codes. Thin local aliases let the x509 call sites read unchanged. csr-sign binds the same
63
+ // builder to its own namespace (Hard rule #5 -- one encode definition, no per-format hand-roll).
64
+ var _b = pkiBuild.makeBuilder({ ErrorClass: CertificateError, prefix: "x509", O: O, NS: NS, NAME_SCHEMA: NAME_SCHEMA, SPKI_SCHEMA: SPKI_SCHEMA, EXT_DECODERS: EXT_DECODERS });
65
+ var _encodeName = _b.encodeName, _isEmptyName = _b.isEmptyName, _reqDer = _b.reqDer,
66
+ _assertValidSpki = _b.assertValidSpki, _assertValidExtension = _b.assertValidExtension,
67
+ _certLikeFromSpki = _b.certLikeFromSpki, _assertCertVerifies = _b.assertSignatureVerifies,
68
+ _ext = _b.ext, _extBasicConstraints = _b.extBasicConstraints, _validateBcSpec = _b.validateBcSpec,
69
+ _extKeyUsage = _b.extKeyUsage, _extExtKeyUsage = _b.extExtKeyUsage, _extSki = _b.extSki,
70
+ _extAki = _b.extAki, _extSan = _b.extSan, _extCertPolicies = _b.extCertPolicies,
71
+ _skiKeyId = _b.skiKeyId, _spkiKeyId = _b.spkiKeyId, _serialInteger = _b.serialInteger;
68
72
 
69
- // ---- distinguished name encoding (RFC 5280 sec. 4.1.2.4) -------------------
73
+ // ---- issuer-side extension helpers (subjectKeyIdentifier lookup + authorityKeyIdentifier) ----
70
74
 
71
- // countryName is a PrintableString, emailAddress an IA5String; every other new-certificate attribute
72
- // is a UTF8String (Teletex/BMP/Universal are backward-compat only and never emitted).
73
- function _atvString(attrName, value) {
74
- if (attrName === "countryName") {
75
- // countryName is a PrintableString SIZE(2) -- the two-letter ISO 3166 alpha-2 code (RFC 5280 /
76
- // X.520). Reject any other length at config-time.
77
- if (String(value).length !== 2) throw _err("x509/bad-name", "countryName must be a two-letter ISO 3166 code (PrintableString SIZE(2))");
78
- return b.printable(value);
79
- }
80
- if (attrName === "emailAddress") return b.ia5(value);
81
- return b.utf8(value);
82
- }
83
- function _encodeAtv(attrName, value) {
84
- if (value == null || value === "") throw _err("x509/bad-name", "the " + attrName + " attribute value must be a non-empty string");
85
- var typeOid;
86
- try { typeOid = O(attrName); }
87
- catch (e) { throw _err("x509/bad-name", "unknown distinguished-name attribute " + JSON.stringify(attrName), e); }
88
- var valueTlv;
89
- try { valueTlv = _atvString(attrName, value); }
90
- catch (e) { if (e instanceof CertificateError) throw e; throw _err("x509/bad-name", "the " + attrName + " value has characters invalid for its string type", e); }
91
- return b.sequence([b.oid(typeOid), valueTlv]);
92
- }
93
- function _encodeRdn(rdnSpec) {
94
- if (!rdnSpec || typeof rdnSpec !== "object" || Buffer.isBuffer(rdnSpec)) throw _err("x509/bad-name", "each RDN must be an object of { attributeName: value }");
95
- var keys = Object.keys(rdnSpec);
96
- if (!keys.length) throw _err("x509/bad-name", "an RDN must carry at least one attribute");
97
- // build.set DER-sorts the AttributeTypeAndValue members (X.690 SET-OF ordering) for a multi-valued RDN.
98
- return b.set(keys.map(function (k) { return _encodeAtv(k, rdnSpec[k]); }));
99
- }
100
- // A DN spec -> RDNSequence DER. A string is shorthand for a single commonName RDN; a Buffer is raw
101
- // pre-encoded Name DER (the escape hatch). An empty array yields an empty RDNSequence (a subject MAY
102
- // be empty with a critical SAN; the issuer non-empty rule is enforced by the caller).
103
- function _encodeName(spec) {
104
- if (Buffer.isBuffer(spec)) { _assertValidNameDer(spec); return spec; }
105
- if (typeof spec === "string") spec = [{ commonName: spec }];
106
- if (!Array.isArray(spec)) throw _err("x509/bad-name", "a name must be a string, an array of RDNs, or raw Name DER");
107
- return b.sequence(spec.map(_encodeRdn));
108
- }
109
- // A raw Name DER is embedded verbatim, so validate it is a well-formed RDNSequence -- a SEQUENCE OF
110
- // RelativeDistinguishedName, each a non-empty SET OF AttributeTypeAndValue{ type OID, value } -- before
111
- // it is embedded. An empty RDNSequence is permitted (an empty subject; the issuer non-empty rule is
112
- // enforced separately). asn1.decode already rejects trailing bytes.
113
- function _assertValidNameDer(der) {
114
- var node;
115
- try { node = asn1.decode(der); }
116
- catch (e) { throw _err("x509/bad-name", "the raw Name DER is not valid DER", e); }
117
- // Full validation: walk the raw Name through the exact RDNSequence parser the certificate parser
118
- // uses (the SET-OF structure, the AttributeTypeAndValue arity, the DirectoryString value types),
119
- // not a partial hand-rolled shape check -- so an embedded raw Name is exactly what parses back.
120
- try { schema.walk(NAME_SCHEMA, node, NS); }
121
- catch (e) {
122
- if (e instanceof CertificateError || (e && e.name === "Asn1Error")) throw e;
123
- throw _err("x509/bad-name", "the raw Name DER is not a well-formed distinguished name", e);
124
- }
125
- }
126
- function _isEmptyName(nameDer) { return asn1.decode(nameDer).children.length === 0; }
127
-
128
- // ---- GeneralName encoding (RFC 5280 sec. 4.2.1.6) --------------------------
129
-
130
- function _ia5Content(s) {
131
- s = String(s);
132
- for (var i = 0; i < s.length; i++) {
133
- if (s.charCodeAt(i) > 0x7F) throw _err("x509/bad-input", "value requires 7-bit ASCII (IA5String): " + JSON.stringify(s));
134
- }
135
- return Buffer.from(s, "latin1");
136
- }
137
- function _encodeGeneralName(entry) {
138
- if (!entry || typeof entry !== "object" || Buffer.isBuffer(entry)) throw _err("x509/bad-input", "a GeneralName must be an object with exactly one name form");
139
- var keys = Object.keys(entry);
140
- if (keys.length !== 1) throw _err("x509/bad-input", "a GeneralName entry must have exactly one form, got " + keys.length);
141
- var k = keys[0], v = entry[k];
142
- if (v == null || v === "") throw _err("x509/bad-input", "an empty GeneralName value is not permitted (RFC 5280 sec. 4.2.1.6)");
143
- switch (k) {
144
- // IMPLICIT [1]/[2]/[6] IA5String -- context-primitive over the raw ASCII content.
145
- case "rfc822Name": return b.contextPrimitive(1, _ia5Content(v));
146
- case "dNSName": return b.contextPrimitive(2, _ia5Content(v));
147
- case "uniformResourceIdentifier": case "uri": return b.contextPrimitive(6, _ia5Content(v));
148
- // IMPLICIT [7] OCTET STRING -- 4 octets (IPv4) or 16 (IPv6).
149
- case "iPAddress":
150
- if (!Buffer.isBuffer(v) || (v.length !== 4 && v.length !== 16)) throw _err("x509/bad-input", "iPAddress must be a 4- or 16-octet Buffer");
151
- return b.contextPrimitive(7, v);
152
- // [4] Name -- Name is a CHOICE, so the context tag is necessarily EXPLICIT.
153
- case "directoryName": return b.explicit(4, _encodeName(v));
154
- default: throw _err("x509/bad-input", "unsupported GeneralName form " + JSON.stringify(k) + " (supported: rfc822Name, dNSName, uniformResourceIdentifier, iPAddress, directoryName)");
155
- }
156
- }
157
-
158
- // ---- extension-value encoders (the inverse of certExtensionDecoders) -------
159
-
160
- function _extKeyUsage(names) {
161
- if (!Array.isArray(names) || !names.length) throw _err("x509/bad-input", "keyUsage must assert at least one bit (RFC 5280 sec. 4.2.1.3)");
162
- var positions = names.map(function (n) {
163
- var pos = KU_BIT[n];
164
- if (pos == null) throw _err("x509/bad-input", "unknown keyUsage bit " + JSON.stringify(n));
165
- return pos;
166
- });
167
- return b.namedBitString(positions); // minimal NamedBitList DER (X.690 sec. 11.2.2)
168
- }
169
- function _extExtKeyUsage(names) {
170
- if (!Array.isArray(names) || !names.length) throw _err("x509/bad-input", "extendedKeyUsage must list at least one KeyPurposeId");
171
- return b.sequence(names.map(function (n) {
172
- var purposeOid;
173
- try { purposeOid = O(n); }
174
- catch (e) { throw _err("x509/bad-input", "unknown extendedKeyUsage purpose " + JSON.stringify(n), e); }
175
- return b.oid(purposeOid);
176
- }));
177
- }
178
- // Validate a basicConstraints spec object before it drives the coherence checks + encoding: cA must be
179
- // an explicit boolean (a truthy non-true value like 1 or "true" would silently encode as a non-CA),
180
- // pathLen a non-negative integer, and only cA/pathLen/critical are recognized.
181
- function _validateBcSpec(bc) {
182
- if (bc.cA != null && typeof bc.cA !== "boolean") throw _err("x509/bad-input", "basicConstraints cA must be a boolean");
183
- if (bc.critical != null && typeof bc.critical !== "boolean") throw _err("x509/bad-input", "basicConstraints critical must be a boolean");
184
- if (bc.pathLen != null) _pathLen(bc.pathLen);
185
- Object.keys(bc).forEach(function (k) {
186
- if (k !== "cA" && k !== "pathLen" && k !== "critical") throw _err("x509/bad-input", "unknown basicConstraints field " + JSON.stringify(k));
187
- });
188
- }
189
- function _extBasicConstraints(spec) {
190
- var children = [];
191
- if (spec.cA === true) children.push(b.boolean(true)); // cA=FALSE omitted (DER DEFAULT)
192
- if (spec.pathLen != null) children.push(b.integer(_pathLen(spec.pathLen)));
193
- return b.sequence(children);
194
- }
195
- function _pathLen(v) {
196
- if (typeof v !== "number" || !isFinite(v) || v < 0 || (v | 0) !== v) throw _err("x509/bad-input", "basicConstraints pathLenConstraint must be a non-negative integer");
197
- return BigInt(v);
198
- }
199
- function _extSki(keyid) { return b.octetString(keyid); }
200
- function _extAki(keyid) { return b.sequence([b.contextPrimitive(0, keyid)]); } // keyIdentifier [0] IMPLICIT OCTET STRING
201
- function _extSan(entries) {
202
- if (!Array.isArray(entries) || !entries.length) throw _err("x509/bad-input", "subjectAltName must carry at least one GeneralName");
203
- return b.sequence(entries.map(_encodeGeneralName));
204
- }
205
- function _extCertPolicies(names) {
206
- if (!Array.isArray(names) || !names.length) throw _err("x509/bad-input", "certificatePolicies must list at least one policy OID");
207
- var seen = {};
208
- return b.sequence(names.map(function (n) {
209
- var pOid;
210
- try { pOid = O(n); }
211
- catch (e) { throw _err("x509/bad-input", "unknown certificate policy " + JSON.stringify(n), e); }
212
- if (seen[pOid]) throw _err("x509/bad-input", "duplicate certificate policy " + JSON.stringify(n) + " (RFC 5280 sec. 4.2.1.4)");
213
- seen[pOid] = true;
214
- return b.sequence([b.oid(pOid)]); // PolicyInformation ::= SEQUENCE { policyIdentifier OID }
215
- }));
216
- }
217
- // Wrap an encoded extension value in Extension ::= SEQUENCE { extnID, critical?, extnValue }. A
218
- // FALSE critical bit is omitted (DER DEFAULT), so the boolean appears only when the extension is critical.
219
- function _ext(oidStr, critical, valueDer) {
220
- var children = [b.oid(oidStr)];
221
- if (critical) children.push(b.boolean(true));
222
- children.push(b.octetString(valueDer));
223
- return b.sequence(children);
224
- }
225
-
226
- // The SHA-1 subjectKeyIdentifier (RFC 5280 sec. 4.2.1.2 method 1): the hash of the subjectPublicKey
227
- // BIT STRING CONTENT (past the unused-bits octet), NOT the whole SPKI or the whole BIT STRING TLV.
228
- function _spkiKeyId(spkiDer) {
229
- var keyBytes = asn1.read.bitString(asn1.decode(spkiDer).children[1]).bytes;
230
- // nosemgrep: pki-weak-hash-md5-sha1 -- RFC 5280 sec. 4.2.1.2 method 1 DEFINES the subjectKeyIdentifier
231
- // as the SHA-1 of the subjectPublicKey; this is a key identifier, not a signature or a
232
- // collision-resistance use, and the algorithm is fixed by the standard.
233
- return nodeCrypto.createHash("sha1").update(keyBytes).digest();
234
- }
235
75
  function _skiValueOf(caCert) {
236
76
  var ext = (caCert.extensions || []).filter(function (e) { return e.oid === OID_SKI; })[0];
237
77
  if (ext) { try { return asn1.read.octetString(asn1.decode(ext.value)); } catch (_e) { /* fall through to re-derive from the issuer SPKI */ } }
238
78
  return null;
239
79
  }
240
- function _skiKeyId(val, spkiDer) {
241
- if (Buffer.isBuffer(val)) return val;
242
- if (val === true) return _spkiKeyId(spkiDer);
243
- throw _err("x509/bad-input", "subjectKeyIdentifier must be true (auto-derive) or a Buffer key id");
244
- }
245
80
  function _akiKeyId(val, ctx) {
246
81
  if (Buffer.isBuffer(val)) return val;
247
82
  if (val === true) {
@@ -330,25 +165,6 @@ function _buildExtensions(extSpec, ctx) {
330
165
 
331
166
  // ---- serial + validity + key plumbing --------------------------------------
332
167
 
333
- // RFC 5280 sec. 4.1.2.2 -- a positive, non-zero INTEGER of at most 20 content octets. A random
334
- // 20-octet positive serial is generated when none is supplied.
335
- function _serialInteger(serial) {
336
- var v;
337
- if (serial == null) {
338
- var rnd = nodeCrypto.randomBytes(20);
339
- rnd[0] &= 0x7f; // keep the top bit clear so the magnitude stays <= 20 octets and positive
340
- if (rnd[0] === 0) rnd[0] = 0x01; // never all-zero leading -> non-zero and no redundant sign octet
341
- v = BigInt("0x" + rnd.toString("hex"));
342
- } else if (typeof serial === "bigint") { v = serial; }
343
- else if (typeof serial === "number") { if (!Number.isInteger(serial)) throw _err("x509/bad-serial", "serialNumber must be an integer"); v = BigInt(serial); }
344
- 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); } }
345
- else if (Buffer.isBuffer(serial)) { v = serial.length ? BigInt("0x" + serial.toString("hex")) : 0n; }
346
- else { throw _err("x509/bad-serial", "serialNumber must be a BigInt, integer, hex string, or Buffer"); }
347
- if (v <= 0n) throw _err("x509/bad-serial", "serialNumber must be a positive integer (RFC 5280 sec. 4.1.2.2)");
348
- var tlv = b.integer(v);
349
- 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)");
350
- return tlv;
351
- }
352
168
  // RFC 5280 sec. 4.1.2.5 -- UTCTime for 1950..2049, GeneralizedTime otherwise. UTCTime's two-digit year
353
169
  // only represents 1950..2049 (sec. 4.1.2.5.1), so a pre-1950 date MUST use GeneralizedTime (the same
354
170
  // arm as a date from 2050 on); using UTCTime for a pre-1950 year would be unrepresentable.
@@ -357,65 +173,6 @@ function _timeDer(date, which) {
357
173
  var y = date.getUTCFullYear();
358
174
  return (y >= 1950 && y <= 2049) ? b.utcTime(date) : b.generalizedTime(date);
359
175
  }
360
- function _reqDer(v, what) {
361
- if (Buffer.isBuffer(v)) return v;
362
- if (v instanceof Uint8Array) return Buffer.from(v);
363
- throw _err("x509/bad-input", what + " must be a DER Buffer");
364
- }
365
- // A well-formed SubjectPublicKeyInfo is a SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey
366
- // BIT STRING }. The subject key is embedded verbatim (b.raw), so reject a malformed one AT ISSUANCE
367
- // rather than emitting a certificate that fails to parse. (asn1.decode already rejects trailing bytes.)
368
- function _assertValidSpki(spkiDer, what) {
369
- var node;
370
- try { node = asn1.decode(spkiDer); }
371
- catch (e) { throw _err("x509/bad-input", what + " is not valid DER", e); }
372
- // Full validation: run it through the SAME SubjectPublicKeyInfo parser pki.schema.x509.parse uses
373
- // (the AlgorithmIdentifier + the subjectPublicKey BIT STRING), not a partial hand-rolled shape check.
374
- try { schema.walk(SPKI_SCHEMA, node, NS); }
375
- catch (e) {
376
- if (e instanceof CertificateError || (e && e.name === "Asn1Error")) throw e;
377
- throw _err("x509/bad-input", what + " is not a well-formed SubjectPublicKeyInfo", e);
378
- }
379
- }
380
- // A well-formed Extension is a SEQUENCE { extnID OID, critical BOOLEAN OPTIONAL, extnValue OCTET
381
- // STRING }. A pre-encoded (array-form) extension is embedded verbatim, so validate its shape here.
382
- function _assertValidExtension(der, idx) {
383
- var n;
384
- try { n = asn1.decode(der); }
385
- catch (e) { throw _err("x509/bad-input", "pre-encoded extension [" + idx + "] is not valid DER", e); }
386
- if (n.tagNumber !== asn1.TAGS.SEQUENCE || n.tagClass !== "universal" || !n.children || n.children.length < 2 || n.children.length > 3) throw _err("x509/bad-input", "pre-encoded extension [" + idx + "] must be an Extension SEQUENCE { extnID, critical?, extnValue }");
387
- try { asn1.read.oid(n.children[0]); }
388
- catch (e) { throw _err("x509/bad-input", "pre-encoded extension [" + idx + "] extnID is not an OBJECT IDENTIFIER", e); }
389
- if (n.children.length === 3) {
390
- var crit;
391
- try { crit = asn1.read.boolean(n.children[1]); }
392
- catch (e) { throw _err("x509/bad-input", "pre-encoded extension [" + idx + "] critical must be a BOOLEAN", e); }
393
- // DER DEFAULT: a FALSE criticality MUST be omitted, so an explicitly-encoded critical=FALSE is
394
- // non-canonical (RFC 5280 sec. 4.2 / X.690 sec. 11.5).
395
- if (crit !== true) throw _err("x509/bad-input", "pre-encoded extension [" + idx + "] critical=FALSE must be omitted (DER DEFAULT)");
396
- }
397
- var last = n.children[n.children.length - 1];
398
- if (last.tagNumber !== asn1.TAGS.OCTET_STRING || last.tagClass !== "universal") throw _err("x509/bad-input", "pre-encoded extension [" + idx + "] extnValue must be an OCTET STRING");
399
- }
400
- // Synthesize the parsed-cert shape resolveSignScheme reads (it only needs subjectPublicKeyInfo.algorithm)
401
- // from a raw SPKI DER: { algorithm: { oid, parameters } }. `parameters` is the raw params TLV (or undefined).
402
- function _certLikeFromSpki(spkiDer) {
403
- var spki = asn1.decode(spkiDer);
404
- if (!spki.children || !spki.children.length) throw _err("x509/bad-input", "the signing key SPKI is not a SubjectPublicKeyInfo");
405
- var alg = spki.children[0];
406
- var keyOid;
407
- try { keyOid = asn1.read.oid(alg.children[0]); }
408
- catch (e) { throw _err("x509/bad-input", "the signing key SPKI algorithm is not an OID", e); }
409
- return { subjectPublicKeyInfo: { algorithm: { oid: keyOid, parameters: alg.children.length > 1 ? alg.children[1].bytes : undefined } } };
410
- }
411
- // The raw subject Name TLV of a parsed CA certificate (byte-identical, so the child issuer chains to
412
- // the CA subject exactly). The subject follows the optional version [0], serial, signature, issuer,
413
- // and validity in the tbsCertificate.
414
- function _caSubjectBytes(caCert) {
415
- var tbs = asn1.decode(caCert.tbsBytes);
416
- var hasVersion = tbs.children[0].tagClass === "context" && tbs.children[0].tagNumber === 0;
417
- return tbs.children[(hasVersion ? 1 : 0) + 4].bytes; // [version?] serial(0) sig(1) issuer(2) validity(3) SUBJECT(4)
418
- }
419
176
  // A supplied issuer certificate MUST be a CA that may sign certificates: basicConstraints present AND
420
177
  // critical AND cA=TRUE (RFC 5280 sec. 4.2.1.9), and -- when a keyUsage extension is present -- the
421
178
  // keyCertSign bit (sec. 4.2.1.3). Refuse a non-CA issuer rather than mint a certificate that will not
@@ -455,33 +212,6 @@ function _issuedCaInfo(extSpec) {
455
212
  }
456
213
  return { cA: false, pathLen: null };
457
214
  }
458
- // Confirm the produced signature verifies under the ISSUER public key, so a signing key that does not
459
- // correspond to it cannot silently yield a certificate that fails to chain. Verifying the actual
460
- // signature is key-type-agnostic -- it holds for a PKCS#8 key, a WebCrypto CryptoKey, or any signer --
461
- // where deriving-and-comparing the public key cannot (a non-extractable CryptoKey has no exportable
462
- // public half). A composite {mldsa, trad} arm is the caller's to pair and is skipped here.
463
- function _assertCertVerifies(tbsDer, sig, issuerSpki, scheme) {
464
- if (scheme.composite) {
465
- // Verify BOTH composite component signatures against the composite public key.
466
- return compositeSig.compositeVerify(issuerSpki, sig, tbsDer, scheme.composite, CertificateError, "x509/unsupported-algorithm", "x509/bad-input").then(function (r) {
467
- if (!r.ok) throw _err("x509/bad-input", "the composite signing key does not correspond to the issuer public key -- the certificate would not chain");
468
- });
469
- }
470
- var pub;
471
- try { pub = nodeCrypto.createPublicKey({ key: issuerSpki, format: "der", type: "spki" }); }
472
- catch (e) { throw _err("x509/bad-input", "the issuer public key could not be imported for the chain self-check", e); }
473
- var s = scheme.sign, ok;
474
- try {
475
- if (s.name === "ECDSA") ok = nodeCrypto.verify(scheme.digest, tbsDer, { key: pub, dsaEncoding: "der" }, sig);
476
- else if (s.name === "RSA-PSS") ok = nodeCrypto.verify(scheme.digest, tbsDer, { key: pub, padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: s.saltLength }, sig);
477
- else if (s.name === "RSASSA-PKCS1-v1_5") ok = nodeCrypto.verify(scheme.digest, tbsDer, pub, sig);
478
- // allow:eddsa-verify-without-loworder-gate -- this is a self-check that OUR just-produced signature
479
- // verifies under the issuer key the caller controls, not a security verify of an untrusted EdDSA
480
- // signature, so the low-order-point gate (a forged-signature defense) does not apply.
481
- else ok = nodeCrypto.verify(null, tbsDer, pub, sig); // Ed25519 / Ed448 / ML-DSA / SLH-DSA
482
- } catch (e) { throw _err("x509/bad-input", "the chain self-check could not run against the issuer public key", e); }
483
- if (!ok) throw _err("x509/bad-input", "the signing key does not correspond to the issuer public key -- the certificate would not chain");
484
- }
485
215
  // Does the extensions spec carry a subjectAltName that will be emitted critical? The object form forces
486
216
  // SAN critical when the subject is empty (so any subjectAltName qualifies); the pre-encoded array form
487
217
  // is decoded to look for an Extension whose extnID is subjectAltName with a TRUE critical flag.
@@ -558,7 +288,7 @@ function _sign(spec, issuer, opts) {
558
288
  issuerCert = (Buffer.isBuffer(issuer.cert) || typeof issuer.cert === "string") ? x509.parse(issuer.cert) : issuer.cert;
559
289
  if (!issuerCert || !issuerCert.tbsBytes) throw _err("x509/bad-input", "issuer.cert must be a certificate DER/PEM or a parsed certificate");
560
290
  issuerPathLen = _assertIssuerIsCa(issuerCert);
561
- issuerDer = _caSubjectBytes(issuerCert);
291
+ issuerDer = pkiBuild.tbsNameField(issuerCert, "subject");
562
292
  issuerSpki = issuerCert.subjectPublicKeyInfo.bytes;
563
293
  } else {
564
294
  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.0",
3
+ "version": "0.3.2",
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",
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:6f299c9a-151b-4a48-9580-bc19e1ab66b0",
5
+ "serialNumber": "urn:uuid:3787cd37-e9d0-465d-bed3-0f6822edb52c",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-07-17T06:00:11.553Z",
8
+ "timestamp": "2026-07-17T08:27:56.310Z",
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.3.0",
22
+ "bom-ref": "@blamejs/pki@0.3.2",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.3.0",
25
+ "version": "0.3.2",
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.3.0",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.3.2",
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.3.0",
57
+ "ref": "@blamejs/pki@0.3.2",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]