From 484258ee19babc648526001ecf415717e360c868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Fern=C3=A1ndez=20Garc=C3=ADa-Boente?= Date: Mon, 28 Oct 2024 18:28:49 +0100 Subject: [PATCH] New deriveKey test for ECDH This test has been ported from WebKit's source [1], defined to check the impact of the 'Get Key Length' behavior of HKDF and PBKDF2, which should return 'null' in both cases, in the 'deriveKey' operation. [1] https://bugs.webkit.org/show_bug.cgi?id=282096 --- .../derive_key_and_encrypt.https.any.js | 9 ++++ .../derive_key_and_encrypt.js | 49 +++++++++++++++++++ WebCryptoAPI/util/helpers.js | 38 ++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 WebCryptoAPI/derive_bits_keys/derive_key_and_encrypt.https.any.js create mode 100644 WebCryptoAPI/derive_bits_keys/derive_key_and_encrypt.js diff --git a/WebCryptoAPI/derive_bits_keys/derive_key_and_encrypt.https.any.js b/WebCryptoAPI/derive_bits_keys/derive_key_and_encrypt.https.any.js new file mode 100644 index 00000000000000..5edc832b6163bd --- /dev/null +++ b/WebCryptoAPI/derive_bits_keys/derive_key_and_encrypt.https.any.js @@ -0,0 +1,9 @@ +// META: title=WebCryptoAPI: deriveKey() Using HKDF and PBKDF2 from an ECDH key +// META: script=derive_key_and_encrypt.js +// META: script=../util/helpers.js + +// Test imported from WebKit's source, defined to check the impact of the +// 'Get Key Length' behavior of HKDF and PBKDF2, which should return 'null' +// in both cases, in the 'deriveKey' operation. +// https://bugs.webkit.org/show_bug.cgi?id=282096 +promise_test(define_tests, 'setup - define tests'); diff --git a/WebCryptoAPI/derive_bits_keys/derive_key_and_encrypt.js b/WebCryptoAPI/derive_bits_keys/derive_key_and_encrypt.js new file mode 100644 index 00000000000000..5963a852fcfbe1 --- /dev/null +++ b/WebCryptoAPI/derive_bits_keys/derive_key_and_encrypt.js @@ -0,0 +1,49 @@ +let iv = new Uint8Array(Array(12).keys()); +let salt = new Uint8Array(Array(10).keys()); +let plaintext = new Uint8Array(Array(100).keys()); + +function define_tests() { + importKeys().then((keys) => { + // Make sure that ecdh produces the same shared secret and the same encryption results using a key derived from that secret. + keys.forEach(keyData => { + promise_test(async() => { + let hkdfKey = await crypto.subtle.deriveKey({name: "ECDH", public: keyData.publicKey }, keyData.privateKey, { name: "HKDF", hash: "" , salt: new Uint8Array(), info: new Uint8Array() }, false, ["deriveKey"]); + let aesKey = await crypto.subtle.deriveKey({name: "HKDF", hash: "SHA-256", salt: salt, info: plaintext}, hkdfKey, {name:"AES-GCM", length: 256}, true, ["encrypt", "decrypt"]); + let result = await crypto.subtle.encrypt({ name: "AES-GCM", iv: iv }, aesKey, plaintext); + assert_equals(bytesToHexString(result), "a6280c522670eaf82f6564afbeb20a5b3f2d4e13c5596f6df3dcff8c34cb2118d2770fb24d83cfac5079c323118485bb01170292ee41eb82b07208f4840478fea3771d8922785c476ba06c2a0b933fc1661431419530a916ad4468545d1af5004a1149fea241c2ff1582ee58a8b7d79935de5def"); + }, "HKDF derivation of a ECDH key " + keyData.test); + promise_test(async() => { + let pkdf2Key = await crypto.subtle.deriveKey({name: "ECDH", public: keyData.publicKey }, keyData.privateKey, { name: "PBKDF2", hash: "" , salt: new Uint8Array(), iterations: 32 }, false, ["deriveKey"]); + let aesKey = await crypto.subtle.deriveKey({name: "PBKDF2", hash: "SHA-256", salt: salt, iterations: 32 }, pkdf2Key, { name:"AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]); + let result = await crypto.subtle.encrypt({ name: "AES-GCM", iv: iv }, aesKey, plaintext); + assert_equals(bytesToHexString(result), "c6201dfbb6fa92c1c246f6ce52f8f1c037f087efde41bac7f6485a2a8207623d2d3825b9cbe8ef864a90378667ed25544ce44cd2904bd96c19f0eeb611d626185165a8afb4e52f95700d7880f83939a42712fc4e377f198c01a61b397b76c3a4b93d932c321084bbef33332169dea09458b27df3"); + }, "PBKDF2 derivation of a ECDH key " + keyData.test); + }); + }, (e) => { + assert_unreached("Setup failed: " + e.message); + }); + + return Promise.resolve("define_tests"); +} + +async function importKeys() { + // "ECDSA" with a 'P-256' curve + let keyData = [ + hexStringToUint8Array("308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420fe77a808a7109ba5ceb93ebebad2c84a714d864ad29b62d6537e1969035c0079a144034200042684c752eef1c927a80c74e8b02ce459f848b5977f37fd878b36dae632be9a6cadd56126e404a4f75c535e5769d95b49fb1106f784f3d231b776d1f4d57927ce"), + hexStringToUint8Array("042684c752eef1c927a80c74e8b02ce459f848b5977f37fd878b36dae632be9a6cadd56126e404a4f75c535e5769d95b49fb1106f784f3d231b776d1f4d57927ce"), + hexStringToUint8Array("308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b020101042067521ccd1f85516118182bca3394c273bab9ce5cd6265105559e325e01f2df1ca144034200043042d8698882f2b59de972390d3fc9277e2e677a6c560148017c9475218fda1b38f76f7645fbcaf3d03e6259d080204fbafb04731b6ad53cb25c3d35d95b7c73"), + hexStringToUint8Array("043042d8698882f2b59de972390d3fc9277e2e677a6c560148017c9475218fda1b38f76f7645fbcaf3d03e6259d080204fbafb04731b6ad53cb25c3d35d95b7c73"), + ]; + let extractable = true; + var allKeys = await Promise.all([ + crypto.subtle.importKey("pkcs8", keyData[0], {name: "ECDH", namedCurve: "P-256"}, extractable, ["deriveKey", 'deriveBits']), + crypto.subtle.importKey("raw", keyData[1], {name: "ECDH", namedCurve: "P-256"}, extractable, []), + crypto.subtle.importKey("pkcs8", keyData[2], {name: "ECDH", namedCurve: "P-256"}, extractable, ["deriveKey", 'deriveBits']), + crypto.subtle.importKey("raw", keyData[3], {name: "ECDH", namedCurve: "P-256"}, extractable, []), + ]); + // Test cases defined combining public and private keys of each key-pair. + return [ + { test: 1, publicKey: allKeys[3], privateKey: allKeys[0] }, + { test: 2, publicKey: allKeys[1], privateKey: allKeys[2] } + ]; +} diff --git a/WebCryptoAPI/util/helpers.js b/WebCryptoAPI/util/helpers.js index bda97003263ff4..c60371dc6adac9 100644 --- a/WebCryptoAPI/util/helpers.js +++ b/WebCryptoAPI/util/helpers.js @@ -259,3 +259,41 @@ function allNameVariants(name, slowTest) { if (slowTest) return [mixedCaseName]; return unique([upCaseName, lowCaseName, mixedCaseName]); } + +// Builds a hex string representation for an array-like input. +// "bytes" can be an Array of bytes, an ArrayBuffer, or any TypedArray. +// The output looks like this: +// ab034c99 +function bytesToHexString(bytes) +{ + if (!bytes) + return null; + + bytes = new Uint8Array(bytes); + var hexBytes = []; + + for (var i = 0; i < bytes.length; ++i) { + var byteString = bytes[i].toString(16); + if (byteString.length < 2) + byteString = "0" + byteString; + hexBytes.push(byteString); + } + + return hexBytes.join(""); +} + +function hexStringToUint8Array(hexString) +{ + if (hexString.length % 2 != 0) + throw "Invalid hexString"; + var arrayBuffer = new Uint8Array(hexString.length / 2); + + for (var i = 0; i < hexString.length; i += 2) { + var byteValue = parseInt(hexString.substr(i, 2), 16); + if (byteValue == NaN) + throw "Invalid hexString"; + arrayBuffer[i/2] = byteValue; + } + + return arrayBuffer; +}