@blamejs/pki 0.3.32 → 0.3.33
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 +13 -0
- package/README.md +1 -1
- package/lib/oid.js +23 -0
- package/lib/schema-c509.js +132 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to `@blamejs/pki` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
|
|
5
5
|
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## v0.3.33 — 2026-08-06
|
|
8
|
+
|
|
9
|
+
pki.schema.c509 encodes and decodes the compact certificatePolicies value form -- a C509 certificate's policy identifiers ride their draft-20 registry integers (or unwrapped OIDs) and their CPS-URI and UserNotice qualifiers ride the specific compact CBOR shape, interoperating with a conformant C509 implementation rather than only this decoder.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- pki.schema.c509 encodes and decodes the compact value form for the certificatePolicies extension (draft-ietf-cose-cbor-encoded-cert-20 sec. 3.3): each policy identifier is a sec. 8.9 registry integer -- the CA/Browser Forum validation levels, the RFC 3779 id-cp-ipAddr resource-certificate policies, and the GSMA SGP.22 id-rspRole roles -- or an unwrapped OID for a policy outside the registry; each policy qualifier is a sec. 8.10 integer (id-qt-cps / id-qt-unotice) with its text, reconstructing the CPS pointer as a URI IA5String and the UserNotice as an explicit-text UTF8String. Both directions invert to the DER extnValue byte-for-byte, so a certificate carrying policies is the specific compact shape a conformant C509 implementation reads rather than an opaque DER byte string. The encoder is guarded: a UserNotice with a noticeRef, a non-UTF8String explicit text, or a policy-qualifier identifier outside the sec. 8.10 registry is not compact-representable and falls the whole extension back to the unwrapped-OID byte-string form instead of encoding lossily; a malformed compact value (an empty UserNotice text, a control-byte CPS URI, an unregistered policy or qualifier integer) fails closed with a typed C509Error.
|
|
14
|
+
- The OID registry gains the CA/Browser Forum certificate-policy identifiers (domain-, organization-, individual-validated, ev-guidelines), the RFC 3779 id-cp-ipAddr-asNumber policies, the GSMA SGP.22 id-rspRole roles, and the RFC 5280 id-qt policy qualifiers (cps, unotice), resolvable through pki.oid.byName / pki.oid.name.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- A C509 certificate carrying certificatePolicies now encodes to -- and decodes from -- its compact CBOR value shape rather than the unwrapped-OID byte-string form an earlier release emitted; both reconstruct the same DER, but the CBOR bytes differ, so re-encode any C509 produced by an earlier release.
|
|
19
|
+
|
|
7
20
|
## v0.3.32 — 2026-08-05
|
|
8
21
|
|
|
9
22
|
pki.schema.c509 encodes and decodes the compact general-name extension values -- a C509 certificate's subjectAltName, issuer alternative name, name constraints, CRL distribution points, authority/subject information access, and the authority key identifier issuer form now ride their specific draft-20 CBOR shape and interoperate with a conformant C509 implementation, not only this decoder.
|
package/README.md
CHANGED
|
@@ -204,7 +204,7 @@ is callable today; nothing below is a stub.
|
|
|
204
204
|
| `pki.webcrypto` | A W3C WebCrypto (`SubtleCrypto`) engine over `node:crypto` — `sign`/`verify`/`encrypt`/`decrypt`/`deriveBits`/`digest`/`generateKey`/`importKey`/`exportKey` across RSA, ECDSA, ECDH, Ed25519/Ed448, AES, HMAC, HKDF, PBKDF2, SHA — **and** post-quantum ML-DSA-44/65/87 and SLH-DSA signatures, plus ML-KEM-512/768/1024 key generation and certificate/PKCS#8 import — the RFC 9935 seed / expandedKey / both private-key CHOICE is validated fail-closed, so an OpenSSL-legacy bare-seed or an internally inconsistent key is rejected with a typed error (KEM encapsulation lands with CMS KEM-decrypt). Zero-dependency, OpenSSL-interoperable |
|
|
205
205
|
| `pki.schema` | The schema family — `parse` detects which PKI format DER / PEM encodes and routes to the right parser, `all` enumerates the registered formats, and the engine + per-format members are grouped here |
|
|
206
206
|
| `pki.schema.x509` | Parse DER / PEM certificates into structured, validated fields, with named + partly-decoded extensions — including the RFC 3739 / ETSI EN 319 412-5 qualified-certificate `qcStatements` (EU-qualified declaration, reliance limit, QSCD flag, certificate type, retention, PDS URLs, country of qualification; unknown statements preserved opaque) and the Microsoft Active Directory Certificate Services enrollment extensions (certificate template, CA version, previous-CA-certificate hash, application policies), fail-closed — `parse`, `pemDecode`, `pemEncode` |
|
|
207
|
-
| `pki.schema.c509` | Parse **and encode** C509 CBOR-encoded certificates (draft-ietf-cose-cbor-encoded-cert) — the compact CBOR profile of X.509, decoded fail-closed under deterministic CBOR; an explicit `parse` call (CBOR, not DER, so not auto-routed). `encode(input)` is the byte-exact inverse: a DER X.509 v3 certificate forward-transforms to a compact type-3 C509 whose reconstruction reproduces the original DER byte for byte (so the original signature still verifies), or a `parse` result re-emits its native array — canonical deterministic CBOR with the registry integer shorthands, the C509 compressions, and the compact draft-20 per-extension value forms — the scalar extensions (keyUsage, basicConstraints, extended key usage, subject key identifier, and more)
|
|
207
|
+
| `pki.schema.c509` | Parse **and encode** C509 CBOR-encoded certificates (draft-ietf-cose-cbor-encoded-cert) — the compact CBOR profile of X.509, decoded fail-closed under deterministic CBOR; an explicit `parse` call (CBOR, not DER, so not auto-routed). `encode(input)` is the byte-exact inverse: a DER X.509 v3 certificate forward-transforms to a compact type-3 C509 whose reconstruction reproduces the original DER byte for byte (so the original signature still verifies), or a `parse` result re-emits its native array — canonical deterministic CBOR with the registry integer shorthands, the C509 compressions, and the compact draft-20 per-extension value forms — the scalar extensions (keyUsage, basicConstraints, extended key usage, subject key identifier, and more), the general-name-bearing extensions (subjectAltName, issuer alt name, name constraints, CRL distribution points, authority/subject information access, and the full authority key identifier) over one shared GeneralNames value codec, and certificate policies (registry-integer or OID policy identifiers with their CPS-URI and UserNotice qualifiers); a certificate outside the invertible set throws a typed `C509Error` |
|
|
208
208
|
| `pki.schema.crl` | Parse DER / PEM X.509 CRLs per RFC 5280 §5 — revoked serials with real-`Date` revocation times, named + partly-decoded extensions, fail-closed — `parse`, `pemDecode`, `pemEncode` |
|
|
209
209
|
| `pki.schema.csr` | Parse DER / PEM PKCS#10 certification requests per RFC 2986 — subject DN, public key, requested attributes, signature, fail-closed — `parse`, `pemDecode`, `pemEncode` |
|
|
210
210
|
| `pki.schema.pkcs8` | Parse DER / PEM PKCS#8 private keys per RFC 5208 / 5958 — algorithm, raw key bytes, attributes, optional public key, fail-closed; encrypted keys recognized (not decrypted) — `parse`, `parseEncrypted`, `pemDecode`, `pemEncode` |
|
package/lib/oid.js
CHANGED
|
@@ -134,6 +134,29 @@ var FAMILIES = {
|
|
|
134
134
|
// id-on-MACAddress (I-D.ietf-lamps-macaddress-on) on the id-pkix 8 arc.
|
|
135
135
|
pkixOn: { base: [1, 3, 6, 1, 5, 5, 7, 8], of: { hardwareModuleName: 4, smtpUtf8Mailbox: 9, macAddress: 12 } },
|
|
136
136
|
|
|
137
|
+
// CA/Browser Forum certificate policies (draft-ietf-cose-cbor-encoded-cert sec. 8.9 ints 1-4) on the
|
|
138
|
+
// 2.23.140.1 arc: the id-cabf validation levels under .2, ev-guidelines under .1.
|
|
139
|
+
cabfPolicy: { base: [2, 23, 140, 1], of: {
|
|
140
|
+
"ev-guidelines": 1, "domain-validated": [2, 1], "organization-validated": [2, 2], "individual-validated": [2, 3] } },
|
|
141
|
+
|
|
142
|
+
// id-cp-ipAddr-asNumber (RFC 3779 / RFC 8360) on the id-pkix 14 arc -- the RPKI resource-certificate
|
|
143
|
+
// policy OIDs (sec. 8.9 ints 7-8).
|
|
144
|
+
idCp: { base: [1, 3, 6, 1, 5, 5, 7, 14], of: { "id-cp-ipAddr-asNumber": 2, "id-cp-ipAddr-asNumber-v2": 3 } },
|
|
145
|
+
|
|
146
|
+
// GSMA SGP.22 Remote SIM Provisioning roles (sec. 8.9 ints 24-38) on the 2.23.146.1.2.1 id-rspRole arc.
|
|
147
|
+
// The -v2 roles are shallow leaves .{1..7}; the non-v2 roles descend into deeper sub-arcs (draft-20).
|
|
148
|
+
gsmaRspRole: { base: [2, 23, 146, 1, 2, 1], of: {
|
|
149
|
+
"id-rspRole-ci": 0, "id-rspRole-euicc-v2": 1, "id-rspRole-euicc": [0, 0, 0, 0, 0],
|
|
150
|
+
"id-rspRole-eum-v2": 2, "id-rspRole-eum": [0, 0, 0],
|
|
151
|
+
"id-rspRole-dp-tls-v2": 3, "id-rspRole-dp-tls": [0, 0, 1, 0],
|
|
152
|
+
"id-rspRole-dp-auth-v2": 4, "id-rspRole-dp-auth": [0, 0, 1, 1],
|
|
153
|
+
"id-rspRole-dp-pb-v2": 5, "id-rspRole-dp-pb": [0, 0, 1, 2],
|
|
154
|
+
"id-rspRole-ds-tls-v2": 6, "id-rspRole-ds-tls": [0, 0, 2, 0],
|
|
155
|
+
"id-rspRole-ds-auth-v2": 7, "id-rspRole-ds-auth": [0, 0, 2, 1] } },
|
|
156
|
+
|
|
157
|
+
// id-qt policy qualifiers (RFC 5280 sec. 4.2.1.4) on the id-pkix 2 arc -- the C509 sec. 8.10 registry.
|
|
158
|
+
pkixQt: { base: [1, 3, 6, 1, 5, 5, 7, 2], of: { cps: 1, unotice: 2 } },
|
|
159
|
+
|
|
137
160
|
// OCSP (RFC 6960) on the id-pkix-ocsp arc (= id-ad-ocsp). id-pkix-ocsp-basic is
|
|
138
161
|
// the ResponseBytes.responseType this build decodes; id-pkix-ocsp-nonce (sec. 4.4.1)
|
|
139
162
|
// names the nonce extension; the remaining members name the other OCSP extensions
|
package/lib/schema-c509.js
CHANGED
|
@@ -83,6 +83,7 @@ var EXT_BY_INT = {
|
|
|
83
83
|
3: _name("subjectAltName"),
|
|
84
84
|
4: _name("basicConstraints"),
|
|
85
85
|
5: _name("cRLDistributionPoints"),
|
|
86
|
+
6: _name("certificatePolicies"),
|
|
86
87
|
7: _name("authorityKeyIdentifier"),
|
|
87
88
|
8: _name("extKeyUsage"),
|
|
88
89
|
9: _name("authorityInfoAccess"),
|
|
@@ -100,7 +101,7 @@ var EXT_COMPACT = {
|
|
|
100
101
|
subjectKeyIdentifier: 1, keyUsage: 1, basicConstraints: 1, authorityKeyIdentifier: 1,
|
|
101
102
|
extKeyUsage: 1, inhibitAnyPolicy: 1, ocspNoCheck: 1, tlsFeature: 1,
|
|
102
103
|
subjectAltName: 1, issuerAltName: 1, nameConstraints: 1, cRLDistributionPoints: 1,
|
|
103
|
-
freshestCRL: 1, authorityInfoAccess: 1, subjectInfoAccess: 1,
|
|
104
|
+
freshestCRL: 1, authorityInfoAccess: 1, subjectInfoAccess: 1, certificatePolicies: 1,
|
|
104
105
|
};
|
|
105
106
|
// sec. 8.12 Extended Key Usages registry (C509 int -> registered id-kp purpose name). A KeyPurposeId
|
|
106
107
|
// outside this set encodes as an unwrapped ~oid; a C509 int outside it fails closed on decode.
|
|
@@ -141,6 +142,28 @@ var IA_BY_INT = {
|
|
|
141
142
|
var IA_TO_INT = {};
|
|
142
143
|
Object.keys(IA_BY_INT).forEach(function (k) { IA_TO_INT[oid.byName(IA_BY_INT[k])] = Number(k); });
|
|
143
144
|
|
|
145
|
+
// sec. 8.9 C509 Certificate Policies registry (int -> registered policy OID name; the CA/Browser Forum
|
|
146
|
+
// levels, the RFC 3779 id-cp-ipAddr policies, and the GSMA SGP.22 id-rspRole roles). A policy OID outside
|
|
147
|
+
// this set encodes as ~oid; a C509 policy int outside it fails closed. CP_TO_INT is the derived inverse.
|
|
148
|
+
var CP_BY_INT = {
|
|
149
|
+
0: _name("anyPolicy"), 1: _name("domain-validated"), 2: _name("organization-validated"),
|
|
150
|
+
3: _name("individual-validated"), 4: _name("ev-guidelines"),
|
|
151
|
+
7: _name("id-cp-ipAddr-asNumber"), 8: _name("id-cp-ipAddr-asNumber-v2"),
|
|
152
|
+
24: _name("id-rspRole-ci"), 25: _name("id-rspRole-euicc-v2"), 26: _name("id-rspRole-euicc"),
|
|
153
|
+
27: _name("id-rspRole-eum-v2"), 28: _name("id-rspRole-eum"),
|
|
154
|
+
29: _name("id-rspRole-dp-tls-v2"), 30: _name("id-rspRole-dp-tls"),
|
|
155
|
+
31: _name("id-rspRole-dp-auth-v2"), 32: _name("id-rspRole-dp-auth"),
|
|
156
|
+
33: _name("id-rspRole-dp-pb-v2"), 34: _name("id-rspRole-dp-pb"),
|
|
157
|
+
35: _name("id-rspRole-ds-tls-v2"), 36: _name("id-rspRole-ds-tls"),
|
|
158
|
+
37: _name("id-rspRole-ds-auth-v2"), 38: _name("id-rspRole-ds-auth"),
|
|
159
|
+
};
|
|
160
|
+
var CP_TO_INT = {};
|
|
161
|
+
Object.keys(CP_BY_INT).forEach(function (k) { CP_TO_INT[oid.byName(CP_BY_INT[k])] = Number(k); });
|
|
162
|
+
// sec. 8.10 C509 Policies Qualifiers registry (int -> id-qt qualifier OID name; the derived inverse).
|
|
163
|
+
var PQ_BY_INT = { 1: _name("cps"), 2: _name("unotice") };
|
|
164
|
+
var PQ_TO_INT = {};
|
|
165
|
+
Object.keys(PQ_BY_INT).forEach(function (k) { PQ_TO_INT[oid.byName(PQ_BY_INT[k])] = Number(k); });
|
|
166
|
+
|
|
144
167
|
// ---- field readers (the unwrapped ~biguint / ~time / ~oid contracts; draft-20 sec. 3.1) ----
|
|
145
168
|
|
|
146
169
|
// ~biguint (sec. 3.1.2): a BARE byte string (major type 2), big-endian magnitude, the non-negative
|
|
@@ -531,6 +554,66 @@ function _serialIntContent(node) {
|
|
|
531
554
|
return (mag[0] & 0x80) ? Buffer.concat([Buffer.from([0x00]), mag]) : mag;
|
|
532
555
|
}
|
|
533
556
|
|
|
557
|
+
// ---- certificatePolicies (draft-20 sec. 3.3 / sec. 8.9 / sec. 8.10; RFC 5280 sec. 4.2.1.4) ----
|
|
558
|
+
// A CBOR text -> a UNIVERSAL IA5String TLV, running the same 7-bit + CVE-2009-2408 control-byte guard the
|
|
559
|
+
// general-name IA5 arms use (never the bare b.ia5, which permits control bytes). Non-empty (the general-name
|
|
560
|
+
// IA5 arms reject empty too; a CPSuri parity tightening -- RFC 5280 sets no SIZE floor on CPSuri).
|
|
561
|
+
function _ia5Universal(text, label) {
|
|
562
|
+
if (text.length === 0) throw _err("c509/bad-extensions", label + " must be non-empty");
|
|
563
|
+
for (var i = 0; i < text.length; i++) { if (text.charCodeAt(i) > 0x7f) throw _err("c509/bad-extensions", label + " must be a 7-bit IA5String"); }
|
|
564
|
+
guard.name.assertPrintableIa5(Buffer.from(text, "latin1"), _err, "c509/bad-extensions", label);
|
|
565
|
+
return b.ia5(text);
|
|
566
|
+
}
|
|
567
|
+
// A CBOR policyIdentifier (a sec. 8.9 registry int, or an unwrapped ~oid) -> the dotted policy OID.
|
|
568
|
+
function _policyIdToDerOid(node) {
|
|
569
|
+
if (node.majorType === 0 || node.majorType === 1) {
|
|
570
|
+
var i = Number(cbor.read.int(node)), nm = CP_BY_INT[i];
|
|
571
|
+
if (nm === undefined) throw _err("c509/bad-extensions", "a certificatePolicies policy int " + i + " has no C509 sec. 8.9 registry row");
|
|
572
|
+
return oid.byName(nm);
|
|
573
|
+
}
|
|
574
|
+
return _oidName(node, "c509/bad-extensions", "a certificatePolicies policyIdentifier").oid;
|
|
575
|
+
}
|
|
576
|
+
// The inverse: a dotted policy OID -> CBOR (a sec. 8.9 int if registered, else an unwrapped ~oid).
|
|
577
|
+
function _policyIdFromDer(dotted) {
|
|
578
|
+
var i = CP_TO_INT[dotted];
|
|
579
|
+
return i !== undefined ? cbor.build.int(BigInt(i)) : _oidCbor(dotted);
|
|
580
|
+
}
|
|
581
|
+
// (qualifierId CBOR, qualifier-text CBOR) -> one DER PolicyQualifierInfo SEQUENCE { policyQualifierId OID,
|
|
582
|
+
// qualifier ANY DEFINED BY }. The qualifierId resolves to a sec. 8.10 int ONLY: a ~oid / unregistered
|
|
583
|
+
// qualifierId has no defined qualifier:text semantics, so it is not compact-representable (fail closed, never
|
|
584
|
+
// guess the ANY type). id-qt-cps -> CPSuri IA5String; id-qt-unotice -> UserNotice { explicitText utf8String }.
|
|
585
|
+
function _qualifierToDer(qidNode, qtextNode) {
|
|
586
|
+
if (qidNode.majorType !== 0 && qidNode.majorType !== 1) throw _err("c509/bad-extensions", "a policyQualifierId must be a C509 sec. 8.10 int (a ~oid qualifier is not compact-representable)");
|
|
587
|
+
var qi = Number(cbor.read.int(qidNode)), nm = PQ_BY_INT[qi];
|
|
588
|
+
if (nm === undefined) throw _err("c509/bad-extensions", "a policyQualifierId int " + qi + " has no C509 sec. 8.10 registry row");
|
|
589
|
+
if (qtextNode.majorType !== 3) throw _err("c509/bad-extensions", "a policyQualifier value must be a CBOR text string");
|
|
590
|
+
var text = cbor.read.textString(qtextNode);
|
|
591
|
+
if (qi === 1) return b.sequence([b.oid(oid.byName("cps")), _ia5Universal(text, "a CPSuri")]); // CPSuri ::= IA5String
|
|
592
|
+
// id-qt-unotice: UserNotice ::= SEQUENCE { explicitText utf8String } -- noticeRef omitted, DisplayText SIZE 1..200 floor.
|
|
593
|
+
if (text.length === 0) throw _err("c509/bad-extensions", "a UserNotice explicitText must be non-empty (DisplayText SIZE 1..200)");
|
|
594
|
+
return b.sequence([b.oid(oid.byName("unotice")), b.sequence([b.utf8(text)])]);
|
|
595
|
+
}
|
|
596
|
+
// One DER PolicyQualifierInfo node -> [ qidCbor, qtextCbor ], or null when not compact-representable: an
|
|
597
|
+
// unregistered qualifierId, a cps qualifier whose value is not a plain IA5String, or a UserNotice that is
|
|
598
|
+
// anything but SEQUENCE { explicitText utf8String } (a noticeRef, a non-UTF8String DisplayText arm, or more
|
|
599
|
+
// than the one explicitText child) -> whole-extension ~oid fallback.
|
|
600
|
+
function _qualifierFromDer(pq) {
|
|
601
|
+
var qid = asn1.read.oid(pq.children[0]), qi = PQ_TO_INT[qid];
|
|
602
|
+
if (qi === undefined) return null;
|
|
603
|
+
var q = pq.children[1];
|
|
604
|
+
if (qi === 1) { // CPSuri ::= IA5String
|
|
605
|
+
if (q.tagClass !== "universal" || q.tagNumber !== asn1.TAGS.IA5_STRING) return null;
|
|
606
|
+
var uri; try { uri = asn1.read.string(q); } catch (_e) { return null; }
|
|
607
|
+
return [cbor.build.int(1n), cbor.build.textString(uri)];
|
|
608
|
+
}
|
|
609
|
+
// id-qt-unotice: SEQUENCE { explicitText utf8String } ONLY (noticeRef absent).
|
|
610
|
+
if (q.tagClass !== "universal" || q.tagNumber !== asn1.TAGS.SEQUENCE || !q.children || q.children.length !== 1) return null;
|
|
611
|
+
var dt = q.children[0];
|
|
612
|
+
if (dt.tagClass !== "universal" || dt.tagNumber !== asn1.TAGS.UTF8_STRING) return null;
|
|
613
|
+
var txt; try { txt = asn1.read.string(dt); } catch (_e2) { return null; }
|
|
614
|
+
return [cbor.build.int(2n), cbor.build.textString(txt)];
|
|
615
|
+
}
|
|
616
|
+
|
|
534
617
|
// One CBOR DistributionPointName [ fullName, reasons, cRLIssuer ] -> one DER DistributionPoint SEQUENCE.
|
|
535
618
|
// distributionPoint [0] is EXPLICIT (it wraps the DistributionPointName CHOICE); fullName [0], reasons [1],
|
|
536
619
|
// cRLIssuer [2] are IMPLICIT (RFC 5280 sec. 4.2.1.13) -- mixing these is the classic byte-exactness trap.
|
|
@@ -662,6 +745,31 @@ function _extValueToDer(name, node) {
|
|
|
662
745
|
if (node.majorType === 3) return b.sequence([b.sequence([b.explicit(0, b.contextConstructed(0, b.contextPrimitive(6, _ia5Bytes(node, 6))))])]);
|
|
663
746
|
if (node.majorType !== 4 || !node.children || node.children.length < 1) throw _err("c509/bad-extensions", "a " + name + " value must be a CBOR array of DistributionPoints or a bare URI text (sec. 3.3)");
|
|
664
747
|
return b.sequence(node.children.map(function (dp) { return _dpToDer(dp); }));
|
|
748
|
+
case "certificatePolicies": { // [ pid, [ *(qid, qtext) ], ... ] -> SEQUENCE OF PolicyInformation
|
|
749
|
+
if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "a certificatePolicies value must be a CBOR array");
|
|
750
|
+
var cpKids = node.children;
|
|
751
|
+
if (cpKids.length === 0 || cpKids.length % 2 !== 0) throw _err("c509/bad-extensions", "a certificatePolicies array must be non-empty (policyIdentifier, qualifiers) pairs (sec. 3.3)");
|
|
752
|
+
var polInfos = [], seenPolicy = {};
|
|
753
|
+
for (var cpi = 0; cpi + 1 < cpKids.length; cpi += 2) {
|
|
754
|
+
var quals = cpKids[cpi + 1];
|
|
755
|
+
if (quals.majorType !== 4 || !quals.children) throw _err("c509/bad-extensions", "a certificatePolicies qualifiers slot must be a CBOR array");
|
|
756
|
+
// A policy OID MUST NOT appear more than once (RFC 5280 sec. 4.2.1.4); the toolkit's own DER decoder
|
|
757
|
+
// rejects a duplicate (schema-pkix certExtensionDecoders), so reconstruct fails closed rather than
|
|
758
|
+
// producing a certificatePolicies extension that decoder would reject.
|
|
759
|
+
var policyOid = _policyIdToDerOid(cpKids[cpi]);
|
|
760
|
+
if (seenPolicy[policyOid]) throw _err("c509/bad-extensions", "a certificatePolicies policy OID must not appear more than once (RFC 5280 sec. 4.2.1.4)");
|
|
761
|
+
seenPolicy[policyOid] = true;
|
|
762
|
+
var polFields = [b.oid(policyOid)];
|
|
763
|
+
if (quals.children.length) {
|
|
764
|
+
if (quals.children.length % 2 !== 0) throw _err("c509/bad-extensions", "a policyQualifiers array must be (policyQualifierId, qualifier) pairs");
|
|
765
|
+
var pqDers = [];
|
|
766
|
+
for (var qk = 0; qk + 1 < quals.children.length; qk += 2) pqDers.push(_qualifierToDer(quals.children[qk], quals.children[qk + 1]));
|
|
767
|
+
polFields.push(b.sequence(pqDers)); // policyQualifiers SEQUENCE OF PolicyQualifierInfo
|
|
768
|
+
}
|
|
769
|
+
polInfos.push(b.sequence(polFields));
|
|
770
|
+
}
|
|
771
|
+
return b.sequence(polInfos);
|
|
772
|
+
}
|
|
665
773
|
default:
|
|
666
774
|
throw _err("c509/bad-extensions", "extension " + name + " has no compact value decoder");
|
|
667
775
|
}
|
|
@@ -775,6 +883,29 @@ function _extValueFromDer(name, der) {
|
|
|
775
883
|
if (dpResults.length === 1 && dpResults[0].oneUri != null && dpResults[0].noReasons && dpResults[0].noIssuer) return cbor.build.textString(dpResults[0].oneUri);
|
|
776
884
|
return cbor.build.array(dpResults.map(function (r) { return r.triple; }));
|
|
777
885
|
}
|
|
886
|
+
case "certificatePolicies": { // SEQUENCE OF PolicyInformation -> [ pid, [ *(qid, qtext) ], ... ]
|
|
887
|
+
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1) return null;
|
|
888
|
+
var cpOut = [];
|
|
889
|
+
for (var pli = 0; pli < node.children.length; pli++) {
|
|
890
|
+
var pol = node.children[pli];
|
|
891
|
+
if (pol.tagClass !== "universal" || pol.tagNumber !== asn1.TAGS.SEQUENCE || !pol.children || pol.children.length < 1 || pol.children.length > 2) return null;
|
|
892
|
+
cpOut.push(_policyIdFromDer(asn1.read.oid(pol.children[0])));
|
|
893
|
+
var qArr = [];
|
|
894
|
+
if (pol.children.length === 2) {
|
|
895
|
+
var qseq = pol.children[1];
|
|
896
|
+
if (qseq.tagClass !== "universal" || qseq.tagNumber !== asn1.TAGS.SEQUENCE || !qseq.children || !qseq.children.length) return null; // policyQualifiers SIZE 1..MAX
|
|
897
|
+
for (var qj = 0; qj < qseq.children.length; qj++) {
|
|
898
|
+
var pq = qseq.children[qj];
|
|
899
|
+
if (pq.tagClass !== "universal" || pq.tagNumber !== asn1.TAGS.SEQUENCE || !pq.children || pq.children.length !== 2) return null;
|
|
900
|
+
var qpair = _qualifierFromDer(pq);
|
|
901
|
+
if (qpair == null) return null; // any non-cps/unotice qualifier -> whole-ext fallback
|
|
902
|
+
qArr.push(qpair[0]); qArr.push(qpair[1]);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
cpOut.push(cbor.build.array(qArr)); // empty [] when policyQualifiers absent
|
|
906
|
+
}
|
|
907
|
+
return cbor.build.array(cpOut);
|
|
908
|
+
}
|
|
778
909
|
default:
|
|
779
910
|
return null;
|
|
780
911
|
}
|
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:d39fe4d9-9f98-4126-8a96-8660ce1805e0",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-08-
|
|
8
|
+
"timestamp": "2026-08-06T04:08:23.299Z",
|
|
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.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.3.33",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.3.
|
|
25
|
+
"version": "0.3.33",
|
|
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.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.3.33",
|
|
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.
|
|
57
|
+
"ref": "@blamejs/pki@0.3.33",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|