diff --git a/.vscode/launch.json b/.vscode/launch.json index e43542e0ab..055b521e30 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -28,7 +28,7 @@ "AWS_SECRET_ACCESS_KEY": "", "AWS_SESSION_TOKEN": "" }, - "args": [], + "args": ["index.mjs"], "cwd": "${workspaceFolder}" }, { diff --git a/API.md b/API.md index 0c3d7ae7e7..dbddad25fa 100644 --- a/API.md +++ b/API.md @@ -52,6 +52,26 @@ Everything else inherited from [Uint8Array](https://developer.mozilla.org/en-US/ [randomUUID](https://nodejs.org/api/crypto.html#cryptorandomuuidoptions) +## crypto.subtle + +[subtle.decrypt](https://nodejs.org/api/webcrypto.html#subtledecryptalgorithm-key-data) + +[subtle.deriveBits](https://nodejs.org/api/webcrypto.html#subtlederivebitsalgorithm-basekey-length) + +[subtle.digest](https://nodejs.org/api/webcrypto.html#subtledigestalgorithm-data) + +[subtle.encrypt](https://nodejs.org/api/webcrypto.html#subtleencryptalgorithm-key-data) + +[subtle.exportKey](hthttps://nodejs.org/api/webcrypto.html#subtleexportkeyformat-key) + +[subtle.generateKey](https://nodejs.org/api/webcrypto.html#subtlegeneratekeyalgorithm-extractable-keyusages) + +[subtle.importKey](https://nodejs.org/api/webcrypto.html#subtleimportkeyformat-keydata-algorithm-extractable-keyusages) + +[subtle.sign](https://nodejs.org/api/webcrypto.html#subtlesignalgorithm-key-data) + +[subtle.verify](hthttps://nodejs.org/api/webcrypto.html#subtleverifyalgorithm-key-signature-datah) + ## events [EventEmitter](https://nodejs.org/api/events.html#class-eventemitter) diff --git a/Cargo.lock b/Cargo.lock index deff1ba1c1..ef7f7e9806 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,41 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.11" @@ -55,9 +90,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-tzdata" @@ -113,9 +148,9 @@ version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] @@ -154,6 +189,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" @@ -170,6 +211,12 @@ dependencies = [ "vsimd", ] +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bindgen" version = "0.69.5" @@ -184,12 +231,12 @@ dependencies = [ "lazycell", "log", "prettyplease", - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", "regex", "rustc-hash", "shlex", - "syn 2.0.86", + "syn 2.0.90", "which", ] @@ -208,6 +255,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "brotli" version = "7.0.0" @@ -255,9 +311,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" [[package]] name = "byteorder" @@ -267,9 +323,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cast" @@ -277,6 +333,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.1.31" @@ -305,9 +370,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -344,6 +409,16 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -357,18 +432,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstyle", "clap_lex", @@ -376,15 +451,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cmake" -version = "0.1.51" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" +checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" dependencies = [ "cc", ] @@ -398,6 +473,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "convert_case" version = "0.6.0" @@ -413,6 +494,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + [[package]] name = "crc32c" version = "0.6.8" @@ -529,6 +619,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -536,9 +638,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "deadpool" version = "0.10.0" @@ -557,6 +669,17 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "digest" version = "0.10.7" @@ -564,7 +687,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -573,9 +698,9 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] @@ -601,12 +726,47 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -615,12 +775,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -636,14 +796,24 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ "event-listener", "pin-project-lite", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "flate2" version = "1.0.35" @@ -733,9 +903,9 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] @@ -776,6 +946,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -791,6 +962,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.31.1" @@ -803,11 +984,22 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -853,9 +1045,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hermit-abi" @@ -879,6 +1071,24 @@ dependencies = [ "vsimd", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.9" @@ -890,9 +1100,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -936,9 +1146,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", @@ -1128,9 +1338,9 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] @@ -1141,24 +1351,43 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd69211b9b519e98303c015e21a007e293db403b6c85b9b124e133d25e242cdd" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "icu_normalizer", - "icu_properties", + "idna_adapter", "smallvec", "utf8_iter", ] +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", ] [[package]] @@ -1192,9 +1421,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -1207,10 +1436,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1229,6 +1459,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "lazycell" @@ -1238,20 +1471,26 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.161" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets", ] +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "libz-ng-sys" version = "1.1.20" @@ -1270,14 +1509,15 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "llrt" version = "0.3.0-beta" dependencies = [ + "cc", "chrono", "llrt_core", "snmalloc-rs", @@ -1413,8 +1653,12 @@ dependencies = [ name = "llrt_crypto" version = "0.3.0-beta" dependencies = [ + "aes", + "aes-gcm", + "cbc", "crc32c", "crc32fast", + "ctr", "llrt_buffer", "llrt_context", "llrt_encoding", @@ -1423,9 +1667,12 @@ dependencies = [ "md-5", "memchr", "once_cell", + "p256", + "p384", "rand", "ring", "rquickjs", + "rsa", "tokio", "uuid", "uuid-simd", @@ -1772,11 +2019,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -1810,6 +2056,43 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1817,6 +2100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1850,12 +2134,42 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "outref" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking" version = "2.2.1" @@ -1885,6 +2199,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1929,9 +2252,9 @@ checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" dependencies = [ "phf_generator", "phf_shared", - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] @@ -1955,6 +2278,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.31" @@ -1989,6 +2333,18 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -2004,8 +2360,17 @@ version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ - "proc-macro2 1.0.89", - "syn 2.0.86", + "proc-macro2 1.0.92", + "syn 2.0.90", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", ] [[package]] @@ -2029,18 +2394,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.37.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbfb3ddf5364c9cfcd65549a1e7b801d0e8d1b14c1a1590a6408aa93cfbfa84" +checksum = "f22f29bdff3987b4d8632ef95fd6424ec7e4e0a57e2f4fc63e489e75357f6a03" dependencies = [ "memchr", ] @@ -2060,7 +2425,7 @@ version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", ] [[package]] @@ -2137,9 +2502,9 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] @@ -2156,9 +2521,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -2177,6 +2542,16 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.8" @@ -2230,10 +2605,10 @@ dependencies = [ "phf_generator", "phf_shared", "proc-macro-crate", - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", "rquickjs-core", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] @@ -2245,6 +2620,27 @@ dependencies = [ "cc", ] +[[package]] +name = "rsa" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "sha2", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2268,15 +2664,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.38" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2340,6 +2736,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.23" @@ -2348,29 +2758,29 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -2384,6 +2794,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2399,6 +2820,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "simd-json" version = "0.14.3" @@ -2459,9 +2890,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2473,6 +2904,16 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2498,11 +2939,11 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.86" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", "unicode-ident", ] @@ -2513,16 +2954,16 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] name = "sysinfo" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ae3f4f7d64646c46c4cae4e3f01d1c5d255c7406fdd7c7f999a94e488791" +checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" dependencies = [ "core-foundation-sys", "libc", @@ -2563,9 +3004,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", @@ -2585,27 +3026,26 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -2655,9 +3095,9 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] @@ -2684,9 +3124,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-segmentation" @@ -2700,6 +3140,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -2825,9 +3275,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -2836,24 +3286,23 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote 1.0.37", "wasm-bindgen-macro-support", @@ -2861,28 +3310,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -2987,9 +3436,9 @@ version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] @@ -2998,9 +3447,9 @@ version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] @@ -3179,9 +3628,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -3191,13 +3640,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", "synstructure", ] @@ -3217,29 +3666,29 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", "synstructure", ] @@ -3266,9 +3715,9 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ - "proc-macro2 1.0.89", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.86", + "syn 2.0.90", ] [[package]] diff --git a/Makefile b/Makefile index 7a78917870..613584ff98 100644 --- a/Makefile +++ b/Makefile @@ -168,9 +168,8 @@ run-ssr: js cargo build cd example/functions && yarn build && cd build && ../../../target/debug/llrt -flame: export CARGO_PROFILE_RELEASE_DEBUG = true flame: - cargo flamegraph + cargo flamegraph --profile flame -- index.mjs run-cli: export RUST_LOG = llrt=trace run-cli: js diff --git a/libs/llrt_utils/src/macros.rs b/libs/llrt_utils/src/macros.rs index d72e0f4a94..0efad5626d 100644 --- a/libs/llrt_utils/src/macros.rs +++ b/libs/llrt_utils/src/macros.rs @@ -1,17 +1,55 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +#[macro_export] +macro_rules! count_members { + () => (0); + ($head:tt $(,$tail:tt)*) => (1 + count_members!($($tail),*)); +} + #[macro_export] macro_rules! iterable_enum { - ($visibility:vis, $name:ident, $($member:tt),*) => { - #[derive(Copy, Clone)] - $visibility enum $name {$($member),*} + ($name:ident, $($variant:ident),*) => { impl $name { - pub fn iterate() -> Vec<$name> { - vec![$($name::$member,)*] + const VARIANTS: &'static [$name] = &[$($name::$variant,)*]; + pub fn iter() -> std::slice::Iter<'static, $name> { + Self::VARIANTS.iter() + } + + #[allow(dead_code)] + fn _ensure_all_variants(s: Self) { + match s { + $($name::$variant => {},)* + } } } }; - ($name:ident, $($member:tt),*) => { - iterable_enum!(, $name, $($member),*) +} + +#[macro_export] +macro_rules! str_enum { + ($name:ident, $($variant:ident => $str:expr),*) => { + impl $name { + pub fn as_str(&self) -> &'static str { + match self { + $($name::$variant => $str,)* + } + } + } + + impl AsRef for $name { + fn as_ref(&self) -> &str { + self.as_str() + } + } + + impl TryFrom<&str> for $name { + type Error = String; + fn try_from(s: &str) -> std::result::Result { + match s.to_ascii_uppercase().as_str() { + $($str => Ok($name::$variant),)* + _ => Err(["'", s, "' not available"].concat()) + } + } + } }; } diff --git a/libs/llrt_utils/src/object.rs b/libs/llrt_utils/src/object.rs index 2658f3aa49..f53ae30fc8 100644 --- a/libs/llrt_utils/src/object.rs +++ b/libs/llrt_utils/src/object.rs @@ -1,14 +1,21 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{ - atom::PredefinedAtom, function::IntoJsFunc, prelude::Func, Array, Ctx, FromJs, IntoAtom, - IntoJs, Object, Result, Symbol, Undefined, Value, + atom::PredefinedAtom, function::IntoJsFunc, prelude::Func, Array, Ctx, Error, Exception, + FromJs, IntoAtom, IntoJs, Object, Result, Symbol, Undefined, Value, }; use crate::primordials::{BasePrimordials, Primordial}; pub trait ObjectExt<'js> { fn get_optional + Clone, V: FromJs<'js>>(&self, k: K) -> Result>; + fn get_required, V: FromJs<'js>>( + &self, + k: K, + object_name: &'static str, + ) -> Result; + fn into_object_or_throw(self, ctx: &Ctx<'js>, object_name: &'static str) + -> Result>; } impl<'js> ObjectExt<'js> for Object<'js> { @@ -18,6 +25,24 @@ impl<'js> ObjectExt<'js> for Object<'js> { ) -> Result> { self.get::>(k) } + + fn get_required, V: FromJs<'js>>( + &self, + k: K, + object_name: &'static str, + ) -> Result { + let k = k.as_ref(); + self.get::<&str, Option>(k)?.ok_or_else(|| { + Exception::throw_type( + self.ctx(), + &[object_name, " '", k, "' property required"].concat(), + ) + }) + } + + fn into_object_or_throw(self, _: &Ctx<'js>, _: &'static str) -> Result> { + Ok(self) + } } impl<'js> ObjectExt<'js> for Value<'js> { @@ -27,6 +52,29 @@ impl<'js> ObjectExt<'js> for Value<'js> { } Ok(None) } + + fn get_required, V: FromJs<'js>>( + &self, + k: K, + object_name: &'static str, + ) -> Result { + self.as_object() + .ok_or_else(|| not_a_object_error(self.ctx(), object_name))? + .get_required(k, object_name) + } + + fn into_object_or_throw( + self, + ctx: &Ctx<'js>, + object_name: &'static str, + ) -> Result> { + self.into_object() + .ok_or_else(|| not_a_object_error(ctx, object_name)) + } +} + +pub fn not_a_object_error(ctx: &Ctx<'_>, object_name: &str) -> Error { + Exception::throw_type(ctx, &[object_name, " is not an object"].concat()) } pub trait CreateSymbol<'js> { diff --git a/libs/llrt_utils/src/result.rs b/libs/llrt_utils/src/result.rs index 22e9666850..22377660a2 100644 --- a/libs/llrt_utils/src/result.rs +++ b/libs/llrt_utils/src/result.rs @@ -6,8 +6,8 @@ use rquickjs::{Ctx, Exception, Result}; pub trait ResultExt { fn or_throw_msg(self, ctx: &Ctx, msg: &str) -> Result; - fn or_throw_range(self, ctx: &Ctx, msg: Option<&str>) -> Result; - fn or_throw_type(self, ctx: &Ctx, msg: Option<&str>) -> Result; + fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result; + fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result; fn or_throw(self, ctx: &Ctx) -> Result; } @@ -32,10 +32,10 @@ impl ResultExt for StdResult { }) } - fn or_throw_range(self, ctx: &Ctx, msg: Option<&str>) -> Result { + fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result { self.map_err(|e| { let mut message = String::with_capacity(100); - if let Some(msg) = msg { + if !message.is_empty() { message.push_str(msg); message.push_str(". "); } @@ -44,10 +44,10 @@ impl ResultExt for StdResult { }) } - fn or_throw_type(self, ctx: &Ctx, msg: Option<&str>) -> Result { + fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result { self.map_err(|e| { let mut message = String::with_capacity(100); - if let Some(msg) = msg { + if !msg.is_empty() { message.push_str(msg); message.push_str(". "); } @@ -66,12 +66,12 @@ impl ResultExt for Option { self.ok_or_else(|| Exception::throw_message(ctx, msg)) } - fn or_throw_range(self, ctx: &Ctx, msg: Option<&str>) -> Result { - self.ok_or_else(|| Exception::throw_range(ctx, msg.unwrap_or(""))) + fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result { + self.ok_or_else(|| Exception::throw_range(ctx, msg)) } - fn or_throw_type(self, ctx: &Ctx, msg: Option<&str>) -> Result { - self.ok_or_else(|| Exception::throw_type(ctx, msg.unwrap_or(""))) + fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result { + self.ok_or_else(|| Exception::throw_type(ctx, msg)) } fn or_throw(self, ctx: &Ctx) -> Result { diff --git a/llrt/Cargo.toml b/llrt/Cargo.toml index 81657b2efb..6da651f426 100644 --- a/llrt/Cargo.toml +++ b/llrt/Cargo.toml @@ -21,6 +21,8 @@ tokio = { version = "1", features = ["full"] } [target.'cfg(not(target_os = "windows"))'.dependencies] snmalloc-rs = { version = "0.3.7", features = ["lto"] } +# FIXME remove when https://github.com/rust-lang/cc-rs/issues/1278 is resolved +cc = "=1.1.31" [[bin]] name = "llrt" diff --git a/llrt_core/src/modules/util/text_decoder.rs b/llrt_core/src/modules/util/text_decoder.rs index 1f3ec53475..7679d34622 100644 --- a/llrt_core/src/modules/util/text_decoder.rs +++ b/llrt_core/src/modules/util/text_decoder.rs @@ -22,7 +22,7 @@ impl<'js> TextDecoder { let mut fatal = false; let mut ignore_bom = false; - let encoder = Encoder::from_optional_str(label.as_deref()).or_throw_range(&ctx, None)?; + let encoder = Encoder::from_optional_str(label.as_deref()).or_throw_range(&ctx, "")?; if let Some(options) = options.0 { if let Some(opt) = options.get_optional("fatal")? { @@ -69,6 +69,6 @@ impl<'js> TextDecoder { self.encoder .encode_to_string(&bytes[start_pos..], !self.fatal) - .or_throw_type(&ctx, None) + .or_throw_type(&ctx, "") } } diff --git a/modules/llrt_crypto/Cargo.toml b/modules/llrt_crypto/Cargo.toml index 3cce71245b..9ed9236307 100644 --- a/modules/llrt_crypto/Cargo.toml +++ b/modules/llrt_crypto/Cargo.toml @@ -20,7 +20,7 @@ llrt_encoding = { version = "0.3.0-beta", path = "../../libs/llrt_encoding" } llrt_utils = { version = "0.3.0-beta", path = "../../libs/llrt_utils", default-features = false } once_cell = "1" rand = "0.8" -ring = "0.17" +ring = { version = "0.17", features = ["std"] } rquickjs = { git = "https://github.com/DelSkayn/rquickjs.git", version = "0.8.1", features = [ "macro", ], default-features = false } @@ -30,6 +30,14 @@ uuid = { version = "1", default-features = false, features = [ ] } uuid-simd = "0.8" +aes = "0.8" +aes-gcm = "0.10" +cbc = { version = "0.1", features = ["std"] } +ctr = "0.9" +rsa = { version = "0.9", features = ["std", "sha2"], default-features = false } +p256 = { version = "0.13", features = ["ecdh"] } +p384 = "0.13" + [target.'cfg(target_os = "windows")'.dependencies] memchr = "2" md-5 = "0.10" diff --git a/modules/llrt_crypto/src/lib.rs b/modules/llrt_crypto/src/lib.rs index 0f9cf6f9b3..43af99147a 100644 --- a/modules/llrt_crypto/src/lib.rs +++ b/modules/llrt_crypto/src/lib.rs @@ -3,6 +3,8 @@ mod crc32; mod md5_hash; mod sha_hash; +mod subtle; + use std::slice; use llrt_buffer::Buffer; @@ -22,9 +24,13 @@ use ring::rand::{SecureRandom, SystemRandom}; use rquickjs::{ function::{Constructor, Opt}, module::{Declarations, Exports, ModuleDef}, - prelude::{Func, Rest}, + prelude::{Async, Func, Rest}, Class, Ctx, Error, Exception, Function, IntoJs, Null, Object, Result, Value, }; +use subtle::{ + subtle_decrypt, subtle_derive_bits, subtle_derive_key, subtle_digest, subtle_encrypt, + subtle_export_key, subtle_generate_key, subtle_sign, subtle_verify, CryptoKey, +}; use uuid::Uuid; use uuid_simd::UuidExt; @@ -174,6 +180,8 @@ fn uuidv4() -> String { pub fn init(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); + Class::::define(&globals)?; + let crypto = Object::new(ctx.clone())?; crypto.set("createHash", Func::from(Hash::new))?; @@ -185,6 +193,19 @@ pub fn init(ctx: &Ctx<'_>) -> Result<()> { crypto.set("randomFill", Func::from(random_fill))?; crypto.set("getRandomValues", Func::from(get_random_values))?; + let subtle = Object::new(ctx.clone())?; + subtle.set("decrypt", Func::from(Async(subtle_decrypt)))?; + subtle.set("deriveKey", Func::from(Async(subtle_derive_key)))?; + subtle.set("deriveBits", Func::from(Async(subtle_derive_bits)))?; + subtle.set("digest", Func::from(Async(subtle_digest)))?; + subtle.set("encrypt", Func::from(Async(subtle_encrypt)))?; + subtle.set("exportKey", Func::from(Async(subtle_export_key)))?; + subtle.set("generateKey", Func::from(Async(subtle_generate_key)))?; + // subtle.set("importKey", Func::from(Async(subtle_import_key)))?; + subtle.set("sign", Func::from(Async(subtle_sign)))?; + subtle.set("verify", Func::from(Async(subtle_verify)))?; + crypto.set("subtle", subtle)?; + globals.set("crypto", crypto)?; Ok(()) @@ -206,7 +227,7 @@ impl ModuleDef for CryptoModule { declare.declare("randomFill")?; declare.declare("getRandomValues")?; - for sha_algorithm in ShaAlgorithm::iterate() { + for sha_algorithm in ShaAlgorithm::iter() { let class_name = sha_algorithm.class_name(); declare.declare(class_name)?; } @@ -218,12 +239,12 @@ impl ModuleDef for CryptoModule { fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { - for sha_algorithm in ShaAlgorithm::iterate() { + for sha_algorithm in ShaAlgorithm::iter() { let class_name: &str = sha_algorithm.class_name(); let algo = sha_algorithm; let ctor = Constructor::new_class::(ctx.clone(), move |secret| { - ShaHash::new(algo, secret) + ShaHash::new(algo.clone(), secret) })?; default.set(class_name, ctor)?; diff --git a/modules/llrt_crypto/src/sha_hash.rs b/modules/llrt_crypto/src/sha_hash.rs index cca91d6170..091c44cf27 100644 --- a/modules/llrt_crypto/src/sha_hash.rs +++ b/modules/llrt_crypto/src/sha_hash.rs @@ -3,12 +3,13 @@ use llrt_utils::{ bytes::{bytes_to_typed_array, ObjectBytes}, iterable_enum, + result::ResultExt, }; use ring::{ digest::{self, Context as DigestContext}, hmac::{self, Context as HmacContext}, }; -use rquickjs::{function::Opt, prelude::This, Class, Ctx, Exception, Result, Value}; +use rquickjs::{function::Opt, prelude::This, Class, Ctx, Result, Value}; use super::encoded_bytes; @@ -23,18 +24,8 @@ pub struct Hmac { impl Hmac { #[qjs(skip)] pub fn new<'js>(ctx: Ctx<'js>, algorithm: String, key_value: ObjectBytes<'js>) -> Result { - let algorithm = match algorithm.to_lowercase().as_str() { - "sha1" => hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, - "sha256" => hmac::HMAC_SHA256, - "sha384" => hmac::HMAC_SHA384, - "sha512" => hmac::HMAC_SHA512, - _ => { - return Err(Exception::throw_message( - &ctx, - &["Algorithm \"", &algorithm, "\" not supported"].concat(), - )) - }, - }; + let algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; + let algorithm = *algorithm.hmac_algorithm(); Ok(Self { context: HmacContext::with_key(&hmac::Key::new(algorithm, key_value.as_bytes())), @@ -81,18 +72,8 @@ pub struct Hash { impl Hash { #[qjs(skip)] pub fn new(ctx: Ctx<'_>, algorithm: String) -> Result { - let algorithm = match algorithm.to_lowercase().as_str() { - "sha1" => &digest::SHA1_FOR_LEGACY_USE_ONLY, - "sha256" => &digest::SHA256, - "sha384" => &digest::SHA384, - "sha512" => &digest::SHA512, - _ => { - return Err(Exception::throw_message( - &ctx, - &["Algorithm \"", &algorithm, "\" not supported"].concat(), - )) - }, - }; + let algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; + let algorithm = algorithm.digest_algorithm(); Ok(Self { context: DigestContext::new(algorithm), @@ -121,7 +102,15 @@ impl Hash { } } -iterable_enum!(pub, ShaAlgorithm, SHA1, SHA256, SHA384, SHA512); +#[derive(Debug, Clone)] +pub enum ShaAlgorithm { + SHA1, + SHA256, + SHA384, + SHA512, +} + +iterable_enum!(ShaAlgorithm, SHA1, SHA256, SHA384, SHA512); impl ShaAlgorithm { pub fn class_name(&self) -> &'static str { @@ -141,7 +130,7 @@ impl ShaAlgorithm { } } - fn digest_algorithm(&self) -> &'static digest::Algorithm { + pub fn digest_algorithm(&self) -> &'static digest::Algorithm { match self { ShaAlgorithm::SHA1 => &digest::SHA1_FOR_LEGACY_USE_ONLY, ShaAlgorithm::SHA256 => &digest::SHA256, @@ -149,6 +138,38 @@ impl ShaAlgorithm { ShaAlgorithm::SHA512 => &digest::SHA512, } } + + pub fn as_str(&self) -> &'static str { + match self { + ShaAlgorithm::SHA1 => "SHA-1", + ShaAlgorithm::SHA256 => "SHA-256", + ShaAlgorithm::SHA384 => "SHA-384", + ShaAlgorithm::SHA512 => "SHA-512", + } + } +} + +impl TryFrom<&str> for ShaAlgorithm { + type Error = String; + fn try_from(s: &str) -> std::result::Result { + Ok(match s.to_ascii_uppercase().as_str() { + "SHA1" => ShaAlgorithm::SHA1, + "SHA-1" => ShaAlgorithm::SHA1, + "SHA256" => ShaAlgorithm::SHA256, + "SHA-256" => ShaAlgorithm::SHA256, + "SHA384" => ShaAlgorithm::SHA384, + "SHA-384" => ShaAlgorithm::SHA384, + "SHA512" => ShaAlgorithm::SHA512, + "SHA-512" => ShaAlgorithm::SHA512, + _ => return Err(["'", s, "' not available"].concat()), + }) + } +} + +impl AsRef for ShaAlgorithm { + fn as_ref(&self) -> &str { + self.as_str() + } } #[rquickjs::class] diff --git a/modules/llrt_crypto/src/subtle/crypto_key.rs b/modules/llrt_crypto/src/subtle/crypto_key.rs new file mode 100644 index 0000000000..21eaf29447 --- /dev/null +++ b/modules/llrt_crypto/src/subtle/crypto_key.rs @@ -0,0 +1,92 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use std::rc::Rc; + +use rquickjs::{ + class::{Trace, Tracer}, + Ctx, Result, Value, +}; + +use super::key_algorithm::KeyAlgorithm; + +#[rquickjs::class] +#[derive(rquickjs::JsLifetime)] +pub struct CryptoKey { + type_name: &'static str, + pub extractable: bool, + pub algorithm: KeyAlgorithm, + pub name: Box, + usages: Vec, + pub handle: Rc<[u8]>, +} + +impl CryptoKey { + pub fn new( + type_name: &'static str, + name: N, + extractable: bool, + algorithm: KeyAlgorithm, + usages: Vec, + handle: H, + ) -> Self + where + N: Into>, + H: Into>, + { + Self { + type_name, + extractable, + algorithm, + name: name.into(), + usages, + handle: handle.into(), + } + } +} + +impl<'js> Trace<'js> for CryptoKey { + fn trace<'a>(&self, _: Tracer<'a, 'js>) {} +} + +#[rquickjs::methods] +impl CryptoKey { + #[qjs(get, rename = "type")] + pub fn get_type(&self) -> &str { + self.type_name + } + + #[qjs(get)] + pub fn extractable(&self) -> bool { + self.extractable + } + + #[qjs(get)] + pub fn algorithm<'js>(&self, ctx: Ctx<'js>) -> Result> { + self.algorithm + .as_object(&ctx, self.name.as_ref()) + .map(|a| a.into_value()) + } + + #[qjs(get)] + pub fn usages(&self) -> Vec { + self.usages.iter().map(|u| u.to_string()).collect() + } +} + +impl CryptoKey { + pub fn check_validity(&self, usage: &str) -> std::result::Result<(), String> { + for key in self.usages.iter() { + if key == usage { + return Ok(()); + } + } + Err([ + "CryptoKey with '", + self.name.as_ref(), + "', doesn't support '", + usage, + "'", + ] + .concat()) + } +} diff --git a/modules/llrt_crypto/src/subtle/decrypt.rs b/modules/llrt_crypto/src/subtle/decrypt.rs new file mode 100644 index 0000000000..56a9b14bb5 --- /dev/null +++ b/modules/llrt_crypto/src/subtle/decrypt.rs @@ -0,0 +1,121 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use aes::cipher::{block_padding::Pkcs7, typenum::U12, BlockDecryptMut, KeyIvInit}; +use aes_gcm::Nonce; +use ctr::cipher::StreamCipher; +use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; +use rquickjs::{ArrayBuffer, Class, Ctx, Result}; + +use crate::subtle::{ + Aes128Ctr128, Aes128Ctr32, Aes128Ctr64, Aes192Ctr128, Aes192Ctr32, Aes192Ctr64, Aes256Ctr128, + Aes256Ctr32, Aes256Ctr64, CryptoKey, +}; + +use super::{ + algorithm_missmatch_error, encryption_algorithm::EncryptionAlgorithm, + key_algorithm::KeyAlgorithm, rsa_private_key, AesGcmVariant, +}; + +type Aes128CbcDec = cbc::Decryptor; +type Aes192CbcDec = cbc::Decryptor; +type Aes256CbcDec = cbc::Decryptor; + +pub async fn subtle_decrypt<'js>( + ctx: Ctx<'js>, + algorithm: EncryptionAlgorithm, + key: Class<'js, CryptoKey>, + data: ObjectBytes<'js>, +) -> Result> { + let key = key.borrow(); + key.check_validity("decrypt").or_throw(&ctx)?; + let bytes = decrypt(&ctx, &algorithm, &key, data.as_bytes())?; + ArrayBuffer::new(ctx, bytes) +} + +fn decrypt( + ctx: &Ctx<'_>, + algorithm: &EncryptionAlgorithm, + key: &CryptoKey, + data: &[u8], +) -> Result> { + let handle = key.handle.as_ref(); + match algorithm { + EncryptionAlgorithm::AesCbc { iv } => { + if let KeyAlgorithm::Aes { length } = key.algorithm { + match length { + 128 => decrypt_aes_cbc_gen::(ctx, handle, iv, data), + 192 => decrypt_aes_cbc_gen::(ctx, handle, iv, data), + 256 => decrypt_aes_cbc_gen::(ctx, handle, iv, data), + _ => unreachable!(), // 'length' has already been sanitized. + } + } else { + algorithm_missmatch_error(ctx) + } + }, + EncryptionAlgorithm::AesCtr { counter, length } => { + let encryption_length = length; + if let KeyAlgorithm::Aes { length } = key.algorithm { + match (length, encryption_length) { + (128, 32) => decrypt_aes_ctr_gen::(ctx, handle, counter, data), + (128, 64) => decrypt_aes_ctr_gen::(ctx, handle, counter, data), + (128, 128) => decrypt_aes_ctr_gen::(ctx, handle, counter, data), + (192, 32) => decrypt_aes_ctr_gen::(ctx, handle, counter, data), + (192, 64) => decrypt_aes_ctr_gen::(ctx, handle, counter, data), + (192, 128) => decrypt_aes_ctr_gen::(ctx, handle, counter, data), + (256, 32) => decrypt_aes_ctr_gen::(ctx, handle, counter, data), + (256, 64) => decrypt_aes_ctr_gen::(ctx, handle, counter, data), + (256, 128) => decrypt_aes_ctr_gen::(ctx, handle, counter, data), + _ => unreachable!(), // 'length' has already been sanitized. + } + } else { + algorithm_missmatch_error(ctx) + } + }, + EncryptionAlgorithm::AesGcm { + iv, + tag_length, + additional_data, + } => { + if let KeyAlgorithm::Aes { length } = key.algorithm { + let nonce = Nonce::::from_slice(iv); + + let variant = AesGcmVariant::new(length, *tag_length, handle).or_throw(ctx)?; + variant + .decrypt(nonce, data, additional_data.as_deref()) + .or_throw(ctx) + } else { + algorithm_missmatch_error(ctx) + } + }, + EncryptionAlgorithm::RsaOaep { label } => { + if let KeyAlgorithm::Rsa { hash, .. } = &key.algorithm { + let (private_key, padding) = rsa_private_key(ctx, handle, label, hash)?; + + private_key.decrypt(padding, data).or_throw(ctx) + } else { + algorithm_missmatch_error(ctx) + } + }, + } +} + +fn decrypt_aes_cbc_gen(ctx: &Ctx<'_>, key: &[u8], iv: &[u8], data: &[u8]) -> Result> +where + T: KeyIvInit + BlockDecryptMut, +{ + T::new(key.into(), iv.into()) + .decrypt_padded_vec_mut::(data) + .or_throw(ctx) +} + +fn decrypt_aes_ctr_gen(ctx: &Ctx<'_>, key: &[u8], counter: &[u8], data: &[u8]) -> Result> +where + T: KeyIvInit + StreamCipher, +{ + let mut cipher = T::new(key.into(), counter.into()); + + let mut plaintext = data.to_vec(); + cipher.try_apply_keystream(&mut plaintext).or_throw(ctx)?; + + Ok(plaintext) +} diff --git a/modules/llrt_crypto/src/subtle/derive.rs b/modules/llrt_crypto/src/subtle/derive.rs new file mode 100644 index 0000000000..551ea0878e --- /dev/null +++ b/modules/llrt_crypto/src/subtle/derive.rs @@ -0,0 +1,165 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use std::num::NonZeroU32; + +use llrt_utils::result::ResultExt; +use p256::pkcs8::DecodePrivateKey; +use ring::{hkdf, pbkdf2}; +use rquickjs::{Array, ArrayBuffer, Class, Ctx, Exception, Result, Value}; + +use super::{ + algorithm_not_supported_error, + derive_algorithm::DeriveAlgorithm, + key_algorithm::{KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages, KeyDerivation}, +}; + +use crate::{ + sha_hash::ShaAlgorithm, + subtle::{CryptoKey, EllipticCurve}, +}; + +struct HkdfOutput(usize); + +impl hkdf::KeyType for HkdfOutput { + fn len(&self) -> usize { + self.0 + } +} + +pub async fn subtle_derive_bits<'js>( + ctx: Ctx<'js>, + algorithm: DeriveAlgorithm, + base_key: Class<'js, CryptoKey>, + length: u32, +) -> Result> { + let base_key = base_key.borrow(); + base_key.check_validity("deriveBits").or_throw(&ctx)?; + + let bytes = derive_bits(&ctx, &algorithm, &base_key.handle, length)?; + ArrayBuffer::new(ctx, bytes) +} + +fn derive_bits( + ctx: &Ctx<'_>, + algorithm: &DeriveAlgorithm, + base_key: &[u8], + length: u32, +) -> Result> { + Ok(match algorithm { + DeriveAlgorithm::Ecdh { curve, public } => match curve { + EllipticCurve::P256 => { + let secret_key = p256::SecretKey::from_pkcs8_der(base_key).or_throw(ctx)?; + let public_key = p256::SecretKey::from_pkcs8_der(public) + .or_throw(ctx)? + .public_key(); + let shared_secret = p256::elliptic_curve::ecdh::diffie_hellman( + secret_key.to_nonzero_scalar(), + public_key.as_affine(), + ); + + shared_secret.raw_secret_bytes().to_vec() + }, + EllipticCurve::P384 => { + let secret_key = p384::SecretKey::from_pkcs8_der(base_key).or_throw(ctx)?; + let public_key = p384::SecretKey::from_pkcs8_der(public) + .or_throw(ctx)? + .public_key(); + let shared_secret = p384::elliptic_curve::ecdh::diffie_hellman( + secret_key.to_nonzero_scalar(), + public_key.as_affine(), + ); + + shared_secret.raw_secret_bytes().to_vec() + }, + }, + DeriveAlgorithm::Derive(KeyDerivation::Hkdf { hash, salt, info }) => { + let hash_algorithm = match hash { + ShaAlgorithm::SHA1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY, + ShaAlgorithm::SHA256 => hkdf::HKDF_SHA256, + ShaAlgorithm::SHA384 => hkdf::HKDF_SHA384, + ShaAlgorithm::SHA512 => hkdf::HKDF_SHA512, + }; + let salt = hkdf::Salt::new(hash_algorithm, salt); + let info: &[&[u8]] = &[&info[..]]; + let prk = salt.extract(base_key); + let out_length = (length / 8).try_into().or_throw(ctx)?; + let okm = prk + .expand(info, HkdfOutput((length / 8).try_into().or_throw(ctx)?)) + .or_throw(ctx)?; + let mut out = vec![0u8; out_length]; + okm.fill(&mut out).or_throw(ctx)?; + + out + }, + DeriveAlgorithm::Derive(KeyDerivation::Pbkdf2 { + hash, + salt, + iterations, + }) => { + let hash_algorithm = match hash { + ShaAlgorithm::SHA1 => pbkdf2::PBKDF2_HMAC_SHA1, + ShaAlgorithm::SHA256 => pbkdf2::PBKDF2_HMAC_SHA256, + ShaAlgorithm::SHA384 => pbkdf2::PBKDF2_HMAC_SHA384, + ShaAlgorithm::SHA512 => pbkdf2::PBKDF2_HMAC_SHA512, + }; + + let mut out = vec![0; (length / 8).try_into().or_throw(ctx)?]; + let not_zero_iterations = NonZeroU32::new(*iterations) + .ok_or_else(|| Exception::throw_message(ctx, "Iterations are zero"))?; + pbkdf2::derive( + hash_algorithm, + not_zero_iterations, + salt, + base_key, + &mut out, + ); + + out + }, + }) +} + +pub async fn subtle_derive_key<'js>( + ctx: Ctx<'js>, + algorithm: DeriveAlgorithm, + base_key: Class<'js, CryptoKey>, + derived_key_algorithm: Value<'js>, + extractable: bool, + key_usages: Array<'js>, +) -> Result> { + let KeyAlgorithmWithUsages { + algorithm: derived_key_algorithm, + name, + public_usages, + .. + } = KeyAlgorithm::from_js( + &ctx, + KeyAlgorithmMode::Derive, + derived_key_algorithm, + key_usages, + )?; + + let length: u16 = match &derived_key_algorithm { + KeyAlgorithm::Aes { length } => *length, + KeyAlgorithm::Hmac { length, .. } => *length, + KeyAlgorithm::Derive { .. } => 0, + _ => { + return algorithm_not_supported_error(&ctx); + }, + }; + + let handle = &base_key.borrow().handle; + + let bytes = derive_bits(&ctx, &algorithm, handle, length as u32)?; + + let key = CryptoKey::new( + "secret", + name, + extractable, + derived_key_algorithm, + public_usages, + bytes, + ); + + Class::instance(ctx, key) +} diff --git a/modules/llrt_crypto/src/subtle/derive_algorithm.rs b/modules/llrt_crypto/src/subtle/derive_algorithm.rs new file mode 100644 index 0000000000..e6c2c2db3b --- /dev/null +++ b/modules/llrt_crypto/src/subtle/derive_algorithm.rs @@ -0,0 +1,54 @@ +use std::rc::Rc; + +use llrt_utils::object::ObjectExt; +use rquickjs::{Class, Ctx, Exception, FromJs, Result, Value}; + +use super::{ + key_algorithm::{KeyAlgorithm, KeyDerivation}, + CryptoKey, EllipticCurve, +}; + +#[derive(Debug)] +pub enum DeriveAlgorithm { + Ecdh { + curve: EllipticCurve, + public: Rc<[u8]>, + }, + Derive(KeyDerivation), +} + +impl<'js> FromJs<'js> for DeriveAlgorithm { + fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { + let obj = value.into_object_or_throw(ctx, "algorithm")?; + + let name: String = obj.get_required("name", "algorithm")?; + + Ok(match name.as_str() { + "ECDH" | "X25519" => { + let public_key: Class = obj.get_required("public", "algorithm")?; + let public_key = public_key.borrow(); + let curve = if let KeyAlgorithm::Ec { curve } = &public_key.algorithm { + curve.clone() + } else { + return Err(Exception::throw_message( + ctx, + "public key must be ECDSA or ECDH key", + )); + }; + + DeriveAlgorithm::Ecdh { + curve, + public: public_key.handle.clone(), + } + }, + "HKDF" => DeriveAlgorithm::Derive(KeyDerivation::for_hkdf_object(ctx, obj)?), + "PBKDF2" => DeriveAlgorithm::Derive(KeyDerivation::for_pbkf2_object(&ctx, obj)?), + _ => { + return Err(Exception::throw_message( + ctx, + "Algorithm 'name' must be X25519 | ECDH | HKDF | PBKDF2", + )) + }, + }) + } +} diff --git a/modules/llrt_crypto/src/subtle/digest.rs b/modules/llrt_crypto/src/subtle/digest.rs new file mode 100644 index 0000000000..8915ff32ab --- /dev/null +++ b/modules/llrt_crypto/src/subtle/digest.rs @@ -0,0 +1,33 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt}; +use ring::digest::Context; +use rquickjs::{ArrayBuffer, Ctx, Result, Value}; + +use crate::sha_hash::ShaAlgorithm; + +pub async fn subtle_digest<'js>( + ctx: Ctx<'js>, + algorithm: Value<'js>, + data: ObjectBytes<'js>, +) -> Result> { + let algorithm = if let Some(algorithm) = algorithm.as_string() { + algorithm.to_string().or_throw(&ctx)? + } else { + algorithm.get_required::<_, String>("name", "algorithm")? + }; + + let bytes = digest(&ctx, &algorithm, data.as_bytes())?; + ArrayBuffer::new(ctx, bytes) +} + +fn digest(ctx: &Ctx<'_>, algorithm: &str, data: &[u8]) -> Result> { + let hash = ShaAlgorithm::try_from(algorithm).or_throw(ctx)?; + + let hash = hash.digest_algorithm(); + let mut context = Context::new(hash); + context.update(data); + let digest = context.finish(); + + Ok(digest.as_ref().to_vec()) +} diff --git a/modules/llrt_crypto/src/subtle/encrypt.rs b/modules/llrt_crypto/src/subtle/encrypt.rs new file mode 100644 index 0000000000..a85550cbde --- /dev/null +++ b/modules/llrt_crypto/src/subtle/encrypt.rs @@ -0,0 +1,123 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use aes::cipher::{block_padding::Pkcs7, typenum::U12, KeyIvInit}; +use aes_gcm::Nonce; +use ctr::cipher::{BlockEncryptMut, StreamCipher}; +use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; +use rquickjs::{ArrayBuffer, Class, Ctx, Result}; +use rsa::rand_core::OsRng; + +use crate::subtle::{ + Aes128Ctr128, Aes128Ctr32, Aes128Ctr64, Aes192Ctr128, Aes192Ctr32, Aes192Ctr64, Aes256Ctr128, + Aes256Ctr32, Aes256Ctr64, CryptoKey, +}; + +use super::{ + algorithm_missmatch_error, encryption_algorithm::EncryptionAlgorithm, + key_algorithm::KeyAlgorithm, rsa_private_key, AesGcmVariant, +}; + +type Aes128CbcEnc = cbc::Encryptor; +type Aes192CbcEnc = cbc::Encryptor; +type Aes256CbcEnc = cbc::Encryptor; + +pub async fn subtle_encrypt<'js>( + ctx: Ctx<'js>, + algorithm: EncryptionAlgorithm, + key: Class<'js, CryptoKey>, + data: ObjectBytes<'js>, +) -> Result> { + let key = key.borrow(); + key.check_validity("encrypt").or_throw(&ctx)?; + + let bytes = encrypt(&ctx, &algorithm, &key, data.as_bytes())?; + ArrayBuffer::new(ctx, bytes) +} + +fn encrypt( + ctx: &Ctx<'_>, + algorithm: &EncryptionAlgorithm, + key: &CryptoKey, + data: &[u8], +) -> Result> { + let handle = key.handle.as_ref(); + match algorithm { + EncryptionAlgorithm::AesCbc { iv } => { + if let KeyAlgorithm::Aes { length } = key.algorithm { + match length { + 128 => encrypt_aes_cbc_gen::(ctx, handle, iv, data), + 192 => encrypt_aes_cbc_gen::(ctx, handle, iv, data), + 256 => encrypt_aes_cbc_gen::(ctx, handle, iv, data), + _ => unreachable!(), // 'length' has already been sanitized. + } + } else { + algorithm_missmatch_error(ctx) + } + }, + EncryptionAlgorithm::AesCtr { counter, length } => { + let encryption_length = length; + if let KeyAlgorithm::Aes { length } = key.algorithm { + match (length, encryption_length) { + (128, 32) => encrypt_aes_ctr_gen::(ctx, handle, counter, data), + (128, 64) => encrypt_aes_ctr_gen::(ctx, handle, counter, data), + (128, 128) => encrypt_aes_ctr_gen::(ctx, handle, counter, data), + (192, 32) => encrypt_aes_ctr_gen::(ctx, handle, counter, data), + (192, 64) => encrypt_aes_ctr_gen::(ctx, handle, counter, data), + (192, 128) => encrypt_aes_ctr_gen::(ctx, handle, counter, data), + (256, 32) => encrypt_aes_ctr_gen::(ctx, handle, counter, data), + (256, 64) => encrypt_aes_ctr_gen::(ctx, handle, counter, data), + (256, 128) => encrypt_aes_ctr_gen::(ctx, handle, counter, data), + _ => unreachable!(), // 'length' has already been sanitized. + } + } else { + algorithm_missmatch_error(ctx) + } + }, + EncryptionAlgorithm::AesGcm { + iv, + tag_length, + additional_data, + } => { + if let KeyAlgorithm::Aes { length } = key.algorithm { + let nonce = Nonce::::from_slice(iv); + + let variant = AesGcmVariant::new(length, *tag_length, handle).or_throw(ctx)?; + variant + .encrypt(nonce, data, additional_data.as_deref()) + .or_throw(ctx) + } else { + algorithm_missmatch_error(ctx) + } + }, + EncryptionAlgorithm::RsaOaep { label } => { + if let KeyAlgorithm::Rsa { hash, .. } = &key.algorithm { + let (private_key, padding) = rsa_private_key(ctx, handle, label, hash)?; + let public_key = private_key.to_public_key(); + let mut rng = OsRng; + + public_key.encrypt(&mut rng, padding, data).or_throw(ctx) + } else { + algorithm_missmatch_error(ctx) + } + }, + } +} + +fn encrypt_aes_cbc_gen(_ctx: &Ctx<'_>, key: &[u8], iv: &[u8], data: &[u8]) -> Result> +where + T: KeyIvInit + BlockEncryptMut, +{ + Ok(T::new(key.into(), iv.into()).encrypt_padded_vec_mut::(data)) +} + +fn encrypt_aes_ctr_gen(ctx: &Ctx<'_>, key: &[u8], counter: &[u8], data: &[u8]) -> Result> +where + T: KeyIvInit + StreamCipher, +{ + let mut cipher = T::new(key.into(), counter.into()); + + let mut ciphertext = data.to_vec(); + cipher.try_apply_keystream(&mut ciphertext).or_throw(ctx)?; + + Ok(ciphertext) +} diff --git a/modules/llrt_crypto/src/subtle/encryption_algorithm.rs b/modules/llrt_crypto/src/subtle/encryption_algorithm.rs new file mode 100644 index 0000000000..568f0cd958 --- /dev/null +++ b/modules/llrt_crypto/src/subtle/encryption_algorithm.rs @@ -0,0 +1,107 @@ +use llrt_utils::{bytes::ObjectBytes, object::ObjectExt}; +use rquickjs::{Ctx, Exception, FromJs, Result, Value}; + +use super::algorithm_not_supported_error; + +#[derive(Debug)] +pub enum EncryptionAlgorithm { + AesCbc { + iv: Box<[u8]>, + }, + AesCtr { + counter: Box<[u8]>, + length: u32, + }, + AesGcm { + iv: Box<[u8]>, + tag_length: u8, + additional_data: Option>, + }, + RsaOaep { + label: Option>, + }, +} + +impl<'js> FromJs<'js> for EncryptionAlgorithm { + fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { + let obj = value.into_object_or_throw(ctx, "algorithm")?; + + let name: String = obj.get_required("name", "algorithm")?; + + match name.as_str() { + "AES-CBC" => { + let iv = obj + .get_required::<_, ObjectBytes>("iv", "algorithm")? + .into_bytes() + .into_boxed_slice(); + + if iv.len() != 16 { + return Err(Exception::throw_message( + ctx, + "invalid length of iv. Currently supported 16 bytes", + )); + } + + Ok(EncryptionAlgorithm::AesCbc { iv }) + }, + "AES-CTR" => { + let counter = obj + .get_required::<_, ObjectBytes>("counter", "algorithm")? + .into_bytes() + .into_boxed_slice(); + + let length = obj.get_required::<_, u32>("length", "algorithm")?; + + if !matches!(length, 32 | 64 | 128) { + return Err(Exception::throw_message( + ctx, + "invalid counter length. Currently supported 32/64/128 bits", + )); + } + + Ok(EncryptionAlgorithm::AesCtr { counter, length }) + }, + "AES-GCM" => { + let iv = obj + .get_required::<_, ObjectBytes>("iv", "algorithm")? + .into_bytes() + .into_boxed_slice(); + + //FIXME only 12? 96 maybe recommended? + if iv.len() != 12 { + return Err(Exception::throw_type( + ctx, + "invalid length of iv. Currently supported 12 bytes", + )); + } + + let additional_data = obj + .get_optional::<_, ObjectBytes>("additionalData")? + .map(|v| v.into_bytes().into_boxed_slice()); + + let tag_length = obj.get_optional::<_, u8>("tagLength")?.unwrap_or(128); + + //ensure tag length is supported using a match statement 32, 64, 96, 104, 112, 120, or 128 + if !matches!(tag_length, 96 | 104 | 112 | 120 | 128) { + return Err(Exception::throw_message( + ctx, + "Invalid tagLength. Currently supported 96/104/112/120/128 bits", + )); + } + + Ok(EncryptionAlgorithm::AesGcm { + iv, + additional_data, + tag_length, + }) + }, + "RSA-OAEP" => { + let label = obj + .get_optional::<_, ObjectBytes>("label")? + .map(|bytes| bytes.into_bytes().into_boxed_slice()); + Ok(EncryptionAlgorithm::RsaOaep { label }) + }, + _ => algorithm_not_supported_error(ctx), + } + } +} diff --git a/modules/llrt_crypto/src/subtle/export_key.rs b/modules/llrt_crypto/src/subtle/export_key.rs new file mode 100644 index 0000000000..15254308a7 --- /dev/null +++ b/modules/llrt_crypto/src/subtle/export_key.rs @@ -0,0 +1,57 @@ +use llrt_utils::result::ResultExt; +use ring::signature::{EcdsaKeyPair, Ed25519KeyPair, KeyPair}; +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use rquickjs::{ArrayBuffer, Class, Ctx, Exception, Result}; + +use crate::{subtle::CryptoKey, SYSTEM_RANDOM}; + +use super::{algorithm_not_supported_error, key_algorithm::KeyAlgorithm}; + +pub async fn subtle_export_key<'js>( + ctx: Ctx<'js>, + format: String, + key: Class<'js, CryptoKey>, +) -> Result> { + let key = key.borrow(); + + if !key.extractable { + return Err(Exception::throw_type( + &ctx, + "The CryptoKey is non extractable", + )); + }; + + //TODO add more formats + if format != "raw" { + return Err(Exception::throw_type( + &ctx, + &["Format '", &format, "' is not implemented"].concat(), + )); + } + export_raw(ctx, &key) +} + +fn export_raw<'js>(ctx: Ctx<'js>, key: &CryptoKey) -> Result> { + let handle = key.handle.as_ref(); + let bytes: Vec = match &key.algorithm { + KeyAlgorithm::Aes { .. } | KeyAlgorithm::Hmac { .. } => handle.into(), + KeyAlgorithm::Ec { curve } => { + let alg = curve.as_signing_algorithm(); + let rng = &(*SYSTEM_RANDOM); + let key_pair = EcdsaKeyPair::from_pkcs8(alg, &key.handle, rng).or_throw(&ctx)?; + key_pair.public_key().as_ref().into() + }, + KeyAlgorithm::Ed25519 => { + let key_pair = Ed25519KeyPair::from_pkcs8(handle).or_throw(&ctx)?; + key_pair.public_key().as_ref().into() + }, + KeyAlgorithm::Rsa { .. } => { + let key_pair = ring::signature::RsaKeyPair::from_pkcs8(handle).or_throw(&ctx)?; + key_pair.public_key().as_ref().into() + }, + _ => return algorithm_not_supported_error(&ctx), + }; + + ArrayBuffer::new(ctx, bytes) +} diff --git a/modules/llrt_crypto/src/subtle/generate_key.rs b/modules/llrt_crypto/src/subtle/generate_key.rs new file mode 100644 index 0000000000..b55db63f3d --- /dev/null +++ b/modules/llrt_crypto/src/subtle/generate_key.rs @@ -0,0 +1,161 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use std::rc::Rc; + +use llrt_utils::result::ResultExt; +use ring::{ + rand::SecureRandom, + signature::{EcdsaKeyPair, Ed25519KeyPair}, +}; +use rquickjs::Class; +use rquickjs::{object::Property, Array, Ctx, Exception, Object, Result, Value}; +use rsa::{pkcs1::EncodeRsaPrivateKey, rand_core::OsRng, BigUint, RsaPrivateKey}; + +use crate::{sha_hash::ShaAlgorithm, CryptoKey, SYSTEM_RANDOM}; + +use super::{ + algorithm_not_supported_error, + key_algorithm::{KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages}, +}; + +pub async fn subtle_generate_key<'js>( + ctx: Ctx<'js>, + algorithm: Value<'js>, + extractable: bool, + key_usages: Array<'js>, +) -> Result> { + let KeyAlgorithmWithUsages { + name, + algorithm: key_algorithm, + private_usages, + public_usages, + } = KeyAlgorithm::from_js(&ctx, KeyAlgorithmMode::Generate, algorithm, key_usages)?; + + let bytes = generate_key(&ctx, &key_algorithm)?; + + if matches!( + key_algorithm, + KeyAlgorithm::Aes { .. } | KeyAlgorithm::Hmac { .. } + ) { + return Ok(Class::instance( + ctx, + CryptoKey::new( + "secret", + name, + extractable, + key_algorithm, + public_usages, + bytes, + ), + )? + .into_value()); + } + let bytes: Rc<[u8]> = bytes.into(); + + let private_key = Class::instance( + ctx.clone(), + CryptoKey::new( + "private", + name.clone(), + false, + key_algorithm.clone(), + private_usages, + bytes.clone(), + ), + )?; + + let public_key = Class::instance( + ctx.clone(), + CryptoKey::new( + "public", + name, + extractable, + key_algorithm, + public_usages, + bytes.clone(), + ), + )?; + + let key_pair = Object::new(ctx.clone())?; + key_pair.prop("privateKey", Property::from(private_key).enumerable())?; + key_pair.prop("publicKey", Property::from(public_key).enumerable())?; + Ok(key_pair.into_value()) +} + +fn generate_key(ctx: &Ctx<'_>, algorithm: &KeyAlgorithm) -> Result> { + Ok(match algorithm { + KeyAlgorithm::Aes { length } => { + let length = *length as usize; + if length % 8 != 0 || length > 256 { + return Err(Exception::throw_message(ctx, "Invalid AES key length")); + } + + let mut key = vec![0u8; length / 8]; + SYSTEM_RANDOM.fill(&mut key).or_throw(ctx)?; + + key + }, + KeyAlgorithm::Ec { curve } => { + let rng = &(*SYSTEM_RANDOM); + let curve = curve.as_signing_algorithm(); + let pkcs8 = EcdsaKeyPair::generate_pkcs8(curve, rng).or_throw(ctx)?; + + pkcs8.as_ref().to_vec() + }, + KeyAlgorithm::Ed25519 => { + let rng = &(*SYSTEM_RANDOM); + let pkcs8 = Ed25519KeyPair::generate_pkcs8(rng).or_throw(ctx)?; + pkcs8.as_ref().to_vec() + }, + KeyAlgorithm::Hmac { hash, length } => { + let length = get_hash_length(ctx, hash, *length)?; + + let mut key = vec![0u8; length]; + SYSTEM_RANDOM.fill(&mut key).or_throw(ctx)?; + + key + }, + // KeyAlgorithm::X25519 => {}, //TODO + KeyAlgorithm::Rsa { + modulus_length, + public_exponent, + .. + } => { + let public_exponent = public_exponent.as_ref().as_ref(); + let exponent: u64 = match public_exponent { + [0x01, 0x00, 0x01] => 65537, // fast pass + [0x03] => 3, // fast pass + bytes + if bytes.ends_with(&[0x03]) + && bytes[..bytes.len() - 1].iter().all(|&b| b == 0) => + { + 3 + }, + _ => return Err(Exception::throw_message(ctx, "Bad public exponent")), + }; + + let mut rng = OsRng; + let private_key = RsaPrivateKey::new_with_exp( + &mut rng, + *modulus_length as usize, + &BigUint::from(exponent), + ) + .or_throw(ctx)?; + let pkcs = private_key.to_pkcs1_der().or_throw(ctx)?; + pkcs.as_bytes().to_vec() + }, + _ => return algorithm_not_supported_error(ctx), + }) +} + +pub fn get_hash_length(ctx: &Ctx, hash: &ShaAlgorithm, length: u16) -> Result { + if length == 0 { + return Ok(hash.hmac_algorithm().digest_algorithm().block_len()); + } + + if length % 8 != 0 || (length / 8) > ring::digest::MAX_BLOCK_LEN.try_into().unwrap() { + return Err(Exception::throw_message(ctx, "Invalid HMAC key length")); + } + + Ok((length / 8) as usize) +} diff --git a/modules/llrt_crypto/src/subtle/import_key.rs b/modules/llrt_crypto/src/subtle/import_key.rs new file mode 100644 index 0000000000..b68401fdfd --- /dev/null +++ b/modules/llrt_crypto/src/subtle/import_key.rs @@ -0,0 +1,59 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use llrt_utils::{bytes::ObjectBytes, object::ObjectExt}; +use rquickjs::{Array, Class, Ctx, Exception, Result, Value}; + +use crate::subtle::CryptoKey; + +use super::key_algorithm::{KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages}; + +#[allow(dead_code)] +pub async fn subtle_import_key<'js>( + ctx: Ctx<'js>, + format: String, + key_data: ObjectBytes<'js>, + algorithm: Value<'js>, + extractable: bool, + key_usages: Array<'js>, +) -> Result> { + if format != "raw" { + return Err(Exception::throw_type( + &ctx, + &["Format '", &format, "' is not implemented"].concat(), + )); + }; + + let data = key_data.into_bytes(); + + if let Some(obj) = algorithm.as_object() { + let name: String = obj.get_required("name", "algorithm")?; + if name.starts_with("AES") || name == "HMAC" { + obj.set("length", data.len())?; + } + if name.starts_with("RSA") { + return Err(Exception::throw_type( + &ctx, + "RSA keys are not supported for import yet", + )); + } + } + + let KeyAlgorithmWithUsages { + name, + algorithm: key_algorithm, + public_usages, + .. + } = KeyAlgorithm::from_js(&ctx, KeyAlgorithmMode::Import, algorithm, key_usages)?; + + Class::instance( + ctx, + CryptoKey::new( + "secret", + name, + extractable, + key_algorithm, + public_usages, + data, + ), + ) +} diff --git a/modules/llrt_crypto/src/subtle/key_algorithm.rs b/modules/llrt_crypto/src/subtle/key_algorithm.rs new file mode 100644 index 0000000000..c8c27df559 --- /dev/null +++ b/modules/llrt_crypto/src/subtle/key_algorithm.rs @@ -0,0 +1,523 @@ +use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt}; +use rquickjs::{atom::PredefinedAtom, Array, Ctx, Exception, Object, Result, TypedArray, Value}; + +use std::rc::Rc; + +use crate::sha_hash::ShaAlgorithm; + +use super::{algorithm_not_supported_error, to_name_and_maybe_object, EllipticCurve}; + +static SYMMETRIC_USAGES: &[&str] = &["encrypt", "decrypt", "wrapKey", "unwrapKey"]; +static SIGNATURE_USAGES: &[&str] = &["sign", "verify"]; +static EMPTY_USAGES: &[&str] = &[]; +static SIGN_USAGES: &[&str] = &["sign"]; +static RSA_OAEP_USAGES: &[&str] = &["decrypt", "unwrapKey"]; +static ECDH_USAGES: &[&str] = &["deriveKey", "deriveBits"]; +static AES_KW_USAGES: &[&str] = &["wrapKey", "unwrapKey"]; + +#[derive(Debug, Clone)] +pub enum KeyDerivation { + Hkdf { + hash: ShaAlgorithm, + salt: Box<[u8]>, + info: Box<[u8]>, + }, + Pbkdf2 { + hash: ShaAlgorithm, + salt: Box<[u8]>, + iterations: u32, + }, +} + +impl KeyDerivation { + pub fn for_hkdf_object<'js>(ctx: &Ctx<'js>, obj: Object<'js>) -> Result { + let hash = extract_sha_hash(ctx, &obj)?; + + let salt = obj + .get_required::<_, ObjectBytes>("salt", "algorithm")? + .into_bytes() + .into_boxed_slice(); + + let info = obj + .get_required::<_, ObjectBytes>("info", "algorithm")? + .into_bytes() + .into_boxed_slice(); + + Ok(KeyDerivation::Hkdf { hash, salt, info }) + } + + pub fn for_pbkf2_object<'js>(ctx: &&Ctx<'js>, obj: Object<'js>) -> Result { + let hash = extract_sha_hash(ctx, &obj)?; + + let salt = obj + .get_required::<_, ObjectBytes>("salt", "algorithm")? + .into_bytes() + .into_boxed_slice(); + + let iterations = obj.get_required("iterations", "algorithm")?; + Ok(KeyDerivation::Pbkdf2 { + hash, + salt, + iterations, + }) + } +} + +#[derive(Debug, Clone)] +pub enum KeyAlgorithm { + Aes { + length: u16, + }, + Ec { + curve: EllipticCurve, + }, + X25519, + Ed25519, + Hmac { + hash: ShaAlgorithm, + length: u16, + }, + Rsa { + modulus_length: u32, + public_exponent: Rc>, + hash: ShaAlgorithm, + }, + Derive(KeyDerivation), + HkdfImport, + Pbkdf2Import, +} + +#[derive(PartialEq)] +pub enum KeyAlgorithmMode { + Import, + Generate, + Derive, +} + +pub struct KeyAlgorithmWithUsages { + pub name: String, + pub algorithm: KeyAlgorithm, + pub public_usages: Vec, + pub private_usages: Vec, +} + +impl KeyAlgorithm { + pub fn from_js<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode, + value: Value<'js>, + usages: Array<'js>, + ) -> Result { + let (name, obj) = to_name_and_maybe_object(ctx, value)?; + let mut public_usages = vec![]; + let mut private_usages = vec![]; + let name_ref = name.as_str(); + let mut is_symmetric = false; + let algorithm = match name_ref { + "Ed25519" => { + if !matches!(mode, KeyAlgorithmMode::Import) { + Self::classify_and_check_signature_usages( + ctx, + name_ref, + &usages, + is_symmetric, + &mut private_usages, + &mut public_usages, + )?; + } + KeyAlgorithm::Ed25519 + }, + "X25519" => { + if !matches!(mode, KeyAlgorithmMode::Import) { + Self::classify_and_check_symmetric_usages( + ctx, + name_ref, + &usages, + is_symmetric, + &mut private_usages, + &mut public_usages, + )?; + } + KeyAlgorithm::X25519 + }, + "AES-CBC" | "AES-CTR" | "AES-GCM" | "AES-KW" => { + is_symmetric = true; + if name_ref == "AES-KW" { + Self::classify_and_check_usages( + ctx, + name_ref, + &usages, + AES_KW_USAGES, + EMPTY_USAGES, + is_symmetric, + &mut private_usages, + &mut public_usages, + )?; + } else { + Self::classify_and_check_symmetric_usages( + ctx, + name_ref, + &usages, + is_symmetric, + &mut private_usages, + &mut public_usages, + )?; + } + + let length: u16 = obj.or_throw(ctx)?.get_required("length", "algorithm")?; + + if !matches!(length, 128 | 192 | 256) { + return Err(Exception::throw_type( + ctx, + "Algorithm 'length' must be one of: 128, 192, or 256", + )); + } + + KeyAlgorithm::Aes { length } + }, + "ECDH" | "ECDSA" => { + let obj = obj.or_throw(ctx)?; + let curive: String = obj.get_required("namedCurve", "algorithm")?; + let curve = EllipticCurve::try_from(curive.as_str()).or_throw(ctx)?; + if !matches!(mode, KeyAlgorithmMode::Import) { + match name_ref { + "ECDH" => match mode { + KeyAlgorithmMode::Generate => Self::classify_and_check_usages( + ctx, + name_ref, + &usages, + ECDH_USAGES, + EMPTY_USAGES, + is_symmetric, + &mut private_usages, + &mut public_usages, + )?, + KeyAlgorithmMode::Derive => Self::classify_and_check_symmetric_usages( + ctx, + name_ref, + &usages, + is_symmetric, + &mut private_usages, + &mut public_usages, + )?, + _ => unreachable!(), + }, + "ECDSA" => Self::classify_and_check_signature_usages( + ctx, + name_ref, + &usages, + is_symmetric, + &mut private_usages, + &mut public_usages, + )?, + _ => unreachable!(), + } + } + KeyAlgorithm::Ec { curve } + }, + + "HMAC" => { + is_symmetric = true; + Self::classify_and_check_usages( + ctx, + name_ref, + &usages, + SIGNATURE_USAGES, + EMPTY_USAGES, + is_symmetric, + &mut private_usages, + &mut public_usages, + )?; + + let obj = obj.or_throw(ctx)?; + let hash = extract_sha_hash(ctx, &obj)?; + let length = obj.get_optional("length")?.unwrap_or_default(); + + KeyAlgorithm::Hmac { hash, length } + }, + "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-v1_5" => { + if !matches!(mode, KeyAlgorithmMode::Import) { + if name == "RSA-PSS" { + Self::classify_and_check_usages( + ctx, + name_ref, + &usages, + SYMMETRIC_USAGES, + RSA_OAEP_USAGES, + is_symmetric, + &mut private_usages, + &mut public_usages, + )?; + } else { + Self::classify_and_check_signature_usages( + ctx, + name_ref, + &usages, + is_symmetric, + &mut private_usages, + &mut public_usages, + )?; + } + } + + let obj = obj.or_throw(ctx)?; + let hash = extract_sha_hash(ctx, &obj)?; + + let modulus_length = obj.get_required("modulusLength", "algorithm")?; + let public_exponent: TypedArray = + obj.get_required("publicExponent", "algorithm")?; + let public_exponent: Box<[u8]> = public_exponent + .as_bytes() + .ok_or_else(|| Exception::throw_message(ctx, "array buffer has been detached"))? + .into(); + let public_exponent = Rc::new(public_exponent); + + KeyAlgorithm::Rsa { + modulus_length, + public_exponent, + hash, + } + }, + "HKDF" => match mode { + KeyAlgorithmMode::Import => KeyAlgorithm::HkdfImport, + KeyAlgorithmMode::Derive => { + Self::classify_and_check_symmetric_usages( + ctx, + name_ref, + &usages, + is_symmetric, + &mut private_usages, + &mut public_usages, + )?; + + let obj = obj.or_throw(ctx)?; + KeyAlgorithm::Derive(KeyDerivation::for_hkdf_object(ctx, obj)?) + }, + _ => { + return algorithm_not_supported_error(ctx); + }, + }, + + "PBKDF2" => match mode { + KeyAlgorithmMode::Import => KeyAlgorithm::Pbkdf2Import, + KeyAlgorithmMode::Derive => { + Self::classify_and_check_symmetric_usages( + ctx, + name_ref, + &usages, + is_symmetric, + &mut private_usages, + &mut public_usages, + )?; + + let obj = obj.or_throw(ctx)?; + KeyAlgorithm::Derive(KeyDerivation::for_pbkf2_object(&ctx, obj)?) + }, + _ => { + return algorithm_not_supported_error(ctx); + }, + }, + _ => return algorithm_not_supported_error(ctx), + }; + + //some import key algorithms allows for unchecked usages, let's just classify + if public_usages.is_empty() && private_usages.is_empty() { + for usage in usages.iter() { + let usage = usage?; + classify_usage(usage, is_symmetric, &mut private_usages, &mut public_usages); + } + } + + Ok(KeyAlgorithmWithUsages { + name, + algorithm, + public_usages, + private_usages, + }) + } + + pub fn as_object<'js, T: AsRef>(&self, ctx: &Ctx<'js>, name: T) -> Result> { + let obj = Object::new(ctx.clone())?; + obj.set(PredefinedAtom::Name, name.as_ref())?; + match self { + KeyAlgorithm::Aes { length } => { + obj.set(PredefinedAtom::Length, length)?; + }, + KeyAlgorithm::Ec { curve } => { + obj.set("namedCurve", curve.as_str())?; + }, + + KeyAlgorithm::Hmac { hash, length } => { + let hash_obj = create_hash_object(ctx, hash)?; + obj.set("hash", hash_obj)?; + + obj.set(PredefinedAtom::Length, length)?; + }, + KeyAlgorithm::Rsa { + modulus_length, + public_exponent, + hash, + } => { + let public_exponent = public_exponent.as_ref().to_vec(); + let array = TypedArray::new(ctx.clone(), public_exponent)?; + + let hash_obj = create_hash_object(ctx, hash)?; + obj.set("hash", hash_obj)?; + + obj.set("modulusLength", modulus_length)?; + obj.set("publicExponent", array)?; + }, + KeyAlgorithm::Derive(KeyDerivation::Hkdf { hash, salt, info }) => { + let salt = TypedArray::::new(ctx.clone(), salt.to_vec())?; + let info = TypedArray::::new(ctx.clone(), info.to_vec())?; + + obj.set("hash", hash.as_str())?; + obj.set("salt", salt)?; + obj.set("info", info)?; + }, + KeyAlgorithm::Derive(KeyDerivation::Pbkdf2 { + hash, + salt, + iterations, + }) => { + let salt = TypedArray::::new(ctx.clone(), salt.to_vec())?; + obj.set("hash", hash.as_str())?; + obj.set("salt", salt)?; + obj.set("iterations", iterations)?; + }, + _ => {}, + }; + Ok(obj) + } + + fn classify_and_check_signature_usages<'js>( + ctx: &Ctx<'js>, + name: &str, + usages: &Array<'js>, + is_symmetric: bool, + private_usages: &mut Vec, + public_usages: &mut Vec, + ) -> Result<()> { + Self::classify_and_check_usages( + ctx, + name, + usages, + SIGNATURE_USAGES, + SIGN_USAGES, + is_symmetric, + private_usages, + public_usages, + ) + } + + fn classify_and_check_symmetric_usages<'js>( + ctx: &Ctx<'js>, + name: &str, + usages: &Array<'js>, + is_symmetric: bool, + private_usages: &mut Vec, + public_usages: &mut Vec, + ) -> Result<()> { + Self::classify_and_check_usages( + ctx, + name, + usages, + SYMMETRIC_USAGES, + EMPTY_USAGES, + is_symmetric, + private_usages, + public_usages, + ) + } + + #[allow(clippy::too_many_arguments)] + fn classify_and_check_usages<'js>( + ctx: &Ctx<'js>, + name: &str, + key_usages: &Array<'js>, + allowed_usages: &[&str], + required_usages: &[&str], + is_symmetric: bool, + private_usages: &mut Vec, + public_usages: &mut Vec, + ) -> Result<()> { + let usages_len = key_usages.len(); + + let mut generated_public_usages = Vec::with_capacity(usages_len); + let mut generated_private_usages = Vec::with_capacity(usages_len); + let mut has_any_required_usages = required_usages.is_empty(); + for usage in key_usages.iter::() { + let value = usage?; + if !allowed_usages.contains(&value.as_str()) { + return Err(Exception::throw_range( + ctx, + &["'", &value, "' is not supported for ", name].concat(), + )); + } + + if !has_any_required_usages { + has_any_required_usages = required_usages.contains(&value.as_str()); + } + + classify_usage( + value, + is_symmetric, + &mut generated_private_usages, + &mut generated_public_usages, + ); + } + + if !has_any_required_usages { + return Err(Exception::throw_range( + ctx, + &[name, " is missing some required usages"].concat(), + )); + } + + *public_usages = generated_public_usages; + *private_usages = generated_private_usages; + + Ok(()) + } +} + +fn classify_usage( + value: String, + is_symmetric: bool, + private_usages: &mut Vec, + public_usages: &mut Vec, +) { + if is_symmetric { + public_usages.push(value); + return; + } + match value.as_str() { + "sign" | "decrypt" | "unwrapKey" | "deriveKey" | "deriveBits" => { + private_usages.push(value); + }, + _ => { + public_usages.push(value); + }, + } +} + +pub fn extract_sha_hash<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> Result { + let hash: Value = obj.get_required("hash", "algorithm")?; + let hash = if let Some(string) = hash.as_string() { + string.to_string() + } else if let Some(obj) = hash.into_object() { + obj.get_required("name", "hash") + } else { + return Err(Exception::throw_message( + ctx, + "hash must be a string or an object", + )); + }?; + ShaAlgorithm::try_from(hash.as_str()).or_throw(ctx) +} + +fn create_hash_object<'js>(ctx: &Ctx<'js>, hash: &ShaAlgorithm) -> Result> { + let hash_obj = Object::new(ctx.clone())?; + hash_obj.set(PredefinedAtom::Name, hash.as_str())?; + Ok(hash_obj) +} diff --git a/modules/llrt_crypto/src/subtle/mod.rs b/modules/llrt_crypto/src/subtle/mod.rs new file mode 100644 index 0000000000..a4bc25b8e5 --- /dev/null +++ b/modules/llrt_crypto/src/subtle/mod.rs @@ -0,0 +1,241 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +mod crypto_key; +mod decrypt; +mod derive; +mod derive_algorithm; +mod digest; +mod encrypt; +mod encryption_algorithm; +mod export_key; +mod generate_key; +mod import_key; +mod key_algorithm; +mod sign; +mod sign_algorithm; +mod verify; + +pub use crypto_key::CryptoKey; +pub use decrypt::subtle_decrypt; +pub use derive::subtle_derive_bits; +pub use derive::subtle_derive_key; +pub use digest::subtle_digest; +pub use encrypt::subtle_encrypt; +pub use export_key::subtle_export_key; +pub use generate_key::subtle_generate_key; +use llrt_utils::object::ObjectExt; +use ring::signature; +use rquickjs::Object; +use rquickjs::Value; +use rsa::{pkcs1::DecodeRsaPrivateKey, Oaep, RsaPrivateKey}; +pub use sign::subtle_sign; +pub use verify::subtle_verify; + +use aes::{ + cipher::{ + consts::{U13, U14, U15, U16}, + typenum::U12, + InvalidLength, + }, + Aes128, Aes192, Aes256, +}; +use aes_gcm::{ + aead::{Aead, Payload}, + AesGcm, KeyInit, +}; +use ctr::{Ctr128BE, Ctr32BE, Ctr64BE}; +use llrt_utils::{result::ResultExt, str_enum}; +use rquickjs::{Ctx, Exception, Result}; + +use crate::sha_hash::ShaAlgorithm; + +type Aes128Ctr32 = Ctr32BE; +type Aes128Ctr64 = Ctr64BE; +type Aes128Ctr128 = Ctr128BE; +type Aes192Ctr32 = Ctr32BE; +type Aes192Ctr64 = Ctr64BE; +type Aes192Ctr128 = Ctr128BE; +type Aes256Ctr32 = Ctr32BE; +type Aes256Ctr64 = Ctr64BE; +type Aes256Ctr128 = Ctr128BE; + +pub enum AesGcmVariant { + Aes128Gcm96(AesGcm), + Aes192Gcm96(AesGcm), + Aes256Gcm96(AesGcm), + Aes128Gcm104(AesGcm), + Aes192Gcm104(AesGcm), + Aes256Gcm104(AesGcm), + Aes128Gcm112(AesGcm), + Aes192Gcm112(AesGcm), + Aes256Gcm112(AesGcm), + Aes128Gcm120(AesGcm), + Aes192Gcm120(AesGcm), + Aes256Gcm120(AesGcm), + Aes128Gcm128(AesGcm), + Aes192Gcm128(AesGcm), + Aes256Gcm128(AesGcm), +} + +impl AesGcmVariant { + pub fn new( + key_len: u16, + tag_length: u8, + key: &[u8], + ) -> std::result::Result { + let variant = match (key_len, tag_length) { + (128, 96) => Self::Aes128Gcm96(AesGcm::new_from_slice(key)?), + (192, 96) => Self::Aes192Gcm96(AesGcm::new_from_slice(key)?), + (256, 96) => Self::Aes256Gcm96(AesGcm::new_from_slice(key)?), + (128, 104) => Self::Aes128Gcm104(AesGcm::new_from_slice(key)?), + (192, 104) => Self::Aes192Gcm104(AesGcm::new_from_slice(key)?), + (256, 104) => Self::Aes256Gcm104(AesGcm::new_from_slice(key)?), + (128, 112) => Self::Aes128Gcm112(AesGcm::new_from_slice(key)?), + (192, 112) => Self::Aes192Gcm112(AesGcm::new_from_slice(key)?), + (256, 112) => Self::Aes256Gcm112(AesGcm::new_from_slice(key)?), + (128, 120) => Self::Aes128Gcm120(AesGcm::new_from_slice(key)?), + (192, 120) => Self::Aes192Gcm120(AesGcm::new_from_slice(key)?), + (256, 120) => Self::Aes256Gcm120(AesGcm::new_from_slice(key)?), + (128, 128) => Self::Aes128Gcm128(AesGcm::new_from_slice(key)?), + (192, 128) => Self::Aes192Gcm128(AesGcm::new_from_slice(key)?), + (256, 128) => Self::Aes256Gcm128(AesGcm::new_from_slice(key)?), + _ => return Err(InvalidLength), + }; + + Ok(variant) + } + + pub fn encrypt( + &self, + nonce: &[u8], + msg: &[u8], + aad: Option<&[u8]>, + ) -> std::result::Result, aes_gcm::Error> { + let plaintext: Payload = Payload { + msg, + aad: aad.unwrap_or_default(), + }; + match self { + Self::Aes128Gcm96(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes192Gcm96(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes256Gcm96(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes128Gcm104(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes192Gcm104(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes256Gcm104(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes128Gcm112(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes192Gcm112(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes256Gcm112(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes128Gcm120(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes192Gcm120(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes256Gcm120(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes128Gcm128(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes192Gcm128(v) => v.encrypt(nonce.into(), plaintext), + Self::Aes256Gcm128(v) => v.encrypt(nonce.into(), plaintext), + } + } + + pub fn decrypt( + &self, + nonce: &[u8], + msg: &[u8], + aad: Option<&[u8]>, + ) -> std::result::Result, aes_gcm::Error> { + let ciphertext: Payload = Payload { + msg, + aad: aad.unwrap_or_default(), + }; + match self { + Self::Aes128Gcm96(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes192Gcm96(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes256Gcm96(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes128Gcm104(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes192Gcm104(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes256Gcm104(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes128Gcm112(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes192Gcm112(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes256Gcm112(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes128Gcm120(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes192Gcm120(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes256Gcm120(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes128Gcm128(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes192Gcm128(v) => v.decrypt(nonce.into(), ciphertext), + Self::Aes256Gcm128(v) => v.decrypt(nonce.into(), ciphertext), + } + } +} + +#[derive(Debug, Clone)] +pub enum EllipticCurve { + P256, + P384, +} + +impl EllipticCurve { + fn as_signing_algorithm<'a>(&self) -> &'a signature::EcdsaSigningAlgorithm { + match self { + EllipticCurve::P256 => &signature::ECDSA_P256_SHA256_FIXED_SIGNING, + EllipticCurve::P384 => &signature::ECDSA_P384_SHA384_FIXED_SIGNING, + } + } +} + +str_enum!(EllipticCurve,P256 => "P-256", P384 => "P-384"); + +pub fn rsa_private_key( + ctx: &Ctx<'_>, + handle: &[u8], + label: &Option>, + hash: &ShaAlgorithm, +) -> Result<(RsaPrivateKey, Oaep)> { + let private_key = RsaPrivateKey::from_pkcs1_der(handle).or_throw(ctx)?; + let mut padding = match hash { + ShaAlgorithm::SHA1 => { + return Err(Exception::throw_message( + ctx, + "SHA-1 is not supported for RSA-OAEP", + )); + }, + ShaAlgorithm::SHA256 => Oaep::new::(), + ShaAlgorithm::SHA384 => Oaep::new::(), + ShaAlgorithm::SHA512 => Oaep::new::(), + }; + if let Some(label) = label { + if !label.is_empty() { + padding.label = Some(String::from_utf8_lossy(label).to_string()); + } + } + + Ok((private_key, padding)) +} + +pub fn to_name_and_maybe_object<'js, 'a>( + ctx: &Ctx<'js>, + value: Value<'js>, +) -> Result<(String, std::result::Result, &'a str>)> { + let obj; + let name = if let Some(string) = value.as_string() { + obj = Err("Not an object"); + string.to_string()? + } else if let Some(object) = value.into_object() { + let name = object.get_required("name", "algorithm")?; + obj = Ok(object); + name + } else { + return Err(Exception::throw_message( + ctx, + "algorithm must be a string or an object", + )); + }; + Ok((name, obj)) +} + +pub fn algorithm_missmatch_error(ctx: &Ctx<'_>) -> Result> { + Err(Exception::throw_message( + ctx, + "key.algorithm does not match that of operation", + )) +} + +pub fn algorithm_not_supported_error(ctx: &Ctx<'_>) -> Result { + Err(Exception::throw_message(ctx, "Algorithm not supported")) +} diff --git a/modules/llrt_crypto/src/subtle/sign.rs b/modules/llrt_crypto/src/subtle/sign.rs new file mode 100644 index 0000000000..e5dd83ba3c --- /dev/null +++ b/modules/llrt_crypto/src/subtle/sign.rs @@ -0,0 +1,183 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; +use rand::rngs::OsRng; +use ring::{ + hmac::{Context as HmacContext, Key as HmacKey}, + signature::{EcdsaKeyPair, Ed25519KeyPair}, +}; +use rquickjs::{ArrayBuffer, Class, Ctx, Exception, Result}; +use rsa::{pkcs1::DecodeRsaPrivateKey, pss::Pss, Pkcs1v15Sign, RsaPrivateKey}; + +use crate::{sha_hash::ShaAlgorithm, subtle::CryptoKey, SYSTEM_RANDOM}; + +use super::{key_algorithm::KeyAlgorithm, sign_algorithm::SigningAlgorithm}; + +pub async fn subtle_sign<'js>( + ctx: Ctx<'js>, + algorithm: SigningAlgorithm, + key: Class<'js, CryptoKey>, + data: ObjectBytes<'js>, +) -> Result> { + let key = key.borrow(); + key.check_validity("sign").or_throw(&ctx)?; + + let bytes = sign(&ctx, &algorithm, &key, data.as_bytes())?; + ArrayBuffer::new(ctx, bytes) +} + +fn sign( + ctx: &Ctx<'_>, + algorithm: &SigningAlgorithm, + key: &CryptoKey, + data: &[u8], +) -> Result> { + let handle = key.handle.as_ref(); + Ok(match algorithm { + SigningAlgorithm::Ecdsa { hash } => { + // Get hash algorithm from key + if !matches!(&key.algorithm, KeyAlgorithm::Ec { .. }) { + return Err(Exception::throw_message( + ctx, + "Invalid key algorithm for ECDSA", + )); + }; + + let hash_alg = match hash { + ShaAlgorithm::SHA256 => &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, + ShaAlgorithm::SHA384 => &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING, + _ => { + return Err(Exception::throw_message( + ctx, + "Ecdsa.hash only support Sha256 or Sha384", + )) + }, + }; + let rng = &(*SYSTEM_RANDOM); + let key_pair = EcdsaKeyPair::from_pkcs8(hash_alg, handle, rng).or_throw(ctx)?; + let signature = key_pair.sign(rng, data).or_throw(ctx)?; + + signature.as_ref().to_vec() + }, + SigningAlgorithm::Ed25519 => { + // Verify key algorithm + if !matches!(&key.algorithm, KeyAlgorithm::Ed25519) { + return Err(Exception::throw_message( + ctx, + "Invalid key algorithm for Ed25519", + )); + } + let key_pair = Ed25519KeyPair::from_pkcs8(handle).or_throw(ctx)?; + let signature = key_pair.sign(data); + + signature.as_ref().to_vec() + }, + SigningAlgorithm::Hmac => { + let hash = if let KeyAlgorithm::Hmac { hash, .. } = &key.algorithm { + hash + } else { + return Err(Exception::throw_message( + ctx, + "Invalid key algorithm for HMAC", + )); + }; + + let hmac_alg = hash.hmac_algorithm(); + + let key = HmacKey::new(*hmac_alg, handle); + let mut hmac = HmacContext::with_key(&key); + hmac.update(data); + + hmac.sign().as_ref().to_vec() + }, + SigningAlgorithm::RsaPss { salt_length } => { + let salt_length = *salt_length as usize; + let hash = if let KeyAlgorithm::Rsa { hash, .. } = &key.algorithm { + hash + } else { + return Err(Exception::throw_message( + ctx, + "Invalid key algorithm for RSA-PSS", + )); + }; + + let digest_algorithm = hash.digest_algorithm(); + + // //verify salt_length + // if salt_length > digest_algorithm.output_len() { + // return Err(Exception::throw_message( + // ctx, + // "Invalid salt length for RSA-PSS", + // )); + // } + + // data.len() + + //also apply similar verification to this Math.ceil((keySizeInBits - 1) / 8) - digestSizeInBytes - 2; + + let private_key = RsaPrivateKey::from_pkcs1_der(handle).or_throw(ctx)?; + let mut rng = OsRng; + let digest = ring::digest::digest(digest_algorithm, data); + let digest = digest.as_ref(); + match hash { + ShaAlgorithm::SHA256 => private_key.sign_with_rng( + &mut rng, + Pss::new_with_salt::(salt_length), + digest, + ), + + ShaAlgorithm::SHA384 => private_key.sign_with_rng( + &mut rng, + Pss::new_with_salt::(salt_length), + digest, + ), + ShaAlgorithm::SHA512 => private_key.sign_with_rng( + &mut rng, + Pss::new_with_salt::(salt_length), + digest, + ), + _ => { + return Err(Exception::throw_message( + ctx, + "Unsupported hash algorithm for RSA-PSS", + )) + }, + } + .or_throw(ctx)? + }, + SigningAlgorithm::RsassaPkcs1v15 => { + let hash = match &key.algorithm { + KeyAlgorithm::Rsa { hash, .. } => hash, + _ => { + return Err(Exception::throw_message( + ctx, + "Invalid key algorithm for RSASSA-PKCS1-v1_5", + )) + }, + }; + + let private_key = RsaPrivateKey::from_pkcs1_der(handle).or_throw(ctx)?; + let digest = ring::digest::digest(hash.digest_algorithm(), data); + let digest = digest.as_ref(); + match hash { + ShaAlgorithm::SHA256 => { + private_key.sign(Pkcs1v15Sign::new::(), digest) + }, + + ShaAlgorithm::SHA384 => { + private_key.sign(Pkcs1v15Sign::new::(), digest) + }, + ShaAlgorithm::SHA512 => { + private_key.sign(Pkcs1v15Sign::new::(), digest) + }, + _ => { + return Err(Exception::throw_message( + ctx, + "Unsupported hash algorithm for RSA-PSS", + )) + }, + } + .or_throw(ctx)? + }, + }) +} diff --git a/modules/llrt_crypto/src/subtle/sign_algorithm.rs b/modules/llrt_crypto/src/subtle/sign_algorithm.rs new file mode 100644 index 0000000000..b98cd899c0 --- /dev/null +++ b/modules/llrt_crypto/src/subtle/sign_algorithm.rs @@ -0,0 +1,41 @@ +use llrt_utils::{object::ObjectExt, result::ResultExt}; +use rquickjs::{Ctx, FromJs, Result, Value}; + +use crate::sha_hash::ShaAlgorithm; + +use super::{ + algorithm_not_supported_error, key_algorithm::extract_sha_hash, to_name_and_maybe_object, +}; + +#[derive(Debug)] +pub enum SigningAlgorithm { + Ecdsa { hash: ShaAlgorithm }, + Ed25519, + RsaPss { salt_length: u32 }, + RsassaPkcs1v15, + Hmac, +} + +impl<'js> FromJs<'js> for SigningAlgorithm { + fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { + let (name, obj) = to_name_and_maybe_object(ctx, value)?; + + let algorithm = match name.as_str() { + "Ed25519" => SigningAlgorithm::Ed25519, + "HMAC" => SigningAlgorithm::Hmac, + "RSASSA-PKCS1-v1_5" => SigningAlgorithm::RsassaPkcs1v15, + "ECDSA" => { + let obj = obj.or_throw(ctx)?; + let hash = extract_sha_hash(ctx, &obj)?; + SigningAlgorithm::Ecdsa { hash } + }, + "RSA-PSS" => { + let salt_length = obj.or_throw(ctx)?.get_required("saltLength", "algorithm")?; + + SigningAlgorithm::RsaPss { salt_length } + }, + _ => return algorithm_not_supported_error(ctx), + }; + Ok(algorithm) + } +} diff --git a/modules/llrt_crypto/src/subtle/verify.rs b/modules/llrt_crypto/src/subtle/verify.rs new file mode 100644 index 0000000000..f08dbb7125 --- /dev/null +++ b/modules/llrt_crypto/src/subtle/verify.rs @@ -0,0 +1,118 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; +use ring::{ + hmac::{Context as HmacContext, Key as HmacKey}, + signature::{EcdsaKeyPair, Ed25519KeyPair, KeyPair, UnparsedPublicKey}, +}; +use rquickjs::{Class, Ctx, Exception, Result}; +use rsa::{ + pkcs1::DecodeRsaPrivateKey, + pkcs1v15::Pkcs1v15Sign, + pss::Pss, + sha2::{Digest, Sha256}, + RsaPrivateKey, +}; + +use crate::{sha_hash::ShaAlgorithm, subtle::CryptoKey, SYSTEM_RANDOM}; + +use super::sign_algorithm::SigningAlgorithm; + +pub async fn subtle_verify<'js>( + ctx: Ctx<'js>, + algorithm: SigningAlgorithm, + key: Class<'js, CryptoKey>, + signature: ObjectBytes<'js>, + data: ObjectBytes<'js>, +) -> Result { + let key = key.borrow(); + key.check_validity("verify").or_throw(&ctx)?; + + verify( + &ctx, + &algorithm, + &key.handle, + signature.as_bytes(), + data.as_bytes(), + ) +} + +fn verify( + ctx: &Ctx<'_>, + algorithm: &SigningAlgorithm, + key: &[u8], + signature: &[u8], + data: &[u8], +) -> Result { + Ok(match algorithm { + SigningAlgorithm::Ecdsa { hash } => { + let (fixed_signing, fixed) = match hash { + ShaAlgorithm::SHA256 => ( + &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, + &ring::signature::ECDSA_P256_SHA256_FIXED, + ), + ShaAlgorithm::SHA384 => ( + &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING, + &ring::signature::ECDSA_P384_SHA384_FIXED, + ), + _ => { + return Err(Exception::throw_message( + ctx, + "Ecdsa.hash only support Sha256 or Sha384", + )) + }, + }; + + let rng = &(*SYSTEM_RANDOM); + + let private_key = EcdsaKeyPair::from_pkcs8(fixed_signing, key, rng).or_throw(ctx)?; + let public_key_bytes = private_key.public_key().as_ref(); + let public_key = UnparsedPublicKey::new(fixed, &public_key_bytes); + + public_key.verify(data, signature).is_ok() + }, + SigningAlgorithm::Ed25519 => { + let private_key = Ed25519KeyPair::from_pkcs8(key).or_throw(ctx)?; + let public_key_bytes = private_key.public_key().as_ref(); + let public_key = UnparsedPublicKey::new(&ring::signature::ED25519, public_key_bytes); + + public_key.verify(data, signature).is_ok() + }, + SigningAlgorithm::Hmac => { + let key = HmacKey::new(ring::hmac::HMAC_SHA256, key); + let mut hmac = HmacContext::with_key(&key); + hmac.update(data); + + hmac.sign().as_ref() == signature + }, + SigningAlgorithm::RsaPss { salt_length } => { + let public_key = RsaPrivateKey::from_pkcs1_der(key) + .or_throw(ctx)? + .to_public_key(); + let mut hasher = Sha256::new(); + hasher.update(data); + let hashed = hasher.finalize(); + + public_key + .verify( + Pss::new_with_salt::(*salt_length as usize), + &hashed, + signature, + ) + .is_ok() + }, + SigningAlgorithm::RsassaPkcs1v15 => { + let public_key = RsaPrivateKey::from_pkcs1_der(key) + .or_throw(ctx)? + .to_public_key(); + let mut hasher = Sha256::new(); + hasher.update(data); + + let hashed = hasher.finalize(); + + public_key + .verify(Pkcs1v15Sign::new::(), &hashed, signature) + .is_ok() + }, + }) +} diff --git a/tests/unit/crypto.subtle.test.ts b/tests/unit/crypto.subtle.test.ts new file mode 100644 index 0000000000..a3d962048a --- /dev/null +++ b/tests/unit/crypto.subtle.test.ts @@ -0,0 +1,1289 @@ +import type { webcrypto } from "crypto"; + +const DECODER = new TextDecoder(); +const ENCODER = new TextEncoder(); +const TEST_MESSAGE = "This is test message."; +const ENCODED_DATA = ENCODER.encode(TEST_MESSAGE); + +describe("SubtleCrypto digest", () => { + it("should calculate correctly SHA-1/256/384/512 digest", async () => { + const parameters: [string, number[]][] = [ + [ + "SHA-1", + [ + 77, 178, 99, 24, 75, 24, 35, 67, 75, 116, 194, 145, 251, 77, 201, 158, + 163, 128, 52, 146, + ], + ], + [ + "SHA-256", + [ + 25, 251, 197, 98, 46, 227, 66, 238, 69, 151, 67, 175, 68, 184, 76, + 182, 55, 172, 65, 183, 49, 68, 7, 196, 44, 100, 140, 80, 173, 34, 85, + 162, + ], + ], + [ + "SHA-384", + [ + 120, 197, 163, 25, 168, 231, 230, 188, 103, 31, 71, 249, 169, 230, + 153, 177, 79, 131, 234, 88, 93, 74, 22, 71, 169, 225, 35, 40, 129, + 238, 168, 107, 148, 105, 238, 23, 160, 190, 147, 195, 162, 135, 202, + 230, 26, 130, 124, 245, + ], + ], + [ + "SHA-512", + [ + 197, 205, 134, 50, 153, 136, 53, 103, 159, 209, 236, 27, 212, 147, + 229, 162, 64, 1, 116, 206, 59, 187, 12, 223, 135, 121, 147, 143, 26, + 203, 161, 238, 57, 27, 254, 202, 96, 207, 172, 168, 12, 47, 150, 164, + 182, 235, 183, 159, 134, 226, 198, 183, 61, 128, 211, 133, 33, 12, + 168, 19, 139, 120, 80, 145, + ], + ], + ]; + for (const [name, digest] of parameters) { + const result = new Uint8Array( + await crypto.subtle.digest(name, ENCODED_DATA) + ); + + expect(result).toEqual(new Uint8Array(digest)); + } + }); +}); + +describe("SubtleCrypto generateKey/sign/verify", () => { + it("should be processing AES-CBC/AES-CTR/AES-GCM/AES-KW algorithm", async () => { + const parameters = [ + { + name: "AES-CBC", + length: 128, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CBC", + length: 192, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CBC", + length: 256, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CTR", + length: 128, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CTR", + length: 192, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CTR", + length: 256, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-GCM", + length: 128, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-GCM", + length: 192, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-GCM", + length: 256, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { name: "AES-KW", length: 128, usages: ["wrapKey", "unwrapKey"] }, + { name: "AES-KW", length: 192, usages: ["wrapKey", "unwrapKey"] }, + { name: "AES-KW", length: 256, usages: ["wrapKey", "unwrapKey"] }, + ]; + + for (const t of parameters) { + const algorithm = { name: t.name, length: t.length }; + + const key = await crypto.subtle.generateKey( + algorithm, + false, + t.usages as webcrypto.KeyUsage[] + ); + + expect(key.algorithm.name).toEqual(algorithm.name); + expect((key.algorithm as any).length).toEqual(algorithm.length); + expect(key.extractable).toEqual(false); + } + }); + + it("should be processing HMAC algorithm", async () => { + const parameters = [ + { name: "HMAC", hash: "SHA-1", usages: ["sign", "verify"] }, + { name: "HMAC", hash: "SHA-256", usages: ["sign", "verify"] }, + { name: "HMAC", hash: "SHA-384", usages: ["sign", "verify"] }, + { name: "HMAC", hash: "SHA-512", usages: ["sign", "verify"] }, + ]; + + for (const t of parameters) { + const algorithm = { name: t.name, hash: t.hash }; + + const key = (await crypto.subtle.generateKey( + algorithm, + false, + t.usages as webcrypto.KeyUsage[] + )) as webcrypto.CryptoKey; + + expect(key.algorithm.name).toEqual(algorithm.name); + expect((key.algorithm as any).hash).toEqual({ name: algorithm.hash }); + expect(key.extractable).toEqual(false); + } + }); + + it("should be processing ECDH/ECDSA algorithm", async () => { + const parameters = [ + { + name: "ECDH", + namedCurve: "P-256", + usages: ["deriveKey", "deriveBits"], + }, + { + name: "ECDH", + namedCurve: "P-384", + usages: ["deriveKey", "deriveBits"], + }, + { + name: "ECDSA", + namedCurve: "P-256", + usages: ["sign", "verify"], + hash: "SHA-256", + }, + { + name: "ECDSA", + namedCurve: "P-384", + usages: ["sign", "verify"], + hash: "SHA-384", + }, + ]; + + for (const t of parameters) { + const algorithm = { name: t.name, namedCurve: t.namedCurve }; + + const { privateKey, publicKey } = await crypto.subtle.generateKey( + algorithm, + true, + t.usages as webcrypto.KeyUsage[] + ); + + const keyAlgorithm = privateKey.algorithm as any; + + expect(keyAlgorithm.name).toEqual(algorithm.name); + expect(keyAlgorithm.namedCurve).toEqual(algorithm.namedCurve); + expect(privateKey.extractable).toEqual(false); + + expect(keyAlgorithm.name).toEqual(algorithm.name); + expect(keyAlgorithm.namedCurve).toEqual(algorithm.namedCurve); + expect(publicKey.extractable).toEqual(true); + + if (t.usages.includes("sign")) { + const signature = await crypto.subtle.sign( + { + name: t.name, + hash: t.hash, + }, + privateKey, + ENCODED_DATA + ); + const isValid = await crypto.subtle.verify( + { + name: t.name, + hash: t.hash, + }, + publicKey, + signature, + ENCODED_DATA + ); + + expect(isValid).toBeTruthy(); + } + } + }); + + it("should be processing Ed25519 algorithm", async () => { + const parameters = [ + { + name: "Ed25519", + usages: ["sign", "verify"], + }, + ]; + + for (const t of parameters) { + const algorithm = { name: t.name }; + + const { privateKey, publicKey } = (await crypto.subtle.generateKey( + algorithm, + true, + t.usages as webcrypto.KeyUsage[] + )) as webcrypto.CryptoKeyPair; + + expect(privateKey.algorithm.name).toEqual(algorithm.name); + expect(privateKey.extractable).toEqual(false); + + expect(publicKey.algorithm.name).toEqual(algorithm.name); + expect(publicKey.extractable).toEqual(true); + + if (t.usages.includes("sign")) { + const signature = await crypto.subtle.sign( + { + name: t.name, + }, + privateKey, + ENCODED_DATA + ); + const isValid = await crypto.subtle.verify( + { + name: t.name, + }, + publicKey, + signature, + ENCODED_DATA + ); + + expect(isValid).toBeTruthy(); + } + } + }); + + it.skip("should be processing RSA-PSS/RSA-OAEP/RSASSA-PKCS1-v1_5 algorithm", async () => { + const parameters = [ + { + name: "RSA-PSS", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: "SHA-256", + usages: ["sign", "verify"], + }, + { + name: "RSA-PSS", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: "SHA-384", + usages: ["sign", "verify"], + }, + { + name: "RSA-PSS", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: "SHA-512", + usages: ["sign", "verify"], + }, + { + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: "SHA-256", + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: "SHA-384", + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: "SHA-512", + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "RSASSA-PKCS1-v1_5", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: "SHA-256", + }, + { + name: "RSASSA-PKCS1-v1_5", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: "SHA-384", + }, + { + name: "RSASSA-PKCS1-v1_5", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: "SHA-512", + }, + ]; + + for (const t of parameters) { + const algorithm = { + name: t.name, + modulusLength: t.modulusLength, + publicExponent: t.publicExponent, + hash: t.hash, + }; + + const { privateKey, publicKey } = await crypto.subtle.generateKey( + algorithm, + true, + t.usages as webcrypto.KeyUsage[] + ); + + const privateKeyAlgorithm = privateKey.algorithm as any; + const publicKeyAlgorithm = publicKey.algorithm as any; + + expect(privateKey.algorithm.name).toEqual(t.name); + expect(privateKeyAlgorithm.hash).toEqual({ name: algorithm.hash }); + expect(privateKey.extractable).toEqual(false); + + expect(publicKey.algorithm.name).toEqual(algorithm.name); + expect(publicKeyAlgorithm.hash).toEqual({ name: algorithm.hash }); + expect(publicKey.extractable).toEqual(true); + + if (t.usages?.includes("sign")) { + const signature = await crypto.subtle.sign( + { + name: t.name, + }, + privateKey, + ENCODED_DATA + ); + const isValid = await crypto.subtle.verify( + { + name: t.name, + }, + publicKey, + signature, + ENCODED_DATA + ); + + expect(isValid).toBeTruthy(); + } + } + }, 60000); +}); + +describe("SubtleCrypto generateKey/encrypt/decrypt", () => { + it("should be processing AES-CBC algorithm", async () => { + const parameters = [ + { + name: "AES-CBC", + length: 128, + iv: crypto.getRandomValues(new Uint8Array(16)), + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CBC", + length: 192, + iv: crypto.getRandomValues(new Uint8Array(16)), + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CBC", + length: 256, + iv: crypto.getRandomValues(new Uint8Array(16)), + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ]; + + for (const t of parameters) { + const algorithm = { name: t.name, length: t.length }; + + const key = await crypto.subtle.generateKey( + algorithm, + false, + t.usages as webcrypto.KeyUsage[] + ); + + expect(key.algorithm.name).toEqual(algorithm.name); + expect((key.algorithm as any).length).toEqual(algorithm.length); + expect(key.extractable).toEqual(false); + + if (t.usages.includes("encrypt")) { + const encryptedData = await crypto.subtle.encrypt( + { + name: t.name, + iv: t.iv, + }, + key, + ENCODED_DATA + ); + const decryptedData = await crypto.subtle.decrypt( + { + name: t.name, + iv: t.iv, + }, + key, + encryptedData + ); + + const result = DECODER.decode(decryptedData); + expect(result).toEqual(TEST_MESSAGE); + } + } + }); +}); + +describe("SubtleCrypto generateKey/encrypt/decrypt", () => { + it("should be processing AES-CTR algorithm", async () => { + const parameters = [ + { + name: "AES-CTR", + length: 128, + counter: crypto.getRandomValues(new Uint8Array(16)), + counterLength: 32, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CTR", + length: 128, + counter: crypto.getRandomValues(new Uint8Array(16)), + counterLength: 64, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CTR", + length: 128, + counter: crypto.getRandomValues(new Uint8Array(16)), + counterLength: 128, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CTR", + length: 192, + counter: crypto.getRandomValues(new Uint8Array(16)), + counterLength: 32, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CTR", + length: 192, + counter: crypto.getRandomValues(new Uint8Array(16)), + counterLength: 64, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CTR", + length: 192, + counter: crypto.getRandomValues(new Uint8Array(16)), + counterLength: 128, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CTR", + length: 256, + counter: crypto.getRandomValues(new Uint8Array(16)), + counterLength: 32, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CTR", + length: 256, + counter: crypto.getRandomValues(new Uint8Array(16)), + counterLength: 64, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-CTR", + length: 256, + counter: crypto.getRandomValues(new Uint8Array(16)), + counterLength: 128, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ]; + + for (const t of parameters) { + const algorithm = { name: t.name, length: t.length }; + + const key = await crypto.subtle.generateKey( + algorithm, + false, + t.usages as webcrypto.KeyUsage[] + ); + + const keyAlgorithm = key.algorithm as any; + + expect(key.algorithm.name).toEqual(algorithm.name); + expect(keyAlgorithm.length).toEqual(algorithm.length); + expect(key.extractable).toEqual(false); + + if (t.usages.includes("encrypt")) { + const encryptedData = await crypto.subtle.encrypt( + { + name: t.name, + counter: t.counter, + length: t.counterLength, + }, + key, + ENCODED_DATA + ); + const decryptedData = await crypto.subtle.decrypt( + { + name: t.name, + counter: t.counter, + length: t.counterLength, + }, + key, + encryptedData + ); + + const result = DECODER.decode(decryptedData); + expect(result).toEqual(TEST_MESSAGE); + } + } + }); +}); + +describe("SubtleCrypto generateKey/encrypt/decrypt", () => { + it("should be processing AES-GCM algorithm", async () => { + const parameters = [ + { + name: "AES-GCM", + length: 128, + iv: crypto.getRandomValues(new Uint8Array(12)), + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-GCM", + length: 192, + iv: crypto.getRandomValues(new Uint8Array(12)), + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "AES-GCM", + length: 256, + iv: crypto.getRandomValues(new Uint8Array(12)), + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ]; + + for (const t of parameters) { + const algorithm = { name: t.name, length: t.length }; + + const key = await crypto.subtle.generateKey( + algorithm, + false, + t.usages as webcrypto.KeyUsage[] + ); + + const keyAlgorithm = key.algorithm as any; + + expect(key.algorithm.name).toEqual(algorithm.name); + expect(keyAlgorithm.length).toEqual(algorithm.length); + expect(key.extractable).toEqual(false); + + if (t.usages.includes("encrypt")) { + const encryptedData = await crypto.subtle.encrypt( + { + name: t.name, + iv: t.iv, + }, + key, + ENCODED_DATA + ); + const decryptedData = await crypto.subtle.decrypt( + { + name: t.name, + iv: t.iv, + }, + key, + encryptedData + ); + + const result = DECODER.decode(decryptedData); + expect(result).toEqual(TEST_MESSAGE); + } + } + }); +}); + +// Caveat: The current RSA implementation is too slow to complete the test within the time limit. +describe("SubtleCrypto generateKey/encrypt/decrypt", () => { + it.skip("should be processing RSA-OAEP algorithm", async () => { + const parameters = [ + { + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: "SHA-256", + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: "SHA-384", + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: "SHA-512", + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ]; + + for (const t of parameters) { + const algorithm = { + name: t.name, + modulusLength: t.modulusLength, + publicExponent: t.publicExponent, + hash: t.hash, + }; + + const { privateKey, publicKey } = (await crypto.subtle.generateKey( + algorithm, + true, + t.usages as webcrypto.KeyUsage[] + )) as unknown as webcrypto.CryptoKeyPair; + + const privateKeyAlgorithm = privateKey.algorithm as any; + const publicKeyAlgorithm = publicKey.algorithm as any; + + expect(privateKey.algorithm.name).toEqual(t.name); + expect(privateKeyAlgorithm.hash).toEqual({ name: algorithm.hash }); + expect(privateKey.extractable).toEqual(false); + + expect(publicKey.algorithm.name).toEqual(algorithm.name); + expect(publicKeyAlgorithm.hash).toEqual({ name: algorithm.hash }); + expect(publicKey.extractable).toEqual(true); + + if (t.usages.includes("encrypt")) { + const encryptedData = await crypto.subtle.encrypt( + { + name: t.name, + }, + publicKey, + ENCODED_DATA + ); + const decryptedData = await crypto.subtle.decrypt( + { + name: t.name, + }, + privateKey, + encryptedData + ); + + const result = DECODER.decode(decryptedData); + expect(result).toEqual(TEST_MESSAGE); + } + } + }, 60000); +}); + +describe("SubtleCrypto deriveBits/deriveKey", () => { + it("should be processing ECDH algorithm", async () => { + const generatedParams = [ + { + name: "ECDH", + namedCurve: "P-256", + }, + { + name: "ECDH", + namedCurve: "P-384", + }, + ]; + const derivedParams = [ + { + name: "AES-CBC", + length: 128, + }, + { + name: "AES-CBC", + length: 192, + }, + { + name: "AES-CBC", + length: 256, + }, + { + name: "AES-CTR", + length: 128, + }, + { + name: "AES-CTR", + length: 192, + }, + { + name: "AES-CTR", + length: 256, + }, + { + name: "AES-GCM", + length: 128, + }, + { + name: "AES-GCM", + length: 192, + }, + { + name: "AES-GCM", + length: 256, + }, + // { + // name: "AES-KW", + // length: 128, + // }, + // { + // name: "AES-KW", + // length: 192, + // }, + // { + // name: "AES-KW", + // length: 256, + // }, + // { + // name: "HMAC", + // hash: "SHA-1", + // }, + // { + // name: "HMAC", + // hash: "SHA-256", + // }, + // { + // name: "HMAC", + // hash: "SHA-384", + // }, + // { + // name: "HMAC", + // hash: "SHA-512", + // }, + ]; + + // 1. Generate Alice's key pair + const aliceKeyPair = await crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve: "P-256", + }, + true, // whether the key is extractable (i.e. can be used in exportKey) + ["deriveKey", "deriveBits"] // can be any combination of "deriveKey" and "deriveBits" + ); + + // 2. Generate Bob's key pair + const bobKeyPair = await crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve: "P-256", + }, + true, + ["deriveKey", "deriveBits"] + ); + + // 3. Export Bob's public key to share with Alice + // const bobPublicKey = await crypto.subtle.exportKey( + // "raw", + // bobKeyPair.publicKey + // ); + + // 3.5. Alice imports Bob's public key + // const bobImportKey = await crypto.subtle.importKey( + // "raw", + // bobPublicKey, + // { + // name: "ECDH", + // namedCurve: "P-256", + // }, + // true, + // [] + // ); + + for (const generated of generatedParams) { + for (const derived of derivedParams) { + // 4. Alice derives a shared key using Bob's public key + const aliceDerivedKey = await crypto.subtle.deriveKey( + { + name: "ECDH", + // public: bobImportKey, + public: bobKeyPair.publicKey, + }, + aliceKeyPair.privateKey, + derived, + true, // The derived key is extractable + ["encrypt", "decrypt"] // You can specify operations that the derived key will be used for + ); + + // 5. Export Alice's public key to share with Bob + // const alicePublicKey = await crypto.subtle.exportKey( + // "raw", + // aliceKeyPair.publicKey + // ); + + // 6. Bob derives a shared key using Alice's public key + // const aliceImportKey = await crypto.subtle.importKey( + // "raw", + // alicePublicKey, + // { + // name: "ECDH", + // namedCurve: "P-256", + // }, + // true, + // [] + // ); + + const bobDerivedKey = await crypto.subtle.deriveKey( + { + name: "ECDH", + // public: aliceImportKey, + public: aliceKeyPair.publicKey, + }, + bobKeyPair.privateKey, + derived, + true, // The derived key is extractable + ["encrypt", "decrypt"] // You can specify operations that the derived key will be used for + ); + + // To verify if both Alice and Bob have the same derived key, you can compare them + const aliceKeyBuffer = new Uint8Array( + await crypto.subtle.exportKey("raw", aliceDerivedKey) + ); + const bobKeyBuffer = new Uint8Array( + await crypto.subtle.exportKey("raw", bobDerivedKey) + ); + + // Compare the raw key buffers to check if the derived keys are equal + expect(aliceKeyBuffer).toEqual(bobKeyBuffer); + } + } + }); + + it.skip("should be processing HKDF algorithm", async () => { + const hkdfSalt = new Uint8Array(16); // Salt value (can be random, but here it's set to all zeros) + const hkdfInfo = new TextEncoder().encode("HKDF info"); // Info parameter, can be any label string + + const generatedParams = [ + { + name: "HKDF", + salt: hkdfSalt, + info: hkdfInfo, + hash: "SHA-1", + }, + { + name: "HKDF", + salt: hkdfSalt, + info: hkdfInfo, + hash: "SHA-256", + }, + { + name: "HKDF", + salt: hkdfSalt, + info: hkdfInfo, + hash: "SHA-384", + }, + { + name: "HKDF", + salt: hkdfSalt, + info: hkdfInfo, + hash: "SHA-512", + }, + ]; + const derivedParams = [ + { + name: "AES-CBC", + length: 128, + }, + { + name: "AES-CBC", + length: 192, + }, + { + name: "AES-CBC", + length: 256, + }, + { + name: "AES-CTR", + length: 128, + }, + { + name: "AES-CTR", + length: 192, + }, + { + name: "AES-CTR", + length: 256, + }, + { + name: "AES-GCM", + length: 128, + }, + { + name: "AES-GCM", + length: 192, + }, + { + name: "AES-GCM", + length: 256, + }, + { + name: "AES-KW", + length: 128, + }, + { + name: "AES-KW", + length: 192, + }, + { + name: "AES-KW", + length: 256, + }, + { + name: "HMAC", + hash: "SHA-1", + }, + { + name: "HMAC", + hash: "SHA-256", + }, + { + name: "HMAC", + hash: "SHA-384", + }, + { + name: "HMAC", + hash: "SHA-512", + }, + ]; + + // 1. Generate Alice's key pair + const aliceKeyPair = await crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve: "P-256", + }, + true, // whether the key is extractable (i.e. can be used in exportKey) + ["deriveKey", "deriveBits"] // can be any combination of "deriveKey" and "deriveBits" + ); + + // 2. Generate Bob's key pair + const bobKeyPair = await crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve: "P-256", + }, + true, + ["deriveKey", "deriveBits"] + ); + + // 3. Export Bob's public key to share with Alice + const bobPublicKey = await crypto.subtle.exportKey( + "raw", + bobKeyPair.publicKey + ); + + // 3.5. Alice imports Bob's public key + const bobImportKey = await crypto.subtle.importKey( + "raw", + bobPublicKey, + { + name: "ECDH", + namedCurve: "P-256", + }, + true, + [] + ); + + for (const generated of generatedParams) { + for (const derived of derivedParams) { + // 4. Alice derives a shared secret using Bob's public key + const aliceSharedSecret = await crypto.subtle.deriveBits( + { + name: "ECDH", + public: bobImportKey, + }, + aliceKeyPair.privateKey, + 256 // number of bits to derive + ); + + // 5. Convert Alice's derived secret to a key using HKDF + const aliceDerivedKey = await crypto.subtle.importKey( + "raw", + aliceSharedSecret, + "HKDF", + false, + ["deriveKey"] + ); + + const aliceDerivedlKey = await crypto.subtle.deriveKey( + generated, + aliceDerivedKey, + derived, + true, + ["encrypt", "decrypt"] + ); + + // 6. Export Alice's public key to share with Bob + const alicePublicKey = await crypto.subtle.exportKey( + "raw", + aliceKeyPair.publicKey + ); + + // 7. Bob derives a shared secret using Alice's public key + const aliceImportKey = await crypto.subtle.importKey( + "raw", + alicePublicKey, + { + name: "ECDH", + namedCurve: "P-256", + }, + true, + [] + ); + + const bobSharedSecret = await crypto.subtle.deriveBits( + { + name: "ECDH", + public: aliceImportKey, + }, + bobKeyPair.privateKey, + 256 + ); + + // 8. Convert Bob's derived secret to a key using HKDF + const bobDerivedKey = await crypto.subtle.importKey( + "raw", + bobSharedSecret, + "HKDF", + false, + ["deriveKey"] + ); + + const bobDerivedlKey = await crypto.subtle.deriveKey( + generated, + bobDerivedKey, + derived, + true, + ["encrypt", "decrypt"] + ); + console.log("8. Bob derives the final key using HKDF : complete"); + + // 9. Verify if both derived keys are the same + const aliceKeyBuffer = new Uint8Array( + await crypto.subtle.exportKey("raw", aliceDerivedlKey) + ); + const bobKeyBuffer = new Uint8Array( + await crypto.subtle.exportKey("raw", bobDerivedlKey) + ); + + // Compare the raw key buffers to check if the derived keys are equal + expect(aliceKeyBuffer).toEqual(bobKeyBuffer); + } + } + }); + + it.skip("should be processing PBKDF2 algorithm", async () => { + const pbkdf2Salt = new Uint8Array(16); // Salt value (can be random, but here it's set to all zeros) + const pbkdf2Iterations = 50000; // Number of iterations for PBKDF2 + + // We skip some tests because they run slowly in CI. + const generatedParams = [ + { + // name: "PBKDF2", + // salt: pbkdf2Salt, + // iterations: pbkdf2Iterations, + // hash: "SHA-1" + // }, { + name: "PBKDF2", + salt: pbkdf2Salt, + iterations: pbkdf2Iterations, + hash: "SHA-256", + // }, { + // name: "PBKDF2", + // salt: pbkdf2Salt, + // iterations: pbkdf2Iterations, + // hash: "SHA-384" + // }, { + // name: "PBKDF2", + // salt: pbkdf2Salt, + // iterations: pbkdf2Iterations, + // hash: "SHA-512" + }, + ]; + const derivedParams = [ + { + name: "AES-CBC", + length: 128, + }, + { + // name: "AES-CBC", + // length: 192, + // }, { + // name: "AES-CBC", + // length: 256, + // }, { + name: "AES-CTR", + length: 128, + }, + { + // name: "AES-CTR", + // length: 192, + // }, { + // name: "AES-CTR", + // length: 256, + // }, { + name: "AES-GCM", + length: 128, + }, + { + // name: "AES-GCM", + // length: 192, + // }, { + // name: "AES-GCM", + // length: 256, + // }, { + name: "AES-KW", + length: 128, + }, + { + // name: "AES-KW", + // length: 192, + // }, { + // name: "AES-KW", + // length: 256, + // }, { + name: "HMAC", + hash: "SHA-1", + // }, { + // name: "HMAC", + // hash: "SHA-256", + // }, { + // name: "HMAC", + // hash: "SHA-384", + // }, { + // name: "HMAC", + // hash: "SHA-512", + }, + ]; + + // 1. Generate Alice's key pair + const aliceKeyPair = await crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve: "P-256", + }, + true, // whether the key is extractable (i.e. can be used in exportKey) + ["deriveKey", "deriveBits"] // can be any combination of "deriveKey" and "deriveBits" + ); + + // 2. Generate Bob's key pair + const bobKeyPair = await crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve: "P-256", + }, + true, + ["deriveKey", "deriveBits"] + ); + + // 3. Export Bob's public key to share with Alice + const bobPublicKey = await crypto.subtle.exportKey( + "raw", + bobKeyPair.publicKey + ); + + // 3.5. Alice imports Bob's public key + const bobImportKey = await crypto.subtle.importKey( + "raw", + bobPublicKey, + { + name: "ECDH", + namedCurve: "P-256", + }, + true, + [] + ); + + for (const generated of generatedParams) { + for (const derived of derivedParams) { + // 4. Alice derives a shared secret using Bob's public key + const aliceSharedSecret = await crypto.subtle.deriveBits( + { + name: "ECDH", + public: bobImportKey, + }, + aliceKeyPair.privateKey, + 256 // number of bits to derive + ); + + // Use PBKDF2 to generate a derived key from Alice's shared secret + const aliceFinalKey = await crypto.subtle.importKey( + "raw", + aliceSharedSecret, + "PBKDF2", + false, + ["deriveKey"] + ); + + const aliceDerivedKey = await crypto.subtle.deriveKey( + generated, + aliceFinalKey, + derived, + true, + ["encrypt", "decrypt"] + ); + + // 6. Export Alice's public key to share with Bob + const alicePublicKey = await crypto.subtle.exportKey( + "raw", + aliceKeyPair.publicKey + ); + + // 7. Bob derives a shared secret using Alice's public key + const aliceImportKey = await crypto.subtle.importKey( + "raw", + alicePublicKey, + { + name: "ECDH", + namedCurve: "P-256", + }, + true, + [] + ); + + const bobSharedSecret = await crypto.subtle.deriveBits( + { + name: "ECDH", + public: aliceImportKey, + }, + bobKeyPair.privateKey, + 256 + ); + + // 8. Convert Bob's derived secret to a key using PBKDF2 + const bobFinalKey = await crypto.subtle.importKey( + "raw", + bobSharedSecret, + "PBKDF2", + false, + ["deriveKey"] + ); + + const bobDerivedKey = await crypto.subtle.deriveKey( + generated, + bobFinalKey, + derived, + true, + ["encrypt", "decrypt"] + ); + + // 9. Verify if both derived keys are the same + const aliceKeyBuffer = new Uint8Array( + await crypto.subtle.exportKey("raw", aliceDerivedKey) + ); + const bobKeyBuffer = new Uint8Array( + await crypto.subtle.exportKey("raw", bobDerivedKey) + ); + + // Compare the raw key buffers to check if the derived keys are equal + expect(aliceKeyBuffer).toEqual(bobKeyBuffer); + } + } + }); +}); diff --git a/types/crypto.d.ts b/types/crypto.d.ts index ef9b450212..9516baa678 100644 --- a/types/crypto.d.ts +++ b/types/crypto.d.ts @@ -330,4 +330,525 @@ declare module "crypto" { * The UUID is generated using a cryptographic pseudorandom number generator. */ function randomUUID(): UUID; + + /** + * A convenient alias for `crypto.webcrypto.subtle`. + * @since v17.4.0 + */ + const subtle: webcrypto.SubtleCrypto; + /** + * An implementation of the Web Crypto API standard. + * + * See the {@link https://nodejs.org/docs/latest/api/webcrypto.html Web Crypto API documentation} for details. + */ + namespace webcrypto { + type BufferSource = ArrayBufferView | ArrayBuffer; + type KeyFormat = "jwk" | "pkcs8" | "raw" | "spki"; + type KeyType = "private" | "public" | "secret"; + type KeyUsage = + | "decrypt" + | "deriveBits" + | "deriveKey" + | "encrypt" + | "sign" + | "unwrapKey" + | "verify" + | "wrapKey"; + type AlgorithmIdentifier = Algorithm | string; + type HashAlgorithmIdentifier = AlgorithmIdentifier; + type NamedCurve = string; + type BigInteger = Uint8Array; + interface AesCbcParams extends Algorithm { + iv: BufferSource; + } + interface AesCtrParams extends Algorithm { + counter: BufferSource; + length: number; + } + interface AesDerivedKeyParams extends Algorithm { + length: number; + } + interface AesGcmParams extends Algorithm { + additionalData?: BufferSource; + iv: BufferSource; + tagLength?: number; + } + interface AesKeyAlgorithm extends KeyAlgorithm { + length: number; + } + interface AesKeyGenParams extends Algorithm { + length: number; + } + interface Algorithm { + name: string; + } + interface EcKeyAlgorithm extends KeyAlgorithm { + namedCurve: NamedCurve; + } + interface EcKeyGenParams extends Algorithm { + namedCurve: NamedCurve; + } + interface EcKeyImportParams extends Algorithm { + namedCurve: NamedCurve; + } + interface EcdhKeyDeriveParams extends Algorithm { + public: CryptoKey; + } + interface EcdsaParams extends Algorithm { + hash: HashAlgorithmIdentifier; + } + interface Ed448Params extends Algorithm { + context?: BufferSource; + } + interface HkdfParams extends Algorithm { + hash: HashAlgorithmIdentifier; + info: BufferSource; + salt: BufferSource; + } + interface HmacImportParams extends Algorithm { + hash: HashAlgorithmIdentifier; + length?: number; + } + interface HmacKeyAlgorithm extends KeyAlgorithm { + hash: KeyAlgorithm; + length: number; + } + interface HmacKeyGenParams extends Algorithm { + hash: HashAlgorithmIdentifier; + length?: number; + } + interface JsonWebKey { + alg?: string; + crv?: string; + d?: string; + dp?: string; + dq?: string; + e?: string; + ext?: boolean; + k?: string; + key_ops?: string[]; + kty?: string; + n?: string; + oth?: RsaOtherPrimesInfo[]; + p?: string; + q?: string; + qi?: string; + use?: string; + x?: string; + y?: string; + } + interface KeyAlgorithm { + name: string; + } + interface Pbkdf2Params extends Algorithm { + hash: HashAlgorithmIdentifier; + iterations: number; + salt: BufferSource; + } + interface RsaHashedImportParams extends Algorithm { + hash: HashAlgorithmIdentifier; + } + interface RsaHashedKeyAlgorithm extends RsaKeyAlgorithm { + hash: KeyAlgorithm; + } + interface RsaHashedKeyGenParams extends RsaKeyGenParams { + hash: HashAlgorithmIdentifier; + } + interface RsaKeyAlgorithm extends KeyAlgorithm { + modulusLength: number; + publicExponent: BigInteger; + } + interface RsaKeyGenParams extends Algorithm { + modulusLength: number; + publicExponent: BigInteger; + } + interface RsaOaepParams extends Algorithm { + label?: BufferSource; + } + interface RsaOtherPrimesInfo { + d?: string; + r?: string; + t?: string; + } + interface RsaPssParams extends Algorithm { + saltLength: number; + } + + interface CryptoKey { + /** + * An object detailing the algorithm for which the key can be used along with additional algorithm-specific parameters. + */ + readonly algorithm: KeyAlgorithm; + /** + * When `true`, the {@link CryptoKey} can be extracted using either `subtleCrypto.exportKey()` or `subtleCrypto.wrapKey()`. + */ + readonly extractable: boolean; + /** + * A string identifying whether the key is a symmetric (`'secret'`) or asymmetric (`'private'` or `'public'`) key. + */ + readonly type: KeyType; + /** + * An array of strings identifying the operations for which the key may be used. + * + * The possible usages are: + * - `'encrypt'` - The key may be used to encrypt data. + * - `'decrypt'` - The key may be used to decrypt data. + * - `'sign'` - The key may be used to generate digital signatures. + * - `'verify'` - The key may be used to verify digital signatures. + * - `'deriveKey'` - The key may be used to derive a new key. + * - `'deriveBits'` - The key may be used to derive bits. + * - `'wrapKey'` - The key may be used to wrap another key. + * - `'unwrapKey'` - The key may be used to unwrap another key. + * + * Valid key usages depend on the key algorithm (identified by `cryptokey.algorithm.name`). + * @since v15.0.0 + */ + readonly usages: KeyUsage[]; + } + /** + * The `CryptoKeyPair` is a simple dictionary object with `publicKey` and `privateKey` properties, representing an asymmetric key pair. + */ + interface CryptoKeyPair { + /** + * A {@link CryptoKey} whose type will be `'private'`. + */ + privateKey: CryptoKey; + /** + * A {@link CryptoKey} whose type will be `'public'`. + */ + publicKey: CryptoKey; + } + + interface SubtleCrypto { + /** + * Using the method and parameters specified in `algorithm` and the keying material provided by `key`, + * `subtle.decrypt()` attempts to decipher the provided `data`. If successful, + * the returned promise will be resolved with an `` containing the plaintext result. + * + * The algorithms currently supported include: + * + * - `'RSA-OAEP'` + * - `'AES-CTR'` + * - `'AES-CBC'` + * - `'AES-GCM'` + */ + decrypt( + algorithm: + | AlgorithmIdentifier + | RsaOaepParams + | AesCtrParams + | AesCbcParams + | AesGcmParams, + key: CryptoKey, + data: BufferSource + ): Promise; + /** + * Using the method and parameters specified in `algorithm` and the keying material provided by `baseKey`, + * `subtle.deriveBits()` attempts to generate `length` bits. + * The LLRT implementation requires that when `length` is a number it must be multiple of `8`. + * When `length` is `null` the maximum number of bits for a given algorithm is generated. This is allowed + * for the `'ECDH'`, `'X25519'`, and `'X448'` algorithms. + * If successful, the returned promise will be resolved with an `` containing the generated data. + * + * The algorithms currently supported include: + * + * - `'ECDH'` + * - `'X25519'` + * - `'X448'` + * - `'HKDF'` + * - `'PBKDF2'` + */ + deriveBits( + algorithm: EcdhKeyDeriveParams, + baseKey: CryptoKey, + length: number | null + ): Promise; + deriveBits( + algorithm: AlgorithmIdentifier | HkdfParams | Pbkdf2Params, + baseKey: CryptoKey, + length: number + ): Promise; + /** + * Using the method and parameters specified in `algorithm`, and the keying material provided by `baseKey`, + * `subtle.deriveKey()` attempts to generate a new ` based on the method and parameters in `derivedKeyAlgorithm`. + * + * Calling `subtle.deriveKey()` is equivalent to calling `subtle.deriveBits()` to generate raw keying material, + * then passing the result into the `subtle.importKey()` method using the `deriveKeyAlgorithm`, `extractable`, and `keyUsages` parameters as input. + * + * The algorithms currently supported include: + * + * - `'ECDH'` + * - `'X25519'` + * - `'X448'` + * - `'HKDF'` + * - `'PBKDF2'` + * @param keyUsages See {@link https://nodejs.org/docs/latest/api/webcrypto.html#cryptokeyusages Key usages}. + */ + deriveKey( + algorithm: + | AlgorithmIdentifier + | EcdhKeyDeriveParams + | HkdfParams + | Pbkdf2Params, + baseKey: CryptoKey, + derivedKeyAlgorithm: + | AlgorithmIdentifier + | AesDerivedKeyParams + | HmacImportParams + | HkdfParams + | Pbkdf2Params, + extractable: boolean, + keyUsages: readonly KeyUsage[] + ): Promise; + /** + * Using the method identified by `algorithm`, `subtle.digest()` attempts to generate a digest of `data`. + * If successful, the returned promise is resolved with an `` containing the computed digest. + * + * If `algorithm` is provided as a ``, it must be one of: + * + * - `'SHA-1'` + * - `'SHA-256'` + * - `'SHA-384'` + * - `'SHA-512'` + * + * If `algorithm` is provided as an ``, it must have a `name` property whose value is one of the above. + */ + digest( + algorithm: AlgorithmIdentifier, + data: BufferSource + ): Promise; + /** + * Using the method and parameters specified by `algorithm` and the keying material provided by `key`, + * `subtle.encrypt()` attempts to encipher `data`. If successful, + * the returned promise is resolved with an `` containing the encrypted result. + * + * The algorithms currently supported include: + * + * - `'RSA-OAEP'` + * - `'AES-CTR'` + * - `'AES-CBC'` + * - `'AES-GCM'` + */ + encrypt( + algorithm: + | AlgorithmIdentifier + | RsaOaepParams + | AesCtrParams + | AesCbcParams + | AesGcmParams, + key: CryptoKey, + data: BufferSource + ): Promise; + /** + * Exports the given key into the specified format, if supported. + * + * If the `` is not extractable, the returned promise will reject. + * + * When `format` is either `'pkcs8'` or `'spki'` and the export is successful, + * the returned promise will be resolved with an `` containing the exported key data. + * + * When `format` is `'jwk'` and the export is successful, the returned promise will be resolved with a + * JavaScript object conforming to the {@link https://tools.ietf.org/html/rfc7517 JSON Web Key} specification. + * @param format Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. + * @returns `` containing ``. + */ + exportKey(format: "jwk", key: CryptoKey): Promise; + exportKey( + format: Exclude, + key: CryptoKey + ): Promise; + /** + * Using the method and parameters provided in `algorithm`, + * `subtle.generateKey()` attempts to generate new keying material. + * Depending the method used, the method may generate either a single `` or a ``. + * + * The `` (public and private key) generating algorithms supported include: + * + * - `'RSASSA-PKCS1-v1_5'` + * - `'RSA-PSS'` + * - `'RSA-OAEP'` + * - `'ECDSA'` + * - `'ECDH'` + * - `'Ed25519'` + * The `` (secret key) generating algorithms supported include: + * + * - `'HMAC'` + * - `'AES-CTR'` + * - `'AES-CBC'` + * - `'AES-GCM'` + * - `'AES-KW'` + * @param keyUsages See {@link https://nodejs.org/docs/latest/api/webcrypto.html#cryptokeyusages Key usages}. + */ + generateKey( + algorithm: RsaHashedKeyGenParams | EcKeyGenParams, + extractable: boolean, + keyUsages: readonly KeyUsage[] + ): Promise; + generateKey( + algorithm: AesKeyGenParams | HmacKeyGenParams | Pbkdf2Params, + extractable: boolean, + keyUsages: readonly KeyUsage[] + ): Promise; + generateKey( + algorithm: AlgorithmIdentifier, + extractable: boolean, + keyUsages: KeyUsage[] + ): Promise; + /** + * The `subtle.importKey()` method attempts to interpret the provided `keyData` as the given `format` + * to create a `` instance using the provided `algorithm`, `extractable`, and `keyUsages` arguments. + * If the import is successful, the returned promise will be resolved with the created ``. + * + * If importing a `'PBKDF2'` key, `extractable` must be `false`. + * @param format Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. + * @param keyUsages See {@link https://nodejs.org/docs/latest/api/webcrypto.html#cryptokeyusages Key usages}. + */ + importKey( + format: "jwk", + keyData: JsonWebKey, + algorithm: + | AlgorithmIdentifier + | RsaHashedImportParams + | EcKeyImportParams + | HmacImportParams + | AesKeyAlgorithm, + extractable: boolean, + keyUsages: readonly KeyUsage[] + ): Promise; + importKey( + format: Exclude, + keyData: BufferSource, + algorithm: + | AlgorithmIdentifier + | RsaHashedImportParams + | EcKeyImportParams + | HmacImportParams + | AesKeyAlgorithm, + extractable: boolean, + keyUsages: KeyUsage[] + ): Promise; + /** + * Using the method and parameters given by `algorithm` and the keying material provided by `key`, + * `subtle.sign()` attempts to generate a cryptographic signature of `data`. If successful, + * the returned promise is resolved with an `` containing the generated signature. + * + * The algorithms currently supported include: + * + * - `'RSASSA-PKCS1-v1_5'` + * - `'RSA-PSS'` + * - `'ECDSA'` + * - `'Ed25519'` + * - `'HMAC'` + */ + sign( + algorithm: + | AlgorithmIdentifier + | RsaPssParams + | EcdsaParams + | Ed448Params, + key: CryptoKey, + data: BufferSource + ): Promise; + /** + * In cryptography, "wrapping a key" refers to exporting and then encrypting the keying material. + * The `subtle.unwrapKey()` method attempts to decrypt a wrapped key and create a `` instance. + * It is equivalent to calling `subtle.decrypt()` first on the encrypted key data (using the `wrappedKey`, `unwrapAlgo`, and `unwrappingKey` arguments as input) + * then passing the results in to the `subtle.importKey()` method using the `unwrappedKeyAlgo`, `extractable`, and `keyUsages` arguments as inputs. + * If successful, the returned promise is resolved with a `` object. + * + * The wrapping algorithms currently supported include: + * + * - `'RSA-OAEP'` + * - `'AES-CTR'` + * - `'AES-CBC'` + * - `'AES-GCM'` + * - `'AES-KW'` + * + * The unwrapped key algorithms supported include: + * + * - `'RSASSA-PKCS1-v1_5'` + * - `'RSA-PSS'` + * - `'RSA-OAEP'` + * - `'ECDSA'` + * - `'ECDH'` + * - `'HMAC'` + * - `'AES-CTR'` + * - `'AES-CBC'` + * - `'AES-GCM'` + * - `'AES-KW'` + * @param format Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. + * @param keyUsages See {@link https://nodejs.org/docs/latest/api/webcrypto.html#cryptokeyusages Key usages}. + */ + unwrapKey( + format: KeyFormat, + wrappedKey: BufferSource, + unwrappingKey: CryptoKey, + unwrapAlgorithm: + | AlgorithmIdentifier + | RsaOaepParams + | AesCtrParams + | AesCbcParams + | AesGcmParams, + unwrappedKeyAlgorithm: + | AlgorithmIdentifier + | RsaHashedImportParams + | EcKeyImportParams + | HmacImportParams + | AesKeyAlgorithm, + extractable: boolean, + keyUsages: KeyUsage[] + ): Promise; + /** + * Using the method and parameters given in `algorithm` and the keying material provided by `key`, + * `subtle.verify()` attempts to verify that `signature` is a valid cryptographic signature of `data`. + * The returned promise is resolved with either `true` or `false`. + * + * The algorithms currently supported include: + * + * - `'RSASSA-PKCS1-v1_5'` + * - `'RSA-PSS'` + * - `'ECDSA'` + * - `'Ed25519'` + * - `'HMAC'` + */ + verify( + algorithm: + | AlgorithmIdentifier + | RsaPssParams + | EcdsaParams + | Ed448Params, + key: CryptoKey, + signature: BufferSource, + data: BufferSource + ): Promise; + /** + * In cryptography, "wrapping a key" refers to exporting and then encrypting the keying material. + * The `subtle.wrapKey()` method exports the keying material into the format identified by `format`, + * then encrypts it using the method and parameters specified by `wrapAlgo` and the keying material provided by `wrappingKey`. + * It is the equivalent to calling `subtle.exportKey()` using `format` and `key` as the arguments, + * then passing the result to the `subtle.encrypt()` method using `wrappingKey` and `wrapAlgo` as inputs. + * If successful, the returned promise will be resolved with an `` containing the encrypted key data. + * + * The wrapping algorithms currently supported include: + * + * - `'RSA-OAEP'` + * - `'AES-CTR'` + * - `'AES-CBC'` + * - `'AES-GCM'` + * - `'AES-KW'` + * @param format Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. + */ + wrapKey( + format: KeyFormat, + key: CryptoKey, + wrappingKey: CryptoKey, + wrapAlgorithm: + | AlgorithmIdentifier + | RsaOaepParams + | AesCtrParams + | AesCbcParams + | AesGcmParams + ): Promise; + } + } }