@blamejs/pki 0.5.6 → 0.5.8
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 +419 -378
- package/MIGRATING.md +65 -0
- package/README.md +12 -12
- package/lib/acme.js +31 -31
- package/lib/asn1-der.js +10 -10
- package/lib/attrcert-sign.js +24 -20
- 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 +104 -104
- package/lib/cmc-verify.js +108 -32
- package/lib/cmp-build.js +31 -26
- package/lib/cmp-session.js +74 -72
- package/lib/cmp-verify.js +72 -58
- package/lib/cms-compress.js +8 -9
- package/lib/cms-decrypt.js +92 -76
- package/lib/cms-encrypt.js +33 -34
- package/lib/cms-sign.js +100 -54
- package/lib/cms-verify.js +141 -82
- package/lib/composite-sig.js +13 -13
- package/lib/constants.js +4 -4
- package/lib/crl-sign.js +31 -25
- package/lib/crl-verify.js +7 -6
- package/lib/crmf-sign.js +19 -15
- package/lib/csr-sign.js +13 -9
- package/lib/ct.js +37 -37
- package/lib/edwards-point.js +7 -7
- package/lib/est.js +103 -59
- package/lib/framework-error.js +5 -5
- package/lib/guard-all.js +3 -3
- package/lib/guard-async.js +4 -4
- package/lib/guard-bytes.js +378 -15
- package/lib/guard-compress.js +17 -17
- package/lib/guard-crypto.js +1 -1
- 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 +144 -75
- 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 +32 -20
- package/lib/oid.js +29 -29
- package/lib/path-validate.js +114 -113
- package/lib/pbes2.js +16 -16
- package/lib/pkcs12-build.js +71 -56
- package/lib/pki-build.js +23 -22
- 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 +144 -142
- package/lib/schema-cmc.js +58 -58
- package/lib/schema-cmp.js +43 -43
- package/lib/schema-cms.js +169 -36
- 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 +19 -16
- 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 +22 -18
- 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 +21 -20
- package/lib/webauthn-mds.js +67 -67
- package/lib/webauthn.js +34 -34
- package/lib/webcrypto.js +15 -15
- package/lib/x509-sign.js +24 -15
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/pbes2.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
4
|
//
|
|
5
|
-
// @internal -- the RFC 8018 PBES2 / PBKDF2 primitives, the
|
|
5
|
+
// @internal -- the RFC 8018 PBES2 / PBKDF2 primitives, the single home shared by pki.cms EncryptedData / PWRI
|
|
6
6
|
// password encryption and pki.key EncryptedPrivateKeyInfo (RFC 5958). Error-factory-parameterized like the
|
|
7
|
-
// guard family: every reject goes through the
|
|
8
|
-
// `prefix`, so pki.cms keeps cms/* codes and pki.key keeps key/* codes off
|
|
9
|
-
// behavior-preservingly from cms-encrypt.js
|
|
10
|
-
// A shape here that
|
|
7
|
+
// guard family: every reject goes through the caller's `E(code, msg, cause)` full-code factory plus a domain
|
|
8
|
+
// `prefix`, so pki.cms keeps cms/* codes and pki.key keeps key/* codes off one implementation. Extracted
|
|
9
|
+
// behavior-preservingly from cms-encrypt.js and cms-decrypt.js; the shipped CMS PBES2 tests are the guard.
|
|
10
|
+
// A shape here that cannot route through a guard stays with its caller; the reusable PBES2 shape lives here
|
|
11
11
|
// so a fourth copy (the drift a reviewer flags) cannot re-accrete.
|
|
12
12
|
|
|
13
13
|
var nodeCrypto = require("crypto");
|
|
@@ -26,7 +26,7 @@ var PRF_NODE_BY_NAME = { hmacWithSHA1: "sha1", hmacWithSHA256: "sha256", hmacWit
|
|
|
26
26
|
var PRF_NODE_BY_OID = {}; Object.keys(PRF_NODE_BY_NAME).forEach(function (n) { PRF_NODE_BY_OID[O(n)] = PRF_NODE_BY_NAME[n]; });
|
|
27
27
|
|
|
28
28
|
// content-encryption OID -> AES key bits (CBC + GCM). The PBES2 encryptionScheme + CMS content cipher table.
|
|
29
|
-
// CONTENT_MODE rides the
|
|
29
|
+
// CONTENT_MODE rides the same rows so a cipher can never be present for its key length but absent for its
|
|
30
30
|
// mode: key length alone does not distinguish an AEAD cipher from a plain one, and a consumer that resolves
|
|
31
31
|
// only the length would open content in the wrong mode whenever the two happen to share a key size.
|
|
32
32
|
var CONTENT_KEYBITS = {}, CONTENT_MODE = {};
|
|
@@ -79,8 +79,8 @@ function pbkdf2ParamsSeq(salt, iterations, prf) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// The PBES2 AlgorithmIdentifier: SEQUENCE { id-PBES2, SEQUENCE { keyDerivationFunc PBKDF2, encryptionScheme } }.
|
|
82
|
-
// The
|
|
83
|
-
// encryptionAlgorithm
|
|
82
|
+
// The single home for the CMS EncryptedData contentEncryptionAlgorithm and the RFC 5958 EncryptedPrivateKeyInfo
|
|
83
|
+
// encryptionAlgorithm: both wrap the same PBES2 structure around a PBKDF2 KDF plus an AES-CBC scheme whose
|
|
84
84
|
// IV rides in the encryptionScheme parameter. `cipherName` is a registry NAME (an AES-CBC OID name).
|
|
85
85
|
function pbes2AlgId(salt, iterations, prf, cipherName, iv) {
|
|
86
86
|
var kdf = b.sequence([b.oid(O("pbkdf2")), pbkdf2ParamsSeq(salt, iterations, prf)]);
|
|
@@ -100,7 +100,7 @@ function seqChildren(paramsDer, minLen, what, E, prefix) {
|
|
|
100
100
|
return requireChildren(asn1.decode(paramsDer), minLen, what, E, prefix);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
// Strict PBKDF2-params decode: salt (capped
|
|
103
|
+
// Strict PBKDF2-params decode: salt (capped before derivation), iterationCount (positiveInt31 + cap, downward-
|
|
104
104
|
// overridable via a validated-positive-int opts.maxIterations so Math.min(NaN, cap) cannot silently disable
|
|
105
105
|
// the DoS cap), prf. Returns { salt, iterations, prfNode }.
|
|
106
106
|
function parsePbkdf2Params(paramsDer, opts, E, prefix, strictPrf) {
|
|
@@ -131,7 +131,7 @@ function parsePbkdf2Params(paramsDer, opts, E, prefix, strictPrf) {
|
|
|
131
131
|
return { salt: salt, iterations: iterations, prfNode: prfNode };
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
// AES-CBC content encryption / decryption (PKCS#7 pad). decrypt's final() throws on bad pad
|
|
134
|
+
// AES-CBC content encryption / decryption (PKCS#7 pad). decrypt's final() throws on bad pad, and the caller
|
|
135
135
|
// collapses that into its uniform decrypt-failed verdict.
|
|
136
136
|
function cbcEncrypt(key, iv, plaintext, keyBits) {
|
|
137
137
|
var c = nodeCrypto.createCipheriv("aes-" + keyBits + "-cbc", key, iv);
|
|
@@ -155,9 +155,9 @@ function pbes2Encrypt(pwBytes, plaintext, opts, E, prefix) {
|
|
|
155
155
|
var iterations = assertIterations(opts.iterations == null ? 2048 : opts.iterations, E, prefix);
|
|
156
156
|
var salt = opts.salt != null ? assertSalt(opts.salt, E, prefix) : nodeCrypto.randomBytes(16);
|
|
157
157
|
var iv = opts.iv != null ? opts.iv : nodeCrypto.randomBytes(16);
|
|
158
|
-
// The derived key is this module's allocation and protects the caller's plaintext
|
|
159
|
-
// in the pkcs8
|
|
160
|
-
//
|
|
158
|
+
// The derived key is this module's allocation and protects the caller's plaintext (a private key
|
|
159
|
+
// in the pkcs8 and pkcs12 callers), so it is cleared once the encryption has consumed it. pwBytes
|
|
160
|
+
// is not cleared: the caller formatted it and may still own the buffer.
|
|
161
161
|
var key = nodeCrypto.pbkdf2Sync(pwBytes, salt, iterations, keyBits / 8, prfNode);
|
|
162
162
|
try {
|
|
163
163
|
return { algId: pbes2AlgId(salt, iterations, prf, cipherName, iv), ct: cbcEncrypt(key, iv, plaintext, keyBits) };
|
|
@@ -202,7 +202,7 @@ function pbes2Decrypt(pwBytes, params, ciphertext, opts, E, prefix, budget) {
|
|
|
202
202
|
}
|
|
203
203
|
throw E(prefix + "/bad-algorithm-parameters", "malformed PBES2 parameters", e);
|
|
204
204
|
}
|
|
205
|
-
// Charge the shared budget
|
|
205
|
+
// Charge the shared budget before deriving, so the work is refused instead of performed.
|
|
206
206
|
if (budget) {
|
|
207
207
|
budget.rounds -= pb.iterations;
|
|
208
208
|
if (budget.rounds < 0) throw E(prefix + "/iteration-limit", "the aggregate PBKDF2 key-derivation work exceeds the budget (a hostile many-element input)");
|
|
@@ -218,7 +218,7 @@ function pbes2Decrypt(pwBytes, params, ciphertext, opts, E, prefix, budget) {
|
|
|
218
218
|
function _hmacAlgId(name) { return b.sequence([b.oid(O(name)), b.nullValue()]); }
|
|
219
219
|
|
|
220
220
|
// The id-PBMAC1 AlgorithmIdentifier: id-PBMAC1 + PBMAC1-params { keyDerivationFunc PBKDF2, messageAuthScheme
|
|
221
|
-
// HMAC }
|
|
221
|
+
// HMAC }, the byte-exact inverse of schema-pkcs12 / schema-cmp's PBMAC1_PARAMS. `desc` = { salt,
|
|
222
222
|
// iterationCount, keyLength, prfName, macName }. The prf is omitted iff it equals the default hmacWithSHA1.
|
|
223
223
|
function pbmac1AlgId(desc) {
|
|
224
224
|
var pbkdf2Params = [b.octetString(desc.salt), b.integer(BigInt(desc.iterationCount)), b.integer(BigInt(desc.keyLength))];
|
|
@@ -238,7 +238,7 @@ function pbmac1(pwBytes, salt, iterationCount, keyLength, prfHash, macHash, mess
|
|
|
238
238
|
}).then(function (bits) {
|
|
239
239
|
// The derived MAC key is this function's allocation, in two forms: the ArrayBuffer deriveBits
|
|
240
240
|
// returned and the Buffer view over it that is imported. Clearing the view clears both, and it
|
|
241
|
-
// runs whether the signing succeeds or throws
|
|
241
|
+
// runs whether the signing succeeds or throws. The sibling PBES2 encrypt/decrypt above clear
|
|
242
242
|
// their derived keys, and every PBMAC1 consumer (PKCS#12 build and verification) inherits this.
|
|
243
243
|
var mk = Buffer.from(bits);
|
|
244
244
|
return subtle.importKey("raw", mk, { name: "HMAC", hash: macHash }, false, ["sign"]).then(function (hmacKey) {
|
package/lib/pkcs12-build.js
CHANGED
|
@@ -17,20 +17,20 @@
|
|
|
17
17
|
*/
|
|
18
18
|
//
|
|
19
19
|
// The PBES2 bag encryption and the PBMAC1 MAC compose the shared lib/pbes2.js home (the same PBKDF2 + AES-CBC
|
|
20
|
-
// + HMAC primitives pki.cms and pki.key use), fed PKCS#12 App. B.1-formatted password bytes
|
|
20
|
+
// + HMAC primitives pki.cms and pki.key use), fed PKCS#12 App. B.1-formatted password bytes, never the CMS
|
|
21
21
|
// UTF-8 encoding, the single most common PKCS#12 interop failure. The classic Appendix B.2 KDF (ID=3, the MAC
|
|
22
22
|
// integrity purpose) is bespoke PKCS#12 crypto with no in-tree equivalent, built here as one primitive and
|
|
23
|
-
// cross-checked against OpenSSL. The MAC is computed over the
|
|
24
|
-
// STRING (excluding its TLV header)
|
|
25
|
-
// trap designed out. Public-key integrity (an id-signedData authSafe
|
|
23
|
+
// cross-checked against OpenSSL. The MAC is computed over the content octets of the id-data authSafe OCTET
|
|
24
|
+
// STRING (excluding its TLV header), exactly the parser's `macedBytes`, the canonical off-by-the-header
|
|
25
|
+
// trap designed out. Public-key integrity (an id-signedData authSafe, a CMS SignedData over the exact
|
|
26
26
|
// AuthenticatedSafe, no MacData) is produced by composing pki.cms.sign and verified on open by pki.cms.verify
|
|
27
27
|
// as the integrity gate before any bag is trusted; the signer is surfaced, never trust-chained (the caller's
|
|
28
28
|
// pki.path.validate step). Public-key PRIVACY (an id-envelopedData safe encrypting the SafeContents to a
|
|
29
29
|
// recipient public key, RFC 7292 sec. 3.1) is produced by composing pki.cms.encrypt (AES-CBC EnvelopedData,
|
|
30
|
-
// never GCM/AuthEnvelopedData) and opened by composing pki.cms.decrypt with the recipient key
|
|
30
|
+
// never GCM/AuthEnvelopedData) and opened by composing pki.cms.decrypt with the recipient key, after the
|
|
31
31
|
// integrity gate, with every recipient-side fault collapsed to the uniform pkcs12/decrypt-failed. Privacy is
|
|
32
32
|
// independent of integrity: all four combinations (password/public-key integrity x password/public-key privacy)
|
|
33
|
-
// are permitted, and the recipient key is a privacy credential only
|
|
33
|
+
// are permitted, and the recipient key is a privacy credential only: never a MAC key, signer, or PBES2 password.
|
|
34
34
|
|
|
35
35
|
var nodeCrypto = require("crypto");
|
|
36
36
|
var asn1 = require("./asn1-der");
|
|
@@ -55,13 +55,13 @@ var PemError = frameworkError.PemError;
|
|
|
55
55
|
function O(n) { return oid.byName(n); }
|
|
56
56
|
function _err(code, msg, cause) { return new Pkcs12Error(code, msg, cause); }
|
|
57
57
|
|
|
58
|
-
// PKCS#12 integrity/privacy defaults
|
|
58
|
+
// PKCS#12 integrity/privacy defaults, named here so no call site writes them as literals (rule 12).
|
|
59
59
|
var DEFAULT_MAC_ITER = 2048;
|
|
60
60
|
var DEFAULT_PBMAC1_ITER = 2048;
|
|
61
61
|
var MAC_SALT_BYTES = 8;
|
|
62
|
-
var MAX_PBMAC1_KEYLEN = 1024; // an HMAC key beyond a hash block is pointless
|
|
63
|
-
// Both MAC paths cap iterations to bound the
|
|
64
|
-
// different counts
|
|
62
|
+
var MAX_PBMAC1_KEYLEN = 1024; // an HMAC key beyond a hash block is pointless, so bound the derived length
|
|
63
|
+
// Both MAC paths cap iterations to bound the synchronous derivation that blocks the event loop, but at
|
|
64
|
+
// different counts, because the per-iteration cost differs by ~5x: the two ceilings target a comparable
|
|
65
65
|
// ~1-2 second wall-clock budget, not inconsistent DoS budgets. The classic Appendix B KDF is a hand-rolled
|
|
66
66
|
// JS hash loop (a createHash per iteration, ~1.1us each, so 1e6 ~= 1s); PBMAC1 derives via native
|
|
67
67
|
// crypto.pbkdf2Sync (one FFI call, ~0.2us per iteration, so the shared C.LIMITS.PBKDF2_MAX_ITERATIONS of 1e7
|
|
@@ -69,12 +69,12 @@ var MAX_PBMAC1_KEYLEN = 1024; // an HMAC key beyond a hash block is pointless
|
|
|
69
69
|
// pkcs12-local (its KDF is bespoke here) while PBMAC1 reuses the toolkit-wide PBKDF2 ceiling. 1e6 is ~500x
|
|
70
70
|
// the OpenSSL default (2048) yet still bounds the loop to ~1 second.
|
|
71
71
|
var CLASSIC_MAC_MAX_ITERATIONS = 1000000;
|
|
72
|
-
// The
|
|
72
|
+
// The aggregate cap on synchronous KDF work across a single open(): rounds summed over every encrypted
|
|
73
73
|
// bag and safe, whichever scheme it uses: the legacy App. B KDF (its block count x iterations) and PBKDF2
|
|
74
74
|
// alike. The per-bag iteration cap resets per bag, so a hostile store that duplicates a costly bag up to the
|
|
75
75
|
// parser's 1024-element limit would otherwise block the event loop for minutes; this bounds the total to
|
|
76
|
-
// ~the classic MAC ceiling. It covers
|
|
77
|
-
// path
|
|
76
|
+
// ~the classic MAC ceiling. It covers both schemes because covering only the legacy one left the modern
|
|
77
|
+
// path (the one every current producer emits) free to multiply its cap by the element limit.
|
|
78
78
|
// A conforming store runs only a few thousand rounds (its handful of bags at ~2048 iterations).
|
|
79
79
|
var KDF_MAX_ROUNDS = CLASSIC_MAC_MAX_ITERATIONS;
|
|
80
80
|
|
|
@@ -104,10 +104,10 @@ var DIGEST_NAME = { sha1: "sha1", sha256: "sha256", sha384: "sha384", sha512: "s
|
|
|
104
104
|
// is taken verbatim as already-formatted bytes (an escape hatch for a caller that pre-encodes).
|
|
105
105
|
function _p12Password(pw) { return _p12PasswordOwned(pw).bytes; }
|
|
106
106
|
|
|
107
|
-
// An
|
|
107
|
+
// An absent password is refused, never encoded as the empty one. The two are not the same
|
|
108
108
|
// credential, and the difference is invisible at the call site: a caller who misspells the option,
|
|
109
109
|
// or threads it through a layer that drops it, otherwise gets a store whose private key is
|
|
110
|
-
// protected by nothing and no error anywhere saying so. The empty password remains available
|
|
110
|
+
// protected by nothing and no error anywhere saying so. The empty password remains available:
|
|
111
111
|
// it just has to be asked for, as "".
|
|
112
112
|
var _MISSING_PASSWORD = "a password must be a string, Buffer, or Uint8Array -- an omitted password " +
|
|
113
113
|
"is not the empty password; pass \"\" to use the empty one deliberately";
|
|
@@ -116,17 +116,19 @@ var _MISSING_PASSWORD = "a password must be a string, Buffer, or Uint8Array -- a
|
|
|
116
116
|
// capability cannot arrive with its option silently ignored at this boundary.
|
|
117
117
|
var _INTEGRITY_OPTS = { mode: 1, signer: 1, signers: 1, certificates: 1, sid: 1, signingTime: 1 };
|
|
118
118
|
|
|
119
|
-
// The same encoding, reporting
|
|
120
|
-
// Buffer is returned
|
|
119
|
+
// The same encoding, reporting ownership, mirroring pbes2.passwordBytesOwned. A caller-supplied
|
|
120
|
+
// Buffer is returned as-is and is borrowed: clearing it would destroy the caller's own credential,
|
|
121
121
|
// which is a worse defect than leaving a copy readable. Every other input is re-encoded into a
|
|
122
122
|
// buffer this module allocated, which it must clear once a derivation has consumed it.
|
|
123
123
|
function _p12PasswordOwned(pw) {
|
|
124
|
-
if (Buffer.isBuffer(pw)) return { bytes: pw, owned: false };
|
|
124
|
+
if (Buffer.isBuffer(pw)) return { bytes: guard.bytes.view(pw, Pkcs12Error, "pkcs12/bad-input", "the password"), owned: false };
|
|
125
125
|
return { bytes: _p12Encode(pw), owned: true };
|
|
126
126
|
}
|
|
127
127
|
function _p12Encode(pw) {
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
// A view first, so a detached backing store is a reject and never an empty password; then a copy,
|
|
129
|
+
// because everything this function returns is reported owned and gets zeroized after derivation,
|
|
130
|
+
// and handing back a view of a caller's Uint8Array would wipe the caller's own credential.
|
|
131
|
+
if (Buffer.isBuffer(pw) || pw instanceof Uint8Array) return guard.bytes.snapshot(pw, Pkcs12Error, "pkcs12/bad-input", "the password");
|
|
130
132
|
if (typeof pw !== "string") throw _err("pkcs12/bad-input", _MISSING_PASSWORD);
|
|
131
133
|
var out = Buffer.alloc(pw.length * 2 + 2); // + the 2-byte NULL terminator
|
|
132
134
|
for (var i = 0; i < pw.length; i++) {
|
|
@@ -139,18 +141,18 @@ function _p12Encode(pw) {
|
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
// The password bytes for the RFC 8018 PBES2 bag ciphers and the RFC 9579 PBMAC1 MAC. Although RFC 9579 sec. 6
|
|
142
|
-
// specifies the BMPString encoding here too, OpenSSL and NSS
|
|
144
|
+
// specifies the BMPString encoding here too, OpenSSL and NSS (and thus the interoperable ecosystem) feed
|
|
143
145
|
// PBKDF2 the raw UTF-8 password for these modern schemes (confirmed byte-for-byte against `openssl pkcs12`),
|
|
144
146
|
// reserving the BMPString+NULL form for the bespoke Appendix B KDF only. A file we emit must open in OpenSSL,
|
|
145
147
|
// so the modern schemes use UTF-8 here; only the classic Appendix B MAC uses `_p12Password`.
|
|
146
|
-
// The UTF-8 encoding, reporting
|
|
147
|
-
// for the same reason. A caller-supplied Buffer is
|
|
148
|
+
// The UTF-8 encoding, reporting ownership: the sibling of _p12PasswordOwned, and it exists
|
|
149
|
+
// for the same reason. A caller-supplied Buffer is borrowed and left alone; every other input is
|
|
148
150
|
// re-encoded into a buffer this module allocated, and a plaintext password copy this module made
|
|
149
151
|
// is cleared once the derivation has consumed it. Without it the App. B.1 copy taken from the same
|
|
150
152
|
// argument in the same call was wiped while this one was not.
|
|
151
153
|
function _pbePasswordOwned(pw) {
|
|
152
|
-
if (Buffer.isBuffer(pw)) return { bytes: pw, owned: false };
|
|
153
|
-
if (pw instanceof Uint8Array) return { bytes:
|
|
154
|
+
if (Buffer.isBuffer(pw)) return { bytes: guard.bytes.view(pw, Pkcs12Error, "pkcs12/bad-input", "the password"), owned: false };
|
|
155
|
+
if (pw instanceof Uint8Array) return { bytes: guard.bytes.snapshot(pw, Pkcs12Error, "pkcs12/bad-input", "the password"), owned: true };
|
|
154
156
|
if (typeof pw !== "string") throw _err("pkcs12/bad-input", _MISSING_PASSWORD);
|
|
155
157
|
return { bytes: Buffer.from(pw, "utf8"), owned: true };
|
|
156
158
|
}
|
|
@@ -169,7 +171,7 @@ function _blockFill(src, blockSize) {
|
|
|
169
171
|
}
|
|
170
172
|
|
|
171
173
|
// RFC 7292 Appendix B.2 KDF: derive `nBytes` of key material for purpose `id` (1 = cipher key, 2 = IV,
|
|
172
|
-
// 3 = MAC) from the App. B.1 password bytes + salt over `iterations` rounds of `hashName`.
|
|
174
|
+
// 3 = MAC) from the App. B.1 password bytes + salt over `iterations` rounds of `hashName`. This is not PBKDF2. The
|
|
173
175
|
// block count c = ceil(nBytes/u): the MAC always requests exactly the hash output u (c = 1, one diversified,
|
|
174
176
|
// salted, iterated hash), while a legacy-PBE cipher key wider than u (e.g. 24-byte 3-key-3DES from SHA-1's
|
|
175
177
|
// 20-byte output, c = 2) exercises the multi-block feedback (step 6C: I = (I + B + 1) mod 2^(8v) between
|
|
@@ -179,7 +181,7 @@ function _p12Kdf(hashName, id, pwBytes, salt, iterations, nBytes) {
|
|
|
179
181
|
var uv = P12_KDF_UV[hashName], u = uv.u, v = uv.v;
|
|
180
182
|
var D = Buffer.alloc(v, id); // step 1: v copies of the ID diversifier
|
|
181
183
|
// Each fill is a full block-repeated copy of the salt / password; only their concatenation is
|
|
182
|
-
// used, so the sources are cleared
|
|
184
|
+
// used, so the sources are cleared and never abandoned.
|
|
183
185
|
var sFill = _blockFill(salt, v), pFill = _blockFill(pwBytes, v);
|
|
184
186
|
var I = Buffer.concat([sFill, pFill]); // steps 2-4: I = S || P
|
|
185
187
|
guard.secret.zeroizeAll([sFill, pFill], Pkcs12Error, "pkcs12/bad-input", "a key-derivation fill");
|
|
@@ -204,7 +206,7 @@ function _p12Kdf(hashName, id, pwBytes, salt, iterations, nBytes) {
|
|
|
204
206
|
}
|
|
205
207
|
guard.secret.zeroize(A, Pkcs12Error, "pkcs12/bad-input", "a key-derivation block");
|
|
206
208
|
}
|
|
207
|
-
// I is S || P
|
|
209
|
+
// I is S || P, so it carries the password bytes, and is finished with here.
|
|
208
210
|
guard.secret.zeroize(I, Pkcs12Error, "pkcs12/bad-input", "the key-derivation input block");
|
|
209
211
|
// Step 8 is "the first nBytes of A", but returning a SUBARRAY would hand back a view over a
|
|
210
212
|
// larger allocation: a caller clearing what it received would leave the rest of the final digest
|
|
@@ -356,7 +358,7 @@ function _pbeOpts(enc) {
|
|
|
356
358
|
};
|
|
357
359
|
}
|
|
358
360
|
|
|
359
|
-
// SafeContents ::= SEQUENCE OF SafeBag (ordered,
|
|
361
|
+
// SafeContents ::= SEQUENCE OF SafeBag (ordered, and not a SET OF).
|
|
360
362
|
function _buildSafeContents(bags, opts, depth) {
|
|
361
363
|
if (!Array.isArray(bags)) throw _err("pkcs12/bad-input", "bags must be an array");
|
|
362
364
|
if (bags.length > C.LIMITS.PKCS12_MAX_ELEMENTS) throw _err("pkcs12/bad-input", "a SafeContents exceeds the element cap " + C.LIMITS.PKCS12_MAX_ELEMENTS);
|
|
@@ -364,13 +366,13 @@ function _buildSafeContents(bags, opts, depth) {
|
|
|
364
366
|
}
|
|
365
367
|
|
|
366
368
|
// One AuthenticatedSafe element: a plaintext id-data ContentInfo, an id-encryptedData ContentInfo whose
|
|
367
|
-
// EncryptedData is the PBES2 encryption of the SafeContents (RFC 7292 sec. 5.1 step 2A/2B), or
|
|
368
|
-
//
|
|
369
|
+
// EncryptedData is the PBES2 encryption of the SafeContents (RFC 7292 sec. 5.1 step 2A/2B), or, for
|
|
370
|
+
// public-key privacy (sec. 3.1), an id-envelopedData ContentInfo whose CMS EnvelopedData encrypts the
|
|
369
371
|
// SafeContents to one or more recipient public keys. Async because the envelope path awaits pki.cms.encrypt.
|
|
370
372
|
async function _buildAuthSafeElement(sc, opts) {
|
|
371
373
|
if (!sc || typeof sc !== "object") throw _err("pkcs12/bad-input", "each safeContents entry must be an object");
|
|
372
|
-
// A privacy directive is honored whenever its field is
|
|
373
|
-
// or `encrypt` (null / false / "") is a configuration error that intended privacy
|
|
374
|
+
// A privacy directive is honored whenever its field is present (even if falsy). A present-but-falsy `recipients`
|
|
375
|
+
// or `encrypt` (null / false / "") is a configuration error that intended privacy, so it must fail closed and never
|
|
374
376
|
// silently fall through to a plaintext id-data safe that would emit the bags in cleartext (privacy downgraded to
|
|
375
377
|
// none while still producing a valid MAC-protected PFX). Omit the field entirely for a genuinely plaintext safe.
|
|
376
378
|
var hasRecipients = sc.recipients !== undefined;
|
|
@@ -379,10 +381,10 @@ async function _buildAuthSafeElement(sc, opts) {
|
|
|
379
381
|
var safeContentsDer = _buildSafeContents(sc.bags || [], opts, 0);
|
|
380
382
|
if (hasRecipients) {
|
|
381
383
|
if (!Array.isArray(sc.recipients) || !sc.recipients.length) throw _err("pkcs12/bad-input", "safeContents.recipients must be a non-empty array of recipient descriptors (RFC 5652 sec. 6.1)");
|
|
382
|
-
// Public-key privacy admits only
|
|
383
|
-
// ML-KEM, dispatched off the cert key). A password (pwri) or KEK (kekri) recipient is
|
|
384
|
-
// and pki.pkcs12.open (which forwards only { recipientKey, recipientCert } to cms.decrypt) cannot reopen it
|
|
385
|
-
// so reject it here
|
|
384
|
+
// Public-key privacy admits only certificate recipients (ktri / kari / kemri: RSA / ECDH / X25519 / X448 /
|
|
385
|
+
// ML-KEM, dispatched off the cert key). A password (pwri) or KEK (kekri) recipient is not public-key privacy
|
|
386
|
+
// and pki.pkcs12.open (which forwards only { recipientKey, recipientCert } to cms.decrypt) cannot reopen it,
|
|
387
|
+
// so reject it here and never emit a store the toolkit's own reader cannot process. (Re-open: pwri/kekri
|
|
386
388
|
// privacy when open forwards the full cms.decrypt key-material model.)
|
|
387
389
|
sc.recipients.forEach(function (r) {
|
|
388
390
|
if (!r || typeof r !== "object" || r.cert == null) throw _err("pkcs12/bad-input", "a public-key privacy recipient must be a certificate recipient { cert } (a password or KEK recipient is not public-key privacy and cannot be reopened by pkcs12.open)");
|
|
@@ -472,9 +474,9 @@ function _normalizeSpec(spec, opts) {
|
|
|
472
474
|
(spec.ca || []).forEach(function (ca) { certBags.push({ type: "cert", cert: ca }); });
|
|
473
475
|
var sc = [];
|
|
474
476
|
if (enveloped) {
|
|
475
|
-
// Public-key-privacy convenience: the cert bags
|
|
477
|
+
// Public-key-privacy convenience: the cert bags plus a plaintext keyBag in one recipient-enveloped safe (the
|
|
476
478
|
// envelope supplies confidentiality; there is no recipient-encrypted key-bag type, RFC 7292 sec. 4.2). The
|
|
477
|
-
// caller password, if any, remains the MAC (integrity) password
|
|
479
|
+
// caller password, if any, remains the MAC (integrity) password; privacy and integrity are independent.
|
|
478
480
|
var bags = certBags.slice();
|
|
479
481
|
if (spec.key != null) bags.push({ type: "key", key: spec.key, friendlyName: spec.friendlyName, localKeyId: spec.localKeyId });
|
|
480
482
|
sc.push({ recipients: opts.recipientCerts.map(function (c) { return { cert: c }; }), bags: bags });
|
|
@@ -503,7 +505,7 @@ function _normalizeSpec(spec, opts) {
|
|
|
503
505
|
* plaintext or PBES2-encrypted `SafeContents` of key / shroudedKey / cert / crl / secret / nested
|
|
504
506
|
* safeContents bags. Keys and certs are validated before wrapping. Password integrity (the default) MACs the
|
|
505
507
|
* AuthenticatedSafe with a classic Appendix B HMAC or an RFC 9579 PBMAC1. Public-key integrity
|
|
506
|
-
* (`opts.integrity.mode: "public-key"`) instead wraps the AuthenticatedSafe in a CMS SignedData
|
|
508
|
+
* (`opts.integrity.mode: "public-key"`) instead wraps the AuthenticatedSafe in a CMS SignedData: a
|
|
507
509
|
* signature from a keypair, no MacData (RFC 7292 sec. 4). Privacy (PBES2 bag encryption via `password`) is
|
|
508
510
|
* independent of the integrity mode. The store is re-parsed before return.
|
|
509
511
|
*
|
|
@@ -523,7 +525,20 @@ function _normalizeSpec(spec, opts) {
|
|
|
523
525
|
* { type: 'shroudedKey', key: signerKeyPkcs8, encrypt: { password: 'changeit' } } ] }] },
|
|
524
526
|
* { password: 'changeit', mac: { algorithm: 'hmac', hash: 'sha256' } });
|
|
525
527
|
*/
|
|
526
|
-
|
|
528
|
+
function build(spec, opts) {
|
|
529
|
+
// Both arguments copied at entry and released when the call settles -- see the note on the same
|
|
530
|
+
// call in x509-sign. A PKCS#12 file is assembled over many promise turns (a key derivation per
|
|
531
|
+
// bag, then the outer MAC) while the caller still owns the object holding the passwords and the
|
|
532
|
+
// bag contents. Rewriting a password buffer partway through produced a file whose MAC and whose
|
|
533
|
+
// bag encryption were keyed to two different values, so it opened with neither the password
|
|
534
|
+
// passed in nor the one written over it. The release is what keeps the copy of that password
|
|
535
|
+
// from outliving the call.
|
|
536
|
+
return guard.bytes.fixedCall(Pkcs12Error, "pkcs12/bad-input", [
|
|
537
|
+
[spec, "the PKCS#12 spec"], [opts, "pki.pkcs12.build options"],
|
|
538
|
+
], _build);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async function _build(spec, opts) {
|
|
527
542
|
opts = opts || {};
|
|
528
543
|
// The integrity mode is checked against the permitted set rather than compared to one literal.
|
|
529
544
|
// Compared, any other spelling reads as "not public-key" and silently selects password integrity,
|
|
@@ -660,33 +675,33 @@ function _capWork(iterations, salt, opts, keyLength, hardCap) {
|
|
|
660
675
|
* @defends pkcs12-unauthenticated-decrypt (CWE-347), pbes2-padding-oracle (CWE-208)
|
|
661
676
|
* @related pki.pkcs12.build, pki.pkcs12.verifyMac, pki.schema.pkcs12.parse
|
|
662
677
|
*
|
|
663
|
-
* Read a PKCS#12 store: verify its integrity
|
|
664
|
-
* SignedData signature
|
|
665
|
-
* then decrypt every PBES2 privacy safe and pkcs8ShroudedKeyBag with the password
|
|
666
|
-
* (public-key privacy) safe with `opts.recipientKey` (RFC 7292 sec. 3.1, via `pki.cms.decrypt`)
|
|
667
|
-
* structured bundle `{ integrityMode, macVerified, signers, keys, certs, crls, secrets }`
|
|
678
|
+
* Read a PKCS#12 store: verify its integrity first (a password store's MAC, or a public-key store's CMS
|
|
679
|
+
* SignedData signature, per RFC 7292 sec. 4 / 5.1, so no bag from a store whose integrity check fails is trusted),
|
|
680
|
+
* then decrypt every PBES2 privacy safe and pkcs8ShroudedKeyBag with the password, and every id-envelopedData
|
|
681
|
+
* (public-key privacy) safe with `opts.recipientKey` (RFC 7292 sec. 3.1, via `pki.cms.decrypt`), returning a
|
|
682
|
+
* structured bundle `{ integrityMode, macVerified, signers, keys, certs, crls, secrets }` with each private key as PKCS#8
|
|
668
683
|
* `PrivateKeyInfo` DER (re-validated), each certificate / CRL / secret as raw DER, all carrying their
|
|
669
684
|
* `friendlyName` / `localKeyId` for pairing. `pfx` is the store's DER `Buffer`, a PEM string, or an
|
|
670
|
-
* unmodified `pki.schema.pkcs12.parse` result; a
|
|
685
|
+
* unmodified `pki.schema.pkcs12.parse` result; a rebuilt parsed store is refused, since the bytes whose
|
|
671
686
|
* integrity is checked and the bags returned as checked are separate properties of it.
|
|
672
687
|
*
|
|
673
688
|
* A MAC-less store is refused (`pkcs12/no-integrity`) unless `opts.allowUnauthenticated` is set. A public-key
|
|
674
689
|
* integrity store is verified through `pki.cms.verify` before any bag is trusted; a signature failure is
|
|
675
690
|
* `pkcs12/signature-invalid`, and `signers` carries the per-signer verdict `[{ ok, sid, cert }]` (`null` in
|
|
676
|
-
* password / MAC-less mode). The signer is surfaced,
|
|
691
|
+
* password / MAC-less mode). The signer is surfaced, never trust-chained: anchoring `signers[i].cert` to a
|
|
677
692
|
* trust root is the caller's `pki.path.validate` step (the out-of-path signer contract). Privacy is
|
|
678
693
|
* independent of integrity, so the bag `password` still decrypts a public-key store's bags; a wrong bag
|
|
679
694
|
* password there is the uniform `pkcs12/decrypt-failed` (no MAC to catch it first). Bags encrypted under
|
|
680
|
-
* PBES2 (AES-CBC), or the RFC 7292 App. C legacy schemes an `openssl pkcs12 -legacy` store uses
|
|
681
|
-
* 2-key Triple-DES-CBC and 40-/128-bit RC2-CBC
|
|
682
|
-
* RFC 2268 cipher)
|
|
695
|
+
* PBES2 (AES-CBC), or the RFC 7292 App. C legacy schemes an `openssl pkcs12 -legacy` store uses (3-key /
|
|
696
|
+
* 2-key Triple-DES-CBC and 40-/128-bit RC2-CBC, the App. B KDF over the BMPString password, RC2 via an in-tree
|
|
697
|
+
* RFC 2268 cipher), are decrypted; the legacy RC4 schemes are named and refused.
|
|
683
698
|
*
|
|
684
699
|
* @opts
|
|
685
|
-
* - `allowUnauthenticated` (boolean)
|
|
686
|
-
* - `signerCerts` (array of cert DER)
|
|
687
|
-
* - `recipientKey` (PKCS#8 DER|PEM) + `recipientCert` (cert DER|PEM, or `recipientIndex`)
|
|
688
|
-
* - `maxIterations` (number)
|
|
689
|
-
* - `keys` (string)
|
|
700
|
+
* - `allowUnauthenticated` (boolean): open a MAC-less store anyway (result carries `macVerified: false`).
|
|
701
|
+
* - `signerCerts` (array of cert DER): signer certificate(s) for a public-key store built with `certificates: false` (forwarded to `pki.cms.verify`).
|
|
702
|
+
* - `recipientKey` (PKCS#8 DER|PEM) + `recipientCert` (cert DER|PEM, or `recipientIndex`): decrypt an id-envelopedData (public-key privacy) safe. The recipient key is a privacy credential only; a wrong key / tampered envelope is the uniform `pkcs12/decrypt-failed`, and an enveloped safe with no `recipientKey` is `pkcs12/no-recipient-key`.
|
|
703
|
+
* - `maxIterations` (number): lower the PBKDF2 / MAC iteration cap for this call (downward-only).
|
|
704
|
+
* - `keys` (string): `der` (default) or `crypto` (also `pki.key.import` each private key to a CryptoKey).
|
|
690
705
|
* - `importAlgorithm` -- forwarded to `pki.key.import` for the ambiguous RSA / EC arms when `keys: crypto`.
|
|
691
706
|
* @example
|
|
692
707
|
* var pair = await pki.key.generate("Ed25519");
|
package/lib/pki-build.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// binds a domain namespace once and returns the shared PKIX producing primitives every signer module
|
|
7
7
|
// composes: the distinguished-name encoder, the GeneralName + RFC 5280 sec. 4.2.1 extension-value
|
|
8
8
|
// encoders, the embedded-input validators (a raw Name / a SubjectPublicKeyInfo / a pre-encoded Extension
|
|
9
|
-
// run through the
|
|
9
|
+
// run through the same parser the decoder uses), the sign-scheme bridge, and the post-sign signature
|
|
10
10
|
// self-check (the key-match / proof-of-possession verify). Each is parameterized on the CALLER's error
|
|
11
11
|
// class + code prefix, so pki.x509.sign keeps x509/* codes and pki.csr.sign keeps csr/*.
|
|
12
12
|
|
|
@@ -64,7 +64,7 @@ function makeBuilder(ctx) {
|
|
|
64
64
|
}
|
|
65
65
|
function encodeAtv(attrName, value) {
|
|
66
66
|
if (value == null || value === "") throw E("bad-name", "the " + attrName + " attribute value must be a non-empty string");
|
|
67
|
-
// oid.byName returns undefined
|
|
67
|
+
// For an unrecognized name oid.byName returns undefined and does not throw, so reject explicitly.
|
|
68
68
|
var typeOid = O(attrName);
|
|
69
69
|
if (typeOid == null) throw E("bad-name", "unknown distinguished-name attribute " + JSON.stringify(attrName));
|
|
70
70
|
var valueTlv;
|
|
@@ -119,8 +119,8 @@ function makeBuilder(ctx) {
|
|
|
119
119
|
case "uniformResourceIdentifier": case "uri": return b.contextPrimitive(6, ia5Content(v));
|
|
120
120
|
case "iPAddress":
|
|
121
121
|
// Accept a dotted-quad / colon-hex string (packed to its 4/16 network octets) as well as a
|
|
122
|
-
// pre-packed Buffer, so an iPAddress SAN reads like dNSName/URI
|
|
123
|
-
//
|
|
122
|
+
// pre-packed Buffer, so an iPAddress SAN reads like dNSName/URI and the caller does not pack
|
|
123
|
+
// octets by hand (a GeneralName iPAddress is a bare host address, RFC 5280 sec. 4.2.1.6).
|
|
124
124
|
var ipBuf = v;
|
|
125
125
|
if (typeof v === "string") {
|
|
126
126
|
ipBuf = ipUtils.packIpLiteral(v);
|
|
@@ -144,15 +144,15 @@ function makeBuilder(ctx) {
|
|
|
144
144
|
return b.namedBitString(positions);
|
|
145
145
|
}
|
|
146
146
|
// Resolve an OID token to a dotted-decimal OID: a registered name via the registry, OR a raw dotted-OID
|
|
147
|
-
// string accepted directly (an unregistered KeyPurposeId / policy OID
|
|
148
|
-
//
|
|
147
|
+
// string accepted directly (an unregistered KeyPurposeId / policy OID for BIMI, document-signing or a
|
|
148
|
+
// vendor purpose is a common valid input). A token that is neither fails closed (never silently accept a
|
|
149
149
|
// typo'd name); a dotted string that passes the shape check is still arc-validated by b.oid's encoder.
|
|
150
150
|
function _resolveOid(n, label) {
|
|
151
151
|
var dotted = O(n);
|
|
152
152
|
if (dotted != null) return dotted;
|
|
153
153
|
if (typeof n === "string" && DOTTED_OID_RE.test(n)) {
|
|
154
154
|
// The regex fixes the general shape; b.oid's encoder is the authoritative X.660 arc-bounds check
|
|
155
|
-
// (a first arc 0/1 caps the second at 39; an arc must DER-encode). A failure there is
|
|
155
|
+
// (a first arc 0/1 caps the second at 39; an arc must DER-encode). A failure there is this producer's
|
|
156
156
|
// bad-input, not a leaked oid/* code, so every shared-builder consumer keeps its own error contract.
|
|
157
157
|
try { b.oid(n); }
|
|
158
158
|
catch (e) { throw E("bad-input", "invalid " + label + " OID " + JSON.stringify(n) + " (violates the X.660 arc bounds)", e); }
|
|
@@ -184,7 +184,7 @@ function makeBuilder(ctx) {
|
|
|
184
184
|
function extAki(keyid) { return b.sequence([b.contextPrimitive(0, keyid)]); } // keyIdentifier [0] IMPLICIT OCTET STRING
|
|
185
185
|
// GeneralNames ::= SEQUENCE SIZE(1..MAX) OF GeneralName. With no implicitTag it is a universal SEQUENCE
|
|
186
186
|
// (subjectAltName / issuerAltName / IssuerSerial.issuer). With an implicitTag it is a context-constructed
|
|
187
|
-
// [n] whose tag REPLACES the SEQUENCE tag (RFC 5755 IMPLICIT TAGS
|
|
187
|
+
// [n] whose tag REPLACES the SEQUENCE tag (RFC 5755 IMPLICIT TAGS: Holder.entityName [1],
|
|
188
188
|
// RoleSyntax.roleAuthority [0], IetfAttrSyntax.policyAuthority [0]); the [n] node's children ARE the
|
|
189
189
|
// GeneralName members, no inner SEQUENCE wrapper.
|
|
190
190
|
function encodeGeneralNames(entries, implicitTag) {
|
|
@@ -213,7 +213,8 @@ function makeBuilder(ctx) {
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
// The SHA-1 subjectKeyIdentifier (RFC 5280 sec. 4.2.1.2 method 1): SHA-1 of the subjectPublicKey BIT
|
|
216
|
-
// STRING
|
|
216
|
+
// STRING content, taken past the unused-bits octet. Hashing the whole SPKI or the BIT STRING TLV
|
|
217
|
+
// gives a different, wrong identifier.
|
|
217
218
|
function spkiKeyId(spkiDer) {
|
|
218
219
|
var keyBytes = asn1.read.bitString(asn1.decode(spkiDer).children[1]).bytes;
|
|
219
220
|
// RFC 5280 sec. 4.2.1.2 method 1 DEFINES the subjectKeyIdentifier as the SHA-1 of the
|
|
@@ -223,18 +224,17 @@ function makeBuilder(ctx) {
|
|
|
223
224
|
return nodeCrypto.createHash("sha1").update(keyBytes).digest();
|
|
224
225
|
}
|
|
225
226
|
function skiKeyId(val, spkiDer) {
|
|
226
|
-
if (Buffer.isBuffer(val)) return val;
|
|
227
|
+
if (Buffer.isBuffer(val)) return guard.bytes.snapshot(val, ErrorClass, ctx.prefix + "/bad-input", "subjectKeyIdentifier");
|
|
227
228
|
if (val === true) return spkiKeyId(spkiDer);
|
|
228
229
|
throw E("bad-input", "subjectKeyIdentifier must be true (auto-derive) or a Buffer key id");
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
// ---- embedded-input validators ----
|
|
232
233
|
function reqDer(v, what) {
|
|
233
|
-
if (Buffer.isBuffer(v)) return v;
|
|
234
|
-
if (v instanceof Uint8Array) return Buffer.from(v);
|
|
234
|
+
if (Buffer.isBuffer(v) || v instanceof Uint8Array) return guard.bytes.snapshot(v, ErrorClass, ctx.prefix + "/bad-input", what);
|
|
235
235
|
throw E("bad-input", what + " must be a DER Buffer");
|
|
236
236
|
}
|
|
237
|
-
// Full validation of an embedded SubjectPublicKeyInfo
|
|
237
|
+
// Full validation of an embedded SubjectPublicKeyInfo, walked through the parser the decoder uses.
|
|
238
238
|
function assertValidSpki(spkiDer, what) {
|
|
239
239
|
var node;
|
|
240
240
|
try { node = asn1.decode(spkiDer); }
|
|
@@ -345,10 +345,11 @@ function makeBuilder(ctx) {
|
|
|
345
345
|
return { subjectPublicKeyInfo: { algorithm: { oid: keyOid, parameters: alg.children.length > 1 ? alg.children[1].bytes : undefined } } };
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
-
// Confirm the produced signature verifies under `spki
|
|
349
|
-
// CryptoKey, or any signer), where deriving-and-comparing the public key cannot
|
|
350
|
-
// CryptoKey has no exportable public half). This is the x509 chain self-check
|
|
351
|
-
// possession. Composite verifies both components. Returns a promise for
|
|
348
|
+
// Confirm the produced signature verifies under `spki`. This works for any key type (a PKCS#8
|
|
349
|
+
// key, a WebCrypto CryptoKey, or any signer), where deriving-and-comparing the public key cannot
|
|
350
|
+
// (a non-extractable CryptoKey has no exportable public half). This is the x509 chain self-check
|
|
351
|
+
// AND the CSR proof of possession. Composite verifies both components. Returns a promise for
|
|
352
|
+
// composite, sync-throws for classical.
|
|
352
353
|
function assertSignatureVerifies(preimage, sig, spki, scheme) {
|
|
353
354
|
if (scheme.composite) {
|
|
354
355
|
return compositeSig.compositeVerify(spki, sig, preimage, scheme.composite, ErrorClass, code("unsupported-algorithm"), code("bad-input")).then(function (r) {
|
|
@@ -395,11 +396,11 @@ function tbsNameField(cert, which) {
|
|
|
395
396
|
return tbs.children[(hasVersion ? 1 : 0) + (which === "subject" ? 4 : 2)].bytes;
|
|
396
397
|
}
|
|
397
398
|
|
|
398
|
-
// The serialNumber of a parsed X.509 certificate, read from the SIGNED bytes
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
//
|
|
402
|
-
//
|
|
399
|
+
// The serialNumber of a parsed X.509 certificate, read from the SIGNED bytes. Same tbs layout as
|
|
400
|
+
// above, and the same reason: where a producer binds an identity to an existing certificate, that
|
|
401
|
+
// identity is issuer AND serial together. Deriving one from the bytes and reading the other from
|
|
402
|
+
// the object lets the halves name different certificates, producing a Holder whose issuer is
|
|
403
|
+
// genuine and whose serial is whatever the caller wrote.
|
|
403
404
|
function tbsSerialNumber(cert) {
|
|
404
405
|
var tbs = asn1.decode(cert.tbsBytes);
|
|
405
406
|
var hasVersion = tbs.children[0].tagClass === "context" && tbs.children[0].tagNumber === 0;
|
package/lib/rc2.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
//
|
|
9
9
|
// rc2 -- a from-scratch RFC 2268 RC2-CBC. OpenSSL 3.x moved RC2 to the legacy provider (not loaded by Node),
|
|
10
10
|
// so node:crypto cannot decrypt these bags; this in-tree primitive fills that single gap (Hard rule #1: own
|
|
11
|
-
// code, no npm hop
|
|
11
|
+
// code, no npm hop, so MANIFEST stays empty; it is a lib primitive alongside webcrypto.js, outside lib/vendor/). The
|
|
12
12
|
// RFC 2268 sec. 5 known-answer vectors pin the cipher; the `openssl -legacy` store is the end-to-end KAT.
|
|
13
13
|
|
|
14
14
|
// RFC 2268 sec. 2: PITABLE, a fixed 256-byte permutation of the digits of pi used by the key schedule.
|
package/lib/rfc3339.js
CHANGED
|
@@ -41,13 +41,13 @@ function parse(v, E, code, label) {
|
|
|
41
41
|
return new Date(v);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// RFC 3339 sec. 5.6 `full-date`: a calendar date with
|
|
45
|
-
// the date-time above, and deliberately a separate pair of functions
|
|
44
|
+
// RFC 3339 sec. 5.6 `full-date`: a calendar date with no time component. A distinct production from
|
|
45
|
+
// the date-time above, and deliberately a separate pair of functions instead of a flag: a consumer
|
|
46
46
|
// that expects one and is handed the other has a malformed value, not a permissible variant.
|
|
47
47
|
var FULL_DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
48
48
|
|
|
49
49
|
// isValidDate(v) -> boolean: v is a syntactically well-formed AND calendar-valid RFC 3339 full-date.
|
|
50
|
-
// Calendar validity matters as much as shape
|
|
50
|
+
// Calendar validity matters as much as shape: "2021-02-30" parses as a Date in JS by rolling over
|
|
51
51
|
// into March, so a shape-only check would silently accept a day that does not exist.
|
|
52
52
|
function isValidDate(v) {
|
|
53
53
|
if (typeof v !== "string") return false;
|
|
@@ -61,8 +61,8 @@ function isValidDate(v) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
// parseDate(v, E, code, label) -> Date at UTC midnight. Anchored explicitly at UTC ("T00:00:00Z")
|
|
64
|
-
//
|
|
65
|
-
// zone
|
|
64
|
+
// instead of left to `new Date("YYYY-MM-DD")`, so the instant does not shift with the host time
|
|
65
|
+
// zone. A freshness or rollback comparison must not depend on where it runs.
|
|
66
66
|
function parseDate(v, E, code, label) {
|
|
67
67
|
if (!isValidDate(v)) throw E(code, (label || "the value") + " is not a valid RFC 3339 full-date (YYYY-MM-DD)");
|
|
68
68
|
return new Date(v + "T00:00:00Z");
|