@blamejs/pki 0.1.1
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 +32 -0
- package/LICENSE +201 -0
- package/MIGRATING.md +9 -0
- package/NOTICE +21 -0
- package/README.md +252 -0
- package/bin/pki.js +76 -0
- package/index.js +50 -0
- package/lib/asn1-der.js +576 -0
- package/lib/constants.js +115 -0
- package/lib/framework-error.js +130 -0
- package/lib/oid.js +219 -0
- package/lib/vendor/MANIFEST.json +4 -0
- package/lib/vendor/README.md +41 -0
- package/lib/webcrypto.js +542 -0
- package/lib/x509.js +320 -0
- package/package.json +74 -0
- package/sbom.cdx.json +61 -0
package/lib/x509.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* @module pki.x509
|
|
6
|
+
* @nav Certificates
|
|
7
|
+
* @title X.509
|
|
8
|
+
* @order 100
|
|
9
|
+
* @featured true
|
|
10
|
+
* @slug x509
|
|
11
|
+
*
|
|
12
|
+
* @intro
|
|
13
|
+
* X.509 certificate handling per RFC 5280. The seed surface is
|
|
14
|
+
* `parse` — turn a DER or PEM certificate into a structured,
|
|
15
|
+
* fully-decoded object: version, serial, signature algorithm, issuer
|
|
16
|
+
* and subject distinguished names, validity window (as real `Date`s),
|
|
17
|
+
* subject public-key info, and the extension list. The parser composes
|
|
18
|
+
* the strict DER codec and the OID registry, so every field is
|
|
19
|
+
* validated on the way in and every algorithm / attribute / extension
|
|
20
|
+
* is named where the registry knows it.
|
|
21
|
+
*
|
|
22
|
+
* The raw `tbsCertificate` bytes are returned alongside the parsed
|
|
23
|
+
* fields so a signature-verification layer can hash exactly the bytes
|
|
24
|
+
* that were signed rather than re-encoding and hoping for round-trip
|
|
25
|
+
* fidelity.
|
|
26
|
+
*
|
|
27
|
+
* @card
|
|
28
|
+
* Parse DER / PEM X.509 certificates into structured, validated fields
|
|
29
|
+
* with named algorithms, extensions, and real-`Date` validity windows.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
var constants = require("./constants");
|
|
33
|
+
var asn1 = require("./asn1-der");
|
|
34
|
+
var oid = require("./oid");
|
|
35
|
+
var frameworkError = require("./framework-error");
|
|
36
|
+
|
|
37
|
+
var CertificateError = frameworkError.CertificateError;
|
|
38
|
+
var PemError = frameworkError.PemError;
|
|
39
|
+
|
|
40
|
+
var TAGS = asn1.TAGS;
|
|
41
|
+
|
|
42
|
+
// Distinguished-name attribute short labels (RFC 4514 §3 + common use).
|
|
43
|
+
var DN_SHORT = {
|
|
44
|
+
commonName: "CN",
|
|
45
|
+
countryName: "C",
|
|
46
|
+
localityName: "L",
|
|
47
|
+
stateOrProvinceName: "ST",
|
|
48
|
+
streetAddress: "STREET",
|
|
49
|
+
organizationName: "O",
|
|
50
|
+
organizationalUnitName: "OU",
|
|
51
|
+
domainComponent: "DC",
|
|
52
|
+
surname: "SN",
|
|
53
|
+
givenName: "GN",
|
|
54
|
+
serialNumber: "SERIALNUMBER",
|
|
55
|
+
emailAddress: "emailAddress",
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ---- PEM -------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
var PEM_RE = /-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----/;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @primitive pki.x509.pemDecode
|
|
64
|
+
* @signature pki.x509.pemDecode(text, label?) -> Buffer
|
|
65
|
+
* @since 0.1.0
|
|
66
|
+
* @status stable
|
|
67
|
+
* @related pki.x509.pemEncode
|
|
68
|
+
*
|
|
69
|
+
* Extract the DER bytes from a PEM block. With `label` given (e.g.
|
|
70
|
+
* `"CERTIFICATE"`) the block type must match; without it, the first block
|
|
71
|
+
* is taken. Throws `PemError` on a missing / mismatched envelope or a
|
|
72
|
+
* non-base64 body.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* var der = pki.x509.pemDecode(pemText, "CERTIFICATE");
|
|
76
|
+
*/
|
|
77
|
+
function pemDecode(text, label) {
|
|
78
|
+
if (Buffer.isBuffer(text)) text = text.toString("latin1");
|
|
79
|
+
if (typeof text !== "string") throw new PemError("pem/bad-input", "pemDecode expects a string or Buffer");
|
|
80
|
+
if (text.length > constants.LIMITS.PEM_MAX_BYTES) throw new PemError("pem/too-large", "PEM input exceeds size cap");
|
|
81
|
+
var m = PEM_RE.exec(text);
|
|
82
|
+
if (!m) throw new PemError("pem/no-block", "no PEM block found");
|
|
83
|
+
if (label && m[1] !== label) throw new PemError("pem/label-mismatch", "expected " + JSON.stringify(label) + " block, got " + JSON.stringify(m[1]));
|
|
84
|
+
var b64 = m[2].replace(/[\r\n\t ]+/g, "");
|
|
85
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(b64)) throw new PemError("pem/bad-base64", "PEM body is not valid base64");
|
|
86
|
+
return Buffer.from(b64, "base64");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @primitive pki.x509.pemEncode
|
|
91
|
+
* @signature pki.x509.pemEncode(der, label) -> string
|
|
92
|
+
* @since 0.1.0
|
|
93
|
+
* @status stable
|
|
94
|
+
* @related pki.x509.pemDecode
|
|
95
|
+
*
|
|
96
|
+
* Wrap DER bytes in a PEM envelope with 64-column base64 lines.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* var pem = pki.x509.pemEncode(der, "CERTIFICATE");
|
|
100
|
+
*/
|
|
101
|
+
function pemEncode(der, label) {
|
|
102
|
+
if (typeof label !== "string" || label.length === 0) throw new PemError("pem/bad-label", "pemEncode requires a label");
|
|
103
|
+
var buf = Buffer.isBuffer(der) ? der : Buffer.from(der);
|
|
104
|
+
var b64 = buf.toString("base64").replace(/(.{64})/g, "$1\n").replace(/\n$/, "");
|
|
105
|
+
return "-----BEGIN " + label + "-----\n" + b64 + "\n-----END " + label + "-----\n";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---- helpers ---------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
function _algId(node) {
|
|
111
|
+
if (node.tagClass !== "universal" || node.tagNumber !== TAGS.SEQUENCE || !node.children.length) {
|
|
112
|
+
throw new CertificateError("x509/bad-algorithm-identifier", "AlgorithmIdentifier must be a non-empty SEQUENCE");
|
|
113
|
+
}
|
|
114
|
+
var dotted = asn1.read.oid(node.children[0]);
|
|
115
|
+
return {
|
|
116
|
+
oid: dotted,
|
|
117
|
+
name: oid.name(dotted) || null,
|
|
118
|
+
parameters: node.children.length > 1 ? node.children[1].bytes : null,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function _attrValueToString(node) {
|
|
123
|
+
try { return asn1.read.string(node); }
|
|
124
|
+
catch (_e) {
|
|
125
|
+
// Not a recognized string type (e.g. an ANY-typed value) — surface
|
|
126
|
+
// the raw bytes as hex so the DN is still representable.
|
|
127
|
+
return "#" + (node.content ? node.content.toString("hex") : node.bytes.toString("hex"));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function _escapeDnValue(v) {
|
|
132
|
+
return v.replace(/([,+"\\<>;])/g, "\\$1");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function _parseName(node) {
|
|
136
|
+
if (node.tagClass !== "universal" || node.tagNumber !== TAGS.SEQUENCE) {
|
|
137
|
+
throw new CertificateError("x509/bad-name", "Name must be an RDNSequence (SEQUENCE)");
|
|
138
|
+
}
|
|
139
|
+
var rdns = [];
|
|
140
|
+
var parts = [];
|
|
141
|
+
for (var i = 0; i < node.children.length; i++) {
|
|
142
|
+
var rdn = node.children[i];
|
|
143
|
+
if (rdn.tagClass !== "universal" || rdn.tagNumber !== TAGS.SET) {
|
|
144
|
+
throw new CertificateError("x509/bad-rdn", "RelativeDistinguishedName must be a SET");
|
|
145
|
+
}
|
|
146
|
+
var atvs = [];
|
|
147
|
+
var atvParts = [];
|
|
148
|
+
for (var j = 0; j < rdn.children.length; j++) {
|
|
149
|
+
var atv = rdn.children[j];
|
|
150
|
+
if (!atv.children || atv.children.length < 2) {
|
|
151
|
+
throw new CertificateError("x509/bad-atv", "AttributeTypeAndValue must be a SEQUENCE of {type, value}");
|
|
152
|
+
}
|
|
153
|
+
var typeOid = asn1.read.oid(atv.children[0]);
|
|
154
|
+
var typeName = oid.name(typeOid);
|
|
155
|
+
var value = _attrValueToString(atv.children[1]);
|
|
156
|
+
atvs.push({ type: typeOid, name: typeName || null, value: value });
|
|
157
|
+
var label = (typeName && DN_SHORT[typeName]) || typeName || typeOid;
|
|
158
|
+
atvParts.push(label + "=" + _escapeDnValue(value));
|
|
159
|
+
}
|
|
160
|
+
rdns.push(atvs);
|
|
161
|
+
parts.push(atvParts.join("+"));
|
|
162
|
+
}
|
|
163
|
+
return { rdns: rdns, dn: parts.join(", ") };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function _parseValidityTime(node) {
|
|
167
|
+
if (node.tagClass !== "universal" || (node.tagNumber !== TAGS.UTC_TIME && node.tagNumber !== TAGS.GENERALIZED_TIME)) {
|
|
168
|
+
throw new CertificateError("x509/bad-time", "Validity time must be UTCTime or GeneralizedTime");
|
|
169
|
+
}
|
|
170
|
+
return asn1.read.time(node);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function _parseExtensions(seqNode) {
|
|
174
|
+
var out = [];
|
|
175
|
+
for (var i = 0; i < seqNode.children.length; i++) {
|
|
176
|
+
var ext = seqNode.children[i];
|
|
177
|
+
if (!ext.children || ext.children.length < 2) {
|
|
178
|
+
throw new CertificateError("x509/bad-extension", "Extension must be a SEQUENCE");
|
|
179
|
+
}
|
|
180
|
+
var extnID = asn1.read.oid(ext.children[0]);
|
|
181
|
+
var critical = false;
|
|
182
|
+
var valueNode;
|
|
183
|
+
if (ext.children.length === 3) {
|
|
184
|
+
critical = asn1.read.boolean(ext.children[1]);
|
|
185
|
+
valueNode = ext.children[2];
|
|
186
|
+
} else {
|
|
187
|
+
valueNode = ext.children[1];
|
|
188
|
+
}
|
|
189
|
+
out.push({
|
|
190
|
+
oid: extnID,
|
|
191
|
+
name: oid.name(extnID) || null,
|
|
192
|
+
critical: critical,
|
|
193
|
+
value: asn1.read.octetString(valueNode),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ---- parse -----------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @primitive pki.x509.parse
|
|
203
|
+
* @signature pki.x509.parse(input) -> certificate
|
|
204
|
+
* @since 0.1.0
|
|
205
|
+
* @status stable
|
|
206
|
+
* @related pki.asn1.decode, pki.oid.name
|
|
207
|
+
*
|
|
208
|
+
* Parse a DER `Buffer` or a PEM string/Buffer into a structured
|
|
209
|
+
* certificate: `{ version, serialNumber, serialNumberHex,
|
|
210
|
+
* signatureAlgorithm, issuer, subject, validity, subjectPublicKeyInfo,
|
|
211
|
+
* extensions, tbsBytes, signatureValue }`. Distinguished names come back
|
|
212
|
+
* both as a rendered `dn` string and as structured `rdns`; the validity
|
|
213
|
+
* window is real `Date`s; `tbsBytes` is the exact signed byte range for a
|
|
214
|
+
* downstream verifier.
|
|
215
|
+
*
|
|
216
|
+
* Throws `CertificateError` when the bytes are not a well-formed
|
|
217
|
+
* certificate and `Asn1Error` when the underlying DER is malformed.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* var cert = pki.x509.parse(pemString);
|
|
221
|
+
* cert.subject.dn; // "CN=example.com, O=Example"
|
|
222
|
+
* cert.validity.notAfter; // Date
|
|
223
|
+
* cert.signatureAlgorithm.name; // "sha256WithRSAEncryption"
|
|
224
|
+
*/
|
|
225
|
+
function parse(input) {
|
|
226
|
+
var der;
|
|
227
|
+
if (typeof input === "string" || (Buffer.isBuffer(input) && input.length >= 5 && input.toString("latin1", 0, 5) === "-----")) {
|
|
228
|
+
der = pemDecode(input, "CERTIFICATE");
|
|
229
|
+
} else if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
|
|
230
|
+
der = Buffer.isBuffer(input) ? input : Buffer.from(input);
|
|
231
|
+
} else {
|
|
232
|
+
throw new CertificateError("x509/bad-input", "parse expects a DER Buffer or a PEM string");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
var root;
|
|
236
|
+
try {
|
|
237
|
+
root = asn1.decode(der);
|
|
238
|
+
} catch (e) {
|
|
239
|
+
throw new CertificateError("x509/bad-der", "certificate DER did not decode: " + e.message, e);
|
|
240
|
+
}
|
|
241
|
+
if (root.tagClass !== "universal" || root.tagNumber !== TAGS.SEQUENCE || !root.children || root.children.length !== 3) {
|
|
242
|
+
throw new CertificateError("x509/not-a-certificate", "Certificate must be a SEQUENCE of {tbsCertificate, signatureAlgorithm, signature}");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
var tbs = root.children[0];
|
|
246
|
+
var outerSigAlg = root.children[1];
|
|
247
|
+
var sigValueNode = root.children[2];
|
|
248
|
+
if (!tbs.children || tbs.children.length < 6) {
|
|
249
|
+
throw new CertificateError("x509/bad-tbs", "tbsCertificate is too short");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
var idx = 0;
|
|
253
|
+
var version = 1;
|
|
254
|
+
var first = tbs.children[0];
|
|
255
|
+
if (first.tagClass === "context" && first.tagNumber === 0) {
|
|
256
|
+
if (!first.children || !first.children.length) throw new CertificateError("x509/bad-version", "version [0] must wrap an INTEGER");
|
|
257
|
+
version = Number(asn1.read.integer(first.children[0])) + 1;
|
|
258
|
+
idx = 1;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
var serialNode = tbs.children[idx++];
|
|
262
|
+
var serialNumber = asn1.read.integer(serialNode);
|
|
263
|
+
var innerSigAlg = tbs.children[idx++];
|
|
264
|
+
var issuer = _parseName(tbs.children[idx++]);
|
|
265
|
+
var validityNode = tbs.children[idx++];
|
|
266
|
+
var subject = _parseName(tbs.children[idx++]);
|
|
267
|
+
var spkiNode = tbs.children[idx++];
|
|
268
|
+
|
|
269
|
+
if (!validityNode.children || validityNode.children.length !== 2) {
|
|
270
|
+
throw new CertificateError("x509/bad-validity", "Validity must be a SEQUENCE of {notBefore, notAfter}");
|
|
271
|
+
}
|
|
272
|
+
var validity = {
|
|
273
|
+
notBefore: _parseValidityTime(validityNode.children[0]),
|
|
274
|
+
notAfter: _parseValidityTime(validityNode.children[1]),
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
if (!spkiNode.children || spkiNode.children.length !== 2) {
|
|
278
|
+
throw new CertificateError("x509/bad-spki", "SubjectPublicKeyInfo must be a SEQUENCE of {algorithm, subjectPublicKey}");
|
|
279
|
+
}
|
|
280
|
+
var spkiBits = asn1.read.bitString(spkiNode.children[1]);
|
|
281
|
+
var subjectPublicKeyInfo = {
|
|
282
|
+
algorithm: _algId(spkiNode.children[0]),
|
|
283
|
+
publicKey: { unusedBits: spkiBits.unusedBits, bytes: spkiBits.bytes },
|
|
284
|
+
bytes: spkiNode.bytes,
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Remaining tbs children: optional issuerUniqueID [1], subjectUniqueID
|
|
288
|
+
// [2], and extensions [3] EXPLICIT. Only extensions are surfaced.
|
|
289
|
+
var extensions = [];
|
|
290
|
+
for (; idx < tbs.children.length; idx++) {
|
|
291
|
+
var t = tbs.children[idx];
|
|
292
|
+
if (t.tagClass === "context" && t.tagNumber === 3) {
|
|
293
|
+
if (!t.children || !t.children.length) throw new CertificateError("x509/bad-extensions", "extensions [3] must wrap a SEQUENCE");
|
|
294
|
+
extensions = _parseExtensions(t.children[0]);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
var sigBits = asn1.read.bitString(sigValueNode);
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
version: version,
|
|
302
|
+
serialNumber: serialNumber,
|
|
303
|
+
serialNumberHex: serialNode.content.toString("hex"),
|
|
304
|
+
signatureAlgorithm: _algId(outerSigAlg),
|
|
305
|
+
tbsSignatureAlgorithm: _algId(innerSigAlg),
|
|
306
|
+
issuer: issuer,
|
|
307
|
+
subject: subject,
|
|
308
|
+
validity: validity,
|
|
309
|
+
subjectPublicKeyInfo: subjectPublicKeyInfo,
|
|
310
|
+
extensions: extensions,
|
|
311
|
+
tbsBytes: tbs.bytes,
|
|
312
|
+
signatureValue: { unusedBits: sigBits.unusedBits, bytes: sigBits.bytes },
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
module.exports = {
|
|
317
|
+
parse: parse,
|
|
318
|
+
pemDecode: pemDecode,
|
|
319
|
+
pemEncode: pemEncode,
|
|
320
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blamejs/pki",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "blamejs contributors",
|
|
7
|
+
"homepage": "https://pkijs.com",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/blamejs/pki.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/blamejs/pki/issues"
|
|
14
|
+
},
|
|
15
|
+
"main": "index.js",
|
|
16
|
+
"bin": {
|
|
17
|
+
"pki": "bin/pki.js"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"pki",
|
|
21
|
+
"x509",
|
|
22
|
+
"asn1",
|
|
23
|
+
"der",
|
|
24
|
+
"oid",
|
|
25
|
+
"cms",
|
|
26
|
+
"pkcs7",
|
|
27
|
+
"ocsp",
|
|
28
|
+
"crl",
|
|
29
|
+
"csr",
|
|
30
|
+
"pkcs10",
|
|
31
|
+
"pkcs8",
|
|
32
|
+
"pkcs12",
|
|
33
|
+
"tsp",
|
|
34
|
+
"timestamp",
|
|
35
|
+
"certificate",
|
|
36
|
+
"post-quantum",
|
|
37
|
+
"pqc",
|
|
38
|
+
"ml-dsa",
|
|
39
|
+
"ml-kem",
|
|
40
|
+
"slh-dsa",
|
|
41
|
+
"composite-signatures",
|
|
42
|
+
"zero-dependencies",
|
|
43
|
+
"vendored-dependencies",
|
|
44
|
+
"pure-javascript",
|
|
45
|
+
"supply-chain",
|
|
46
|
+
"security-first",
|
|
47
|
+
"owns-its-stack"
|
|
48
|
+
],
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=24.18.0"
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"index.js",
|
|
54
|
+
"bin/",
|
|
55
|
+
"lib/",
|
|
56
|
+
"CHANGELOG.md",
|
|
57
|
+
"LICENSE",
|
|
58
|
+
"MIGRATING.md",
|
|
59
|
+
"NOTICE",
|
|
60
|
+
"README.md",
|
|
61
|
+
"sbom.cdx.json"
|
|
62
|
+
],
|
|
63
|
+
"scripts": {
|
|
64
|
+
"test": "node test/smoke.js",
|
|
65
|
+
"lint": "eslint --max-warnings 0 .",
|
|
66
|
+
"fuzz": "npm ci --prefix fuzz && npx --prefix fuzz jazzer fuzz/asn1-der.fuzz.js -- -max_total_time=60",
|
|
67
|
+
"gates": "node test/layer-0-primitives/codebase-patterns.test.js && node scripts/validate-source-comment-blocks.js && node scripts/check-api-snapshot.js",
|
|
68
|
+
"prepack": "node scripts/check-pack-against-gitignore.js",
|
|
69
|
+
"check:vendor-currency": "node scripts/check-vendor-currency.js"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"esbuild": "0.28.1"
|
|
73
|
+
}
|
|
74
|
+
}
|
package/sbom.cdx.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
|
+
"bomFormat": "CycloneDX",
|
|
4
|
+
"specVersion": "1.5",
|
|
5
|
+
"serialNumber": "urn:uuid:3eef8bae-7aaa-4ecf-a97b-3774a378db59",
|
|
6
|
+
"version": 1,
|
|
7
|
+
"metadata": {
|
|
8
|
+
"timestamp": "2026-07-04T12:48:34.391Z",
|
|
9
|
+
"lifecycles": [
|
|
10
|
+
{
|
|
11
|
+
"phase": "build"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"tools": [
|
|
15
|
+
{
|
|
16
|
+
"vendor": "npm",
|
|
17
|
+
"name": "cli",
|
|
18
|
+
"version": "11.16.0"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"component": {
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.1.1",
|
|
23
|
+
"type": "application",
|
|
24
|
+
"name": "pki",
|
|
25
|
+
"version": "0.1.1",
|
|
26
|
+
"scope": "required",
|
|
27
|
+
"author": "blamejs contributors",
|
|
28
|
+
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.1.1",
|
|
30
|
+
"properties": [],
|
|
31
|
+
"externalReferences": [
|
|
32
|
+
{
|
|
33
|
+
"type": "vcs",
|
|
34
|
+
"url": "git+https://github.com/blamejs/pki.git"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"type": "website",
|
|
38
|
+
"url": "https://pkijs.com"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type": "issue-tracker",
|
|
42
|
+
"url": "https://github.com/blamejs/pki/issues"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"licenses": [
|
|
46
|
+
{
|
|
47
|
+
"license": {
|
|
48
|
+
"id": "Apache-2.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"components": [],
|
|
55
|
+
"dependencies": [
|
|
56
|
+
{
|
|
57
|
+
"ref": "@blamejs/pki@0.1.1",
|
|
58
|
+
"dependsOn": []
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|