@blamejs/pki 0.8.7 → 0.8.10
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 +137 -76
- package/README.md +3 -2
- package/index.js +5 -0
- package/lib/acme.js +24 -83
- package/lib/cmc-build.js +1 -1
- package/lib/cmc-verify.js +3 -3
- package/lib/cmp-session.js +1 -1
- package/lib/cms-sign.js +3 -3
- package/lib/cms-verify.js +5 -5
- package/lib/constants.js +11 -0
- package/lib/est.js +1 -1
- package/lib/framework-error.js +6 -0
- package/lib/guard-async.js +53 -2
- package/lib/guard-encoding.js +74 -0
- package/lib/guard-identifier.js +74 -1
- package/lib/guard-intrinsic.js +2 -0
- package/lib/guard-limits.js +40 -1
- package/lib/guard-list.js +25 -0
- package/lib/guard-name.js +133 -16
- package/lib/guard-text.js +33 -1
- package/lib/http-transport.js +39 -7
- package/lib/identity-match.js +561 -0
- package/lib/ip-utils.js +52 -1
- package/lib/key.js +250 -5
- package/lib/ocsp.js +125 -22
- package/lib/oid.js +1 -1
- package/lib/path-validate.js +199 -61
- package/lib/revocation-fetch.js +772 -0
- package/lib/schema-all.js +124 -0
- package/lib/schema-attrcert.js +4 -2
- package/lib/schema-crmf.js +4 -2
- package/lib/schema-pkcs1.js +335 -0
- package/lib/schema-pkcs8.js +15 -0
- package/lib/schema-pkix.js +322 -40
- package/lib/schema-sec1.js +244 -0
- package/lib/schema-tsp.js +4 -2
- package/lib/schema-x509.js +4 -2
- package/lib/sigstore.js +35 -21
- package/lib/trust.js +2 -2
- package/lib/tsp-sign.js +21 -14
- package/lib/webauthn.js +4 -4
- package/lib/webcrypto.js +17 -5
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* @module pki.schema.sec1
|
|
6
|
+
* @nav Schema
|
|
7
|
+
* @title SEC1
|
|
8
|
+
* @fullname SEC1 EC private keys, the EC PRIVATE KEY encoding
|
|
9
|
+
* @order 146
|
|
10
|
+
* @slug sec1
|
|
11
|
+
*
|
|
12
|
+
* @intro
|
|
13
|
+
* The elliptic-curve private-key encoding of RFC 5915: `ECPrivateKey`, the
|
|
14
|
+
* `EC PRIVATE KEY` PEM block OpenSSL writes and appliances still emit. `parse`
|
|
15
|
+
* reads one into its version, scalar, curve and stored public point, and
|
|
16
|
+
* `encode` writes it back.
|
|
17
|
+
*
|
|
18
|
+
* The curve is what fixes the scalar's width, so the parameters field is
|
|
19
|
+
* required here even though the ASN.1 marks it optional: without it there is no
|
|
20
|
+
* width to hold the scalar to, and a scalar of the wrong width is a different
|
|
21
|
+
* key. RFC 5915 sec. 3 states the scalar as `ceiling(log2(n)/8)` octets with its
|
|
22
|
+
* leading zeros intact, so a short one is refused and never padded into place.
|
|
23
|
+
*
|
|
24
|
+
* The parameters field carries a named curve and nothing else. RFC 5480
|
|
25
|
+
* sec. 2.1.1 says a specified curve and an implicit curve MUST NOT appear, and
|
|
26
|
+
* each is refused by name, so the message says which one arrived.
|
|
27
|
+
*
|
|
28
|
+
* The stored public key is surfaced as the bytes it carries. It is the
|
|
29
|
+
* encoding's own copy of the public half, not the key's identity: a verb that
|
|
30
|
+
* needs that half derives it from the scalar, so a stored point that disagrees
|
|
31
|
+
* cannot decide anything.
|
|
32
|
+
*
|
|
33
|
+
* @card
|
|
34
|
+
* Read and write the RFC 5915 `ECPrivateKey`, DER or PEM, with the scalar held
|
|
35
|
+
* to its curve's width and the parameters field held to a named curve.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
var asn1 = require("./asn1-der");
|
|
39
|
+
var schema = require("./schema-engine");
|
|
40
|
+
var pkix = require("./schema-pkix");
|
|
41
|
+
var oid = require("./oid");
|
|
42
|
+
var frameworkError = require("./framework-error");
|
|
43
|
+
var intrinsic = require("./guard-intrinsic");
|
|
44
|
+
|
|
45
|
+
var Sec1Error = frameworkError.Sec1Error;
|
|
46
|
+
var PemError = frameworkError.PemError;
|
|
47
|
+
|
|
48
|
+
var NS = pkix.makeNS("sec1", Sec1Error, oid);
|
|
49
|
+
|
|
50
|
+
var LABEL = "EC PRIVATE KEY";
|
|
51
|
+
|
|
52
|
+
/** @internal The scalar width each curve fixes, `ceiling(log2(n)/8)` over the curve's order
|
|
53
|
+
* (RFC 5915 sec. 3). A curve with no row here is one this toolkit cannot hold a scalar to, which
|
|
54
|
+
* is a refusal, since guessing the width would accept a key of the wrong size. */
|
|
55
|
+
var CURVE_SCALAR_BYTES = intrinsic.assign(intrinsic.create(null), {
|
|
56
|
+
"prime256v1": 32,
|
|
57
|
+
"secp384r1": 48,
|
|
58
|
+
"secp521r1": 66,
|
|
59
|
+
"secp256k1": 32,
|
|
60
|
+
"brainpoolP256r1": 32,
|
|
61
|
+
"brainpoolP384r1": 48,
|
|
62
|
+
"brainpoolP512r1": 64,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/** @internal RFC 5480 sec. 2.2 writes the point's first octet as the form: 0x04 uncompressed,
|
|
66
|
+
* 0x02 and 0x03 compressed. The hybrid forms 0x06 and 0x07 are named there as MUST NOT, and every
|
|
67
|
+
* other value names no form at all. The width follows from the form and the curve. */
|
|
68
|
+
function _assertPoint(point, scalarBytes) {
|
|
69
|
+
if (point.length === 0) {
|
|
70
|
+
throw NS.E("sec1/bad-public-key", "the stored public key is empty (RFC 5480 sec. 2.2)");
|
|
71
|
+
}
|
|
72
|
+
var form = point[0];
|
|
73
|
+
if (form === 0x06 || form === 0x07) {
|
|
74
|
+
throw NS.E("sec1/bad-public-key", "the hybrid point forms 0x06 and 0x07 are refused (RFC 5480 sec. 2.2)");
|
|
75
|
+
}
|
|
76
|
+
if (form === 0x04) {
|
|
77
|
+
if (point.length !== 1 + 2 * scalarBytes) {
|
|
78
|
+
throw NS.E("sec1/bad-public-key", "an uncompressed point is 1 + 2 * " + scalarBytes + " octets on this curve, got " + point.length);
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (form === 0x02 || form === 0x03) {
|
|
83
|
+
if (point.length !== 1 + scalarBytes) {
|
|
84
|
+
throw NS.E("sec1/bad-public-key", "a compressed point is 1 + " + scalarBytes + " octets on this curve, got " + point.length);
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
throw NS.E("sec1/bad-public-key", "the stored public key's first octet names no point form (RFC 5480 sec. 2.2)");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
var EC_PRIVATE_KEY = schema.seq([
|
|
92
|
+
schema.field("version", schema.integerLeaf()),
|
|
93
|
+
schema.field("privateKey", schema.octetString()),
|
|
94
|
+
/** @internal RFC 5915's module is DEFINITIONS EXPLICIT TAGS, so `[0]` and `[1]` wrap their
|
|
95
|
+
* values, and do not replace their tags. */
|
|
96
|
+
/** @internal Read as ANY so the two shapes RFC 5480 sec. 2.1.1 names as MUST NOT, a SEQUENCE
|
|
97
|
+
* (specifiedCurve) and a NULL (implicitCurve), each get a verdict naming it. The OID reader
|
|
98
|
+
* would answer both with "expected tag 6", which says nothing about which one arrived. */
|
|
99
|
+
schema.optional("parameters", schema.any(), { tag: 0, explicit: true }),
|
|
100
|
+
schema.optional("publicKey", schema.bitString(), { tag: 1, explicit: true }),
|
|
101
|
+
], {
|
|
102
|
+
assert: "sequence", arity: { min: 2, max: 4 }, code: "sec1/not-an-ec-private-key", what: "ECPrivateKey",
|
|
103
|
+
build: function (m) {
|
|
104
|
+
var version = m.fields.version.value;
|
|
105
|
+
/** @internal RFC 5915 sec. 3 fixes the version at 1. A PKCS#8 PrivateKeyInfo opens with a
|
|
106
|
+
* version 0 INTEGER and an OCTET STRING too, so this is also what keeps one from reading as
|
|
107
|
+
* the other. */
|
|
108
|
+
if (version !== 1n) {
|
|
109
|
+
throw NS.E("sec1/bad-version", "an ECPrivateKey is version 1 (RFC 5915 sec. 3), got " + version);
|
|
110
|
+
}
|
|
111
|
+
if (!m.fields.parameters.present) {
|
|
112
|
+
throw NS.E("sec1/missing-curve", "an ECPrivateKey read on its own carries its [0] parameters, since the curve is what fixes the scalar's width (RFC 5915 sec. 3)");
|
|
113
|
+
}
|
|
114
|
+
var paramNode = m.fields.parameters.value;
|
|
115
|
+
if (paramNode.tagClass !== "universal" || paramNode.tagNumber !== asn1.TAGS.OBJECT_IDENTIFIER) {
|
|
116
|
+
throw NS.E("sec1/bad-curve", paramNode.tagClass === "universal" && paramNode.tagNumber === asn1.TAGS.SEQUENCE
|
|
117
|
+
? "the parameters field carries a specified curve, which RFC 5480 sec. 2.1.1 refuses; name the curve instead"
|
|
118
|
+
: (paramNode.tagClass === "universal" && paramNode.tagNumber === asn1.TAGS.NULL
|
|
119
|
+
? "the parameters field carries an implicit curve, which RFC 5480 sec. 2.1.1 refuses; name the curve instead"
|
|
120
|
+
: "the parameters field carries a named curve OBJECT IDENTIFIER and nothing else (RFC 5480 sec. 2.1.1)"));
|
|
121
|
+
}
|
|
122
|
+
var curveOid = asn1.read.oid(paramNode);
|
|
123
|
+
var curve = oid.name(curveOid);
|
|
124
|
+
var scalarBytes = curve === undefined ? undefined : CURVE_SCALAR_BYTES[curve];
|
|
125
|
+
if (scalarBytes === undefined) {
|
|
126
|
+
throw NS.E("sec1/unsupported-curve", "this toolkit holds no scalar width for the curve " + curveOid +
|
|
127
|
+
(curve === undefined ? "" : " (" + curve + ")"));
|
|
128
|
+
}
|
|
129
|
+
var scalar = m.fields.privateKey.value;
|
|
130
|
+
if (scalar.length !== scalarBytes) {
|
|
131
|
+
throw NS.E("sec1/bad-private-key", "the private key is " + scalarBytes + " octets on " + curve +
|
|
132
|
+
", with its leading zeros (RFC 5915 sec. 3), got " + scalar.length);
|
|
133
|
+
}
|
|
134
|
+
var out = { version: intrinsic.Number(version), privateKey: scalar, curve: curve, curveOid: curveOid };
|
|
135
|
+
if (m.fields.publicKey.present) {
|
|
136
|
+
var bits = m.fields.publicKey.value;
|
|
137
|
+
if (bits.unusedBits !== 0) {
|
|
138
|
+
throw NS.E("sec1/bad-public-key", "the stored public key's BIT STRING holds whole octets");
|
|
139
|
+
}
|
|
140
|
+
_assertPoint(bits.bytes, scalarBytes);
|
|
141
|
+
out.publicKey = bits.bytes;
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
var PARSE_OPTS = intrinsic.assign(intrinsic.create(null), {
|
|
148
|
+
pemLabel: LABEL, PemError: PemError, ErrorClass: Sec1Error, prefix: "sec1",
|
|
149
|
+
what: "EC private key", topSchema: EC_PRIVATE_KEY, ns: NS,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @primitive pki.schema.sec1.parse
|
|
154
|
+
* @signature pki.schema.sec1.parse(input, opts) -> key
|
|
155
|
+
* @since 0.8.8
|
|
156
|
+
* @status stable
|
|
157
|
+
* @spec RFC 5915
|
|
158
|
+
* @related pki.schema.sec1.encode, pki.schema.pkcs8.parse
|
|
159
|
+
*
|
|
160
|
+
* Read an RFC 5915 `ECPrivateKey`, as DER bytes or an `EC PRIVATE KEY` PEM block,
|
|
161
|
+
* into `{ version, privateKey, curve, curveOid, publicKey }`. The scalar and the
|
|
162
|
+
* stored point are `Buffer`s, and `curve` is the registry name of the named curve
|
|
163
|
+
* the key carries.
|
|
164
|
+
*
|
|
165
|
+
* The parameters field is required, because the curve is what fixes the scalar's
|
|
166
|
+
* width and a scalar of the wrong width is a different key. It carries a named
|
|
167
|
+
* curve and nothing else: a specified curve and an implicit curve are both refused
|
|
168
|
+
* with `sec1/bad-curve`, which RFC 5480 sec. 2.1.1 states as a MUST NOT.
|
|
169
|
+
*
|
|
170
|
+
* The stored public key is the encoding's own copy of the public half, not the key's
|
|
171
|
+
* identity. A verb that needs that half derives it from the scalar.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* // requires: der -- an ECPrivateKey, from `openssl ecparam -genkey` or an appliance
|
|
175
|
+
* var key = pki.schema.sec1.parse(der);
|
|
176
|
+
* key.version; // 1
|
|
177
|
+
* key.curve; // "prime256v1"
|
|
178
|
+
*/
|
|
179
|
+
var parse = pkix.makeParser(PARSE_OPTS);
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @primitive pki.schema.sec1.encode
|
|
183
|
+
* @signature pki.schema.sec1.encode(key) -> Buffer
|
|
184
|
+
* @since 0.8.8
|
|
185
|
+
* @status stable
|
|
186
|
+
* @spec RFC 5915
|
|
187
|
+
* @related pki.schema.sec1.parse
|
|
188
|
+
*
|
|
189
|
+
* Write an `ECPrivateKey` back to DER from the shape `parse` returns, with the
|
|
190
|
+
* parameters field always present, which RFC 5915 sec. 3 asks of a generator. A key
|
|
191
|
+
* read and written again produces the bytes it came from.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* // requires: der -- an ECPrivateKey, from `openssl ecparam -genkey` or an appliance
|
|
195
|
+
* pki.schema.sec1.encode(pki.schema.sec1.parse(der)).equals(der); // true
|
|
196
|
+
*/
|
|
197
|
+
function encode(key) {
|
|
198
|
+
if (key === null || typeof key !== "object") {
|
|
199
|
+
throw NS.E("sec1/bad-input", "encode takes the object parse returns");
|
|
200
|
+
}
|
|
201
|
+
var curveOid = key.curveOid;
|
|
202
|
+
if (typeof curveOid !== "string") {
|
|
203
|
+
var named = typeof key.curve === "string" ? oid.byName(key.curve) : undefined;
|
|
204
|
+
if (typeof named !== "string") {
|
|
205
|
+
throw NS.E("sec1/bad-input", "encode needs the key's named curve, as curveOid or a registered curve name");
|
|
206
|
+
}
|
|
207
|
+
curveOid = named;
|
|
208
|
+
}
|
|
209
|
+
var kids = [asn1.build.integer(intrinsic.BigInt(key.version === undefined ? 1 : key.version)),
|
|
210
|
+
asn1.build.octetString(key.privateKey),
|
|
211
|
+
asn1.build.explicit(0, asn1.build.oid(curveOid))];
|
|
212
|
+
if (key.publicKey !== undefined && key.publicKey !== null) {
|
|
213
|
+
kids[3] = asn1.build.explicit(1, asn1.build.bitString(key.publicKey, 0));
|
|
214
|
+
}
|
|
215
|
+
return asn1.build.sequence(kids);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function pemDecode(text) { return pkix.pemDecode(text, LABEL, PemError); }
|
|
219
|
+
function pemEncode(der) { return pkix.pemEncode(der, LABEL, PemError); }
|
|
220
|
+
|
|
221
|
+
/** @internal RFC 5915 sec. 3: `ECPrivateKey ::= SEQUENCE { version INTEGER, privateKey OCTET
|
|
222
|
+
* STRING, parameters [0] ECParameters OPTIONAL, publicKey [1] BIT STRING OPTIONAL }`. Structure
|
|
223
|
+
* alone does not separate this from every other SEQUENCE opening that way, which is why nothing
|
|
224
|
+
* routes to it from bare bytes; it answers for a caller that already has the armor label. */
|
|
225
|
+
function matches(root) {
|
|
226
|
+
var k = pkix.rootSequenceChildren(root, 2, 4);
|
|
227
|
+
if (!k) return false;
|
|
228
|
+
if (!schema.isUniversal(k[0], asn1.TAGS.INTEGER)) return false;
|
|
229
|
+
if (!schema.isUniversal(k[1], asn1.TAGS.OCTET_STRING)) return false;
|
|
230
|
+
var last = -1;
|
|
231
|
+
for (var i = 2; i < k.length; i++) {
|
|
232
|
+
if (!schema.isContextInRange(k[i], 0, 1) || k[i].tagNumber <= last) return false;
|
|
233
|
+
last = k[i].tagNumber;
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
module.exports = {
|
|
239
|
+
parse: parse,
|
|
240
|
+
matches: matches,
|
|
241
|
+
encode: encode,
|
|
242
|
+
pemDecode: pemDecode,
|
|
243
|
+
pemEncode: pemEncode,
|
|
244
|
+
};
|
package/lib/schema-tsp.js
CHANGED
|
@@ -455,8 +455,10 @@ function parseToken(input, callerOpts) {
|
|
|
455
455
|
* @related pki.schema.tsp.parse
|
|
456
456
|
*
|
|
457
457
|
* Extract the DER bytes from a PEM block (RFC 3161 defines no standard label, so the
|
|
458
|
-
*
|
|
459
|
-
*
|
|
458
|
+
* a block of any label is accepted unless `label` is given). The text holds one
|
|
459
|
+
* object: a file of several is refused with `pem/multiple-blocks` and read with
|
|
460
|
+
* `pki.schema.pem.decodeBundle`. Throws `PemError` on a missing envelope or a
|
|
461
|
+
* non-base64 body.
|
|
460
462
|
*
|
|
461
463
|
* @example
|
|
462
464
|
* var req = pki.tsp.request({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) }, {});
|
package/lib/schema-x509.js
CHANGED
|
@@ -51,8 +51,10 @@ var PemError = frameworkError.PemError;
|
|
|
51
51
|
* Extract the DER bytes from a PEM block (default label `CERTIFICATE`, the
|
|
52
52
|
* RFC 7468 sec. 5 armor, the canonical-label default every sibling format
|
|
53
53
|
* applies). Pass a `label` to enforce a different block type, or an explicit
|
|
54
|
-
* `null` to
|
|
55
|
-
*
|
|
54
|
+
* `null` to accept a block of any type. The text holds one object: a file of
|
|
55
|
+
* several is refused with `pem/multiple-blocks` and read with
|
|
56
|
+
* `pki.schema.pem.decodeBundle`. Throws `PemError` on a missing / mismatched
|
|
57
|
+
* envelope or a non-base64 body.
|
|
56
58
|
*
|
|
57
59
|
* @example
|
|
58
60
|
* var pair = await pki.key.generate("Ed25519");
|
package/lib/sigstore.js
CHANGED
|
@@ -1055,6 +1055,13 @@ async function _verifyChain(leaf, chainDers, fulcioRoots, timeMs) {
|
|
|
1055
1055
|
|
|
1056
1056
|
|
|
1057
1057
|
var SAN_OID = oid.byName("subjectAltName");
|
|
1058
|
+
/** @internal The shared table's entry, so the SAN this verb reads is read under the rules every
|
|
1059
|
+
* other consumer of a GeneralNames reads it under. It is built on the certificate namespace rather
|
|
1060
|
+
* than this module's, so a malformed name raises a CertificateError that `_identity` re-types to
|
|
1061
|
+
* `sigstore/bad-certificate` with that fault as its cause. That is the code this module already
|
|
1062
|
+
* reports for a certificate it cannot read, so the fix adds no new code to the public surface. */
|
|
1063
|
+
var _sanDecoder = pkix.certExtensionDecoders(
|
|
1064
|
+
pkix.makeNS("x509", frameworkError.CertificateError, oid)).byOid[SAN_OID];
|
|
1058
1065
|
var FULCIO_PREFIX = _join(_arraySlice(_split(oid.byName("otherName"), "."), 0, -1), ".") + ".";
|
|
1059
1066
|
function _identity(leaf) {
|
|
1060
1067
|
var out = { san: null, extensions: _create(null) };
|
|
@@ -1072,29 +1079,36 @@ function _identity(leaf) {
|
|
|
1072
1079
|
return out;
|
|
1073
1080
|
}
|
|
1074
1081
|
var GN_TYPE = _assign(_create(null), { 1: "rfc822Name", 2: "dNSName", 6: "uri" });
|
|
1082
|
+
/** @internal The GeneralNames come from the shared extension decoder, which is where the RFC 5280
|
|
1083
|
+
* sec. 4.2.1.6 rules live: at least one name, a non-empty IA5String for the text forms, and no
|
|
1084
|
+
* control byte inside one (CVE-2009-2408). Reading the SEQUENCE here instead left this verb
|
|
1085
|
+
* accepting a dNSName carrying a NUL or a CR and handing it on as the signer's identity.
|
|
1086
|
+
*
|
|
1087
|
+
* The one-identity rule counts every name the SAN carries, not the subset this verb turns into an
|
|
1088
|
+
* identity string. Counting only the recognized forms let a certificate pair a URI with an
|
|
1089
|
+
* iPAddress and pass as single-identity, with the second binding invisible to the caller's policy
|
|
1090
|
+
* -- which is the thing the rule exists to stop. A SAN of one name this verb has no string form
|
|
1091
|
+
* for still yields null, so a policy gates on the null rather than on a name nothing read. */
|
|
1075
1092
|
function _sanValue(ext) {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
}
|
|
1094
|
-
if (val) guard.list.append(names, val);
|
|
1093
|
+
/** @internal The shared decoder is the gate: it applies the sec. 4.2.1.6 rules and throws on a
|
|
1094
|
+
* name that breaks one. What it returns is a GeneralNames view rather than the codec nodes the
|
|
1095
|
+
* mapping below reads, so the count comes from it and the one name is then taken from the raw
|
|
1096
|
+
* decode, which carries the node shape `octetStringImplicit` and the otherName walk need. */
|
|
1097
|
+
var validated = _sanDecoder(ext.value).names || [];
|
|
1098
|
+
if (validated.length > 1) throw _err("sigstore/bad-certificate", "the Fulcio certificate carries multiple SAN identities");
|
|
1099
|
+
var n = (asn1.decode(ext.value).children || [])[0];
|
|
1100
|
+
if (!n || n.tagClass !== "context") return null;
|
|
1101
|
+
if (!n.constructed && GN_TYPE[n.tagNumber]) {
|
|
1102
|
+
return { type: GN_TYPE[n.tagNumber], value: _bufToString(asn1.read.octetStringImplicit(n, n.tagNumber), "utf8") };
|
|
1103
|
+
}
|
|
1104
|
+
if (n.constructed && n.tagNumber === 0 && (n.children || []).length >= 2) {
|
|
1105
|
+
var inner = (n.children[1].children || [])[0] || n.children[1];
|
|
1106
|
+
var v;
|
|
1107
|
+
try { v = asn1.read.string(inner); }
|
|
1108
|
+
catch (_e) { v = null; } // allow:swallow-unverified an otherName body with no string form has no identity string, which the caller's policy then gates on
|
|
1109
|
+
return { type: "otherName", oid: asn1.read.oid(n.children[0]), value: v };
|
|
1095
1110
|
}
|
|
1096
|
-
|
|
1097
|
-
return names[0] || null;
|
|
1111
|
+
return null;
|
|
1098
1112
|
}
|
|
1099
1113
|
function _fulcioExtValue(ext, leafArc) {
|
|
1100
1114
|
if (!_isBuffer(ext.value)) throw _err("sigstore/bad-certificate", "Fulcio extension " + ext.oid + " has no value");
|
package/lib/trust.js
CHANGED
|
@@ -383,8 +383,8 @@ function _copyDistrustAfter(src) {
|
|
|
383
383
|
function _copyName(name) {
|
|
384
384
|
if (!name || !Array.isArray(name.rdns)) return name;
|
|
385
385
|
var out = Object.assign({}, name);
|
|
386
|
-
out.rdns = name.rdns
|
|
387
|
-
return Array.isArray(rdn) ?
|
|
386
|
+
out.rdns = guard.list.copyMap(name.rdns, function (rdn) {
|
|
387
|
+
return Array.isArray(rdn) ? guard.list.copyMap(rdn, function (atv) { return Object.assign({}, atv); }) : rdn;
|
|
388
388
|
});
|
|
389
389
|
out.bytes = Buffer.isBuffer(name.bytes) ? Buffer.from(name.bytes) : name.bytes;
|
|
390
390
|
return out;
|
package/lib/tsp-sign.js
CHANGED
|
@@ -34,6 +34,9 @@ var pkiBuild = require("./pki-build");
|
|
|
34
34
|
var constants = require("./constants");
|
|
35
35
|
var TspError = frameworkError.TspError;
|
|
36
36
|
var _NS = pkix.makeNS("tsp", TspError, oid);
|
|
37
|
+
/** @internal The shared certificate-extension decoders, so an extension this module reads is read
|
|
38
|
+
* under the rules every other consumer reads it under rather than under a second set written here. */
|
|
39
|
+
var _EXT_DECODERS = pkix.certExtensionDecoders(_NS).byOid;
|
|
37
40
|
var b = asn1.build;
|
|
38
41
|
function _err(code, message, cause) { return new TspError(code, message, cause); }
|
|
39
42
|
|
|
@@ -76,7 +79,7 @@ function _certDer(c) {
|
|
|
76
79
|
c = buf.toString("latin1");
|
|
77
80
|
}
|
|
78
81
|
if (typeof c !== "string") throw _err("tsp/bad-input", "the TSA certificate must be a DER BufferSource or a PEM string");
|
|
79
|
-
var der = pkix.pemDecodeLenient(c, "CERTIFICATE");
|
|
82
|
+
var der = pkix.pemDecodeLenient(c, "CERTIFICATE", _err, "tsp/bad-input");
|
|
80
83
|
if (der === null) throw _err("tsp/bad-input", "the TSA certificate PEM is not a CERTIFICATE block");
|
|
81
84
|
return der;
|
|
82
85
|
}
|
|
@@ -487,12 +490,13 @@ function _checkTsaCertUsage(tsaCertDer) {
|
|
|
487
490
|
var exts = (cert.extensions || []).filter(function (e) { return e.oid === O("extKeyUsage"); });
|
|
488
491
|
if (exts.length !== 1) return "tsp/bad-eku";
|
|
489
492
|
if (!exts[0].critical) return "tsp/eku-not-critical";
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
493
|
+
/** @internal Read through the shared decoder, which is where the sec. 4.2.1.12 rules live: an
|
|
494
|
+
* ExtKeyUsage carries at least one KeyPurposeId. Reading the SEQUENCE here instead called an
|
|
495
|
+
* empty one well-formed and then reported it as the wrong PURPOSE, so the same class of fault
|
|
496
|
+
* answered with a different code here than at every other consumer of this extension. */
|
|
497
|
+
var purposes;
|
|
498
|
+
try { purposes = _EXT_DECODERS[O("extKeyUsage")](exts[0].value); }
|
|
499
|
+
catch (_e) { return "tsp/bad-eku"; } // allow:swallow-unverified this verb answers with a code rather than throwing, and every malformed shape is the one code
|
|
496
500
|
if (purposes.length !== 1 || purposes[0] !== O("timeStamping")) return "tsp/eku-not-exclusive";
|
|
497
501
|
var ku;
|
|
498
502
|
try { ku = pkix.keyUsageOf(_NS, cert, _err, "tsp/bad-key-usage", "TSA certificate"); }
|
|
@@ -508,13 +512,16 @@ function _tsaMatchesCert(tsaBytes, signerCert) {
|
|
|
508
512
|
if (gn.tagNumber === 4 && gn.value && guard.name.dnEqual(gn.value.rdns, signerCert.subject.rdns, _err, "tsp/tsa-mismatch", "the TSA subject")) return true;
|
|
509
513
|
var sanExt = (signerCert.extensions || []).filter(function (e) { return e.oid === O("subjectAltName"); })[0];
|
|
510
514
|
if (sanExt) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
515
|
+
/** @internal Gated by the shared decoder before the comparison, so a SAN this toolkit refuses
|
|
516
|
+
* everywhere else cannot be a source of identity here. The match itself stays a comparison of
|
|
517
|
+
* the encoded GeneralName bytes, which is what "the token names this TSA" means: the name in
|
|
518
|
+
* the token and the name in the certificate have to be the same name, not merely names that
|
|
519
|
+
* decode alike. */
|
|
520
|
+
var names;
|
|
521
|
+
try { names = _EXT_DECODERS[O("subjectAltName")](sanExt.value).names || []; }
|
|
522
|
+
catch (_e) { return false; } // allow:swallow-unverified a SAN that does not decode names no TSA, which is what this verb answers
|
|
523
|
+
for (var i = 0; i < names.length; i++) {
|
|
524
|
+
if (names[i].bytes && names[i].bytes.equals(tsaBytes)) return true;
|
|
518
525
|
}
|
|
519
526
|
}
|
|
520
527
|
return false;
|
package/lib/webauthn.js
CHANGED
|
@@ -350,7 +350,7 @@ var _FORMAT_SCOPED_BOOLEAN_OPTS = ["verifySafetyNetJws", "requireCtsProfileMatch
|
|
|
350
350
|
function _cloneParsed(v, depth) {
|
|
351
351
|
if (depth > 64) throw _err("webauthn/bad-input", "opts.rootCertificates[] is nested too deeply to be a parsed certificate");
|
|
352
352
|
if (guard.bytes.isByteSource(v)) return guard.bytes.snapshotSource(v, _err, "webauthn/bad-input", "a byte field of opts.rootCertificates[]");
|
|
353
|
-
if (Array.isArray(v)) return
|
|
353
|
+
if (Array.isArray(v)) return guard.list.copyMap(v, function (x) { return _cloneParsed(x, depth + 1); });
|
|
354
354
|
if (guard.time.isDate(v)) return new Date(guard.time.instantOf(v));
|
|
355
355
|
if (v && typeof v === "object") {
|
|
356
356
|
var out = intrinsic.create(null);
|
|
@@ -1497,9 +1497,9 @@ function _snapshotAssertion(input) {
|
|
|
1497
1497
|
}
|
|
1498
1498
|
out.credentialPublicKey = key;
|
|
1499
1499
|
}
|
|
1500
|
-
if (Array.isArray(out.allowedAlgorithms)) out.allowedAlgorithms = out.allowedAlgorithms
|
|
1501
|
-
if (Array.isArray(out.expectedOrigin)) out.expectedOrigin = out.expectedOrigin
|
|
1502
|
-
if (Array.isArray(out.expectedTopOrigin)) out.expectedTopOrigin = out.expectedTopOrigin
|
|
1500
|
+
if (Array.isArray(out.allowedAlgorithms)) out.allowedAlgorithms = guard.list.snapshot(out.allowedAlgorithms);
|
|
1501
|
+
if (Array.isArray(out.expectedOrigin)) out.expectedOrigin = guard.list.snapshot(out.expectedOrigin);
|
|
1502
|
+
if (Array.isArray(out.expectedTopOrigin)) out.expectedTopOrigin = guard.list.snapshot(out.expectedTopOrigin);
|
|
1503
1503
|
return out;
|
|
1504
1504
|
}
|
|
1505
1505
|
|
package/lib/webcrypto.js
CHANGED
|
@@ -1148,13 +1148,24 @@ function isCryptoKeyLike(x) {
|
|
|
1148
1148
|
}
|
|
1149
1149
|
|
|
1150
1150
|
// @internal
|
|
1151
|
-
|
|
1152
|
-
|
|
1151
|
+
/** @internal `want` names the WebCrypto export format, defaulting to the DER one the key's type
|
|
1152
|
+
* calls for. A key from another implementation can be reached three ways, and which of them works
|
|
1153
|
+
* is a property of the key rather than of the format asked for, so every format is exported
|
|
1154
|
+
* through the same three; a format that went straight to this engine's `exportKey` would refuse a
|
|
1155
|
+
* key this engine did not make while the others returned it. A JWK is an object rather than
|
|
1156
|
+
* bytes, so only the byte formats are wrapped. */
|
|
1157
|
+
function exportAnyKey(key, E, code, want) {
|
|
1158
|
+
var format = want || _EXPORT_FORMAT[key && key.type];
|
|
1153
1159
|
if (!format) throw E(code, "a CryptoKey with a private, public, or secret type is required");
|
|
1160
|
+
if (want === "jwk" && !_EXPORT_FORMAT[key && key.type]) {
|
|
1161
|
+
throw E(code, "a CryptoKey with a private, public, or secret type is required");
|
|
1162
|
+
}
|
|
1163
|
+
var asJwk = format === "jwk";
|
|
1164
|
+
function wrap(b) { return asJwk ? b : _bufferFrom(b); }
|
|
1154
1165
|
if (key instanceof CryptoKey) {
|
|
1155
1166
|
return _resolve(Promise)
|
|
1156
1167
|
.then(function () { return _crypto.subtle.exportKey(format, key); })
|
|
1157
|
-
.then(
|
|
1168
|
+
.then(wrap);
|
|
1158
1169
|
}
|
|
1159
1170
|
if (!key.extractable) {
|
|
1160
1171
|
throw E(code, "the CryptoKey comes from a different WebCrypto implementation and is not extractable, so its key material cannot be reached; import it through pki.webcrypto.subtle, or pass the key as DER");
|
|
@@ -1162,14 +1173,15 @@ function exportAnyKey(key, E, code) {
|
|
|
1162
1173
|
var handle = key._handle;
|
|
1163
1174
|
if (handle instanceof nodeCrypto.KeyObject && handle.type === key.type) {
|
|
1164
1175
|
return _resolve(Promise).then(function () {
|
|
1176
|
+
if (asJwk) return handle.export({ format: "jwk" });
|
|
1165
1177
|
return format === "raw" ? handle.export() : handle.export({ format: "der", type: format });
|
|
1166
|
-
}).then(
|
|
1178
|
+
}).then(wrap, function (e) {
|
|
1167
1179
|
throw E(code, "the CryptoKey comes from a different copy of this WebCrypto engine and its key material could not be read; import it through pki.webcrypto.subtle, or pass the key as DER", e);
|
|
1168
1180
|
});
|
|
1169
1181
|
}
|
|
1170
1182
|
return _resolve(Promise)
|
|
1171
1183
|
.then(function () { return nodeCrypto.webcrypto.subtle.exportKey(format, key); })
|
|
1172
|
-
.then(
|
|
1184
|
+
.then(wrap, function (e) {
|
|
1173
1185
|
throw E(code, "the CryptoKey comes from a different WebCrypto implementation and could not be exported for re-import; import it through pki.webcrypto.subtle, or pass the key as DER", e);
|
|
1174
1186
|
});
|
|
1175
1187
|
}
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:4cdebd00-c3c0-4ba6-9d9c-daacb902163c",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-09-
|
|
8
|
+
"timestamp": "2026-09-23T19:58:00.767Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.8.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.8.10",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.8.
|
|
25
|
+
"version": "0.8.10",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.8.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.8.10",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.8.
|
|
57
|
+
"ref": "@blamejs/pki@0.8.10",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|