@blamejs/pki 0.8.31 → 0.8.49
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 +401 -0
- package/README.md +66 -15
- package/bin/pki.js +716 -30
- package/examples/issue-a-certificate.js +90 -0
- package/examples/post-quantum.js +66 -0
- package/examples/read-a-certificate.js +56 -0
- package/examples/revoke-and-check.js +86 -0
- package/examples/sign-and-verify-cms.js +63 -0
- package/index.js +18 -5
- package/lib/acme.js +223 -74
- package/lib/alt-sig.js +441 -0
- package/lib/asn1-der.js +48 -25
- package/lib/attrcert-sign.js +27 -19
- package/lib/cbor-det.js +24 -14
- package/lib/cmc-build.js +9 -2
- package/lib/cmc-verify.js +14 -3
- package/lib/cmp-build.js +17 -5
- package/lib/cmp-session.js +24 -11
- package/lib/cmp-verify.js +28 -19
- package/lib/cms-compress.js +3 -1
- package/lib/cms-decrypt.js +102 -7
- package/lib/cms-encrypt.js +59 -7
- package/lib/cms-sign.js +31 -9
- package/lib/cms-verify.js +22 -18
- package/lib/composite-kem.js +30 -18
- package/lib/composite-sig.js +29 -14
- package/lib/constants.js +26 -0
- package/lib/crl-sign.js +111 -19
- package/lib/crmf-sign.js +167 -15
- package/lib/csr-sign.js +117 -6
- package/lib/ct.js +1008 -58
- package/lib/est.js +29 -16
- package/lib/framework-error.js +12 -0
- package/lib/guard-async.js +1 -1
- package/lib/guard-bytes.js +103 -20
- package/lib/guard-crypto.js +205 -3
- package/lib/guard-encoding.js +6 -1
- package/lib/guard-identifier.js +55 -8
- package/lib/guard-intrinsic.js +43 -0
- package/lib/guard-json.js +122 -9
- package/lib/guard-limits.js +12 -1
- package/lib/guard-list.js +63 -8
- package/lib/guard-name.js +167 -20
- package/lib/guard-range.js +43 -5
- package/lib/guard-text.js +41 -0
- package/lib/guard-verdict.js +39 -8
- package/lib/hpke.js +688 -42
- package/lib/http-transport.js +156 -4
- package/lib/identity-match.js +4 -4
- package/lib/inspect.js +33 -6
- package/lib/jose.js +93 -25
- package/lib/key.js +18 -13
- package/lib/lint.js +165 -1
- package/lib/merkle.js +233 -27
- package/lib/ocsp.js +27 -14
- package/lib/oid.js +14 -4
- package/lib/path-validate.js +143 -61
- package/lib/pkcs12-build.js +7 -2
- package/lib/pki-build.js +31 -7
- package/lib/possession.js +575 -0
- package/lib/related-cert.js +708 -0
- package/lib/revocation-fetch.js +6 -6
- package/lib/scep.js +15 -5
- package/lib/schema-c509.js +5 -2
- package/lib/schema-cmc.js +1 -1
- package/lib/schema-cmp.js +8 -8
- package/lib/schema-cms.js +2 -15
- package/lib/schema-csr.js +69 -0
- package/lib/schema-engine.js +10 -3
- package/lib/schema-pkix.js +228 -2
- package/lib/schema-tsp.js +7 -6
- package/lib/sign-scheme.js +22 -9
- package/lib/sigstore.js +684 -168
- package/lib/sleep.js +8 -1
- package/lib/smime.js +22 -1
- package/lib/tlog.js +1235 -0
- package/lib/tls-cert-compress.js +25 -8
- package/lib/tsp-sign.js +120 -35
- package/lib/tuf.js +1395 -0
- package/lib/validator-tpm.js +4 -1
- package/lib/verify-seam.js +7 -1
- package/lib/webauthn-mds.js +41 -4
- package/lib/webauthn.js +43 -25
- package/lib/webcrypto.js +48 -46
- package/lib/x509-sign.js +225 -19
- package/package.json +2 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* Issue a certificate from your own CA, then validate the path.
|
|
6
|
+
*
|
|
7
|
+
* node examples/issue-a-certificate.js
|
|
8
|
+
*
|
|
9
|
+
* Generates a CA key and a self-signed CA certificate, generates a leaf key, issues a leaf
|
|
10
|
+
* certificate from the CA, and validates leaf-against-CA through RFC 5280 path validation.
|
|
11
|
+
* Nothing is written to disk and no network is used.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
var pki = require("@blamejs/pki");
|
|
15
|
+
|
|
16
|
+
// A subject is a distinguished name, and `pki.x509.parseDn(s).bytes` is how a DN string becomes one
|
|
17
|
+
// the builders take. Handing them the string directly is not an error and not the same thing: a bare
|
|
18
|
+
// string is read as a COMMON NAME, so "CN=Example CA, O=Example" would certify one commonName whose
|
|
19
|
+
// value is that whole text.
|
|
20
|
+
function dn(s) { return pki.x509.parseDn(s).bytes; }
|
|
21
|
+
|
|
22
|
+
async function main() {
|
|
23
|
+
// A CA key and the self-signed certificate that names it. basicConstraints cA marks it as a CA
|
|
24
|
+
// and keyCertSign is what lets it sign other certificates; a CA missing either is refused as an
|
|
25
|
+
// issuer during path validation.
|
|
26
|
+
var caKeys = await pki.key.generate("ML-DSA-65");
|
|
27
|
+
var caSpki = await pki.key.export(caKeys.publicKey);
|
|
28
|
+
var caKey = await pki.key.export(caKeys.privateKey);
|
|
29
|
+
var now = new Date();
|
|
30
|
+
var caDer = await pki.x509.sign({
|
|
31
|
+
subject: dn("CN=Example CA, O=Example"),
|
|
32
|
+
subjectPublicKey: caSpki,
|
|
33
|
+
notBefore: now,
|
|
34
|
+
notAfter: new Date(now.getTime() + pki.C.TIME.days(3650)),
|
|
35
|
+
extensions: {
|
|
36
|
+
basicConstraints: { cA: true },
|
|
37
|
+
keyUsage: ["keyCertSign", "cRLSign"],
|
|
38
|
+
subjectKeyIdentifier: true,
|
|
39
|
+
},
|
|
40
|
+
}, { key: caKey });
|
|
41
|
+
console.log("ca subject: " + pki.schema.x509.parse(caDer).subject.dn);
|
|
42
|
+
|
|
43
|
+
// The leaf. Its key is its own; the CA only signs over its public half.
|
|
44
|
+
var leafKeys = await pki.key.generate("ML-DSA-65");
|
|
45
|
+
var leafDer = await pki.x509.sign({
|
|
46
|
+
subject: dn("CN=host.example"),
|
|
47
|
+
subjectPublicKey: await pki.key.export(leafKeys.publicKey),
|
|
48
|
+
notBefore: now,
|
|
49
|
+
notAfter: new Date(now.getTime() + pki.C.TIME.days(90)),
|
|
50
|
+
extensions: {
|
|
51
|
+
subjectAltName: ["host.example", "www.host.example"],
|
|
52
|
+
keyUsage: ["digitalSignature"],
|
|
53
|
+
extendedKeyUsage: ["serverAuth"],
|
|
54
|
+
},
|
|
55
|
+
}, { key: caKey, cert: caDer });
|
|
56
|
+
var leaf = pki.schema.x509.parse(leafDer);
|
|
57
|
+
console.log("leaf subject: " + leaf.subject.dn);
|
|
58
|
+
console.log("leaf issuer: " + leaf.issuer.dn);
|
|
59
|
+
console.log("leaf serial: " + leaf.serialNumberHex.length / 2 + " bytes of entropy");
|
|
60
|
+
|
|
61
|
+
// Path validation is the verb that decides whether the leaf is trusted, and it is separate from
|
|
62
|
+
// having issued it: the anchor is supplied as a name and a key, never as bytes to be trusted.
|
|
63
|
+
var ca = pki.schema.x509.parse(caDer);
|
|
64
|
+
var result = await pki.path.validate([leafDer], {
|
|
65
|
+
time: now,
|
|
66
|
+
trustAnchors: {
|
|
67
|
+
name: ca.subject,
|
|
68
|
+
publicKey: ca.subjectPublicKeyInfo.bytes,
|
|
69
|
+
algorithm: ca.subjectPublicKeyInfo.algorithm.oid,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
console.log("path valid: " + result.valid);
|
|
73
|
+
|
|
74
|
+
// The same validation one day after the leaf expires. A certificate outside its validity window
|
|
75
|
+
// is not trusted, which is the check an expiry outage is.
|
|
76
|
+
var expired = await pki.path.validate([leafDer], {
|
|
77
|
+
time: new Date(now.getTime() + pki.C.TIME.days(91)),
|
|
78
|
+
trustAnchors: {
|
|
79
|
+
name: ca.subject,
|
|
80
|
+
publicKey: ca.subjectPublicKeyInfo.bytes,
|
|
81
|
+
algorithm: ca.subjectPublicKeyInfo.algorithm.oid,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
console.log("after expiry: " + expired.valid);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
main().then(null, function (e) {
|
|
88
|
+
console.error(e && (e.code ? e.code + ": " + e.message : e.message));
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* Post-quantum signatures and key establishment, through the same verbs as everything else.
|
|
6
|
+
*
|
|
7
|
+
* node examples/post-quantum.js
|
|
8
|
+
*
|
|
9
|
+
* ML-DSA (FIPS 204) and SLH-DSA (FIPS 205) sign certificates here the way ECDSA and Ed25519 do:
|
|
10
|
+
* an algorithm is a registry entry, so nothing about issuing, verifying or validating a path
|
|
11
|
+
* changes with the suite. ML-KEM (FIPS 203) does key establishment. No network.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
var pki = require("@blamejs/pki");
|
|
15
|
+
|
|
16
|
+
async function certificateFor(alg) {
|
|
17
|
+
var keys = await pki.key.generate(alg);
|
|
18
|
+
var now = new Date();
|
|
19
|
+
var der = await pki.x509.sign({
|
|
20
|
+
subject: pki.x509.parseDn("CN=" + alg.toLowerCase() + ".example").bytes,
|
|
21
|
+
subjectPublicKey: await pki.key.export(keys.publicKey),
|
|
22
|
+
notBefore: now,
|
|
23
|
+
notAfter: new Date(now.getTime() + pki.C.TIME.days(365)),
|
|
24
|
+
extensions: { keyUsage: ["digitalSignature"] },
|
|
25
|
+
}, { key: await pki.key.export(keys.privateKey) });
|
|
26
|
+
return { der: der, cert: pki.schema.x509.parse(der) };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function main() {
|
|
30
|
+
// The same call shape across three signature families. The name the certificate carries comes
|
|
31
|
+
// from the OID registry, so a reader sees the algorithm rather than a dotted string.
|
|
32
|
+
for (var alg of ["ML-DSA-65", "SLH-DSA-SHA2-128s", "Ed25519"]) {
|
|
33
|
+
var made = await certificateFor(alg);
|
|
34
|
+
console.log(alg.padEnd(18) + " certificate " + String(made.der.length).padStart(6) +
|
|
35
|
+
" bytes, signed with " + made.cert.signatureAlgorithm.name);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// A post-quantum certificate validates through the same path validator, with no suite-specific
|
|
39
|
+
// argument: the anchor names its own key algorithm and the verifier resolves the rest.
|
|
40
|
+
var ca = await certificateFor("ML-DSA-65");
|
|
41
|
+
var result = await pki.path.validate([ca.der], {
|
|
42
|
+
time: new Date(),
|
|
43
|
+
trustAnchors: {
|
|
44
|
+
name: ca.cert.subject,
|
|
45
|
+
publicKey: ca.cert.subjectPublicKeyInfo.bytes,
|
|
46
|
+
algorithm: ca.cert.subjectPublicKeyInfo.algorithm.oid,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
console.log("path validates with a post-quantum anchor: " + result.valid);
|
|
50
|
+
|
|
51
|
+
// ML-KEM is key establishment rather than signing: the sender encapsulates to the recipient's
|
|
52
|
+
// public key and both sides end up with the same secret, which neither transmitted. This is the
|
|
53
|
+
// pair the CMS KEMRecipientInfo arm rides on.
|
|
54
|
+
var kemAlg = { name: "ML-KEM-768" };
|
|
55
|
+
var kem = await pki.key.generate("ML-KEM-768");
|
|
56
|
+
var sent = await pki.webcrypto.subtle.encapsulateBits(kemAlg, kem.publicKey);
|
|
57
|
+
var received = await pki.webcrypto.subtle.decapsulateBits(kemAlg, kem.privateKey, sent.ciphertext);
|
|
58
|
+
var agreed = Buffer.compare(Buffer.from(sent.sharedKey), Buffer.from(received)) === 0;
|
|
59
|
+
console.log("ml-kem-768 ciphertext " + sent.ciphertext.byteLength + " bytes, shared secret " +
|
|
60
|
+
sent.sharedKey.byteLength + " bytes, both sides agree: " + agreed);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
main().then(null, function (e) {
|
|
64
|
+
console.error(e && (e.code ? e.code + ": " + e.message : e.message));
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* Read a certificate: parse it, render it, lint it, dump its bytes.
|
|
6
|
+
*
|
|
7
|
+
* node examples/read-a-certificate.js
|
|
8
|
+
*
|
|
9
|
+
* The four ways this toolkit reads one certificate, from the structured fields a program uses to
|
|
10
|
+
* the byte offsets a debugger needs. No network, nothing written to disk.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
var pki = require("@blamejs/pki");
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
// A certificate to read. Any DER or PEM certificate works here; this one is generated so the
|
|
17
|
+
// example needs no fixture.
|
|
18
|
+
var keys = await pki.key.generate("Ed25519");
|
|
19
|
+
var now = new Date();
|
|
20
|
+
var der = await pki.x509.sign({
|
|
21
|
+
subject: pki.x509.parseDn("CN=host.example, O=Example").bytes,
|
|
22
|
+
subjectPublicKey: await pki.key.export(keys.publicKey),
|
|
23
|
+
notBefore: now,
|
|
24
|
+
notAfter: new Date(now.getTime() + pki.C.TIME.days(90)),
|
|
25
|
+
extensions: { subjectAltName: ["host.example"], keyUsage: ["digitalSignature"] },
|
|
26
|
+
}, { key: await pki.key.export(keys.privateKey) });
|
|
27
|
+
|
|
28
|
+
// 1. parse -- the structured fields, validated. This is what a program reads.
|
|
29
|
+
var cert = pki.schema.x509.parse(der);
|
|
30
|
+
console.log("subject: " + cert.subject.dn);
|
|
31
|
+
console.log("algorithm: " + cert.signatureAlgorithm.name);
|
|
32
|
+
console.log("extensions: " + cert.extensions.map(function (e) { return e.name || e.oid; }).join(", "));
|
|
33
|
+
|
|
34
|
+
// 2. inspect -- the report a person reads, the pure-JS equivalent of `openssl x509 -text`.
|
|
35
|
+
// pki.inspect.any detects the format, so the same call reads a CRL or a CMS message.
|
|
36
|
+
var report = pki.inspect.any(der);
|
|
37
|
+
console.log("report lines: " + report.split("\n").length);
|
|
38
|
+
|
|
39
|
+
// 3. lint -- graded conformance findings. The data path never throws: malformed bytes become a
|
|
40
|
+
// fatal finding rather than an exception, so a survey over a corpus cannot be stopped by one
|
|
41
|
+
// bad file. `worst` is the highest severity present, and "pass" means nothing was found.
|
|
42
|
+
var lint = pki.lint.certificate(der);
|
|
43
|
+
console.log("lint worst: " + (lint.worst || "pass"));
|
|
44
|
+
console.log("lint findings: " + lint.findings.length);
|
|
45
|
+
|
|
46
|
+
// 4. asn1 -- the structural dump, in the shape `openssl asn1parse` prints. This one renders every
|
|
47
|
+
// value in the file, so it is the verb to reach for on a file that is wrong rather than one
|
|
48
|
+
// holding a key.
|
|
49
|
+
var dump = pki.inspect.asn1(der);
|
|
50
|
+
console.log("first node: " + dump.split("\n")[1].trim());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
main().then(null, function (e) {
|
|
54
|
+
console.error(e && (e.code ? e.code + ": " + e.message : e.message));
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* Revoke a certificate and see a path stop trusting it (CRL, RFC 5280 section 5).
|
|
6
|
+
*
|
|
7
|
+
* node examples/revoke-and-check.js
|
|
8
|
+
*
|
|
9
|
+
* Issues a leaf, validates it, publishes a CRL that revokes it, and validates again with that
|
|
10
|
+
* CRL supplied. The second answer is the one revocation exists to produce. No network.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
var pki = require("@blamejs/pki");
|
|
14
|
+
|
|
15
|
+
function dn(s) { return pki.x509.parseDn(s).bytes; }
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
var now = new Date();
|
|
19
|
+
var caKeys = await pki.key.generate("Ed25519");
|
|
20
|
+
var caKey = await pki.key.export(caKeys.privateKey);
|
|
21
|
+
var caDer = await pki.x509.sign({
|
|
22
|
+
subject: dn("CN=Revocation CA"),
|
|
23
|
+
subjectPublicKey: await pki.key.export(caKeys.publicKey),
|
|
24
|
+
notBefore: now,
|
|
25
|
+
notAfter: new Date(now.getTime() + pki.C.TIME.days(3650)),
|
|
26
|
+
extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"], subjectKeyIdentifier: true },
|
|
27
|
+
}, { key: caKey });
|
|
28
|
+
var ca = pki.schema.x509.parse(caDer);
|
|
29
|
+
|
|
30
|
+
var leafKeys = await pki.key.generate("Ed25519");
|
|
31
|
+
var leafDer = await pki.x509.sign({
|
|
32
|
+
subject: dn("CN=doomed.example"),
|
|
33
|
+
subjectPublicKey: await pki.key.export(leafKeys.publicKey),
|
|
34
|
+
notBefore: now,
|
|
35
|
+
notAfter: new Date(now.getTime() + pki.C.TIME.days(90)),
|
|
36
|
+
extensions: { keyUsage: ["digitalSignature"] },
|
|
37
|
+
}, { key: caKey, cert: caDer });
|
|
38
|
+
var leaf = pki.schema.x509.parse(leafDer);
|
|
39
|
+
console.log("leaf serial: " + leaf.serialNumberHex.slice(0, 16) + "...");
|
|
40
|
+
|
|
41
|
+
var anchor = {
|
|
42
|
+
name: ca.subject,
|
|
43
|
+
publicKey: ca.subjectPublicKeyInfo.bytes,
|
|
44
|
+
algorithm: ca.subjectPublicKeyInfo.algorithm.oid,
|
|
45
|
+
};
|
|
46
|
+
var before = await pki.path.validate([leafDer], { time: now, trustAnchors: anchor });
|
|
47
|
+
console.log("before crl: " + before.valid);
|
|
48
|
+
|
|
49
|
+
// The CA publishes a CRL naming that serial. nextUpdate is required by RFC 5280 section 5.1.2.5
|
|
50
|
+
// and is what tells a relying party how long the list may be believed.
|
|
51
|
+
// The issuer argument names the CA through its certificate, so the spec does not repeat it: the
|
|
52
|
+
// name a CRL is signed under is the one in that certificate.
|
|
53
|
+
var crlDer = await pki.crl.sign({
|
|
54
|
+
thisUpdate: now,
|
|
55
|
+
nextUpdate: new Date(now.getTime() + pki.C.TIME.days(7)),
|
|
56
|
+
crlNumber: 1n,
|
|
57
|
+
revoked: [{
|
|
58
|
+
serialNumber: BigInt("0x" + leaf.serialNumberHex),
|
|
59
|
+
revocationDate: now,
|
|
60
|
+
reason: "keyCompromise",
|
|
61
|
+
}],
|
|
62
|
+
}, { key: caKey, cert: caDer });
|
|
63
|
+
var crl = pki.schema.crl.parse(crlDer);
|
|
64
|
+
console.log("crl entries: " + crl.revokedCertificates.length +
|
|
65
|
+
", next update in " + Math.round((crl.nextUpdate - crl.thisUpdate) / pki.C.TIME.days(1)) + " days");
|
|
66
|
+
|
|
67
|
+
// The same path, with the CRL supplied. Revocation is a pluggable hook rather than a built-in
|
|
68
|
+
// fetch: pki.path.crlChecker answers from the CRLs you hand it, and a validator given none
|
|
69
|
+
// cannot know a certificate was revoked. Supplying it is what changes the answer.
|
|
70
|
+
var after = await pki.path.validate([leafDer], {
|
|
71
|
+
time: now,
|
|
72
|
+
trustAnchors: anchor,
|
|
73
|
+
revocationChecker: pki.path.crlChecker([crlDer]),
|
|
74
|
+
});
|
|
75
|
+
console.log("after crl: " + after.valid);
|
|
76
|
+
var reasons = [];
|
|
77
|
+
(after.results || []).forEach(function (r) {
|
|
78
|
+
(r.checks || []).forEach(function (c) { if (c.ok === false) reasons.push(c.code); });
|
|
79
|
+
});
|
|
80
|
+
console.log("refused for: " + (reasons.join(", ") || "(nothing)"));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main().then(null, function (e) {
|
|
84
|
+
console.error(e && (e.code ? e.code + ": " + e.message : e.message));
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* Sign a document and verify the signature (CMS SignedData, RFC 5652).
|
|
6
|
+
*
|
|
7
|
+
* node examples/sign-and-verify-cms.js
|
|
8
|
+
*
|
|
9
|
+
* Signs bytes into a SignedData, verifies it, and shows what changes when the content is
|
|
10
|
+
* detached and when the signature is over content that has since been altered. No network,
|
|
11
|
+
* nothing written to disk.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
var pki = require("@blamejs/pki");
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
// A signer needs a key and a certificate naming it: a signature that no certificate binds to an
|
|
18
|
+
// identity proves only that someone held a key.
|
|
19
|
+
var keys = await pki.key.generate("ML-DSA-65");
|
|
20
|
+
var key = await pki.key.export(keys.privateKey);
|
|
21
|
+
var now = new Date();
|
|
22
|
+
var cert = await pki.x509.sign({
|
|
23
|
+
subject: pki.x509.parseDn("CN=signer.example").bytes,
|
|
24
|
+
subjectPublicKey: await pki.key.export(keys.publicKey),
|
|
25
|
+
notBefore: now,
|
|
26
|
+
notAfter: new Date(now.getTime() + pki.C.TIME.days(365)),
|
|
27
|
+
extensions: { keyUsage: ["digitalSignature"] },
|
|
28
|
+
}, { key: key });
|
|
29
|
+
|
|
30
|
+
var content = Buffer.from("the bytes under signature", "utf8");
|
|
31
|
+
var signed = await pki.cms.sign(content, { cert: cert, key: key });
|
|
32
|
+
console.log("signeddata: " + signed.length + " bytes");
|
|
33
|
+
|
|
34
|
+
// Verifying against the signer certificate. `verify` answers about the signature; whether that
|
|
35
|
+
// certificate is trusted is what pki.path.validate answers, and they are separate questions.
|
|
36
|
+
var ok = await pki.cms.verify(signed, { certs: [cert] });
|
|
37
|
+
console.log("verified: " + ok.valid);
|
|
38
|
+
console.log("signers: " + ok.signers.length);
|
|
39
|
+
|
|
40
|
+
// Detached: the signature travels without the content, so the verifier is given the content it
|
|
41
|
+
// already has. This is how a signature over a large artifact is shipped.
|
|
42
|
+
var detached = await pki.cms.sign(content, { cert: cert, key: key }, { detached: true });
|
|
43
|
+
var okDetached = await pki.cms.verify(detached, { certs: [cert], content: content });
|
|
44
|
+
console.log("detached: " + detached.length + " bytes, verified " + okDetached.valid);
|
|
45
|
+
|
|
46
|
+
// The same detached signature against content that changed by one byte. A signature is over
|
|
47
|
+
// bytes, so this is the answer that makes it worth having.
|
|
48
|
+
var altered = Buffer.from(content);
|
|
49
|
+
altered[0] ^= 0x01;
|
|
50
|
+
var verdict;
|
|
51
|
+
try {
|
|
52
|
+
var bad = await pki.cms.verify(detached, { certs: [cert], content: altered });
|
|
53
|
+
verdict = bad.valid ? "accepted (this would be a defect)" : "rejected";
|
|
54
|
+
} catch (e) {
|
|
55
|
+
verdict = "rejected (" + (e.code || "threw") + ")";
|
|
56
|
+
}
|
|
57
|
+
console.log("altered: " + verdict);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
main().then(null, function (e) {
|
|
61
|
+
console.error(e && (e.code ? e.code + ": " + e.message : e.message));
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
});
|
package/index.js
CHANGED
|
@@ -37,6 +37,10 @@ var schema = require("./lib/schema-all");
|
|
|
37
37
|
var path = require("./lib/path-validate");
|
|
38
38
|
var identity = require("./lib/identity-match");
|
|
39
39
|
var ct = require("./lib/ct");
|
|
40
|
+
var tuf = require("./lib/tuf");
|
|
41
|
+
var relatedCert = require("./lib/related-cert");
|
|
42
|
+
var altSig = require("./lib/alt-sig");
|
|
43
|
+
var possession = require("./lib/possession");
|
|
40
44
|
var tls = require("./lib/tls-cert-compress");
|
|
41
45
|
var cms = require("./lib/cms-verify");
|
|
42
46
|
var smime = require("./lib/smime");
|
|
@@ -53,6 +57,7 @@ var key = require("./lib/key");
|
|
|
53
57
|
var pkcs12 = require("./lib/pkcs12-build");
|
|
54
58
|
var pkcs11 = require("./lib/pkcs11-uri");
|
|
55
59
|
var merkle = require("./lib/merkle");
|
|
60
|
+
var tlog = require("./lib/tlog");
|
|
56
61
|
var shbs = require("./lib/shbs");
|
|
57
62
|
var hpke = require("./lib/hpke");
|
|
58
63
|
var compositeKem = require("./lib/composite-kem");
|
|
@@ -97,6 +102,10 @@ module.exports = {
|
|
|
97
102
|
// SCT-list extension a certificate / OCSP response carries; the signature is
|
|
98
103
|
// surfaced raw for external verification (pki.ct.reconstructSignedData).
|
|
99
104
|
ct: ct,
|
|
105
|
+
tuf: tuf,
|
|
106
|
+
relatedCert: relatedCert,
|
|
107
|
+
altSig: altSig,
|
|
108
|
+
possession: possession,
|
|
100
109
|
// 'tls' is TLS handshake structures that carry certificates -- RFC 8879 compressed
|
|
101
110
|
// certificate messages (zlib / brotli / zstd) and the RFC 8446 Certificate message
|
|
102
111
|
// inside them, decoded to per-entry certificate DER. Structure only; no handshake.
|
|
@@ -166,15 +175,19 @@ module.exports = {
|
|
|
166
175
|
// verifyConsistency fold an audit / consistency proof and constant-time-
|
|
167
176
|
// compare to a checkpoint root. Pure sync hashing, fail-closed, transport-free.
|
|
168
177
|
merkle: merkle,
|
|
178
|
+
// `tlog` is the C2SP transparency-log envelope over those hashes: a signed
|
|
179
|
+
// note, the checkpoint body a log signs into one, and the tile paths a static
|
|
180
|
+
// log serves them beside. Verifying only; nothing signs and nothing fetches.
|
|
181
|
+
tlog: tlog,
|
|
169
182
|
// `shbs` verifies stateful hash-based signatures -- HSS/LMS (RFC 8554),
|
|
170
183
|
// carried by RFC 9802 (X.509) and RFC 9708 (CMS), profiled by NIST SP 800-208.
|
|
171
184
|
// VERIFY ONLY by design: stateful signing requires atomic one-time-key index
|
|
172
185
|
// state that belongs in an HSM, so this module never mints a signature.
|
|
173
186
|
shbs: shbs,
|
|
174
|
-
// `hpke` is RFC 9180 Hybrid Public Key Encryption -- the KEM +
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
187
|
+
// `hpke` is RFC 9180 Hybrid Public Key Encryption -- the KEM + key schedule +
|
|
188
|
+
// AEAD context construction behind TLS ECH / MLS / OHTTP. Pure composition over
|
|
189
|
+
// node:crypto; five DHKEM suites, three ML-KEM suites and three PQ/T hybrids,
|
|
190
|
+
// over the two-stage HKDF and single-stage SHAKE key schedules, all four modes.
|
|
178
191
|
hpke: hpke,
|
|
179
192
|
// `kem` is composite ML-KEM key establishment (draft-ietf-lamps-pq-composite-kem):
|
|
180
193
|
// pki.kem.encapsulate / decapsulate over a post-quantum ML-KEM hybridized with a
|
|
@@ -197,7 +210,7 @@ module.exports = {
|
|
|
197
210
|
// rejectUnauthorized always on, a TLS floor, a streaming response cap, and a timeout.
|
|
198
211
|
// Curated to the public `https` factory; the module's `isBlockedIp` classifier is an
|
|
199
212
|
// internal helper pki.path.build reuses (require the module), not a public surface.
|
|
200
|
-
transport: { https: transport.https },
|
|
213
|
+
transport: { https: transport.https, peerChain: transport.peerChain },
|
|
201
214
|
// `jose` is the RFC 7515 Flattened JWS + RFC 7638 JWK-thumbprint layer: a strict
|
|
202
215
|
// base64url codec, a bounded duplicate-key-rejecting JSON reader, profiled
|
|
203
216
|
// sign/verify (ACME-outer / EAB-inner / keyChange-inner), and an alg registry
|