@blamejs/pki 0.5.7 → 0.5.9
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 +418 -386
- package/MIGRATING.md +43 -0
- package/README.md +12 -12
- package/lib/acme.js +31 -31
- package/lib/asn1-der.js +10 -10
- package/lib/attrcert-sign.js +19 -19
- package/lib/byte-reader.js +6 -6
- package/lib/byte-writer.js +5 -5
- package/lib/cbor-det.js +27 -24
- package/lib/cmc-build.js +97 -92
- package/lib/cmc-verify.js +106 -30
- package/lib/cmp-build.js +25 -25
- package/lib/cmp-session.js +70 -70
- package/lib/cmp-verify.js +71 -57
- package/lib/cms-compress.js +7 -7
- package/lib/cms-decrypt.js +90 -72
- package/lib/cms-encrypt.js +32 -32
- package/lib/cms-sign.js +74 -55
- package/lib/cms-verify.js +97 -75
- package/lib/composite-sig.js +13 -13
- package/lib/constants.js +4 -4
- package/lib/crl-sign.js +22 -22
- package/lib/crl-verify.js +7 -6
- package/lib/crmf-sign.js +14 -14
- package/lib/csr-sign.js +8 -8
- package/lib/ct.js +37 -37
- package/lib/edwards-point.js +7 -7
- package/lib/est.js +98 -55
- package/lib/framework-error.js +5 -5
- package/lib/guard-all.js +5 -3
- package/lib/guard-async.js +4 -4
- package/lib/guard-bytes.js +79 -79
- package/lib/guard-compress.js +17 -17
- package/lib/guard-crypto.js +1 -1
- package/lib/guard-der.js +152 -0
- package/lib/guard-encoding.js +15 -15
- package/lib/guard-header.js +3 -3
- package/lib/guard-identifier.js +16 -16
- package/lib/guard-json.js +15 -15
- package/lib/guard-limits.js +7 -7
- package/lib/guard-name.js +81 -16
- package/lib/guard-parsed.js +80 -80
- package/lib/guard-range.js +19 -19
- package/lib/guard-secret.js +11 -10
- package/lib/guard-text.js +6 -6
- package/lib/guard-time.js +10 -10
- package/lib/hpke.js +18 -17
- package/lib/http-digest.js +35 -35
- package/lib/http-retry-after.js +13 -13
- package/lib/http-transport.js +20 -19
- package/lib/inspect.js +53 -53
- package/lib/ip-utils.js +2 -2
- package/lib/jose.js +13 -13
- package/lib/key.js +16 -16
- package/lib/lint.js +51 -51
- package/lib/merkle.js +51 -36
- package/lib/mime.js +18 -18
- package/lib/ocsp-verify.js +10 -10
- package/lib/ocsp.js +13 -13
- package/lib/oid.js +29 -29
- package/lib/path-validate.js +114 -113
- package/lib/pbes2.js +16 -16
- package/lib/pkcs12-build.js +53 -53
- package/lib/pki-build.js +48 -20
- package/lib/rc2.js +1 -1
- package/lib/rfc3339.js +5 -5
- package/lib/schema-all.js +31 -31
- package/lib/schema-attrcert.js +12 -12
- package/lib/schema-c509.js +149 -197
- package/lib/schema-cmc.js +58 -58
- package/lib/schema-cmp.js +43 -43
- package/lib/schema-cms.js +45 -45
- package/lib/schema-crl.js +7 -7
- package/lib/schema-crmf.js +28 -28
- package/lib/schema-csr.js +12 -12
- package/lib/schema-csrattrs.js +16 -16
- package/lib/schema-engine.js +18 -18
- package/lib/schema-ocsp.js +15 -15
- package/lib/schema-pkcs12.js +20 -20
- package/lib/schema-pkcs8.js +2 -2
- package/lib/schema-pkix.js +131 -126
- package/lib/schema-smime.js +19 -19
- package/lib/schema-tsp.js +12 -12
- package/lib/schema-x509.js +3 -3
- package/lib/shbs.js +18 -18
- package/lib/sign-scheme.js +13 -13
- package/lib/sigstore.js +10 -11
- package/lib/sleep.js +1 -1
- package/lib/smime.js +308 -96
- package/lib/tls-cert-compress.js +18 -18
- package/lib/trust.js +27 -27
- package/lib/tsp-sign.js +17 -17
- package/lib/validator-all.js +1 -1
- package/lib/validator-attcert.js +1 -1
- package/lib/validator-cose.js +43 -44
- package/lib/validator-keydesc.js +3 -3
- package/lib/validator-sig.js +13 -13
- package/lib/validator-tls.js +11 -11
- package/lib/validator-tpm.js +20 -19
- package/lib/webauthn-mds.js +66 -66
- package/lib/webauthn.js +33 -33
- package/lib/webcrypto.js +15 -15
- package/lib/x509-sign.js +13 -13
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/schema-smime.js
CHANGED
|
@@ -11,23 +11,23 @@
|
|
|
11
11
|
* @intro
|
|
12
12
|
* S/MIME Enhanced Security Services signed-attribute values per RFC 5035 (ESS)
|
|
13
13
|
* and RFC 8551 (S/MIME 4.0). These are the DER-decoded VALUES of CMS signed
|
|
14
|
-
* attributes
|
|
15
|
-
* companion decoder a CMS consumer invokes by attribute OID,
|
|
14
|
+
* attributes: they ride inside a `SignerInfo.signedAttrs`, so this is a
|
|
15
|
+
* companion decoder a CMS consumer invokes by attribute OID, not a top-level
|
|
16
16
|
* format the schema orchestrator auto-routes.
|
|
17
17
|
*
|
|
18
18
|
* `parseSigningCertificate` / `parseSigningCertificateV2` decode the ESS
|
|
19
19
|
* signing-certificate attributes that bind a signature to the exact certificate
|
|
20
|
-
* that made it: each surfaces its list of `ESSCertID`(v2)
|
|
20
|
+
* that made it: each surfaces its list of `ESSCertID`(v2) (the certificate hash
|
|
21
21
|
* (raw), the hash algorithm (decoded for v2, or the implied SHA-1 for v1), and
|
|
22
22
|
* the optional `issuerSerial` (issuer `GeneralNames` validated + surfaced raw,
|
|
23
|
-
* serial as a BigInt + hex)
|
|
23
|
+
* serial as a BigInt + hex)) plus the optional certificate policies.
|
|
24
24
|
* `parseSmimeCapabilities` decodes the ordered `SMIMECapabilities` list (each a
|
|
25
25
|
* capability OID + raw parameters). `decodeAttribute` takes a CMS-shaped
|
|
26
26
|
* `{ type, values }` attribute, enforces the single-`AttributeValue` rule
|
|
27
27
|
* (RFC 8551 sec. 2.5.2), routes on the attribute OID, and recognize-and-defers an
|
|
28
28
|
* unknown attribute type with its raw values intact.
|
|
29
29
|
*
|
|
30
|
-
* Structure is decoded; verification is the consumer's
|
|
30
|
+
* Structure is decoded; verification is the consumer's. The parser surfaces
|
|
31
31
|
* `certHash` + `hashAlgorithm` + `issuerSerial` so a verifier recomputes the
|
|
32
32
|
* certificate hash (compose `webcrypto`) and matches the issuer/serial against
|
|
33
33
|
* the actual signing certificate; it never recomputes a hash or trusts a cert.
|
|
@@ -62,15 +62,15 @@ var OID_SIGNING_CERTIFICATE_V2 = oid.byName("signingCertificateV2");
|
|
|
62
62
|
var OID_SMIME_CAPABILITIES = oid.byName("smimeCapabilities");
|
|
63
63
|
|
|
64
64
|
// IssuerSerial ::= SEQUENCE { issuer GeneralNames, serialNumber CertificateSerialNumber }
|
|
65
|
-
// (RFC 5035 App A). A bare universal SEQUENCE of exactly two fields
|
|
65
|
+
// (RFC 5035 App A). A bare universal SEQUENCE of exactly two fields, distinct
|
|
66
66
|
// from the RFC 5755 attribute-certificate IssuerSerial (three fields, reached
|
|
67
67
|
// IMPLICIT-tagged), so declared per-format. `issuer` composes the shared
|
|
68
68
|
// GeneralNames factory (every CHOICE arm validated + surfaced raw, SIZE 1..MAX)
|
|
69
69
|
// and is surfaced unchanged here: the RFC 5035 sec. 5 directoryName narrowing applies
|
|
70
70
|
// only to a non-attribute (public-key) certificate reference, and an ESSCertID
|
|
71
71
|
// entry may reference an attribute certificate whose issuer is a full GeneralNames.
|
|
72
|
-
// The
|
|
73
|
-
// directoryName rule is enforced
|
|
72
|
+
// The first cert is known to be the signing public-key cert (RFC 5035 sec. 3), so the
|
|
73
|
+
// directoryName rule is enforced in the SigningCertificate build and not here.
|
|
74
74
|
var GENERAL_NAME_DIRECTORY = 4;
|
|
75
75
|
var ISSUER_SERIAL = schema.seq([
|
|
76
76
|
schema.field("issuer", pkix.generalNames(NS, { code: "smime/bad-general-names" })),
|
|
@@ -111,7 +111,7 @@ var POLICY_INFORMATION = schema.seq([
|
|
|
111
111
|
var qualifiers = null;
|
|
112
112
|
if (m.fields.policyQualifiers.present) {
|
|
113
113
|
// The qualifier body stays raw (per the ESS scope), but the RFC 5280
|
|
114
|
-
// sec. 4.2.1.4 structure is validated fail-closed by the shared assertion
|
|
114
|
+
// sec. 4.2.1.4 structure is validated fail-closed by the shared assertion, the
|
|
115
115
|
// same PolicyInformation shape the certificatePolicies decoder enforces.
|
|
116
116
|
var q = m.fields.policyQualifiers.node;
|
|
117
117
|
pkix.assertPolicyQualifiers(q, function (msg, cause) { throw ctx.E("smime/bad-policy-information", msg, cause); });
|
|
@@ -132,8 +132,8 @@ var POLICIES = schema.seqOf(POLICY_INFORMATION, {
|
|
|
132
132
|
});
|
|
133
133
|
|
|
134
134
|
// ESSCertID ::= SEQUENCE { certHash Hash, issuerSerial IssuerSerial OPTIONAL }
|
|
135
|
-
// (RFC 5035 sec. 5.4.2). Hash ::= OCTET STRING
|
|
136
|
-
// whole certificate with
|
|
135
|
+
// (RFC 5035 sec. 5.4.2). Hash ::= OCTET STRING; for v1 it is the SHA-1 hash of the
|
|
136
|
+
// whole certificate with no algorithm field, so the hash algorithm is synthesized
|
|
137
137
|
// as the implied SHA-1 to make v1 shape-compatible with v2 for a verifier.
|
|
138
138
|
var ESS_CERT_ID = schema.seq([
|
|
139
139
|
schema.field("certHash", schema.octetString()),
|
|
@@ -169,7 +169,7 @@ var ESS_CERT_ID_V2 = schema.seq([
|
|
|
169
169
|
// to it is a non-canonical DEFAULT and is rejected fail-closed (the
|
|
170
170
|
// structured-value analogue of the primitive-DEFAULT rejection the toolkit
|
|
171
171
|
// already enforces). A present hashAlgorithm carrying a redundant NULL
|
|
172
|
-
// parameters is
|
|
172
|
+
// parameters is not byte-equal to the params-absent default and decodes.
|
|
173
173
|
if (alg.oid === OID_SHA256 && alg.parameters === null) {
|
|
174
174
|
throw ctx.E("smime/non-canonical-default",
|
|
175
175
|
"ESSCertIDv2 hashAlgorithm equal to the DEFAULT {algorithm id-sha256} MUST be omitted (X.690 sec. 11.5)");
|
|
@@ -188,8 +188,8 @@ var ESS_CERT_ID_V2 = schema.seq([
|
|
|
188
188
|
|
|
189
189
|
// SigningCertificate ::= SEQUENCE { certs SEQUENCE OF ESSCertID,
|
|
190
190
|
// policies SEQUENCE OF PolicyInformation OPTIONAL } (RFC 5035 sec. 5.4.2). certs is
|
|
191
|
-
// order-preserving
|
|
192
|
-
//
|
|
191
|
+
// order-preserving (RFC 5035 sec. 3 makes the first element the signing certificate)
|
|
192
|
+
// and non-empty (an empty certs cannot name a signing cert).
|
|
193
193
|
function signingCertificateSchema(essCertId, code, what) {
|
|
194
194
|
return schema.seq([
|
|
195
195
|
schema.field("certs", schema.seqOf(essCertId, { assert: "sequence", min: 1, code: "smime/bad-certs", what: "certs" })),
|
|
@@ -226,7 +226,7 @@ var SMIME_CAPABILITY = schema.seq([
|
|
|
226
226
|
};
|
|
227
227
|
},
|
|
228
228
|
});
|
|
229
|
-
// SMIMECapabilities ::= SEQUENCE OF SMIMECapability
|
|
229
|
+
// SMIMECapabilities ::= SEQUENCE OF SMIMECapability, ordered by preference
|
|
230
230
|
// (RFC 8551 sec. 2.5.2), never sorted; an empty list is legal.
|
|
231
231
|
var SMIME_CAPABILITIES = schema.seqOf(SMIME_CAPABILITY, {
|
|
232
232
|
assert: "sequence", min: 0, code: "smime/bad-capabilities", what: "SMIMECapabilities",
|
|
@@ -241,7 +241,7 @@ var SMIME_CAPABILITIES = schema.seqOf(SMIME_CAPABILITY, {
|
|
|
241
241
|
* @spec RFC 5035, RFC 2634
|
|
242
242
|
* @related pki.schema.smime.parseSigningCertificateV2, pki.schema.smime.decodeAttribute
|
|
243
243
|
*
|
|
244
|
-
* Decode an ESS v1 `SigningCertificate` attribute value (RFC 5035 sec. 5.4.2)
|
|
244
|
+
* Decode an ESS v1 `SigningCertificate` attribute value (RFC 5035 sec. 5.4.2): the
|
|
245
245
|
* raw `AttributeValue` a CMS consumer plucks off `SignerInfo.signedAttrs`. Returns
|
|
246
246
|
* `{ certs, policies }`: each `certs` entry is `{ certHash, hashAlgorithm,
|
|
247
247
|
* issuerSerial }` in wire order (the first is the signing certificate), where
|
|
@@ -291,7 +291,7 @@ var parseSigningCertificateV2 = pkix.makeParser({ pemLabel: null, PemError: PemE
|
|
|
291
291
|
* @related pki.schema.smime.decodeAttribute
|
|
292
292
|
*
|
|
293
293
|
* Decode an `SMIMECapabilities` attribute value (RFC 8551 sec. 2.5.2) into
|
|
294
|
-
* `{ capabilities }
|
|
294
|
+
* `{ capabilities }`, an ordered list (preference order, never sorted), each
|
|
295
295
|
* `{ capabilityID, name, parameters }` with `parameters` the raw
|
|
296
296
|
* `ANY DEFINED BY capabilityID` bytes (or `null`). Throws a typed `smime/*` error
|
|
297
297
|
* on malformed input.
|
|
@@ -315,8 +315,8 @@ var parseSmimeCapabilities = pkix.makeParser({ pemLabel: null, PemError: PemErro
|
|
|
315
315
|
* OID-dispatch convenience over the three value decoders for a CMS-shaped
|
|
316
316
|
* `{ type, values }` attribute (the shape `cms.parse` surfaces on
|
|
317
317
|
* `signerInfos[i].signedAttrs`). Enforces the single-`AttributeValue` rule
|
|
318
|
-
* (RFC 8551 sec. 2.5.2 / sec. 2.5)
|
|
319
|
-
* `smime/multi-valued-attribute
|
|
318
|
+
* (RFC 8551 sec. 2.5.2 / sec. 2.5), so a `values` length other than 1 is rejected
|
|
319
|
+
* `smime/multi-valued-attribute`, then routes on `attr.type`:
|
|
320
320
|
* `signingCertificate` / `signingCertificateV2` / `smimeCapabilities` decode to
|
|
321
321
|
* `{ kind, ...result }`; any other type is recognize-and-deferred
|
|
322
322
|
* `smime/unsupported-attribute` (its `type`, registry `name`, and raw `values`
|
package/lib/schema-tsp.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* verification-layer concerns above parse altitude. DER-only, fail-closed.
|
|
29
29
|
*
|
|
30
30
|
* @card
|
|
31
|
-
* Parse DER / PEM RFC 3161 timestamp responses and tokens
|
|
31
|
+
* Parse DER / PEM RFC 3161 timestamp responses and tokens: per-response status,
|
|
32
32
|
* the TSTInfo payload (imprint, genTime, serial, nonce, accuracy), raw verifier
|
|
33
33
|
* inputs, single-signer token composition over CMS, fail-closed.
|
|
34
34
|
*/
|
|
@@ -48,7 +48,7 @@ var NS = pkix.makeNS("tsp", TspError, oid);
|
|
|
48
48
|
var ALGORITHM_IDENTIFIER = pkix.algorithmIdentifier(NS);
|
|
49
49
|
var EXTENSION = pkix.extension(NS);
|
|
50
50
|
|
|
51
|
-
// TSTInfo.version is INTEGER { v1(1) }
|
|
51
|
+
// TSTInfo.version is INTEGER { v1(1) }; the only legal value is 1.
|
|
52
52
|
var VERSION = pkix.versionReader(NS, { "1": 1 });
|
|
53
53
|
|
|
54
54
|
// id-ct-TSTInfo is the eContentType that identifies a timestamp token (RFC 3161
|
|
@@ -77,7 +77,7 @@ var FAILURE_BITS = {
|
|
|
77
77
|
// RFC 3161 genTime may carry sub-second precision (X.690 sec. 11.7 fractional profile).
|
|
78
78
|
var GEN_TIME = pkix.generalizedTime(NS, { code: "tsp/bad-gentime", message: "genTime must be a GeneralizedTime (RFC 3161 sec. 2.4.2)", allowFractional: true });
|
|
79
79
|
|
|
80
|
-
// tsa [0] EXPLICIT GeneralName (RFC 3161 sec. 2.4.2)
|
|
80
|
+
// tsa [0] EXPLICIT GeneralName (RFC 3161 sec. 2.4.2), validated and surfaced raw via the
|
|
81
81
|
// shared pkix.generalName primitive (RFC 5280 sec. 4.2.1.6), which checks the chosen
|
|
82
82
|
// alternative's form and content so a malformed GeneralName fails closed.
|
|
83
83
|
var GENERAL_NAME_RAW = pkix.generalName(NS, { code: "tsp/bad-tsa" });
|
|
@@ -88,7 +88,7 @@ var UTF8_TEXT = pkix.utf8Text(NS, { code: "tsp/bad-status-info", message: "PKIFr
|
|
|
88
88
|
// ---- MessageImprint --------------------------------------------------
|
|
89
89
|
|
|
90
90
|
// MessageImprint ::= SEQUENCE { hashAlgorithm AlgorithmIdentifier, hashedMessage
|
|
91
|
-
// OCTET STRING } (RFC 3161 sec. 2.4.1). hashedMessage is a digest
|
|
91
|
+
// OCTET STRING } (RFC 3161 sec. 2.4.1). hashedMessage is a digest, surfaced raw.
|
|
92
92
|
var MESSAGE_IMPRINT = schema.seq([
|
|
93
93
|
schema.field("hashAlgorithm", ALGORITHM_IDENTIFIER),
|
|
94
94
|
schema.field("hashedMessage", schema.octetString()),
|
|
@@ -224,9 +224,9 @@ function _assertMinimalNamedBits(unusedBits, bytes) {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
// Decode the set NamedBitList bits to their RFC 3161 names (bit 0 = MSB of byte 0).
|
|
227
|
-
// A set bit outside the defined set is an unsupported PKIFailureInfo value
|
|
227
|
+
// A set bit outside the defined set is an unsupported PKIFailureInfo value, and a client
|
|
228
228
|
// MUST error on a failInfo it does not understand (RFC 3161 sec. 2.4.2), so reject it
|
|
229
|
-
//
|
|
229
|
+
// instead of surfacing an opaque "bitN".
|
|
230
230
|
function _namedBits(bytes) {
|
|
231
231
|
var out = [];
|
|
232
232
|
for (var i = 0; i < bytes.length * 8; i++) {
|
|
@@ -257,11 +257,11 @@ var TIME_STAMP_RESP = schema.seq([
|
|
|
257
257
|
var granted = status.status === 0 || status.status === 1;
|
|
258
258
|
if (granted && !present) throw NS.E("tsp/missing-token", "a granted TimeStampResp must carry a timeStampToken (RFC 3161 sec. 2.4.2)");
|
|
259
259
|
if (!granted && present) throw NS.E("tsp/unexpected-token", "a non-granted TimeStampResp must not carry a timeStampToken (RFC 3161 sec. 2.4.2)");
|
|
260
|
-
// failInfo is the reason a request was rejected, so it is present
|
|
261
|
-
// status is rejection(2)
|
|
260
|
+
// failInfo is the reason a request was rejected, so it is present only when the
|
|
261
|
+
// status is rejection(2), never on granted(0/1) nor on waiting / revocation* (3/4/5).
|
|
262
262
|
if (status.status !== 2 && status.failInfo) throw NS.E("tsp/unexpected-failinfo", "failInfo is present only when the status is rejection(2) (RFC 3161 sec. 2.4.2)");
|
|
263
|
-
// A granted response's timeStampToken MUST be a well-formed TimeStampToken
|
|
264
|
-
// decode it (composing the CMS parser)
|
|
263
|
+
// A granted response's timeStampToken MUST be a well-formed TimeStampToken, so
|
|
264
|
+
// decode it (composing the CMS parser) instead of surfacing arbitrary SEQUENCE
|
|
265
265
|
// bytes as a token; a malformed token fails the response parse.
|
|
266
266
|
return {
|
|
267
267
|
status: status.status,
|
|
@@ -407,9 +407,9 @@ var parse = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: Ts
|
|
|
407
407
|
* attached (`tsp/detached-token`), the single-signer rule (`tsp/multi-signer`,
|
|
408
408
|
* RFC 3161 sec. 2.4.2), and that the signerInfo carries a SigningCertificate or
|
|
409
409
|
* SigningCertificateV2 signed attribute (`tsp/missing-signing-certificate`,
|
|
410
|
-
* RFC 3161 sec. 2.4.2 / RFC 5816
|
|
410
|
+
* RFC 3161 sec. 2.4.2 / RFC 5816; the hash-vs-certificate binding stays a
|
|
411
411
|
* verification-layer concern), then decodes the inner `TSTInfo`. Returns
|
|
412
|
-
* `{ tstInfo, eContent, signerInfo, certificates }
|
|
412
|
+
* `{ tstInfo, eContent, signerInfo, certificates }`: the decoded payload, the raw
|
|
413
413
|
* eContent bytes a verifier hashes for the CMS message-digest, and the CMS signer
|
|
414
414
|
* material.
|
|
415
415
|
*
|
package/lib/schema-x509.js
CHANGED
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
* is named where the registry knows it.
|
|
21
21
|
*
|
|
22
22
|
* The raw `tbsCertificate` bytes are returned alongside the parsed
|
|
23
|
-
* fields
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* fields. A signature-verification layer hashes exactly the bytes that
|
|
24
|
+
* were signed, with no re-encoding step whose round-trip fidelity it
|
|
25
|
+
* would have to trust.
|
|
26
26
|
*
|
|
27
27
|
* @card
|
|
28
28
|
* Parse DER / PEM X.509 certificates into structured, validated fields
|
package/lib/shbs.js
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
* @title Stateful hash-based
|
|
8
8
|
* @intro Stateful hash-based signature VERIFICATION -- HSS/LMS (RFC 8554),
|
|
9
9
|
* carried in X.509 by RFC 9802 and in CMS by RFC 9708, profiled by NIST
|
|
10
|
-
* SP 800-208.
|
|
11
|
-
* is catastrophic to get wrong
|
|
10
|
+
* SP 800-208. Verify only, by deliberate design: stateful hash-based signing
|
|
11
|
+
* is catastrophic to get wrong. Each one-time key must be used exactly once,
|
|
12
12
|
* so the private key embeds a monotonic index whose state must advance and
|
|
13
13
|
* persist atomically across every signature and every process restart. A single
|
|
14
14
|
* index reuse (a restored VM snapshot, a crashed writer, a concurrent signer)
|
|
15
15
|
* forfeits security and can leak enough one-time-key material to forge, which is
|
|
16
16
|
* why SP 800-208 sec. 8 constrains signing-state handling to hardware. So this
|
|
17
|
-
* module
|
|
17
|
+
* module never mints a signature; it verifies signatures produced in an HSM
|
|
18
18
|
* elsewhere. Verification is pure public-input SHA-256 / SHAKE256 hashing
|
|
19
19
|
* (no secret, no side-channel surface), so a pure-JavaScript verifier is safe.
|
|
20
20
|
* @spec RFC 8554, RFC 9802, RFC 9708, NIST SP 800-208
|
|
@@ -104,8 +104,8 @@ function _hash(family, n, parts) {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
// ---- bounded big-endian reader (bounds-before-slice; the ct.js TlsReader model
|
|
107
|
-
// for a non-DER positional wire, kept shbs-local
|
|
108
|
-
// fixed-n reads, not CT's length-prefixed vector helpers)
|
|
107
|
+
// for a non-DER positional wire, kept shbs-local because shbs needs u32 typecodes
|
|
108
|
+
// and fixed-n reads, not CT's length-prefixed vector helpers) ----------------
|
|
109
109
|
|
|
110
110
|
function Reader(buf, code, label) { this.buf = buf; this.pos = 0; this.code = code; this.label = label; }
|
|
111
111
|
Reader.prototype._need = function (k) {
|
|
@@ -177,7 +177,7 @@ function _lmsVerify(pubBytes, message, sigBytes) {
|
|
|
177
177
|
var T1 = pr.take(m);
|
|
178
178
|
|
|
179
179
|
// -- signature (RFC 8554 Algorithm 6a). q first, then the LM-OTS signature,
|
|
180
|
-
// then the LMS typecode (
|
|
180
|
+
// then the LMS typecode (after the LM-OTS sig), then the auth path. --
|
|
181
181
|
if (sigBytes.length < 8) throw _err("shbs/bad-signature", "LMS signature is shorter than 8 bytes");
|
|
182
182
|
var sr = new Reader(sigBytes, "shbs/bad-signature", "LMS signature");
|
|
183
183
|
var q = sr.u32();
|
|
@@ -185,14 +185,14 @@ function _lmsVerify(pubBytes, message, sigBytes) {
|
|
|
185
185
|
// The public key -- never the attacker-controlled signature -- is the authority
|
|
186
186
|
// (downgrade defense): a signature whose OTS typecode does not equal the one the
|
|
187
187
|
// public key commits to cannot verify against it. RFC 8554 Algorithm 6a checks
|
|
188
|
-
// the typecode (step 2c, and 2g for the LMS type below)
|
|
189
|
-
// 2d / 2i), and returns INVALID
|
|
190
|
-
// structural error. So a mismatch is `false` at this point
|
|
188
|
+
// the typecode (step 2c, and 2g for the LMS type below) before the length (steps
|
|
189
|
+
// 2d / 2i), and returns INVALID, a verification failure (false) and not a
|
|
190
|
+
// structural error. So a mismatch is `false` at this point even for a blob too
|
|
191
191
|
// short to be a complete signature: the mismatch is decidable once q + typecode
|
|
192
192
|
// are read (the bounded reader already threw if those 8 bytes are absent). A
|
|
193
193
|
// matching typecode with a truncated body still throws below (bounds-before-
|
|
194
|
-
// slice). Re-sizing the body by the signature's own mismatched typecode
|
|
195
|
-
// "fully validate before returning false"
|
|
194
|
+
// slice). Re-sizing the body by the signature's own mismatched typecode, so as to
|
|
195
|
+
// "fully validate before returning false", would violate this order and reject
|
|
196
196
|
// a legitimate typecode-mutation test vector as malformed instead of INVALID.
|
|
197
197
|
if (otsSigType !== otsSet.code) return false;
|
|
198
198
|
var n = otsSet.n, p = otsSet.p;
|
|
@@ -204,10 +204,10 @@ function _lmsVerify(pubBytes, message, sigBytes) {
|
|
|
204
204
|
var path = [];
|
|
205
205
|
for (var pi = 0; pi < h; pi++) path.push(sr.take(m));
|
|
206
206
|
if (!sr.atEnd()) throw _err("shbs/bad-signature", "LMS signature has " + sr.remaining() + " trailing byte(s)");
|
|
207
|
-
// RFC 8554 Algorithm 6a step 2i: a leaf index q >= 2^h is INVALID
|
|
208
|
-
// verification
|
|
209
|
-
// exact-length validation above (a truncated
|
|
210
|
-
// typed), against the
|
|
207
|
+
// RFC 8554 Algorithm 6a step 2i: a leaf index q >= 2^h is INVALID, a
|
|
208
|
+
// verification failure (false) and not a structural error. Checked here, after
|
|
209
|
+
// the exact-length validation above (a truncated or trailing blob already threw
|
|
210
|
+
// typed), against the registry height and never the blob. 2^h fits Number (h<=25).
|
|
211
211
|
if (q >= Math.pow(2, h)) return false;
|
|
212
212
|
|
|
213
213
|
// -- recompute the LM-OTS public-key candidate, then fold the Merkle path --
|
|
@@ -272,7 +272,7 @@ function _hssVerify(pubBytes, message, sigBytes) {
|
|
|
272
272
|
|
|
273
273
|
var sr = new Reader(sigBytes, "shbs/bad-signature", "HSS signature");
|
|
274
274
|
var Nspk = sr.u32();
|
|
275
|
-
// The level-count gate, checked
|
|
275
|
+
// The level-count gate, checked before parsing any component (RFC 8554 sec. 6.3).
|
|
276
276
|
if (Nspk + 1 !== L) throw _err("shbs/bad-signature", "HSS Nspk+1 (" + (Nspk + 1) + ") does not equal the public-key level count L (" + L + ")");
|
|
277
277
|
|
|
278
278
|
var key = topKey;
|
|
@@ -312,7 +312,7 @@ function _asBytes(x, label) { return guard.bytes.source(x, ShbsError, "shbs/bad-
|
|
|
312
312
|
* Returns true for a valid signature, false for a well-formed signature that does
|
|
313
313
|
* not verify; a malformed blob (bad length, unknown or unapproved typecode,
|
|
314
314
|
* truncation, a typecode disagreeing between the key and the signature) throws a
|
|
315
|
-
* typed `ShbsError`.
|
|
315
|
+
* typed `ShbsError`. Verification only: this module never signs.
|
|
316
316
|
*
|
|
317
317
|
* @example
|
|
318
318
|
* // throws: shbs/bad-public-key -- an Ed25519 key is not an HSS public key, and
|
|
@@ -337,7 +337,7 @@ function verify(publicKey, message, signature) {
|
|
|
337
337
|
* @spec RFC 8554 sec. 5
|
|
338
338
|
* @related pki.shbs.verify
|
|
339
339
|
*
|
|
340
|
-
* Verify a single-tree LMS (Leighton-Micali Signature) over `message
|
|
340
|
+
* Verify a single-tree LMS (Leighton-Micali Signature) over `message`: the
|
|
341
341
|
* component an HSS hierarchy composes at each level, and a standalone algorithm
|
|
342
342
|
* in its own right. Same verdict contract as `pki.shbs.verify`: true / false for
|
|
343
343
|
* a well-formed signature, a typed `ShbsError` for a malformed blob.
|
package/lib/sign-scheme.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
//
|
|
5
5
|
// @internal -- no operator-facing namespace. The signature-scheme resolver + signer shared by
|
|
6
6
|
// every producer that signs a raw preimage with a certificate's key: pki.cms.sign (SignerInfo),
|
|
7
|
-
// pki.ocsp.sign (BasicOCSPResponse), and future X.509 / CRL issuance. It owns the
|
|
7
|
+
// pki.ocsp.sign (BasicOCSPResponse), and future X.509 / CRL issuance. It owns the single key ->
|
|
8
8
|
// { signatureAlgorithm, WebCrypto import/sign params } dispatch across the whole algorithm set
|
|
9
9
|
// (RSA-PKCS1 / RSA-PSS / ECDSA P-256/384/521 / Ed25519 / Ed448 / ML-DSA / SLH-DSA / composite
|
|
10
|
-
// ML-DSA), so a new signer surface inherits the full registry
|
|
10
|
+
// ML-DSA), so a new signer surface inherits the full registry, PQC included, instead of
|
|
11
11
|
// re-deriving a partial, drifting copy (Hard rule #2: registry not switch; and no second signer).
|
|
12
12
|
//
|
|
13
13
|
// Error-parameterized like the guard / validator families: the caller passes its typed error
|
|
@@ -31,8 +31,8 @@ var HASH = { sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
|
|
|
31
31
|
var NODE_DIGEST = { sha256: "sha256", sha384: "sha384", sha512: "sha512", shake128: "shake128", shake256: "shake256" };
|
|
32
32
|
var PSS_SALT = { "SHA-256": 32, "SHA-384": 48, "SHA-512": 64 };
|
|
33
33
|
var ECDSA_ALG = { sha256: "ecdsaWithSHA256", sha384: "ecdsaWithSHA384", sha512: "ecdsaWithSHA512" };
|
|
34
|
-
// RSA-PKCS1 signatures fold the digest into a combined signature OID when there is
|
|
35
|
-
// digestAlgorithm field to carry it (X.509 / CRL / OCSP
|
|
34
|
+
// RSA-PKCS1 signatures fold the digest into a combined signature OID when there is no separate
|
|
35
|
+
// digestAlgorithm field to carry it (X.509 / CRL / OCSP; RFC 5280 sec. 4.1.1.2, RFC 6960). CMS
|
|
36
36
|
// SignerInfo has that field, so it signs under the bare rsaEncryption key OID instead.
|
|
37
37
|
var RSA_PKCS1_SIG = { sha256: "sha256WithRSAEncryption", sha384: "sha384WithRSAEncryption", sha512: "sha512WithRSAEncryption" };
|
|
38
38
|
var HASH_NAME_BY_OID = {};
|
|
@@ -115,8 +115,8 @@ function _pssHashFromSpki(cert, E) {
|
|
|
115
115
|
function pssSpkiPinnedHash(cert, E) {
|
|
116
116
|
var d = _pssHashFromSpki(cert, E);
|
|
117
117
|
if (!d) return null;
|
|
118
|
-
// A pin this toolkit has no WebCrypto hash for
|
|
119
|
-
//
|
|
118
|
+
// A pin this toolkit has no WebCrypto hash for (SHA-1, which RSASSA-PSS-params names by DEFAULT)
|
|
119
|
+
// is still a pin. Returning undefined here would hand the caller a falsy value it reads as
|
|
120
120
|
// "unrestricted", which is the same fail-open the DEFAULT reading above exists to close, one
|
|
121
121
|
// layer up.
|
|
122
122
|
if (!HASH[d]) throw E("unsupported-algorithm", "the id-RSASSA-PSS key is restricted to " + d + ", which this toolkit does not verify with");
|
|
@@ -124,7 +124,7 @@ function pssSpkiPinnedHash(cert, E) {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// resolveSignScheme(cert, so, noSignedAttrs, E) -> the signature scheme from the signer cert's
|
|
127
|
-
// public-key algorithm + per-signer opts (so.digestAlgorithm / so.pss / so.combinedRsaSig
|
|
127
|
+
// public-key algorithm + per-signer opts (so.digestAlgorithm / so.pss / so.combinedRsaSig, the
|
|
128
128
|
// last folds the digest into a combined RSA signature OID for a caller with no digestAlgorithm
|
|
129
129
|
// field). Returns
|
|
130
130
|
// { composite?, digest, digestAlgId, sigAlgId, imp?, sign?, ecdsaDer?, coordLen? }. `digest`/
|
|
@@ -188,14 +188,14 @@ function resolveSignScheme(cert, so, noSignedAttrs, E) {
|
|
|
188
188
|
// A CryptoKey's algorithm must match the resolved scheme (name / hash / curve).
|
|
189
189
|
function _assertKeyMatchesScheme(key, imp, E) {
|
|
190
190
|
var ka = key.algorithm || {};
|
|
191
|
-
// WebCrypto algorithm names are ASCII-case-insensitive (RSASSA-PKCS1-v1_5
|
|
192
|
-
// algorithm), so compare case-folded
|
|
191
|
+
// WebCrypto algorithm names are ASCII-case-insensitive (RSASSA-PKCS1-v1_5 and RSASSA-PKCS1-V1_5 are one
|
|
192
|
+
// algorithm), so compare case-folded; a genuine mismatch (e.g. RSASSA-PKCS1-v1_5 vs ECDSA) still fails.
|
|
193
193
|
if (String(ka.name).toUpperCase() !== String(imp.name).toUpperCase()) throw E("bad-input", "the signer CryptoKey algorithm (" + ka.name + ") does not match the certificate's key algorithm (" + imp.name + ")");
|
|
194
194
|
if (imp.hash && (!ka.hash || ka.hash.name !== imp.hash)) throw E("bad-input", "the signer CryptoKey hash (" + (ka.hash && ka.hash.name) + ") does not match the signing digest (" + imp.hash + ")");
|
|
195
195
|
if (imp.namedCurve && ka.namedCurve !== imp.namedCurve) throw E("bad-input", "the signer CryptoKey curve (" + ka.namedCurve + ") does not match the certificate curve (" + imp.namedCurve + ")");
|
|
196
196
|
}
|
|
197
197
|
function _normPkcs8(k, label, E) {
|
|
198
|
-
// A caller's own Buffer is
|
|
198
|
+
// A caller's own Buffer is borrowed, not copied, the same rule _importKey states below. Every
|
|
199
199
|
// other form is copied, and neither copy is duplicated further, so a private key never gains a
|
|
200
200
|
// plaintext duplicate this module cannot account for.
|
|
201
201
|
if (Buffer.isBuffer(k)) return guard.bytes.view(k, E, "bad-input", label);
|
|
@@ -217,8 +217,8 @@ function _importKey(key, imp, E) {
|
|
|
217
217
|
// signs with. One whose material this process cannot reach is refused with that as the reason.
|
|
218
218
|
return webcrypto.adoptKey(key, imp, ["sign"], E, "bad-input");
|
|
219
219
|
}
|
|
220
|
-
// A Uint8Array is copied here and a PEM string is decoded here, so both leave a
|
|
221
|
-
// private key that nothing else can reach
|
|
220
|
+
// A Uint8Array is copied here and a PEM string is decoded here, so both leave a second copy of a
|
|
221
|
+
// private key that nothing else can reach, wiped once the engine has imported it, which is the
|
|
222
222
|
// only thing that reads it. A caller's own Buffer is passed through untouched: they hold a live
|
|
223
223
|
// reference and will use it again, so clearing it would destroy their key.
|
|
224
224
|
var der, owned = false;
|
|
@@ -243,7 +243,7 @@ function _importKey(key, imp, E) {
|
|
|
243
243
|
|
|
244
244
|
// signOverTbs(scheme, key, signedBytes, E) -> Promise<Buffer> the raw signature over signedBytes.
|
|
245
245
|
// The classical path imports the key + signs (re-encoding ECDSA to canonical DER); the composite
|
|
246
|
-
// path signs
|
|
246
|
+
// path signs both component keys and returns mldsaSig || tradSig (composite-sig.js owns it).
|
|
247
247
|
function signOverTbs(scheme, key, signedBytes, E) {
|
|
248
248
|
if (scheme.composite) {
|
|
249
249
|
return compositeSig.compositeSign(scheme.composite, _normCompositeKeys(key, scheme.composite, E), signedBytes).then(function (sig) { return Buffer.from(sig); });
|
package/lib/sigstore.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module pki.sigstore
|
|
6
6
|
* @nav Supply chain
|
|
7
7
|
* @title Sigstore
|
|
8
|
-
* @intro Zero-dependency verifier for a Sigstore bundle
|
|
8
|
+
* @intro Zero-dependency verifier for a Sigstore bundle, the exact artifact
|
|
9
9
|
* `npm publish --provenance` produces and the npm registry serves at its
|
|
10
10
|
* attestations API. A bundle is a keyless (Fulcio) signature over a DSSE-wrapped
|
|
11
11
|
* in-toto SLSA provenance attestation, with a Rekor transparency-log inclusion
|
|
@@ -114,7 +114,7 @@ function _b64(s, label) {
|
|
|
114
114
|
* Buffer) fail-closed: a non-object, malformed JSON, an oversize input, an
|
|
115
115
|
* unknown `mediaType`, or a missing required member throws a typed
|
|
116
116
|
* `sigstore/bad-bundle` / `sigstore/bad-bundle-version`. Returns the validated
|
|
117
|
-
* bundle object (structure only
|
|
117
|
+
* bundle object (structure only, no cryptographic verification).
|
|
118
118
|
*
|
|
119
119
|
* @example
|
|
120
120
|
* // requires: `bundle` -- a Sigstore bundle as cosign or npm provenance emits it
|
|
@@ -604,18 +604,17 @@ function _statement(payload, payloadType, expectedPredicate) {
|
|
|
604
604
|
* the log entry bound to this exact signature; and the in-toto SLSA statement.
|
|
605
605
|
* Any leg failing throws a typed `sigstore/*` error. On success returns
|
|
606
606
|
* `{ verified: true, payload, statement, subjects, predicateType, predicate,
|
|
607
|
-
* identity, identityChecked, integratedTime }
|
|
607
|
+
* identity, identityChecked, integratedTime }`. `payload` is the raw verified
|
|
608
608
|
* envelope bytes (never a re-serialization), and the caller confirms a
|
|
609
609
|
* `subjects[].digest` matches the published artifact.
|
|
610
610
|
*
|
|
611
|
-
* `verified: true` says the artifact was signed and logged
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
* operator believes constrains the signer.
|
|
611
|
+
* `verified: true` says the artifact was signed and logged; it says nothing about
|
|
612
|
+
* who. Fulcio issues a certificate to anyone who completes an OIDC flow, so who
|
|
613
|
+
* signed is decided only by `opts.identity`, and `identityChecked` reports which of
|
|
614
|
+
* its fields were compared (`{ san, issuer, sourceRepositoryURI }`, each a boolean).
|
|
615
|
+
* An `identity` naming none of them is refused, since it would accept every signer
|
|
616
|
+
* while reading as a policy; so is an unrecognized field name, which would otherwise
|
|
617
|
+
* pin nothing under a spelling the operator believes constrains the signer.
|
|
619
618
|
*
|
|
620
619
|
* @opts
|
|
621
620
|
* fulcioRoots: Array, // the Fulcio CA anchors: a DER Buffer or { der, validFor } each
|
package/lib/sleep.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// pki.cmp.session). A delay above Node's 32-bit setTimeout ceiling is SPLIT into chained maximum-size
|
|
7
7
|
// chunks: a bare setTimeout(fn, > 2^31-1) is silently clamped to 1 ms (a TimeoutOverflowWarning) and would
|
|
8
8
|
// then rapidly re-poll instead of waiting the full interval. Each client's opts.sleep overrides this in
|
|
9
|
-
// tests, so this default
|
|
9
|
+
// tests, so this default, the one sleeper that touches a real timer, is never driven by a test wait.
|
|
10
10
|
|
|
11
11
|
var SETTIMEOUT_MAX_MS = 2147483647; // 2^31 - 1: Node's setTimeout delay ceiling
|
|
12
12
|
|