@blamejs/pki 0.6.39 → 0.6.41
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 +25 -0
- package/lib/acme.js +24 -13
- package/lib/asn1-der.js +5 -5
- package/lib/attrcert-sign.js +16 -16
- package/lib/cbor-det.js +2 -2
- package/lib/cmc-build.js +16 -16
- package/lib/cmc-verify.js +5 -5
- package/lib/cmp-build.js +29 -19
- package/lib/cmp-session.js +25 -20
- package/lib/cmp-verify.js +8 -12
- package/lib/cms-decrypt.js +8 -8
- package/lib/cms-encrypt.js +22 -22
- package/lib/cms-sign.js +17 -17
- package/lib/cms-verify.js +13 -13
- package/lib/composite-kem.js +1 -1
- package/lib/composite-sig.js +4 -4
- package/lib/constants.js +13 -9
- package/lib/crl-sign.js +25 -25
- package/lib/crmf-sign.js +11 -11
- package/lib/csr-sign.js +4 -4
- package/lib/ct.js +33 -25
- package/lib/edwards-point.js +2 -2
- package/lib/est.js +74 -43
- package/lib/guard-async.js +17 -1
- package/lib/guard-bytes.js +3 -3
- package/lib/guard-compress.js +5 -5
- package/lib/guard-identifier.js +46 -2
- package/lib/guard-json.js +1 -1
- package/lib/guard-list.js +16 -0
- package/lib/guard-name.js +1 -1
- package/lib/guard-parsed.js +1 -1
- package/lib/guard-time.js +3 -1
- package/lib/guard-verdict.js +7 -1
- package/lib/hpke.js +10 -10
- package/lib/http-digest.js +2 -2
- package/lib/http-transport.js +3 -3
- package/lib/inspect.js +9 -9
- package/lib/ip-utils.js +1 -1
- package/lib/jose.js +14 -14
- package/lib/key.js +7 -7
- package/lib/lint.js +6 -4
- package/lib/merkle.js +2 -2
- package/lib/mime.js +2 -2
- package/lib/ocsp-verify.js +1 -1
- package/lib/ocsp.js +5 -5
- package/lib/oid.js +3 -3
- package/lib/path-validate.js +127 -117
- package/lib/pbes2.js +2 -2
- package/lib/pkcs12-build.js +26 -24
- package/lib/pki-build.js +2 -2
- package/lib/scep.js +47 -16
- package/lib/schema-all.js +1 -1
- package/lib/schema-attrcert.js +4 -3
- package/lib/schema-c509.js +23 -23
- package/lib/schema-cmc.js +6 -6
- package/lib/schema-cmp.js +3 -3
- package/lib/schema-cms.js +15 -8
- package/lib/schema-crmf.js +1 -1
- package/lib/schema-csr.js +1 -1
- package/lib/schema-engine.js +2 -1
- package/lib/schema-pkcs12.js +1 -1
- package/lib/schema-pkix.js +3 -0
- package/lib/schema-tsp.js +2 -2
- package/lib/shbs.js +2 -2
- package/lib/sign-scheme.js +9 -9
- package/lib/sigstore.js +24 -14
- package/lib/smime.js +16 -16
- package/lib/tls-cert-compress.js +6 -6
- package/lib/trust.js +9 -9
- package/lib/tsp-sign.js +30 -30
- package/lib/validator-attcert.js +2 -2
- package/lib/validator-cose.js +5 -5
- package/lib/validator-sig.js +3 -3
- package/lib/validator-tpm.js +5 -4
- package/lib/webauthn.js +58 -54
- package/lib/webcrypto.js +22 -14
- package/lib/x509-sign.js +8 -8
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/validator-cose.js
CHANGED
|
@@ -9,20 +9,20 @@ var oid = require("./oid");
|
|
|
9
9
|
var edwardsPoint = require("./edwards-point");
|
|
10
10
|
var nodeCrypto = require("crypto");
|
|
11
11
|
|
|
12
|
-
var EC2_CRV_LEN = { 1: 32, 2: 48, 3: 66 };
|
|
13
|
-
var EC2_CRV_OID = { 1: "prime256v1", 2: "secp384r1", 3: "secp521r1" };
|
|
14
|
-
var OKP_CRV = { 6: { oid: "Ed25519", len: 32 }, 7: { oid: "Ed448", len: 57 } };
|
|
12
|
+
var EC2_CRV_LEN = Object.assign(Object.create(null), { 1: 32, 2: 48, 3: 66 });
|
|
13
|
+
var EC2_CRV_OID = Object.assign(Object.create(null), { 1: "prime256v1", 2: "secp384r1", 3: "secp521r1" });
|
|
14
|
+
var OKP_CRV = Object.assign(Object.create(null), { 6: { oid: "Ed25519", len: 32 }, 7: { oid: "Ed448", len: 57 } });
|
|
15
15
|
var RSA_MIN_MODULUS_BITS = 2048;
|
|
16
16
|
var RSA_MAX_EXPONENT_BYTES = 8;
|
|
17
17
|
function _modulusBits(n) { return (n.length - 1) * 8 + (32 - Math.clz32(n[0])); }
|
|
18
18
|
|
|
19
|
-
var ALG_PROFILE = {
|
|
19
|
+
var ALG_PROFILE = Object.assign(Object.create(null), {
|
|
20
20
|
"-7": { kty: 2, crv: 1 }, "-35": { kty: 2, crv: 2 }, "-36": { kty: 2, crv: 3 },
|
|
21
21
|
"-9": { kty: 2, crv: 1 }, "-51": { kty: 2, crv: 2 }, "-52": { kty: 2, crv: 3 },
|
|
22
22
|
"-8": { kty: 1, crv: 6 }, "-19": { kty: 1, crv: 6 }, "-53": { kty: 1, crv: 7 },
|
|
23
23
|
"-257": { kty: 3 }, "-258": { kty: 3 }, "-259": { kty: 3 },
|
|
24
24
|
"-37": { kty: 3 }, "-38": { kty: 3 }, "-39": { kty: 3 }, "-65535": { kty: 3 },
|
|
25
|
-
};
|
|
25
|
+
});
|
|
26
26
|
|
|
27
27
|
// @enforced-by validator-shape-reinlined
|
|
28
28
|
// @validator-shape kty\s*===\s*2n
|
package/lib/validator-sig.js
CHANGED
|
@@ -15,12 +15,12 @@ function rawToEcdsaDer(raw, coordLen) {
|
|
|
15
15
|
return asn1.build.sequence([asn1.build.integer(r), asn1.build.integer(s)]);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
var CURVE_FIELD_BYTES = { "P-256": 32, "P-384": 48, "P-521": 66 };
|
|
19
|
-
var CURVE_ORDER = {
|
|
18
|
+
var CURVE_FIELD_BYTES = Object.assign(Object.create(null), { "P-256": 32, "P-384": 48, "P-521": 66 });
|
|
19
|
+
var CURVE_ORDER = Object.assign(Object.create(null), {
|
|
20
20
|
"P-256": BigInt("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"),
|
|
21
21
|
"P-384": BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973"),
|
|
22
22
|
"P-521": BigInt("0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409"),
|
|
23
|
-
};
|
|
23
|
+
});
|
|
24
24
|
|
|
25
25
|
// @enforced-by behavioral -- a DER ECDSA-Sig-Value decode has no rename-proof code shape distinct
|
|
26
26
|
function ecdsaDerToP1363(der, curve, E, code) {
|
package/lib/validator-tpm.js
CHANGED
|
@@ -8,9 +8,9 @@ var guard = require("./guard-all");
|
|
|
8
8
|
|
|
9
9
|
var TPM_GENERATED_VALUE = 0xff544347;
|
|
10
10
|
var TPM_ST_ATTEST_CERTIFY = 0x8017;
|
|
11
|
-
var TPM_ALG = { RSA: 0x0001, SHA1: 0x0004, SHA256: 0x000b, SHA384: 0x000c, SHA512: 0x000d, NULL: 0x0010, ECC: 0x0023 };
|
|
12
|
-
var TPM_ALG_HASH =
|
|
13
|
-
var TPM_CURVE_TO_COSE =
|
|
11
|
+
var TPM_ALG = Object.assign(Object.create(null), { RSA: 0x0001, SHA1: 0x0004, SHA256: 0x000b, SHA384: 0x000c, SHA512: 0x000d, NULL: 0x0010, ECC: 0x0023 });
|
|
12
|
+
var TPM_ALG_HASH = Object.create(null); TPM_ALG_HASH[TPM_ALG.SHA1] = "sha1"; TPM_ALG_HASH[TPM_ALG.SHA256] = "sha256"; TPM_ALG_HASH[TPM_ALG.SHA384] = "sha384"; TPM_ALG_HASH[TPM_ALG.SHA512] = "sha512";
|
|
13
|
+
var TPM_CURVE_TO_COSE = Object.create(null); TPM_CURVE_TO_COSE[0x0003] = 1; TPM_CURVE_TO_COSE[0x0004] = 2; TPM_CURVE_TO_COSE[0x0005] = 3;
|
|
14
14
|
|
|
15
15
|
function _ucmp(a, b) {
|
|
16
16
|
function strip(x) { var i = 0; while (i < x.length - 1 && x[i] === 0) i++; return x.subarray(i); }
|
|
@@ -32,6 +32,7 @@ var TPMA_OBJECT_RESERVED = 0xfff0f009;
|
|
|
32
32
|
|
|
33
33
|
// @enforced-by behavioral -- a bit-position table lookup has no rename-proof code shape; the RED
|
|
34
34
|
function _decodeAttributes(oa) {
|
|
35
|
+
// allow:registry-table-inherits-object-prototype -- the attributes record an operator reads as result.tpm.attributes by field name, whose keys are the fixed TPMA_OBJECT_BITS list rather than anything the attestation supplies
|
|
35
36
|
var out = {};
|
|
36
37
|
Object.keys(TPMA_OBJECT_BITS).forEach(function (n) { out[n] = ((oa >>> TPMA_OBJECT_BITS[n]) & 1) === 1; });
|
|
37
38
|
return out;
|
|
@@ -112,7 +113,7 @@ function normalizeObjectAttributePolicy(policy, E, code) {
|
|
|
112
113
|
if (policy === undefined) return null;
|
|
113
114
|
if (!policy || typeof policy !== "object" || Array.isArray(policy)) throw new E(code, "opts.tpmPolicy must be an object");
|
|
114
115
|
guard.identifier.assertKnownKeys(policy, _TPM_POLICY_KEYS, function (c, m) { return new E(c, m); }, code, "opts.tpmPolicy has an unknown key ");
|
|
115
|
-
var want =
|
|
116
|
+
var want = Object.create(null);
|
|
116
117
|
if (policy.profile !== undefined) {
|
|
117
118
|
var preset = TPM_POLICY_PROFILES[guard.text.keyOf(policy.profile)];
|
|
118
119
|
if (!preset) throw new E(code, "opts.tpmPolicy.profile " + guard.text.showValue(policy.profile) + " is not a defined profile");
|
package/lib/webauthn.js
CHANGED
|
@@ -59,7 +59,7 @@ function _ucmp(a, b) {
|
|
|
59
59
|
return Buffer.compare(strip(a), strip(b)) === 0;
|
|
60
60
|
}
|
|
61
61
|
function _isInteger(node) { return !!node && !node.constructed && node.tagClass === "universal" && node.tagNumber === asn1.TAGS.INTEGER; }
|
|
62
|
-
var COSE_ALG_HASH = { "-7": "sha256", "-9": "sha256", "-257": "sha256", "-37": "sha256", "-35": "sha384", "-51": "sha384", "-258": "sha384", "-38": "sha384", "-36": "sha512", "-52": "sha512", "-259": "sha512", "-39": "sha512", "-65535": "sha1" };
|
|
62
|
+
var COSE_ALG_HASH = intrinsic.assign(intrinsic.create(null), { "-7": "sha256", "-9": "sha256", "-257": "sha256", "-37": "sha256", "-35": "sha384", "-51": "sha384", "-258": "sha384", "-38": "sha384", "-36": "sha512", "-52": "sha512", "-259": "sha512", "-39": "sha512", "-65535": "sha1" });
|
|
63
63
|
function _coseAlgHash(alg, E) {
|
|
64
64
|
var h = COSE_ALG_HASH[String(alg)];
|
|
65
65
|
if (!h) throw E("webauthn/unsupported-algorithm", "no hash mapping for COSE algorithm " + alg);
|
|
@@ -154,7 +154,7 @@ function parseCoseKey(bytes) {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
|
|
157
|
-
var COSE_ALG = {
|
|
157
|
+
var COSE_ALG = intrinsic.assign(intrinsic.create(null), {
|
|
158
158
|
"-7": { imp: { name: "ECDSA", namedCurve: "P-256" }, verify: { name: "ECDSA", hash: "SHA-256" }, ecdsa: 32 },
|
|
159
159
|
"-35": { imp: { name: "ECDSA", namedCurve: "P-384" }, verify: { name: "ECDSA", hash: "SHA-384" }, ecdsa: 48 },
|
|
160
160
|
"-36": { imp: { name: "ECDSA", namedCurve: "P-521" }, verify: { name: "ECDSA", hash: "SHA-512" }, ecdsa: 66 },
|
|
@@ -171,7 +171,7 @@ var COSE_ALG = {
|
|
|
171
171
|
"-38": { imp: { name: "RSA-PSS", hash: "SHA-384" }, verify: { name: "RSA-PSS", saltLength: 48 }, ecdsa: 0 },
|
|
172
172
|
"-39": { imp: { name: "RSA-PSS", hash: "SHA-512" }, verify: { name: "RSA-PSS", saltLength: 64 }, ecdsa: 0 },
|
|
173
173
|
"-65535": { imp: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" }, verify: { name: "RSASSA-PKCS1-v1_5" }, ecdsa: 0 },
|
|
174
|
-
};
|
|
174
|
+
});
|
|
175
175
|
|
|
176
176
|
function _derEcdsaToRaw(der, curve) { return validator.sig.ecdsaDerToP1363(der, curve, WebauthnError, "webauthn/bad-signature"); }
|
|
177
177
|
|
|
@@ -186,7 +186,7 @@ function _verifySig(alg, sig, spkiBytes, message, E) {
|
|
|
186
186
|
.then(function (key) { return subtle.verify(ver, key, s, message); })
|
|
187
187
|
.catch(function (e) { throw _err("webauthn/verify-error", "the attestation signature could not be evaluated", e); });
|
|
188
188
|
}
|
|
189
|
-
var ED_OID_NAME =
|
|
189
|
+
var ED_OID_NAME = intrinsic.create(null);
|
|
190
190
|
ED_OID_NAME[oid.byName("Ed25519")] = "Ed25519";
|
|
191
191
|
ED_OID_NAME[oid.byName("Ed448")] = "Ed448";
|
|
192
192
|
function _edName(spkiBytes, E) {
|
|
@@ -325,7 +325,7 @@ function _attRead(map, key, reader, what) {
|
|
|
325
325
|
function _algOf(attStmt) { return Number(_attRead(attStmt, "alg", cbor.read.int, "an integer")); }
|
|
326
326
|
function _sigOf(attStmt) { return _attRead(attStmt, "sig", cbor.read.byteString, "a byte string"); }
|
|
327
327
|
function _requireAttShape(attStmt, allowed, required) {
|
|
328
|
-
var have =
|
|
328
|
+
var have = intrinsic.create(null);
|
|
329
329
|
cbor.read.map(attStmt).forEach(function (kv) {
|
|
330
330
|
if (kv[0].majorType !== 3) throw _err("webauthn/bad-att-stmt", "the attestation statement has a non-text-string field key");
|
|
331
331
|
have[cbor.read.textString(kv[0])] = true;
|
|
@@ -333,7 +333,7 @@ function _requireAttShape(attStmt, allowed, required) {
|
|
|
333
333
|
Object.keys(have).forEach(function (k) { if (allowed.indexOf(k) === -1) throw _err("webauthn/bad-att-stmt", "the attestation statement carries an unexpected field '" + k + "'"); });
|
|
334
334
|
required.forEach(function (k) { if (!have[k]) throw _err("webauthn/bad-att-stmt", "the attestation statement is missing the '" + k + "' field"); });
|
|
335
335
|
}
|
|
336
|
-
var ATT_STMT_MAJOR = { compound: 4 };
|
|
336
|
+
var ATT_STMT_MAJOR = intrinsic.assign(intrinsic.create(null), { compound: 4 });
|
|
337
337
|
|
|
338
338
|
var _AAGUID_SIGNED_BY_FMT = Object.assign(Object.create(null), { "fido-u2f": false });
|
|
339
339
|
function _aaguidIsSigned(fmt) { return _AAGUID_SIGNED_BY_FMT[fmt] !== false; }
|
|
@@ -353,7 +353,7 @@ function _cloneParsed(v, depth) {
|
|
|
353
353
|
if (Array.isArray(v)) return v.map(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
|
-
var out =
|
|
356
|
+
var out = intrinsic.create(null);
|
|
357
357
|
Object.keys(v).forEach(function (k) { out[k] = _cloneParsed(v[k], depth + 1); });
|
|
358
358
|
return out;
|
|
359
359
|
}
|
|
@@ -395,8 +395,8 @@ function _applyCallerRoots(res, supplied, vopts, onlyPaths) {
|
|
|
395
395
|
.map(function (el) { return { tp: el.trustPath, at: el.chainValidatedAt }; })
|
|
396
396
|
: (res.trustPath && res.trustPath.length ? [{ tp: res.trustPath, at: res.chainValidatedAt }] : []));
|
|
397
397
|
if (!onlyPaths) {
|
|
398
|
-
res
|
|
399
|
-
anchored: paths.length };
|
|
398
|
+
res = guard.verdict.of(res, { anchoredElements: { total: (res.fmt === "compound" && Array.isArray(res.compound)) ? res.compound.length : 1,
|
|
399
|
+
anchored: paths.length } });
|
|
400
400
|
}
|
|
401
401
|
if (!paths.length) {
|
|
402
402
|
throw _err("webauthn/anchor-not-applicable",
|
|
@@ -410,8 +410,7 @@ function _applyCallerRoots(res, supplied, vopts, onlyPaths) {
|
|
|
410
410
|
"attestation trust path (against the roots supplied as opts.rootCertificates)");
|
|
411
411
|
});
|
|
412
412
|
}, Promise.resolve()).then(function () {
|
|
413
|
-
res
|
|
414
|
-
return res;
|
|
413
|
+
return guard.verdict.of(res, { anchoredTo: _anchoredRoutes(res, "rootCertificates") });
|
|
415
414
|
});
|
|
416
415
|
}
|
|
417
416
|
|
|
@@ -425,7 +424,7 @@ function _safetyNetAnchored(res) {
|
|
|
425
424
|
|
|
426
425
|
function _anchoredRoutes(res, base) {
|
|
427
426
|
var routes = base ? base.split("+") : [];
|
|
428
|
-
if (_safetyNetAnchored(res) && routes.indexOf("safetyNetRoots") < 0)
|
|
427
|
+
if (_safetyNetAnchored(res) && routes.indexOf("safetyNetRoots") < 0) guard.list.append(routes, "safetyNetRoots");
|
|
429
428
|
return routes.length ? routes.join("+") : null;
|
|
430
429
|
}
|
|
431
430
|
|
|
@@ -502,13 +501,13 @@ function _readX5c(attStmt) {
|
|
|
502
501
|
try { return x509.parse(der); } catch (e) { throw _err("webauthn/bad-att-stmt", "an x5c certificate is not a well-formed X.509 certificate", e); }
|
|
503
502
|
});
|
|
504
503
|
}
|
|
505
|
-
var _exts = { find: _findExt, decode: _decodeExt };
|
|
504
|
+
var _exts = intrinsic.assign(intrinsic.create(null), { find: _findExt, decode: _decodeExt });
|
|
506
505
|
function _requireV3(cert) { validator.attcert.requireV3(cert, WebauthnError, "webauthn/bad-att-cert"); }
|
|
507
506
|
function _checkPackedCert(cert) { validator.attcert.packedCert(cert, _exts, WebauthnError, "webauthn/bad-att-cert"); }
|
|
508
507
|
function _checkAikCert(cert) { validator.attcert.aikCert(cert, _exts, WebauthnError, "webauthn/bad-att-cert"); }
|
|
509
508
|
function _checkAaguidExt(cert, aaguid) { validator.attcert.aaguidExt(cert, aaguid, _exts, WebauthnError, "webauthn/bad-att-cert", "webauthn/aaguid-mismatch"); }
|
|
510
509
|
|
|
511
|
-
var VERIFIERS = {
|
|
510
|
+
var VERIFIERS = intrinsic.assign(intrinsic.create(null), {
|
|
512
511
|
packed: function (att, clientDataHash) {
|
|
513
512
|
var isX5c = !!cbor.read.mapGet(att.attStmt, "x5c");
|
|
514
513
|
_requireAttShape(att.attStmt, isX5c ? ["alg", "sig", "x5c"] : ["alg", "sig"], isX5c ? ["alg", "sig", "x5c"] : ["alg", "sig"]);
|
|
@@ -604,9 +603,8 @@ var VERIFIERS = {
|
|
|
604
603
|
_checkAikCert(aik);
|
|
605
604
|
_checkAaguidExt(aik, att.authData.aaguid);
|
|
606
605
|
validator.tpm.assertObjectAttributePolicy(pub, tpmPolicy, WebauthnError, "webauthn/tpm-policy", "webauthn/bad-tpm");
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
return tpmRes;
|
|
606
|
+
return guard.verdict.of(_result("tpm", "AttCA", chain, att),
|
|
607
|
+
{ tpm: { objectAttributes: pub.objectAttributes, attributes: pub.attributes, authPolicy: pub.authPolicy } });
|
|
610
608
|
});
|
|
611
609
|
},
|
|
612
610
|
|
|
@@ -680,9 +678,8 @@ var VERIFIERS = {
|
|
|
680
678
|
? new Date(payload.timestampMs) : undefined);
|
|
681
679
|
return _safetyNetChainTrusted(chain, roots, chainAt);
|
|
682
680
|
}).then(function () {
|
|
683
|
-
var res = _result("android-safetynet", "Basic", chain, att);
|
|
684
|
-
res
|
|
685
|
-
if (chainAt !== undefined) res.chainValidatedAt = chainAt;
|
|
681
|
+
var res = guard.verdict.of(_result("android-safetynet", "Basic", chain, att), { safetyNet: signals });
|
|
682
|
+
if (chainAt !== undefined) res = guard.verdict.of(res, { chainValidatedAt: chainAt });
|
|
686
683
|
return res;
|
|
687
684
|
});
|
|
688
685
|
},
|
|
@@ -710,14 +707,12 @@ var VERIFIERS = {
|
|
|
710
707
|
return elements.reduce(function (p, e) {
|
|
711
708
|
return p.then(function () {
|
|
712
709
|
return Promise.resolve().then(function () { return VERIFIERS[e.fmt](e.att, clientDataHash, opts); })
|
|
713
|
-
.then(function (r) {
|
|
710
|
+
.then(function (r) { guard.list.append(out, r); }, function (err) {
|
|
714
711
|
throw _err("webauthn/compound-element-failed", "compound element " + e.index + " (format '" + e.fmt + "') did not verify", err);
|
|
715
712
|
});
|
|
716
713
|
});
|
|
717
714
|
}, Promise.resolve()).then(function () {
|
|
718
|
-
|
|
719
|
-
res.compound = out;
|
|
720
|
-
return res;
|
|
715
|
+
return guard.verdict.of(_result("compound", "Compound", [], att), { compound: out });
|
|
721
716
|
});
|
|
722
717
|
},
|
|
723
718
|
|
|
@@ -727,7 +722,7 @@ var VERIFIERS = {
|
|
|
727
722
|
}
|
|
728
723
|
return Promise.resolve(_result("none", "None", [], att));
|
|
729
724
|
},
|
|
730
|
-
};
|
|
725
|
+
});
|
|
731
726
|
|
|
732
727
|
function _isPlainObject(v) { return !!v && typeof v === "object" && !Array.isArray(v); }
|
|
733
728
|
|
|
@@ -761,7 +756,7 @@ function _safetyNetChainTrusted(chain, roots, time) {
|
|
|
761
756
|
}
|
|
762
757
|
|
|
763
758
|
function _result(fmt, attestationType, chain, att) {
|
|
764
|
-
return {
|
|
759
|
+
return guard.verdict.of({
|
|
765
760
|
valid: true, attestationVerified: true, fmt: fmt, attestationType: attestationType,
|
|
766
761
|
trustPath: (chain || []).slice().reverse(),
|
|
767
762
|
aaguid: att.authData.aaguid,
|
|
@@ -772,7 +767,7 @@ function _result(fmt, attestationType, chain, att) {
|
|
|
772
767
|
flags: att.authData.flags,
|
|
773
768
|
rpIdHash: att.authData.rpIdHash,
|
|
774
769
|
extensions: att.authData.extensions,
|
|
775
|
-
};
|
|
770
|
+
});
|
|
776
771
|
}
|
|
777
772
|
|
|
778
773
|
function _appleNonce(extValue) {
|
|
@@ -969,37 +964,41 @@ function verify(attestationObject, clientDataHash, opts) {
|
|
|
969
964
|
} catch (e) { return Promise.reject(e); }
|
|
970
965
|
return Promise.resolve().then(function () { return verifier(att, clientDataHash, vopts); })
|
|
971
966
|
.then(function (res) {
|
|
972
|
-
res
|
|
973
|
-
return res;
|
|
967
|
+
return guard.verdict.of(res, { clientData: clientData });
|
|
974
968
|
})
|
|
975
969
|
.then(function (res) {
|
|
976
970
|
if (vopts.metadata === undefined) {
|
|
977
971
|
if (vopts.rootCertificates !== undefined) return _applyCallerRoots(res, vopts.rootCertificates, vopts);
|
|
978
|
-
res
|
|
979
|
-
var sn = _safetyNetAnchored(
|
|
972
|
+
var anchored = guard.verdict.of(res, { anchoredTo: _anchoredRoutes(res, null) });
|
|
973
|
+
var sn = _safetyNetAnchored(anchored);
|
|
980
974
|
if (sn) {
|
|
981
|
-
|
|
982
|
-
anchored:
|
|
975
|
+
anchored = guard.verdict.of(anchored, { anchoredElements: {
|
|
976
|
+
total: (anchored.fmt === "compound" && Array.isArray(anchored.compound)) ? anchored.compound.length : 1,
|
|
977
|
+
anchored: sn } });
|
|
983
978
|
}
|
|
984
|
-
return
|
|
979
|
+
return anchored;
|
|
985
980
|
}
|
|
986
981
|
return Promise.resolve().then(function () { return _applyMetadata(res, att, vopts); })
|
|
987
|
-
.then(function (out) { out
|
|
982
|
+
.then(function (out) { return guard.verdict.of(out, { anchoredTo: _anchoredRoutes(out, "metadata") }); }, function (e) {
|
|
988
983
|
if (!e || e.code !== "webauthn/metadata-not-found" || vopts.rootCertificates === undefined) throw e;
|
|
989
984
|
return Promise.resolve(_applyCallerRoots(res, vopts.rootCertificates, vopts, e.missedPaths))
|
|
990
|
-
.then(function (
|
|
985
|
+
.then(function (withRoots) {
|
|
986
|
+
/** @internal The element coverage the metadata attempt measured is the whole
|
|
987
|
+
* statement's, so it travels on the error rather than being recomputed from the
|
|
988
|
+
* fallback's own paths. */
|
|
989
|
+
var out = e.anchoredElements ? guard.verdict.of(withRoots, { anchoredElements: e.anchoredElements }) : withRoots;
|
|
991
990
|
var applied = e.appliedEntries || [];
|
|
992
991
|
if (!applied.length) return out;
|
|
993
992
|
var primary = applied[0];
|
|
994
|
-
|
|
993
|
+
var withMd = guard.verdict.of(out, { metadata: { aaguid: primary.entry.aaguid,
|
|
994
|
+
keyIdentifiers: primary.entry.keyIdentifiers || [],
|
|
995
995
|
entry: primary.entry, entries: applied.map(function (a) { return a.entry; }),
|
|
996
|
-
anchors: primary.anchors.length };
|
|
997
|
-
|
|
998
|
-
return out;
|
|
996
|
+
anchors: primary.anchors.length } });
|
|
997
|
+
return guard.verdict.of(withMd, { anchoredTo: _anchoredRoutes(withMd, "metadata+rootCertificates") });
|
|
999
998
|
});
|
|
1000
999
|
});
|
|
1001
1000
|
})
|
|
1002
|
-
.then(function (res) { res
|
|
1001
|
+
.then(function (res) { return guard.verdict.of(res, { bindingChecked: bindingChecked }); });
|
|
1003
1002
|
}
|
|
1004
1003
|
|
|
1005
1004
|
function _applyMetadata(res, att, opts) {
|
|
@@ -1011,8 +1010,9 @@ function _applyMetadata(res, att, opts) {
|
|
|
1011
1010
|
? res.compound.filter(function (el) { return el.trustPath && el.trustPath.length; })
|
|
1012
1011
|
.map(function (el) { return { tp: el.trustPath, fmt: el.fmt, at: el.chainValidatedAt }; })
|
|
1013
1012
|
: (res.trustPath && res.trustPath.length ? [{ tp: res.trustPath, fmt: res.fmt, at: res.chainValidatedAt }] : []);
|
|
1014
|
-
res
|
|
1015
|
-
|
|
1013
|
+
res = guard.verdict.of(res, { anchoredElements: {
|
|
1014
|
+
total: (res.fmt === "compound" && Array.isArray(res.compound)) ? res.compound.length : 1,
|
|
1015
|
+
anchored: paths.length } });
|
|
1016
1016
|
if (!paths.length) {
|
|
1017
1017
|
throw _err("webauthn/metadata-not-applicable", "opts.metadata was supplied, but this attestation carries no trust path to anchor (format '" + res.fmt + "')");
|
|
1018
1018
|
}
|
|
@@ -1036,7 +1036,7 @@ function _applyMetadata(res, att, opts) {
|
|
|
1036
1036
|
}
|
|
1037
1037
|
var anchors = mds.metadataAnchors(entry, { metadata: md, time: at, certificate: tp[tp.length - 1] });
|
|
1038
1038
|
if (!anchors.length) throw _err("webauthn/metadata-no-anchor", "the metadata entry for " + identifier + " supplies no attestation root certificate");
|
|
1039
|
-
|
|
1039
|
+
guard.list.append(applied, { entry: entry, anchors: anchors, identifier: identifier });
|
|
1040
1040
|
return { anchors: anchors, identifier: identifier };
|
|
1041
1041
|
}
|
|
1042
1042
|
var at = opts.time !== undefined ? opts.time : new Date();
|
|
@@ -1046,13 +1046,17 @@ function _applyMetadata(res, att, opts) {
|
|
|
1046
1046
|
var missedPaths = [];
|
|
1047
1047
|
for (var gi = 0; gi < paths.length; gi++) {
|
|
1048
1048
|
try {
|
|
1049
|
-
|
|
1049
|
+
guard.list.append(governed, { info: paths[gi], g: govern(paths[gi]) });
|
|
1050
1050
|
} catch (ge) {
|
|
1051
|
-
if (ge && ge.code === "webauthn/metadata-not-found") { missed = missed || ge;
|
|
1051
|
+
if (ge && ge.code === "webauthn/metadata-not-found") { missed = missed || ge; guard.list.append(missedPaths, paths[gi]); continue; }
|
|
1052
1052
|
throw ge;
|
|
1053
1053
|
}
|
|
1054
1054
|
}
|
|
1055
|
-
if (missed) {
|
|
1055
|
+
if (missed) {
|
|
1056
|
+
guard.verdict.set(missed, "missedPaths", missedPaths);
|
|
1057
|
+
guard.verdict.set(missed, "appliedEntries", applied);
|
|
1058
|
+
guard.verdict.set(missed, "anchoredElements", res.anchoredElements);
|
|
1059
|
+
}
|
|
1056
1060
|
return governed.reduce(function (p, item) {
|
|
1057
1061
|
return p.then(function () {
|
|
1058
1062
|
var g = item.g, info = item.info;
|
|
@@ -1064,10 +1068,10 @@ function _applyMetadata(res, att, opts) {
|
|
|
1064
1068
|
.then(function () {
|
|
1065
1069
|
if (missed) throw missed;
|
|
1066
1070
|
var primary = applied[0];
|
|
1067
|
-
res
|
|
1071
|
+
return guard.verdict.of(res, { metadata: { aaguid: primary.entry.aaguid,
|
|
1072
|
+
keyIdentifiers: primary.entry.keyIdentifiers || [],
|
|
1068
1073
|
entry: primary.entry, entries: applied.map(function (a) { return a.entry; }),
|
|
1069
|
-
anchors: primary.anchors.length };
|
|
1070
|
-
return res;
|
|
1074
|
+
anchors: primary.anchors.length } });
|
|
1071
1075
|
});
|
|
1072
1076
|
}
|
|
1073
1077
|
|
|
@@ -1469,7 +1473,7 @@ var _ASSERT_OPTS = Object.assign(Object.create(null), {
|
|
|
1469
1473
|
function _snapshotAssertion(input) {
|
|
1470
1474
|
if (!_isPlainObject(input)) throw _err("webauthn/bad-input", "pki.webauthn.verifyAssertion takes an options object");
|
|
1471
1475
|
guard.identifier.assertKnownKeys(input, _ASSERT_OPTS, _err, "webauthn/bad-input", "verifyAssertion input has an unknown key ");
|
|
1472
|
-
var out =
|
|
1476
|
+
var out = intrinsic.create(null), k;
|
|
1473
1477
|
for (k in input) { if (_hasOwn(input, k)) out[k] = input[k]; }
|
|
1474
1478
|
["authenticatorData", "clientDataJSON", "clientDataHash", "signature", "expectedChallenge"].forEach(function (f) {
|
|
1475
1479
|
if (_isBufferSource(out[f])) out[f] = guard.bytes.snapshotSource(out[f], WebauthnError, "webauthn/bad-input", f);
|
|
@@ -1478,7 +1482,7 @@ function _snapshotAssertion(input) {
|
|
|
1478
1482
|
out.credentialPublicKey = guard.bytes.snapshotSource(out.credentialPublicKey, WebauthnError,
|
|
1479
1483
|
"webauthn/bad-input", "credentialPublicKey");
|
|
1480
1484
|
} else if (_isPlainObject(out.credentialPublicKey)) {
|
|
1481
|
-
var key =
|
|
1485
|
+
var key = intrinsic.create(null), kk;
|
|
1482
1486
|
for (kk in out.credentialPublicKey) {
|
|
1483
1487
|
if (!_hasOwn(out.credentialPublicKey, kk)) continue;
|
|
1484
1488
|
var v;
|
|
@@ -1558,7 +1562,7 @@ function verifyAssertion(input) {
|
|
|
1558
1562
|
signCountChecked = true;
|
|
1559
1563
|
}
|
|
1560
1564
|
}
|
|
1561
|
-
return {
|
|
1565
|
+
return guard.verdict.of({
|
|
1562
1566
|
valid: true,
|
|
1563
1567
|
signatureVerified: true,
|
|
1564
1568
|
signCount: authData.signCount,
|
|
@@ -1568,7 +1572,7 @@ function verifyAssertion(input) {
|
|
|
1568
1572
|
extensions: authData.extensions,
|
|
1569
1573
|
clientData: clientData,
|
|
1570
1574
|
bindingChecked: bindingChecked,
|
|
1571
|
-
};
|
|
1575
|
+
});
|
|
1572
1576
|
});
|
|
1573
1577
|
});
|
|
1574
1578
|
}
|
package/lib/webcrypto.js
CHANGED
|
@@ -116,7 +116,7 @@ function _normalizeAlg(algorithm, who) {
|
|
|
116
116
|
if (!alg || typeof alg.name !== "string") {
|
|
117
117
|
throw new WebCryptoError("webcrypto/syntax", (who || "operation") + ": algorithm must be a string or an object with a name");
|
|
118
118
|
}
|
|
119
|
-
var out =
|
|
119
|
+
var out = intrinsic.create(null);
|
|
120
120
|
for (var k in alg) { if (_hasOwn(alg, k)) out[k] = alg[k]; }
|
|
121
121
|
out.name = _toUpperCase(alg.name);
|
|
122
122
|
return out;
|
|
@@ -125,7 +125,7 @@ function _normalizeAlg(algorithm, who) {
|
|
|
125
125
|
var _STD_ALG_NAME = intrinsic.assign(intrinsic.create(null), { "RSASSA-PKCS1-V1_5": "RSASSA-PKCS1-v1_5", "ED25519": "Ed25519", "ED448": "Ed448" });
|
|
126
126
|
function _stdName(name) { return _STD_ALG_NAME[name] || name; }
|
|
127
127
|
|
|
128
|
-
var HASH_NODE = {
|
|
128
|
+
var HASH_NODE = intrinsic.assign(intrinsic.create(null), {
|
|
129
129
|
"SHA-1": "sha1",
|
|
130
130
|
"SHA-224": "sha224",
|
|
131
131
|
"SHA-256": "sha256",
|
|
@@ -134,12 +134,12 @@ var HASH_NODE = {
|
|
|
134
134
|
"SHA3-256": "sha3-256",
|
|
135
135
|
"SHA3-384": "sha3-384",
|
|
136
136
|
"SHA3-512": "sha3-512",
|
|
137
|
-
};
|
|
137
|
+
});
|
|
138
138
|
|
|
139
|
-
var XOF_NODE = {
|
|
139
|
+
var XOF_NODE = intrinsic.assign(intrinsic.create(null), {
|
|
140
140
|
"SHAKE128": { node: "shake128", length: 32 },
|
|
141
141
|
"SHAKE256": { node: "shake256", length: 64 },
|
|
142
|
-
};
|
|
142
|
+
});
|
|
143
143
|
|
|
144
144
|
function _hashNode(h, who) {
|
|
145
145
|
var name = (typeof h === "string") ? h : (h && h.name);
|
|
@@ -148,10 +148,10 @@ function _hashNode(h, who) {
|
|
|
148
148
|
return node;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
var HMAC_BLOCK_BITS = {
|
|
151
|
+
var HMAC_BLOCK_BITS = intrinsic.assign(intrinsic.create(null), {
|
|
152
152
|
"SHA-1": 512, "SHA-256": 512, "SHA-384": 1024, "SHA-512": 1024,
|
|
153
153
|
"SHA3-256": 1088, "SHA3-384": 832, "SHA3-512": 576,
|
|
154
|
-
};
|
|
154
|
+
});
|
|
155
155
|
|
|
156
156
|
function _hmacLengthBits(alg, who) {
|
|
157
157
|
var name = (typeof alg.hash === "string") ? alg.hash : (alg.hash && alg.hash.name);
|
|
@@ -859,7 +859,7 @@ function _nodeKey(fn, who) {
|
|
|
859
859
|
}
|
|
860
860
|
}
|
|
861
861
|
|
|
862
|
-
var ML_KEM_INNER =
|
|
862
|
+
var ML_KEM_INNER = intrinsic.create(null);
|
|
863
863
|
["id-ml-kem-512", "id-ml-kem-768", "id-ml-kem-1024"].forEach(function (n) {
|
|
864
864
|
var row = oid.kemParams(n);
|
|
865
865
|
ML_KEM_INNER[oid.byName(n)] = { ek: row.ek, dk: row.dk };
|
|
@@ -891,7 +891,7 @@ function _validateMlKemInner(innerBytes, sizes) {
|
|
|
891
891
|
throw new WebCryptoError("webcrypto/data", "an ML-KEM private key must be the seed, expandedKey, or both CHOICE (RFC 9935 sec. 6)");
|
|
892
892
|
}
|
|
893
893
|
|
|
894
|
-
var PKCS8_INNER_VALIDATORS =
|
|
894
|
+
var PKCS8_INNER_VALIDATORS = intrinsic.create(null);
|
|
895
895
|
_forEach(_keys(ML_KEM_INNER), function (o) { PKCS8_INNER_VALIDATORS[o] = function (inner) { _validateMlKemInner(inner, ML_KEM_INNER[o]); }; });
|
|
896
896
|
|
|
897
897
|
function _preValidatePkcs8(p8, name) {
|
|
@@ -974,14 +974,22 @@ function _importRawPublic(name, alg, raw, extractable, usages) {
|
|
|
974
974
|
throw new WebCryptoError("webcrypto/not-supported", "importKey raw: unsupported public-key algorithm " + _stringify(name));
|
|
975
975
|
}
|
|
976
976
|
|
|
977
|
+
/** @internal Each inner set is asked whether it holds the key's own type, so it answers from its own
|
|
978
|
+
* entries alone: a type naming an Object.prototype member would otherwise resolve to a truthy
|
|
979
|
+
* inherited value and pass a key the algorithm does not accept. */
|
|
980
|
+
function _typeSet(names) {
|
|
981
|
+
var t = intrinsic.create(null);
|
|
982
|
+
_forEach(names, function (n) { t[n] = 1; });
|
|
983
|
+
return t;
|
|
984
|
+
}
|
|
977
985
|
var IMPORT_KEY_TYPE = intrinsic.assign(intrinsic.create(null), {
|
|
978
|
-
"RSASSA-PKCS1-V1_5":
|
|
979
|
-
"ECDSA":
|
|
980
|
-
"ED25519":
|
|
981
|
-
"X25519":
|
|
986
|
+
"RSASSA-PKCS1-V1_5": _typeSet(["rsa"]), "RSA-PSS": _typeSet(["rsa", "rsa-pss"]), "RSA-OAEP": _typeSet(["rsa"]),
|
|
987
|
+
"ECDSA": _typeSet(["ec"]), "ECDH": _typeSet(["ec"]),
|
|
988
|
+
"ED25519": _typeSet(["ed25519"]), "ED448": _typeSet(["ed448"]),
|
|
989
|
+
"X25519": _typeSet(["x25519"]), "X448": _typeSet(["x448"]),
|
|
982
990
|
});
|
|
983
991
|
[ML_DSA_NODE, ML_KEM_NODE, SLH_DSA_NODE].forEach(function (m) {
|
|
984
|
-
_forEach(_keys(m), function (webName) {
|
|
992
|
+
_forEach(_keys(m), function (webName) { IMPORT_KEY_TYPE[webName] = _typeSet([m[webName]]); });
|
|
985
993
|
});
|
|
986
994
|
function _algFromImport(name, alg, keyObject) {
|
|
987
995
|
var wantType = IMPORT_KEY_TYPE[name];
|
package/lib/x509-sign.js
CHANGED
|
@@ -40,18 +40,18 @@ function O(n) { return oid.byName(n); }
|
|
|
40
40
|
|
|
41
41
|
var OID_SKI = O("subjectKeyIdentifier");
|
|
42
42
|
|
|
43
|
-
var KNOWN_SPEC_KEYS = {
|
|
43
|
+
var KNOWN_SPEC_KEYS = intrinsic.assign(intrinsic.create(null), {
|
|
44
44
|
subject: 1, subjectPublicKey: 1, notBefore: 1, notAfter: 1, serialNumber: 1, extensions: 1,
|
|
45
|
-
};
|
|
46
|
-
var KNOWN_ISSUER_CERT_KEYS = { key: 1, cert: 1 };
|
|
47
|
-
var KNOWN_ISSUER_EXPLICIT_KEYS = { key: 1, name: 1, publicKey: 1 };
|
|
48
|
-
var KNOWN_SIGN_OPTS = { digestAlgorithm: 1, pem: 1, pss: 1 };
|
|
45
|
+
});
|
|
46
|
+
var KNOWN_ISSUER_CERT_KEYS = intrinsic.assign(intrinsic.create(null), { key: 1, cert: 1 });
|
|
47
|
+
var KNOWN_ISSUER_EXPLICIT_KEYS = intrinsic.assign(intrinsic.create(null), { key: 1, name: 1, publicKey: 1 });
|
|
48
|
+
var KNOWN_SIGN_OPTS = intrinsic.assign(intrinsic.create(null), { digestAlgorithm: 1, pem: 1, pss: 1 });
|
|
49
49
|
|
|
50
|
-
var KNOWN_EXT_KEYS = {
|
|
50
|
+
var KNOWN_EXT_KEYS = intrinsic.assign(intrinsic.create(null), {
|
|
51
51
|
subjectKeyIdentifier: 1, authorityKeyIdentifier: 1, keyUsage: 1, keyUsageCritical: 1,
|
|
52
52
|
extendedKeyUsage: 1, extendedKeyUsageCritical: 1, basicConstraints: 1, subjectAltName: 1,
|
|
53
53
|
certificatePolicies: 1, certificatePoliciesCritical: 1,
|
|
54
|
-
};
|
|
54
|
+
});
|
|
55
55
|
|
|
56
56
|
var _b = pkiBuild.makeBuilder({ ErrorClass: CertificateError, prefix: "x509", O: O, NS: NS, NAME_SCHEMA: NAME_SCHEMA, SPKI_SCHEMA: SPKI_SCHEMA, EXT_DECODERS: EXT_DECODERS });
|
|
57
57
|
var _encodeName = _b.encodeName, _isEmptyName = _b.isEmptyName, _reqDer = _b.reqDer,
|
|
@@ -80,7 +80,7 @@ function _akiKeyId(val, ctx) {
|
|
|
80
80
|
function _buildExtensions(extSpec, ctx) {
|
|
81
81
|
if (extSpec == null) return [];
|
|
82
82
|
if (_isArray(extSpec)) {
|
|
83
|
-
var seenExt =
|
|
83
|
+
var seenExt = intrinsic.create(null), arrCa = false, arrKeyCertSign = false, arrPathLen = false;
|
|
84
84
|
var oidBc = O("basicConstraints"), oidKu = O("keyUsage");
|
|
85
85
|
var arr = extSpec.map(function (e, i) {
|
|
86
86
|
var der = _reqDer(e, "extension");
|
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:c14ed2d9-f3d5-4322-88e1-51afbe3a3fbe",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-09-
|
|
8
|
+
"timestamp": "2026-09-06T02:19:25.175Z",
|
|
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.6.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.6.41",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.6.
|
|
25
|
+
"version": "0.6.41",
|
|
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.6.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.6.41",
|
|
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.6.
|
|
57
|
+
"ref": "@blamejs/pki@0.6.41",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|