diff options
author | Sean Michael Wykes <8363933+SeanWykes@users.noreply.github.com> | 2021-12-16 13:28:43 -0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-16 17:28:43 +0100 |
commit | 60faf7a0edd492771943e603ec3d01c3602150e8 (patch) | |
tree | 81de27bb87d50365b8245a37691faf92d3a787b1 /ext/crypto/00_crypto.js | |
parent | 8efe829fca5ddea855b7f569c74b67c161ec4b06 (diff) |
feat(ext/crypto): support importing ECSDA and ECDH (#13088)
Co-authored-by: Luca Casonato <hello@lcas.dev>
Diffstat (limited to 'ext/crypto/00_crypto.js')
-rw-r--r-- | ext/crypto/00_crypto.js | 271 |
1 files changed, 266 insertions, 5 deletions
diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index c8c7d9810..f15238b3a 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -108,6 +108,8 @@ "RSASSA-PKCS1-v1_5": "RsaHashedImportParams", "RSA-PSS": "RsaHashedImportParams", "RSA-OAEP": "RsaHashedImportParams", + "ECDSA": "EcImportParams", + "ECDH": "EcImportParams", "HMAC": "HmacImportParams", "HKDF": null, "PBKDF2": null, @@ -796,8 +798,9 @@ keyUsages, ); } + case "ECDH": case "ECDSA": { - return importKeyECDSA( + return importKeyEC( format, normalizedAlgorithm, keyData, @@ -1144,6 +1147,7 @@ } // 2. const hash = normalizedAlgorithm.hash.name; + // 3-8. return await core.opAsync("op_crypto_verify_key", { key: keyData, @@ -2195,13 +2199,28 @@ return key; } - function importKeyECDSA( + const SUPPORTED_EC_KEY_USAGES = { + "ECDSA": { + public: ["verify"], + private: ["sign"], + jwtUse: "sig", + }, + "ECDH": { + public: [], + private: ["deriveKey", "deriveBits"], + jwtUse: "enc", + }, + }; + + function importKeyEC( format, normalizedAlgorithm, keyData, extractable, keyUsages, ) { + const supportedUsages = SUPPORTED_EC_KEY_USAGES[normalizedAlgorithm.name]; + switch (format) { case "raw": { // 1. @@ -2221,7 +2240,7 @@ if ( ArrayPrototypeFind( keyUsages, - (u) => !ArrayPrototypeIncludes(["verify"], u), + (u) => !ArrayPrototypeIncludes(supportedUsages.public, u), ) !== undefined ) { throw new DOMException("Invalid key usages", "SyntaxError"); @@ -2229,7 +2248,7 @@ // 3. const { rawData } = core.opSync("op_crypto_import_key", { - algorithm: "ECDSA", + algorithm: normalizedAlgorithm.name, namedCurve: normalizedAlgorithm.namedCurve, }, { raw: keyData }); @@ -2238,7 +2257,7 @@ // 4-5. const algorithm = { - name: "ECDSA", + name: normalizedAlgorithm.name, namedCurve: normalizedAlgorithm.namedCurve, }; @@ -2253,6 +2272,248 @@ return key; } + case "pkcs8": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(supportedUsages.private, u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 2-9. + const { rawData } = core.opSync("op_crypto_import_key", { + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { pkcs8: keyData }); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; + + const key = constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + case "spki": { + // 1. + if (normalizedAlgorithm.name == "ECDSA") { + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(supportedUsages.public, u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + } else if (keyUsages.length != 0) { + throw new DOMException("Key usage must be empty", "SyntaxError"); + } + + // 2-12 + const { rawData } = core.opSync("op_crypto_import_key", { + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { spki: keyData }); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; + + // 6-8. + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + case "jwk": { + const jwk = keyData; + + const keyType = (jwk.d !== undefined) ? "private" : "public"; + + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(supportedUsages[keyType], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 3. + if (jwk.kty !== "EC") { + throw new DOMException( + "'kty' property of JsonWebKey must be 'EC'", + "DataError", + ); + } + + // 4. + if ( + keyUsages.length > 0 && jwk.use !== undefined && + jwk.use !== supportedUsages.jwkUse + ) { + throw new DOMException( + `'use' property of JsonWebKey must be '${supportedUsages.jwkUse}'`, + "DataError", + ); + } + + // 5. + // Section 4.3 of RFC7517 + if (jwk.key_ops !== undefined) { + if ( + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined + ) { + throw new DOMException( + "'key_ops' member of JsonWebKey is invalid", + "DataError", + ); + } + + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "'key_ops' member of JsonWebKey is invalid", + "DataError", + ); + } + } + + // 6. + if (jwk.ext === false && extractable === true) { + throw new DOMException( + "'ext' property of JsonWebKey must not be false if extractable is true", + "DataError", + ); + } + + // 9. + if (jwk.alg !== undefined && normalizedAlgorithm.name == "ECDSA") { + let algNamedCurve; + + switch (jwk.alg) { + case "ES256": { + algNamedCurve = "P-256"; + break; + } + case "ES384": { + algNamedCurve = "P-384"; + break; + } + case "ES512": { + algNamedCurve = "P-521"; + break; + } + default: + throw new DOMException( + "Curve algorithm not supported", + "DataError", + ); + } + + if (algNamedCurve) { + if (algNamedCurve !== normalizedAlgorithm.namedCurve) { + throw new DOMException( + "Mismatched curve algorithm", + "DataError", + ); + } + } + } + + // Validate that this is a valid public key. + if (jwk.x === undefined) { + throw new DOMException( + "'x' property of JsonWebKey is required for EC keys", + "DataError", + ); + } + if (jwk.y === undefined) { + throw new DOMException( + "'y' property of JsonWebKey is required for EC keys", + "DataError", + ); + } + + if (jwk.d !== undefined) { + // it's also a Private key + const { rawData } = core.opSync("op_crypto_import_key", { + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { jwkPrivateEc: jwk }); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; + + const key = constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } else { + const { rawData } = core.opSync("op_crypto_import_key", { + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { jwkPublicEc: jwk }); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; + + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + } default: throw new DOMException("Not implemented", "NotSupportedError"); } |