From 2787cf8ed775872b6f00a4d2d3efcfcd0357d079 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Mon, 29 Sep 2025 11:35:16 +0200 Subject: [PATCH 01/77] WIP --- Cargo.lock | 415 ++++----- llrt_core/src/vm.rs | 6 +- modules/llrt_crypto/Cargo.toml | 23 +- modules/llrt_crypto/src/lib.rs | 72 +- modules/llrt_crypto/src/md5_hash.rs | 17 +- modules/llrt_crypto/src/provider/mod.rs | 222 +++++ modules/llrt_crypto/src/provider/openssl.rs | 467 ++++++++++ modules/llrt_crypto/src/provider/ring.rs | 302 +++++++ modules/llrt_crypto/src/provider/rust.rs | 848 ++++++++++++++++++ modules/llrt_crypto/src/sha_hash.rs | 123 +-- modules/llrt_crypto/src/subtle/derive.rs | 103 +-- modules/llrt_crypto/src/subtle/digest.rs | 12 +- modules/llrt_crypto/src/subtle/encryption.rs | 213 ++--- .../llrt_crypto/src/subtle/generate_key.rs | 242 ++--- modules/llrt_crypto/src/subtle/mod.rs | 26 +- modules/llrt_crypto/src/subtle/sign.rs | 109 +-- modules/llrt_crypto/src/subtle/verify.rs | 109 +-- 17 files changed, 2450 insertions(+), 859 deletions(-) create mode 100644 modules/llrt_crypto/src/provider/mod.rs create mode 100644 modules/llrt_crypto/src/provider/openssl.rs create mode 100644 modules/llrt_crypto/src/provider/ring.rs create mode 100644 modules/llrt_crypto/src/provider/rust.rs diff --git a/Cargo.lock b/Cargo.lock index d8b057160d..ab71c270a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,9 +10,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" -version = "0.6.0-rc.3" +version = "0.6.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d2d54c4d9e7006f132f615a167865bff927a79ca63d8f637237575ce0a9795" +checksum = "cc2b86f658b0f536411ee61c10cec8376f83375b0c98bdc6a640e249f01549d0" dependencies = [ "crypto-common", "inout", @@ -124,9 +124,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener", "event-listener-strategy", @@ -151,6 +151,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b59d472eab27ade8d770dcb11da7201c11234bef9f82ce7aa517be028d462b" +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.22.1" @@ -169,9 +175,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bindgen" @@ -210,9 +216,9 @@ dependencies = [ [[package]] name = "block-padding" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d28ed5f5f65056148fd25e1a596b5b6d9e772270abf9a9085d7cbfbf26c563" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" dependencies = [ "hybrid-array", ] @@ -258,9 +264,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytes" @@ -285,9 +291,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.47" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "jobserver", @@ -331,7 +337,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -373,9 +379,9 @@ dependencies = [ [[package]] name = "cipher" -version = "0.5.0-rc.2" +version = "0.5.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "155e4a260750fa4f7754649f049748aacc31db238a358d85fd721002f230f92f" +checksum = "eba4d87abf4032a6d927f84b71af5086128a3349b929b4501c51a0fe0981a937" dependencies = [ "block-buffer", "crypto-common", @@ -395,18 +401,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "3e34525d5bbbd55da2bb745d34b36121baac88d07619a9a09cfcf4a6c0832785" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "59a20016a20a3da95bef50ec7238dbd09baeef4311dcdd38ec15aba69812fb61" dependencies = [ "anstyle", "clap_lex", @@ -414,15 +420,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "cmake" -version = "0.1.54" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] @@ -473,6 +479,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpubits" +version = "0.1.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251aaca52dbc19119279d9364222e188ffdf48006791040bfd002617ab0ebc41" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -637,9 +649,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0-rc.5" +version = "0.2.0-rc.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919bd05924682a5480aec713596b9e2aabed3a0a6022fab6847f85a99e5f190a" +checksum = "c7722afd27468475c9b6063dc03a57ef2ca833816981619f8ebe64d38d207eef" dependencies = [ "hybrid-array", ] @@ -666,9 +678,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "5.0.0-pre.3" +version = "5.0.0-pre.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92419e1cdc506051ffd30713ad09d0ec6a24bba9197e12989de389e35b19c77a" +checksum = "a434aec7908df6ca86cda069864d7686aea8afad979aadc9e30e50ac3e40b45a" dependencies = [ "cfg-if", "cpufeatures", @@ -733,9 +745,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.0-rc.4" +version = "0.11.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea390c940e465846d64775e55e3115d5dc934acb953de6f6e6360bc232fe2bf7" +checksum = "bff8de092798697546237a3a701e4174fe021579faec9b854379af9bf1e31962" dependencies = [ "block-buffer", "const-oid", @@ -756,9 +768,9 @@ dependencies = [ [[package]] name = "dlopen2" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" dependencies = [ "dlopen2_derive", "libc", @@ -768,9 +780,9 @@ dependencies = [ [[package]] name = "dlopen2_derive" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" dependencies = [ "proc-macro2", "quote", @@ -812,7 +824,7 @@ version = "0.14.0-rc.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ecd2903524729de5d0cba7589121744513feadd56d71980cb480c48caceb11" dependencies = [ - "base16ct", + "base16ct 0.3.0", "crypto-bigint", "digest", "hkdf", @@ -876,9 +888,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "flate2" @@ -1012,9 +1024,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -1035,9 +1047,9 @@ dependencies = [ [[package]] name = "ghash" -version = "0.6.0-rc.3" +version = "0.6.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "333de57ed9494a40df4bbb866752b100819dde0d18f2264c48f5a08a85fe673d" +checksum = "e2450dbd372f0b86224cdb9220351b1d5384e0aa0d29a50defd22b29a12f5e50" dependencies = [ "polyval", ] @@ -1050,9 +1062,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -1116,18 +1128,18 @@ dependencies = [ [[package]] name = "hkdf" -version = "0.13.0-rc.3" +version = "0.13.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfbb4225acf2b5cc4e12d384672cd6d1f0cb980ff5859ffcf144db25b593a24d" +checksum = "c1493605868fc7d216afa78a26956d56f5c0a12dbdb8ee4fe9e0b70a28ec7d57" dependencies = [ "hmac", ] [[package]] name = "hmac" -version = "0.13.0-rc.3" +version = "0.13.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c597ac7d6cc8143e30e83ef70915e7f883b18d8bec2e2b2bce47f5bbb06d57" +checksum = "d9956e202a691c5c86c60303a421f66f93f44b29433407b7c43cf2bebadc750e" dependencies = [ "digest", ] @@ -1188,9 +1200,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" +checksum = "b41fb3dc24fe72c2e3a4685eed55917c2fb228851257f4a8f2d985da9443c3e5" dependencies = [ "subtle", "typenum", @@ -1259,9 +1271,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1269,7 +1281,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core", ] [[package]] @@ -1329,9 +1341,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -1343,9 +1355,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -1391,9 +1403,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", @@ -1401,9 +1413,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7357b6e7aa75618c7864ebd0634b115a7218b0615f4cb1df33ac3eca23943d4" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" dependencies = [ "block-padding", "hybrid-array", @@ -1436,9 +1448,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -1483,14 +1495,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link 0.2.1", + "windows-link", ] [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" @@ -1711,6 +1723,7 @@ dependencies = [ "llrt_utils", "md-5", "once_cell", + "openssl-sys", "p256", "p384", "p521", @@ -2002,7 +2015,7 @@ dependencies = [ "users", "whoami", "windows-registry", - "windows-result 0.4.1", + "windows-result", "windows-version", ] @@ -2189,15 +2202,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "md-5" -version = "0.11.0-rc.3" +version = "0.11.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64dd2c9099caf8e29b629305199dddb1c6d981562b62c089afea54b0b4b5c333" +checksum = "340c5c3970c137330f5289dc52a6f6c24db6d6839b6121efdeeb120061c30313" dependencies = [ "cfg-if", "digest", @@ -2227,9 +2240,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", @@ -2249,9 +2262,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" dependencies = [ "winapi", ] @@ -2308,9 +2321,21 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl-probe" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] [[package]] name = "outref" @@ -2351,7 +2376,7 @@ version = "0.14.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75296e7cb5d53c8a5083ff26b5707177962cd5851af961a56316e863f1ea757c" dependencies = [ - "base16ct", + "base16ct 0.3.0", "ecdsa", "elliptic-curve", "primefield", @@ -2389,7 +2414,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -2502,9 +2527,9 @@ dependencies = [ [[package]] name = "pkcs8" -version = "0.11.0-rc.8" +version = "0.11.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77089aec8290d0b7bb01b671b091095cf1937670725af4fd73d47249f03b12c0" +checksum = "b226d2cc389763951db8869584fd800cbbe2962bf454e2edeb5172b31ee99774" dependencies = [ "der", "spki", @@ -2518,11 +2543,11 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polyval" -version = "0.7.0-rc.3" +version = "0.7.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad60831c19edda4b20878a676595c357e93a9b4e6dca2ba98d75b01066b317b" +checksum = "d7c26fb288ad859274ad7e33746324ab027f70802d54bfadf07732b93bbe3ef7" dependencies = [ - "cfg-if", + "cpubits", "cpufeatures", "universal-hash", ] @@ -2579,9 +2604,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -2607,9 +2632,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -2633,9 +2658,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.0-rc-2" +version = "0.10.0-rc-6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "104a23e4e8b77312a823b6b5613edbac78397e2f34320bc7ac4277013ec4478e" +checksum = "70765ff7112b0fb2d272d24d9a2f907fc206211304328fe58b2db15a5649ef28" [[package]] name = "rayon" @@ -2726,9 +2751,9 @@ dependencies = [ [[package]] name = "rfc6979" -version = "0.5.0-rc.3" +version = "0.5.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8e2323084c987a72875b2fd682b7307d5cf14d47e3875bb5e89948e8809d4" +checksum = "4a478f26ee9327bce006ff44c03e9a80dba5cedfba171d37c03ca3669e70d461" dependencies = [ "hmac", "subtle", @@ -2742,7 +2767,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -2865,9 +2890,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", @@ -2913,18 +2938,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", @@ -2969,11 +2994,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sec1" -version = "0.8.0-rc.10" +version = "0.8.0-rc.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dff52f6118bc9f0ac974a54a639d499ac26a6cad7a6e39bc0990c19625e793b" +checksum = "7a2400ed44a13193820aa528a19f376c3843141a8ce96ff34b11104cc79763f2" dependencies = [ - "base16ct", + "base16ct 1.0.0", "der", "hybrid-array", "subtle", @@ -3041,32 +3066,32 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] name = "serdect" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ef0e35b322ddfaecbc60f34ab448e157e48531288ee49fafbb053696b8ffe2" +checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06" dependencies = [ - "base16ct", + "base16ct 1.0.0", "serde", ] [[package]] name = "sha2" -version = "0.11.0-rc.3" +version = "0.11.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d43dc0354d88b791216bb5c1bfbb60c0814460cc653ae0ebd71f286d0bd927" +checksum = "7535f94fa3339fe9e5e9be6260a909e62af97f6e14b32345ccf79b92b8b81233" dependencies = [ "cfg-if", "cpufeatures", @@ -3102,18 +3127,19 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] [[package]] name = "signature" -version = "3.0.0-rc.5" +version = "3.0.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0251c9d6468f4ba853b6352b190fb7c1e405087779917c238445eb03993826" +checksum = "0ad0ce3b3f8efd7406f22e2ca5d02be21cdf3b3d1d53ab141f784de8965c7c7e" dependencies = [ "digest", "rand_core", @@ -3121,9 +3147,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "simd-json" @@ -3145,9 +3171,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" @@ -3181,9 +3207,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -3213,9 +3239,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -3317,9 +3343,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -3330,18 +3356,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", "toml_datetime", @@ -3351,9 +3377,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] @@ -3410,9 +3436,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "universal-hash" -version = "0.6.0-rc.3" +version = "0.6.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ad6682ddb0189a4d3c2a5c54b8920ab6231ae911db53fc61a0709507bf1713b" +checksum = "82084f2919885ea910502c74f0a362827aaad3d28d142ca97448bfb39c7e271a" dependencies = [ "crypto-common", "subtle", @@ -3472,6 +3498,12 @@ dependencies = [ "ryu", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vsimd" version = "0.8.0" @@ -3505,18 +3537,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -3527,9 +3559,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3537,9 +3569,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -3550,9 +3582,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -3568,9 +3600,9 @@ dependencies = [ [[package]] name = "whoami" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace4d5c7b5ab3d99629156d4e0997edbe98a4beb6d5ba99e2cae830207a81983" +checksum = "8fae98cf96deed1b7572272dfc777713c249ae40aa1cf8862e091e8b745f5361" dependencies = [ "libredox", ] @@ -3613,7 +3645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", - "windows-core 0.62.2", + "windows-core", "windows-future", "windows-numerics", ] @@ -3624,20 +3656,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.62.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-core", ] [[package]] @@ -3648,9 +3667,9 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -3659,8 +3678,8 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", + "windows-core", + "windows-link", "windows-threading", ] @@ -3686,12 +3705,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -3704,8 +3717,8 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", + "windows-core", + "windows-link", ] [[package]] @@ -3714,18 +3727,9 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -3734,16 +3738,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -3752,7 +3747,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -3779,7 +3774,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -3804,7 +3799,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -3821,7 +3816,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -3830,7 +3825,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -3931,9 +3926,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -3963,9 +3958,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" @@ -3975,9 +3970,9 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "x25519-dalek" -version = "3.0.0-pre.3" +version = "3.0.0-pre.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8367a41efe370c38fa4af81968298cdd695311791e4797118a1621f04ed75859" +checksum = "40dac5a6478dc8baae2031cfc87d966e427a4fe5011e7f90785ff5c8aa9f2d06" dependencies = [ "curve25519-dalek", "rand_core", @@ -4009,18 +4004,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.28" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90" +checksum = "fdea86ddd5568519879b8187e1cf04e24fce28f7fe046ceecbce472ff19a2572" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.28" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26" +checksum = "0c15e1b46eff7c6c91195752e0eeed8ef040e391cdece7c25376957d5f15df22" dependencies = [ "proc-macro2", "quote", @@ -4087,6 +4082,12 @@ dependencies = [ "syn", ] +[[package]] +name = "zmij" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" + [[package]] name = "zstd" version = "0.13.3" diff --git a/llrt_core/src/vm.rs b/llrt_core/src/vm.rs index b1a2d9660f..7c2776b77a 100644 --- a/llrt_core/src/vm.rs +++ b/llrt_core/src/vm.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use std::{env, result::Result as StdResult}; +use llrt_modules::crypto; use ring::rand::SecureRandom; use rquickjs::{ context::EvalOptions, loader::FileResolver, prelude::Func, AsyncContext, AsyncRuntime, @@ -22,7 +23,6 @@ use crate::libs::{ }; use crate::modules::{ async_hooks::promise_hook_tracker, - crypto::SYSTEM_RANDOM, embedded::{loader::EmbeddedLoader, resolver::EmbeddedResolver}, module_builder::ModuleBuilder, package::{loader::PackageLoader, resolver::PackageResolver}, @@ -85,10 +85,6 @@ impl Vm { http::init()?; security::init()?; - SYSTEM_RANDOM - .fill(&mut [0; 8]) - .expect("Failed to initialize SystemRandom"); - let mut file_resolver = FileResolver::default(); let mut paths: Vec<&str> = Vec::with_capacity(10); diff --git a/modules/llrt_crypto/Cargo.toml b/modules/llrt_crypto/Cargo.toml index 865701db01..82e17647f1 100644 --- a/modules/llrt_crypto/Cargo.toml +++ b/modules/llrt_crypto/Cargo.toml @@ -12,9 +12,8 @@ name = "llrt_crypto" path = "src/lib.rs" [features] -default = ["subtle-rs"] - -subtle-rs = [ +default = ["crypto-rust"] +crypto-rust = [ "llrt_json", "aes", "aes-gcm", @@ -33,6 +32,8 @@ subtle-rs = [ "const-oid", "pkcs8", ] +# Crypto provider features +crypto-openssl = ["openssl-sys"] [dependencies] crc32c = { version = "0.6", default-features = false } @@ -48,13 +49,13 @@ rand = { version = "0.10.0-rc.5", features = [ "thread_rng", ], default-features = false } ring = { version = "0.17", default-features = false } -rquickjs = { version = "0.11", features = [ - "macro", -], default-features = false } +rquickjs = { version = "0.11", features = ["macro"], default-features = false } + -# Optional -aes = { version = "0.9.0-rc.2", optional = true } -aes-gcm = { version = "0.11.0-rc.2", features = [ +# optional +llrt_json = { version = "0.7.0-beta", path = "../../libs/llrt_json", optional = true } +aes = { version = "0.9.0-rc.0", optional = true } +aes-gcm = { version = "0.11.0-rc.0", features = [ "alloc", ], default-features = false, optional = true } aes-kw = { version = "0.3.0-rc.1", features = [ @@ -73,7 +74,6 @@ ecdsa = { version = "0.17.0-rc.9", default-features = false, optional = true } elliptic-curve = { version = "0.14.0-rc.17", features = [ "alloc", ], default-features = false, optional = true } -llrt_json = { version = "0.7.0-beta", path = "../../libs/llrt_json", optional = true } md-5 = { version = "0.11.0-rc.3", default-features = false } rsa = { version = "0.10.0-rc.10", features = [ "std", @@ -108,5 +108,8 @@ x25519-dalek = { version = "3.0.0-pre.3", features = [ "zeroize", ], default-features = false, optional = true } +# Crypto provider dependencies +openssl-sys = { version = "0.9", optional = true } + [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } diff --git a/modules/llrt_crypto/src/lib.rs b/modules/llrt_crypto/src/lib.rs index 22ba50a9fc..937d9d4d39 100644 --- a/modules/llrt_crypto/src/lib.rs +++ b/modules/llrt_crypto/src/lib.rs @@ -3,9 +3,10 @@ mod crc32; mod md5_hash; mod sha_hash; -#[cfg(feature = "subtle-rs")] mod subtle; +mod provider; + use std::slice; use llrt_buffer::Buffer; @@ -24,8 +25,7 @@ use ring::rand::{SecureRandom, SystemRandom}; #[cfg(feature = "subtle-rs")] use rquickjs::prelude::Async; use rquickjs::{ - atom::PredefinedAtom, - function::{Constructor, Opt}, + function::Opt, module::{Declarations, Exports, ModuleDef}, prelude::{Func, Rest}, Class, Ctx, Error, Exception, Function, IntoJs, Null, Object, Result, Value, @@ -40,10 +40,11 @@ use subtle::{ use self::{ crc32::{Crc32, Crc32c}, md5_hash::Md5, - sha_hash::{Hash, Hmac, ShaAlgorithm, ShaHash}, + sha_hash::{Hash, Hmac, ShaAlgorithm}, }; -pub static SYSTEM_RANDOM: Lazy = Lazy::new(SystemRandom::new); +static CRYPTO_PROVIDER: Lazy = + Lazy::new(|| provider::DefaultProvider {}); fn encoded_bytes<'js>(ctx: Ctx<'js>, bytes: &[u8], encoding: &str) -> Result> { match encoding { @@ -63,8 +64,8 @@ fn encoded_bytes<'js>(ctx: Ctx<'js>, bytes: &[u8], encoding: &str) -> Result Vec { - let mut vec = vec![0; length]; - SYSTEM_RANDOM.fill(&mut vec).unwrap(); + let mut vec = vec![0u8; length]; + rand::rng().fill(&mut vec[..]); vec } @@ -134,9 +135,7 @@ fn random_fill_sync<'js>( let bytes = unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), source_length) }; - SYSTEM_RANDOM - .fill(&mut bytes[start + source_offset..end - source_offset]) - .unwrap(); + rand::rng().fill(&mut bytes[start + source_offset..end - source_offset]); } Ok(obj) @@ -170,7 +169,7 @@ fn get_random_values<'js>(ctx: Ctx<'js>, obj: Object<'js>) -> Result std::slice::from_raw_parts_mut(raw.ptr.as_ptr().add(source_offset), source_length) }; - SYSTEM_RANDOM.fill(bytes).unwrap() + rand::rng().fill(bytes) } Ok(obj) @@ -244,27 +243,23 @@ pub fn init(ctx: &Ctx<'_>) -> Result<()> { crypto.set("randomFill", Func::from(random_fill))?; crypto.set("getRandomValues", Func::from(get_random_values))?; - #[cfg(feature = "subtle-rs")] - { - Class::::define(&globals)?; - Class::::define(&globals)?; - - let subtle = Class::instance(ctx.clone(), SubtleCrypto {})?; - - 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)))?; - subtle.set("wrapKey", Func::from(Async(subtle_wrap_key)))?; - subtle.set("unwrapKey", Func::from(Async(subtle_unwrap_key)))?; - crypto.set("subtle", subtle)?; - } + Class::::define(&globals)?; + Class::::define(&globals)?; + + let subtle = Class::instance(ctx.clone(), SubtleCrypto {})?; + 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)))?; + subtle.set("wrapKey", Func::from(Async(subtle_wrap_key)))?; + subtle.set("unwrapKey", Func::from(Async(subtle_unwrap_key)))?; + crypto.set("subtle", subtle)?; globals.set("crypto", crypto)?; @@ -301,17 +296,8 @@ impl ModuleDef for CryptoModule { fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { 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 |ctx, secret| { - struct Args<'js>(Ctx<'js>, Opt>); - let Args(ctx, secret) = Args(ctx, secret); - ShaHash::new(ctx, algo.clone(), secret) - })?; - - default.set(class_name, ctor)?; + let _class_name: &str = sha_algorithm.class_name(); + // ShaHash class removed - using Hash and Hmac instead } let crypto: Object = ctx.globals().get("crypto")?; diff --git a/modules/llrt_crypto/src/md5_hash.rs b/modules/llrt_crypto/src/md5_hash.rs index 6686196cfa..475717c9e4 100644 --- a/modules/llrt_crypto/src/md5_hash.rs +++ b/modules/llrt_crypto/src/md5_hash.rs @@ -1,16 +1,16 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::bytes::{bytes_to_typed_array, ObjectBytes}; -use md5::{Digest as Md5Digest, Md5 as MdHasher}; use rquickjs::{function::Opt, prelude::This, Class, Ctx, Result, Value}; -use super::encoded_bytes; +use crate::{provider::{CryptoProvider, SimpleDigest}, sha_hash::ShaAlgorithm}; +use super::{encoded_bytes, CRYPTO_PROVIDER}; #[rquickjs::class] #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] pub struct Md5 { #[qjs(skip_trace)] - hasher: MdHasher, + hasher: ::Digest, } #[rquickjs::methods] @@ -18,18 +18,17 @@ impl Md5 { #[qjs(constructor)] fn new() -> Self { Self { - hasher: MdHasher::new(), + hasher: CRYPTO_PROVIDER.digest(ShaAlgorithm::MD5), } } #[qjs(rename = "digest")] - fn md5_digest<'js>(&self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let digest = self.hasher.clone().finalize(); - let bytes: &[u8] = digest.as_ref(); + fn md5_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { + let digest = std::mem::replace(&mut self.hasher, CRYPTO_PROVIDER.digest(ShaAlgorithm::MD5)).finalize(); match encoding.0 { - Some(encoding) => encoded_bytes(ctx, bytes, &encoding), - None => bytes_to_typed_array(ctx, bytes), + Some(encoding) => encoded_bytes(ctx, &digest, &encoding), + None => bytes_to_typed_array(ctx, &digest), } } diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs new file mode 100644 index 0000000000..c0c6c2db19 --- /dev/null +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -0,0 +1,222 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(feature = "crypto-openssl")] +mod openssl; +#[cfg(feature = "crypto-ring")] +mod ring; +#[cfg(feature = "crypto-rust")] +mod rust; + +use crate::sha_hash::ShaAlgorithm; +use crate::subtle::EllipticCurve; + +pub trait SimpleDigest { + fn update(&mut self, data: &[u8]); + fn finalize(self) -> Vec; +} + +#[derive(Debug, Clone, Copy)] +pub enum AesMode { + Ctr { counter_length: u32 }, + Cbc, + Gcm { tag_length: u8 }, +} + +pub trait CryptoProvider { + type Digest: SimpleDigest; + type Hmac: HmacProvider; + + // Digest operations + fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest; + + // HMAC operations + fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac; + + // ECDSA operations + fn ecdsa_sign( + &self, + curve: EllipticCurve, + private_key_der: &[u8], + digest: &[u8], + ) -> Result, CryptoError>; + fn ecdsa_verify( + &self, + curve: EllipticCurve, + public_key_sec1: &[u8], + signature: &[u8], + digest: &[u8], + ) -> Result; + + // EdDSA operations + fn ed25519_sign(&self, private_key_der: &[u8], data: &[u8]) -> Result, CryptoError>; + fn ed25519_verify( + &self, + public_key_bytes: &[u8], + signature: &[u8], + data: &[u8], + ) -> Result; + + // RSA operations + fn rsa_pss_sign( + &self, + private_key_der: &[u8], + digest: &[u8], + salt_length: usize, + hash_alg: ShaAlgorithm, + ) -> Result, CryptoError>; + fn rsa_pss_verify( + &self, + public_key_der: &[u8], + signature: &[u8], + digest: &[u8], + salt_length: usize, + hash_alg: ShaAlgorithm, + ) -> Result; + fn rsa_pkcs1v15_sign( + &self, + private_key_der: &[u8], + digest: &[u8], + hash_alg: ShaAlgorithm, + ) -> Result, CryptoError>; + fn rsa_pkcs1v15_verify( + &self, + public_key_der: &[u8], + signature: &[u8], + digest: &[u8], + hash_alg: ShaAlgorithm, + ) -> Result; + fn rsa_oaep_encrypt( + &self, + public_key_der: &[u8], + data: &[u8], + hash_alg: ShaAlgorithm, + label: Option<&[u8]>, + ) -> Result, CryptoError>; + fn rsa_oaep_decrypt( + &self, + private_key_der: &[u8], + data: &[u8], + hash_alg: ShaAlgorithm, + label: Option<&[u8]>, + ) -> Result, CryptoError>; + + // ECDH operations + fn ecdh_derive_bits( + &self, + curve: EllipticCurve, + private_key_der: &[u8], + public_key_sec1: &[u8], + ) -> Result, CryptoError>; + + // X25519 operations + fn x25519_derive_bits( + &self, + private_key: &[u8], + public_key: &[u8], + ) -> Result, CryptoError>; + + // AES operations + fn aes_encrypt( + &self, + mode: AesMode, + key: &[u8], + iv: &[u8], + data: &[u8], + additional_data: Option<&[u8]>, + ) -> Result, CryptoError>; + fn aes_decrypt( + &self, + mode: AesMode, + key: &[u8], + iv: &[u8], + data: &[u8], + additional_data: Option<&[u8]>, + ) -> Result, CryptoError>; + + // AES-KW operations + fn aes_kw_wrap(&self, kek: &[u8], key: &[u8]) -> Result, CryptoError>; + fn aes_kw_unwrap(&self, kek: &[u8], wrapped_key: &[u8]) -> Result, CryptoError>; + + // KDF operations + fn hkdf_derive_key( + &self, + key: &[u8], + salt: &[u8], + info: &[u8], + length: usize, + hash_alg: ShaAlgorithm, + ) -> Result, CryptoError>; + fn pbkdf2_derive_key( + &self, + password: &[u8], + salt: &[u8], + iterations: u32, + length: usize, + hash_alg: ShaAlgorithm, + ) -> Result, CryptoError>; + + fn generate_aes_key(&self, length_bits: u16) -> Result, CryptoError>; + fn generate_hmac_key( + &self, + hash_alg: ShaAlgorithm, + length_bits: u16, + ) -> Result, CryptoError>; + fn generate_ec_key(&self, curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError>; // (private, public) + fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError>; + fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError>; + fn generate_rsa_key( + &self, + modulus_length: u32, + public_exponent: &[u8], + ) -> Result<(Vec, Vec), CryptoError>; +} + +pub trait HmacProvider { + fn update(&mut self, data: &[u8]); + fn finalize(self) -> Vec; +} + +#[derive(Debug)] +pub enum CryptoError { + InvalidKey, + InvalidData, + InvalidSignature, + InvalidLength, + SigningFailed, + VerificationFailed, + OperationFailed, + UnsupportedAlgorithm, + DerivationFailed, + EncryptionFailed, + DecryptionFailed, +} + +impl std::fmt::Display for CryptoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CryptoError::InvalidKey => write!(f, "Invalid key"), + CryptoError::InvalidData => write!(f, "Invalid data"), + CryptoError::InvalidSignature => write!(f, "Invalid signature"), + CryptoError::InvalidLength => write!(f, "Invalid length"), + CryptoError::SigningFailed => write!(f, "Signing failed"), + CryptoError::VerificationFailed => write!(f, "Verification failed"), + CryptoError::OperationFailed => write!(f, "Operation failed"), + CryptoError::UnsupportedAlgorithm => write!(f, "Unsupported algorithm"), + CryptoError::DerivationFailed => write!(f, "Derivation failed"), + CryptoError::EncryptionFailed => write!(f, "Encryption failed"), + CryptoError::DecryptionFailed => write!(f, "Decryption failed"), + } + } +} + +impl std::error::Error for CryptoError {} + +#[cfg(feature = "crypto-openssl")] +pub type DefaultProvider = openssl::OpenSslProvider; + +#[cfg(feature = "crypto-rust")] +pub type DefaultProvider = rust::RustCryptoProvider; + +#[cfg(feature = "crypto-ring")] +pub type DefaultProvider = ring::RingProvider; diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs new file mode 100644 index 0000000000..688bdca647 --- /dev/null +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -0,0 +1,467 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::provider::{ + AesGcmProvider, AesKwProvider, AesProvider, CryptoError, CryptoProvider, EcdhProvider, + EcdsaProvider, EdDsaProvider, HmacProvider, KdfProvider, RsaProvider, X25519Provider, +}; + +pub struct OpenSslProvider; + +// Stub implementations - would be replaced with actual OpenSSL bindings + +pub struct OpenSslMd5; + +pub struct OpenSslSha1; + +pub struct OpenSslSha256; + +pub struct OpenSslSha384; + +pub struct OpenSslSha512; + +pub struct OpenSslHmacSha1; + +pub struct OpenSslHmacSha256; + +pub struct OpenSslHmacSha384; + +pub struct OpenSslHmacSha512; + +pub struct OpenSslRsa; + +pub struct OpenSslEcdsa; + +pub struct OpenSslEcdh; + +pub struct OpenSslEd25519; + +pub struct OpenSslX25519; + +pub struct OpenSslAes; + +pub struct OpenSslAesGcm; + +pub struct OpenSslAesKw; + +pub struct OpenSslKdf; + +impl digest::Digest for OpenSslMd5 { + type OutputSize = digest::consts::U16; + fn new() -> Self { + OpenSslMd5 + } + fn update(&mut self, _data: impl AsRef<[u8]>) {} + fn chain_update(self, _data: impl AsRef<[u8]>) -> Self { + self + } + fn finalize(self) -> digest::Output { + [0u8; 16].into() + } + fn finalize_into(self, _out: &mut digest::Output) {} + fn finalize_reset(&mut self) -> digest::Output { + [0u8; 16].into() + } + fn reset(&mut self) {} + fn output_size() -> usize { + 16 + } + fn digest(_data: impl AsRef<[u8]>) -> digest::Output { + [0u8; 16].into() + } +} + +impl Clone for OpenSslMd5 { + fn clone(&self) -> Self { + OpenSslMd5 + } +} + +impl digest::Digest for OpenSslSha1 { + type OutputSize = digest::consts::U20; + fn new() -> Self { + OpenSslSha1 + } + fn update(&mut self, _data: impl AsRef<[u8]>) {} + fn chain_update(self, _data: impl AsRef<[u8]>) -> Self { + self + } + fn finalize(self) -> digest::Output { + [0u8; 20].into() + } + fn finalize_into(self, _out: &mut digest::Output) {} + fn finalize_reset(&mut self) -> digest::Output { + [0u8; 20].into() + } + fn reset(&mut self) {} + fn output_size() -> usize { + 20 + } + fn digest(_data: impl AsRef<[u8]>) -> digest::Output { + [0u8; 20].into() + } +} + +impl digest::Digest for OpenSslSha256 { + type OutputSize = digest::consts::U32; + fn new() -> Self { + OpenSslSha256 + } + fn update(&mut self, _data: impl AsRef<[u8]>) {} + fn chain_update(self, _data: impl AsRef<[u8]>) -> Self { + self + } + fn finalize(self) -> digest::Output { + [0u8; 32].into() + } + fn finalize_into(self, _out: &mut digest::Output) {} + fn finalize_reset(&mut self) -> digest::Output { + [0u8; 32].into() + } + fn reset(&mut self) {} + fn output_size() -> usize { + 32 + } + fn digest(_data: impl AsRef<[u8]>) -> digest::Output { + [0u8; 32].into() + } +} + +impl digest::Digest for OpenSslSha384 { + type OutputSize = digest::consts::U48; + fn new() -> Self { + OpenSslSha384 + } + fn update(&mut self, _data: impl AsRef<[u8]>) {} + fn chain_update(self, _data: impl AsRef<[u8]>) -> Self { + self + } + fn finalize(self) -> digest::Output { + [0u8; 48].into() + } + fn finalize_into(self, _out: &mut digest::Output) {} + fn finalize_reset(&mut self) -> digest::Output { + [0u8; 48].into() + } + fn reset(&mut self) {} + fn output_size() -> usize { + 48 + } + fn digest(_data: impl AsRef<[u8]>) -> digest::Output { + [0u8; 48].into() + } +} + +impl digest::Digest for OpenSslSha512 { + type OutputSize = digest::consts::U64; + fn new() -> Self { + OpenSslSha512 + } + fn update(&mut self, _data: impl AsRef<[u8]>) {} + fn chain_update(self, _data: impl AsRef<[u8]>) -> Self { + self + } + fn finalize(self) -> digest::Output { + [0u8; 64].into() + } + fn finalize_into(self, _out: &mut digest::Output) {} + fn finalize_reset(&mut self) -> digest::Output { + [0u8; 64].into() + } + fn reset(&mut self) {} + fn output_size() -> usize { + 64 + } + fn digest(_data: impl AsRef<[u8]>) -> digest::Output { + [0u8; 64].into() + } +} + +impl Clone for OpenSslSha1 { + fn clone(&self) -> Self { + OpenSslSha1 + } +} + +impl Clone for OpenSslSha256 { + fn clone(&self) -> Self { + OpenSslSha256 + } +} + +impl Clone for OpenSslSha384 { + fn clone(&self) -> Self { + OpenSslSha384 + } +} + +impl Clone for OpenSslSha512 { + fn clone(&self) -> Self { + OpenSslSha512 + } +} + +// Similar implementations for other hash algorithms would go here... + +impl HmacProvider for OpenSslHmacSha1 { + fn update(&mut self, _data: &[u8]) {} + fn finalize(self) -> Vec { + vec![0u8; 20] + } +} + +impl HmacProvider for OpenSslHmacSha256 { + fn update(&mut self, _data: &[u8]) {} + fn finalize(self) -> Vec { + vec![0u8; 32] + } +} + +impl HmacProvider for OpenSslHmacSha384 { + fn update(&mut self, _data: &[u8]) {} + fn finalize(self) -> Vec { + vec![0u8; 48] + } +} + +impl HmacProvider for OpenSslHmacSha512 { + fn update(&mut self, _data: &[u8]) {} + fn finalize(self) -> Vec { + vec![0u8; 64] + } +} + +impl RsaProvider for OpenSslRsa { + fn generate_key(&self, _modulus_length: usize) -> Result<(Vec, Vec), CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn sign(&self, _private_key: &[u8], _data: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn verify( + &self, + _public_key: &[u8], + _signature: &[u8], + _data: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn encrypt(&self, _public_key: &[u8], _data: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn decrypt(&self, _private_key: &[u8], _data: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } +} + +// Similar stub implementations for other providers... + +impl EcdsaProvider for OpenSslEcdsa { + fn generate_key(&self) -> Result<(Vec, Vec), CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn sign(&self, _: &[u8], _: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn verify(&self, _: &[u8], _: &[u8], _: &[u8]) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } +} + +impl EcdhProvider for OpenSslEcdh { + fn generate_key(&self) -> Result<(Vec, Vec), CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn derive_bits(&self, _: &[u8], _: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } +} + +impl EdDsaProvider for OpenSslEd25519 { + fn generate_key(&self) -> Result<(Vec, Vec), CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn sign(&self, _: &[u8], _: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn verify(&self, _: &[u8], _: &[u8], _: &[u8]) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } +} + +impl X25519Provider for OpenSslX25519 { + fn generate_key(&self) -> Result<(Vec, Vec), CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn derive_bits(&self, _: &[u8], _: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } +} + +impl AesProvider for OpenSslAes { + fn encrypt(&self, _: &[u8], _: &[u8], _: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn decrypt(&self, _: &[u8], _: &[u8], _: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } +} + +impl AesGcmProvider for OpenSslAesGcm { + fn encrypt( + &self, + _: &[u8], + _: &[u8], + _: &[u8], + _: &[u8], + ) -> Result<(Vec, Vec), CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn decrypt( + &self, + _: &[u8], + _: &[u8], + _: &[u8], + _: &[u8], + _: &[u8], + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } +} + +impl AesKwProvider for OpenSslAesKw { + fn wrap_key(&self, _: &[u8], _: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn unwrap_key(&self, _: &[u8], _: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } +} + +impl KdfProvider for OpenSslKdf { + fn derive_key(&self, _: &[u8], _: &[u8], _: &[u8], _: usize) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } +} + +impl CryptoProvider for OpenSslProvider { + type Md5 = OpenSslMd5; + type Sha1 = OpenSslSha1; + type Sha256 = OpenSslSha256; + type Sha384 = OpenSslSha384; + type Sha512 = OpenSslSha512; + + type HmacSha1 = OpenSslHmacSha1; + type HmacSha256 = OpenSslHmacSha256; + type HmacSha384 = OpenSslHmacSha384; + type HmacSha512 = OpenSslHmacSha512; + + type RsaPkcs1v15 = OpenSslRsa; + type RsaPss = OpenSslRsa; + type RsaOaep = OpenSslRsa; + + type EcdsaP256 = OpenSslEcdsa; + type EcdsaP384 = OpenSslEcdsa; + type EcdsaP521 = OpenSslEcdsa; + + type EcdhP256 = OpenSslEcdh; + type EcdhP384 = OpenSslEcdh; + type EcdhP521 = OpenSslEcdh; + + type Ed25519 = OpenSslEd25519; + type X25519 = OpenSslX25519; + + type AesCtr = OpenSslAes; + type AesCbc = OpenSslAes; + type AesGcm = OpenSslAesGcm; + type AesKw = OpenSslAesKw; + + type Hkdf = OpenSslKdf; + type Pbkdf2 = OpenSslKdf; + + fn md5(&self) -> Self::Md5 { + OpenSslMd5 + } + fn sha1(&self) -> Self::Sha1 { + OpenSslSha1 + } + fn sha256(&self) -> Self::Sha256 { + OpenSslSha256 + } + fn sha384(&self) -> Self::Sha384 { + OpenSslSha384 + } + fn sha512(&self) -> Self::Sha512 { + OpenSslSha512 + } + + fn hmac_sha1(&self, _key: &[u8]) -> Self::HmacSha1 { + OpenSslHmacSha1 + } + fn hmac_sha256(&self, _key: &[u8]) -> Self::HmacSha256 { + OpenSslHmacSha256 + } + fn hmac_sha384(&self, _key: &[u8]) -> Self::HmacSha384 { + OpenSslHmacSha384 + } + fn hmac_sha512(&self, _key: &[u8]) -> Self::HmacSha512 { + OpenSslHmacSha512 + } + + fn rsa_pkcs1v15(&self) -> Self::RsaPkcs1v15 { + OpenSslRsa + } + fn rsa_pss(&self) -> Self::RsaPss { + OpenSslRsa + } + fn rsa_oaep(&self) -> Self::RsaOaep { + OpenSslRsa + } + + fn ecdsa_p256(&self) -> Self::EcdsaP256 { + OpenSslEcdsa + } + fn ecdsa_p384(&self) -> Self::EcdsaP384 { + OpenSslEcdsa + } + fn ecdsa_p521(&self) -> Self::EcdsaP521 { + OpenSslEcdsa + } + + fn ecdh_p256(&self) -> Self::EcdhP256 { + OpenSslEcdh + } + fn ecdh_p384(&self) -> Self::EcdhP384 { + OpenSslEcdh + } + fn ecdh_p521(&self) -> Self::EcdhP521 { + OpenSslEcdh + } + + fn ed25519(&self) -> Self::Ed25519 { + OpenSslEd25519 + } + fn x25519(&self) -> Self::X25519 { + OpenSslX25519 + } + + fn aes_ctr(&self) -> Self::AesCtr { + OpenSslAes + } + fn aes_cbc(&self) -> Self::AesCbc { + OpenSslAes + } + fn aes_gcm(&self) -> Self::AesGcm { + OpenSslAesGcm + } + fn aes_kw(&self) -> Self::AesKw { + OpenSslAesKw + } + + fn hkdf(&self) -> Self::Hkdf { + OpenSslKdf + } + fn pbkdf2(&self) -> Self::Pbkdf2 { + OpenSslKdf + } +} diff --git a/modules/llrt_crypto/src/provider/ring.rs b/modules/llrt_crypto/src/provider/ring.rs new file mode 100644 index 0000000000..6f15d81633 --- /dev/null +++ b/modules/llrt_crypto/src/provider/ring.rs @@ -0,0 +1,302 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; +use crate::sha_hash::ShaAlgorithm; +use crate::subtle::EllipticCurve; +use md5::{Digest, Md5 as Md5Hasher}; +use ring::{digest, hmac}; + +pub struct RingProvider; + +pub enum RingDigestType { + Sha1(RingDigest), + Sha256(RingDigest), + Sha384(RingDigest), + Sha512(RingDigest), + Md5(RingMd5), +} + +pub enum RingHmacType { + Sha1(RingHmacSha1), + Sha256(RingHmacSha256), + Sha384(RingHmacSha384), + Sha512(RingHmacSha512), +} + +impl SimpleDigest for RingDigestType { + fn update(&mut self, data: &[u8]) { + match self { + RingDigestType::Sha1(d) => d.update(data), + RingDigestType::Sha256(d) => d.update(data), + RingDigestType::Sha384(d) => d.update(data), + RingDigestType::Sha512(d) => d.update(data), + RingDigestType::Md5(d) => d.update(data), + } + } + + fn finalize(self) -> Vec { + match self { + RingDigestType::Sha1(d) => d.finalize(), + RingDigestType::Sha256(d) => d.finalize(), + RingDigestType::Sha384(d) => d.finalize(), + RingDigestType::Sha512(d) => d.finalize(), + RingDigestType::Md5(d) => d.finalize(), + } + } +} + +impl HmacProvider for RingHmacType { + fn update(&mut self, data: &[u8]) { + match self { + RingHmacType::Sha1(h) => h.update(data), + RingHmacType::Sha256(h) => h.update(data), + RingHmacType::Sha384(h) => h.update(data), + RingHmacType::Sha512(h) => h.update(data), + } + } + + fn finalize(self) -> Vec { + match self { + RingHmacType::Sha1(h) => h.finalize(), + RingHmacType::Sha256(h) => h.finalize(), + RingHmacType::Sha384(h) => h.finalize(), + RingHmacType::Sha512(h) => h.finalize(), + } + } +} + +// Simple wrapper for Ring digest +pub struct RingDigest { + algorithm: &'static digest::Algorithm, + data: Vec, +} + +impl RingDigest { + fn new(algorithm: &'static digest::Algorithm) -> Self { + Self { + algorithm, + data: Vec::new(), + } + } +} + +impl SimpleDigest for RingDigest { + fn update(&mut self, data: &[u8]) { + self.data.extend_from_slice(data); + } + + fn finalize(self) -> Vec { + digest::digest(self.algorithm, &self.data).as_ref().to_vec() + } +} + +// MD5 wrapper +pub struct RingMd5(Md5Hasher); + +impl SimpleDigest for RingMd5 { + fn update(&mut self, data: &[u8]) { + Digest::update(&mut self.0, data); + } + + fn finalize(self) -> Vec { + self.0.finalize().to_vec() + } +} + +// HMAC implementations +pub struct RingHmacSha1(hmac::Context); +pub struct RingHmacSha256(hmac::Context); +pub struct RingHmacSha384(hmac::Context); +pub struct RingHmacSha512(hmac::Context); + +impl HmacProvider for RingHmacSha1 { + fn update(&mut self, data: &[u8]) { + self.0.update(data); + } + fn finalize(self) -> Vec { + self.0.sign().as_ref().to_vec() + } +} +impl HmacProvider for RingHmacSha256 { + fn update(&mut self, data: &[u8]) { + self.0.update(data); + } + fn finalize(self) -> Vec { + self.0.sign().as_ref().to_vec() + } +} +impl HmacProvider for RingHmacSha384 { + fn update(&mut self, data: &[u8]) { + self.0.update(data); + } + fn finalize(self) -> Vec { + self.0.sign().as_ref().to_vec() + } +} +impl HmacProvider for RingHmacSha512 { + fn update(&mut self, data: &[u8]) { + self.0.update(data); + } + fn finalize(self) -> Vec { + self.0.sign().as_ref().to_vec() + } +} + +impl CryptoProvider for RingProvider { + type Digest = RingDigestType; + type Hmac = RingHmacType; + + fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { + match algorithm { + ShaAlgorithm::MD5 => RingDigestType::Md5(RingMd5(Md5Hasher::new())), + ShaAlgorithm::SHA1 => { + RingDigestType::Sha1(RingDigest::new(&digest::SHA1_FOR_LEGACY_USE_ONLY)) + }, + ShaAlgorithm::SHA256 => RingDigestType::Sha256(RingDigest::new(&digest::SHA256)), + ShaAlgorithm::SHA384 => RingDigestType::Sha384(RingDigest::new(&digest::SHA384)), + ShaAlgorithm::SHA512 => RingDigestType::Sha512(RingDigest::new(&digest::SHA512)), + } + } + + fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + match algorithm { + ShaAlgorithm::MD5 => { + // Ring doesn't support HMAC-MD5, return error + panic!("HMAC-MD5 not supported by Ring provider"); + }, + ShaAlgorithm::SHA1 => RingHmacType::Sha1(RingHmacSha1(hmac::Context::with_key( + &hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, key), + ))), + ShaAlgorithm::SHA256 => RingHmacType::Sha256(RingHmacSha256(hmac::Context::with_key( + &hmac::Key::new(hmac::HMAC_SHA256, key), + ))), + ShaAlgorithm::SHA384 => RingHmacType::Sha384(RingHmacSha384(hmac::Context::with_key( + &hmac::Key::new(hmac::HMAC_SHA384, key), + ))), + ShaAlgorithm::SHA512 => RingHmacType::Sha512(RingHmacSha512(hmac::Context::with_key( + &hmac::Key::new(hmac::HMAC_SHA512, key), + ))), + } + } + + // Stub implementations for unsupported operations + fn ecdsa_sign( + &self, + _curve: EllipticCurve, + _private_key_der: &[u8], + _digest: &[u8], + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn ecdsa_verify( + &self, + _curve: EllipticCurve, + _public_key_sec1: &[u8], + _signature: &[u8], + _digest: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn ed25519_sign(&self, _private_key_der: &[u8], _data: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn ed25519_verify( + &self, + _public_key_bytes: &[u8], + _signature: &[u8], + _data: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pss_sign( + &self, + _private_key_der: &[u8], + _digest: &[u8], + _salt_length: usize, + _hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pss_verify( + &self, + _public_key_der: &[u8], + _signature: &[u8], + _digest: &[u8], + _salt_length: usize, + _hash_alg: ShaAlgorithm, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pkcs1v15_sign( + &self, + _private_key_der: &[u8], + _digest: &[u8], + _hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pkcs1v15_verify( + &self, + _public_key_der: &[u8], + _signature: &[u8], + _digest: &[u8], + _hash_alg: ShaAlgorithm, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn ecdh_derive_bits( + &self, + _curve: EllipticCurve, + _private_key_der: &[u8], + _public_key_sec1: &[u8], + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn x25519_derive_bits( + &self, + _private_key: &[u8], + _public_key: &[u8], + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn aes_kw_wrap(&self, _kek: &[u8], _key: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn aes_kw_unwrap(&self, _kek: &[u8], _wrapped_key: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn hkdf_derive_key( + &self, + _key: &[u8], + _salt: &[u8], + _info: &[u8], + _length: usize, + _hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn pbkdf2_derive_key( + &self, + _password: &[u8], + _salt: &[u8], + _iterations: u32, + _length: usize, + _hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } +} diff --git a/modules/llrt_crypto/src/provider/rust.rs b/modules/llrt_crypto/src/provider/rust.rs new file mode 100644 index 0000000000..414b0e2ff3 --- /dev/null +++ b/modules/llrt_crypto/src/provider/rust.rs @@ -0,0 +1,848 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::num::NonZeroU32; + +use aes::cipher::{ + block_padding::Pkcs7, BlockModeDecrypt, BlockModeEncrypt, KeyIvInit, StreamCipher, + StreamCipherError, +}; +use aes_gcm::{ + aead::{Aead, Payload}, + AesGcm, KeyInit, Nonce, +}; +use aes_kw::{KeyInit as KwKeyInit, KwAes128, KwAes192, KwAes256}; +use cbc::{Decryptor, Encryptor}; +use ctr::{cipher::Array, Ctr128BE, Ctr32BE, Ctr64BE}; +use ecdsa::signature::hazmat::PrehashVerifier; +use elliptic_curve::{ + consts::U12, + sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}, + AffinePoint, CurveArithmetic, FieldBytesSize, +}; +use once_cell::sync::Lazy; +use p256::{ + ecdsa::{Signature as P256Signature, VerifyingKey as P256VerifyingKey}, + SecretKey as P256SecretKey, +}; +use p384::{ + ecdsa::{Signature as P384Signature, VerifyingKey as P384VerifyingKey}, + SecretKey as P384SecretKey, +}; +use p521::{ + ecdsa::{Signature as P521Signature, VerifyingKey as P521VerifyingKey}, + SecretKey as P521SecretKey, +}; +use pkcs8::EncodePrivateKey; +use ring::signature::KeyPair; +use ring::{ + digest::{self, Context as DigestContext}, + hmac::{self, Context as HmacContext, Key as HmacKey}, + pbkdf2, + rand::{SecureRandom, SystemRandom}, + signature::{EcdsaKeyPair, Ed25519KeyPair, UnparsedPublicKey}, +}; +use rsa::pkcs1::EncodeRsaPrivateKey; +use rsa::pkcs1::EncodeRsaPublicKey; +use rsa::signature::hazmat::PrehashSigner; +use rsa::{ + pkcs1::DecodeRsaPrivateKey, + pkcs8::DecodePrivateKey, + pss::Pss, + sha2::{Sha256, Sha384, Sha512}, + Oaep, Pkcs1v15Sign, RsaPrivateKey, RsaPublicKey, +}; +use rsa::{pkcs1::DecodeRsaPublicKey, BoxedUint}; + +use crate::{ + get_random_bytes, + provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}, + random_byte_array, + sha_hash::ShaAlgorithm, + subtle::{AesGcmVariant, EllipticCurve}, +}; + +impl From for CryptoError { + fn from(_: aes::cipher::InvalidLength) -> Self { + CryptoError::InvalidLength + } +} + +impl From for CryptoError { + fn from(_: StreamCipherError) -> Self { + CryptoError::OperationFailed + } +} + +// Digest implementation +pub struct RingDigest { + context: DigestContext, +} + +impl SimpleDigest for RingDigest { + fn update(&mut self, data: &[u8]) { + self.context.update(data); + } + + fn finalize(self) -> Vec { + self.context.finish().as_ref().to_vec() + } +} + +// HMAC implementation +pub struct RingHmac { + context: HmacContext, +} + +impl HmacProvider for RingHmac { + fn update(&mut self, data: &[u8]) { + self.context.update(data); + } + + fn finalize(self) -> Vec { + self.context.sign().as_ref().to_vec() + } +} + +// Main Crypto Provider +#[derive(Default)] +pub struct RustCryptoProvider; + +pub static SYSTEM_RANDOM: Lazy = Lazy::new(SystemRandom::new); + +impl CryptoProvider for RustCryptoProvider { + type Digest = RingDigest; + type Hmac = RingHmac; + + fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { + RingDigest { + context: DigestContext::new(algorithm.digest_algorithm()), + } + } + + fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + let hmac_key = HmacKey::new(*algorithm.hmac_algorithm(), key); + RingHmac { + context: HmacContext::with_key(&hmac_key), + } + } + + fn ecdsa_sign( + &self, + curve: EllipticCurve, + private_key_der: &[u8], + digest: &[u8], + ) -> Result, CryptoError> { + let rng = SecureRandom::new(); + match curve { + EllipticCurve::P256 => { + let key_pair = EcdsaKeyPair::from_pkcs8( + &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, + private_key_der, + rng, + ) + .map_err(|_| CryptoError::InvalidKey)?; + + let signature = key_pair + .sign(rng, digest) + .map_err(|_| CryptoError::SigningFailed)?; + Ok(signature.as_ref().to_vec()) + }, + EllipticCurve::P384 => { + let key_pair = EcdsaKeyPair::from_pkcs8( + &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING, + private_key_der, + rng, + ) + .map_err(|_| CryptoError::InvalidKey)?; + + let signature = key_pair + .sign(rng, digest) + .map_err(|_| CryptoError::SigningFailed)?; + Ok(signature.as_ref().to_vec()) + }, + EllipticCurve::P521 => { + let secret_key = P521SecretKey::from_pkcs8_der(private_key_der) + .map_err(|_| CryptoError::InvalidKey)?; + let signing_key = p521::ecdsa::SigningKey::from(secret_key); + let signature: p521::ecdsa::Signature = signing_key + .sign_prehash(digest) + .map_err(|_| CryptoError::SigningFailed)?; + Ok(signature.to_bytes().to_vec()) + }, + } + } + + fn ecdsa_verify( + &self, + curve: EllipticCurve, + public_key_sec1: &[u8], + signature: &[u8], + digest: &[u8], + ) -> Result { + match curve { + EllipticCurve::P256 => { + let verifying_key = P256VerifyingKey::from_sec1_bytes(public_key_sec1) + .map_err(|_| CryptoError::InvalidKey)?; + let sig = P256Signature::from_slice(signature) + .map_err(|_| CryptoError::InvalidSignature)?; + Ok(verifying_key.verify_prehash(digest, &sig).is_ok()) + }, + EllipticCurve::P384 => { + let verifying_key = P384VerifyingKey::from_sec1_bytes(public_key_sec1) + .map_err(|_| CryptoError::InvalidKey)?; + let sig = P384Signature::from_slice(signature) + .map_err(|_| CryptoError::InvalidSignature)?; + Ok(verifying_key.verify_prehash(digest, &sig).is_ok()) + }, + EllipticCurve::P521 => { + let verifying_key = P521VerifyingKey::from_sec1_bytes(public_key_sec1) + .map_err(|_| CryptoError::InvalidKey)?; + let sig = P521Signature::from_slice(signature) + .map_err(|_| CryptoError::InvalidSignature)?; + Ok(verifying_key.verify_prehash(digest, &sig).is_ok()) + }, + } + } + + fn ed25519_sign(&self, private_key_der: &[u8], data: &[u8]) -> Result, CryptoError> { + let key_pair = + Ed25519KeyPair::from_pkcs8(private_key_der).map_err(|_| CryptoError::InvalidKey)?; + let signature = key_pair.sign(data); + Ok(signature.as_ref().to_vec()) + } + + fn ed25519_verify( + &self, + public_key_bytes: &[u8], + signature: &[u8], + data: &[u8], + ) -> Result { + let public_key = UnparsedPublicKey::new(&ring::signature::ED25519, public_key_bytes); + Ok(public_key.verify(data, signature).is_ok()) + } + + fn rsa_pss_sign( + &self, + private_key_der: &[u8], + digest: &[u8], + salt_length: usize, + hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + let mut rng = rand::rng(); + let private_key = + RsaPrivateKey::from_pkcs1_der(private_key_der).map_err(|_| CryptoError::InvalidKey)?; + + match hash_alg { + ShaAlgorithm::SHA256 => private_key + .sign_with_rng(&mut rng, Pss::new_with_salt::(salt_length), digest) + .map_err(|_| CryptoError::SigningFailed), + ShaAlgorithm::SHA384 => private_key + .sign_with_rng(&mut rng, Pss::new_with_salt::(salt_length), digest) + .map_err(|_| CryptoError::SigningFailed), + ShaAlgorithm::SHA512 => private_key + .sign_with_rng(&mut rng, Pss::new_with_salt::(salt_length), digest) + .map_err(|_| CryptoError::SigningFailed), + _ => Err(CryptoError::UnsupportedAlgorithm), + } + } + + fn rsa_pss_verify( + &self, + public_key_der: &[u8], + signature: &[u8], + digest: &[u8], + salt_length: usize, + hash_alg: ShaAlgorithm, + ) -> Result { + let public_key = + RsaPublicKey::from_pkcs1_der(public_key_der).map_err(|_| CryptoError::InvalidKey)?; + + match hash_alg { + ShaAlgorithm::SHA256 => Ok(public_key + .verify(Pss::new_with_salt::(salt_length), digest, signature) + .is_ok()), + ShaAlgorithm::SHA384 => Ok(public_key + .verify(Pss::new_with_salt::(salt_length), digest, signature) + .is_ok()), + ShaAlgorithm::SHA512 => Ok(public_key + .verify(Pss::new_with_salt::(salt_length), digest, signature) + .is_ok()), + _ => Err(CryptoError::UnsupportedAlgorithm), + } + } + + fn rsa_pkcs1v15_sign( + &self, + private_key_der: &[u8], + digest: &[u8], + hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + let mut rng = rand::rng(); + let private_key = + RsaPrivateKey::from_pkcs1_der(private_key_der).map_err(|_| CryptoError::InvalidKey)?; + + match hash_alg { + ShaAlgorithm::SHA256 => private_key + .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) + .map_err(|_| CryptoError::SigningFailed), + ShaAlgorithm::SHA384 => private_key + .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) + .map_err(|_| CryptoError::SigningFailed), + ShaAlgorithm::SHA512 => private_key + .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) + .map_err(|_| CryptoError::SigningFailed), + _ => Err(CryptoError::UnsupportedAlgorithm), + } + } + + fn rsa_pkcs1v15_verify( + &self, + public_key_der: &[u8], + signature: &[u8], + digest: &[u8], + hash_alg: ShaAlgorithm, + ) -> Result { + let public_key = + RsaPublicKey::from_pkcs1_der(public_key_der).map_err(|_| CryptoError::InvalidKey)?; + + match hash_alg { + ShaAlgorithm::SHA256 => Ok(public_key + .verify(Pkcs1v15Sign::new::(), digest, signature) + .is_ok()), + ShaAlgorithm::SHA384 => Ok(public_key + .verify(Pkcs1v15Sign::new::(), digest, signature) + .is_ok()), + ShaAlgorithm::SHA512 => Ok(public_key + .verify(Pkcs1v15Sign::new::(), digest, signature) + .is_ok()), + _ => Err(CryptoError::UnsupportedAlgorithm), + } + } + + fn rsa_oaep_encrypt( + &self, + public_key_der: &[u8], + data: &[u8], + hash_alg: ShaAlgorithm, + label: Option<&[u8]>, + ) -> Result, CryptoError> { + let mut rng = rand::rng(); + let public_key = + RsaPublicKey::from_pkcs1_der(public_key_der).map_err(|_| CryptoError::InvalidKey)?; + + let mut padding = match hash_alg { + ShaAlgorithm::SHA256 => Oaep::new::(), + ShaAlgorithm::SHA384 => Oaep::new::(), + ShaAlgorithm::SHA512 => Oaep::new::(), + _ => return Err(CryptoError::UnsupportedAlgorithm), + }; + + if let Some(label) = label { + if !label.is_empty() { + padding.label = Some(label.into()); + } + } + + public_key + .encrypt(&mut rng, padding, data) + .map_err(|_| CryptoError::EncryptionFailed) + } + + fn rsa_oaep_decrypt( + &self, + private_key_der: &[u8], + data: &[u8], + hash_alg: ShaAlgorithm, + label: Option<&[u8]>, + ) -> Result, CryptoError> { + let private_key = + RsaPrivateKey::from_pkcs1_der(private_key_der).map_err(|_| CryptoError::InvalidKey)?; + + let mut padding = match hash_alg { + ShaAlgorithm::SHA256 => Oaep::new::(), + ShaAlgorithm::SHA384 => Oaep::new::(), + ShaAlgorithm::SHA512 => Oaep::new::(), + _ => return Err(CryptoError::UnsupportedAlgorithm), + }; + + if let Some(label) = label { + if !label.is_empty() { + padding.label = Some(label.into()); + } + } + + private_key + .decrypt(padding, data) + .map_err(|_| CryptoError::DecryptionFailed) + } + + fn ecdh_derive_bits( + &self, + curve: EllipticCurve, + private_key_der: &[u8], + public_key_sec1: &[u8], + ) -> Result, CryptoError> { + match curve { + EllipticCurve::P256 => { + let secret_key = P256SecretKey::from_pkcs8_der(private_key_der) + .map_err(|_| CryptoError::InvalidKey)?; + let public_key = p256::PublicKey::from_sec1_bytes(public_key_sec1) + .map_err(|_| CryptoError::InvalidKey)?; + let shared_secret = p256::elliptic_curve::ecdh::diffie_hellman( + secret_key.to_nonzero_scalar(), + public_key.as_affine(), + ); + Ok(shared_secret.raw_secret_bytes().to_vec()) + }, + EllipticCurve::P384 => { + let secret_key = P384SecretKey::from_pkcs8_der(private_key_der) + .map_err(|_| CryptoError::InvalidKey)?; + let public_key = p384::PublicKey::from_sec1_bytes(public_key_sec1) + .map_err(|_| CryptoError::InvalidKey)?; + let shared_secret = p384::elliptic_curve::ecdh::diffie_hellman( + secret_key.to_nonzero_scalar(), + public_key.as_affine(), + ); + Ok(shared_secret.raw_secret_bytes().to_vec()) + }, + EllipticCurve::P521 => { + let secret_key = P521SecretKey::from_pkcs8_der(private_key_der) + .map_err(|_| CryptoError::InvalidKey)?; + let public_key = p521::PublicKey::from_sec1_bytes(public_key_sec1) + .map_err(|_| CryptoError::InvalidKey)?; + let shared_secret = p521::elliptic_curve::ecdh::diffie_hellman( + secret_key.to_nonzero_scalar(), + public_key.as_affine(), + ); + Ok(shared_secret.raw_secret_bytes().to_vec()) + }, + } + } + + fn x25519_derive_bits( + &self, + private_key: &[u8], + public_key: &[u8], + ) -> Result, CryptoError> { + let private_array: [u8; 32] = private_key + .try_into() + .map_err(|_| CryptoError::InvalidKey)?; + let public_array: [u8; 32] = public_key.try_into().map_err(|_| CryptoError::InvalidKey)?; + + let secret_key = x25519_dalek::StaticSecret::from(private_array); + let public_key = x25519_dalek::PublicKey::from(public_array); + let shared_secret = secret_key.diffie_hellman(&public_key); + + Ok(shared_secret.as_bytes().to_vec()) + } + + fn aes_encrypt( + &self, + mode: AesMode, + key: &[u8], + iv: &[u8], + data: &[u8], + additional_data: Option<&[u8]>, + ) -> Result, CryptoError> { + match mode { + AesMode::Cbc => match key.len() { + 16 => { + let encryptor = Encryptor::::new_from_slices(key, iv)?; + Ok(encryptor.encrypt_padded_vec::(data)) + }, + 24 => { + let encryptor = Encryptor::::new_from_slices(key, iv)?; + Ok(encryptor.encrypt_padded_vec::(data)) + }, + 32 => { + let encryptor = Encryptor::::new_from_slices(key, iv)?; + Ok(encryptor.encrypt_padded_vec::(data)) + }, + _ => Err(CryptoError::InvalidKey), + }, + AesMode::Ctr { counter_length } => { + let mut ciphertext = data.to_vec(); + match (key.len(), counter_length) { + (16, 32) => { + let mut cipher = Ctr32BE::::new_from_slices(key, iv)?; + cipher.try_apply_keystream(&mut ciphertext)?; + }, + (16, 64) => { + let mut cipher = Ctr64BE::::new_from_slices(key, iv)?; + cipher.try_apply_keystream(&mut ciphertext)?; + }, + (16, 128) => { + let mut cipher = Ctr128BE::::new_from_slices(key, iv)?; + cipher.try_apply_keystream(&mut ciphertext)?; + }, + (24, 32) => { + let mut cipher = Ctr32BE::::new_from_slices(key, iv)?; + cipher.try_apply_keystream(&mut ciphertext)?; + }, + (24, 64) => { + let mut cipher = Ctr64BE::::new_from_slices(key, iv)?; + cipher.try_apply_keystream(&mut ciphertext)?; + }, + (24, 128) => { + let mut cipher = Ctr128BE::::new_from_slices(key, iv)?; + cipher.try_apply_keystream(&mut ciphertext)?; + }, + (32, 32) => { + let mut cipher = Ctr32BE::::new_from_slices(key, iv)?; + cipher.try_apply_keystream(&mut ciphertext)?; + }, + (32, 64) => { + let mut cipher = Ctr64BE::::new_from_slices(key, iv)?; + cipher.try_apply_keystream(&mut ciphertext)?; + }, + (32, 128) => { + let mut cipher = Ctr128BE::::new_from_slices(key, iv)?; + cipher.try_apply_keystream(&mut ciphertext)?; + }, + _ => return Err(CryptoError::InvalidKey), + } + Ok(ciphertext) + }, + AesMode::Gcm { tag_length } => { + let variant = AesGcmVariant::new((key.len() * 8) as u16, tag_length, key)?; + let nonce: &Array<_, _> = + &Nonce::::try_from(iv).map_err(|_| CryptoError::InvalidData)?; + + let plaintext = Payload { + msg: data, + aad: additional_data.unwrap_or_default(), + }; + + match variant { + AesGcmVariant::Aes128Gcm96(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes192Gcm96(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes256Gcm96(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes128Gcm104(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes192Gcm104(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes256Gcm104(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes128Gcm112(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes192Gcm112(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes256Gcm112(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes128Gcm120(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes192Gcm120(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes256Gcm120(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes128Gcm128(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes192Gcm128(v) => v.encrypt(nonce, plaintext), + AesGcmVariant::Aes256Gcm128(v) => v.encrypt(nonce, plaintext), + } + .map_err(|_| CryptoError::EncryptionFailed) + }, + } + } + + fn aes_decrypt( + &self, + mode: AesMode, + key: &[u8], + iv: &[u8], + data: &[u8], + additional_data: Option<&[u8]>, + ) -> Result, CryptoError> { + match mode { + AesMode::Cbc => match key.len() { + 16 => { + let decryptor = Decryptor::::new_from_slices(key, iv)?; + decryptor + .decrypt_padded_vec::(data) + .map_err(|_| CryptoError::DecryptionFailed) + }, + 24 => { + let decryptor = Decryptor::::new_from_slices(key, iv)?; + decryptor + .decrypt_padded_vec::(data) + .map_err(|_| CryptoError::DecryptionFailed) + }, + 32 => { + let decryptor = Decryptor::::new_from_slices(key, iv)?; + decryptor + .decrypt_padded_vec::(data) + .map_err(|_| CryptoError::DecryptionFailed) + }, + _ => Err(CryptoError::InvalidKey), + }, + AesMode::Ctr { .. } => { + // CTR decryption is the same as encryption + self.aes_encrypt(mode, key, iv, data, additional_data) + }, + AesMode::Gcm { tag_length } => { + let variant = AesGcmVariant::new((key.len() * 8) as u16, tag_length, key)?; + let nonce: &Array<_, _> = + &Nonce::::try_from(iv).map_err(|_| CryptoError::InvalidData)?; + + let ciphertext = Payload { + msg: data, + aad: additional_data.unwrap_or_default(), + }; + + match variant { + AesGcmVariant::Aes128Gcm96(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes192Gcm96(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes256Gcm96(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes128Gcm104(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes192Gcm104(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes256Gcm104(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes128Gcm112(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes192Gcm112(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes256Gcm112(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes128Gcm120(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes192Gcm120(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes256Gcm120(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes128Gcm128(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes192Gcm128(v) => v.decrypt(nonce, ciphertext), + AesGcmVariant::Aes256Gcm128(v) => v.decrypt(nonce, ciphertext), + } + .map_err(|_| CryptoError::DecryptionFailed) + }, + } + } + + fn aes_kw_wrap(&self, kek: &[u8], key: &[u8]) -> Result, CryptoError> { + match kek.len() { + 16 => { + let kw = KwAes128::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey)?; + let mut buf = vec![0u8; key.len() + 8]; + let result = kw + .wrap_key(key, &mut buf) + .map_err(|_| CryptoError::OperationFailed)?; + Ok(result.to_vec()) + }, + 24 => { + let kw = KwAes192::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey)?; + let mut buf = vec![0u8; key.len() + 8]; + let result = kw + .wrap_key(key, &mut buf) + .map_err(|_| CryptoError::OperationFailed)?; + Ok(result.to_vec()) + }, + 32 => { + let kw = KwAes256::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey)?; + let mut buf = vec![0u8; key.len() + 8]; + let result = kw + .wrap_key(key, &mut buf) + .map_err(|_| CryptoError::OperationFailed)?; + Ok(result.to_vec()) + }, + _ => Err(CryptoError::InvalidKey), + } + } + + fn aes_kw_unwrap(&self, kek: &[u8], wrapped_key: &[u8]) -> Result, CryptoError> { + match kek.len() { + 16 => { + let kw = KwAes128::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey)?; + let mut buf = vec![0u8; wrapped_key.len()]; + let result = kw + .unwrap_key(wrapped_key, &mut buf) + .map_err(|_| CryptoError::OperationFailed)?; + Ok(result.to_vec()) + }, + 24 => { + let kw = KwAes192::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey)?; + let mut buf = vec![0u8; wrapped_key.len()]; + let result = kw + .unwrap_key(wrapped_key, &mut buf) + .map_err(|_| CryptoError::OperationFailed)?; + Ok(result.to_vec()) + }, + 32 => { + let kw = KwAes256::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey)?; + let mut buf = vec![0u8; wrapped_key.len()]; + let result = kw + .unwrap_key(wrapped_key, &mut buf) + .map_err(|_| CryptoError::OperationFailed)?; + Ok(result.to_vec()) + }, + _ => Err(CryptoError::InvalidKey), + } + } + + fn hkdf_derive_key( + &self, + key: &[u8], + salt: &[u8], + info: &[u8], + length: usize, + hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + use ring::hkdf; + + let algorithm = match hash_alg { + ShaAlgorithm::SHA1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY, + ShaAlgorithm::SHA256 => hkdf::HKDF_SHA256, + ShaAlgorithm::SHA384 => hkdf::HKDF_SHA384, + ShaAlgorithm::SHA512 => hkdf::HKDF_SHA512, + _ => return Err(CryptoError::UnsupportedAlgorithm), + }; + + let salt = hkdf::Salt::new(algorithm, salt); + let prk = salt.extract(key); + let info = &[info]; + let okm = prk + .expand(info, HkdfOutput(length)) + .map_err(|_| CryptoError::DerivationFailed)?; + + let mut out = vec![0u8; length]; + okm.fill(&mut out) + .map_err(|_| CryptoError::DerivationFailed)?; + Ok(out) + } + + fn pbkdf2_derive_key( + &self, + password: &[u8], + salt: &[u8], + iterations: u32, + length: usize, + hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + let algorithm = match hash_alg { + ShaAlgorithm::SHA1 => pbkdf2::PBKDF2_HMAC_SHA1, + ShaAlgorithm::SHA256 => pbkdf2::PBKDF2_HMAC_SHA256, + ShaAlgorithm::SHA384 => pbkdf2::PBKDF2_HMAC_SHA384, + ShaAlgorithm::SHA512 => pbkdf2::PBKDF2_HMAC_SHA512, + _ => return Err(CryptoError::UnsupportedAlgorithm), + }; + + let mut out = vec![0; length]; + let iterations = NonZeroU32::new(iterations).ok_or(CryptoError::InvalidData)?; + pbkdf2::derive(algorithm, iterations, salt, password, &mut out); + Ok(out) + } + + fn generate_aes_key(&self, length_bits: u16) -> Result, CryptoError> { + let length_bytes = (length_bits / 8) as usize; + if !matches!(length_bits, 128 | 192 | 256) { + return Err(CryptoError::InvalidLength); + } + Ok(random_byte_array(length_bytes)) + } + + fn generate_hmac_key( + &self, + hash_alg: ShaAlgorithm, + length_bits: u16, + ) -> Result, CryptoError> { + let length_bytes = if length_bits == 0 { + hash_alg.hmac_algorithm().digest_algorithm().block_len() + } else { + (length_bits / 8) as usize + }; + + if length_bytes > ring::digest::MAX_BLOCK_LEN { + return Err(CryptoError::InvalidLength); + } + + Ok(random_byte_array(length_bytes)) + } + + fn generate_ec_key(&self, curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { + let rng = &(*SYSTEM_RANDOM); + + match curve { + EllipticCurve::P256 => { + let pkcs8 = EcdsaKeyPair::generate_pkcs8( + &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, + rng, + ) + .map_err(|_| CryptoError::OperationFailed)?; + let private_key = pkcs8.as_ref().to_vec(); + let signing_key = P256SecretKey::from_pkcs8_der(&private_key) + .map_err(|_| CryptoError::OperationFailed)?; + let public_key = signing_key.public_key().to_sec1_bytes().to_vec(); + Ok((private_key, public_key)) + }, + EllipticCurve::P384 => { + let pkcs8 = EcdsaKeyPair::generate_pkcs8( + &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING, + rng, + ) + .map_err(|_| CryptoError::OperationFailed)?; + let private_key = pkcs8.as_ref().to_vec(); + let signing_key = P384SecretKey::from_pkcs8_der(&private_key) + .map_err(|_| CryptoError::OperationFailed)?; + let public_key = signing_key.public_key().to_sec1_bytes().to_vec(); + Ok((private_key, public_key)) + }, + EllipticCurve::P521 => { + let mut rng = rand::rng(); + let key = P521SecretKey::random(&mut rng); + let pkcs8 = key + .to_pkcs8_der() + .map_err(|_| CryptoError::OperationFailed)?; + let private_key = pkcs8.as_bytes().to_vec(); + let public_key = key.public_key().to_sec1_bytes().to_vec(); + Ok((private_key, public_key)) + }, + } + } + + fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { + let rng = &(*SYSTEM_RANDOM); + let pkcs8 = + Ed25519KeyPair::generate_pkcs8(rng).map_err(|_| CryptoError::OperationFailed)?; + let private_key = pkcs8.as_ref().to_vec(); + let key_pair = + Ed25519KeyPair::from_pkcs8(&private_key).map_err(|_| CryptoError::OperationFailed)?; + let public_key = key_pair.public_key().as_ref().to_vec(); + Ok((private_key, public_key)) + } + + fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { + let secret_key = x25519_dalek::StaticSecret::random(); + let private_key = secret_key.as_bytes().to_vec(); + let public_key = x25519_dalek::PublicKey::from(&secret_key) + .as_bytes() + .to_vec(); + Ok((private_key, public_key)) + } + + fn generate_rsa_key( + &self, + modulus_length: u32, + public_exponent: &[u8], + ) -> Result<(Vec, Vec), CryptoError> { + let exponent: u64 = match public_exponent { + [0x01, 0x00, 0x01] => 65537, + [0x03] => 3, + bytes + if bytes.ends_with(&[0x03]) && bytes[..bytes.len() - 1].iter().all(|&b| b == 0) => + { + 3 + }, + _ => return Err(CryptoError::InvalidData), + }; + + let exp = BoxedUint::from(exponent); + let mut rng = rand::rng(); + let rsa_private_key = RsaPrivateKey::new_with_exp(&mut rng, modulus_length as usize, exp) + .map_err(|_| CryptoError::OperationFailed)?; + + let public_key = rsa_private_key + .to_public_key() + .to_pkcs1_der() + .map_err(|_| CryptoError::OperationFailed)?; + let private_key = rsa_private_key + .to_pkcs1_der() + .map_err(|_| CryptoError::OperationFailed)?; + + Ok(( + private_key.as_bytes().to_vec(), + public_key.as_bytes().to_vec(), + )) + } +} + +// Helper struct for HKDF output length +struct HkdfOutput(usize); + +impl ring::hkdf::KeyType for HkdfOutput { + fn len(&self) -> usize { + self.0 + } +} diff --git a/modules/llrt_crypto/src/sha_hash.rs b/modules/llrt_crypto/src/sha_hash.rs index 56a34939b2..5763cf16b0 100644 --- a/modules/llrt_crypto/src/sha_hash.rs +++ b/modules/llrt_crypto/src/sha_hash.rs @@ -1,23 +1,27 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 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 ring::{digest, hmac}; use rquickjs::{function::Opt, prelude::This, Class, Ctx, Result, Value}; -use super::encoded_bytes; +use crate::provider::{CryptoProvider, SimpleDigest, HmacProvider}; +use super::{encoded_bytes, CRYPTO_PROVIDER}; #[rquickjs::class] #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] pub struct Hmac { #[qjs(skip_trace)] - context: HmacContext, + algorithm: ShaAlgorithm, + #[qjs(skip_trace)] + key: Vec, + #[qjs(skip_trace)] + data: Vec, } #[rquickjs::methods] @@ -25,20 +29,23 @@ impl Hmac { #[qjs(skip)] pub fn new<'js>(ctx: Ctx<'js>, algorithm: String, key_value: ObjectBytes<'js>) -> Result { let algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; - let algorithm = *algorithm.hmac_algorithm(); + let key = key_value.as_bytes(&ctx)?.to_vec(); Ok(Self { - context: HmacContext::with_key(&hmac::Key::new(algorithm, key_value.as_bytes(&ctx)?)), + algorithm, + key, + data: Vec::new(), }) } fn digest<'js>(&self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let signature = self.context.clone().sign(); - let bytes: &[u8] = signature.as_ref(); + let mut hmac = CRYPTO_PROVIDER.hmac(self.algorithm, &self.key); + hmac.update(&self.data); + let result = hmac.finalize(); match encoding.into_inner() { - Some(encoding) => encoded_bytes(ctx, bytes, &encoding), - None => bytes_to_typed_array(ctx, bytes), + Some(encoding) => encoded_bytes(ctx, &result, &encoding), + None => bytes_to_typed_array(ctx, &result), } } @@ -48,25 +55,18 @@ impl Hmac { bytes: ObjectBytes<'js>, ) -> Result> { let bytes = bytes.as_bytes(&ctx)?; - this.0.borrow_mut().context.update(bytes); - + this.0.borrow_mut().data.extend_from_slice(bytes); Ok(this.0) } } -impl Clone for Hmac { - fn clone(&self) -> Self { - Self { - context: self.context.clone(), - } - } -} - #[rquickjs::class] #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] pub struct Hash { #[qjs(skip_trace)] - context: DigestContext, + algorithm: ShaAlgorithm, + #[qjs(skip_trace)] + data: Vec, } #[rquickjs::methods] @@ -74,21 +74,22 @@ impl Hash { #[qjs(skip)] pub fn new(ctx: Ctx<'_>, algorithm: String) -> Result { let algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; - let algorithm = algorithm.digest_algorithm(); Ok(Self { - context: DigestContext::new(algorithm), + algorithm, + data: Vec::new(), }) } #[qjs(rename = "digest")] fn hash_digest<'js>(&self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let digest = self.context.clone().finish(); - let bytes: &[u8] = digest.as_ref(); + let mut digest_hasher = CRYPTO_PROVIDER.digest(self.algorithm); + digest_hasher.update(&self.data); + let digest = digest_hasher.finalize(); match encoding.0 { - Some(encoding) => encoded_bytes(ctx, bytes, &encoding), - None => bytes_to_typed_array(ctx, bytes), + Some(encoding) => encoded_bytes(ctx, &digest, &encoding), + None => bytes_to_typed_array(ctx, &digest), } } @@ -99,32 +100,37 @@ impl Hash { bytes: ObjectBytes<'js>, ) -> Result> { let bytes = bytes.as_bytes(&ctx)?; - this.0.borrow_mut().context.update(bytes); + this.0.borrow_mut().data.extend_from_slice(bytes); Ok(this.0) } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub enum ShaAlgorithm { + MD5, SHA1, SHA256, SHA384, SHA512, } -iterable_enum!(ShaAlgorithm, SHA1, SHA256, SHA384, SHA512); +iterable_enum!(ShaAlgorithm, MD5, SHA1, SHA256, SHA384, SHA512); impl ShaAlgorithm { pub fn class_name(&self) -> &'static str { match self { + ShaAlgorithm::MD5 => "Md5", ShaAlgorithm::SHA1 => "Sha1", ShaAlgorithm::SHA256 => "Sha256", ShaAlgorithm::SHA384 => "Sha384", ShaAlgorithm::SHA512 => "Sha512", } } + + // Keep Ring compatibility for subtle crypto pub fn hmac_algorithm(&self) -> &'static hmac::Algorithm { match self { + ShaAlgorithm::MD5 => panic!("MD5 HMAC not supported by Ring"), ShaAlgorithm::SHA1 => &hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, ShaAlgorithm::SHA256 => &hmac::HMAC_SHA256, ShaAlgorithm::SHA384 => &hmac::HMAC_SHA384, @@ -134,6 +140,7 @@ impl ShaAlgorithm { pub fn digest_algorithm(&self) -> &'static digest::Algorithm { match self { + ShaAlgorithm::MD5 => panic!("MD5 digest not supported by Ring"), ShaAlgorithm::SHA1 => &digest::SHA1_FOR_LEGACY_USE_ONLY, ShaAlgorithm::SHA256 => &digest::SHA256, ShaAlgorithm::SHA384 => &digest::SHA384, @@ -143,6 +150,7 @@ impl ShaAlgorithm { pub fn as_str(&self) -> &'static str { match self { + ShaAlgorithm::MD5 => "MD5", ShaAlgorithm::SHA1 => "SHA-1", ShaAlgorithm::SHA256 => "SHA-256", ShaAlgorithm::SHA384 => "SHA-384", @@ -152,6 +160,7 @@ impl ShaAlgorithm { pub fn as_numeric_str(&self) -> &'static str { match self { + ShaAlgorithm::MD5 => "md5", ShaAlgorithm::SHA1 => "1", ShaAlgorithm::SHA256 => "256", ShaAlgorithm::SHA384 => "384", @@ -182,53 +191,3 @@ impl AsRef for ShaAlgorithm { self.as_str() } } - -#[rquickjs::class] -#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] -pub struct ShaHash { - #[qjs(skip_trace)] - secret: Option>, - #[qjs(skip_trace)] - bytes: Vec, - #[qjs(skip_trace)] - algorithm: ShaAlgorithm, -} - -#[rquickjs::methods] -impl ShaHash { - #[qjs(skip)] - pub fn new(ctx: Ctx, algorithm: ShaAlgorithm, secret: Opt>) -> Result { - let secret = secret.0.map(|bytes| bytes.into_bytes(&ctx)).transpose()?; - - Ok(ShaHash { - secret, - bytes: Vec::new(), - algorithm, - }) - } - - #[qjs(rename = "digest")] - fn sha_digest<'js>(&self, ctx: Ctx<'js>) -> Result> { - if let Some(secret) = &self.secret { - let key_value = secret; - let key = hmac::Key::new(*self.algorithm.hmac_algorithm(), key_value); - - return bytes_to_typed_array(ctx, hmac::sign(&key, &self.bytes).as_ref()); - } - - bytes_to_typed_array( - ctx, - digest::digest(self.algorithm.digest_algorithm(), &self.bytes).as_ref(), - ) - } - - #[qjs(rename = "update")] - fn sha_update<'js>( - this: This>, - ctx: Ctx<'js>, - bytes: ObjectBytes<'js>, - ) -> Result> { - this.0.borrow_mut().bytes = bytes.try_into().or_throw(&ctx)?; - Ok(this.0) - } -} diff --git a/modules/llrt_crypto/src/subtle/derive.rs b/modules/llrt_crypto/src/subtle/derive.rs index 43aa979758..21eadeb1f7 100644 --- a/modules/llrt_crypto/src/subtle/derive.rs +++ b/modules/llrt_crypto/src/subtle/derive.rs @@ -1,15 +1,12 @@ // 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 crate::{ - sha_hash::ShaAlgorithm, - subtle::{CryptoKey, EllipticCurve}, + subtle::CryptoKey, + provider::CryptoProvider, + CRYPTO_PROVIDER, }; use super::{ @@ -21,14 +18,6 @@ use super::{ }, }; -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, @@ -48,7 +37,7 @@ fn derive_bits( base_key: &CryptoKey, length: u32, ) -> Result> { - let bits = match algorithm { + match algorithm { DeriveAlgorithm::Ecdh { curve, public_key } => { if let KeyAlgorithm::Ec { curve: base_key_curve, @@ -60,41 +49,7 @@ fn derive_bits( && matches!(algorithm, EcAlgorithm::Ecdh) { let handle = &base_key.handle; - return Ok(match curve { - EllipticCurve::P256 => { - let secret_key = - p256::SecretKey::from_pkcs8_der(handle).or_throw(ctx)?; - let public_key = - p256::PublicKey::from_sec1_bytes(public_key).or_throw(ctx)?; - 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(handle).or_throw(ctx)?; - let public_key = - p384::PublicKey::from_sec1_bytes(public_key).or_throw(ctx)?; - 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() - }, - EllipticCurve::P521 => { - let secret_key = - p521::SecretKey::from_pkcs8_der(handle).or_throw(ctx)?; - let public_key = - p521::PublicKey::from_sec1_bytes(public_key).or_throw(ctx)?; - let shared_secret = p521::elliptic_curve::ecdh::diffie_hellman( - secret_key.to_nonzero_scalar(), - public_key.as_affine(), - ); - shared_secret.raw_secret_bytes().to_vec() - }, - }); + return CRYPTO_PROVIDER.ecdh_derive_bits(*curve, handle, public_key).or_throw(ctx); } return Err(Exception::throw_message( ctx, @@ -108,34 +63,14 @@ fn derive_bits( return algorithm_mismatch_error(ctx, "X25519"); } - let private_array: [u8; 32] = base_key.handle.as_ref().try_into().or_throw(ctx)?; - let public_array: [u8; 32] = public_key.as_ref().try_into().or_throw(ctx)?; - let secret_key = x25519_dalek::StaticSecret::from(private_array); - let public_key = x25519_dalek::PublicKey::from(public_array); - let shared_secret = secret_key.diffie_hellman(&public_key); - shared_secret.as_bytes().to_vec() + return CRYPTO_PROVIDER.x25519_derive_bits(&base_key.handle, public_key).or_throw(ctx); }, DeriveAlgorithm::Derive(KeyDerivation::Hkdf { hash, salt, info }) => { if !matches!(base_key.algorithm, KeyAlgorithm::HkdfImport) { return algorithm_mismatch_error(ctx, "HKDF"); } - 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.handle); 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 + return CRYPTO_PROVIDER.hkdf_derive_key(&base_key.handle, salt, info, out_length, *hash).or_throw(ctx); }, DeriveAlgorithm::Derive(KeyDerivation::Pbkdf2 { hash, @@ -145,28 +80,10 @@ fn derive_bits( if !matches!(base_key.algorithm, KeyAlgorithm::Pbkdf2Import) { return algorithm_mismatch_error(ctx, "PBKDF2"); } - 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.handle, - &mut out, - ); - - out + let out_length = (length / 8).try_into().or_throw(ctx)?; + return CRYPTO_PROVIDER.pbkdf2_derive_key(&base_key.handle, salt, *iterations, out_length, *hash).or_throw(ctx); }, - }; - Ok(bits) + } } pub async fn subtle_derive_key<'js>( diff --git a/modules/llrt_crypto/src/subtle/digest.rs b/modules/llrt_crypto/src/subtle/digest.rs index a358e91815..c8fef4d6c7 100644 --- a/modules/llrt_crypto/src/subtle/digest.rs +++ b/modules/llrt_crypto/src/subtle/digest.rs @@ -1,10 +1,9 @@ // 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; +use crate::{sha_hash::ShaAlgorithm, provider::{CryptoProvider, SimpleDigest}, CRYPTO_PROVIDER}; pub async fn subtle_digest<'js>( ctx: Ctx<'js>, @@ -23,10 +22,7 @@ pub async fn subtle_digest<'js>( } pub fn digest(sha_algorithm: &ShaAlgorithm, data: &[u8]) -> Vec { - let hash = sha_algorithm.digest_algorithm(); - let mut context = Context::new(hash); - context.update(data); - let digest = context.finish(); - - digest.as_ref().to_vec() + let mut hasher = CRYPTO_PROVIDER.digest(*sha_algorithm); + hasher.update(data); + hasher.finalize() } diff --git a/modules/llrt_crypto/src/subtle/encryption.rs b/modules/llrt_crypto/src/subtle/encryption.rs index 1d1fb99ad5..6e63822354 100644 --- a/modules/llrt_crypto/src/subtle/encryption.rs +++ b/modules/llrt_crypto/src/subtle/encryption.rs @@ -1,14 +1,12 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use aes::cipher::typenum::U12; -use aes_gcm::Nonce; -use aes_kw::{KeyInit, KwAes128, KwAes192, KwAes256}; use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; use rquickjs::{ArrayBuffer, Class, Ctx, Exception, Result}; -use rsa::{ - pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey}, - Oaep, RsaPrivateKey, RsaPublicKey, + +use crate::{ + provider::{AesMode, CryptoProvider}, + CRYPTO_PROVIDER, }; use crate::sha_hash::ShaAlgorithm; @@ -20,9 +18,8 @@ pub(super) enum OaepPadding { } use super::{ - algorithm_mismatch_error, encryption_algorithm::EncryptionAlgorithm, extract_aes_length, - key_algorithm::KeyAlgorithm, AesCbcDecVariant, AesCbcEncVariant, AesCtrVariant, AesGcmVariant, - CryptoKey, EncryptionMode, + algorithm_mismatch_error, encryption_algorithm::EncryptionAlgorithm, + key_algorithm::KeyAlgorithm, validate_aes_length, CryptoKey, EncryptionMode, }; pub async fn subtle_decrypt<'js>( @@ -80,29 +77,45 @@ pub fn encrypt_decrypt( let handle = key.handle.as_ref(); let bytes = match algorithm { EncryptionAlgorithm::AesCbc { iv } => { - let length = extract_aes_length(ctx, key, "AES-CBC")?; + validate_aes_length(ctx, key, handle, "AES-CBC")?; + match operation { - EncryptionOperation::Encrypt => { - let variant = AesCbcEncVariant::new(length, handle, iv).or_throw(ctx)?; - variant.encrypt(data) - }, - EncryptionOperation::Decrypt => { - let variant = AesCbcDecVariant::new(length, handle, iv).or_throw(ctx)?; - variant.decrypt(data).or_throw(ctx)? - }, + EncryptionOperation::Encrypt => CRYPTO_PROVIDER + .aes_encrypt(AesMode::Cbc, handle, iv, data, None) + .or_throw(ctx)?, + EncryptionOperation::Decrypt => CRYPTO_PROVIDER + .aes_decrypt(AesMode::Cbc, handle, iv, data, None) + .or_throw(ctx)?, } }, EncryptionAlgorithm::AesCtr { counter, length: encryption_length, } => { - let length = extract_aes_length(ctx, key, "AES-CTR")?; - - let mut variant = - AesCtrVariant::new(length, *encryption_length, handle, counter).or_throw(ctx)?; + validate_aes_length(ctx, key, handle, "AES-CTR")?; match operation { - EncryptionOperation::Encrypt => variant.encrypt(data).or_throw(ctx)?, - EncryptionOperation::Decrypt => variant.decrypt(data).or_throw(ctx)?, + EncryptionOperation::Encrypt => CRYPTO_PROVIDER + .aes_encrypt( + AesMode::Ctr { + counter_length: *encryption_length, + }, + handle, + counter, + data, + None, + ) + .or_throw(ctx)?, + EncryptionOperation::Decrypt => CRYPTO_PROVIDER + .aes_decrypt( + AesMode::Ctr { + counter_length: *encryption_length, + }, + handle, + counter, + data, + None, + ) + .or_throw(ctx)?, } }, EncryptionAlgorithm::AesGcm { @@ -110,125 +123,73 @@ pub fn encrypt_decrypt( tag_length, additional_data, } => { - let length = extract_aes_length(ctx, key, "AES-GCM")?; - - let nonce: &ctr::cipher::Array<_, _> = - &Nonce::::try_from(iv.as_ref()).or_throw(ctx)?; + validate_aes_length(ctx, key, handle, "AES-GCM")?; + let aad = additional_data.as_deref(); - let variant = AesGcmVariant::new(length, *tag_length, handle).or_throw(ctx)?; match operation { - EncryptionOperation::Encrypt => variant - .encrypt(nonce, data, additional_data.as_deref()) - .or_throw(ctx)?, - EncryptionOperation::Decrypt => variant - .decrypt(nonce, data, additional_data.as_deref()) + EncryptionOperation::Encrypt => CRYPTO_PROVIDER + .aes_encrypt( + AesMode::Gcm { + tag_length: *tag_length, + }, + handle, + iv, + data, + aad, + ) .or_throw(ctx)?, + EncryptionOperation::Decrypt => { + if data.len() < 16 { + return Err(Exception::throw_message(ctx, "Invalid ciphertext length")); + } + let (ciphertext, tag) = data.split_at(data.len() - 16); + CRYPTO_PROVIDER + .aes_decrypt( + AesMode::Gcm { + tag_length: *tag_length, + }, + handle, + iv, + ciphertext, + aad, + ) + .or_throw(ctx)? + }, } }, EncryptionAlgorithm::AesKw => { - let padding = match mode { + let _padding = match mode { EncryptionMode::Encryption => { return Err(Exception::throw_message( ctx, "AES-KW can only be used for wrapping keys", )); }, - EncryptionMode::Wrapping(padding) => padding, - }; - - let is_encrypt = matches!(operation, EncryptionOperation::Encrypt); - - //Only create new vec if padding is needed, otherwise use original slice - let data = if !data.len().is_multiple_of(8) && is_encrypt && padding != 0 { - let padding_size = (8 - (data.len() % 8)) % 8; - let mut padded = Vec::with_capacity(data.len() + padding_size); - padded.extend_from_slice(data); - padded.resize(data.len() + padding_size, padding); - std::borrow::Cow::Owned(padded) - } else { - std::borrow::Cow::Borrowed(data) + EncryptionMode::Wrapping(_padding) => _padding, }; - match handle.len() { - 16 => { - let kek = KwAes128::new(handle.try_into().or_throw(ctx)?); - match operation { - EncryptionOperation::Encrypt => { - let mut buf = vec![0u8; data.len() + 8]; - let result = kek.wrap_key(&data, &mut buf).or_throw(ctx)?; - rquickjs::Result::Ok(result.to_vec()) - }, - EncryptionOperation::Decrypt => { - let mut buf = vec![0u8; data.len()]; - let result = kek.unwrap_key(&data, &mut buf).or_throw(ctx)?; - Ok(result.to_vec()) - }, - } - }, - 24 => { - let kek = KwAes192::new(handle.try_into().or_throw(ctx)?); - match operation { - EncryptionOperation::Encrypt => { - let mut buf = vec![0u8; data.len() + 8]; - let result = kek.wrap_key(&data, &mut buf).or_throw(ctx)?; - Ok(result.to_vec()) - }, - EncryptionOperation::Decrypt => { - let mut buf = vec![0u8; data.len()]; - let result = kek.unwrap_key(&data, &mut buf).or_throw(ctx)?; - Ok(result.to_vec()) - }, - } + match operation { + EncryptionOperation::Encrypt => { + CRYPTO_PROVIDER.aes_kw_wrap(handle, data).or_throw(ctx)? }, - 32 => { - let kek = KwAes256::new(handle.try_into().or_throw(ctx)?); - match operation { - EncryptionOperation::Encrypt => { - let mut buf = vec![0u8; data.len() + 8]; - let result = kek.wrap_key(&data, &mut buf).or_throw(ctx)?; - Ok(result.to_vec()) - }, - EncryptionOperation::Decrypt => { - let mut buf = vec![0u8; data.len()]; - let result = kek.unwrap_key(&data, &mut buf).or_throw(ctx)?; - Ok(result.to_vec()) - }, - } + EncryptionOperation::Decrypt => { + CRYPTO_PROVIDER.aes_kw_unwrap(handle, data).or_throw(ctx)? }, - _ => return Err(Exception::throw_message(ctx, "Invalid AES-KW key length")), } - .or_throw(ctx)? }, EncryptionAlgorithm::RsaOaep { label } => { let hash = match &key.algorithm { KeyAlgorithm::Rsa { hash, .. } => hash, _ => return algorithm_mismatch_error(ctx, "RSA-OAEP"), }; - let padding = rsa_oaep_padding(ctx, label, hash)?; + match operation { - EncryptionOperation::Encrypt => { - let public_key = RsaPublicKey::from_pkcs1_der(handle).or_throw(ctx)?; - let mut rng = rand::rng(); - match padding { - OaepPadding::Sha256(p) => { - public_key.encrypt(&mut rng, p, data).or_throw(ctx)? - }, - OaepPadding::Sha384(p) => { - public_key.encrypt(&mut rng, p, data).or_throw(ctx)? - }, - OaepPadding::Sha512(p) => { - public_key.encrypt(&mut rng, p, data).or_throw(ctx)? - }, - } - }, - EncryptionOperation::Decrypt => { - let private_key = RsaPrivateKey::from_pkcs1_der(handle).or_throw(ctx)?; - match padding { - OaepPadding::Sha256(p) => private_key.decrypt(p, data).or_throw(ctx)?, - OaepPadding::Sha384(p) => private_key.decrypt(p, data).or_throw(ctx)?, - OaepPadding::Sha512(p) => private_key.decrypt(p, data).or_throw(ctx)?, - } - }, + EncryptionOperation::Encrypt => CRYPTO_PROVIDER + .rsa_oaep_encrypt(handle, data, *hash, label.as_deref()) + .or_throw(ctx)?, + EncryptionOperation::Decrypt => CRYPTO_PROVIDER + .rsa_oaep_decrypt(handle, data, *hash, label.as_deref()) + .or_throw(ctx)?, } }, }; @@ -239,7 +200,7 @@ pub fn rsa_oaep_padding( ctx: &Ctx<'_>, label: &Option>, hash: &ShaAlgorithm, -) -> Result { +) -> Result { let mut padding = match hash { ShaAlgorithm::SHA1 => { return Err(Exception::throw_message( @@ -247,17 +208,13 @@ pub fn rsa_oaep_padding( "SHA-1 is not supported for RSA-OAEP", )); }, - ShaAlgorithm::SHA256 => OaepPadding::Sha256(Oaep::new()), - ShaAlgorithm::SHA384 => OaepPadding::Sha384(Oaep::new()), - ShaAlgorithm::SHA512 => OaepPadding::Sha512(Oaep::new()), + ShaAlgorithm::SHA256 => Oaep::new::(), + ShaAlgorithm::SHA384 => Oaep::new::(), + ShaAlgorithm::SHA512 => Oaep::new::(), }; if let Some(label) = label { if !label.is_empty() { - match &mut padding { - OaepPadding::Sha256(p) => p.label = Some(label.to_owned()), - OaepPadding::Sha384(p) => p.label = Some(label.to_owned()), - OaepPadding::Sha512(p) => p.label = Some(label.to_owned()), - } + padding.label = Some(label.to_owned()); } } diff --git a/modules/llrt_crypto/src/subtle/generate_key.rs b/modules/llrt_crypto/src/subtle/generate_key.rs index 1a8abc744f..d3d971a552 100644 --- a/modules/llrt_crypto/src/subtle/generate_key.rs +++ b/modules/llrt_crypto/src/subtle/generate_key.rs @@ -12,7 +12,9 @@ use rsa::{ BoxedUint, RsaPrivateKey, }; -use crate::{sha_hash::ShaAlgorithm, CryptoKey, SYSTEM_RANDOM}; +use crate::{provider::CryptoProvider, CRYPTO_PROVIDER}; + +use crate::{sha_hash::ShaAlgorithm, subtle::CryptoKey}; use super::{ algorithm_not_supported_error, @@ -85,118 +87,158 @@ pub async fn subtle_generate_key<'js>( } fn generate_key(ctx: &Ctx<'_>, algorithm: &KeyAlgorithm) -> Result<(Vec, Vec)> { - let private_key; - let public_or_secret_key; match algorithm { KeyAlgorithm::Aes { length } => { - let length = *length as usize; - - match length { - 128 | 192 | 256 => (), - _ => { - return Err(Exception::throw_message( - ctx, - "AES key length must be 128, 192, or 256 bits", - )) - }, - } - - public_or_secret_key = generate_symmetric_key(ctx, length / 8)?; - private_key = vec![]; + // Default to AES-256 + let key = CRYPTO_PROVIDER.generate_aes_key(*length).map_err(|e| { + Exception::throw_message(ctx, &format!("AES key generation failed: {}", e)) + })?; + Ok((vec![], key)) }, KeyAlgorithm::Hmac { hash, length } => { - let length = get_hash_length(ctx, hash, *length)?; - public_or_secret_key = generate_symmetric_key(ctx, length)?; - private_key = vec![]; + let key = CRYPTO_PROVIDER + .generate_hmac_key(hash.clone(), *length) + .map_err(|e| { + Exception::throw_message(ctx, &format!("HMAC key generation failed: {}", e)) + })?; + Ok((vec![], key)) }, KeyAlgorithm::Ec { curve, .. } => { - let rng = &(*SYSTEM_RANDOM); - - match curve { - EllipticCurve::P256 => { - let pkcs8 = EcdsaKeyPair::generate_pkcs8( - &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, - rng, - ) - .or_throw(ctx)?; - private_key = pkcs8.as_ref().into(); - let signing_key = p256::SecretKey::from_pkcs8_der(&private_key).unwrap(); - public_or_secret_key = signing_key.public_key().to_sec1_bytes().into(); - }, - EllipticCurve::P384 => { - let pkcs8 = EcdsaKeyPair::generate_pkcs8( - &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING, - rng, - ) - .or_throw(ctx)?; - private_key = pkcs8.as_ref().into(); - let signing_key = p384::SecretKey::from_pkcs8_der(&private_key).unwrap(); - public_or_secret_key = signing_key.public_key().to_sec1_bytes().into(); - }, - EllipticCurve::P521 => { - let mut rng = rand::rng(); - let key = p521::SecretKey::try_from_rng(&mut rng).or_throw(ctx)?; - let pkcs8 = key.to_pkcs8_der().or_throw(ctx)?; - private_key = pkcs8.as_bytes().into(); - public_or_secret_key = key.public_key().to_sec1_bytes().into(); - }, - } - }, - KeyAlgorithm::Ed25519 => { - let rng = &(*SYSTEM_RANDOM); - let pkcs8 = Ed25519KeyPair::generate_pkcs8(rng).or_throw(ctx)?; - private_key = pkcs8.as_ref().into(); - - let key_pair = Ed25519KeyPair::from_pkcs8(&private_key).unwrap(); - public_or_secret_key = key_pair.public_key().as_ref().into(); - }, - - KeyAlgorithm::X25519 => { - let mut rng = rand::rng(); - let secret_key = x25519_dalek::StaticSecret::random_from_rng(&mut rng); - private_key = secret_key.as_bytes().into(); - public_or_secret_key = x25519_dalek::PublicKey::from(&secret_key).as_bytes().into(); + CRYPTO_PROVIDER.generate_ec_key(curve.clone()).map_err(|e| { + Exception::throw_message(ctx, &format!("EC key generation failed: {}", e)) + }) }, + KeyAlgorithm::Ed25519 => CRYPTO_PROVIDER.generate_ed25519_key().map_err(|e| { + Exception::throw_message(ctx, &format!("Ed25519 key generation failed: {}", e)) + }), + KeyAlgorithm::X25519 => CRYPTO_PROVIDER.generate_x25519_key().map_err(|e| { + Exception::throw_message(ctx, &format!("X25519 key generation failed: {}", e)) + }), KeyAlgorithm::Rsa { modulus_length, public_exponent, .. - } => { - let public_exponent = public_exponent.as_ref().as_ref(); - // Convert public exponent bytes to u64 value - let exponent: u64 = match public_exponent { - [0x01, 0x00, 0x01] => 65537, // Standard RSA exponent F4 (0x10001) - [0x03] => 3, // Alternative RSA exponent 3 - bytes - if bytes.ends_with(&[0x03]) - && bytes[..bytes.len() - 1].iter().all(|&b| b == 0) => - { - 3 - }, - _ => return Err(Exception::throw_message(ctx, "Invalid RSA public exponent")), - }; - let exp = BoxedUint::from(exponent); - let mut rng = rand::rng(); - let rsa_private_key = - RsaPrivateKey::new_with_exp(&mut rng, *modulus_length as usize, exp) - .or_throw(ctx)?; - - let public_key = rsa_private_key - .to_public_key() - .to_pkcs1_der() - .or_throw(ctx)?; - - let pkcs1 = rsa_private_key.to_pkcs1_der().or_throw(ctx)?; - - private_key = pkcs1.as_bytes().into(); - - public_or_secret_key = public_key.as_bytes().into(); - }, - _ => return algorithm_not_supported_error(ctx), - }; - Ok((private_key, public_or_secret_key)) + } => CRYPTO_PROVIDER + .generate_rsa_key(*modulus_length, public_exponent.as_ref()) + .map_err(|e| { + Exception::throw_message(ctx, &format!("RSA key generation failed: {}", e)) + }), + _ => algorithm_not_supported_error(ctx), + } } +// fn generate_key(ctx: &Ctx<'_>, algorithm: &KeyAlgorithm) -> Result<(Vec, Vec)> { +// let private_key; +// let public_or_secret_key; +// match algorithm { +// KeyAlgorithm::Aes { length } => { +// let length = *length as usize; + +// match length { +// 128 | 192 | 256 => (), +// _ => { +// return Err(Exception::throw_message( +// ctx, +// "AES key length must be 128, 192, or 256 bits", +// )) +// }, +// } + +// public_or_secret_key = generate_symmetric_key(ctx, length / 8)?; +// private_key = vec![]; +// }, +// KeyAlgorithm::Hmac { hash, length } => { +// let length = get_hash_length(ctx, hash, *length)?; +// public_or_secret_key = generate_symmetric_key(ctx, length)?; +// private_key = vec![]; +// }, +// KeyAlgorithm::Ec { curve, .. } => { +// let rng = &(*SYSTEM_RANDOM); + +// match curve { +// EllipticCurve::P256 => { +// let pkcs8 = EcdsaKeyPair::generate_pkcs8( +// &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, +// rng, +// ) +// .or_throw(ctx)?; +// private_key = pkcs8.as_ref().into(); +// let signing_key = p256::SecretKey::from_pkcs8_der(&private_key).unwrap(); +// public_or_secret_key = signing_key.public_key().to_sec1_bytes().into(); +// }, +// EllipticCurve::P384 => { +// let pkcs8 = EcdsaKeyPair::generate_pkcs8( +// &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING, +// rng, +// ) +// .or_throw(ctx)?; +// private_key = pkcs8.as_ref().into(); +// let signing_key = p384::SecretKey::from_pkcs8_der(&private_key).unwrap(); +// public_or_secret_key = signing_key.public_key().to_sec1_bytes().into(); +// }, +// EllipticCurve::P521 => { +// let mut rng = rand::rng(); +// let key = p521::SecretKey::random(&mut rng); +// let pkcs8 = key.to_pkcs8_der().or_throw(ctx)?; +// private_key = pkcs8.as_bytes().into(); +// public_or_secret_key = key.public_key().to_sec1_bytes().into(); +// }, +// } +// }, +// KeyAlgorithm::Ed25519 => { +// let rng = &(*SYSTEM_RANDOM); +// let pkcs8 = Ed25519KeyPair::generate_pkcs8(rng).or_throw(ctx)?; +// private_key = pkcs8.as_ref().into(); + +// let key_pair = Ed25519KeyPair::from_pkcs8(&private_key).unwrap(); +// public_or_secret_key = key_pair.public_key().as_ref().into(); +// }, + +// KeyAlgorithm::X25519 => { +// let secret_key = x25519_dalek::StaticSecret::random(); +// private_key = secret_key.as_bytes().into(); +// public_or_secret_key = x25519_dalek::PublicKey::from(&secret_key).as_bytes().into(); +// }, +// KeyAlgorithm::Rsa { +// modulus_length, +// public_exponent, +// .. +// } => { +// let public_exponent = public_exponent.as_ref().as_ref(); +// // Convert public exponent bytes to u64 value +// let exponent: u64 = match public_exponent { +// [0x01, 0x00, 0x01] => 65537, // Standard RSA exponent F4 (0x10001) +// [0x03] => 3, // Alternative RSA exponent 3 +// bytes +// if bytes.ends_with(&[0x03]) +// && bytes[..bytes.len() - 1].iter().all(|&b| b == 0) => +// { +// 3 +// }, +// _ => return Err(Exception::throw_message(ctx, "Invalid RSA public exponent")), +// }; +// let exp = BoxedUint::from(exponent); +// let mut rng = rand::rng(); +// let rsa_private_key = +// RsaPrivateKey::new_with_exp(&mut rng, *modulus_length as usize, exp) +// .or_throw(ctx)?; + +// let public_key = rsa_private_key +// .to_public_key() +// .to_pkcs1_der() +// .or_throw(ctx)?; + +// let pkcs1 = rsa_private_key.to_pkcs1_der().or_throw(ctx)?; + +// private_key = pkcs1.as_bytes().into(); + +// public_or_secret_key = public_key.as_bytes().into(); +// }, +// _ => return algorithm_not_supported_error(ctx), +// }; +// Ok((private_key, public_or_secret_key)) +// } + fn generate_symmetric_key(ctx: &Ctx<'_>, length: usize) -> Result> { let mut key = vec![0u8; length]; SYSTEM_RANDOM.fill(&mut key).or_throw(ctx)?; diff --git a/modules/llrt_crypto/src/subtle/mod.rs b/modules/llrt_crypto/src/subtle/mod.rs index 480e1f1bc3..95412d5821 100644 --- a/modules/llrt_crypto/src/subtle/mod.rs +++ b/modules/llrt_crypto/src/subtle/mod.rs @@ -300,7 +300,7 @@ impl AesGcmVariant { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum EllipticCurve { P256, P384, @@ -339,12 +339,32 @@ pub fn rsa_hash_digest<'a>( Ok((hash, digest)) } -pub fn extract_aes_length(ctx: &Ctx<'_>, key: &CryptoKey, expected_algorithm: &str) -> Result { +pub fn validate_aes_length( + ctx: &Ctx<'_>, + key: &CryptoKey, + handle: &[u8], + expected_algorithm: &str, +) -> Result<()> { let length = match key.algorithm { KeyAlgorithm::Aes { length } => length, _ => return algorithm_mismatch_error(ctx, expected_algorithm), }; - Ok(length) + if length != handle.len() as u16 * 8 { + return Err(Exception::throw_message( + ctx, + &[ + "Invalid key handle length for ", + expected_algorithm, + ". Expected ", + &length.to_string(), + " bits, found ", + &handle.len().to_string(), + " bits", + ] + .concat(), + )); + } + Ok(()) } pub fn to_name_and_maybe_object<'js, 'a>( diff --git a/modules/llrt_crypto/src/subtle/sign.rs b/modules/llrt_crypto/src/subtle/sign.rs index 405d518fe9..c89cec8dcd 100644 --- a/modules/llrt_crypto/src/subtle/sign.rs +++ b/modules/llrt_crypto/src/subtle/sign.rs @@ -1,19 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +use crate::provider::{CryptoProvider, HmacProvider}; use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; -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, - sha2::{Sha256, Sha384, Sha512}, - Pkcs1v15Sign, RsaPrivateKey, -}; +use rquickjs::{ArrayBuffer, Class, Ctx, Result}; -use crate::{sha_hash::ShaAlgorithm, subtle::CryptoKey, SYSTEM_RANDOM}; +use crate::{subtle::CryptoKey, CRYPTO_PROVIDER}; use super::{ algorithm_mismatch_error, key_algorithm::KeyAlgorithm, rsa_hash_digest, @@ -42,36 +33,24 @@ fn sign( let handle = key.handle.as_ref(); Ok(match algorithm { SigningAlgorithm::Ecdsa { hash } => { - // Get hash algorithm from key - if !matches!(&key.algorithm, KeyAlgorithm::Ec { .. }) { - return algorithm_mismatch_error(ctx, "ECDSA"); + let curve = match &key.algorithm { + KeyAlgorithm::Ec { curve, .. } => curve, + _ => return algorithm_mismatch_error(ctx, "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)?; + let digest = crate::subtle::digest::digest(hash, data); - signature.as_ref().to_vec() + crate::CRYPTO_PROVIDER + .ecdsa_sign(*curve, handle, &digest) + .or_throw(ctx)? }, SigningAlgorithm::Ed25519 => { - // Verify key algorithm if !matches!(&key.algorithm, KeyAlgorithm::Ed25519) { return algorithm_mismatch_error(ctx, "Ed25519"); } - let key_pair = Ed25519KeyPair::from_pkcs8(handle).or_throw(ctx)?; - let signature = key_pair.sign(data); - - signature.as_ref().to_vec() + crate::CRYPTO_PROVIDER + .ed25519_sign(handle, data) + .or_throw(ctx)? }, SigningAlgorithm::Hmac => { let hash = if let KeyAlgorithm::Hmac { hash, .. } = &key.algorithm { @@ -80,67 +59,21 @@ fn sign( return algorithm_mismatch_error(ctx, "HMAC"); }; - let hmac_alg = hash.hmac_algorithm(); - - let key = HmacKey::new(*hmac_alg, handle); - let mut hmac = HmacContext::with_key(&key); + let mut hmac = CRYPTO_PROVIDER.hmac(*hash, handle); hmac.update(data); - - hmac.sign().as_ref().to_vec() + hmac.finalize() }, SigningAlgorithm::RsaPss { salt_length } => { - let salt_length = *salt_length as usize; - - let mut rng = rand::rng(); - - let private_key = RsaPrivateKey::from_pkcs1_der(&key.handle).or_throw(ctx)?; let (hash, digest) = rsa_hash_digest(ctx, key, data, "RSA-PSS")?; - let digest = digest.as_ref(); - - match hash { - ShaAlgorithm::SHA256 => private_key - .sign_with_rng( - &mut rng, - Pss::::new_with_salt(salt_length), - digest, - ) - .or_throw(ctx), - ShaAlgorithm::SHA384 => private_key - .sign_with_rng( - &mut rng, - Pss::::new_with_salt(salt_length), - digest, - ) - .or_throw(ctx), - ShaAlgorithm::SHA512 => private_key - .sign_with_rng( - &mut rng, - Pss::::new_with_salt(salt_length), - digest, - ) - .or_throw(ctx), - ShaAlgorithm::SHA1 => unreachable!(), - }? + crate::CRYPTO_PROVIDER + .rsa_pss_sign(&key.handle, digest.as_ref(), *salt_length as usize, *hash) + .or_throw(ctx)? }, SigningAlgorithm::RsassaPkcs1v15 => { - let mut rng = rand::rng(); - - let private_key = RsaPrivateKey::from_pkcs1_der(&key.handle).or_throw(ctx)?; let (hash, digest) = rsa_hash_digest(ctx, key, data, "RSASSA-PKCS1-v1_5")?; - let digest = digest.as_ref(); - - match hash { - ShaAlgorithm::SHA256 => private_key - .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) - .or_throw(ctx), - ShaAlgorithm::SHA384 => private_key - .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) - .or_throw(ctx), - ShaAlgorithm::SHA512 => private_key - .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) - .or_throw(ctx), - ShaAlgorithm::SHA1 => unreachable!(), - }? + crate::CRYPTO_PROVIDER + .rsa_pkcs1v15_sign(&key.handle, digest.as_ref(), *hash) + .or_throw(ctx)? }, }) } diff --git a/modules/llrt_crypto/src/subtle/verify.rs b/modules/llrt_crypto/src/subtle/verify.rs index ea1849599b..1c00cd7c44 100644 --- a/modules/llrt_crypto/src/subtle/verify.rs +++ b/modules/llrt_crypto/src/subtle/verify.rs @@ -1,28 +1,17 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use ecdsa::signature::hazmat::PrehashVerifier; +use crate::provider::{CryptoProvider, HmacProvider}; use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; -use ring::{ - hmac::{Context as HmacContext, Key as HmacKey}, - signature::UnparsedPublicKey, -}; use rquickjs::{Class, Ctx, Result}; -use rsa::{ - pkcs1::DecodeRsaPublicKey, - pkcs1v15::Pkcs1v15Sign, - pss::Pss, - sha2::{Sha256, Sha384, Sha512}, - RsaPublicKey, -}; use crate::{ - sha_hash::ShaAlgorithm, subtle::{digest, CryptoKey}, + CRYPTO_PROVIDER, }; use super::{ algorithm_mismatch_error, key_algorithm::KeyAlgorithm, rsa_hash_digest, - sign_algorithm::SigningAlgorithm, EllipticCurve, + sign_algorithm::SigningAlgorithm, }; pub async fn subtle_verify<'js>( @@ -59,36 +48,20 @@ fn verify( _ => return algorithm_mismatch_error(ctx, "ECDSA"), }; - let hash = digest::digest(hash, data); + let digest = digest::digest(hash, data); - match curve { - EllipticCurve::P256 => { - let verifying_key = - p256::ecdsa::VerifyingKey::from_sec1_bytes(handle).or_throw(ctx)?; - let signature = p256::ecdsa::Signature::from_slice(signature).or_throw(ctx)?; - verifying_key.verify_prehash(&hash, &signature).is_ok() - }, - EllipticCurve::P384 => { - let verifying_key = - p384::ecdsa::VerifyingKey::from_sec1_bytes(handle).or_throw(ctx)?; - let signature = p384::ecdsa::Signature::from_slice(signature).or_throw(ctx)?; - verifying_key.verify_prehash(&hash, &signature).is_ok() - }, - EllipticCurve::P521 => { - let verifying_key = - p521::ecdsa::VerifyingKey::from_sec1_bytes(handle).or_throw(ctx)?; - let signature = p521::ecdsa::Signature::from_slice(signature).or_throw(ctx)?; - verifying_key.verify_prehash(&hash, &signature).is_ok() - }, - } + crate::CRYPTO_PROVIDER + .ecdsa_verify(*curve, handle, signature, &digest) + .or_throw(ctx)? }, SigningAlgorithm::Ed25519 => { if !matches!(&key.algorithm, KeyAlgorithm::Ed25519) { return algorithm_mismatch_error(ctx, "Ed25519"); } - let public_key = UnparsedPublicKey::new(&ring::signature::ED25519, handle); - public_key.verify(data, signature).is_ok() + crate::CRYPTO_PROVIDER + .ed25519_verify(handle, signature, data) + .or_throw(ctx)? }, SigningAlgorithm::Hmac => { let hash = match &key.algorithm { @@ -96,59 +69,29 @@ fn verify( _ => return algorithm_mismatch_error(ctx, "HMAC"), }; - let key = HmacKey::new(*hash.hmac_algorithm(), handle); - let mut hmac = HmacContext::with_key(&key); + let mut hmac = CRYPTO_PROVIDER.hmac(*hash, handle); hmac.update(data); - hmac.sign().as_ref() == signature + let computed_signature = hmac.finalize(); + + computed_signature == signature }, SigningAlgorithm::RsaPss { salt_length } => { let (hash, digest) = rsa_hash_digest(ctx, key, data, "RSA-PSS")?; - let digest = digest.as_ref(); - let public_key = RsaPublicKey::from_pkcs1_der(&key.handle).or_throw(ctx)?; - - match hash { - ShaAlgorithm::SHA256 => public_key - .verify( - Pss::::new_with_salt(*salt_length as usize), - digest, - signature, - ) - .is_ok(), - ShaAlgorithm::SHA384 => public_key - .verify( - Pss::::new_with_salt(*salt_length as usize), - digest, - signature, - ) - .is_ok(), - ShaAlgorithm::SHA512 => public_key - .verify( - Pss::::new_with_salt(*salt_length as usize), - digest, - signature, - ) - .is_ok(), - _ => unreachable!(), - } + crate::CRYPTO_PROVIDER + .rsa_pss_verify( + &key.handle, + signature, + digest.as_ref(), + *salt_length as usize, + *hash, + ) + .or_throw(ctx)? }, SigningAlgorithm::RsassaPkcs1v15 => { let (hash, digest) = rsa_hash_digest(ctx, key, data, "RSASSA-PKCS1-v1_5")?; - let public_key = RsaPublicKey::from_pkcs1_der(&key.handle).or_throw(ctx)?; - - let digest = digest.as_ref(); - - match hash { - ShaAlgorithm::SHA256 => public_key - .verify(Pkcs1v15Sign::new::(), digest, signature) - .is_ok(), - ShaAlgorithm::SHA384 => public_key - .verify(Pkcs1v15Sign::new::(), digest, signature) - .is_ok(), - ShaAlgorithm::SHA512 => public_key - .verify(Pkcs1v15Sign::new::(), digest, signature) - .is_ok(), - _ => unreachable!(), - } + crate::CRYPTO_PROVIDER + .rsa_pkcs1v15_verify(&key.handle, signature, digest.as_ref(), *hash) + .or_throw(ctx)? }, }) } From 9731ce560c7d9652aa2f78b161e5e16d6e5c42fe Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Mon, 15 Dec 2025 22:40:35 +0100 Subject: [PATCH 02/77] Refactor crypto and HTTP modules to support multiple TLS backends Added support for rustls, graviola and OpenSSL TLS backends This makes it easier to choose different TLS implementations depending on deployment needs. --- Cargo.lock | 413 +++++++++++- README.md | 40 ++ llrt/Cargo.toml | 16 +- llrt_core/Cargo.toml | 18 +- llrt_core/src/http.rs | 43 +- llrt_core/src/vm.rs | 2 - llrt_modules/Cargo.toml | 31 +- modules/llrt_crypto/Cargo.toml | 82 ++- modules/llrt_crypto/src/lib.rs | 4 +- modules/llrt_crypto/src/md5_hash.rs | 8 +- modules/llrt_crypto/src/provider/graviola.rs | 399 +++++++++++ modules/llrt_crypto/src/provider/mod.rs | 635 +++++++++++++++++- modules/llrt_crypto/src/provider/openssl.rs | 627 +++++++---------- modules/llrt_crypto/src/provider/ring.rs | 76 ++- modules/llrt_crypto/src/provider/rust.rs | 325 +++++---- modules/llrt_crypto/src/sha_hash.rs | 2 +- modules/llrt_crypto/src/subtle/derive.rs | 24 +- .../src/subtle/derive_algorithm.rs | 2 +- modules/llrt_crypto/src/subtle/digest.rs | 6 +- modules/llrt_crypto/src/subtle/encryption.rs | 73 +- .../llrt_crypto/src/subtle/generate_key.rs | 139 +--- modules/llrt_http/Cargo.toml | 25 +- modules/llrt_http/src/agent.rs | 15 +- modules/llrt_http/src/client.rs | 155 +++-- modules/llrt_tls/Cargo.toml | 15 +- modules/llrt_tls/src/lib.rs | 15 +- modules/llrt_tls/src/openssl_config.rs | 80 +++ .../src/{config.rs => rustls_config.rs} | 19 +- 28 files changed, 2468 insertions(+), 821 deletions(-) create mode 100644 modules/llrt_crypto/src/provider/graviola.rs create mode 100644 modules/llrt_tls/src/openssl_config.rs rename modules/llrt_tls/src/{config.rs => rustls_config.rs} (86%) diff --git a/Cargo.lock b/Cargo.lock index ab71c270a3..ba94c3dfdd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "base16ct" version = "0.3.0" @@ -324,7 +346,7 @@ checksum = "99cbf41c6ec3c4b9eaf7f8f5c11a72cd7d3aa0428125c20d5ef4d09907a0f019" dependencies = [ "cfg-if", "cpufeatures", - "rand_core", + "rand_core 0.10.0-rc-6", ] [[package]] @@ -479,6 +501,17 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-models" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0940496e5c83c54f3b753d5317daec82e8edac71c33aaa1f666d76f518de2444" +dependencies = [ + "hax-lib", + "pastey", + "rand 0.9.2", +] + [[package]] name = "cpubits" version = "0.1.0-rc.1" @@ -641,7 +674,7 @@ checksum = "6715836b4946e8585016e80b79c7561476aff3b22f7b756778e7b109d86086c6" dependencies = [ "hybrid-array", "num-traits", - "rand_core", + "rand_core 0.10.0-rc-6", "serdect", "subtle", "zeroize", @@ -664,7 +697,7 @@ checksum = "fdd9b2855017318a49714c07ee8895b89d3510d54fa6d86be5835de74c389609" dependencies = [ "crypto-bigint", "libm", - "rand_core", + "rand_core 0.10.0-rc-6", ] [[package]] @@ -798,6 +831,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ecdsa" version = "0.17.0-rc.9" @@ -830,7 +869,7 @@ dependencies = [ "hkdf", "hybrid-array", "pkcs8", - "rand_core", + "rand_core 0.10.0-rc-6", "rustcrypto-ff", "rustcrypto-group", "sec1", @@ -924,6 +963,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -933,6 +987,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.31" @@ -1060,6 +1120,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "graviola" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1662fcff7237fbe8c91ff2800fcce9435af25b7f0cb580f5679b31c3a1f1e7a" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", +] + [[package]] name = "h2" version = "0.4.13" @@ -1110,6 +1180,43 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hax-lib" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d9ba66d1739c68e0219b2b2238b5c4145f491ebf181b9c6ab561a19352ae86" +dependencies = [ + "hax-lib-macros", + "num-bigint", + "num-traits", +] + +[[package]] +name = "hax-lib-macros" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba777a231a58d1bce1d68313fa6b6afcc7966adef23d60f45b8a2b9b688bf1" +dependencies = [ + "hax-lib-macros-types", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hax-lib-macros-types" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "867e19177d7425140b417cd27c2e05320e727ee682e98368f88b7194e80ad515" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_json", + "uuid", +] + [[package]] name = "hermit-abi" version = "0.5.2" @@ -1232,6 +1339,25 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-openssl" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527d4d619ca2c2aafa31ec139a3d1d60bf557bf7578a1f20f743637eccd9ca19" +dependencies = [ + "http", + "hyper", + "hyper-util", + "linked_hash_set", + "once_cell", + "openssl", + "openssl-sys", + "parking_lot", + "pin-project", + "tower-layer", + "tower-service", +] + [[package]] name = "hyper-rustls" version = "0.27.7" @@ -1488,6 +1614,70 @@ version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +[[package]] +name = "libcrux-intrinsics" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9ee7ef66569dd7516454fe26de4e401c0c62073929803486b96744594b9632" +dependencies = [ + "core-models", + "hax-lib", +] + +[[package]] +name = "libcrux-ml-kem" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6a88086bf11bd2ec90926c749c4a427f2e59841437dbdede8cde8a96334ab" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-sha3", + "libcrux-traits", +] + +[[package]] +name = "libcrux-platform" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db82d058aa76ea315a3b2092f69dfbd67ddb0e462038a206e1dcd73f058c0778" +dependencies = [ + "libc", +] + +[[package]] +name = "libcrux-secrets" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4dbbf6bc9f2bc0f20dc3bea3e5c99adff3bdccf6d2a40488963da69e2ec307" +dependencies = [ + "hax-lib", +] + +[[package]] +name = "libcrux-sha3" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2400bec764d1c75b8a496d5747cffe32f1fb864a12577f0aca2f55a92021c962" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-traits", +] + +[[package]] +name = "libcrux-traits" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9adfd58e79d860f6b9e40e35127bfae9e5bd3ade33201d1347459011a2add034" +dependencies = [ + "libcrux-secrets", + "rand 0.9.2", +] + [[package]] name = "libloading" version = "0.8.9" @@ -1524,6 +1714,21 @@ dependencies = [ "libc", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linked_hash_set" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "984fb35d06508d1e69fc91050cceba9c0b748f983e6739fa2c7a9237154c52c8" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -1682,10 +1887,11 @@ dependencies = [ "llrt_utils", "md-5", "once_cell", + "openssl", "phf 0.13.1", "phf_codegen", "quick-xml", - "rand", + "rand 0.10.0-rc.5", "ring", "rquickjs", "rustls", @@ -1715,6 +1921,8 @@ dependencies = [ "der", "ecdsa", "elliptic-curve", + "graviola", + "hmac", "llrt_buffer", "llrt_context", "llrt_encoding", @@ -1723,15 +1931,17 @@ dependencies = [ "llrt_utils", "md-5", "once_cell", + "openssl", "openssl-sys", "p256", "p384", "p521", "pkcs8", - "rand", + "rand 0.10.0-rc.5", "ring", "rquickjs", "rsa", + "sha1", "spki", "x25519-dalek", ] @@ -1827,7 +2037,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rand", + "rand 0.10.0-rc.5", "rquickjs", "tokio", "tracing", @@ -1866,12 +2076,14 @@ dependencies = [ "bytes", "http-body-util", "hyper", + "hyper-openssl", "hyper-rustls", "hyper-util", "llrt_dns_cache", "llrt_tls", "llrt_utils", "once_cell", + "openssl", "rquickjs", "rustls", ] @@ -1980,7 +2192,7 @@ dependencies = [ "llrt_stream", "llrt_test", "llrt_utils", - "rand", + "rand 0.10.0-rc.5", "rquickjs", "tokio", "tracing", @@ -1994,7 +2206,7 @@ dependencies = [ "itoa", "llrt_test", "llrt_utils", - "rand", + "rand 0.10.0-rc.5", "rquickjs", "ryu", ] @@ -2027,7 +2239,7 @@ dependencies = [ "llrt_utils", "memchr", "once_cell", - "rand", + "rand 0.10.0-rc.5", "rquickjs", ] @@ -2127,7 +2339,9 @@ name = "llrt_tls" version = "0.7.0-beta" dependencies = [ "once_cell", + "openssl", "rustls", + "rustls-graviola", "rustls-native-certs", "tracing", "webpki-roots", @@ -2269,6 +2483,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[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-traits" version = "0.2.19" @@ -2319,6 +2552,32 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.2.1" @@ -2417,6 +2676,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "pem-rfc7468" version = "1.0.0" @@ -2503,6 +2768,26 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2561,6 +2846,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -2578,7 +2872,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c3ad342f52c70a953d95acb09a55450fdc07c2214283b81536c3f83f714568e" dependencies = [ "crypto-bigint", - "rand_core", + "rand_core 0.10.0-rc-6", "rustcrypto-ff", "subtle", "zeroize", @@ -2602,6 +2896,28 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -2645,6 +2961,16 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + [[package]] name = "rand" version = "0.10.0-rc.5" @@ -2653,7 +2979,26 @@ checksum = "be866deebbade98028b705499827ad6967c8bb1e21f96a2609913c8c076e9307" dependencies = [ "chacha20", "getrandom 0.3.4", - "rand_core", + "rand_core 0.10.0-rc-6", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", ] [[package]] @@ -2843,8 +3188,8 @@ dependencies = [ "digest", "pkcs1", "pkcs8", - "rand", - "rand_core", + "rand 0.10.0-rc.5", + "rand_core 0.10.0-rc-6", "sha2", "signature", "spki", @@ -2873,7 +3218,7 @@ version = "0.14.0-pre.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9cd37111549306f79b09aa2618e15b1e8241b7178c286821e3dd71579db4db" dependencies = [ - "rand_core", + "rand_core 0.10.0-rc-6", "subtle", ] @@ -2883,7 +3228,7 @@ version = "0.14.0-pre.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e394cd734b5f97dfc3484fa42aad7acd912961c2bcd96c99aa05b3d6cab7cafd" dependencies = [ - "rand_core", + "rand_core 0.10.0-rc-6", "rustcrypto-ff", "subtle", ] @@ -2907,6 +3252,7 @@ version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ + "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -2915,6 +3261,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-graviola" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e81f0f2005bfba00e8088f9cb75f4b3ce3f2a31aebfaeed0b2cc05e13d01ce06" +dependencies = [ + "graviola", + "libcrux-ml-kem", + "rustls", +] + [[package]] name = "rustls-native-certs" version = "0.8.3" @@ -2951,6 +3308,7 @@ version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -3087,6 +3445,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.11.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c777f0a122a53fddb0beb6e706771197000b8eb5c9f42b5b850f450ef48c788" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.11.0-rc.4" @@ -3142,7 +3511,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ad0ce3b3f8efd7406f22e2ca5d02be21cdf3b3d1d53ab141f784de8965c7c7e" dependencies = [ "digest", - "rand_core", + "rand_core 0.10.0-rc-6", ] [[package]] @@ -3384,6 +3753,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -3484,6 +3859,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -3975,7 +4352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40dac5a6478dc8baae2031cfc87d966e427a4fe5011e7f90785ff5c8aa9f2d06" dependencies = [ "curve25519-dalek", - "rand_core", + "rand_core 0.10.0-rc-6", "zeroize", ] diff --git a/README.md b/README.md index f3b98e77c3..c246ffda12 100644 --- a/README.md +++ b/README.md @@ -521,6 +521,46 @@ make release-full-sdk You should now have a `llrt-lambda-arm64*.zip` or `llrt-lambda-x64*.zip`. You can manually upload this as a Lambda layer or use it via your Infrastructure-as-code pipeline +## Crypto and TLS Backend Options + +LLRT supports multiple cryptographic backends for both the crypto module and TLS connections. These can be configured via Cargo features. + +### Crypto Provider Features + +| Feature | Description | +| ----------------------- | ----------------------------------------------------------------- | +| `crypto-rust` (default) | Pure Rust crypto using RustCrypto crates | +| `crypto-ring` | Ring-only crypto (limited algorithm support) | +| `crypto-ring-rust` | Ring for hashing/HMAC, RustCrypto for everything else | +| `crypto-graviola` | Graviola-only crypto (limited algorithm support) | +| `crypto-graviola-rust` | Graviola for hashing/HMAC/AES-GCM, RustCrypto for everything else | +| `crypto-openssl` | OpenSSL-based crypto | + +### TLS Backend Features + +| Feature | Description | +| -------------------- | --------------------------------------------- | +| `tls-ring` (default) | rustls with ring crypto | +| `tls-aws-lc` | rustls with AWS-LC crypto (optimized for AWS) | +| `tls-graviola` | rustls with graviola crypto | +| `tls-openssl` | OpenSSL for TLS | + +### Building with Different Backends + +```bash +# Default (crypto-rust + tls-ring) +cargo build --release + +# Using AWS-LC for TLS +cargo build --release --no-default-features --features "macro,tls-aws-lc" + +# Using OpenSSL for both crypto and TLS +cargo build --release --no-default-features --features "macro,crypto-openssl,tls-openssl" + +# Using Graviola for both crypto and TLS +cargo build --release --no-default-features --features "macro,crypto-graviola-rust,tls-graviola" +``` + ## Running Lambda emulator Please note that in order to run the example you will need: diff --git a/llrt/Cargo.toml b/llrt/Cargo.toml index 90997af72e..67fb9abb06 100644 --- a/llrt/Cargo.toml +++ b/llrt/Cargo.toml @@ -5,13 +5,25 @@ edition = "2021" license-file = "LICENSE" [features] -default = ["macro"] +default = ["macro", "tls-ring", "crypto-rust"] macro = ["llrt_core/macro"] lambda = ["llrt_core/lambda"] no-sdk = ["llrt_core/no-sdk"] uncompressed = ["llrt_core/uncompressed"] bindgen = ["llrt_core/bindgen"] +# TLS crypto backend features +tls-ring = ["llrt_core/tls-ring"] +tls-aws-lc = ["llrt_core/tls-aws-lc"] +tls-graviola = ["llrt_core/tls-graviola"] +tls-openssl = ["llrt_core/tls-openssl"] + +# Crypto provider features +crypto-rust = ["llrt_core/crypto-rust"] +crypto-ring-rust = ["llrt_core/crypto-ring-rust"] +crypto-graviola-rust = ["llrt_core/crypto-graviola-rust"] +crypto-openssl = ["llrt_core/crypto-openssl"] + [dependencies] chrono = { version = "0.4", features = ["std"], default-features = false } constcat = { version = "0.6", default-features = false } @@ -19,7 +31,7 @@ crossterm = { version = "0.29", features = [ "events", "windows", ], default-features = false } -llrt_core = { path = "../llrt_core" } +llrt_core = { path = "../llrt_core", default-features = false } tokio = { version = "1", features = [ "macros", "rt-multi-thread", diff --git a/llrt_core/Cargo.toml b/llrt_core/Cargo.toml index 67d3242295..a3c2187aed 100644 --- a/llrt_core/Cargo.toml +++ b/llrt_core/Cargo.toml @@ -5,13 +5,25 @@ edition = "2021" license-file = "LICENSE" [features] -default = ["macro"] +default = ["macro", "tls-ring", "crypto-rust"] lambda = [] no-sdk = [] uncompressed = [] macro = ["rquickjs/macro"] bindgen = ["rquickjs/bindgen"] +# TLS crypto backend features +tls-ring = ["rustls/ring", "llrt_modules/tls-ring"] +tls-aws-lc = ["rustls/aws_lc_rs", "llrt_modules/tls-aws-lc"] +tls-graviola = ["llrt_modules/tls-graviola"] +tls-openssl = ["llrt_modules/tls-openssl", "dep:openssl"] + +# Crypto provider features +crypto-rust = ["llrt_modules/crypto-rust"] +crypto-ring-rust = ["llrt_modules/crypto-ring-rust"] +crypto-graviola-rust = ["llrt_modules/crypto-graviola-rust"] +crypto-openssl = ["llrt_modules/crypto-openssl"] + [dependencies] bytes = { version = "1", default-features = false } chrono = { version = "0.4", features = ["std", "clock"], default-features = false } @@ -26,7 +38,7 @@ llrt_encoding = { path = "../libs/llrt_encoding" } llrt_hooking = { path = "../libs/llrt_hooking" } llrt_json = { path = "../libs/llrt_json" } llrt_logging = { path = "../libs/llrt_logging" } -llrt_modules = { path = "../llrt_modules" } +llrt_modules = { path = "../llrt_modules", default-features = false, features = ["base", "console"] } llrt_numbers = { path = "../libs/llrt_numbers" } llrt_utils = { path = "../libs/llrt_utils", features = ["all"] } once_cell = { version = "1", features = ["std"], default-features = false } @@ -44,7 +56,6 @@ rquickjs = { version = "0.11", features = [ "std", ], default-features = false } rustls = { version = "0.23", features = [ - "ring", "tls12", ], default-features = false } rustls-pemfile = { version = "2", features = ["std"], default-features = false } @@ -54,6 +65,7 @@ terminal_size = { version = "0.4", default-features = false } tokio = { version = "1", features = ["sync", "time"], default-features = false } tracing = { version = "0.1", features = ["log"], default-features = false } zstd = { version = "0.13", default-features = false } +openssl = { version = "0.10", optional = true } [target.'cfg(target_os = "windows")'.dependencies] md-5 = { version = "0.11.0-rc.3", default-features = false } diff --git a/llrt_core/src/http.rs b/llrt_core/src/http.rs index cbd1b561c9..aca08fd954 100644 --- a/llrt_core/src/http.rs +++ b/llrt_core/src/http.rs @@ -1,24 +1,41 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use std::{env, fs::File, io, result::Result as StdResult}; +use std::{env, result::Result as StdResult}; -use rustls::{pki_types::CertificateDer, version, SupportedProtocolVersion}; use tracing::warn; use crate::environment; use crate::modules::https::{set_http_version, set_pool_idle_timeout_seconds, HttpVersion}; + +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +use std::{fs::File, io}; + +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +use rustls::{pki_types::CertificateDer, version, SupportedProtocolVersion}; + +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] use crate::modules::tls::{set_extra_ca_certs, set_tls_versions}; +#[cfg(feature = "tls-openssl")] +use crate::modules::tls::set_tls_version; + pub fn init() -> StdResult<(), Box> { if let Some(pool_idle_timeout) = build_pool_idle_timeout() { set_pool_idle_timeout_seconds(pool_idle_timeout); } - if let Some(extra_ca_certs) = buid_extra_ca_certs()? { - set_extra_ca_certs(extra_ca_certs); + #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] + { + if let Some(extra_ca_certs) = build_extra_ca_certs()? { + set_extra_ca_certs(extra_ca_certs); + } + set_tls_versions(build_tls_versions()); } - set_tls_versions(build_tls_versions()); + #[cfg(feature = "tls-openssl")] + { + set_tls_version(build_tls_version_openssl()); + } set_http_version(build_http_version()); @@ -42,10 +59,11 @@ fn build_pool_idle_timeout() -> Option { Some(pool_idle_timeout) } -fn buid_extra_ca_certs() -> StdResult>>, io::Error> { +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +fn build_extra_ca_certs() -> StdResult>>, io::Error> { if let Ok(extra_ca_certs) = env::var(environment::ENV_LLRT_EXTRA_CA_CERTS) { if !extra_ca_certs.is_empty() { - let file = File::open(extra_ca_certs) // This can be sync since we do this once when the VM starts + let file = File::open(extra_ca_certs) .map_err(|_| io::Error::other("Failed to open extra CA certificates file"))?; let mut reader = io::BufReader::new(file); return Ok(Some( @@ -58,10 +76,19 @@ fn buid_extra_ca_certs() -> StdResult>>, io:: Ok(None) } +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] fn build_tls_versions() -> Vec<&'static SupportedProtocolVersion> { match env::var(environment::ENV_LLRT_TLS_VERSION).as_deref() { Ok("1.3") => vec![&version::TLS13, &version::TLS12], - _ => vec![&version::TLS12], //Use TLS 1.2 by default to increase compat and keep latency low + _ => vec![&version::TLS12], + } +} + +#[cfg(feature = "tls-openssl")] +fn build_tls_version_openssl() -> Option { + match env::var(environment::ENV_LLRT_TLS_VERSION).as_deref() { + Ok("1.3") => Some(openssl::ssl::SslVersion::TLS1_3), + _ => Some(openssl::ssl::SslVersion::TLS1_2), } } diff --git a/llrt_core/src/vm.rs b/llrt_core/src/vm.rs index 7c2776b77a..fc17c84181 100644 --- a/llrt_core/src/vm.rs +++ b/llrt_core/src/vm.rs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 use std::{env, result::Result as StdResult}; -use llrt_modules::crypto; -use ring::rand::SecureRandom; use rquickjs::{ context::EvalOptions, loader::FileResolver, prelude::Func, AsyncContext, AsyncRuntime, CatchResultExt, Ctx, Error, Result, Value, diff --git a/llrt_modules/Cargo.toml b/llrt_modules/Cargo.toml index 076e25dbf5..bdbb6da6c7 100644 --- a/llrt_modules/Cargo.toml +++ b/llrt_modules/Cargo.toml @@ -8,9 +8,21 @@ repository = "https://github.com/awslabs/llrt" readme = "README.md" [features] -default = ["base", "console"] +default = ["base", "console", "tls-ring", "crypto-rust"] lambda = ["base"] +# TLS crypto backend features +tls-ring = ["llrt_http?/tls-ring", "llrt_tls?/tls-ring"] +tls-aws-lc = ["llrt_http?/tls-aws-lc", "llrt_tls?/tls-aws-lc"] +tls-graviola = ["llrt_http?/tls-graviola", "llrt_tls?/tls-graviola"] +tls-openssl = ["llrt_http?/tls-openssl", "llrt_tls?/tls-openssl"] + +# Crypto provider features +crypto-rust = ["llrt_crypto?/crypto-rust"] +crypto-ring-rust = ["llrt_crypto?/crypto-ring-rust"] +crypto-graviola-rust = ["llrt_crypto?/crypto-graviola-rust"] +crypto-openssl = ["llrt_crypto?/crypto-openssl"] + base = [ "abort", "assert", @@ -56,7 +68,12 @@ events = ["llrt_events"] exceptions = ["llrt_exceptions"] fetch = ["llrt_fetch"] fs = ["llrt_fs"] -https = ["llrt_http"] +https = [ + "llrt_http", + "llrt_http?/webpki-roots", + "llrt_http?/http1", + "llrt_http?/http2", +] navigator = ["llrt_navigator"] net = ["llrt_net"] os = ["llrt_os"] @@ -79,9 +96,7 @@ llrt_hooking = { path = "../libs/llrt_hooking" } llrt_json = { path = "../libs/llrt_json" } llrt_utils = { version = "0.7.0-beta", path = "../libs/llrt_utils" } once_cell = { version = "1", features = ["std"], default-features = false } -rquickjs = { version = "0.11", features = [ - "loader", -], default-features = false } +rquickjs = { version = "0.11", features = ["loader"], default-features = false } simd-json = { version = "0.17", default-features = false } tokio = { version = "1", default-features = false } tracing = { version = "0.1", default-features = false } @@ -93,14 +108,14 @@ llrt_async_hooks = { version = "0.7.0-beta", path = "../modules/llrt_async_hooks llrt_buffer = { version = "0.7.0-beta", path = "../modules/llrt_buffer", optional = true } llrt_child_process = { version = "0.7.0-beta", path = "../modules/llrt_child_process", optional = true } llrt_console = { version = "0.7.0-beta", path = "../modules/llrt_console", optional = true } -llrt_crypto = { version = "0.7.0-beta", path = "../modules/llrt_crypto", optional = true } +llrt_crypto = { version = "0.7.0-beta", path = "../modules/llrt_crypto", default-features = false, optional = true } llrt_dgram = { version = "0.7.0-beta", path = "../modules/llrt_dgram", optional = true } llrt_dns = { version = "0.7.0-beta", path = "../modules/llrt_dns", optional = true } llrt_events = { version = "0.7.0-beta", path = "../modules/llrt_events", optional = true } llrt_exceptions = { version = "0.7.0-beta", path = "../modules/llrt_exceptions", optional = true } llrt_fetch = { version = "0.7.0-beta", path = "../modules/llrt_fetch", optional = true } llrt_fs = { version = "0.7.0-beta", path = "../modules/llrt_fs", optional = true } -llrt_http = { version = "0.7.0-beta", path = "../modules/llrt_http", optional = true } +llrt_http = { version = "0.7.0-beta", path = "../modules/llrt_http", default-features = false, optional = true } llrt_navigator = { version = "0.7.0-beta", path = "../modules/llrt_navigator", optional = true } llrt_net = { version = "0.7.0-beta", path = "../modules/llrt_net", optional = true } llrt_os = { version = "0.7.0-beta", path = "../modules/llrt_os", default-features = false, optional = true } @@ -111,7 +126,7 @@ llrt_stream_web = { version = "0.7.0-beta", path = "../modules/llrt_stream_web", llrt_string_decoder = { version = "0.7.0-beta", path = "../modules/llrt_string_decoder", optional = true } llrt_timers = { version = "0.7.0-beta", path = "../modules/llrt_timers", optional = true } llrt_intl = { version = "0.7.0-beta", path = "../modules/llrt_intl", optional = true } -llrt_tls = { version = "0.7.0-beta", path = "../modules/llrt_tls", optional = true } +llrt_tls = { version = "0.7.0-beta", path = "../modules/llrt_tls", default-features = false, optional = true } llrt_tty = { version = "0.7.0-beta", path = "../modules/llrt_tty", optional = true } llrt_url = { version = "0.7.0-beta", path = "../modules/llrt_url", optional = true } llrt_util = { version = "0.7.0-beta", path = "../modules/llrt_util", optional = true } diff --git a/modules/llrt_crypto/Cargo.toml b/modules/llrt_crypto/Cargo.toml index 82e17647f1..9189556042 100644 --- a/modules/llrt_crypto/Cargo.toml +++ b/modules/llrt_crypto/Cargo.toml @@ -31,9 +31,80 @@ crypto-rust = [ "der", "const-oid", "pkcs8", + "hmac", + "sha1", +] +crypto-ring = [] +crypto-ring-rust = [ + "llrt_json", + "aes", + "aes-gcm", + "aes-kw", + "cbc", + "ctr", + "rsa", + "p256", + "p384", + "p521", + "elliptic-curve", + "x25519-dalek", + "ecdsa", + "spki", + "der", + "const-oid", + "pkcs8", + "hmac", + "sha1", +] +crypto-graviola = [ + "graviola", +] +crypto-graviola-rust = [ + "graviola", + "llrt_json", + "aes", + "aes-gcm", + "aes-kw", + "cbc", + "ctr", + "rsa", + "p256", + "p384", + "p521", + "elliptic-curve", + "x25519-dalek", + "ecdsa", + "spki", + "der", + "const-oid", + "pkcs8", + "hmac", + "sha1", ] # Crypto provider features -crypto-openssl = ["openssl-sys"] +crypto-openssl = [ + "openssl-sys", + "openssl", + "llrt_json", + "aes", + "aes-gcm", + "aes-kw", + "cbc", + "ctr", + "rsa", + "p256", + "p384", + "p521", + "elliptic-curve", + "x25519-dalek", + "ecdsa", + "spki", + "der", + "const-oid", + "pkcs8", + "hmac", + "sha1", +] [dependencies] crc32c = { version = "0.6", default-features = false } @@ -54,8 +125,8 @@ rquickjs = { version = "0.11", features = ["macro"], default-features = false } # optional llrt_json = { version = "0.7.0-beta", path = "../../libs/llrt_json", optional = true } -aes = { version = "0.9.0-rc.0", optional = true } -aes-gcm = { version = "0.11.0-rc.0", features = [ +aes = { version = "0.9.0-rc.2", optional = true } +aes-gcm = { version = "0.11.0-rc.2", features = [ "alloc", ], default-features = false, optional = true } aes-kw = { version = "0.3.0-rc.1", features = [ @@ -73,6 +144,7 @@ der = { version = "0.8.0-rc.10", features = [ ecdsa = { version = "0.17.0-rc.9", default-features = false, optional = true } elliptic-curve = { version = "0.14.0-rc.17", features = [ "alloc", + "sec1", ], default-features = false, optional = true } md-5 = { version = "0.11.0-rc.3", default-features = false } rsa = { version = "0.10.0-rc.10", features = [ @@ -107,9 +179,13 @@ x25519-dalek = { version = "3.0.0-pre.3", features = [ "static_secrets", "zeroize", ], default-features = false, optional = true } +hmac = { version = "0.13.0-rc.3", default-features = false, optional = true } +sha1 = { version = "0.11.0-rc.3", default-features = false, optional = true } # Crypto provider dependencies openssl-sys = { version = "0.9", optional = true } +openssl = { version = "0.10", optional = true } +graviola = { version = "0.3", optional = true } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } diff --git a/modules/llrt_crypto/src/lib.rs b/modules/llrt_crypto/src/lib.rs index 937d9d4d39..6dbfe0216b 100644 --- a/modules/llrt_crypto/src/lib.rs +++ b/modules/llrt_crypto/src/lib.rs @@ -21,16 +21,14 @@ use llrt_utils::{ }; use once_cell::sync::Lazy; use rand::Rng; -use ring::rand::{SecureRandom, SystemRandom}; -#[cfg(feature = "subtle-rs")] use rquickjs::prelude::Async; use rquickjs::{ + atom::PredefinedAtom, function::Opt, module::{Declarations, Exports, ModuleDef}, prelude::{Func, Rest}, Class, Ctx, Error, Exception, Function, IntoJs, Null, Object, Result, Value, }; -#[cfg(feature = "subtle-rs")] use subtle::{ subtle_decrypt, subtle_derive_bits, subtle_derive_key, subtle_digest, subtle_encrypt, subtle_export_key, subtle_generate_key, subtle_import_key, subtle_sign, subtle_unwrap_key, diff --git a/modules/llrt_crypto/src/md5_hash.rs b/modules/llrt_crypto/src/md5_hash.rs index 475717c9e4..0538bd1e0c 100644 --- a/modules/llrt_crypto/src/md5_hash.rs +++ b/modules/llrt_crypto/src/md5_hash.rs @@ -3,8 +3,11 @@ use llrt_utils::bytes::{bytes_to_typed_array, ObjectBytes}; use rquickjs::{function::Opt, prelude::This, Class, Ctx, Result, Value}; -use crate::{provider::{CryptoProvider, SimpleDigest}, sha_hash::ShaAlgorithm}; use super::{encoded_bytes, CRYPTO_PROVIDER}; +use crate::{ + provider::{CryptoProvider, SimpleDigest}, + sha_hash::ShaAlgorithm, +}; #[rquickjs::class] #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] @@ -24,7 +27,8 @@ impl Md5 { #[qjs(rename = "digest")] fn md5_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let digest = std::mem::replace(&mut self.hasher, CRYPTO_PROVIDER.digest(ShaAlgorithm::MD5)).finalize(); + let digest = std::mem::replace(&mut self.hasher, CRYPTO_PROVIDER.digest(ShaAlgorithm::MD5)) + .finalize(); match encoding.0 { Some(encoding) => encoded_bytes(ctx, &digest, &encoding), diff --git a/modules/llrt_crypto/src/provider/graviola.rs b/modules/llrt_crypto/src/provider/graviola.rs new file mode 100644 index 0000000000..47a4f96306 --- /dev/null +++ b/modules/llrt_crypto/src/provider/graviola.rs @@ -0,0 +1,399 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Graviola crypto provider - a high-performance crypto library using formally verified assembler. +//! +//! Supported: SHA256/384/512, HMAC, AES-GCM +//! Not supported: Most other operations due to API limitations + +use graviola::{ + aead::AesGcm, + hashing::{hmac::Hmac, Hash, HashContext, Sha256, Sha384, Sha512}, +}; + +use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; +use crate::sha_hash::ShaAlgorithm; +use crate::subtle::EllipticCurve; + +pub struct GraviolaProvider; + +pub enum GraviolaDigest { + Sha256(::Context), + Sha384(::Context), + Sha512(::Context), +} + +impl SimpleDigest for GraviolaDigest { + fn update(&mut self, data: &[u8]) { + match self { + GraviolaDigest::Sha256(h) => h.update(data), + GraviolaDigest::Sha384(h) => h.update(data), + GraviolaDigest::Sha512(h) => h.update(data), + } + } + + fn finalize(self) -> Vec { + match self { + GraviolaDigest::Sha256(h) => h.finish().as_ref().to_vec(), + GraviolaDigest::Sha384(h) => h.finish().as_ref().to_vec(), + GraviolaDigest::Sha512(h) => h.finish().as_ref().to_vec(), + } + } +} + +pub enum GraviolaHmac { + Sha256(Hmac), + Sha384(Hmac), + Sha512(Hmac), +} + +impl HmacProvider for GraviolaHmac { + fn update(&mut self, data: &[u8]) { + match self { + GraviolaHmac::Sha256(h) => h.update(data), + GraviolaHmac::Sha384(h) => h.update(data), + GraviolaHmac::Sha512(h) => h.update(data), + } + } + + fn finalize(self) -> Vec { + match self { + GraviolaHmac::Sha256(h) => h.finish().as_ref().to_vec(), + GraviolaHmac::Sha384(h) => h.finish().as_ref().to_vec(), + GraviolaHmac::Sha512(h) => h.finish().as_ref().to_vec(), + } + } +} + +impl CryptoProvider for GraviolaProvider { + type Digest = GraviolaDigest; + type Hmac = GraviolaHmac; + + fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { + match algorithm { + ShaAlgorithm::SHA256 => GraviolaDigest::Sha256(Sha256::new()), + ShaAlgorithm::SHA384 => GraviolaDigest::Sha384(Sha384::new()), + ShaAlgorithm::SHA512 => GraviolaDigest::Sha512(Sha512::new()), + _ => panic!("Unsupported digest algorithm for Graviola"), + } + } + + fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + match algorithm { + ShaAlgorithm::SHA256 => GraviolaHmac::Sha256(Hmac::::new(key)), + ShaAlgorithm::SHA384 => GraviolaHmac::Sha384(Hmac::::new(key)), + ShaAlgorithm::SHA512 => GraviolaHmac::Sha512(Hmac::::new(key)), + _ => panic!("Unsupported HMAC algorithm for Graviola"), + } + } + + fn ecdsa_sign( + &self, + _curve: EllipticCurve, + _private_key_der: &[u8], + _digest: &[u8], + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn ecdsa_verify( + &self, + _curve: EllipticCurve, + _public_key_sec1: &[u8], + _signature: &[u8], + _digest: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn ed25519_sign(&self, _private_key_der: &[u8], _data: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn ed25519_verify( + &self, + _public_key_bytes: &[u8], + _signature: &[u8], + _data: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pss_sign( + &self, + _private_key_der: &[u8], + _digest: &[u8], + _salt_length: usize, + _hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pss_verify( + &self, + _public_key_der: &[u8], + _signature: &[u8], + _digest: &[u8], + _salt_length: usize, + _hash_alg: ShaAlgorithm, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pkcs1v15_sign( + &self, + _private_key_der: &[u8], + _digest: &[u8], + _hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pkcs1v15_verify( + &self, + _public_key_der: &[u8], + _signature: &[u8], + _digest: &[u8], + _hash_alg: ShaAlgorithm, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_oaep_encrypt( + &self, + _public_key_der: &[u8], + _data: &[u8], + _hash_alg: ShaAlgorithm, + _label: Option<&[u8]>, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_oaep_decrypt( + &self, + _private_key_der: &[u8], + _data: &[u8], + _hash_alg: ShaAlgorithm, + _label: Option<&[u8]>, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn ecdh_derive_bits( + &self, + _curve: EllipticCurve, + _private_key_der: &[u8], + _public_key_sec1: &[u8], + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn x25519_derive_bits( + &self, + _private_key: &[u8], + _public_key: &[u8], + ) -> Result, CryptoError> { + // Graviola doesn't expose from_bytes for X25519 PrivateKey + Err(CryptoError::UnsupportedAlgorithm) + } + + fn aes_encrypt( + &self, + mode: AesMode, + key: &[u8], + iv: &[u8], + data: &[u8], + additional_data: Option<&[u8]>, + ) -> Result, CryptoError> { + match mode { + AesMode::Gcm { .. } => { + let nonce: [u8; 12] = iv.try_into().map_err(|_| CryptoError::InvalidData)?; + if !matches!(key.len(), 16 | 32) { + return Err(CryptoError::InvalidKey); + } + let aead = AesGcm::new(key); + let aad = additional_data.unwrap_or(&[]); + let mut ciphertext = data.to_vec(); + let mut tag = [0u8; 16]; + aead.encrypt(&nonce, aad, &mut ciphertext, &mut tag); + ciphertext.extend_from_slice(&tag); + Ok(ciphertext) + }, + _ => Err(CryptoError::UnsupportedAlgorithm), + } + } + + fn aes_decrypt( + &self, + mode: AesMode, + key: &[u8], + iv: &[u8], + data: &[u8], + additional_data: Option<&[u8]>, + ) -> Result, CryptoError> { + match mode { + AesMode::Gcm { .. } => { + let nonce: [u8; 12] = iv.try_into().map_err(|_| CryptoError::InvalidData)?; + if !matches!(key.len(), 16 | 32) { + return Err(CryptoError::InvalidKey); + } + if data.len() < 16 { + return Err(CryptoError::InvalidData); + } + let aead = AesGcm::new(key); + let aad = additional_data.unwrap_or(&[]); + let (ciphertext, tag) = data.split_at(data.len() - 16); + let tag: [u8; 16] = tag.try_into().unwrap(); + let mut plaintext = ciphertext.to_vec(); + aead.decrypt(&nonce, aad, &mut plaintext, &tag) + .map_err(|_| CryptoError::DecryptionFailed)?; + Ok(plaintext) + }, + _ => Err(CryptoError::UnsupportedAlgorithm), + } + } + + fn aes_kw_wrap(&self, _kek: &[u8], _key: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn aes_kw_unwrap(&self, _kek: &[u8], _wrapped_key: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn hkdf_derive_key( + &self, + _key: &[u8], + _salt: &[u8], + _info: &[u8], + _length: usize, + _hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn pbkdf2_derive_key( + &self, + _password: &[u8], + _salt: &[u8], + _iterations: u32, + _length: usize, + _hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn generate_aes_key(&self, length_bits: u16) -> Result, CryptoError> { + if !matches!(length_bits, 128 | 256) { + return Err(CryptoError::InvalidLength); + } + Ok(crate::random_byte_array((length_bits / 8) as usize)) + } + + fn generate_hmac_key( + &self, + hash_alg: ShaAlgorithm, + length_bits: u16, + ) -> Result, CryptoError> { + let length_bytes = if length_bits == 0 { + match hash_alg { + ShaAlgorithm::SHA256 => 64, + ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 => 128, + _ => return Err(CryptoError::UnsupportedAlgorithm), + } + } else { + (length_bits / 8) as usize + }; + Ok(crate::random_byte_array(length_bytes)) + } + + fn generate_ec_key(&self, _curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { + // Graviola doesn't expose as_bytes for X25519 PrivateKey + Err(CryptoError::UnsupportedAlgorithm) + } + + fn generate_rsa_key( + &self, + _modulus_length: u32, + _public_exponent: &[u8], + ) -> Result<(Vec, Vec), CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } +} + +// Hybrid types for graviola-rust: Graviola for SHA256/384/512, RustCrypto for MD5/SHA1 +#[cfg(feature = "crypto-graviola-rust")] +pub enum GraviolaRustDigest { + Graviola(GraviolaDigest), + Rust(super::rust::RustDigest), +} + +#[cfg(feature = "crypto-graviola-rust")] +impl GraviolaRustDigest { + pub fn new(algorithm: ShaAlgorithm) -> Self { + match algorithm { + ShaAlgorithm::SHA256 | ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 => { + Self::Graviola(GraviolaProvider.digest(algorithm)) + }, + _ => Self::Rust(super::rust::RustCryptoProvider.digest(algorithm)), + } + } +} + +#[cfg(feature = "crypto-graviola-rust")] +impl SimpleDigest for GraviolaRustDigest { + fn update(&mut self, data: &[u8]) { + match self { + Self::Graviola(d) => d.update(data), + Self::Rust(d) => d.update(data), + } + } + fn finalize(self) -> Vec { + match self { + Self::Graviola(d) => d.finalize(), + Self::Rust(d) => d.finalize(), + } + } +} + +#[cfg(feature = "crypto-graviola-rust")] +pub enum GraviolaRustHmac { + Graviola(GraviolaHmac), + Rust(super::rust::RustHmac), +} + +#[cfg(feature = "crypto-graviola-rust")] +impl GraviolaRustHmac { + pub fn new(algorithm: ShaAlgorithm, key: &[u8]) -> Self { + match algorithm { + ShaAlgorithm::SHA256 | ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 => { + Self::Graviola(GraviolaProvider.hmac(algorithm, key)) + }, + _ => Self::Rust(super::rust::RustCryptoProvider.hmac(algorithm, key)), + } + } +} + +#[cfg(feature = "crypto-graviola-rust")] +impl HmacProvider for GraviolaRustHmac { + fn update(&mut self, data: &[u8]) { + match self { + Self::Graviola(h) => h.update(data), + Self::Rust(h) => h.update(data), + } + } + fn finalize(self) -> Vec { + match self { + Self::Graviola(h) => h.finalize(), + Self::Rust(h) => h.finalize(), + } + } +} diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index c0c6c2db19..97bc088309 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -1,11 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +#[cfg(any(feature = "crypto-graviola", feature = "crypto-graviola-rust"))] +mod graviola; #[cfg(feature = "crypto-openssl")] mod openssl; -#[cfg(feature = "crypto-ring")] +#[cfg(any(feature = "crypto-ring", feature = "crypto-ring-rust"))] mod ring; -#[cfg(feature = "crypto-rust")] +#[cfg(any( + feature = "crypto-rust", + feature = "crypto-ring-rust", + feature = "crypto-graviola-rust", + feature = "crypto-openssl" +))] mod rust; use crate::sha_hash::ShaAlgorithm; @@ -220,3 +227,627 @@ pub type DefaultProvider = rust::RustCryptoProvider; #[cfg(feature = "crypto-ring")] pub type DefaultProvider = ring::RingProvider; + +#[cfg(feature = "crypto-ring-rust")] +pub type DefaultProvider = RingRustProvider; + +#[cfg(feature = "crypto-graviola")] +pub type DefaultProvider = graviola::GraviolaProvider; + +#[cfg(feature = "crypto-graviola-rust")] +pub type DefaultProvider = GraviolaRustProvider; + +// Macro to generate hybrid providers that delegate to RustCrypto +#[cfg(any(feature = "crypto-ring-rust", feature = "crypto-graviola-rust"))] +macro_rules! impl_hybrid_provider { + ($name:ident, $digest:ty, $hmac:ty, $digest_fn:expr, $hmac_fn:expr, $aes_encrypt:expr, $aes_decrypt:expr) => { + pub struct $name; + impl CryptoProvider for $name { + type Digest = $digest; + type Hmac = $hmac; + fn digest(&self, alg: ShaAlgorithm) -> Self::Digest { + $digest_fn(alg) + } + fn hmac(&self, alg: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + $hmac_fn(alg, key) + } + fn ecdsa_sign( + &self, + c: EllipticCurve, + k: &[u8], + d: &[u8], + ) -> Result, CryptoError> { + rust::RustCryptoProvider.ecdsa_sign(c, k, d) + } + fn ecdsa_verify( + &self, + c: EllipticCurve, + k: &[u8], + s: &[u8], + d: &[u8], + ) -> Result { + rust::RustCryptoProvider.ecdsa_verify(c, k, s, d) + } + fn ed25519_sign(&self, k: &[u8], d: &[u8]) -> Result, CryptoError> { + rust::RustCryptoProvider.ed25519_sign(k, d) + } + fn ed25519_verify(&self, k: &[u8], s: &[u8], d: &[u8]) -> Result { + rust::RustCryptoProvider.ed25519_verify(k, s, d) + } + fn rsa_pss_sign( + &self, + k: &[u8], + d: &[u8], + s: usize, + a: ShaAlgorithm, + ) -> Result, CryptoError> { + rust::RustCryptoProvider.rsa_pss_sign(k, d, s, a) + } + fn rsa_pss_verify( + &self, + k: &[u8], + s: &[u8], + d: &[u8], + sl: usize, + a: ShaAlgorithm, + ) -> Result { + rust::RustCryptoProvider.rsa_pss_verify(k, s, d, sl, a) + } + fn rsa_pkcs1v15_sign( + &self, + k: &[u8], + d: &[u8], + a: ShaAlgorithm, + ) -> Result, CryptoError> { + rust::RustCryptoProvider.rsa_pkcs1v15_sign(k, d, a) + } + fn rsa_pkcs1v15_verify( + &self, + k: &[u8], + s: &[u8], + d: &[u8], + a: ShaAlgorithm, + ) -> Result { + rust::RustCryptoProvider.rsa_pkcs1v15_verify(k, s, d, a) + } + fn rsa_oaep_encrypt( + &self, + k: &[u8], + d: &[u8], + a: ShaAlgorithm, + l: Option<&[u8]>, + ) -> Result, CryptoError> { + rust::RustCryptoProvider.rsa_oaep_encrypt(k, d, a, l) + } + fn rsa_oaep_decrypt( + &self, + k: &[u8], + d: &[u8], + a: ShaAlgorithm, + l: Option<&[u8]>, + ) -> Result, CryptoError> { + rust::RustCryptoProvider.rsa_oaep_decrypt(k, d, a, l) + } + fn ecdh_derive_bits( + &self, + c: EllipticCurve, + pk: &[u8], + pubk: &[u8], + ) -> Result, CryptoError> { + rust::RustCryptoProvider.ecdh_derive_bits(c, pk, pubk) + } + fn x25519_derive_bits(&self, pk: &[u8], pubk: &[u8]) -> Result, CryptoError> { + rust::RustCryptoProvider.x25519_derive_bits(pk, pubk) + } + fn aes_encrypt( + &self, + m: AesMode, + k: &[u8], + iv: &[u8], + d: &[u8], + aad: Option<&[u8]>, + ) -> Result, CryptoError> { + $aes_encrypt(m, k, iv, d, aad) + } + fn aes_decrypt( + &self, + m: AesMode, + k: &[u8], + iv: &[u8], + d: &[u8], + aad: Option<&[u8]>, + ) -> Result, CryptoError> { + $aes_decrypt(m, k, iv, d, aad) + } + fn aes_kw_wrap(&self, kek: &[u8], k: &[u8]) -> Result, CryptoError> { + rust::RustCryptoProvider.aes_kw_wrap(kek, k) + } + fn aes_kw_unwrap(&self, kek: &[u8], w: &[u8]) -> Result, CryptoError> { + rust::RustCryptoProvider.aes_kw_unwrap(kek, w) + } + fn hkdf_derive_key( + &self, + k: &[u8], + s: &[u8], + i: &[u8], + l: usize, + a: ShaAlgorithm, + ) -> Result, CryptoError> { + rust::RustCryptoProvider.hkdf_derive_key(k, s, i, l, a) + } + fn pbkdf2_derive_key( + &self, + p: &[u8], + s: &[u8], + i: u32, + l: usize, + a: ShaAlgorithm, + ) -> Result, CryptoError> { + rust::RustCryptoProvider.pbkdf2_derive_key(p, s, i, l, a) + } + fn generate_aes_key(&self, b: u16) -> Result, CryptoError> { + rust::RustCryptoProvider.generate_aes_key(b) + } + fn generate_hmac_key(&self, a: ShaAlgorithm, b: u16) -> Result, CryptoError> { + rust::RustCryptoProvider.generate_hmac_key(a, b) + } + fn generate_ec_key(&self, c: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { + rust::RustCryptoProvider.generate_ec_key(c) + } + fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { + rust::RustCryptoProvider.generate_ed25519_key() + } + fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { + rust::RustCryptoProvider.generate_x25519_key() + } + fn generate_rsa_key( + &self, + b: u32, + e: &[u8], + ) -> Result<(Vec, Vec), CryptoError> { + rust::RustCryptoProvider.generate_rsa_key(b, e) + } + } + }; +} + +#[cfg(feature = "crypto-ring-rust")] +impl_hybrid_provider!( + RingRustProvider, + ring::RingDigestType, + ring::RingHmacType, + |a| ring::RingProvider.digest(a), + |a, k| ring::RingProvider.hmac(a, k), + |m, k, iv, d, aad| rust::RustCryptoProvider.aes_encrypt(m, k, iv, d, aad), + |m, k, iv, d, aad| rust::RustCryptoProvider.aes_decrypt(m, k, iv, d, aad) +); + +#[cfg(feature = "crypto-graviola-rust")] +impl_hybrid_provider!( + GraviolaRustProvider, + graviola::GraviolaRustDigest, + graviola::GraviolaRustHmac, + graviola::GraviolaRustDigest::new, + graviola::GraviolaRustHmac::new, + |m: AesMode, k: &[u8], iv: &[u8], d: &[u8], aad: Option<&[u8]>| match m { + // Graviola only supports AES-128 and AES-256, fall back to RustCrypto for AES-192 + AesMode::Gcm { .. } if matches!(k.len(), 16 | 32) => + graviola::GraviolaProvider.aes_encrypt(m, k, iv, d, aad), + _ => rust::RustCryptoProvider.aes_encrypt(m, k, iv, d, aad), + }, + |m: AesMode, k: &[u8], iv: &[u8], d: &[u8], aad: Option<&[u8]>| match m { + // Graviola only supports AES-128 and AES-256, fall back to RustCrypto for AES-192 + AesMode::Gcm { .. } if matches!(k.len(), 16 | 32) => + graviola::GraviolaProvider.aes_decrypt(m, k, iv, d, aad), + _ => rust::RustCryptoProvider.aes_decrypt(m, k, iv, d, aad), + } +); + +#[cfg(test)] +mod tests { + use super::*; + + fn provider() -> impl CryptoProvider { + #[cfg(feature = "crypto-rust")] + return rust::RustCryptoProvider; + #[cfg(feature = "crypto-ring-rust")] + return RingRustProvider; + #[cfg(feature = "crypto-graviola-rust")] + return GraviolaRustProvider; + #[cfg(feature = "crypto-openssl")] + return openssl::OpenSslProvider; + #[cfg(feature = "crypto-ring")] + return ring::RingProvider; + #[cfg(feature = "crypto-graviola")] + return graviola::GraviolaProvider; + } + + fn to_hex(bytes: &[u8]) -> String { + bytes.iter().map(|b| format!("{:02x}", b)).collect() + } + + // SHA digest tests + #[test] + fn test_sha256_digest() { + let p = provider(); + let mut digest = p.digest(ShaAlgorithm::SHA256); + digest.update(b"hello world"); + let result = digest.finalize(); + assert_eq!(result.len(), 32); + assert_eq!( + to_hex(&result), + "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" + ); + } + + #[test] + fn test_sha384_digest() { + let p = provider(); + let mut digest = p.digest(ShaAlgorithm::SHA384); + digest.update(b"hello world"); + let result = digest.finalize(); + assert_eq!(result.len(), 48); + } + + #[test] + fn test_sha512_digest() { + let p = provider(); + let mut digest = p.digest(ShaAlgorithm::SHA512); + digest.update(b"hello world"); + let result = digest.finalize(); + assert_eq!(result.len(), 64); + } + + // HMAC tests + #[test] + fn test_hmac_sha256() { + let p = provider(); + let key = b"secret key"; + let mut hmac = p.hmac(ShaAlgorithm::SHA256, key); + hmac.update(b"hello world"); + let result = hmac.finalize(); + assert_eq!(result.len(), 32); + } + + // AES-GCM tests + #[test] + fn test_aes_gcm_128_roundtrip() { + let p = provider(); + let key = [0u8; 16]; + let iv = [0u8; 12]; + let plaintext = b"hello world"; + let aad = b"additional data"; + + let ciphertext = p + .aes_encrypt( + AesMode::Gcm { tag_length: 128 }, + &key, + &iv, + plaintext, + Some(aad), + ) + .unwrap(); + + assert_eq!(ciphertext.len(), plaintext.len() + 16); // plaintext + tag + + let decrypted = p + .aes_decrypt( + AesMode::Gcm { tag_length: 128 }, + &key, + &iv, + &ciphertext, + Some(aad), + ) + .unwrap(); + + assert_eq!(decrypted, plaintext); + } + + #[test] + fn test_aes_gcm_256_roundtrip() { + let p = provider(); + let key = [0u8; 32]; + let iv = [0u8; 12]; + let plaintext = b"hello world"; + + let ciphertext = p + .aes_encrypt(AesMode::Gcm { tag_length: 128 }, &key, &iv, plaintext, None) + .unwrap(); + + let decrypted = p + .aes_decrypt( + AesMode::Gcm { tag_length: 128 }, + &key, + &iv, + &ciphertext, + None, + ) + .unwrap(); + + assert_eq!(decrypted, plaintext); + } + + #[test] + fn test_aes_gcm_wrong_key_fails() { + let p = provider(); + let key = [0u8; 16]; + let wrong_key = [1u8; 16]; + let iv = [0u8; 12]; + let plaintext = b"hello world"; + + let ciphertext = p + .aes_encrypt(AesMode::Gcm { tag_length: 128 }, &key, &iv, plaintext, None) + .unwrap(); + + let result = p.aes_decrypt( + AesMode::Gcm { tag_length: 128 }, + &wrong_key, + &iv, + &ciphertext, + None, + ); + + assert!(result.is_err()); + } + + // Key generation tests + #[test] + fn test_generate_aes_key_128() { + let p = provider(); + let key = p.generate_aes_key(128).unwrap(); + assert_eq!(key.len(), 16); + } + + #[test] + fn test_generate_aes_key_256() { + let p = provider(); + let key = p.generate_aes_key(256).unwrap(); + assert_eq!(key.len(), 32); + } + + #[test] + fn test_generate_hmac_key() { + let p = provider(); + let key = p.generate_hmac_key(ShaAlgorithm::SHA256, 256).unwrap(); + assert_eq!(key.len(), 32); + } + + // Tests that require full crypto support + #[cfg(any( + feature = "crypto-rust", + feature = "crypto-openssl", + feature = "crypto-ring-rust", + feature = "crypto-graviola-rust" + ))] + mod full_provider_tests { + use super::*; + + #[test] + fn test_aes_cbc_roundtrip() { + let p = provider(); + let key = [0u8; 16]; + let iv = [0u8; 16]; + let plaintext = b"hello world12345"; // 16 bytes for block alignment + + let ciphertext = p + .aes_encrypt(AesMode::Cbc, &key, &iv, plaintext, None) + .unwrap(); + + let decrypted = p + .aes_decrypt(AesMode::Cbc, &key, &iv, &ciphertext, None) + .unwrap(); + + assert_eq!(decrypted, plaintext); + } + + #[test] + fn test_aes_ctr_roundtrip() { + let p = provider(); + let key = [0u8; 16]; + let iv = [0u8; 16]; + let plaintext = b"hello world"; + + let ciphertext = p + .aes_encrypt( + AesMode::Ctr { counter_length: 64 }, + &key, + &iv, + plaintext, + None, + ) + .unwrap(); + + let decrypted = p + .aes_decrypt( + AesMode::Ctr { counter_length: 64 }, + &key, + &iv, + &ciphertext, + None, + ) + .unwrap(); + + assert_eq!(decrypted, plaintext); + } + + #[test] + fn test_aes_kw_roundtrip() { + let p = provider(); + let kek = [0u8; 16]; + let key_to_wrap = [1u8; 16]; + + let wrapped = p.aes_kw_wrap(&kek, &key_to_wrap).unwrap(); + let unwrapped = p.aes_kw_unwrap(&kek, &wrapped).unwrap(); + + assert_eq!(unwrapped, key_to_wrap); + } + + #[test] + fn test_hkdf_derive() { + let p = provider(); + let ikm = b"input key material"; + let salt = b"salt"; + let info = b"info"; + + let derived = p + .hkdf_derive_key(ikm, salt, info, 32, ShaAlgorithm::SHA256) + .unwrap(); + + assert_eq!(derived.len(), 32); + } + + #[test] + fn test_pbkdf2_derive() { + let p = provider(); + let password = b"password"; + let salt = b"salt"; + + let derived = p + .pbkdf2_derive_key(password, salt, 1000, 32, ShaAlgorithm::SHA256) + .unwrap(); + + assert_eq!(derived.len(), 32); + } + + #[test] + fn test_ec_p256_sign_verify() { + let p = provider(); + let (private_key, public_key) = p.generate_ec_key(EllipticCurve::P256).unwrap(); + + // Create a digest to sign + let mut digest = p.digest(ShaAlgorithm::SHA256); + digest.update(b"message to sign"); + let hash = digest.finalize(); + + let signature = p + .ecdsa_sign(EllipticCurve::P256, &private_key, &hash) + .unwrap(); + + let valid = p + .ecdsa_verify(EllipticCurve::P256, &public_key, &signature, &hash) + .unwrap(); + + assert!(valid); + } + + #[test] + fn test_ec_p384_sign_verify() { + let p = provider(); + let (private_key, public_key) = p.generate_ec_key(EllipticCurve::P384).unwrap(); + + let mut digest = p.digest(ShaAlgorithm::SHA384); + digest.update(b"message to sign"); + let hash = digest.finalize(); + + let signature = p + .ecdsa_sign(EllipticCurve::P384, &private_key, &hash) + .unwrap(); + + let valid = p + .ecdsa_verify(EllipticCurve::P384, &public_key, &signature, &hash) + .unwrap(); + + assert!(valid); + } + + #[test] + fn test_ed25519_sign_verify() { + let p = provider(); + let (private_key, public_key) = p.generate_ed25519_key().unwrap(); + + let message = b"message to sign"; + let signature = p.ed25519_sign(&private_key, message).unwrap(); + + let valid = p.ed25519_verify(&public_key, &signature, message).unwrap(); + + assert!(valid); + } + + #[test] + fn test_x25519_key_exchange() { + let p = provider(); + let (alice_private, alice_public) = p.generate_x25519_key().unwrap(); + let (bob_private, bob_public) = p.generate_x25519_key().unwrap(); + + let alice_shared = p.x25519_derive_bits(&alice_private, &bob_public).unwrap(); + let bob_shared = p.x25519_derive_bits(&bob_private, &alice_public).unwrap(); + + assert_eq!(alice_shared, bob_shared); + assert_eq!(alice_shared.len(), 32); + } + + #[test] + fn test_ecdh_p256_key_exchange() { + let p = provider(); + let (alice_private, alice_public) = p.generate_ec_key(EllipticCurve::P256).unwrap(); + let (bob_private, bob_public) = p.generate_ec_key(EllipticCurve::P256).unwrap(); + + let alice_shared = p + .ecdh_derive_bits(EllipticCurve::P256, &alice_private, &bob_public) + .unwrap(); + let bob_shared = p + .ecdh_derive_bits(EllipticCurve::P256, &bob_private, &alice_public) + .unwrap(); + + assert_eq!(alice_shared, bob_shared); + } + + #[test] + fn test_rsa_pss_sign_verify() { + let p = provider(); + let (private_key, public_key) = p.generate_rsa_key(2048, &[1, 0, 1]).unwrap(); + + let mut digest = p.digest(ShaAlgorithm::SHA256); + digest.update(b"message to sign"); + let hash = digest.finalize(); + + let signature = p + .rsa_pss_sign(&private_key, &hash, 32, ShaAlgorithm::SHA256) + .unwrap(); + + let valid = p + .rsa_pss_verify(&public_key, &signature, &hash, 32, ShaAlgorithm::SHA256) + .unwrap(); + + assert!(valid); + } + + #[test] + fn test_rsa_pkcs1v15_sign_verify() { + let p = provider(); + let (private_key, public_key) = p.generate_rsa_key(2048, &[1, 0, 1]).unwrap(); + + let mut digest = p.digest(ShaAlgorithm::SHA256); + digest.update(b"message to sign"); + let hash = digest.finalize(); + + let signature = p + .rsa_pkcs1v15_sign(&private_key, &hash, ShaAlgorithm::SHA256) + .unwrap(); + + let valid = p + .rsa_pkcs1v15_verify(&public_key, &signature, &hash, ShaAlgorithm::SHA256) + .unwrap(); + + assert!(valid); + } + + #[test] + fn test_rsa_oaep_encrypt_decrypt() { + let p = provider(); + let (private_key, public_key) = p.generate_rsa_key(2048, &[1, 0, 1]).unwrap(); + + let plaintext = b"secret message"; + + let ciphertext = p + .rsa_oaep_encrypt(&public_key, plaintext, ShaAlgorithm::SHA256, None) + .unwrap(); + + let decrypted = p + .rsa_oaep_decrypt(&private_key, &ciphertext, ShaAlgorithm::SHA256, None) + .unwrap(); + + assert_eq!(decrypted, plaintext); + } + } +} diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs index 688bdca647..7c64b40b15 100644 --- a/modules/llrt_crypto/src/provider/openssl.rs +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -1,467 +1,308 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use crate::provider::{ - AesGcmProvider, AesKwProvider, AesProvider, CryptoError, CryptoProvider, EcdhProvider, - EcdsaProvider, EdDsaProvider, HmacProvider, KdfProvider, RsaProvider, X25519Provider, -}; +//! OpenSSL crypto provider - uses OpenSSL for cryptographic operations. -pub struct OpenSslProvider; - -// Stub implementations - would be replaced with actual OpenSSL bindings - -pub struct OpenSslMd5; - -pub struct OpenSslSha1; - -pub struct OpenSslSha256; - -pub struct OpenSslSha384; - -pub struct OpenSslSha512; - -pub struct OpenSslHmacSha1; - -pub struct OpenSslHmacSha256; - -pub struct OpenSslHmacSha384; - -pub struct OpenSslHmacSha512; - -pub struct OpenSslRsa; - -pub struct OpenSslEcdsa; - -pub struct OpenSslEcdh; - -pub struct OpenSslEd25519; - -pub struct OpenSslX25519; - -pub struct OpenSslAes; - -pub struct OpenSslAesGcm; +use openssl::hash::{Hasher, MessageDigest}; +use openssl::pkey::{PKey, Private}; +use openssl::sign::{Signer, Verifier}; +use openssl::symm::{Cipher, Crypter, Mode}; -pub struct OpenSslAesKw; +use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; +use crate::sha_hash::ShaAlgorithm; +use crate::subtle::EllipticCurve; -pub struct OpenSslKdf; +pub struct OpenSslProvider; -impl digest::Digest for OpenSslMd5 { - type OutputSize = digest::consts::U16; - fn new() -> Self { - OpenSslMd5 - } - fn update(&mut self, _data: impl AsRef<[u8]>) {} - fn chain_update(self, _data: impl AsRef<[u8]>) -> Self { - self - } - fn finalize(self) -> digest::Output { - [0u8; 16].into() - } - fn finalize_into(self, _out: &mut digest::Output) {} - fn finalize_reset(&mut self) -> digest::Output { - [0u8; 16].into() - } - fn reset(&mut self) {} - fn output_size() -> usize { - 16 - } - fn digest(_data: impl AsRef<[u8]>) -> digest::Output { - [0u8; 16].into() - } +pub enum OpenSslDigest { + Md5(Hasher), + Sha1(Hasher), + Sha256(Hasher), + Sha384(Hasher), + Sha512(Hasher), } -impl Clone for OpenSslMd5 { - fn clone(&self) -> Self { - OpenSslMd5 +impl SimpleDigest for OpenSslDigest { + fn update(&mut self, data: &[u8]) { + match self { + OpenSslDigest::Md5(h) + | OpenSslDigest::Sha1(h) + | OpenSslDigest::Sha256(h) + | OpenSslDigest::Sha384(h) + | OpenSslDigest::Sha512(h) => { + let _ = h.update(data); + }, + } + } + + fn finalize(mut self) -> Vec { + match self { + OpenSslDigest::Md5(ref mut h) + | OpenSslDigest::Sha1(ref mut h) + | OpenSslDigest::Sha256(ref mut h) + | OpenSslDigest::Sha384(ref mut h) + | OpenSslDigest::Sha512(ref mut h) => { + h.finish().map(|d| d.to_vec()).unwrap_or_default() + }, + } } } -impl digest::Digest for OpenSslSha1 { - type OutputSize = digest::consts::U20; - fn new() -> Self { - OpenSslSha1 - } - fn update(&mut self, _data: impl AsRef<[u8]>) {} - fn chain_update(self, _data: impl AsRef<[u8]>) -> Self { - self - } - fn finalize(self) -> digest::Output { - [0u8; 20].into() - } - fn finalize_into(self, _out: &mut digest::Output) {} - fn finalize_reset(&mut self) -> digest::Output { - [0u8; 20].into() - } - fn reset(&mut self) {} - fn output_size() -> usize { - 20 - } - fn digest(_data: impl AsRef<[u8]>) -> digest::Output { - [0u8; 20].into() - } +pub struct OpenSslHmac { + signer: Signer<'static>, } -impl digest::Digest for OpenSslSha256 { - type OutputSize = digest::consts::U32; - fn new() -> Self { - OpenSslSha256 - } - fn update(&mut self, _data: impl AsRef<[u8]>) {} - fn chain_update(self, _data: impl AsRef<[u8]>) -> Self { - self - } - fn finalize(self) -> digest::Output { - [0u8; 32].into() - } - fn finalize_into(self, _out: &mut digest::Output) {} - fn finalize_reset(&mut self) -> digest::Output { - [0u8; 32].into() - } - fn reset(&mut self) {} - fn output_size() -> usize { - 32 +impl HmacProvider for OpenSslHmac { + fn update(&mut self, data: &[u8]) { + let _ = self.signer.update(data); } - fn digest(_data: impl AsRef<[u8]>) -> digest::Output { - [0u8; 32].into() - } -} -impl digest::Digest for OpenSslSha384 { - type OutputSize = digest::consts::U48; - fn new() -> Self { - OpenSslSha384 - } - fn update(&mut self, _data: impl AsRef<[u8]>) {} - fn chain_update(self, _data: impl AsRef<[u8]>) -> Self { - self - } - fn finalize(self) -> digest::Output { - [0u8; 48].into() - } - fn finalize_into(self, _out: &mut digest::Output) {} - fn finalize_reset(&mut self) -> digest::Output { - [0u8; 48].into() - } - fn reset(&mut self) {} - fn output_size() -> usize { - 48 - } - fn digest(_data: impl AsRef<[u8]>) -> digest::Output { - [0u8; 48].into() - } -} - -impl digest::Digest for OpenSslSha512 { - type OutputSize = digest::consts::U64; - fn new() -> Self { - OpenSslSha512 - } - fn update(&mut self, _data: impl AsRef<[u8]>) {} - fn chain_update(self, _data: impl AsRef<[u8]>) -> Self { - self - } - fn finalize(self) -> digest::Output { - [0u8; 64].into() - } - fn finalize_into(self, _out: &mut digest::Output) {} - fn finalize_reset(&mut self) -> digest::Output { - [0u8; 64].into() - } - fn reset(&mut self) {} - fn output_size() -> usize { - 64 - } - fn digest(_data: impl AsRef<[u8]>) -> digest::Output { - [0u8; 64].into() + fn finalize(self) -> Vec { + self.signer.sign_to_vec().unwrap_or_default() } } -impl Clone for OpenSslSha1 { - fn clone(&self) -> Self { - OpenSslSha1 +fn get_message_digest(alg: ShaAlgorithm) -> MessageDigest { + match alg { + ShaAlgorithm::MD5 => MessageDigest::md5(), + ShaAlgorithm::SHA1 => MessageDigest::sha1(), + ShaAlgorithm::SHA256 => MessageDigest::sha256(), + ShaAlgorithm::SHA384 => MessageDigest::sha384(), + ShaAlgorithm::SHA512 => MessageDigest::sha512(), } } -impl Clone for OpenSslSha256 { - fn clone(&self) -> Self { - OpenSslSha256 +impl CryptoProvider for OpenSslProvider { + type Digest = OpenSslDigest; + type Hmac = OpenSslHmac; + + fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { + let md = get_message_digest(algorithm); + let hasher = Hasher::new(md).expect("Failed to create hasher"); + match algorithm { + ShaAlgorithm::MD5 => OpenSslDigest::Md5(hasher), + ShaAlgorithm::SHA1 => OpenSslDigest::Sha1(hasher), + ShaAlgorithm::SHA256 => OpenSslDigest::Sha256(hasher), + ShaAlgorithm::SHA384 => OpenSslDigest::Sha384(hasher), + ShaAlgorithm::SHA512 => OpenSslDigest::Sha512(hasher), + } + } + + fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + let md = get_message_digest(algorithm); + let pkey = PKey::hmac(key).expect("Failed to create HMAC key"); + // SAFETY: We're creating a static lifetime signer, but we own the key + let signer = unsafe { + std::mem::transmute::, Signer<'static>>( + Signer::new(md, &pkey).expect("Failed to create signer"), + ) + }; + OpenSslHmac { signer } + } + + // Delegate to RustCrypto for complex operations + fn ecdsa_sign( + &self, + curve: EllipticCurve, + private_key_der: &[u8], + digest: &[u8], + ) -> Result, CryptoError> { + super::rust::RustCryptoProvider.ecdsa_sign(curve, private_key_der, digest) } -} -impl Clone for OpenSslSha384 { - fn clone(&self) -> Self { - OpenSslSha384 + fn ecdsa_verify( + &self, + curve: EllipticCurve, + public_key_sec1: &[u8], + signature: &[u8], + digest: &[u8], + ) -> Result { + super::rust::RustCryptoProvider.ecdsa_verify(curve, public_key_sec1, signature, digest) } -} -impl Clone for OpenSslSha512 { - fn clone(&self) -> Self { - OpenSslSha512 + fn ed25519_sign(&self, private_key_der: &[u8], data: &[u8]) -> Result, CryptoError> { + super::rust::RustCryptoProvider.ed25519_sign(private_key_der, data) } -} -// Similar implementations for other hash algorithms would go here... - -impl HmacProvider for OpenSslHmacSha1 { - fn update(&mut self, _data: &[u8]) {} - fn finalize(self) -> Vec { - vec![0u8; 20] + fn ed25519_verify( + &self, + public_key_bytes: &[u8], + signature: &[u8], + data: &[u8], + ) -> Result { + super::rust::RustCryptoProvider.ed25519_verify(public_key_bytes, signature, data) } -} -impl HmacProvider for OpenSslHmacSha256 { - fn update(&mut self, _data: &[u8]) {} - fn finalize(self) -> Vec { - vec![0u8; 32] + fn rsa_pss_sign( + &self, + private_key_der: &[u8], + digest: &[u8], + salt_length: usize, + hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + super::rust::RustCryptoProvider.rsa_pss_sign(private_key_der, digest, salt_length, hash_alg) } -} -impl HmacProvider for OpenSslHmacSha384 { - fn update(&mut self, _data: &[u8]) {} - fn finalize(self) -> Vec { - vec![0u8; 48] + fn rsa_pss_verify( + &self, + public_key_der: &[u8], + signature: &[u8], + digest: &[u8], + salt_length: usize, + hash_alg: ShaAlgorithm, + ) -> Result { + super::rust::RustCryptoProvider.rsa_pss_verify( + public_key_der, + signature, + digest, + salt_length, + hash_alg, + ) } -} -impl HmacProvider for OpenSslHmacSha512 { - fn update(&mut self, _data: &[u8]) {} - fn finalize(self) -> Vec { - vec![0u8; 64] + fn rsa_pkcs1v15_sign( + &self, + private_key_der: &[u8], + digest: &[u8], + hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + super::rust::RustCryptoProvider.rsa_pkcs1v15_sign(private_key_der, digest, hash_alg) } -} -impl RsaProvider for OpenSslRsa { - fn generate_key(&self, _modulus_length: usize) -> Result<(Vec, Vec), CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) - } - fn sign(&self, _private_key: &[u8], _data: &[u8]) -> Result, CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) - } - fn verify( + fn rsa_pkcs1v15_verify( &self, - _public_key: &[u8], - _signature: &[u8], - _data: &[u8], + public_key_der: &[u8], + signature: &[u8], + digest: &[u8], + hash_alg: ShaAlgorithm, ) -> Result { - Err(CryptoError::UnsupportedAlgorithm) + super::rust::RustCryptoProvider.rsa_pkcs1v15_verify( + public_key_der, + signature, + digest, + hash_alg, + ) } - fn encrypt(&self, _public_key: &[u8], _data: &[u8]) -> Result, CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) - } - fn decrypt(&self, _private_key: &[u8], _data: &[u8]) -> Result, CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) - } -} - -// Similar stub implementations for other providers... - -impl EcdsaProvider for OpenSslEcdsa { - fn generate_key(&self) -> Result<(Vec, Vec), CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) - } - fn sign(&self, _: &[u8], _: &[u8]) -> Result, CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) - } - fn verify(&self, _: &[u8], _: &[u8], _: &[u8]) -> Result { - Err(CryptoError::UnsupportedAlgorithm) - } -} -impl EcdhProvider for OpenSslEcdh { - fn generate_key(&self) -> Result<(Vec, Vec), CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) - } - fn derive_bits(&self, _: &[u8], _: &[u8]) -> Result, CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) + fn rsa_oaep_encrypt( + &self, + public_key_der: &[u8], + data: &[u8], + hash_alg: ShaAlgorithm, + label: Option<&[u8]>, + ) -> Result, CryptoError> { + super::rust::RustCryptoProvider.rsa_oaep_encrypt(public_key_der, data, hash_alg, label) } -} -impl EdDsaProvider for OpenSslEd25519 { - fn generate_key(&self) -> Result<(Vec, Vec), CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) - } - fn sign(&self, _: &[u8], _: &[u8]) -> Result, CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) - } - fn verify(&self, _: &[u8], _: &[u8], _: &[u8]) -> Result { - Err(CryptoError::UnsupportedAlgorithm) + fn rsa_oaep_decrypt( + &self, + private_key_der: &[u8], + data: &[u8], + hash_alg: ShaAlgorithm, + label: Option<&[u8]>, + ) -> Result, CryptoError> { + super::rust::RustCryptoProvider.rsa_oaep_decrypt(private_key_der, data, hash_alg, label) } -} -impl X25519Provider for OpenSslX25519 { - fn generate_key(&self) -> Result<(Vec, Vec), CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) - } - fn derive_bits(&self, _: &[u8], _: &[u8]) -> Result, CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) + fn ecdh_derive_bits( + &self, + curve: EllipticCurve, + private_key_der: &[u8], + public_key_sec1: &[u8], + ) -> Result, CryptoError> { + super::rust::RustCryptoProvider.ecdh_derive_bits(curve, private_key_der, public_key_sec1) } -} -impl AesProvider for OpenSslAes { - fn encrypt(&self, _: &[u8], _: &[u8], _: &[u8]) -> Result, CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) - } - fn decrypt(&self, _: &[u8], _: &[u8], _: &[u8]) -> Result, CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) + fn x25519_derive_bits( + &self, + private_key: &[u8], + public_key: &[u8], + ) -> Result, CryptoError> { + super::rust::RustCryptoProvider.x25519_derive_bits(private_key, public_key) } -} -impl AesGcmProvider for OpenSslAesGcm { - fn encrypt( + fn aes_encrypt( &self, - _: &[u8], - _: &[u8], - _: &[u8], - _: &[u8], - ) -> Result<(Vec, Vec), CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) + mode: AesMode, + key: &[u8], + iv: &[u8], + data: &[u8], + additional_data: Option<&[u8]>, + ) -> Result, CryptoError> { + super::rust::RustCryptoProvider.aes_encrypt(mode, key, iv, data, additional_data) } - fn decrypt( + + fn aes_decrypt( &self, - _: &[u8], - _: &[u8], - _: &[u8], - _: &[u8], - _: &[u8], + mode: AesMode, + key: &[u8], + iv: &[u8], + data: &[u8], + additional_data: Option<&[u8]>, ) -> Result, CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) + super::rust::RustCryptoProvider.aes_decrypt(mode, key, iv, data, additional_data) } -} -impl AesKwProvider for OpenSslAesKw { - fn wrap_key(&self, _: &[u8], _: &[u8]) -> Result, CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) - } - fn unwrap_key(&self, _: &[u8], _: &[u8]) -> Result, CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) + fn aes_kw_wrap(&self, kek: &[u8], key: &[u8]) -> Result, CryptoError> { + super::rust::RustCryptoProvider.aes_kw_wrap(kek, key) } -} -impl KdfProvider for OpenSslKdf { - fn derive_key(&self, _: &[u8], _: &[u8], _: &[u8], _: usize) -> Result, CryptoError> { - Err(CryptoError::UnsupportedAlgorithm) + fn aes_kw_unwrap(&self, kek: &[u8], wrapped_key: &[u8]) -> Result, CryptoError> { + super::rust::RustCryptoProvider.aes_kw_unwrap(kek, wrapped_key) } -} -impl CryptoProvider for OpenSslProvider { - type Md5 = OpenSslMd5; - type Sha1 = OpenSslSha1; - type Sha256 = OpenSslSha256; - type Sha384 = OpenSslSha384; - type Sha512 = OpenSslSha512; - - type HmacSha1 = OpenSslHmacSha1; - type HmacSha256 = OpenSslHmacSha256; - type HmacSha384 = OpenSslHmacSha384; - type HmacSha512 = OpenSslHmacSha512; - - type RsaPkcs1v15 = OpenSslRsa; - type RsaPss = OpenSslRsa; - type RsaOaep = OpenSslRsa; - - type EcdsaP256 = OpenSslEcdsa; - type EcdsaP384 = OpenSslEcdsa; - type EcdsaP521 = OpenSslEcdsa; - - type EcdhP256 = OpenSslEcdh; - type EcdhP384 = OpenSslEcdh; - type EcdhP521 = OpenSslEcdh; - - type Ed25519 = OpenSslEd25519; - type X25519 = OpenSslX25519; - - type AesCtr = OpenSslAes; - type AesCbc = OpenSslAes; - type AesGcm = OpenSslAesGcm; - type AesKw = OpenSslAesKw; - - type Hkdf = OpenSslKdf; - type Pbkdf2 = OpenSslKdf; - - fn md5(&self) -> Self::Md5 { - OpenSslMd5 - } - fn sha1(&self) -> Self::Sha1 { - OpenSslSha1 - } - fn sha256(&self) -> Self::Sha256 { - OpenSslSha256 - } - fn sha384(&self) -> Self::Sha384 { - OpenSslSha384 - } - fn sha512(&self) -> Self::Sha512 { - OpenSslSha512 + fn hkdf_derive_key( + &self, + key: &[u8], + salt: &[u8], + info: &[u8], + length: usize, + hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + super::rust::RustCryptoProvider.hkdf_derive_key(key, salt, info, length, hash_alg) } - fn hmac_sha1(&self, _key: &[u8]) -> Self::HmacSha1 { - OpenSslHmacSha1 - } - fn hmac_sha256(&self, _key: &[u8]) -> Self::HmacSha256 { - OpenSslHmacSha256 - } - fn hmac_sha384(&self, _key: &[u8]) -> Self::HmacSha384 { - OpenSslHmacSha384 - } - fn hmac_sha512(&self, _key: &[u8]) -> Self::HmacSha512 { - OpenSslHmacSha512 + fn pbkdf2_derive_key( + &self, + password: &[u8], + salt: &[u8], + iterations: u32, + length: usize, + hash_alg: ShaAlgorithm, + ) -> Result, CryptoError> { + super::rust::RustCryptoProvider + .pbkdf2_derive_key(password, salt, iterations, length, hash_alg) } - fn rsa_pkcs1v15(&self) -> Self::RsaPkcs1v15 { - OpenSslRsa - } - fn rsa_pss(&self) -> Self::RsaPss { - OpenSslRsa - } - fn rsa_oaep(&self) -> Self::RsaOaep { - OpenSslRsa + fn generate_aes_key(&self, length_bits: u16) -> Result, CryptoError> { + super::rust::RustCryptoProvider.generate_aes_key(length_bits) } - fn ecdsa_p256(&self) -> Self::EcdsaP256 { - OpenSslEcdsa - } - fn ecdsa_p384(&self) -> Self::EcdsaP384 { - OpenSslEcdsa - } - fn ecdsa_p521(&self) -> Self::EcdsaP521 { - OpenSslEcdsa + fn generate_hmac_key( + &self, + hash_alg: ShaAlgorithm, + length_bits: u16, + ) -> Result, CryptoError> { + super::rust::RustCryptoProvider.generate_hmac_key(hash_alg, length_bits) } - fn ecdh_p256(&self) -> Self::EcdhP256 { - OpenSslEcdh - } - fn ecdh_p384(&self) -> Self::EcdhP384 { - OpenSslEcdh - } - fn ecdh_p521(&self) -> Self::EcdhP521 { - OpenSslEcdh + fn generate_ec_key(&self, curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { + super::rust::RustCryptoProvider.generate_ec_key(curve) } - fn ed25519(&self) -> Self::Ed25519 { - OpenSslEd25519 - } - fn x25519(&self) -> Self::X25519 { - OpenSslX25519 + fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { + super::rust::RustCryptoProvider.generate_ed25519_key() } - fn aes_ctr(&self) -> Self::AesCtr { - OpenSslAes - } - fn aes_cbc(&self) -> Self::AesCbc { - OpenSslAes - } - fn aes_gcm(&self) -> Self::AesGcm { - OpenSslAesGcm - } - fn aes_kw(&self) -> Self::AesKw { - OpenSslAesKw + fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { + super::rust::RustCryptoProvider.generate_x25519_key() } - fn hkdf(&self) -> Self::Hkdf { - OpenSslKdf - } - fn pbkdf2(&self) -> Self::Pbkdf2 { - OpenSslKdf + fn generate_rsa_key( + &self, + modulus_length: u32, + public_exponent: &[u8], + ) -> Result<(Vec, Vec), CryptoError> { + super::rust::RustCryptoProvider.generate_rsa_key(modulus_length, public_exponent) } } diff --git a/modules/llrt_crypto/src/provider/ring.rs b/modules/llrt_crypto/src/provider/ring.rs index 6f15d81633..d97ea3f6c3 100644 --- a/modules/llrt_crypto/src/provider/ring.rs +++ b/modules/llrt_crypto/src/provider/ring.rs @@ -162,7 +162,6 @@ impl CryptoProvider for RingProvider { fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { match algorithm { ShaAlgorithm::MD5 => { - // Ring doesn't support HMAC-MD5, return error panic!("HMAC-MD5 not supported by Ring provider"); }, ShaAlgorithm::SHA1 => RingHmacType::Sha1(RingHmacSha1(hmac::Context::with_key( @@ -180,7 +179,6 @@ impl CryptoProvider for RingProvider { } } - // Stub implementations for unsupported operations fn ecdsa_sign( &self, _curve: EllipticCurve, @@ -253,6 +251,26 @@ impl CryptoProvider for RingProvider { Err(CryptoError::UnsupportedAlgorithm) } + fn rsa_oaep_encrypt( + &self, + _public_key_der: &[u8], + _data: &[u8], + _hash_alg: ShaAlgorithm, + _label: Option<&[u8]>, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_oaep_decrypt( + &self, + _private_key_der: &[u8], + _data: &[u8], + _hash_alg: ShaAlgorithm, + _label: Option<&[u8]>, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn ecdh_derive_bits( &self, _curve: EllipticCurve, @@ -270,6 +288,28 @@ impl CryptoProvider for RingProvider { Err(CryptoError::UnsupportedAlgorithm) } + fn aes_encrypt( + &self, + _mode: AesMode, + _key: &[u8], + _iv: &[u8], + _data: &[u8], + _additional_data: Option<&[u8]>, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn aes_decrypt( + &self, + _mode: AesMode, + _key: &[u8], + _iv: &[u8], + _data: &[u8], + _additional_data: Option<&[u8]>, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn aes_kw_wrap(&self, _kek: &[u8], _key: &[u8]) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -299,4 +339,36 @@ impl CryptoProvider for RingProvider { ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } + + fn generate_aes_key(&self, _length_bits: u16) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn generate_hmac_key( + &self, + _hash_alg: ShaAlgorithm, + _length_bits: u16, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn generate_ec_key(&self, _curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn generate_rsa_key( + &self, + _modulus_length: u32, + _public_exponent: &[u8], + ) -> Result<(Vec, Vec), CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } } diff --git a/modules/llrt_crypto/src/provider/rust.rs b/modules/llrt_crypto/src/provider/rust.rs index 414b0e2ff3..6fd18bdd9f 100644 --- a/modules/llrt_crypto/src/provider/rust.rs +++ b/modules/llrt_crypto/src/provider/rust.rs @@ -9,24 +9,25 @@ use aes::cipher::{ }; use aes_gcm::{ aead::{Aead, Payload}, - AesGcm, KeyInit, Nonce, + KeyInit, Nonce, }; -use aes_kw::{KeyInit as KwKeyInit, KwAes128, KwAes192, KwAes256}; +use aes_kw::{KwAes128, KwAes192, KwAes256}; use cbc::{Decryptor, Encryptor}; use ctr::{cipher::Array, Ctr128BE, Ctr32BE, Ctr64BE}; use ecdsa::signature::hazmat::PrehashVerifier; -use elliptic_curve::{ - consts::U12, - sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}, - AffinePoint, CurveArithmetic, FieldBytesSize, -}; +use elliptic_curve::consts::U12; +use hmac::{Hmac as HmacImpl, Mac}; use once_cell::sync::Lazy; use p256::{ - ecdsa::{Signature as P256Signature, VerifyingKey as P256VerifyingKey}, + ecdsa::{ + Signature as P256Signature, SigningKey as P256SigningKey, VerifyingKey as P256VerifyingKey, + }, SecretKey as P256SecretKey, }; use p384::{ - ecdsa::{Signature as P384Signature, VerifyingKey as P384VerifyingKey}, + ecdsa::{ + Signature as P384Signature, SigningKey as P384SigningKey, VerifyingKey as P384VerifyingKey, + }, SecretKey as P384SecretKey, }; use p521::{ @@ -34,28 +35,24 @@ use p521::{ SecretKey as P521SecretKey, }; use pkcs8::EncodePrivateKey; -use ring::signature::KeyPair; use ring::{ - digest::{self, Context as DigestContext}, - hmac::{self, Context as HmacContext, Key as HmacKey}, pbkdf2, - rand::{SecureRandom, SystemRandom}, - signature::{EcdsaKeyPair, Ed25519KeyPair, UnparsedPublicKey}, + rand::SystemRandom, + signature::{Ed25519KeyPair, KeyPair, UnparsedPublicKey}, +}; +use rsa::pkcs1::{ + DecodeRsaPrivateKey, DecodeRsaPublicKey, EncodeRsaPrivateKey, EncodeRsaPublicKey, }; -use rsa::pkcs1::EncodeRsaPrivateKey; -use rsa::pkcs1::EncodeRsaPublicKey; +use rsa::pkcs8::DecodePrivateKey; use rsa::signature::hazmat::PrehashSigner; use rsa::{ - pkcs1::DecodeRsaPrivateKey, - pkcs8::DecodePrivateKey, pss::Pss, - sha2::{Sha256, Sha384, Sha512}, - Oaep, Pkcs1v15Sign, RsaPrivateKey, RsaPublicKey, + sha2::{Digest, Sha256, Sha384, Sha512}, + BoxedUint, Oaep, Pkcs1v15Sign, RsaPrivateKey, RsaPublicKey, }; -use rsa::{pkcs1::DecodeRsaPublicKey, BoxedUint}; +use sha1::Sha1; use crate::{ - get_random_bytes, provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}, random_byte_array, sha_hash::ShaAlgorithm, @@ -74,33 +71,62 @@ impl From for CryptoError { } } -// Digest implementation -pub struct RingDigest { - context: DigestContext, +// Digest implementation using sha2/md5 crates +pub enum RustDigest { + Md5(md5::Md5), + Sha1(Sha1), + Sha256(Sha256), + Sha384(Sha384), + Sha512(Sha512), } -impl SimpleDigest for RingDigest { +impl SimpleDigest for RustDigest { fn update(&mut self, data: &[u8]) { - self.context.update(data); + match self { + RustDigest::Md5(h) => Digest::update(h, data), + RustDigest::Sha1(h) => Digest::update(h, data), + RustDigest::Sha256(h) => Digest::update(h, data), + RustDigest::Sha384(h) => Digest::update(h, data), + RustDigest::Sha512(h) => Digest::update(h, data), + } } fn finalize(self) -> Vec { - self.context.finish().as_ref().to_vec() + match self { + RustDigest::Md5(h) => h.finalize().to_vec(), + RustDigest::Sha1(h) => h.finalize().to_vec(), + RustDigest::Sha256(h) => h.finalize().to_vec(), + RustDigest::Sha384(h) => h.finalize().to_vec(), + RustDigest::Sha512(h) => h.finalize().to_vec(), + } } } -// HMAC implementation -pub struct RingHmac { - context: HmacContext, +// HMAC implementation using hmac crate +pub enum RustHmac { + Sha1(HmacImpl), + Sha256(HmacImpl), + Sha384(HmacImpl), + Sha512(HmacImpl), } -impl HmacProvider for RingHmac { +impl HmacProvider for RustHmac { fn update(&mut self, data: &[u8]) { - self.context.update(data); + match self { + RustHmac::Sha1(h) => Mac::update(h, data), + RustHmac::Sha256(h) => Mac::update(h, data), + RustHmac::Sha384(h) => Mac::update(h, data), + RustHmac::Sha512(h) => Mac::update(h, data), + } } fn finalize(self) -> Vec { - self.context.sign().as_ref().to_vec() + match self { + RustHmac::Sha1(h) => h.finalize().into_bytes().to_vec(), + RustHmac::Sha256(h) => h.finalize().into_bytes().to_vec(), + RustHmac::Sha384(h) => h.finalize().into_bytes().to_vec(), + RustHmac::Sha512(h) => h.finalize().into_bytes().to_vec(), + } } } @@ -111,19 +137,32 @@ pub struct RustCryptoProvider; pub static SYSTEM_RANDOM: Lazy = Lazy::new(SystemRandom::new); impl CryptoProvider for RustCryptoProvider { - type Digest = RingDigest; - type Hmac = RingHmac; + type Digest = RustDigest; + type Hmac = RustHmac; fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { - RingDigest { - context: DigestContext::new(algorithm.digest_algorithm()), + match algorithm { + ShaAlgorithm::MD5 => RustDigest::Md5(md5::Md5::new()), + ShaAlgorithm::SHA1 => RustDigest::Sha1(Sha1::new()), + ShaAlgorithm::SHA256 => RustDigest::Sha256(Sha256::new()), + ShaAlgorithm::SHA384 => RustDigest::Sha384(Sha384::new()), + ShaAlgorithm::SHA512 => RustDigest::Sha512(Sha512::new()), } } fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { - let hmac_key = HmacKey::new(*algorithm.hmac_algorithm(), key); - RingHmac { - context: HmacContext::with_key(&hmac_key), + match algorithm { + ShaAlgorithm::MD5 => panic!("HMAC-MD5 not supported"), + ShaAlgorithm::SHA1 => RustHmac::Sha1(HmacImpl::::new_from_slice(key).unwrap()), + ShaAlgorithm::SHA256 => { + RustHmac::Sha256(HmacImpl::::new_from_slice(key).unwrap()) + }, + ShaAlgorithm::SHA384 => { + RustHmac::Sha384(HmacImpl::::new_from_slice(key).unwrap()) + }, + ShaAlgorithm::SHA512 => { + RustHmac::Sha512(HmacImpl::::new_from_slice(key).unwrap()) + }, } } @@ -133,33 +172,24 @@ impl CryptoProvider for RustCryptoProvider { private_key_der: &[u8], digest: &[u8], ) -> Result, CryptoError> { - let rng = SecureRandom::new(); match curve { EllipticCurve::P256 => { - let key_pair = EcdsaKeyPair::from_pkcs8( - &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, - private_key_der, - rng, - ) - .map_err(|_| CryptoError::InvalidKey)?; - - let signature = key_pair - .sign(rng, digest) + let secret_key = P256SecretKey::from_pkcs8_der(private_key_der) + .map_err(|_| CryptoError::InvalidKey)?; + let signing_key = P256SigningKey::from(secret_key); + let signature: p256::ecdsa::Signature = signing_key + .sign_prehash(digest) .map_err(|_| CryptoError::SigningFailed)?; - Ok(signature.as_ref().to_vec()) + Ok(signature.to_bytes().to_vec()) }, EllipticCurve::P384 => { - let key_pair = EcdsaKeyPair::from_pkcs8( - &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING, - private_key_der, - rng, - ) - .map_err(|_| CryptoError::InvalidKey)?; - - let signature = key_pair - .sign(rng, digest) + let secret_key = P384SecretKey::from_pkcs8_der(private_key_der) + .map_err(|_| CryptoError::InvalidKey)?; + let signing_key = P384SigningKey::from(secret_key); + let signature: p384::ecdsa::Signature = signing_key + .sign_prehash(digest) .map_err(|_| CryptoError::SigningFailed)?; - Ok(signature.as_ref().to_vec()) + Ok(signature.to_bytes().to_vec()) }, EllipticCurve::P521 => { let secret_key = P521SecretKey::from_pkcs8_der(private_key_der) @@ -235,13 +265,13 @@ impl CryptoProvider for RustCryptoProvider { match hash_alg { ShaAlgorithm::SHA256 => private_key - .sign_with_rng(&mut rng, Pss::new_with_salt::(salt_length), digest) + .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed), ShaAlgorithm::SHA384 => private_key - .sign_with_rng(&mut rng, Pss::new_with_salt::(salt_length), digest) + .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed), ShaAlgorithm::SHA512 => private_key - .sign_with_rng(&mut rng, Pss::new_with_salt::(salt_length), digest) + .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed), _ => Err(CryptoError::UnsupportedAlgorithm), } @@ -260,13 +290,13 @@ impl CryptoProvider for RustCryptoProvider { match hash_alg { ShaAlgorithm::SHA256 => Ok(public_key - .verify(Pss::new_with_salt::(salt_length), digest, signature) + .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), ShaAlgorithm::SHA384 => Ok(public_key - .verify(Pss::new_with_salt::(salt_length), digest, signature) + .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), ShaAlgorithm::SHA512 => Ok(public_key - .verify(Pss::new_with_salt::(salt_length), digest, signature) + .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), _ => Err(CryptoError::UnsupportedAlgorithm), } @@ -331,22 +361,53 @@ impl CryptoProvider for RustCryptoProvider { let public_key = RsaPublicKey::from_pkcs1_der(public_key_der).map_err(|_| CryptoError::InvalidKey)?; - let mut padding = match hash_alg { - ShaAlgorithm::SHA256 => Oaep::new::(), - ShaAlgorithm::SHA384 => Oaep::new::(), - ShaAlgorithm::SHA512 => Oaep::new::(), - _ => return Err(CryptoError::UnsupportedAlgorithm), - }; - - if let Some(label) = label { - if !label.is_empty() { - padding.label = Some(label.into()); - } + match hash_alg { + ShaAlgorithm::SHA1 => { + let mut padding = Oaep::::new(); + if let Some(l) = label { + if !l.is_empty() { + padding.label = Some(l.into()); + } + } + public_key + .encrypt(&mut rng, padding, data) + .map_err(|_| CryptoError::EncryptionFailed) + }, + ShaAlgorithm::SHA256 => { + let mut padding = Oaep::::new(); + if let Some(l) = label { + if !l.is_empty() { + padding.label = Some(l.into()); + } + } + public_key + .encrypt(&mut rng, padding, data) + .map_err(|_| CryptoError::EncryptionFailed) + }, + ShaAlgorithm::SHA384 => { + let mut padding = Oaep::::new(); + if let Some(l) = label { + if !l.is_empty() { + padding.label = Some(l.into()); + } + } + public_key + .encrypt(&mut rng, padding, data) + .map_err(|_| CryptoError::EncryptionFailed) + }, + ShaAlgorithm::SHA512 => { + let mut padding = Oaep::::new(); + if let Some(l) = label { + if !l.is_empty() { + padding.label = Some(l.into()); + } + } + public_key + .encrypt(&mut rng, padding, data) + .map_err(|_| CryptoError::EncryptionFailed) + }, + _ => Err(CryptoError::UnsupportedAlgorithm), } - - public_key - .encrypt(&mut rng, padding, data) - .map_err(|_| CryptoError::EncryptionFailed) } fn rsa_oaep_decrypt( @@ -359,22 +420,53 @@ impl CryptoProvider for RustCryptoProvider { let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der).map_err(|_| CryptoError::InvalidKey)?; - let mut padding = match hash_alg { - ShaAlgorithm::SHA256 => Oaep::new::(), - ShaAlgorithm::SHA384 => Oaep::new::(), - ShaAlgorithm::SHA512 => Oaep::new::(), - _ => return Err(CryptoError::UnsupportedAlgorithm), - }; - - if let Some(label) = label { - if !label.is_empty() { - padding.label = Some(label.into()); - } + match hash_alg { + ShaAlgorithm::SHA1 => { + let mut padding = Oaep::::new(); + if let Some(l) = label { + if !l.is_empty() { + padding.label = Some(l.into()); + } + } + private_key + .decrypt(padding, data) + .map_err(|_| CryptoError::DecryptionFailed) + }, + ShaAlgorithm::SHA256 => { + let mut padding = Oaep::::new(); + if let Some(l) = label { + if !l.is_empty() { + padding.label = Some(l.into()); + } + } + private_key + .decrypt(padding, data) + .map_err(|_| CryptoError::DecryptionFailed) + }, + ShaAlgorithm::SHA384 => { + let mut padding = Oaep::::new(); + if let Some(l) = label { + if !l.is_empty() { + padding.label = Some(l.into()); + } + } + private_key + .decrypt(padding, data) + .map_err(|_| CryptoError::DecryptionFailed) + }, + ShaAlgorithm::SHA512 => { + let mut padding = Oaep::::new(); + if let Some(l) = label { + if !l.is_empty() { + padding.label = Some(l.into()); + } + } + private_key + .decrypt(padding, data) + .map_err(|_| CryptoError::DecryptionFailed) + }, + _ => Err(CryptoError::UnsupportedAlgorithm), } - - private_key - .decrypt(padding, data) - .map_err(|_| CryptoError::DecryptionFailed) } fn ecdh_derive_bits( @@ -742,36 +834,32 @@ impl CryptoProvider for RustCryptoProvider { } fn generate_ec_key(&self, curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { - let rng = &(*SYSTEM_RANDOM); + let mut rng = rand::rng(); match curve { EllipticCurve::P256 => { - let pkcs8 = EcdsaKeyPair::generate_pkcs8( - &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, - rng, - ) - .map_err(|_| CryptoError::OperationFailed)?; - let private_key = pkcs8.as_ref().to_vec(); - let signing_key = P256SecretKey::from_pkcs8_der(&private_key) + let key = P256SecretKey::try_from_rng(&mut rng) .map_err(|_| CryptoError::OperationFailed)?; - let public_key = signing_key.public_key().to_sec1_bytes().to_vec(); + let pkcs8 = key + .to_pkcs8_der() + .map_err(|_| CryptoError::OperationFailed)?; + let private_key = pkcs8.as_bytes().to_vec(); + let public_key = key.public_key().to_sec1_bytes().to_vec(); Ok((private_key, public_key)) }, EllipticCurve::P384 => { - let pkcs8 = EcdsaKeyPair::generate_pkcs8( - &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING, - rng, - ) - .map_err(|_| CryptoError::OperationFailed)?; - let private_key = pkcs8.as_ref().to_vec(); - let signing_key = P384SecretKey::from_pkcs8_der(&private_key) + let key = P384SecretKey::try_from_rng(&mut rng) .map_err(|_| CryptoError::OperationFailed)?; - let public_key = signing_key.public_key().to_sec1_bytes().to_vec(); + let pkcs8 = key + .to_pkcs8_der() + .map_err(|_| CryptoError::OperationFailed)?; + let private_key = pkcs8.as_bytes().to_vec(); + let public_key = key.public_key().to_sec1_bytes().to_vec(); Ok((private_key, public_key)) }, EllipticCurve::P521 => { - let mut rng = rand::rng(); - let key = P521SecretKey::random(&mut rng); + let key = P521SecretKey::try_from_rng(&mut rng) + .map_err(|_| CryptoError::OperationFailed)?; let pkcs8 = key .to_pkcs8_der() .map_err(|_| CryptoError::OperationFailed)?; @@ -794,7 +882,8 @@ impl CryptoProvider for RustCryptoProvider { } fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { - let secret_key = x25519_dalek::StaticSecret::random(); + let mut rng = rand::rng(); + let secret_key = x25519_dalek::StaticSecret::random_from_rng(&mut rng); let private_key = secret_key.as_bytes().to_vec(); let public_key = x25519_dalek::PublicKey::from(&secret_key) .as_bytes() diff --git a/modules/llrt_crypto/src/sha_hash.rs b/modules/llrt_crypto/src/sha_hash.rs index 5763cf16b0..503436ed23 100644 --- a/modules/llrt_crypto/src/sha_hash.rs +++ b/modules/llrt_crypto/src/sha_hash.rs @@ -10,8 +10,8 @@ use llrt_utils::{ use ring::{digest, hmac}; use rquickjs::{function::Opt, prelude::This, Class, Ctx, Result, Value}; -use crate::provider::{CryptoProvider, SimpleDigest, HmacProvider}; use super::{encoded_bytes, CRYPTO_PROVIDER}; +use crate::provider::{CryptoProvider, HmacProvider, SimpleDigest}; #[rquickjs::class] #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] diff --git a/modules/llrt_crypto/src/subtle/derive.rs b/modules/llrt_crypto/src/subtle/derive.rs index 21eadeb1f7..38d943d4a2 100644 --- a/modules/llrt_crypto/src/subtle/derive.rs +++ b/modules/llrt_crypto/src/subtle/derive.rs @@ -3,11 +3,7 @@ use llrt_utils::result::ResultExt; use rquickjs::{Array, ArrayBuffer, Class, Ctx, Exception, Result, Value}; -use crate::{ - subtle::CryptoKey, - provider::CryptoProvider, - CRYPTO_PROVIDER, -}; +use crate::{provider::CryptoProvider, subtle::CryptoKey, CRYPTO_PROVIDER}; use super::{ algorithm_mismatch_error, algorithm_not_supported_error, @@ -49,28 +45,34 @@ fn derive_bits( && matches!(algorithm, EcAlgorithm::Ecdh) { let handle = &base_key.handle; - return CRYPTO_PROVIDER.ecdh_derive_bits(*curve, handle, public_key).or_throw(ctx); + return CRYPTO_PROVIDER + .ecdh_derive_bits(*curve, handle, public_key) + .or_throw(ctx); } return Err(Exception::throw_message( ctx, "ECDH curve must be same as baseKey", )); } - return algorithm_mismatch_error(ctx, "ECDH"); + algorithm_mismatch_error(ctx, "ECDH") }, DeriveAlgorithm::X25519 { public_key } => { if !matches!(base_key.algorithm, KeyAlgorithm::X25519) { return algorithm_mismatch_error(ctx, "X25519"); } - return CRYPTO_PROVIDER.x25519_derive_bits(&base_key.handle, public_key).or_throw(ctx); + CRYPTO_PROVIDER + .x25519_derive_bits(&base_key.handle, public_key) + .or_throw(ctx) }, DeriveAlgorithm::Derive(KeyDerivation::Hkdf { hash, salt, info }) => { if !matches!(base_key.algorithm, KeyAlgorithm::HkdfImport) { return algorithm_mismatch_error(ctx, "HKDF"); } let out_length = (length / 8).try_into().or_throw(ctx)?; - return CRYPTO_PROVIDER.hkdf_derive_key(&base_key.handle, salt, info, out_length, *hash).or_throw(ctx); + CRYPTO_PROVIDER + .hkdf_derive_key(&base_key.handle, salt, info, out_length, *hash) + .or_throw(ctx) }, DeriveAlgorithm::Derive(KeyDerivation::Pbkdf2 { hash, @@ -81,7 +83,9 @@ fn derive_bits( return algorithm_mismatch_error(ctx, "PBKDF2"); } let out_length = (length / 8).try_into().or_throw(ctx)?; - return CRYPTO_PROVIDER.pbkdf2_derive_key(&base_key.handle, salt, *iterations, out_length, *hash).or_throw(ctx); + CRYPTO_PROVIDER + .pbkdf2_derive_key(&base_key.handle, salt, *iterations, out_length, *hash) + .or_throw(ctx) }, } } diff --git a/modules/llrt_crypto/src/subtle/derive_algorithm.rs b/modules/llrt_crypto/src/subtle/derive_algorithm.rs index 32351434d0..1c85803522 100644 --- a/modules/llrt_crypto/src/subtle/derive_algorithm.rs +++ b/modules/llrt_crypto/src/subtle/derive_algorithm.rs @@ -48,7 +48,7 @@ impl<'js> FromJs<'js> for DeriveAlgorithm { if let KeyAlgorithm::Ec { curve, .. } = &public_key.algorithm { DeriveAlgorithm::Ecdh { - curve: curve.clone(), + curve: *curve, public_key: public_key.handle.clone(), } } else { diff --git a/modules/llrt_crypto/src/subtle/digest.rs b/modules/llrt_crypto/src/subtle/digest.rs index c8fef4d6c7..2841f60600 100644 --- a/modules/llrt_crypto/src/subtle/digest.rs +++ b/modules/llrt_crypto/src/subtle/digest.rs @@ -3,7 +3,11 @@ use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt}; use rquickjs::{ArrayBuffer, Ctx, Result, Value}; -use crate::{sha_hash::ShaAlgorithm, provider::{CryptoProvider, SimpleDigest}, CRYPTO_PROVIDER}; +use crate::{ + provider::{CryptoProvider, SimpleDigest}, + sha_hash::ShaAlgorithm, + CRYPTO_PROVIDER, +}; pub async fn subtle_digest<'js>( ctx: Ctx<'js>, diff --git a/modules/llrt_crypto/src/subtle/encryption.rs b/modules/llrt_crypto/src/subtle/encryption.rs index 6e63822354..b1014d83fb 100644 --- a/modules/llrt_crypto/src/subtle/encryption.rs +++ b/modules/llrt_crypto/src/subtle/encryption.rs @@ -9,14 +9,6 @@ use crate::{ CRYPTO_PROVIDER, }; -use crate::sha_hash::ShaAlgorithm; - -pub(super) enum OaepPadding { - Sha256(Oaep), - Sha384(Oaep), - Sha512(Oaep), -} - use super::{ algorithm_mismatch_error, encryption_algorithm::EncryptionAlgorithm, key_algorithm::KeyAlgorithm, validate_aes_length, CryptoKey, EncryptionMode, @@ -139,10 +131,11 @@ pub fn encrypt_decrypt( ) .or_throw(ctx)?, EncryptionOperation::Decrypt => { - if data.len() < 16 { + let tag_len = (*tag_length as usize) / 8; + if data.len() < tag_len { return Err(Exception::throw_message(ctx, "Invalid ciphertext length")); } - let (ciphertext, tag) = data.split_at(data.len() - 16); + // Pass the full data (ciphertext + tag) to the decrypt function CRYPTO_PROVIDER .aes_decrypt( AesMode::Gcm { @@ -150,7 +143,7 @@ pub fn encrypt_decrypt( }, handle, iv, - ciphertext, + data, aad, ) .or_throw(ctx)? @@ -158,22 +151,47 @@ pub fn encrypt_decrypt( } }, EncryptionAlgorithm::AesKw => { - let _padding = match mode { + let padding = match mode { EncryptionMode::Encryption => { return Err(Exception::throw_message( ctx, "AES-KW can only be used for wrapping keys", )); }, - EncryptionMode::Wrapping(_padding) => _padding, + EncryptionMode::Wrapping(padding) => padding, }; match operation { EncryptionOperation::Encrypt => { - CRYPTO_PROVIDER.aes_kw_wrap(handle, data).or_throw(ctx)? + // Pad data to multiple of 8 bytes if needed + let padded_data = if !data.len().is_multiple_of(8) { + let pad_len = 8 - (data.len() % 8); + let mut padded = data.to_vec(); + padded.extend(std::iter::repeat_n(padding, pad_len)); + padded + } else { + data.to_vec() + }; + CRYPTO_PROVIDER + .aes_kw_wrap(handle, &padded_data) + .or_throw(ctx)? }, EncryptionOperation::Decrypt => { - CRYPTO_PROVIDER.aes_kw_unwrap(handle, data).or_throw(ctx)? + let unwrapped = CRYPTO_PROVIDER.aes_kw_unwrap(handle, data).or_throw(ctx)?; + // Remove padding if present + if padding != 0 { + let trimmed: Vec = unwrapped + .into_iter() + .rev() + .skip_while(|&b| b == padding) + .collect::>() + .into_iter() + .rev() + .collect(); + trimmed + } else { + unwrapped + } }, } }, @@ -195,28 +213,3 @@ pub fn encrypt_decrypt( }; Ok(bytes) } - -pub fn rsa_oaep_padding( - ctx: &Ctx<'_>, - label: &Option>, - hash: &ShaAlgorithm, -) -> Result { - 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(label.to_owned()); - } - } - - Ok(padding) -} diff --git a/modules/llrt_crypto/src/subtle/generate_key.rs b/modules/llrt_crypto/src/subtle/generate_key.rs index d3d971a552..34e251ac24 100644 --- a/modules/llrt_crypto/src/subtle/generate_key.rs +++ b/modules/llrt_crypto/src/subtle/generate_key.rs @@ -1,16 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use llrt_utils::result::ResultExt; -use ring::{ - rand::SecureRandom, - signature::{EcdsaKeyPair, Ed25519KeyPair, KeyPair}, -}; use rquickjs::{object::Property, Array, Class, Ctx, Exception, Object, Result, Value}; -use rsa::{ - pkcs1::{EncodeRsaPrivateKey, EncodeRsaPublicKey}, - pkcs8::{DecodePrivateKey, EncodePrivateKey}, - BoxedUint, RsaPrivateKey, -}; use crate::{provider::CryptoProvider, CRYPTO_PROVIDER}; @@ -20,7 +10,6 @@ use super::{ algorithm_not_supported_error, crypto_key::KeyKind, key_algorithm::{KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages}, - EllipticCurve, }; pub async fn subtle_generate_key<'js>( @@ -97,17 +86,15 @@ fn generate_key(ctx: &Ctx<'_>, algorithm: &KeyAlgorithm) -> Result<(Vec, Vec }, KeyAlgorithm::Hmac { hash, length } => { let key = CRYPTO_PROVIDER - .generate_hmac_key(hash.clone(), *length) + .generate_hmac_key(*hash, *length) .map_err(|e| { Exception::throw_message(ctx, &format!("HMAC key generation failed: {}", e)) })?; Ok((vec![], key)) }, - KeyAlgorithm::Ec { curve, .. } => { - CRYPTO_PROVIDER.generate_ec_key(curve.clone()).map_err(|e| { - Exception::throw_message(ctx, &format!("EC key generation failed: {}", e)) - }) - }, + KeyAlgorithm::Ec { curve, .. } => CRYPTO_PROVIDER.generate_ec_key(*curve).map_err(|e| { + Exception::throw_message(ctx, &format!("EC key generation failed: {}", e)) + }), KeyAlgorithm::Ed25519 => CRYPTO_PROVIDER.generate_ed25519_key().map_err(|e| { Exception::throw_message(ctx, &format!("Ed25519 key generation failed: {}", e)) }), @@ -127,122 +114,8 @@ fn generate_key(ctx: &Ctx<'_>, algorithm: &KeyAlgorithm) -> Result<(Vec, Vec } } -// fn generate_key(ctx: &Ctx<'_>, algorithm: &KeyAlgorithm) -> Result<(Vec, Vec)> { -// let private_key; -// let public_or_secret_key; -// match algorithm { -// KeyAlgorithm::Aes { length } => { -// let length = *length as usize; - -// match length { -// 128 | 192 | 256 => (), -// _ => { -// return Err(Exception::throw_message( -// ctx, -// "AES key length must be 128, 192, or 256 bits", -// )) -// }, -// } - -// public_or_secret_key = generate_symmetric_key(ctx, length / 8)?; -// private_key = vec![]; -// }, -// KeyAlgorithm::Hmac { hash, length } => { -// let length = get_hash_length(ctx, hash, *length)?; -// public_or_secret_key = generate_symmetric_key(ctx, length)?; -// private_key = vec![]; -// }, -// KeyAlgorithm::Ec { curve, .. } => { -// let rng = &(*SYSTEM_RANDOM); - -// match curve { -// EllipticCurve::P256 => { -// let pkcs8 = EcdsaKeyPair::generate_pkcs8( -// &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, -// rng, -// ) -// .or_throw(ctx)?; -// private_key = pkcs8.as_ref().into(); -// let signing_key = p256::SecretKey::from_pkcs8_der(&private_key).unwrap(); -// public_or_secret_key = signing_key.public_key().to_sec1_bytes().into(); -// }, -// EllipticCurve::P384 => { -// let pkcs8 = EcdsaKeyPair::generate_pkcs8( -// &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING, -// rng, -// ) -// .or_throw(ctx)?; -// private_key = pkcs8.as_ref().into(); -// let signing_key = p384::SecretKey::from_pkcs8_der(&private_key).unwrap(); -// public_or_secret_key = signing_key.public_key().to_sec1_bytes().into(); -// }, -// EllipticCurve::P521 => { -// let mut rng = rand::rng(); -// let key = p521::SecretKey::random(&mut rng); -// let pkcs8 = key.to_pkcs8_der().or_throw(ctx)?; -// private_key = pkcs8.as_bytes().into(); -// public_or_secret_key = key.public_key().to_sec1_bytes().into(); -// }, -// } -// }, -// KeyAlgorithm::Ed25519 => { -// let rng = &(*SYSTEM_RANDOM); -// let pkcs8 = Ed25519KeyPair::generate_pkcs8(rng).or_throw(ctx)?; -// private_key = pkcs8.as_ref().into(); - -// let key_pair = Ed25519KeyPair::from_pkcs8(&private_key).unwrap(); -// public_or_secret_key = key_pair.public_key().as_ref().into(); -// }, - -// KeyAlgorithm::X25519 => { -// let secret_key = x25519_dalek::StaticSecret::random(); -// private_key = secret_key.as_bytes().into(); -// public_or_secret_key = x25519_dalek::PublicKey::from(&secret_key).as_bytes().into(); -// }, -// KeyAlgorithm::Rsa { -// modulus_length, -// public_exponent, -// .. -// } => { -// let public_exponent = public_exponent.as_ref().as_ref(); -// // Convert public exponent bytes to u64 value -// let exponent: u64 = match public_exponent { -// [0x01, 0x00, 0x01] => 65537, // Standard RSA exponent F4 (0x10001) -// [0x03] => 3, // Alternative RSA exponent 3 -// bytes -// if bytes.ends_with(&[0x03]) -// && bytes[..bytes.len() - 1].iter().all(|&b| b == 0) => -// { -// 3 -// }, -// _ => return Err(Exception::throw_message(ctx, "Invalid RSA public exponent")), -// }; -// let exp = BoxedUint::from(exponent); -// let mut rng = rand::rng(); -// let rsa_private_key = -// RsaPrivateKey::new_with_exp(&mut rng, *modulus_length as usize, exp) -// .or_throw(ctx)?; - -// let public_key = rsa_private_key -// .to_public_key() -// .to_pkcs1_der() -// .or_throw(ctx)?; - -// let pkcs1 = rsa_private_key.to_pkcs1_der().or_throw(ctx)?; - -// private_key = pkcs1.as_bytes().into(); - -// public_or_secret_key = public_key.as_bytes().into(); -// }, -// _ => return algorithm_not_supported_error(ctx), -// }; -// Ok((private_key, public_or_secret_key)) -// } - -fn generate_symmetric_key(ctx: &Ctx<'_>, length: usize) -> Result> { - let mut key = vec![0u8; length]; - SYSTEM_RANDOM.fill(&mut key).or_throw(ctx)?; - Ok(key) +fn generate_symmetric_key(_ctx: &Ctx<'_>, length: usize) -> Result> { + Ok(crate::random_byte_array(length)) } pub fn get_hash_length(ctx: &Ctx, hash: &ShaAlgorithm, length: u16) -> Result { diff --git a/modules/llrt_http/Cargo.toml b/modules/llrt_http/Cargo.toml index 00e492fca8..2bca81772f 100644 --- a/modules/llrt_http/Cargo.toml +++ b/modules/llrt_http/Cargo.toml @@ -12,22 +12,30 @@ name = "llrt_http" path = "src/lib.rs" [features] -default = ["http1", "http2", "webpki-roots"] +default = ["http1", "http2", "webpki-roots", "tls-ring"] -http1 = ["hyper/http1", "hyper-rustls/http1"] -http2 = ["hyper/http2", "hyper-rustls/http2"] +http1 = ["hyper/http1", "hyper-rustls?/http1", "hyper-util/http1"] +http2 = ["hyper/http2", "hyper-rustls?/http2", "hyper-util/http2"] webpki-roots = ["llrt_tls/webpki-roots"] native-roots = ["llrt_tls/native-roots"] +# TLS crypto backend features (rustls-based) +tls-ring = ["llrt_tls/tls-ring", "dep:hyper-rustls", "hyper-rustls?/ring", "dep:rustls"] +tls-aws-lc = ["llrt_tls/tls-aws-lc", "dep:hyper-rustls", "hyper-rustls?/aws-lc-rs", "dep:rustls"] +tls-graviola = ["llrt_tls/tls-graviola", "dep:hyper-rustls", "dep:rustls"] + +# OpenSSL TLS backend +tls-openssl = ["llrt_tls/tls-openssl", "dep:hyper-openssl", "dep:openssl"] + [dependencies] bytes = { version = "1", default-features = false } http-body-util = { version = "0.1", default-features = false } hyper = { version = "1", features = ["client"], default-features = false } -hyper-util = { version = "0.1", default-features = false } -hyper-rustls = { version = "0.27", features = [ - "ring", -], default-features = false } +hyper-util = { version = "0.1", features = ["client-legacy", "tokio"], default-features = false } +hyper-rustls = { version = "0.27", default-features = false, optional = true } +hyper-openssl = { version = "0.10", features = ["client-legacy"], optional = true } +openssl = { version = "0.10", optional = true } llrt_dns_cache = { version = "0.7.0-beta", path = "../../libs/llrt_dns_cache" } llrt_tls = { version = "0.7.0-beta", default-features = false, path = "../llrt_tls" } llrt_utils = { version = "0.7.0-beta", path = "../../libs/llrt_utils", default-features = false } @@ -37,8 +45,7 @@ rquickjs = { version = "0.11", features = [ "std", ], default-features = false } rustls = { version = "0.23", features = [ - "ring", "tls12", -], default-features = false } +], default-features = false, optional = true } [dev-dependencies] diff --git a/modules/llrt_http/src/agent.rs b/modules/llrt_http/src/agent.rs index 0f8138d06a..c876b9d84a 100644 --- a/modules/llrt_http/src/agent.rs +++ b/modules/llrt_http/src/agent.rs @@ -1,27 +1,20 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use std::convert::Infallible; - -use bytes::Bytes; -use http_body_util::combinators::BoxBody; -use hyper_rustls::HttpsConnector; -use hyper_util::client::legacy::{connect::HttpConnector, Client}; -use llrt_dns_cache::CachedDnsResolver; use llrt_utils::result::ResultExt; use llrt_utils::{any_of::AnyOf4, bytes::ObjectBytes, object::ObjectExt}; use rquickjs::{prelude::Opt, Ctx, Error, FromJs, Result, Value}; +use crate::HyperClient; + #[rquickjs::class] #[derive(rquickjs::JsLifetime, rquickjs::class::Trace)] pub struct Agent { #[qjs(skip_trace)] - client: Client>, BoxBody>, + client: HyperClient, } impl Agent { - pub fn client( - &self, - ) -> Client>, BoxBody> { + pub fn client(&self) -> HyperClient { self.client.clone() } } diff --git a/modules/llrt_http/src/client.rs b/modules/llrt_http/src/client.rs index b1bcf115a3..17286c86ff 100644 --- a/modules/llrt_http/src/client.rs +++ b/modules/llrt_http/src/client.rs @@ -2,54 +2,121 @@ use std::convert::Infallible; use bytes::Bytes; use http_body_util::combinators::BoxBody; -use hyper_rustls::HttpsConnector; use hyper_util::{ client::legacy::{connect::HttpConnector, Client}, rt::{TokioExecutor, TokioTimer}, }; use llrt_dns_cache::CachedDnsResolver; -use llrt_tls::TLS_CONFIG; use once_cell::sync::Lazy; -use rustls::ClientConfig; - -use crate::{get_http_version, get_pool_idle_timeout, HttpVersion}; - -pub type HyperClient = - Client>, BoxBody>; -pub static HTTP_CLIENT: Lazy>> = - Lazy::new(|| build_client(None)); - -pub fn build_client( - tls_config: Option, -) -> Result> { - let pool_idle_timeout = get_pool_idle_timeout(); - - let config = if let Some(tls_config) = tls_config { - tls_config - } else { - match &*TLS_CONFIG { - Ok(tls_config) => tls_config.clone(), - Err(e) => return Err(e.to_string().into()), - } - }; - - let builder = hyper_rustls::HttpsConnectorBuilder::new() - .with_tls_config(config) - .https_or_http(); - - let mut cache_dns_connector = CachedDnsResolver::new().into_http_connector(); - cache_dns_connector.enforce_http(false); - - let https = match get_http_version() { - #[cfg(feature = "http2")] - HttpVersion::Http2 => builder - .enable_all_versions() - .wrap_connector(cache_dns_connector), - _ => builder.enable_http1().wrap_connector(cache_dns_connector), - }; - - Ok(Client::builder(TokioExecutor::new()) - .pool_idle_timeout(pool_idle_timeout) - .pool_timer(TokioTimer::new()) - .build(https)) + +use crate::{get_http_version, get_pool_idle_timeout}; + +// Rustls-based TLS backends +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +mod rustls_client { + use super::*; + use hyper_rustls::HttpsConnector; + use llrt_tls::TLS_CONFIG; + use rustls::ClientConfig; + + #[cfg(feature = "http2")] + use crate::HttpVersion; + + pub type HyperClient = + Client>, BoxBody>; + + pub static HTTP_CLIENT: Lazy>> = + Lazy::new(|| build_client(None)); + + pub fn build_client( + tls_config: Option, + ) -> Result> { + let pool_idle_timeout = get_pool_idle_timeout(); + + let config = if let Some(tls_config) = tls_config { + tls_config + } else { + match &*TLS_CONFIG { + Ok(tls_config) => tls_config.clone(), + Err(e) => return Err(e.to_string().into()), + } + }; + + let builder = hyper_rustls::HttpsConnectorBuilder::new() + .with_tls_config(config) + .https_or_http(); + + let mut cache_dns_connector = CachedDnsResolver::new().into_http_connector(); + cache_dns_connector.enforce_http(false); + + let https = match get_http_version() { + #[cfg(feature = "http2")] + HttpVersion::Http2 => builder + .enable_all_versions() + .wrap_connector(cache_dns_connector), + _ => builder.enable_http1().wrap_connector(cache_dns_connector), + }; + + Ok(Client::builder(TokioExecutor::new()) + .pool_idle_timeout(pool_idle_timeout) + .pool_timer(TokioTimer::new()) + .build(https)) + } } + +// OpenSSL TLS backend +#[cfg(feature = "tls-openssl")] +mod openssl_client { + use super::*; + use hyper_openssl::client::legacy::HttpsConnector; + use llrt_tls::TLS_CONFIG; + use openssl::ssl::SslConnectorBuilder; + + pub type HyperClient = + Client>, BoxBody>; + + pub static HTTP_CLIENT: Lazy>> = + Lazy::new(|| build_client(None)); + + pub fn build_client( + tls_config: Option, + ) -> Result> { + let pool_idle_timeout = get_pool_idle_timeout(); + + let connector = if let Some(tls_config) = tls_config { + tls_config + } else { + match TLS_CONFIG.as_ref() { + Ok(builder) => { + // Clone the builder by creating a new one with same settings + llrt_tls::build_client_config(llrt_tls::BuildClientConfigOptions { + reject_unauthorized: true, + ca: None, + })? + }, + Err(e) => return Err(e.to_string().into()), + } + }; + + let mut cache_dns_connector = CachedDnsResolver::new().into_http_connector(); + cache_dns_connector.enforce_http(false); + + let mut https = HttpsConnector::with_connector(cache_dns_connector, connector)?; + https.set_callback(|ssl, _| { + ssl.set_alpn_protos(b"\x02h2\x08http/1.1")?; + Ok(()) + }); + + Ok(Client::builder(TokioExecutor::new()) + .pool_idle_timeout(pool_idle_timeout) + .pool_timer(TokioTimer::new()) + .build(https)) + } +} + +// Re-export based on feature +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +pub use rustls_client::*; + +#[cfg(feature = "tls-openssl")] +pub use openssl_client::*; diff --git a/modules/llrt_tls/Cargo.toml b/modules/llrt_tls/Cargo.toml index b39ecec986..88398d7e46 100644 --- a/modules/llrt_tls/Cargo.toml +++ b/modules/llrt_tls/Cargo.toml @@ -12,18 +12,27 @@ name = "llrt_tls" path = "src/lib.rs" [features] -default = ["webpki-roots"] +default = ["webpki-roots", "tls-ring"] webpki-roots = ["dep:webpki-roots"] native-roots = ["dep:rustls-native-certs"] +# TLS crypto backend features (rustls-based) +tls-ring = ["dep:rustls", "rustls/ring"] +tls-aws-lc = ["dep:rustls", "rustls/aws_lc_rs"] +tls-graviola = ["dep:rustls", "dep:rustls-graviola"] + +# OpenSSL TLS backend +tls-openssl = ["dep:openssl"] + [dependencies] once_cell = { version = "1", features = ["std"], default-features = false } rustls = { version = "0.23", features = [ "std", - "ring", "tls12", -], default-features = false } +], default-features = false, optional = true } +rustls-graviola = { version = "0.3", optional = true } +openssl = { version = "0.10", optional = true } webpki-roots = { version = "1", default-features = false, optional = true } rustls-native-certs = { version = "0.8", default-features = false, optional = true } tracing = { version = "0.1", default-features = false } diff --git a/modules/llrt_tls/src/lib.rs b/modules/llrt_tls/src/lib.rs index e363ab1860..83acf79945 100644 --- a/modules/llrt_tls/src/lib.rs +++ b/modules/llrt_tls/src/lib.rs @@ -1,9 +1,20 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -pub use self::config::*; -mod config; +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +mod rustls_config; + +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +pub use rustls_config::*; + +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] mod no_verification; +#[cfg(feature = "tls-openssl")] +mod openssl_config; + +#[cfg(feature = "tls-openssl")] +pub use openssl_config::*; + // Once we are ready to add the node TLS module, it should be here. // Right now this module is supporting the https/fetch modules. diff --git a/modules/llrt_tls/src/openssl_config.rs b/modules/llrt_tls/src/openssl_config.rs new file mode 100644 index 0000000000..074c0390e3 --- /dev/null +++ b/modules/llrt_tls/src/openssl_config.rs @@ -0,0 +1,80 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use std::sync::OnceLock; + +use once_cell::sync::Lazy; +use openssl::ssl::{SslConnectorBuilder, SslMethod, SslVerifyMode}; +use openssl::x509::X509; + +static EXTRA_CA_CERTS: OnceLock>> = OnceLock::new(); + +pub fn set_extra_ca_certs(certs: Vec>) { + _ = EXTRA_CA_CERTS.set(certs); +} + +pub fn get_extra_ca_certs() -> Option>> { + let certs = EXTRA_CA_CERTS.get_or_init(Vec::new).clone(); + if certs.is_empty() { + None + } else { + Some(certs) + } +} + +static TLS_VERSION: OnceLock> = OnceLock::new(); + +pub fn set_tls_version(version: Option) { + _ = TLS_VERSION.set(version); +} + +pub fn get_tls_version() -> Option { + TLS_VERSION.get_or_init(|| None).clone() +} + +pub static TLS_CONFIG: Lazy>> = + Lazy::new(|| { + build_client_config(BuildClientConfigOptions { + reject_unauthorized: true, + ca: None, + }) + }); + +pub struct BuildClientConfigOptions { + pub reject_unauthorized: bool, + pub ca: Option>>, +} + +pub fn build_client_config( + options: BuildClientConfigOptions, +) -> Result> { + let mut builder = openssl::ssl::SslConnector::builder(SslMethod::tls_client())?; + + // TLS version + if let Some(version) = get_tls_version() { + builder.set_min_proto_version(Some(version))?; + } + + // Certificate verification + if !options.reject_unauthorized { + builder.set_verify(SslVerifyMode::NONE); + } else if let Some(ca) = options.ca { + for cert_pem in ca { + let cert = X509::from_pem(&cert_pem)?; + builder.cert_store_mut().add_cert(cert)?; + } + } else { + // Use system default CA certificates + builder.set_default_verify_paths()?; + + // Add extra CA certs if configured + if let Some(extra_certs) = get_extra_ca_certs() { + for cert_der in extra_certs { + if let Ok(cert) = X509::from_der(&cert_der) { + let _ = builder.cert_store_mut().add_cert(cert); + } + } + } + } + + Ok(builder) +} diff --git a/modules/llrt_tls/src/config.rs b/modules/llrt_tls/src/rustls_config.rs similarity index 86% rename from modules/llrt_tls/src/config.rs rename to modules/llrt_tls/src/rustls_config.rs index eff8f4c69c..c2a6a673d2 100644 --- a/modules/llrt_tls/src/config.rs +++ b/modules/llrt_tls/src/rustls_config.rs @@ -4,7 +4,6 @@ use std::sync::{Arc, OnceLock}; use once_cell::sync::Lazy; use rustls::{ - crypto::ring, pki_types::{pem::PemObject, CertificateDer}, ClientConfig, RootCertStore, SupportedProtocolVersion, }; @@ -13,6 +12,22 @@ use webpki_roots::TLS_SERVER_ROOTS; use crate::no_verification::NoCertificateVerification; +// Select the crypto provider based on feature flags +#[cfg(feature = "tls-ring")] +fn get_crypto_provider() -> Arc { + Arc::new(rustls::crypto::ring::default_provider()) +} + +#[cfg(feature = "tls-aws-lc")] +fn get_crypto_provider() -> Arc { + Arc::new(rustls::crypto::aws_lc_rs::default_provider()) +} + +#[cfg(feature = "tls-graviola")] +fn get_crypto_provider() -> Arc { + Arc::new(rustls_graviola::default_provider()) +} + static EXTRA_CA_CERTS: OnceLock>> = OnceLock::new(); pub fn set_extra_ca_certs(certs: Vec>) { @@ -59,7 +74,7 @@ pub struct BuildClientConfigOptions { pub fn build_client_config( options: BuildClientConfigOptions, ) -> Result> { - let provider = Arc::new(ring::default_provider()); + let provider = get_crypto_provider(); let builder = ClientConfig::builder_with_provider(provider.clone()); // TLS versions From 434bc5ec989768d71d4a64e84e8275ae1d68a9d9 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Mon, 15 Dec 2025 22:41:00 +0100 Subject: [PATCH 03/77] CI --- .github/workflows/build.yml | 8 ++++++++ .github/workflows/ci.yml | 26 ++++++++++++++++++++++++-- Makefile | 5 +++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e50b4ade74..5acb20fd26 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,6 +18,10 @@ on: required: false type: string default: "nightly" + cargo_features: + required: false + type: string + description: "Cargo features to use (e.g. --no-default-features --features crypto-ring-rust,tls-ring,macro)" jobs: build: @@ -81,11 +85,15 @@ jobs: toolchain: ${{ inputs.toolchain }} - name: Run tests if: inputs.platform != 'windows' + env: + CARGO_FEATURES: ${{ inputs.cargo_features }} run: | make test-ci 2>&1 - name: Run tests on windows if: inputs.platform == 'windows' shell: msys2 {0} + env: + CARGO_FEATURES: ${{ inputs.cargo_features }} run: | make test-ci 2>&1 - name: Build Linux binaries diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8adc83ff60..d4e07c18b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,12 +30,32 @@ jobs: echo "console.log(123);" > "bundle/js/test$i.js" done cargo clippy --all-targets --all-features -- -D warnings + build: needs: - check strategy: fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} matrix: + os: + - windows-latest + - ubuntu-latest + - ubuntu-24.04-arm + - macos-latest + - macos-14 + crypto: + - name: default + features: "" + - name: crypto-rust+tls-ring + features: "--no-default-features --features crypto-rust,tls-ring,macro" + - name: crypto-rust+tls-aws-lc + features: "--no-default-features --features crypto-rust,tls-aws-lc,macro" + - name: crypto-ring-rust+tls-ring + features: "--no-default-features --features crypto-ring-rust,tls-ring,macro" + - name: crypto-graviola-rust+tls-graviola + features: "--no-default-features --features crypto-graviola-rust,tls-graviola,macro" + - name: crypto-openssl+tls-openssl + features: "--no-default-features --features crypto-openssl,tls-openssl,macro" include: - os: windows-latest platform: windows @@ -53,7 +73,7 @@ jobs: platform: darwin arch: x86_64 toolchain: nightly - - os: macos-latest + - os: macos-14 platform: darwin arch: aarch64 toolchain: nightly @@ -63,6 +83,8 @@ jobs: platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} + cargo_features: ${{ matrix.crypto.features }} + modules: needs: - check @@ -81,7 +103,7 @@ jobs: platform: darwin arch: x86_64 toolchain: stable - - os: macos-latest + - os: macos-14 platform: darwin arch: aarch64 toolchain: stable diff --git a/Makefile b/Makefile index 9d47a6a93e..fb8ce99384 100644 --- a/Makefile +++ b/Makefile @@ -227,8 +227,13 @@ test-ci: export RUST_BACKTRACE = 1 test-ci: export TEST_SUB_DIR = unit test-ci: export LLRT_ASYNC_HOOKS = 1 test-ci: clean-js | toolchain js +ifdef CARGO_FEATURES + cargo $(TOOLCHAIN) -Z build-std -Z build-std-features test --target $(CURRENT_TARGET) $(CARGO_FEATURES) -- --nocapture --show-output + cargo $(TOOLCHAIN) run -r --target $(CURRENT_TARGET) $(CARGO_FEATURES) -- test -d bundle/js/__tests__/$(TEST_SUB_DIR) +else cargo $(TOOLCHAIN) -Z build-std -Z build-std-features test --target $(CURRENT_TARGET) --all-features -- --nocapture --show-output cargo $(TOOLCHAIN) run -r --target $(CURRENT_TARGET) -- test -d bundle/js/__tests__/$(TEST_SUB_DIR) +endif libs-arm64: lib/arm64/libzstd.a lib/zstd.h lib/zstd_errors.h libs-x64: lib/x64/libzstd.a lib/zstd.h lib/zstd_errors.h From 46821d8e92078f83624e96cebcdf1ca728bd57a6 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Mon, 15 Dec 2025 23:21:05 +0100 Subject: [PATCH 04/77] Allow all features --- llrt_core/src/http.rs | 30 ++++-- modules/llrt_crypto/src/provider/mod.rs | 100 +++++++++++++++--- modules/llrt_crypto/src/provider/openssl.rs | 5 +- .../llrt_crypto/src/subtle/generate_key.rs | 2 + modules/llrt_crypto/src/subtle/mod.rs | 8 ++ modules/llrt_http/src/client.rs | 20 +++- modules/llrt_tls/src/lib.rs | 15 ++- modules/llrt_tls/src/no_verification.rs | 1 + modules/llrt_tls/src/openssl_config.rs | 2 +- modules/llrt_tls/src/rustls_config.rs | 9 +- 10 files changed, 159 insertions(+), 33 deletions(-) diff --git a/llrt_core/src/http.rs b/llrt_core/src/http.rs index aca08fd954..cc762ff576 100644 --- a/llrt_core/src/http.rs +++ b/llrt_core/src/http.rs @@ -7,13 +7,22 @@ use tracing::warn; use crate::environment; use crate::modules::https::{set_http_version, set_pool_idle_timeout_seconds, HttpVersion}; -#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +#[cfg(all( + any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), + not(feature = "tls-openssl") +))] use std::{fs::File, io}; -#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +#[cfg(all( + any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), + not(feature = "tls-openssl") +))] use rustls::{pki_types::CertificateDer, version, SupportedProtocolVersion}; -#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +#[cfg(all( + any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), + not(feature = "tls-openssl") +))] use crate::modules::tls::{set_extra_ca_certs, set_tls_versions}; #[cfg(feature = "tls-openssl")] @@ -24,7 +33,10 @@ pub fn init() -> StdResult<(), Box> { set_pool_idle_timeout_seconds(pool_idle_timeout); } - #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] + #[cfg(all( + any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), + not(feature = "tls-openssl") + ))] { if let Some(extra_ca_certs) = build_extra_ca_certs()? { set_extra_ca_certs(extra_ca_certs); @@ -59,7 +71,10 @@ fn build_pool_idle_timeout() -> Option { Some(pool_idle_timeout) } -#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +#[cfg(all( + any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), + not(feature = "tls-openssl") +))] fn build_extra_ca_certs() -> StdResult>>, io::Error> { if let Ok(extra_ca_certs) = env::var(environment::ENV_LLRT_EXTRA_CA_CERTS) { if !extra_ca_certs.is_empty() { @@ -76,7 +91,10 @@ fn build_extra_ca_certs() -> StdResult>>, io: Ok(None) } -#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +#[cfg(all( + any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), + not(feature = "tls-openssl") +))] fn build_tls_versions() -> Vec<&'static SupportedProtocolVersion> { match env::var(environment::ENV_LLRT_TLS_VERSION).as_deref() { Ok("1.3") => vec![&version::TLS13, &version::TLS12], diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index 97bc088309..c200d4945a 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -3,10 +3,26 @@ #[cfg(any(feature = "crypto-graviola", feature = "crypto-graviola-rust"))] mod graviola; -#[cfg(feature = "crypto-openssl")] + +// OpenSSL module only compiles when it's the active provider (no other crypto features) +#[cfg(all( + feature = "crypto-openssl", + not(feature = "crypto-rust"), + not(feature = "crypto-ring"), + not(feature = "crypto-ring-rust"), + not(feature = "crypto-graviola"), + not(feature = "crypto-graviola-rust") +))] mod openssl; -#[cfg(any(feature = "crypto-ring", feature = "crypto-ring-rust"))] + +// Ring module compiles when ring or ring-rust is enabled and graviola variants aren't +#[cfg(all( + any(feature = "crypto-ring", feature = "crypto-ring-rust"), + not(feature = "crypto-graviola"), + not(feature = "crypto-graviola-rust") +))] mod ring; + #[cfg(any( feature = "crypto-rust", feature = "crypto-ring-rust", @@ -191,6 +207,7 @@ pub enum CryptoError { InvalidSignature, InvalidLength, SigningFailed, + #[allow(dead_code)] VerificationFailed, OperationFailed, UnsupportedAlgorithm, @@ -219,26 +236,55 @@ impl std::fmt::Display for CryptoError { impl std::error::Error for CryptoError {} -#[cfg(feature = "crypto-openssl")] +#[cfg(all( + feature = "crypto-openssl", + not(feature = "crypto-rust"), + not(feature = "crypto-ring"), + not(feature = "crypto-ring-rust"), + not(feature = "crypto-graviola"), + not(feature = "crypto-graviola-rust") +))] pub type DefaultProvider = openssl::OpenSslProvider; -#[cfg(feature = "crypto-rust")] +#[cfg(all( + feature = "crypto-rust", + not(feature = "crypto-ring"), + not(feature = "crypto-ring-rust"), + not(feature = "crypto-graviola"), + not(feature = "crypto-graviola-rust") +))] pub type DefaultProvider = rust::RustCryptoProvider; -#[cfg(feature = "crypto-ring")] +#[cfg(all( + feature = "crypto-ring", + not(feature = "crypto-ring-rust"), + not(feature = "crypto-graviola"), + not(feature = "crypto-graviola-rust") +))] pub type DefaultProvider = ring::RingProvider; -#[cfg(feature = "crypto-ring-rust")] +#[cfg(all( + feature = "crypto-ring-rust", + not(feature = "crypto-graviola"), + not(feature = "crypto-graviola-rust") +))] pub type DefaultProvider = RingRustProvider; -#[cfg(feature = "crypto-graviola")] +#[cfg(all(feature = "crypto-graviola", not(feature = "crypto-graviola-rust")))] pub type DefaultProvider = graviola::GraviolaProvider; #[cfg(feature = "crypto-graviola-rust")] pub type DefaultProvider = GraviolaRustProvider; // Macro to generate hybrid providers that delegate to RustCrypto -#[cfg(any(feature = "crypto-ring-rust", feature = "crypto-graviola-rust"))] +#[cfg(any( + all( + feature = "crypto-ring-rust", + not(feature = "crypto-graviola"), + not(feature = "crypto-graviola-rust") + ), + feature = "crypto-graviola-rust" +))] macro_rules! impl_hybrid_provider { ($name:ident, $digest:ty, $hmac:ty, $digest_fn:expr, $hmac_fn:expr, $aes_encrypt:expr, $aes_decrypt:expr) => { pub struct $name; @@ -411,7 +457,11 @@ macro_rules! impl_hybrid_provider { }; } -#[cfg(feature = "crypto-ring-rust")] +#[cfg(all( + feature = "crypto-ring-rust", + not(feature = "crypto-graviola"), + not(feature = "crypto-graviola-rust") +))] impl_hybrid_provider!( RingRustProvider, ring::RingDigestType, @@ -448,17 +498,39 @@ mod tests { use super::*; fn provider() -> impl CryptoProvider { - #[cfg(feature = "crypto-rust")] + #[cfg(all( + feature = "crypto-rust", + not(feature = "crypto-ring"), + not(feature = "crypto-ring-rust"), + not(feature = "crypto-graviola"), + not(feature = "crypto-graviola-rust") + ))] return rust::RustCryptoProvider; - #[cfg(feature = "crypto-ring-rust")] + #[cfg(all( + feature = "crypto-ring-rust", + not(feature = "crypto-graviola"), + not(feature = "crypto-graviola-rust") + ))] return RingRustProvider; #[cfg(feature = "crypto-graviola-rust")] return GraviolaRustProvider; - #[cfg(feature = "crypto-openssl")] + #[cfg(all( + feature = "crypto-openssl", + not(feature = "crypto-rust"), + not(feature = "crypto-ring"), + not(feature = "crypto-ring-rust"), + not(feature = "crypto-graviola"), + not(feature = "crypto-graviola-rust") + ))] return openssl::OpenSslProvider; - #[cfg(feature = "crypto-ring")] + #[cfg(all( + feature = "crypto-ring", + not(feature = "crypto-ring-rust"), + not(feature = "crypto-graviola"), + not(feature = "crypto-graviola-rust") + ))] return ring::RingProvider; - #[cfg(feature = "crypto-graviola")] + #[cfg(all(feature = "crypto-graviola", not(feature = "crypto-graviola-rust")))] return graviola::GraviolaProvider; } diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs index 7c64b40b15..3daf6472e4 100644 --- a/modules/llrt_crypto/src/provider/openssl.rs +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -4,9 +4,8 @@ //! OpenSSL crypto provider - uses OpenSSL for cryptographic operations. use openssl::hash::{Hasher, MessageDigest}; -use openssl::pkey::{PKey, Private}; -use openssl::sign::{Signer, Verifier}; -use openssl::symm::{Cipher, Crypter, Mode}; +use openssl::pkey::PKey; +use openssl::sign::Signer; use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; use crate::sha_hash::ShaAlgorithm; diff --git a/modules/llrt_crypto/src/subtle/generate_key.rs b/modules/llrt_crypto/src/subtle/generate_key.rs index 34e251ac24..0fc1498c37 100644 --- a/modules/llrt_crypto/src/subtle/generate_key.rs +++ b/modules/llrt_crypto/src/subtle/generate_key.rs @@ -114,10 +114,12 @@ fn generate_key(ctx: &Ctx<'_>, algorithm: &KeyAlgorithm) -> Result<(Vec, Vec } } +#[allow(dead_code)] fn generate_symmetric_key(_ctx: &Ctx<'_>, length: usize) -> Result> { Ok(crate::random_byte_array(length)) } +#[allow(dead_code)] pub fn get_hash_length(ctx: &Ctx, hash: &ShaAlgorithm, length: u16) -> Result { if length == 0 { return Ok(hash.hmac_algorithm().digest_algorithm().block_len()); diff --git a/modules/llrt_crypto/src/subtle/mod.rs b/modules/llrt_crypto/src/subtle/mod.rs index 95412d5821..e6895763d5 100644 --- a/modules/llrt_crypto/src/subtle/mod.rs +++ b/modules/llrt_crypto/src/subtle/mod.rs @@ -69,12 +69,14 @@ impl SubtleCrypto { } } +#[allow(dead_code)] pub enum AesCbcEncVariant { Aes128(cbc::Encryptor), Aes192(cbc::Encryptor), Aes256(cbc::Encryptor), } +#[allow(dead_code)] impl AesCbcEncVariant { pub fn new(key_len: u16, key: &[u8], iv: &[u8]) -> std::result::Result { let variant: AesCbcEncVariant = match key_len { @@ -96,12 +98,14 @@ impl AesCbcEncVariant { } } +#[allow(dead_code)] pub enum AesCbcDecVariant { Aes128(cbc::Decryptor), Aes192(cbc::Decryptor), Aes256(cbc::Decryptor), } +#[allow(dead_code)] impl AesCbcDecVariant { pub fn new(key_len: u16, key: &[u8], iv: &[u8]) -> std::result::Result { let variant: AesCbcDecVariant = match key_len { @@ -123,6 +127,7 @@ impl AesCbcDecVariant { } } +#[allow(dead_code)] pub enum AesCtrVariant { Aes128Ctr32(Ctr32BE), Aes128Ctr64(Ctr64BE), @@ -135,6 +140,7 @@ pub enum AesCtrVariant { Aes256Ctr128(Ctr128BE), } +#[allow(dead_code)] impl AesCtrVariant { pub fn new( key_len: u16, @@ -191,6 +197,7 @@ impl AesCtrVariant { } } +#[allow(dead_code)] pub enum AesGcmVariant { Aes128Gcm96(AesGcm), Aes192Gcm96(AesGcm), @@ -209,6 +216,7 @@ pub enum AesGcmVariant { Aes256Gcm128(AesGcm), } +#[allow(dead_code)] impl AesGcmVariant { pub fn new( key_len: u16, diff --git a/modules/llrt_http/src/client.rs b/modules/llrt_http/src/client.rs index 17286c86ff..00462d4866 100644 --- a/modules/llrt_http/src/client.rs +++ b/modules/llrt_http/src/client.rs @@ -9,10 +9,19 @@ use hyper_util::{ use llrt_dns_cache::CachedDnsResolver; use once_cell::sync::Lazy; -use crate::{get_http_version, get_pool_idle_timeout}; +use crate::get_pool_idle_timeout; + +#[cfg(all( + any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), + not(feature = "tls-openssl") +))] +use crate::get_http_version; // Rustls-based TLS backends -#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +#[cfg(all( + any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), + not(feature = "tls-openssl") +))] mod rustls_client { use super::*; use hyper_rustls::HttpsConnector; @@ -87,7 +96,7 @@ mod openssl_client { tls_config } else { match TLS_CONFIG.as_ref() { - Ok(builder) => { + Ok(_builder) => { // Clone the builder by creating a new one with same settings llrt_tls::build_client_config(llrt_tls::BuildClientConfigOptions { reject_unauthorized: true, @@ -115,7 +124,10 @@ mod openssl_client { } // Re-export based on feature -#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +#[cfg(all( + any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), + not(feature = "tls-openssl") +))] pub use rustls_client::*; #[cfg(feature = "tls-openssl")] diff --git a/modules/llrt_tls/src/lib.rs b/modules/llrt_tls/src/lib.rs index 83acf79945..ff72d1ea17 100644 --- a/modules/llrt_tls/src/lib.rs +++ b/modules/llrt_tls/src/lib.rs @@ -1,13 +1,22 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +#[cfg(all( + any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), + not(feature = "tls-openssl") +))] mod rustls_config; -#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +#[cfg(all( + any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), + not(feature = "tls-openssl") +))] pub use rustls_config::*; -#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +#[cfg(all( + any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), + not(feature = "tls-openssl") +))] mod no_verification; #[cfg(feature = "tls-openssl")] diff --git a/modules/llrt_tls/src/no_verification.rs b/modules/llrt_tls/src/no_verification.rs index 048030a447..92f509edfe 100644 --- a/modules/llrt_tls/src/no_verification.rs +++ b/modules/llrt_tls/src/no_verification.rs @@ -1,5 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 + use std::sync::Arc; use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; diff --git a/modules/llrt_tls/src/openssl_config.rs b/modules/llrt_tls/src/openssl_config.rs index 074c0390e3..60f21267ce 100644 --- a/modules/llrt_tls/src/openssl_config.rs +++ b/modules/llrt_tls/src/openssl_config.rs @@ -28,7 +28,7 @@ pub fn set_tls_version(version: Option) { } pub fn get_tls_version() -> Option { - TLS_VERSION.get_or_init(|| None).clone() + *TLS_VERSION.get_or_init(|| None) } pub static TLS_CONFIG: Lazy>> = diff --git a/modules/llrt_tls/src/rustls_config.rs b/modules/llrt_tls/src/rustls_config.rs index c2a6a673d2..5bb6826b81 100644 --- a/modules/llrt_tls/src/rustls_config.rs +++ b/modules/llrt_tls/src/rustls_config.rs @@ -1,5 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 + use std::sync::{Arc, OnceLock}; use once_cell::sync::Lazy; @@ -13,12 +14,16 @@ use webpki_roots::TLS_SERVER_ROOTS; use crate::no_verification::NoCertificateVerification; // Select the crypto provider based on feature flags -#[cfg(feature = "tls-ring")] +#[cfg(all( + feature = "tls-ring", + not(feature = "tls-aws-lc"), + not(feature = "tls-graviola") +))] fn get_crypto_provider() -> Arc { Arc::new(rustls::crypto::ring::default_provider()) } -#[cfg(feature = "tls-aws-lc")] +#[cfg(all(feature = "tls-aws-lc", not(feature = "tls-graviola")))] fn get_crypto_provider() -> Arc { Arc::new(rustls::crypto::aws_lc_rs::default_provider()) } From 2776c606a67e615b6a0458906210baf2fc0068ac Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 16 Dec 2025 08:33:33 +0100 Subject: [PATCH 05/77] Fix fixtures --- .github/workflows/build-modules.yml | 3 +++ .github/workflows/build.yml | 2 +- .github/workflows/ci.yml | 2 ++ Cargo.lock | 1 + libs/llrt_test_tls/Cargo.toml | 8 +++++++- libs/llrt_test_tls/src/server.rs | 22 +++++++++++++++++++++- modules/llrt_fetch/Cargo.toml | 6 +++++- 7 files changed, 40 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index b18a95c191..6008f329cd 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -22,6 +22,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + - name: Install Linux dependencies + if: inputs.platform == 'linux' + run: sudo apt-get update && sudo apt-get install -y libssl-dev - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5acb20fd26..dd57214174 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,7 +41,7 @@ jobs: if: inputs.platform == 'linux' run: | sudo apt-get -y update - sudo apt-get -y install make nodejs + sudo apt-get -y install make nodejs libssl-dev sudo snap install zig --classic --beta - name: Install MacOS dependencies if: inputs.platform == 'darwin' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4e07c18b6..1ce66aaaf9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,8 @@ jobs: components: clippy, rustfmt - name: Format run: cargo fmt --all -- --check + - name: Install OpenSSL dev packages + run: sudo apt-get update && sudo apt-get install -y libssl-dev - name: Clippy run: | #create mock js files mkdir -p bundle/js diff --git a/Cargo.lock b/Cargo.lock index ba94c3dfdd..a183c6d682 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2317,6 +2317,7 @@ dependencies = [ "hyper", "hyper-util", "rustls", + "rustls-graviola", "tokio", "tokio-rustls", ] diff --git a/libs/llrt_test_tls/Cargo.toml b/libs/llrt_test_tls/Cargo.toml index 0f8161a1aa..4a81294d91 100644 --- a/libs/llrt_test_tls/Cargo.toml +++ b/libs/llrt_test_tls/Cargo.toml @@ -10,6 +10,12 @@ repository = "https://github.com/awslabs/llrt" name = "llrt_test_tls" path = "src/lib.rs" +[features] +default = ["tls-ring"] +tls-ring = ["rustls/ring"] +tls-aws-lc = ["rustls/aws_lc_rs"] +tls-graviola = ["dep:rustls-graviola"] + [dependencies] http-body-util = { version = "0.1", default-features = false } hyper = { version = "1", features = ["server"], default-features = false } @@ -18,8 +24,8 @@ hyper-util = { version = "0.1", features = [ ], default-features = false } http = { version = "1", default-features = false } rustls = { version = "0.23", features = [ - "ring", "tls12", ], default-features = false } +rustls-graviola = { version = "0.3", optional = true } tokio = { version = "1", features = ["net", "fs"], default-features = false } tokio-rustls = { version = "0.26", default-features = false } diff --git a/libs/llrt_test_tls/src/server.rs b/libs/llrt_test_tls/src/server.rs index 96112dbc58..4f5fa6d030 100644 --- a/libs/llrt_test_tls/src/server.rs +++ b/libs/llrt_test_tls/src/server.rs @@ -9,13 +9,33 @@ use tokio_rustls::TlsAcceptor; use crate::MockServerCerts; +#[cfg(all( + feature = "tls-ring", + not(feature = "tls-aws-lc"), + not(feature = "tls-graviola") +))] +fn get_crypto_provider() -> Arc { + Arc::new(rustls::crypto::ring::default_provider()) +} + +#[cfg(all(feature = "tls-aws-lc", not(feature = "tls-graviola")))] +fn get_crypto_provider() -> Arc { + Arc::new(rustls::crypto::aws_lc_rs::default_provider()) +} + +#[cfg(feature = "tls-graviola")] +fn get_crypto_provider() -> Arc { + Arc::new(rustls_graviola::default_provider()) +} + pub(super) async fn run( listener: TcpListener, certs: MockServerCerts, shutdown_rx: tokio::sync::watch::Receiver<()>, ) -> Result<(), Box> { let cert_chain = vec![certs.server_cert, certs.root_cert]; - let mut server_config = ServerConfig::builder() + let mut server_config = ServerConfig::builder_with_provider(get_crypto_provider()) + .with_safe_default_protocol_versions()? .with_no_client_auth() .with_single_cert(cert_chain, certs.server_key)?; server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()]; diff --git a/modules/llrt_fetch/Cargo.toml b/modules/llrt_fetch/Cargo.toml index 6c019ae97e..f8ddad19f6 100644 --- a/modules/llrt_fetch/Cargo.toml +++ b/modules/llrt_fetch/Cargo.toml @@ -23,6 +23,10 @@ compression-rust = ["llrt_compression/all-rust"] webpki-roots = ["llrt_http/webpki-roots"] native-roots = ["llrt_http/native-roots"] +tls-ring = [] +tls-aws-lc = [] +tls-graviola = [] + [dependencies] bytes = { version = "1", default-features = false } either = { version = "1", default-features = false } @@ -60,5 +64,5 @@ tracing = { version = "0.1", default-features = false } [dev-dependencies] llrt_compression = { version = "0.7.0-beta", path = "../../libs/llrt_compression" } llrt_test = { path = "../../libs/llrt_test" } -llrt_test_tls = { path = "../../libs/llrt_test_tls" } +llrt_test_tls = { path = "../../libs/llrt_test_tls", default-features = false } wiremock = { version = "0.6", default-features = false } From 0554c9c8280b490b5be5e5651d1d16d72dee6bee Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 16 Dec 2025 08:42:14 +0100 Subject: [PATCH 06/77] pkg-config --- .github/workflows/build-modules.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/ci.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index 6008f329cd..93c923a6cb 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v6 - name: Install Linux dependencies if: inputs.platform == 'linux' - run: sudo apt-get update && sudo apt-get install -y libssl-dev + run: sudo apt-get update && sudo apt-get install -y libssl-dev pkg-config - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dd57214174..3bb6ab483d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,7 +41,7 @@ jobs: if: inputs.platform == 'linux' run: | sudo apt-get -y update - sudo apt-get -y install make nodejs libssl-dev + sudo apt-get -y install make nodejs libssl-dev pkg-config sudo snap install zig --classic --beta - name: Install MacOS dependencies if: inputs.platform == 'darwin' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ce66aaaf9..d0d9d1bbac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - name: Format run: cargo fmt --all -- --check - name: Install OpenSSL dev packages - run: sudo apt-get update && sudo apt-get install -y libssl-dev + run: sudo apt-get update && sudo apt-get install -y libssl-dev pkg-config - name: Clippy run: | #create mock js files mkdir -p bundle/js From be186c376aafcf99d22b49298af1153363680a07 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 16 Dec 2025 16:08:55 +0100 Subject: [PATCH 07/77] Vendored openssl --- .github/workflows/build-modules.yml | 3 --- .github/workflows/build.yml | 2 +- .github/workflows/ci.yml | 15 ++++++++++++--- Cargo.lock | 10 ++++++++++ llrt/Cargo.toml | 3 +++ llrt_core/Cargo.toml | 3 +++ llrt_modules/Cargo.toml | 3 +++ modules/llrt_crypto/Cargo.toml | 1 + modules/llrt_http/Cargo.toml | 1 + modules/llrt_tls/Cargo.toml | 1 + 10 files changed, 35 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index 93c923a6cb..b18a95c191 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -22,9 +22,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 - - name: Install Linux dependencies - if: inputs.platform == 'linux' - run: sudo apt-get update && sudo apt-get install -y libssl-dev pkg-config - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3bb6ab483d..5acb20fd26 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,7 +41,7 @@ jobs: if: inputs.platform == 'linux' run: | sudo apt-get -y update - sudo apt-get -y install make nodejs libssl-dev pkg-config + sudo apt-get -y install make nodejs sudo snap install zig --classic --beta - name: Install MacOS dependencies if: inputs.platform == 'darwin' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0d9d1bbac..1315e67842 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,15 +23,13 @@ jobs: components: clippy, rustfmt - name: Format run: cargo fmt --all -- --check - - name: Install OpenSSL dev packages - run: sudo apt-get update && sudo apt-get install -y libssl-dev pkg-config - name: Clippy run: | #create mock js files mkdir -p bundle/js for i in {1..5}; do echo "console.log(123);" > "bundle/js/test$i.js" done - cargo clippy --all-targets --all-features -- -D warnings + cargo clippy --all-targets --all-features --features openssl-vendored -- -D warnings build: needs: @@ -58,6 +56,17 @@ jobs: features: "--no-default-features --features crypto-graviola-rust,tls-graviola,macro" - name: crypto-openssl+tls-openssl features: "--no-default-features --features crypto-openssl,tls-openssl,macro" + exclude: + # OpenSSL requires native compilation - exclude from cross-compile targets + - os: ubuntu-latest + crypto: + name: crypto-openssl+tls-openssl + - os: ubuntu-24.04-arm + crypto: + name: crypto-openssl+tls-openssl + - os: windows-latest + crypto: + name: crypto-openssl+tls-openssl include: - os: windows-latest platform: windows diff --git a/Cargo.lock b/Cargo.lock index a183c6d682..e23d5adba5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2585,6 +2585,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "openssl-src" +version = "300.5.4+3.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.111" @@ -2593,6 +2602,7 @@ checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] diff --git a/llrt/Cargo.toml b/llrt/Cargo.toml index 67fb9abb06..1496573e84 100644 --- a/llrt/Cargo.toml +++ b/llrt/Cargo.toml @@ -24,6 +24,9 @@ crypto-ring-rust = ["llrt_core/crypto-ring-rust"] crypto-graviola-rust = ["llrt_core/crypto-graviola-rust"] crypto-openssl = ["llrt_core/crypto-openssl"] +# OpenSSL vendored (builds OpenSSL from source) +openssl-vendored = ["llrt_core/openssl-vendored"] + [dependencies] chrono = { version = "0.4", features = ["std"], default-features = false } constcat = { version = "0.6", default-features = false } diff --git a/llrt_core/Cargo.toml b/llrt_core/Cargo.toml index a3c2187aed..2785fad9c3 100644 --- a/llrt_core/Cargo.toml +++ b/llrt_core/Cargo.toml @@ -24,6 +24,9 @@ crypto-ring-rust = ["llrt_modules/crypto-ring-rust"] crypto-graviola-rust = ["llrt_modules/crypto-graviola-rust"] crypto-openssl = ["llrt_modules/crypto-openssl"] +# OpenSSL vendored (builds OpenSSL from source) +openssl-vendored = ["llrt_modules/openssl-vendored", "openssl?/vendored"] + [dependencies] bytes = { version = "1", default-features = false } chrono = { version = "0.4", features = ["std", "clock"], default-features = false } diff --git a/llrt_modules/Cargo.toml b/llrt_modules/Cargo.toml index bdbb6da6c7..7b3fdb9ca6 100644 --- a/llrt_modules/Cargo.toml +++ b/llrt_modules/Cargo.toml @@ -23,6 +23,9 @@ crypto-ring-rust = ["llrt_crypto?/crypto-ring-rust"] crypto-graviola-rust = ["llrt_crypto?/crypto-graviola-rust"] crypto-openssl = ["llrt_crypto?/crypto-openssl"] +# OpenSSL vendored (builds OpenSSL from source) +openssl-vendored = ["llrt_http?/openssl-vendored", "llrt_crypto?/openssl-vendored"] + base = [ "abort", "assert", diff --git a/modules/llrt_crypto/Cargo.toml b/modules/llrt_crypto/Cargo.toml index 9189556042..1538e04972 100644 --- a/modules/llrt_crypto/Cargo.toml +++ b/modules/llrt_crypto/Cargo.toml @@ -105,6 +105,7 @@ crypto-openssl = [ "hmac", "sha1", ] +openssl-vendored = ["openssl-sys?/vendored", "openssl?/vendored"] [dependencies] crc32c = { version = "0.6", default-features = false } diff --git a/modules/llrt_http/Cargo.toml b/modules/llrt_http/Cargo.toml index 2bca81772f..f4def95789 100644 --- a/modules/llrt_http/Cargo.toml +++ b/modules/llrt_http/Cargo.toml @@ -27,6 +27,7 @@ tls-graviola = ["llrt_tls/tls-graviola", "dep:hyper-rustls", "dep:rustls"] # OpenSSL TLS backend tls-openssl = ["llrt_tls/tls-openssl", "dep:hyper-openssl", "dep:openssl"] +openssl-vendored = ["llrt_tls/openssl-vendored", "openssl?/vendored"] [dependencies] bytes = { version = "1", default-features = false } diff --git a/modules/llrt_tls/Cargo.toml b/modules/llrt_tls/Cargo.toml index 88398d7e46..e6b006859a 100644 --- a/modules/llrt_tls/Cargo.toml +++ b/modules/llrt_tls/Cargo.toml @@ -24,6 +24,7 @@ tls-graviola = ["dep:rustls", "dep:rustls-graviola"] # OpenSSL TLS backend tls-openssl = ["dep:openssl"] +openssl-vendored = ["openssl?/vendored"] [dependencies] once_cell = { version = "1", features = ["std"], default-features = false } From 410ba93158cdd07888fdc14272d1abd264ad1a0c Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 16 Dec 2025 18:05:53 +0100 Subject: [PATCH 08/77] Fix --- modules/llrt_http/src/lib.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/modules/llrt_http/src/lib.rs b/modules/llrt_http/src/lib.rs index 1f80f80d7a..22fc4e420d 100644 --- a/modules/llrt_http/src/lib.rs +++ b/modules/llrt_http/src/lib.rs @@ -6,10 +6,22 @@ use rquickjs::{ Class, Ctx, Result, }; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] pub use self::agent::Agent; pub use self::client::*; pub use self::config::*; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] mod agent; mod client; mod config; @@ -20,6 +32,12 @@ pub struct HttpsModule; impl ModuleDef for HttpsModule { fn declare(declare: &Declarations) -> Result<()> { + #[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" + ))] declare.declare(stringify!(Agent))?; declare.declare("default")?; Ok(()) @@ -27,6 +45,12 @@ impl ModuleDef for HttpsModule { fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { + #[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" + ))] Class::::define(default)?; Ok(()) From d3c8e3d6b09d7bab44ec8554f75637175bb0c7c3 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 16 Dec 2025 23:34:41 +0100 Subject: [PATCH 09/77] Cleanup features --- .github/workflows/ci.yml | 2 +- Makefile | 6 +- llrt_core/src/http.rs | 30 +---- modules/llrt_crypto/src/provider/mod.rs | 114 ++++++------------- modules/llrt_crypto/src/subtle/import_key.rs | 1 - modules/llrt_crypto/src/subtle/mod.rs | 1 - modules/llrt_http/src/client.rs | 15 +-- modules/llrt_tls/src/lib.rs | 34 ++++-- modules/llrt_tls/src/rustls_config.rs | 8 +- 9 files changed, 71 insertions(+), 140 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1315e67842..2cb3680f06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: for i in {1..5}; do echo "console.log(123);" > "bundle/js/test$i.js" done - cargo clippy --all-targets --all-features --features openssl-vendored -- -D warnings + cargo clippy --all-targets --features "lambda,macro,no-sdk,uncompressed,crypto-rust,tls-ring,openssl-vendored" -- -D warnings build: needs: diff --git a/Makefile b/Makefile index fb8ce99384..2c7efacd8e 100644 --- a/Makefile +++ b/Makefile @@ -231,8 +231,8 @@ ifdef CARGO_FEATURES cargo $(TOOLCHAIN) -Z build-std -Z build-std-features test --target $(CURRENT_TARGET) $(CARGO_FEATURES) -- --nocapture --show-output cargo $(TOOLCHAIN) run -r --target $(CURRENT_TARGET) $(CARGO_FEATURES) -- test -d bundle/js/__tests__/$(TEST_SUB_DIR) else - cargo $(TOOLCHAIN) -Z build-std -Z build-std-features test --target $(CURRENT_TARGET) --all-features -- --nocapture --show-output - cargo $(TOOLCHAIN) run -r --target $(CURRENT_TARGET) -- test -d bundle/js/__tests__/$(TEST_SUB_DIR) + cargo $(TOOLCHAIN) -Z build-std -Z build-std-features test --target $(CURRENT_TARGET) --features lambda -- --nocapture --show-output + cargo $(TOOLCHAIN) run -r --target $(CURRENT_TARGET) --features lambda -- test -d bundle/js/__tests__/$(TEST_SUB_DIR) endif libs-arm64: lib/arm64/libzstd.a lib/zstd.h lib/zstd_errors.h @@ -266,7 +266,7 @@ deploy: cd example/infrastructure && yarn deploy --require-approval never check: - cargo clippy --all-targets --all-features -- -D warnings + cargo clippy --all-targets --features "lambda,macro,no-sdk,uncompressed,crypto-rust,tls-ring,openssl-vendored" -- -D warnings check-crates: cargo metadata --no-deps --format-version 1 --quiet | \ diff --git a/llrt_core/src/http.rs b/llrt_core/src/http.rs index cc762ff576..aca08fd954 100644 --- a/llrt_core/src/http.rs +++ b/llrt_core/src/http.rs @@ -7,22 +7,13 @@ use tracing::warn; use crate::environment; use crate::modules::https::{set_http_version, set_pool_idle_timeout_seconds, HttpVersion}; -#[cfg(all( - any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), - not(feature = "tls-openssl") -))] +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] use std::{fs::File, io}; -#[cfg(all( - any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), - not(feature = "tls-openssl") -))] +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] use rustls::{pki_types::CertificateDer, version, SupportedProtocolVersion}; -#[cfg(all( - any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), - not(feature = "tls-openssl") -))] +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] use crate::modules::tls::{set_extra_ca_certs, set_tls_versions}; #[cfg(feature = "tls-openssl")] @@ -33,10 +24,7 @@ pub fn init() -> StdResult<(), Box> { set_pool_idle_timeout_seconds(pool_idle_timeout); } - #[cfg(all( - any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), - not(feature = "tls-openssl") - ))] + #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] { if let Some(extra_ca_certs) = build_extra_ca_certs()? { set_extra_ca_certs(extra_ca_certs); @@ -71,10 +59,7 @@ fn build_pool_idle_timeout() -> Option { Some(pool_idle_timeout) } -#[cfg(all( - any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), - not(feature = "tls-openssl") -))] +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] fn build_extra_ca_certs() -> StdResult>>, io::Error> { if let Ok(extra_ca_certs) = env::var(environment::ENV_LLRT_EXTRA_CA_CERTS) { if !extra_ca_certs.is_empty() { @@ -91,10 +76,7 @@ fn build_extra_ca_certs() -> StdResult>>, io: Ok(None) } -#[cfg(all( - any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), - not(feature = "tls-openssl") -))] +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] fn build_tls_versions() -> Vec<&'static SupportedProtocolVersion> { match env::var(environment::ENV_LLRT_TLS_VERSION).as_deref() { Ok("1.3") => vec![&version::TLS13, &version::TLS12], diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index c200d4945a..089655b919 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -1,26 +1,35 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +// Ensure only one crypto provider is selected +#[cfg(all(feature = "crypto-rust", feature = "crypto-openssl"))] +compile_error!("Features `crypto-rust` and `crypto-openssl` are mutually exclusive"); + +#[cfg(all(feature = "crypto-rust", feature = "crypto-ring"))] +compile_error!("Features `crypto-rust` and `crypto-ring` are mutually exclusive"); + +#[cfg(all(feature = "crypto-rust", feature = "crypto-graviola"))] +compile_error!("Features `crypto-rust` and `crypto-graviola` are mutually exclusive"); + +#[cfg(all(feature = "crypto-ring", feature = "crypto-openssl"))] +compile_error!("Features `crypto-ring` and `crypto-openssl` are mutually exclusive"); + +#[cfg(all(feature = "crypto-ring", feature = "crypto-graviola"))] +compile_error!("Features `crypto-ring` and `crypto-graviola` are mutually exclusive"); + +#[cfg(all(feature = "crypto-openssl", feature = "crypto-graviola"))] +compile_error!("Features `crypto-openssl` and `crypto-graviola` are mutually exclusive"); + +#[cfg(all(feature = "crypto-ring-rust", feature = "crypto-graviola-rust"))] +compile_error!("Features `crypto-ring-rust` and `crypto-graviola-rust` are mutually exclusive"); + #[cfg(any(feature = "crypto-graviola", feature = "crypto-graviola-rust"))] mod graviola; -// OpenSSL module only compiles when it's the active provider (no other crypto features) -#[cfg(all( - feature = "crypto-openssl", - not(feature = "crypto-rust"), - not(feature = "crypto-ring"), - not(feature = "crypto-ring-rust"), - not(feature = "crypto-graviola"), - not(feature = "crypto-graviola-rust") -))] +#[cfg(feature = "crypto-openssl")] mod openssl; -// Ring module compiles when ring or ring-rust is enabled and graviola variants aren't -#[cfg(all( - any(feature = "crypto-ring", feature = "crypto-ring-rust"), - not(feature = "crypto-graviola"), - not(feature = "crypto-graviola-rust") -))] +#[cfg(any(feature = "crypto-ring", feature = "crypto-ring-rust"))] mod ring; #[cfg(any( @@ -236,38 +245,16 @@ impl std::fmt::Display for CryptoError { impl std::error::Error for CryptoError {} -#[cfg(all( - feature = "crypto-openssl", - not(feature = "crypto-rust"), - not(feature = "crypto-ring"), - not(feature = "crypto-ring-rust"), - not(feature = "crypto-graviola"), - not(feature = "crypto-graviola-rust") -))] +#[cfg(feature = "crypto-openssl")] pub type DefaultProvider = openssl::OpenSslProvider; -#[cfg(all( - feature = "crypto-rust", - not(feature = "crypto-ring"), - not(feature = "crypto-ring-rust"), - not(feature = "crypto-graviola"), - not(feature = "crypto-graviola-rust") -))] +#[cfg(feature = "crypto-rust")] pub type DefaultProvider = rust::RustCryptoProvider; -#[cfg(all( - feature = "crypto-ring", - not(feature = "crypto-ring-rust"), - not(feature = "crypto-graviola"), - not(feature = "crypto-graviola-rust") -))] +#[cfg(feature = "crypto-ring")] pub type DefaultProvider = ring::RingProvider; -#[cfg(all( - feature = "crypto-ring-rust", - not(feature = "crypto-graviola"), - not(feature = "crypto-graviola-rust") -))] +#[cfg(feature = "crypto-ring-rust")] pub type DefaultProvider = RingRustProvider; #[cfg(all(feature = "crypto-graviola", not(feature = "crypto-graviola-rust")))] @@ -277,14 +264,7 @@ pub type DefaultProvider = graviola::GraviolaProvider; pub type DefaultProvider = GraviolaRustProvider; // Macro to generate hybrid providers that delegate to RustCrypto -#[cfg(any( - all( - feature = "crypto-ring-rust", - not(feature = "crypto-graviola"), - not(feature = "crypto-graviola-rust") - ), - feature = "crypto-graviola-rust" -))] +#[cfg(any(feature = "crypto-ring-rust", feature = "crypto-graviola-rust"))] macro_rules! impl_hybrid_provider { ($name:ident, $digest:ty, $hmac:ty, $digest_fn:expr, $hmac_fn:expr, $aes_encrypt:expr, $aes_decrypt:expr) => { pub struct $name; @@ -457,11 +437,7 @@ macro_rules! impl_hybrid_provider { }; } -#[cfg(all( - feature = "crypto-ring-rust", - not(feature = "crypto-graviola"), - not(feature = "crypto-graviola-rust") -))] +#[cfg(feature = "crypto-ring-rust")] impl_hybrid_provider!( RingRustProvider, ring::RingDigestType, @@ -498,37 +474,15 @@ mod tests { use super::*; fn provider() -> impl CryptoProvider { - #[cfg(all( - feature = "crypto-rust", - not(feature = "crypto-ring"), - not(feature = "crypto-ring-rust"), - not(feature = "crypto-graviola"), - not(feature = "crypto-graviola-rust") - ))] + #[cfg(feature = "crypto-rust")] return rust::RustCryptoProvider; - #[cfg(all( - feature = "crypto-ring-rust", - not(feature = "crypto-graviola"), - not(feature = "crypto-graviola-rust") - ))] + #[cfg(feature = "crypto-ring-rust")] return RingRustProvider; #[cfg(feature = "crypto-graviola-rust")] return GraviolaRustProvider; - #[cfg(all( - feature = "crypto-openssl", - not(feature = "crypto-rust"), - not(feature = "crypto-ring"), - not(feature = "crypto-ring-rust"), - not(feature = "crypto-graviola"), - not(feature = "crypto-graviola-rust") - ))] + #[cfg(feature = "crypto-openssl")] return openssl::OpenSslProvider; - #[cfg(all( - feature = "crypto-ring", - not(feature = "crypto-ring-rust"), - not(feature = "crypto-graviola"), - not(feature = "crypto-graviola-rust") - ))] + #[cfg(feature = "crypto-ring")] return ring::RingProvider; #[cfg(all(feature = "crypto-graviola", not(feature = "crypto-graviola-rust")))] return graviola::GraviolaProvider; diff --git a/modules/llrt_crypto/src/subtle/import_key.rs b/modules/llrt_crypto/src/subtle/import_key.rs index 2cf7babac0..3b199d7476 100644 --- a/modules/llrt_crypto/src/subtle/import_key.rs +++ b/modules/llrt_crypto/src/subtle/import_key.rs @@ -12,7 +12,6 @@ use super::{ }, }; -#[allow(dead_code)] pub async fn subtle_import_key<'js>( ctx: Ctx<'js>, format: KeyFormat, diff --git a/modules/llrt_crypto/src/subtle/mod.rs b/modules/llrt_crypto/src/subtle/mod.rs index e6895763d5..bf6ae5e204 100644 --- a/modules/llrt_crypto/src/subtle/mod.rs +++ b/modules/llrt_crypto/src/subtle/mod.rs @@ -197,7 +197,6 @@ impl AesCtrVariant { } } -#[allow(dead_code)] pub enum AesGcmVariant { Aes128Gcm96(AesGcm), Aes192Gcm96(AesGcm), diff --git a/modules/llrt_http/src/client.rs b/modules/llrt_http/src/client.rs index 00462d4866..5e742a3278 100644 --- a/modules/llrt_http/src/client.rs +++ b/modules/llrt_http/src/client.rs @@ -11,17 +11,11 @@ use once_cell::sync::Lazy; use crate::get_pool_idle_timeout; -#[cfg(all( - any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), - not(feature = "tls-openssl") -))] +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] use crate::get_http_version; // Rustls-based TLS backends -#[cfg(all( - any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), - not(feature = "tls-openssl") -))] +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] mod rustls_client { use super::*; use hyper_rustls::HttpsConnector; @@ -124,10 +118,7 @@ mod openssl_client { } // Re-export based on feature -#[cfg(all( - any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), - not(feature = "tls-openssl") -))] +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] pub use rustls_client::*; #[cfg(feature = "tls-openssl")] diff --git a/modules/llrt_tls/src/lib.rs b/modules/llrt_tls/src/lib.rs index ff72d1ea17..8c88f029a4 100644 --- a/modules/llrt_tls/src/lib.rs +++ b/modules/llrt_tls/src/lib.rs @@ -1,22 +1,32 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -#[cfg(all( - any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), - not(feature = "tls-openssl") -))] +// Ensure only one TLS backend is selected +#[cfg(all(feature = "tls-ring", feature = "tls-aws-lc"))] +compile_error!("Features `tls-ring` and `tls-aws-lc` are mutually exclusive"); + +#[cfg(all(feature = "tls-ring", feature = "tls-graviola"))] +compile_error!("Features `tls-ring` and `tls-graviola` are mutually exclusive"); + +#[cfg(all(feature = "tls-ring", feature = "tls-openssl"))] +compile_error!("Features `tls-ring` and `tls-openssl` are mutually exclusive"); + +#[cfg(all(feature = "tls-aws-lc", feature = "tls-graviola"))] +compile_error!("Features `tls-aws-lc` and `tls-graviola` are mutually exclusive"); + +#[cfg(all(feature = "tls-aws-lc", feature = "tls-openssl"))] +compile_error!("Features `tls-aws-lc` and `tls-openssl` are mutually exclusive"); + +#[cfg(all(feature = "tls-graviola", feature = "tls-openssl"))] +compile_error!("Features `tls-graviola` and `tls-openssl` are mutually exclusive"); + +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] mod rustls_config; -#[cfg(all( - any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), - not(feature = "tls-openssl") -))] +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] pub use rustls_config::*; -#[cfg(all( - any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"), - not(feature = "tls-openssl") -))] +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] mod no_verification; #[cfg(feature = "tls-openssl")] diff --git a/modules/llrt_tls/src/rustls_config.rs b/modules/llrt_tls/src/rustls_config.rs index 5bb6826b81..ab3b2a874a 100644 --- a/modules/llrt_tls/src/rustls_config.rs +++ b/modules/llrt_tls/src/rustls_config.rs @@ -14,16 +14,12 @@ use webpki_roots::TLS_SERVER_ROOTS; use crate::no_verification::NoCertificateVerification; // Select the crypto provider based on feature flags -#[cfg(all( - feature = "tls-ring", - not(feature = "tls-aws-lc"), - not(feature = "tls-graviola") -))] +#[cfg(feature = "tls-ring")] fn get_crypto_provider() -> Arc { Arc::new(rustls::crypto::ring::default_provider()) } -#[cfg(all(feature = "tls-aws-lc", not(feature = "tls-graviola")))] +#[cfg(feature = "tls-aws-lc")] fn get_crypto_provider() -> Arc { Arc::new(rustls::crypto::aws_lc_rs::default_provider()) } From 779e83ab9425f0f717fb0c43520800e3e29c1c16 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 17 Dec 2025 09:01:57 +0100 Subject: [PATCH 10/77] Tweak features --- Makefile | 2 +- modules/llrt_fetch/Cargo.toml | 10 +++++----- modules/llrt_fetch/src/fetch.rs | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 2c7efacd8e..b334ea2c38 100644 --- a/Makefile +++ b/Makefile @@ -232,7 +232,7 @@ ifdef CARGO_FEATURES cargo $(TOOLCHAIN) run -r --target $(CURRENT_TARGET) $(CARGO_FEATURES) -- test -d bundle/js/__tests__/$(TEST_SUB_DIR) else cargo $(TOOLCHAIN) -Z build-std -Z build-std-features test --target $(CURRENT_TARGET) --features lambda -- --nocapture --show-output - cargo $(TOOLCHAIN) run -r --target $(CURRENT_TARGET) --features lambda -- test -d bundle/js/__tests__/$(TEST_SUB_DIR) + cargo $(TOOLCHAIN) run -r --target $(CURRENT_TARGET) -- test -d bundle/js/__tests__/$(TEST_SUB_DIR) endif libs-arm64: lib/arm64/libzstd.a lib/zstd.h lib/zstd_errors.h diff --git a/modules/llrt_fetch/Cargo.toml b/modules/llrt_fetch/Cargo.toml index f8ddad19f6..506e8b1dd4 100644 --- a/modules/llrt_fetch/Cargo.toml +++ b/modules/llrt_fetch/Cargo.toml @@ -12,7 +12,7 @@ name = "llrt_fetch" path = "src/lib.rs" [features] -default = ["http1", "http2", "compression-c", "webpki-roots"] +default = ["http1", "http2", "compression-c", "webpki-roots", "tls-ring"] http1 = ["hyper/http1", "llrt_http/http1"] http2 = ["hyper/http2", "llrt_http/http2"] @@ -23,9 +23,9 @@ compression-rust = ["llrt_compression/all-rust"] webpki-roots = ["llrt_http/webpki-roots"] native-roots = ["llrt_http/native-roots"] -tls-ring = [] -tls-aws-lc = [] -tls-graviola = [] +tls-ring = ["llrt_http/tls-ring"] +tls-aws-lc = ["llrt_http/tls-aws-lc"] +tls-graviola = ["llrt_http/tls-graviola"] [dependencies] bytes = { version = "1", default-features = false } @@ -64,5 +64,5 @@ tracing = { version = "0.1", default-features = false } [dev-dependencies] llrt_compression = { version = "0.7.0-beta", path = "../../libs/llrt_compression" } llrt_test = { path = "../../libs/llrt_test" } -llrt_test_tls = { path = "../../libs/llrt_test_tls", default-features = false } +llrt_test_tls = { path = "../../libs/llrt_test_tls", default-features = false, features = ["tls-ring"] } wiremock = { version = "0.6", default-features = false } diff --git a/modules/llrt_fetch/src/fetch.rs b/modules/llrt_fetch/src/fetch.rs index d95359d36c..4b89685c10 100644 --- a/modules/llrt_fetch/src/fetch.rs +++ b/modules/llrt_fetch/src/fetch.rs @@ -863,6 +863,7 @@ mod tests { .await; } + #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] #[tokio::test] async fn test_fetch_tls() { let mock_server = llrt_test_tls::MockServer::start().await.unwrap(); @@ -905,6 +906,7 @@ mod tests { .await; } + #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] #[tokio::test] async fn test_fetch_ignore_certificate_errors() { let mock_server = llrt_test_tls::MockServer::start().await.unwrap(); From e9200b43fb0b62e01f3fee1dfad101827af15fe0 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 17 Dec 2025 09:25:15 +0100 Subject: [PATCH 11/77] Do not use macos-14 --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2cb3680f06..f781618bd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,6 @@ jobs: - ubuntu-latest - ubuntu-24.04-arm - macos-latest - - macos-14 crypto: - name: default features: "" @@ -84,7 +83,7 @@ jobs: platform: darwin arch: x86_64 toolchain: nightly - - os: macos-14 + - os: macos-latest platform: darwin arch: aarch64 toolchain: nightly @@ -114,7 +113,7 @@ jobs: platform: darwin arch: x86_64 toolchain: stable - - os: macos-14 + - os: macos-latest platform: darwin arch: aarch64 toolchain: stable From bf4de09004709055ec8587111fc66bf7e0498de3 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 17 Dec 2025 09:46:50 +0100 Subject: [PATCH 12/77] Adjust features --- libs/llrt_test_tls/src/lib.rs | 9 +++++++++ libs/llrt_test_tls/src/server.rs | 8 ++------ llrt_modules/Cargo.toml | 10 +++++----- modules/llrt_fetch/Cargo.toml | 2 +- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/libs/llrt_test_tls/src/lib.rs b/libs/llrt_test_tls/src/lib.rs index f107d7d20e..99cb7ddecb 100644 --- a/libs/llrt_test_tls/src/lib.rs +++ b/libs/llrt_test_tls/src/lib.rs @@ -4,6 +4,15 @@ // FIXME this library is only needed until TLS is natively supported in wiremock. // See https://github.com/LukeMathWalker/wiremock-rs/issues/58 +#[cfg(all(feature = "tls-ring", feature = "tls-aws-lc"))] +compile_error!("Features 'tls-ring' and 'tls-aws-lc' are mutually exclusive"); + +#[cfg(all(feature = "tls-ring", feature = "tls-graviola"))] +compile_error!("Features 'tls-ring' and 'tls-graviola' are mutually exclusive"); + +#[cfg(all(feature = "tls-aws-lc", feature = "tls-graviola"))] +compile_error!("Features 'tls-aws-lc' and 'tls-graviola' are mutually exclusive"); + use std::net::{Ipv4Addr, SocketAddr}; use tokio::net::TcpListener; diff --git a/libs/llrt_test_tls/src/server.rs b/libs/llrt_test_tls/src/server.rs index 4f5fa6d030..84810d8dfd 100644 --- a/libs/llrt_test_tls/src/server.rs +++ b/libs/llrt_test_tls/src/server.rs @@ -9,16 +9,12 @@ use tokio_rustls::TlsAcceptor; use crate::MockServerCerts; -#[cfg(all( - feature = "tls-ring", - not(feature = "tls-aws-lc"), - not(feature = "tls-graviola") -))] +#[cfg(feature = "tls-ring")] fn get_crypto_provider() -> Arc { Arc::new(rustls::crypto::ring::default_provider()) } -#[cfg(all(feature = "tls-aws-lc", not(feature = "tls-graviola")))] +#[cfg(feature = "tls-aws-lc")] fn get_crypto_provider() -> Arc { Arc::new(rustls::crypto::aws_lc_rs::default_provider()) } diff --git a/llrt_modules/Cargo.toml b/llrt_modules/Cargo.toml index 7b3fdb9ca6..a7cf9ec601 100644 --- a/llrt_modules/Cargo.toml +++ b/llrt_modules/Cargo.toml @@ -12,9 +12,9 @@ default = ["base", "console", "tls-ring", "crypto-rust"] lambda = ["base"] # TLS crypto backend features -tls-ring = ["llrt_http?/tls-ring", "llrt_tls?/tls-ring"] -tls-aws-lc = ["llrt_http?/tls-aws-lc", "llrt_tls?/tls-aws-lc"] -tls-graviola = ["llrt_http?/tls-graviola", "llrt_tls?/tls-graviola"] +tls-ring = ["llrt_http?/tls-ring", "llrt_tls?/tls-ring", "llrt_fetch?/tls-ring"] +tls-aws-lc = ["llrt_http?/tls-aws-lc", "llrt_tls?/tls-aws-lc", "llrt_fetch?/tls-aws-lc"] +tls-graviola = ["llrt_http?/tls-graviola", "llrt_tls?/tls-graviola", "llrt_fetch?/tls-graviola"] tls-openssl = ["llrt_http?/tls-openssl", "llrt_tls?/tls-openssl"] # Crypto provider features @@ -69,7 +69,7 @@ dgram = ["llrt_dgram"] dns = ["llrt_dns"] events = ["llrt_events"] exceptions = ["llrt_exceptions"] -fetch = ["llrt_fetch"] +fetch = ["llrt_fetch", "llrt_fetch?/http1", "llrt_fetch?/http2", "llrt_fetch?/compression-c", "llrt_fetch?/webpki-roots"] fs = ["llrt_fs"] https = [ "llrt_http", @@ -116,7 +116,7 @@ llrt_dgram = { version = "0.7.0-beta", path = "../modules/llrt_dgram", optional llrt_dns = { version = "0.7.0-beta", path = "../modules/llrt_dns", optional = true } llrt_events = { version = "0.7.0-beta", path = "../modules/llrt_events", optional = true } llrt_exceptions = { version = "0.7.0-beta", path = "../modules/llrt_exceptions", optional = true } -llrt_fetch = { version = "0.7.0-beta", path = "../modules/llrt_fetch", optional = true } +llrt_fetch = { version = "0.7.0-beta", path = "../modules/llrt_fetch", default-features = false, optional = true } llrt_fs = { version = "0.7.0-beta", path = "../modules/llrt_fs", optional = true } llrt_http = { version = "0.7.0-beta", path = "../modules/llrt_http", default-features = false, optional = true } llrt_navigator = { version = "0.7.0-beta", path = "../modules/llrt_navigator", optional = true } diff --git a/modules/llrt_fetch/Cargo.toml b/modules/llrt_fetch/Cargo.toml index 506e8b1dd4..9a10ecd273 100644 --- a/modules/llrt_fetch/Cargo.toml +++ b/modules/llrt_fetch/Cargo.toml @@ -12,7 +12,7 @@ name = "llrt_fetch" path = "src/lib.rs" [features] -default = ["http1", "http2", "compression-c", "webpki-roots", "tls-ring"] +default = ["http1", "http2", "compression-c", "webpki-roots"] http1 = ["hyper/http1", "llrt_http/http1"] http2 = ["hyper/http2", "llrt_http/http2"] From 15810ad7dcb2d6121c2260f4ba016ae27585dff1 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 17 Dec 2025 10:21:22 +0100 Subject: [PATCH 13/77] Fix flags --- modules/llrt_fetch/Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/llrt_fetch/Cargo.toml b/modules/llrt_fetch/Cargo.toml index 9a10ecd273..d0ac105dbd 100644 --- a/modules/llrt_fetch/Cargo.toml +++ b/modules/llrt_fetch/Cargo.toml @@ -23,9 +23,9 @@ compression-rust = ["llrt_compression/all-rust"] webpki-roots = ["llrt_http/webpki-roots"] native-roots = ["llrt_http/native-roots"] -tls-ring = ["llrt_http/tls-ring"] -tls-aws-lc = ["llrt_http/tls-aws-lc"] -tls-graviola = ["llrt_http/tls-graviola"] +tls-ring = ["llrt_http/tls-ring", "dep:llrt_test_tls", "llrt_test_tls?/tls-ring"] +tls-aws-lc = ["llrt_http/tls-aws-lc", "dep:llrt_test_tls", "llrt_test_tls?/tls-aws-lc"] +tls-graviola = ["llrt_http/tls-graviola", "dep:llrt_test_tls", "llrt_test_tls?/tls-graviola"] [dependencies] bytes = { version = "1", default-features = false } @@ -40,6 +40,7 @@ llrt_context = { version = "0.7.0-beta", path = "../../libs/llrt_context" } llrt_encoding = { version = "0.7.0-beta", path = "../../libs/llrt_encoding" } llrt_http = { version = "0.7.0-beta", default-features = false, path = "../llrt_http" } llrt_json = { version = "0.7.0-beta", path = "../../libs/llrt_json" } +llrt_test_tls = { path = "../../libs/llrt_test_tls", default-features = false, optional = true } llrt_url = { version = "0.7.0-beta", path = "../llrt_url" } llrt_utils = { version = "0.7.0-beta", path = "../../libs/llrt_utils", default-features = false } once_cell = { version = "1", features = ["std"], default-features = false } @@ -64,5 +65,4 @@ tracing = { version = "0.1", default-features = false } [dev-dependencies] llrt_compression = { version = "0.7.0-beta", path = "../../libs/llrt_compression" } llrt_test = { path = "../../libs/llrt_test" } -llrt_test_tls = { path = "../../libs/llrt_test_tls", default-features = false, features = ["tls-ring"] } wiremock = { version = "0.6", default-features = false } From 1a39c39e3ce387b3eb57c5e63c82a984a60e55c2 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 17 Dec 2025 16:08:40 +0100 Subject: [PATCH 14/77] Cleanup CI --- .github/workflows/build-modules.yml | 22 ++++++++++++++++++---- .github/workflows/ci.yml | 17 +++++++++-------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index b18a95c191..6a3674a3ca 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -14,10 +14,14 @@ on: toolchain: required: true type: string + tls_feature: + required: false + type: string + default: "tls-ring" jobs: build: - name: ${{ inputs.arch }}-${{ inputs.platform }} + name: ${{ inputs.arch }}-${{ inputs.platform }}-${{ inputs.tls_feature }} runs-on: ${{ inputs.os }} steps: - name: Checkout @@ -30,15 +34,25 @@ jobs: shell: bash env: RUSTFLAGS: "" + TLS_FEATURE: ${{ inputs.tls_feature }} run: | crates=$(cargo metadata --no-deps --format-version 1 --quiet | jq -r '.packages[] | select(.manifest_path | contains("modules/")) | .name') for crate in $crates; do echo "Compiling crate: $crate" - cargo build -p "$crate" + # Use TLS feature for crates that need TLS + if [ "$crate" = "llrt_fetch" ] || [ "$crate" = "llrt_http" ]; then + cargo build -p "$crate" --features "$TLS_FEATURE" + else + cargo build -p "$crate" + fi done - name: Run build all + env: + TLS_FEATURE: ${{ inputs.tls_feature }} run: | - cargo build -p llrt_modules + cargo build -p llrt_modules --features "$TLS_FEATURE" - name: Run tests all + env: + TLS_FEATURE: ${{ inputs.tls_feature }} run: | - cargo test -p llrt_modules + cargo test -p llrt_modules --features "$TLS_FEATURE" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f781618bd6..07114fb091 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,19 +100,19 @@ jobs: - check strategy: matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + tls: + - tls-ring + - tls-aws-lc + - tls-graviola include: - os: ubuntu-latest platform: linux arch: x86_64 toolchain: stable - - os: ubuntu-24.04-arm - platform: linux - arch: aarch64 - toolchain: stable - - os: macos-latest - platform: darwin - arch: x86_64 - toolchain: stable - os: macos-latest platform: darwin arch: aarch64 @@ -127,3 +127,4 @@ jobs: platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} + tls_feature: ${{ matrix.tls }} From 08da665c9669d0c789a3c462c2152172d6b20d89 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 18 Dec 2025 15:18:58 +0100 Subject: [PATCH 15/77] Cleanups --- .github/workflows/ci.yml | 17 + modules/llrt_crypto/Cargo.toml | 85 +- modules/llrt_crypto/src/provider/graviola.rs | 160 ++- modules/llrt_crypto/src/provider/mod.rs | 330 +++++- modules/llrt_crypto/src/provider/openssl.rs | 997 +++++++++++++++++- modules/llrt_crypto/src/provider/ring.rs | 148 +++ modules/llrt_crypto/src/provider/rust.rs | 732 +++++++++++-- modules/llrt_crypto/src/sha_hash.rs | 30 +- .../llrt_crypto/src/subtle/generate_key.rs | 4 +- .../llrt_crypto/src/subtle/key_algorithm.rs | 391 +++---- modules/llrt_crypto/src/subtle/mod.rs | 558 ++++++---- 11 files changed, 2792 insertions(+), 660 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07114fb091..6d8390c0d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,8 +49,12 @@ jobs: features: "--no-default-features --features crypto-rust,tls-ring,macro" - name: crypto-rust+tls-aws-lc features: "--no-default-features --features crypto-rust,tls-aws-lc,macro" + - name: crypto-ring+tls-ring + features: "--no-default-features --features crypto-ring,tls-ring,macro" - name: crypto-ring-rust+tls-ring features: "--no-default-features --features crypto-ring-rust,tls-ring,macro" + - name: crypto-graviola+tls-graviola + features: "--no-default-features --features crypto-graviola,tls-graviola,macro" - name: crypto-graviola-rust+tls-graviola features: "--no-default-features --features crypto-graviola-rust,tls-graviola,macro" - name: crypto-openssl+tls-openssl @@ -66,6 +70,19 @@ jobs: - os: windows-latest crypto: name: crypto-openssl+tls-openssl + # Graviola only supports aarch64 + - os: ubuntu-latest + crypto: + name: crypto-graviola+tls-graviola + - os: ubuntu-latest + crypto: + name: crypto-graviola-rust+tls-graviola + - os: windows-latest + crypto: + name: crypto-graviola+tls-graviola + - os: windows-latest + crypto: + name: crypto-graviola-rust+tls-graviola include: - os: windows-latest platform: windows diff --git a/modules/llrt_crypto/Cargo.toml b/modules/llrt_crypto/Cargo.toml index 1538e04972..d82340cce0 100644 --- a/modules/llrt_crypto/Cargo.toml +++ b/modules/llrt_crypto/Cargo.toml @@ -13,79 +13,19 @@ path = "src/lib.rs" [features] default = ["crypto-rust"] -crypto-rust = [ - "llrt_json", - "aes", - "aes-gcm", - "aes-kw", - "cbc", - "ctr", - "rsa", - "p256", - "p384", - "p521", - "elliptic-curve", - "x25519-dalek", - "ecdsa", - "spki", - "der", - "const-oid", - "pkcs8", - "hmac", - "sha1", -] -crypto-ring = [] -crypto-ring-rust = [ - "llrt_json", - "aes", - "aes-gcm", - "aes-kw", - "cbc", - "ctr", - "rsa", - "p256", - "p384", - "p521", - "elliptic-curve", - "x25519-dalek", - "ecdsa", - "spki", - "der", - "const-oid", - "pkcs8", - "hmac", - "sha1", -] -crypto-graviola = [ - "graviola", -] -crypto-graviola-rust = [ - "graviola", + +# Internal feature: enables DER parsing for SubtleCrypto key import/export +_subtle-full = [ "llrt_json", - "aes", - "aes-gcm", - "aes-kw", - "cbc", - "ctr", - "rsa", - "p256", - "p384", - "p521", - "elliptic-curve", - "x25519-dalek", - "ecdsa", "spki", "der", "const-oid", "pkcs8", - "hmac", - "sha1", ] -# Crypto provider features -crypto-openssl = [ - "openssl-sys", - "openssl", - "llrt_json", + +# Internal feature: enables RustCrypto dependencies for key import/export +_rustcrypto = [ + "_subtle-full", "aes", "aes-gcm", "aes-kw", @@ -98,13 +38,16 @@ crypto-openssl = [ "elliptic-curve", "x25519-dalek", "ecdsa", - "spki", - "der", - "const-oid", - "pkcs8", "hmac", "sha1", ] + +crypto-rust = ["_rustcrypto"] +crypto-ring = [] +crypto-ring-rust = ["_rustcrypto"] +crypto-graviola = ["graviola"] +crypto-graviola-rust = ["graviola", "_rustcrypto"] +crypto-openssl = ["openssl-sys", "openssl", "_subtle-full"] openssl-vendored = ["openssl-sys?/vendored", "openssl?/vendored"] [dependencies] diff --git a/modules/llrt_crypto/src/provider/graviola.rs b/modules/llrt_crypto/src/provider/graviola.rs index 47a4f96306..f0e41034ad 100644 --- a/modules/llrt_crypto/src/provider/graviola.rs +++ b/modules/llrt_crypto/src/provider/graviola.rs @@ -207,9 +207,9 @@ impl CryptoProvider for GraviolaProvider { ) -> Result, CryptoError> { match mode { AesMode::Gcm { .. } => { - let nonce: [u8; 12] = iv.try_into().map_err(|_| CryptoError::InvalidData)?; + let nonce: [u8; 12] = iv.try_into().map_err(|_| CryptoError::InvalidData(None))?; if !matches!(key.len(), 16 | 32) { - return Err(CryptoError::InvalidKey); + return Err(CryptoError::InvalidKey(None)); } let aead = AesGcm::new(key); let aad = additional_data.unwrap_or(&[]); @@ -233,12 +233,12 @@ impl CryptoProvider for GraviolaProvider { ) -> Result, CryptoError> { match mode { AesMode::Gcm { .. } => { - let nonce: [u8; 12] = iv.try_into().map_err(|_| CryptoError::InvalidData)?; + let nonce: [u8; 12] = iv.try_into().map_err(|_| CryptoError::InvalidData(None))?; if !matches!(key.len(), 16 | 32) { - return Err(CryptoError::InvalidKey); + return Err(CryptoError::InvalidKey(None)); } if data.len() < 16 { - return Err(CryptoError::InvalidData); + return Err(CryptoError::InvalidData(None)); } let aead = AesGcm::new(key); let aad = additional_data.unwrap_or(&[]); @@ -246,7 +246,7 @@ impl CryptoProvider for GraviolaProvider { let tag: [u8; 16] = tag.try_into().unwrap(); let mut plaintext = ciphertext.to_vec(); aead.decrypt(&nonce, aad, &mut plaintext, &tag) - .map_err(|_| CryptoError::DecryptionFailed)?; + .map_err(|_| CryptoError::DecryptionFailed(None))?; Ok(plaintext) }, _ => Err(CryptoError::UnsupportedAlgorithm), @@ -327,6 +327,154 @@ impl CryptoProvider for GraviolaProvider { ) -> Result<(Vec, Vec), CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } + + fn import_rsa_public_key_pkcs1( + &self, + _der: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_rsa_private_key_pkcs1( + &self, + _der: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_rsa_public_key_spki( + &self, + _der: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_rsa_private_key_pkcs8( + &self, + _der: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_rsa_public_key_pkcs1(&self, _key_data: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_rsa_public_key_spki(&self, _key_data: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_rsa_private_key_pkcs8(&self, _key_data: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_ec_public_key_sec1( + &self, + _data: &[u8], + _curve: EllipticCurve, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_ec_public_key_spki(&self, _der: &[u8]) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_ec_private_key_pkcs8( + &self, + _der: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_ec_private_key_sec1( + &self, + _data: &[u8], + _curve: EllipticCurve, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_ec_public_key_sec1( + &self, + _key_data: &[u8], + _curve: EllipticCurve, + _is_private: bool, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_ec_public_key_spki( + &self, + _key_data: &[u8], + _curve: EllipticCurve, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_ec_private_key_pkcs8( + &self, + _key_data: &[u8], + _curve: EllipticCurve, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_okp_public_key_raw( + &self, + _data: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_okp_public_key_spki( + &self, + _der: &[u8], + _expected_oid: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_okp_private_key_pkcs8( + &self, + _der: &[u8], + _expected_oid: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_okp_public_key_raw( + &self, + _key_data: &[u8], + _is_private: bool, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_okp_public_key_spki( + &self, + _key_data: &[u8], + _oid: &[u8], + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_okp_private_key_pkcs8( + &self, + _key_data: &[u8], + _oid: &[u8], + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_rsa_jwk( + &self, + _jwk: super::RsaJwkImport<'_>, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_rsa_jwk( + &self, + _key_data: &[u8], + _is_private: bool, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_ec_jwk( + &self, + _jwk: super::EcJwkImport<'_>, + _curve: EllipticCurve, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_ec_jwk( + &self, + _key_data: &[u8], + _curve: EllipticCurve, + _is_private: bool, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } } // Hybrid types for graviola-rust: Graviola for SHA256/384/512, RustCrypto for MD5/SHA1 diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index 089655b919..ed8a209c8a 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -32,17 +32,74 @@ mod openssl; #[cfg(any(feature = "crypto-ring", feature = "crypto-ring-rust"))] mod ring; -#[cfg(any( - feature = "crypto-rust", - feature = "crypto-ring-rust", - feature = "crypto-graviola-rust", - feature = "crypto-openssl" -))] +#[cfg(feature = "_rustcrypto")] mod rust; use crate::sha_hash::ShaAlgorithm; use crate::subtle::EllipticCurve; +#[derive(Debug)] +pub struct RsaImportResult { + pub key_data: Vec, + pub modulus_length: u32, + pub public_exponent: Vec, + pub is_private: bool, +} + +#[derive(Debug)] +pub struct EcImportResult { + pub key_data: Vec, + pub is_private: bool, +} + +#[derive(Debug)] +pub struct OkpImportResult { + pub key_data: Vec, + pub is_private: bool, +} + +/// RSA JWK components for import (all values are raw bytes, not base64) +#[derive(Debug)] +pub struct RsaJwkImport<'a> { + pub n: &'a [u8], // modulus + pub e: &'a [u8], // public exponent + pub d: Option<&'a [u8]>, // private exponent + pub p: Option<&'a [u8]>, // first prime + pub q: Option<&'a [u8]>, // second prime + pub dp: Option<&'a [u8]>, // first factor CRT exponent + pub dq: Option<&'a [u8]>, // second factor CRT exponent + pub qi: Option<&'a [u8]>, // first CRT coefficient +} + +/// RSA JWK components for export +#[derive(Debug)] +pub struct RsaJwkExport { + pub n: Vec, + pub e: Vec, + pub d: Option>, + pub p: Option>, + pub q: Option>, + pub dp: Option>, + pub dq: Option>, + pub qi: Option>, +} + +/// EC JWK components for import (all values are raw bytes) +#[derive(Debug)] +pub struct EcJwkImport<'a> { + pub x: &'a [u8], + pub y: &'a [u8], + pub d: Option<&'a [u8]>, +} + +/// EC JWK components for export +#[derive(Debug)] +pub struct EcJwkExport { + pub x: Vec, + pub y: Vec, + pub d: Option>, +} + pub trait SimpleDigest { fn update(&mut self, data: &[u8]); fn finalize(self) -> Vec; @@ -202,6 +259,98 @@ pub trait CryptoProvider { modulus_length: u32, public_exponent: &[u8], ) -> Result<(Vec, Vec), CryptoError>; + + // RSA key import from DER formats + fn import_rsa_public_key_pkcs1(&self, der: &[u8]) -> Result; + fn import_rsa_private_key_pkcs1(&self, der: &[u8]) -> Result; + fn import_rsa_public_key_spki(&self, der: &[u8]) -> Result; + fn import_rsa_private_key_pkcs8(&self, der: &[u8]) -> Result; + + // RSA key export to DER formats + fn export_rsa_public_key_pkcs1(&self, key_data: &[u8]) -> Result, CryptoError>; + fn export_rsa_public_key_spki(&self, key_data: &[u8]) -> Result, CryptoError>; + fn export_rsa_private_key_pkcs8(&self, key_data: &[u8]) -> Result, CryptoError>; + + // EC key import from DER formats + fn import_ec_public_key_sec1( + &self, + data: &[u8], + curve: EllipticCurve, + ) -> Result; + fn import_ec_public_key_spki(&self, der: &[u8]) -> Result; + fn import_ec_private_key_pkcs8(&self, der: &[u8]) -> Result; + fn import_ec_private_key_sec1( + &self, + data: &[u8], + curve: EllipticCurve, + ) -> Result; + + // EC key export + fn export_ec_public_key_sec1( + &self, + key_data: &[u8], + curve: EllipticCurve, + is_private: bool, + ) -> Result, CryptoError>; + fn export_ec_public_key_spki( + &self, + key_data: &[u8], + curve: EllipticCurve, + ) -> Result, CryptoError>; + fn export_ec_private_key_pkcs8( + &self, + key_data: &[u8], + curve: EllipticCurve, + ) -> Result, CryptoError>; + + // OKP (Ed25519/X25519) key import + fn import_okp_public_key_raw(&self, data: &[u8]) -> Result; + fn import_okp_public_key_spki( + &self, + der: &[u8], + expected_oid: &[u8], + ) -> Result; + fn import_okp_private_key_pkcs8( + &self, + der: &[u8], + expected_oid: &[u8], + ) -> Result; + + // OKP key export + fn export_okp_public_key_raw( + &self, + key_data: &[u8], + is_private: bool, + ) -> Result, CryptoError>; + fn export_okp_public_key_spki( + &self, + key_data: &[u8], + oid: &[u8], + ) -> Result, CryptoError>; + fn export_okp_private_key_pkcs8( + &self, + key_data: &[u8], + oid: &[u8], + ) -> Result, CryptoError>; + + // JWK import/export + fn import_rsa_jwk(&self, jwk: RsaJwkImport<'_>) -> Result; + fn export_rsa_jwk( + &self, + key_data: &[u8], + is_private: bool, + ) -> Result; + fn import_ec_jwk( + &self, + jwk: EcJwkImport<'_>, + curve: EllipticCurve, + ) -> Result; + fn export_ec_jwk( + &self, + key_data: &[u8], + curve: EllipticCurve, + is_private: bool, + ) -> Result; } pub trait HmacProvider { @@ -210,35 +359,43 @@ pub trait HmacProvider { } #[derive(Debug)] +#[allow(dead_code)] pub enum CryptoError { - InvalidKey, - InvalidData, - InvalidSignature, + InvalidKey(Option>), + InvalidData(Option>), + InvalidSignature(Option>), InvalidLength, - SigningFailed, - #[allow(dead_code)] + SigningFailed(Option>), VerificationFailed, - OperationFailed, + OperationFailed(Option>), UnsupportedAlgorithm, - DerivationFailed, - EncryptionFailed, - DecryptionFailed, + DerivationFailed(Option>), + EncryptionFailed(Option>), + DecryptionFailed(Option>), } impl std::fmt::Display for CryptoError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - CryptoError::InvalidKey => write!(f, "Invalid key"), - CryptoError::InvalidData => write!(f, "Invalid data"), - CryptoError::InvalidSignature => write!(f, "Invalid signature"), + CryptoError::InvalidKey(None) => write!(f, "Invalid key"), + CryptoError::InvalidKey(Some(msg)) => write!(f, "Invalid key: {}", msg), + CryptoError::InvalidData(None) => write!(f, "Invalid data"), + CryptoError::InvalidData(Some(msg)) => write!(f, "Invalid data: {}", msg), + CryptoError::InvalidSignature(None) => write!(f, "Invalid signature"), + CryptoError::InvalidSignature(Some(msg)) => write!(f, "Invalid signature: {}", msg), CryptoError::InvalidLength => write!(f, "Invalid length"), - CryptoError::SigningFailed => write!(f, "Signing failed"), + CryptoError::SigningFailed(None) => write!(f, "Signing failed"), + CryptoError::SigningFailed(Some(msg)) => write!(f, "Signing failed: {}", msg), CryptoError::VerificationFailed => write!(f, "Verification failed"), - CryptoError::OperationFailed => write!(f, "Operation failed"), + CryptoError::OperationFailed(None) => write!(f, "Operation failed"), + CryptoError::OperationFailed(Some(msg)) => write!(f, "Operation failed: {}", msg), CryptoError::UnsupportedAlgorithm => write!(f, "Unsupported algorithm"), - CryptoError::DerivationFailed => write!(f, "Derivation failed"), - CryptoError::EncryptionFailed => write!(f, "Encryption failed"), - CryptoError::DecryptionFailed => write!(f, "Decryption failed"), + CryptoError::DerivationFailed(None) => write!(f, "Derivation failed"), + CryptoError::DerivationFailed(Some(msg)) => write!(f, "Derivation failed: {}", msg), + CryptoError::EncryptionFailed(None) => write!(f, "Encryption failed"), + CryptoError::EncryptionFailed(Some(msg)) => write!(f, "Encryption failed: {}", msg), + CryptoError::DecryptionFailed(None) => write!(f, "Decryption failed"), + CryptoError::DecryptionFailed(Some(msg)) => write!(f, "Decryption failed: {}", msg), } } } @@ -433,6 +590,133 @@ macro_rules! impl_hybrid_provider { ) -> Result<(Vec, Vec), CryptoError> { rust::RustCryptoProvider.generate_rsa_key(b, e) } + fn import_rsa_public_key_pkcs1( + &self, + d: &[u8], + ) -> Result { + rust::RustCryptoProvider.import_rsa_public_key_pkcs1(d) + } + fn import_rsa_private_key_pkcs1( + &self, + d: &[u8], + ) -> Result { + rust::RustCryptoProvider.import_rsa_private_key_pkcs1(d) + } + fn import_rsa_public_key_spki(&self, d: &[u8]) -> Result { + rust::RustCryptoProvider.import_rsa_public_key_spki(d) + } + fn import_rsa_private_key_pkcs8( + &self, + d: &[u8], + ) -> Result { + rust::RustCryptoProvider.import_rsa_private_key_pkcs8(d) + } + fn export_rsa_public_key_pkcs1(&self, d: &[u8]) -> Result, CryptoError> { + rust::RustCryptoProvider.export_rsa_public_key_pkcs1(d) + } + fn export_rsa_public_key_spki(&self, d: &[u8]) -> Result, CryptoError> { + rust::RustCryptoProvider.export_rsa_public_key_spki(d) + } + fn export_rsa_private_key_pkcs8(&self, d: &[u8]) -> Result, CryptoError> { + rust::RustCryptoProvider.export_rsa_private_key_pkcs8(d) + } + fn import_ec_public_key_sec1( + &self, + d: &[u8], + c: EllipticCurve, + ) -> Result { + rust::RustCryptoProvider.import_ec_public_key_sec1(d, c) + } + fn import_ec_public_key_spki(&self, d: &[u8]) -> Result { + rust::RustCryptoProvider.import_ec_public_key_spki(d) + } + fn import_ec_private_key_pkcs8(&self, d: &[u8]) -> Result { + rust::RustCryptoProvider.import_ec_private_key_pkcs8(d) + } + fn import_ec_private_key_sec1( + &self, + d: &[u8], + c: EllipticCurve, + ) -> Result { + rust::RustCryptoProvider.import_ec_private_key_sec1(d, c) + } + fn export_ec_public_key_sec1( + &self, + d: &[u8], + c: EllipticCurve, + p: bool, + ) -> Result, CryptoError> { + rust::RustCryptoProvider.export_ec_public_key_sec1(d, c, p) + } + fn export_ec_public_key_spki( + &self, + d: &[u8], + c: EllipticCurve, + ) -> Result, CryptoError> { + rust::RustCryptoProvider.export_ec_public_key_spki(d, c) + } + fn export_ec_private_key_pkcs8( + &self, + d: &[u8], + c: EllipticCurve, + ) -> Result, CryptoError> { + rust::RustCryptoProvider.export_ec_private_key_pkcs8(d, c) + } + fn import_okp_public_key_raw(&self, d: &[u8]) -> Result { + rust::RustCryptoProvider.import_okp_public_key_raw(d) + } + fn import_okp_public_key_spki( + &self, + d: &[u8], + o: &[u8], + ) -> Result { + rust::RustCryptoProvider.import_okp_public_key_spki(d, o) + } + fn import_okp_private_key_pkcs8( + &self, + d: &[u8], + o: &[u8], + ) -> Result { + rust::RustCryptoProvider.import_okp_private_key_pkcs8(d, o) + } + fn export_okp_public_key_raw(&self, d: &[u8], p: bool) -> Result, CryptoError> { + rust::RustCryptoProvider.export_okp_public_key_raw(d, p) + } + fn export_okp_public_key_spki( + &self, + d: &[u8], + o: &[u8], + ) -> Result, CryptoError> { + rust::RustCryptoProvider.export_okp_public_key_spki(d, o) + } + fn export_okp_private_key_pkcs8( + &self, + d: &[u8], + o: &[u8], + ) -> Result, CryptoError> { + rust::RustCryptoProvider.export_okp_private_key_pkcs8(d, o) + } + fn import_rsa_jwk(&self, j: RsaJwkImport<'_>) -> Result { + rust::RustCryptoProvider.import_rsa_jwk(j) + } + fn export_rsa_jwk(&self, d: &[u8], p: bool) -> Result { + rust::RustCryptoProvider.export_rsa_jwk(d, p) + } + fn import_ec_jwk( + &self, + j: EcJwkImport<'_>, + c: EllipticCurve, + ) -> Result { + rust::RustCryptoProvider.import_ec_jwk(j, c) + } + fn export_ec_jwk( + &self, + d: &[u8], + c: EllipticCurve, + p: bool, + ) -> Result { + rust::RustCryptoProvider.export_ec_jwk(d, c, p) + } } }; } diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs index 3daf6472e4..1edb591ea2 100644 --- a/modules/llrt_crypto/src/provider/openssl.rs +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -3,9 +3,19 @@ //! OpenSSL crypto provider - uses OpenSSL for cryptographic operations. +use openssl::bn::BigNum; +use openssl::derive::Deriver; +use openssl::ec::{EcGroup, EcKey}; +use openssl::ecdsa::EcdsaSig; use openssl::hash::{Hasher, MessageDigest}; -use openssl::pkey::PKey; -use openssl::sign::Signer; +use openssl::md::Md; +use openssl::nid::Nid; +use openssl::pkey::{Id, PKey}; +use openssl::pkey_ctx::PkeyCtx; +use openssl::rand::rand_bytes; +use openssl::rsa::{Padding, Rsa}; +use openssl::sign::{Signer, Verifier}; +use openssl::symm::{self, Cipher}; use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; use crate::sha_hash::ShaAlgorithm; @@ -71,6 +81,30 @@ fn get_message_digest(alg: ShaAlgorithm) -> MessageDigest { } } +fn get_md(alg: ShaAlgorithm) -> &'static openssl::md::MdRef { + match alg { + ShaAlgorithm::MD5 => Md::md5(), + ShaAlgorithm::SHA1 => Md::sha1(), + ShaAlgorithm::SHA256 => Md::sha256(), + ShaAlgorithm::SHA384 => Md::sha384(), + ShaAlgorithm::SHA512 => Md::sha512(), + } +} + +fn curve_to_nid(curve: EllipticCurve) -> Nid { + match curve { + EllipticCurve::P256 => Nid::X9_62_PRIME256V1, + EllipticCurve::P384 => Nid::SECP384R1, + EllipticCurve::P521 => Nid::SECP521R1, + } +} + +fn get_ec_group(curve: EllipticCurve) -> Result { + let nid = curve_to_nid(curve); + EcGroup::from_curve_name(nid) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into()))) +} + impl CryptoProvider for OpenSslProvider { type Digest = OpenSslDigest; type Hmac = OpenSslHmac; @@ -90,7 +124,6 @@ impl CryptoProvider for OpenSslProvider { fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { let md = get_message_digest(algorithm); let pkey = PKey::hmac(key).expect("Failed to create HMAC key"); - // SAFETY: We're creating a static lifetime signer, but we own the key let signer = unsafe { std::mem::transmute::, Signer<'static>>( Signer::new(md, &pkey).expect("Failed to create signer"), @@ -99,14 +132,24 @@ impl CryptoProvider for OpenSslProvider { OpenSslHmac { signer } } - // Delegate to RustCrypto for complex operations fn ecdsa_sign( &self, curve: EllipticCurve, private_key_der: &[u8], digest: &[u8], ) -> Result, CryptoError> { - super::rust::RustCryptoProvider.ecdsa_sign(curve, private_key_der, digest) + let group = get_ec_group(curve)?; + let ec_key = EcKey::private_key_from_der(private_key_der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let sig = EcdsaSig::sign(digest, &ec_key) + .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; + let r = sig.r().to_vec(); + let s = sig.s().to_vec(); + let coord_len = (group.degree() as usize + 7) / 8; + let mut result = vec![0u8; coord_len * 2]; + result[coord_len - r.len()..coord_len].copy_from_slice(&r); + result[coord_len * 2 - s.len()..].copy_from_slice(&s); + Ok(result) } fn ecdsa_verify( @@ -116,11 +159,35 @@ impl CryptoProvider for OpenSslProvider { signature: &[u8], digest: &[u8], ) -> Result { - super::rust::RustCryptoProvider.ecdsa_verify(curve, public_key_sec1, signature, digest) + let group = get_ec_group(curve)?; + let ec_key = EcKey::public_key_from_der(public_key_sec1).or_else(|_| { + let point = openssl::ec::EcPoint::from_bytes( + &group, + public_key_sec1, + &mut openssl::bn::BigNumContext::new().unwrap(), + ) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + EcKey::from_public_key(&group, &point) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) + })?; + let coord_len = signature.len() / 2; + let r = BigNum::from_slice(&signature[..coord_len]) + .map_err(|e| CryptoError::InvalidSignature(Some(e.to_string().into())))?; + let s = BigNum::from_slice(&signature[coord_len..]) + .map_err(|e| CryptoError::InvalidSignature(Some(e.to_string().into())))?; + let sig = EcdsaSig::from_private_components(r, s) + .map_err(|e| CryptoError::InvalidSignature(Some(e.to_string().into())))?; + Ok(sig.verify(digest, &ec_key).unwrap_or(false)) } fn ed25519_sign(&self, private_key_der: &[u8], data: &[u8]) -> Result, CryptoError> { - super::rust::RustCryptoProvider.ed25519_sign(private_key_der, data) + let pkey = PKey::private_key_from_der(private_key_der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let mut signer = Signer::new_without_digest(&pkey) + .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; + signer + .sign_oneshot_to_vec(data) + .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into()))) } fn ed25519_verify( @@ -129,7 +196,11 @@ impl CryptoProvider for OpenSslProvider { signature: &[u8], data: &[u8], ) -> Result { - super::rust::RustCryptoProvider.ed25519_verify(public_key_bytes, signature, data) + let pkey = PKey::public_key_from_raw_bytes(public_key_bytes, Id::ED25519) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let mut verifier = Verifier::new_without_digest(&pkey) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + Ok(verifier.verify_oneshot(signature, data).unwrap_or(false)) } fn rsa_pss_sign( @@ -139,7 +210,28 @@ impl CryptoProvider for OpenSslProvider { salt_length: usize, hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { - super::rust::RustCryptoProvider.rsa_pss_sign(private_key_der, digest, salt_length, hash_alg) + let rsa = Rsa::private_key_from_der(private_key_der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pkey = + PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let md = get_message_digest(hash_alg); + let mut signer = Signer::new(md, &pkey) + .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; + signer + .set_rsa_padding(Padding::PKCS1_PSS) + .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; + signer + .set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::custom(salt_length as i32)) + .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; + signer + .set_rsa_mgf1_md(md) + .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; + signer + .update(digest) + .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; + signer + .sign_to_vec() + .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into()))) } fn rsa_pss_verify( @@ -150,13 +242,26 @@ impl CryptoProvider for OpenSslProvider { salt_length: usize, hash_alg: ShaAlgorithm, ) -> Result { - super::rust::RustCryptoProvider.rsa_pss_verify( - public_key_der, - signature, - digest, - salt_length, - hash_alg, - ) + let rsa = Rsa::public_key_from_der(public_key_der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pkey = + PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let md = get_message_digest(hash_alg); + let mut verifier = Verifier::new(md, &pkey) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + verifier + .set_rsa_padding(Padding::PKCS1_PSS) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + verifier + .set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::custom(salt_length as i32)) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + verifier + .set_rsa_mgf1_md(md) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + verifier + .update(digest) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + Ok(verifier.verify(signature).unwrap_or(false)) } fn rsa_pkcs1v15_sign( @@ -165,7 +270,22 @@ impl CryptoProvider for OpenSslProvider { digest: &[u8], hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { - super::rust::RustCryptoProvider.rsa_pkcs1v15_sign(private_key_der, digest, hash_alg) + let rsa = Rsa::private_key_from_der(private_key_der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pkey = + PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let md = get_message_digest(hash_alg); + let mut signer = Signer::new(md, &pkey) + .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; + signer + .set_rsa_padding(Padding::PKCS1) + .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; + signer + .update(digest) + .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; + signer + .sign_to_vec() + .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into()))) } fn rsa_pkcs1v15_verify( @@ -175,12 +295,20 @@ impl CryptoProvider for OpenSslProvider { digest: &[u8], hash_alg: ShaAlgorithm, ) -> Result { - super::rust::RustCryptoProvider.rsa_pkcs1v15_verify( - public_key_der, - signature, - digest, - hash_alg, - ) + let rsa = Rsa::public_key_from_der(public_key_der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pkey = + PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let md = get_message_digest(hash_alg); + let mut verifier = Verifier::new(md, &pkey) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + verifier + .set_rsa_padding(Padding::PKCS1) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + verifier + .update(digest) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + Ok(verifier.verify(signature).unwrap_or(false)) } fn rsa_oaep_encrypt( @@ -190,7 +318,30 @@ impl CryptoProvider for OpenSslProvider { hash_alg: ShaAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { - super::rust::RustCryptoProvider.rsa_oaep_encrypt(public_key_der, data, hash_alg, label) + let rsa = Rsa::public_key_from_der(public_key_der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pkey = + PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let mut ctx = PkeyCtx::new(&pkey) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + ctx.encrypt_init() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + ctx.set_rsa_padding(Padding::PKCS1_OAEP) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + ctx.set_rsa_oaep_md(get_md(hash_alg)) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + ctx.set_rsa_mgf1_md(get_md(hash_alg)) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + if let Some(lbl) = label { + ctx.set_rsa_oaep_label(lbl) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + } + let mut out = vec![0u8; pkey.size()]; + let len = ctx + .encrypt(data, Some(&mut out)) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + out.truncate(len); + Ok(out) } fn rsa_oaep_decrypt( @@ -200,7 +351,30 @@ impl CryptoProvider for OpenSslProvider { hash_alg: ShaAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { - super::rust::RustCryptoProvider.rsa_oaep_decrypt(private_key_der, data, hash_alg, label) + let rsa = Rsa::private_key_from_der(private_key_der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pkey = + PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let mut ctx = PkeyCtx::new(&pkey) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + ctx.decrypt_init() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + ctx.set_rsa_padding(Padding::PKCS1_OAEP) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + ctx.set_rsa_oaep_md(get_md(hash_alg)) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + ctx.set_rsa_mgf1_md(get_md(hash_alg)) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + if let Some(lbl) = label { + ctx.set_rsa_oaep_label(lbl) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + } + let mut out = vec![0u8; pkey.size()]; + let len = ctx + .decrypt(data, Some(&mut out)) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + out.truncate(len); + Ok(out) } fn ecdh_derive_bits( @@ -209,7 +383,31 @@ impl CryptoProvider for OpenSslProvider { private_key_der: &[u8], public_key_sec1: &[u8], ) -> Result, CryptoError> { - super::rust::RustCryptoProvider.ecdh_derive_bits(curve, private_key_der, public_key_sec1) + let group = get_ec_group(curve)?; + let private_ec = EcKey::private_key_from_der(private_key_der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let private_pkey = PKey::from_ec_key(private_ec) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let public_ec = EcKey::public_key_from_der(public_key_sec1).or_else(|_| { + let point = openssl::ec::EcPoint::from_bytes( + &group, + public_key_sec1, + &mut openssl::bn::BigNumContext::new().unwrap(), + ) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + EcKey::from_public_key(&group, &point) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) + })?; + let public_pkey = PKey::from_ec_key(public_ec) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let mut deriver = Deriver::new(&private_pkey) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + deriver + .set_peer(&public_pkey) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + deriver + .derive_to_vec() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into()))) } fn x25519_derive_bits( @@ -217,7 +415,18 @@ impl CryptoProvider for OpenSslProvider { private_key: &[u8], public_key: &[u8], ) -> Result, CryptoError> { - super::rust::RustCryptoProvider.x25519_derive_bits(private_key, public_key) + let private_pkey = PKey::private_key_from_raw_bytes(private_key, Id::X25519) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let public_pkey = PKey::public_key_from_raw_bytes(public_key, Id::X25519) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let mut deriver = Deriver::new(&private_pkey) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + deriver + .set_peer(&public_pkey) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + deriver + .derive_to_vec() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into()))) } fn aes_encrypt( @@ -228,7 +437,62 @@ impl CryptoProvider for OpenSslProvider { data: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoError> { - super::rust::RustCryptoProvider.aes_encrypt(mode, key, iv, data, additional_data) + match mode { + AesMode::Cbc => { + let cipher = match key.len() { + 16 => Cipher::aes_128_cbc(), + 24 => Cipher::aes_192_cbc(), + 32 => Cipher::aes_256_cbc(), + _ => { + return Err(CryptoError::InvalidKey(Some( + "Invalid AES key length".into(), + ))) + }, + }; + symm::encrypt(cipher, key, Some(iv), data) + .map_err(|e| CryptoError::EncryptionFailed(Some(e.to_string().into()))) + }, + AesMode::Ctr { .. } => { + let cipher = match key.len() { + 16 => Cipher::aes_128_ctr(), + 24 => Cipher::aes_192_ctr(), + 32 => Cipher::aes_256_ctr(), + _ => { + return Err(CryptoError::InvalidKey(Some( + "Invalid AES key length".into(), + ))) + }, + }; + symm::encrypt(cipher, key, Some(iv), data) + .map_err(|e| CryptoError::EncryptionFailed(Some(e.to_string().into()))) + }, + AesMode::Gcm { tag_length } => { + let cipher = match key.len() { + 16 => Cipher::aes_128_gcm(), + 24 => Cipher::aes_192_gcm(), + 32 => Cipher::aes_256_gcm(), + _ => { + return Err(CryptoError::InvalidKey(Some( + "Invalid AES key length".into(), + ))) + }, + }; + let tag_len = tag_length as usize; + let mut tag = vec![0u8; tag_len]; + let ciphertext = symm::encrypt_aead( + cipher, + key, + Some(iv), + additional_data.unwrap_or(&[]), + data, + &mut tag, + ) + .map_err(|e| CryptoError::EncryptionFailed(Some(e.to_string().into())))?; + let mut result = ciphertext; + result.extend_from_slice(&tag); + Ok(result) + }, + } } fn aes_decrypt( @@ -239,15 +503,81 @@ impl CryptoProvider for OpenSslProvider { data: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoError> { - super::rust::RustCryptoProvider.aes_decrypt(mode, key, iv, data, additional_data) + match mode { + AesMode::Cbc => { + let cipher = match key.len() { + 16 => Cipher::aes_128_cbc(), + 24 => Cipher::aes_192_cbc(), + 32 => Cipher::aes_256_cbc(), + _ => { + return Err(CryptoError::InvalidKey(Some( + "Invalid AES key length".into(), + ))) + }, + }; + symm::decrypt(cipher, key, Some(iv), data) + .map_err(|e| CryptoError::DecryptionFailed(Some(e.to_string().into()))) + }, + AesMode::Ctr { .. } => { + let cipher = match key.len() { + 16 => Cipher::aes_128_ctr(), + 24 => Cipher::aes_192_ctr(), + 32 => Cipher::aes_256_ctr(), + _ => { + return Err(CryptoError::InvalidKey(Some( + "Invalid AES key length".into(), + ))) + }, + }; + symm::decrypt(cipher, key, Some(iv), data) + .map_err(|e| CryptoError::DecryptionFailed(Some(e.to_string().into()))) + }, + AesMode::Gcm { tag_length } => { + let cipher = match key.len() { + 16 => Cipher::aes_128_gcm(), + 24 => Cipher::aes_192_gcm(), + 32 => Cipher::aes_256_gcm(), + _ => { + return Err(CryptoError::InvalidKey(Some( + "Invalid AES key length".into(), + ))) + }, + }; + let tag_len = tag_length as usize; + if data.len() < tag_len { + return Err(CryptoError::InvalidData(Some( + "Data too short for GCM tag".into(), + ))); + } + let (ciphertext, tag) = data.split_at(data.len() - tag_len); + symm::decrypt_aead( + cipher, + key, + Some(iv), + additional_data.unwrap_or(&[]), + ciphertext, + tag, + ) + .map_err(|e| CryptoError::DecryptionFailed(Some(e.to_string().into()))) + }, + } } fn aes_kw_wrap(&self, kek: &[u8], key: &[u8]) -> Result, CryptoError> { - super::rust::RustCryptoProvider.aes_kw_wrap(kek, key) + use openssl::aes::{wrap_key, AesKey}; + let aes_key = AesKey::new_encrypt(kek).map_err(|_| CryptoError::InvalidKey(None))?; + let mut out = vec![0u8; key.len() + 8]; + wrap_key(&aes_key, None, &mut out, key).map_err(|_| CryptoError::OperationFailed(None))?; + Ok(out) } fn aes_kw_unwrap(&self, kek: &[u8], wrapped_key: &[u8]) -> Result, CryptoError> { - super::rust::RustCryptoProvider.aes_kw_unwrap(kek, wrapped_key) + use openssl::aes::{unwrap_key, AesKey}; + let aes_key = AesKey::new_decrypt(kek).map_err(|_| CryptoError::InvalidKey(None))?; + let mut out = vec![0u8; wrapped_key.len() - 8]; + unwrap_key(&aes_key, None, &mut out, wrapped_key) + .map_err(|_| CryptoError::OperationFailed(None))?; + Ok(out) } fn hkdf_derive_key( @@ -258,7 +588,30 @@ impl CryptoProvider for OpenSslProvider { length: usize, hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { - super::rust::RustCryptoProvider.hkdf_derive_key(key, salt, info, length, hash_alg) + use openssl::pkey_ctx::HkdfMode; + let md = get_md(hash_alg); + let mut ctx = PkeyCtx::new_id(Id::HKDF) + .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; + ctx.derive_init() + .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; + ctx.set_hkdf_md(md) + .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; + ctx.set_hkdf_mode(HkdfMode::EXTRACT_THEN_EXPAND) + .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; + ctx.set_hkdf_key(key) + .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; + if !salt.is_empty() { + ctx.set_hkdf_salt(salt) + .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; + } + if !info.is_empty() { + ctx.add_hkdf_info(info) + .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; + } + let mut out = vec![0u8; length]; + ctx.derive(Some(&mut out)) + .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; + Ok(out) } fn pbkdf2_derive_key( @@ -269,12 +622,19 @@ impl CryptoProvider for OpenSslProvider { length: usize, hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { - super::rust::RustCryptoProvider - .pbkdf2_derive_key(password, salt, iterations, length, hash_alg) + let md = get_message_digest(hash_alg); + let mut out = vec![0u8; length]; + openssl::pkcs5::pbkdf2_hmac(password, salt, iterations as usize, md, &mut out) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + Ok(out) } fn generate_aes_key(&self, length_bits: u16) -> Result, CryptoError> { - super::rust::RustCryptoProvider.generate_aes_key(length_bits) + let length_bytes = (length_bits / 8) as usize; + let mut key = vec![0u8; length_bytes]; + rand_bytes(&mut key) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + Ok(key) } fn generate_hmac_key( @@ -282,19 +642,58 @@ impl CryptoProvider for OpenSslProvider { hash_alg: ShaAlgorithm, length_bits: u16, ) -> Result, CryptoError> { - super::rust::RustCryptoProvider.generate_hmac_key(hash_alg, length_bits) + let length_bytes = if length_bits == 0 { + match hash_alg { + ShaAlgorithm::MD5 => 16, + ShaAlgorithm::SHA1 => 20, + ShaAlgorithm::SHA256 => 32, + ShaAlgorithm::SHA384 => 48, + ShaAlgorithm::SHA512 => 64, + } + } else { + (length_bits / 8) as usize + }; + let mut key = vec![0u8; length_bytes]; + rand_bytes(&mut key) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + Ok(key) } fn generate_ec_key(&self, curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { - super::rust::RustCryptoProvider.generate_ec_key(curve) + let group = get_ec_group(curve)?; + let ec_key = EcKey::generate(&group) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + let private_der = ec_key + .private_key_to_der() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + let public_der = ec_key + .public_key_to_der() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + Ok((private_der, public_der)) } fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { - super::rust::RustCryptoProvider.generate_ed25519_key() + let pkey = PKey::generate_ed25519() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + let private_der = pkey + .private_key_to_der() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + let public_der = pkey + .public_key_to_der() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + Ok((private_der, public_der)) } fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { - super::rust::RustCryptoProvider.generate_x25519_key() + let pkey = PKey::generate_x25519() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + let private_raw = pkey + .raw_private_key() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + let public_raw = pkey + .raw_public_key() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + Ok((private_raw, public_raw)) } fn generate_rsa_key( @@ -302,6 +701,524 @@ impl CryptoProvider for OpenSslProvider { modulus_length: u32, public_exponent: &[u8], ) -> Result<(Vec, Vec), CryptoError> { - super::rust::RustCryptoProvider.generate_rsa_key(modulus_length, public_exponent) + let exp = BigNum::from_slice(public_exponent) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let rsa = Rsa::generate_with_e(modulus_length, &exp) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + let private_der = rsa + .private_key_to_der() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + let public_der = rsa + .public_key_to_der() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + Ok((private_der, public_der)) + } + + fn import_rsa_public_key_pkcs1( + &self, + der: &[u8], + ) -> Result { + let rsa = Rsa::public_key_from_der(der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let modulus_length = rsa.n().num_bits() as u32; + let public_exponent = rsa.e().to_vec(); + let key_data = rsa + .public_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::RsaImportResult { + key_data, + modulus_length, + public_exponent, + is_private: false, + }) + } + + fn import_rsa_private_key_pkcs1( + &self, + der: &[u8], + ) -> Result { + let rsa = Rsa::private_key_from_der(der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let modulus_length = rsa.n().num_bits() as u32; + let public_exponent = rsa.e().to_vec(); + let key_data = rsa + .private_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::RsaImportResult { + key_data, + modulus_length, + public_exponent, + is_private: true, + }) + } + + fn import_rsa_public_key_spki( + &self, + der: &[u8], + ) -> Result { + let pkey = PKey::public_key_from_der(der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let rsa = pkey + .rsa() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let modulus_length = rsa.n().num_bits() as u32; + let public_exponent = rsa.e().to_vec(); + let key_data = rsa + .public_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::RsaImportResult { + key_data, + modulus_length, + public_exponent, + is_private: false, + }) + } + + fn import_rsa_private_key_pkcs8( + &self, + der: &[u8], + ) -> Result { + let pkey = PKey::private_key_from_der(der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let rsa = pkey + .rsa() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let modulus_length = rsa.n().num_bits() as u32; + let public_exponent = rsa.e().to_vec(); + let key_data = rsa + .private_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::RsaImportResult { + key_data, + modulus_length, + public_exponent, + is_private: true, + }) + } + + fn export_rsa_public_key_pkcs1(&self, key_data: &[u8]) -> Result, CryptoError> { + let rsa = Rsa::public_key_from_der(key_data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + rsa.public_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) + } + + fn export_rsa_public_key_spki(&self, key_data: &[u8]) -> Result, CryptoError> { + let rsa = Rsa::public_key_from_der(key_data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pkey = + PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + pkey.public_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) + } + + fn export_rsa_private_key_pkcs8(&self, key_data: &[u8]) -> Result, CryptoError> { + let rsa = Rsa::private_key_from_der(key_data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pkey = + PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + pkey.private_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) + } + + fn import_ec_public_key_sec1( + &self, + data: &[u8], + curve: EllipticCurve, + ) -> Result { + let nid = curve_to_nid(curve); + let group = EcGroup::from_curve_name(nid) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let mut ctx = openssl::bn::BigNumContext::new() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let point = openssl::ec::EcPoint::from_bytes(&group, data, &mut ctx) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let ec_key = EcKey::from_public_key(&group, &point) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pkey = PKey::from_ec_key(ec_key) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::EcImportResult { + key_data: pkey + .public_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, + is_private: false, + }) + } + + fn import_ec_public_key_spki(&self, der: &[u8]) -> Result { + let pkey = PKey::public_key_from_der(der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::EcImportResult { + key_data: pkey + .public_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, + is_private: false, + }) + } + + fn import_ec_private_key_pkcs8( + &self, + der: &[u8], + ) -> Result { + let pkey = PKey::private_key_from_der(der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::EcImportResult { + key_data: pkey + .private_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, + is_private: true, + }) + } + + fn import_ec_private_key_sec1( + &self, + data: &[u8], + curve: EllipticCurve, + ) -> Result { + let nid = curve_to_nid(curve); + let group = EcGroup::from_curve_name(nid) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let bn = BigNum::from_slice(data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let ec_key = EcKey::from_private_components(&group, &bn, &group.generator()) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pkey = PKey::from_ec_key(ec_key) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::EcImportResult { + key_data: pkey + .private_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, + is_private: true, + }) + } + + fn export_ec_public_key_sec1( + &self, + key_data: &[u8], + _curve: EllipticCurve, + is_private: bool, + ) -> Result, CryptoError> { + let mut ctx = openssl::bn::BigNumContext::new() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + if is_private { + let ec_key = PKey::private_key_from_der(key_data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))? + .ec_key() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + ec_key + .public_key() + .to_bytes( + ec_key.group(), + openssl::ec::PointConversionForm::UNCOMPRESSED, + &mut ctx, + ) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) + } else { + let ec_key = PKey::public_key_from_der(key_data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))? + .ec_key() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + ec_key + .public_key() + .to_bytes( + ec_key.group(), + openssl::ec::PointConversionForm::UNCOMPRESSED, + &mut ctx, + ) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) + } + } + + fn export_ec_public_key_spki( + &self, + key_data: &[u8], + _curve: EllipticCurve, + ) -> Result, CryptoError> { + let pkey = PKey::public_key_from_der(key_data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + pkey.public_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) + } + + fn export_ec_private_key_pkcs8( + &self, + key_data: &[u8], + _curve: EllipticCurve, + ) -> Result, CryptoError> { + let pkey = PKey::private_key_from_der(key_data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + pkey.private_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) + } + + fn import_okp_public_key_raw( + &self, + data: &[u8], + ) -> Result { + if data.len() != 32 { + return Err(CryptoError::InvalidKey(None)); + } + Ok(super::OkpImportResult { + key_data: data.to_vec(), + is_private: false, + }) + } + + fn import_okp_public_key_spki( + &self, + der: &[u8], + _expected_oid: &[u8], + ) -> Result { + let pkey = PKey::public_key_from_der(der) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let raw = pkey + .raw_public_key() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::OkpImportResult { + key_data: raw, + is_private: false, + }) + } + + fn import_okp_private_key_pkcs8( + &self, + der: &[u8], + _expected_oid: &[u8], + ) -> Result { + Ok(super::OkpImportResult { + key_data: der.to_vec(), + is_private: true, + }) + } + + fn export_okp_public_key_raw( + &self, + key_data: &[u8], + is_private: bool, + ) -> Result, CryptoError> { + if is_private { + let pkey = PKey::private_key_from_der(key_data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + pkey.raw_public_key() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) + } else { + Ok(key_data.to_vec()) + } + } + + fn export_okp_public_key_spki( + &self, + key_data: &[u8], + _oid: &[u8], + ) -> Result, CryptoError> { + // key_data is raw public key, need to wrap in SPKI + let pkey = PKey::public_key_from_raw_bytes(key_data, Id::ED25519) + .or_else(|_| PKey::public_key_from_raw_bytes(key_data, Id::X25519)) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + pkey.public_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) + } + + fn export_okp_private_key_pkcs8( + &self, + key_data: &[u8], + _oid: &[u8], + ) -> Result, CryptoError> { + // key_data is already PKCS8 + Ok(key_data.to_vec()) + } + + fn import_rsa_jwk( + &self, + jwk: super::RsaJwkImport<'_>, + ) -> Result { + let n = BigNum::from_slice(jwk.n) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let e = BigNum::from_slice(jwk.e) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let modulus_length = n.num_bits() as u32; + let pub_exp_bytes = jwk.e.to_vec(); + + if let ( + Some(d_bytes), + Some(p_bytes), + Some(q_bytes), + Some(dp_bytes), + Some(dq_bytes), + Some(qi_bytes), + ) = (jwk.d, jwk.p, jwk.q, jwk.dp, jwk.dq, jwk.qi) + { + let d = BigNum::from_slice(d_bytes) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let p = BigNum::from_slice(p_bytes) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let q = BigNum::from_slice(q_bytes) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let dp = BigNum::from_slice(dp_bytes) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let dq = BigNum::from_slice(dq_bytes) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let qi = BigNum::from_slice(qi_bytes) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + + let rsa = Rsa::from_private_components(n, e, d, p, q, dp, dq, qi) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pkey = PKey::from_rsa(rsa) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::RsaImportResult { + key_data: pkey + .private_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, + modulus_length, + public_exponent: pub_exp_bytes, + is_private: true, + }) + } else { + let rsa = Rsa::from_public_components(n, e) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pkey = PKey::from_rsa(rsa) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::RsaImportResult { + key_data: pkey + .public_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, + modulus_length, + public_exponent: pub_exp_bytes, + is_private: false, + }) + } + } + + fn export_rsa_jwk( + &self, + key_data: &[u8], + is_private: bool, + ) -> Result { + if is_private { + let pkey = PKey::private_key_from_der(key_data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let rsa = pkey + .rsa() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::RsaJwkExport { + n: rsa.n().to_vec(), + e: rsa.e().to_vec(), + d: Some(rsa.d().to_vec()), + p: rsa.p().map(|v| v.to_vec()), + q: rsa.q().map(|v| v.to_vec()), + dp: rsa.dmp1().map(|v| v.to_vec()), + dq: rsa.dmq1().map(|v| v.to_vec()), + qi: rsa.iqmp().map(|v| v.to_vec()), + }) + } else { + let pkey = PKey::public_key_from_der(key_data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let rsa = pkey + .rsa() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::RsaJwkExport { + n: rsa.n().to_vec(), + e: rsa.e().to_vec(), + d: None, + p: None, + q: None, + dp: None, + dq: None, + qi: None, + }) + } + } + + fn import_ec_jwk( + &self, + jwk: super::EcJwkImport<'_>, + curve: EllipticCurve, + ) -> Result { + let nid = curve_to_nid(curve); + let group = EcGroup::from_curve_name(nid) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let x = BigNum::from_slice(jwk.x) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let y = BigNum::from_slice(jwk.y) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pub_key = EcKey::from_public_key_affine_coordinates(&group, &x, &y) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + + if let Some(d_bytes) = jwk.d { + let d = BigNum::from_slice(d_bytes) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let priv_key = EcKey::from_private_components(&group, &d, pub_key.public_key()) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let pkey = PKey::from_ec_key(priv_key) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::EcImportResult { + key_data: pkey + .private_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, + is_private: true, + }) + } else { + let pkey = PKey::from_ec_key(pub_key) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::EcImportResult { + key_data: pkey + .public_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, + is_private: false, + }) + } + } + + fn export_ec_jwk( + &self, + key_data: &[u8], + curve: EllipticCurve, + is_private: bool, + ) -> Result { + let nid = curve_to_nid(curve); + let group = EcGroup::from_curve_name(nid) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let mut ctx = openssl::bn::BigNumContext::new() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + + if is_private { + let pkey = PKey::private_key_from_der(key_data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let ec_key = pkey + .ec_key() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let mut x = + BigNum::new().map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let mut y = + BigNum::new().map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + ec_key + .public_key() + .affine_coordinates(&group, &mut x, &mut y, &mut ctx) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::EcJwkExport { + x: x.to_vec(), + y: y.to_vec(), + d: Some(ec_key.private_key().to_vec()), + }) + } else { + let pkey = PKey::public_key_from_der(key_data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let ec_key = pkey + .ec_key() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let mut x = + BigNum::new().map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let mut y = + BigNum::new().map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + ec_key + .public_key() + .affine_coordinates(&group, &mut x, &mut y, &mut ctx) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::EcJwkExport { + x: x.to_vec(), + y: y.to_vec(), + d: None, + }) + } } } diff --git a/modules/llrt_crypto/src/provider/ring.rs b/modules/llrt_crypto/src/provider/ring.rs index d97ea3f6c3..413a3b2371 100644 --- a/modules/llrt_crypto/src/provider/ring.rs +++ b/modules/llrt_crypto/src/provider/ring.rs @@ -371,4 +371,152 @@ impl CryptoProvider for RingProvider { ) -> Result<(Vec, Vec), CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } + + fn import_rsa_public_key_pkcs1( + &self, + _der: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_rsa_private_key_pkcs1( + &self, + _der: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_rsa_public_key_spki( + &self, + _der: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_rsa_private_key_pkcs8( + &self, + _der: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_rsa_public_key_pkcs1(&self, _key_data: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_rsa_public_key_spki(&self, _key_data: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_rsa_private_key_pkcs8(&self, _key_data: &[u8]) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_ec_public_key_sec1( + &self, + _data: &[u8], + _curve: EllipticCurve, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_ec_public_key_spki(&self, _der: &[u8]) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_ec_private_key_pkcs8( + &self, + _der: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_ec_private_key_sec1( + &self, + _data: &[u8], + _curve: EllipticCurve, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_ec_public_key_sec1( + &self, + _key_data: &[u8], + _curve: EllipticCurve, + _is_private: bool, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_ec_public_key_spki( + &self, + _key_data: &[u8], + _curve: EllipticCurve, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_ec_private_key_pkcs8( + &self, + _key_data: &[u8], + _curve: EllipticCurve, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_okp_public_key_raw( + &self, + _data: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_okp_public_key_spki( + &self, + _der: &[u8], + _expected_oid: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_okp_private_key_pkcs8( + &self, + _der: &[u8], + _expected_oid: &[u8], + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_okp_public_key_raw( + &self, + _key_data: &[u8], + _is_private: bool, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_okp_public_key_spki( + &self, + _key_data: &[u8], + _oid: &[u8], + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_okp_private_key_pkcs8( + &self, + _key_data: &[u8], + _oid: &[u8], + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_rsa_jwk( + &self, + _jwk: super::RsaJwkImport<'_>, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_rsa_jwk( + &self, + _key_data: &[u8], + _is_private: bool, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn import_ec_jwk( + &self, + _jwk: super::EcJwkImport<'_>, + _curve: EllipticCurve, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_ec_jwk( + &self, + _key_data: &[u8], + _curve: EllipticCurve, + _is_private: bool, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } } diff --git a/modules/llrt_crypto/src/provider/rust.rs b/modules/llrt_crypto/src/provider/rust.rs index 6fd18bdd9f..27ea19e2ad 100644 --- a/modules/llrt_crypto/src/provider/rust.rs +++ b/modules/llrt_crypto/src/provider/rust.rs @@ -14,8 +14,10 @@ use aes_gcm::{ use aes_kw::{KwAes128, KwAes192, KwAes256}; use cbc::{Decryptor, Encryptor}; use ctr::{cipher::Array, Ctr128BE, Ctr32BE, Ctr64BE}; +use der::Encode; use ecdsa::signature::hazmat::PrehashVerifier; use elliptic_curve::consts::U12; +use elliptic_curve::sec1::ToEncodedPoint; use hmac::{Hmac as HmacImpl, Mac}; use once_cell::sync::Lazy; use p256::{ @@ -34,7 +36,7 @@ use p521::{ ecdsa::{Signature as P521Signature, VerifyingKey as P521VerifyingKey}, SecretKey as P521SecretKey, }; -use pkcs8::EncodePrivateKey; +use pkcs8::{DecodePrivateKey, EncodePrivateKey}; use ring::{ pbkdf2, rand::SystemRandom, @@ -43,7 +45,6 @@ use ring::{ use rsa::pkcs1::{ DecodeRsaPrivateKey, DecodeRsaPublicKey, EncodeRsaPrivateKey, EncodeRsaPublicKey, }; -use rsa::pkcs8::DecodePrivateKey; use rsa::signature::hazmat::PrehashSigner; use rsa::{ pss::Pss, @@ -67,7 +68,7 @@ impl From for CryptoError { impl From for CryptoError { fn from(_: StreamCipherError) -> Self { - CryptoError::OperationFailed + CryptoError::OperationFailed(None) } } @@ -175,29 +176,29 @@ impl CryptoProvider for RustCryptoProvider { match curve { EllipticCurve::P256 => { let secret_key = P256SecretKey::from_pkcs8_der(private_key_der) - .map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::InvalidKey(None))?; let signing_key = P256SigningKey::from(secret_key); let signature: p256::ecdsa::Signature = signing_key .sign_prehash(digest) - .map_err(|_| CryptoError::SigningFailed)?; + .map_err(|_| CryptoError::SigningFailed(None))?; Ok(signature.to_bytes().to_vec()) }, EllipticCurve::P384 => { let secret_key = P384SecretKey::from_pkcs8_der(private_key_der) - .map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::InvalidKey(None))?; let signing_key = P384SigningKey::from(secret_key); let signature: p384::ecdsa::Signature = signing_key .sign_prehash(digest) - .map_err(|_| CryptoError::SigningFailed)?; + .map_err(|_| CryptoError::SigningFailed(None))?; Ok(signature.to_bytes().to_vec()) }, EllipticCurve::P521 => { let secret_key = P521SecretKey::from_pkcs8_der(private_key_der) - .map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::InvalidKey(None))?; let signing_key = p521::ecdsa::SigningKey::from(secret_key); let signature: p521::ecdsa::Signature = signing_key .sign_prehash(digest) - .map_err(|_| CryptoError::SigningFailed)?; + .map_err(|_| CryptoError::SigningFailed(None))?; Ok(signature.to_bytes().to_vec()) }, } @@ -213,31 +214,31 @@ impl CryptoProvider for RustCryptoProvider { match curve { EllipticCurve::P256 => { let verifying_key = P256VerifyingKey::from_sec1_bytes(public_key_sec1) - .map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::InvalidKey(None))?; let sig = P256Signature::from_slice(signature) - .map_err(|_| CryptoError::InvalidSignature)?; + .map_err(|_| CryptoError::InvalidSignature(None))?; Ok(verifying_key.verify_prehash(digest, &sig).is_ok()) }, EllipticCurve::P384 => { let verifying_key = P384VerifyingKey::from_sec1_bytes(public_key_sec1) - .map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::InvalidKey(None))?; let sig = P384Signature::from_slice(signature) - .map_err(|_| CryptoError::InvalidSignature)?; + .map_err(|_| CryptoError::InvalidSignature(None))?; Ok(verifying_key.verify_prehash(digest, &sig).is_ok()) }, EllipticCurve::P521 => { let verifying_key = P521VerifyingKey::from_sec1_bytes(public_key_sec1) - .map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::InvalidKey(None))?; let sig = P521Signature::from_slice(signature) - .map_err(|_| CryptoError::InvalidSignature)?; + .map_err(|_| CryptoError::InvalidSignature(None))?; Ok(verifying_key.verify_prehash(digest, &sig).is_ok()) }, } } fn ed25519_sign(&self, private_key_der: &[u8], data: &[u8]) -> Result, CryptoError> { - let key_pair = - Ed25519KeyPair::from_pkcs8(private_key_der).map_err(|_| CryptoError::InvalidKey)?; + let key_pair = Ed25519KeyPair::from_pkcs8(private_key_der) + .map_err(|_| CryptoError::InvalidKey(None))?; let signature = key_pair.sign(data); Ok(signature.as_ref().to_vec()) } @@ -260,19 +261,19 @@ impl CryptoProvider for RustCryptoProvider { hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { let mut rng = rand::rng(); - let private_key = - RsaPrivateKey::from_pkcs1_der(private_key_der).map_err(|_| CryptoError::InvalidKey)?; + let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) + .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { ShaAlgorithm::SHA256 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) - .map_err(|_| CryptoError::SigningFailed), + .map_err(|_| CryptoError::SigningFailed(None)), ShaAlgorithm::SHA384 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) - .map_err(|_| CryptoError::SigningFailed), + .map_err(|_| CryptoError::SigningFailed(None)), ShaAlgorithm::SHA512 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) - .map_err(|_| CryptoError::SigningFailed), + .map_err(|_| CryptoError::SigningFailed(None)), _ => Err(CryptoError::UnsupportedAlgorithm), } } @@ -285,8 +286,8 @@ impl CryptoProvider for RustCryptoProvider { salt_length: usize, hash_alg: ShaAlgorithm, ) -> Result { - let public_key = - RsaPublicKey::from_pkcs1_der(public_key_der).map_err(|_| CryptoError::InvalidKey)?; + let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) + .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { ShaAlgorithm::SHA256 => Ok(public_key @@ -309,19 +310,19 @@ impl CryptoProvider for RustCryptoProvider { hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { let mut rng = rand::rng(); - let private_key = - RsaPrivateKey::from_pkcs1_der(private_key_der).map_err(|_| CryptoError::InvalidKey)?; + let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) + .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { ShaAlgorithm::SHA256 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) - .map_err(|_| CryptoError::SigningFailed), + .map_err(|_| CryptoError::SigningFailed(None)), ShaAlgorithm::SHA384 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) - .map_err(|_| CryptoError::SigningFailed), + .map_err(|_| CryptoError::SigningFailed(None)), ShaAlgorithm::SHA512 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) - .map_err(|_| CryptoError::SigningFailed), + .map_err(|_| CryptoError::SigningFailed(None)), _ => Err(CryptoError::UnsupportedAlgorithm), } } @@ -333,8 +334,8 @@ impl CryptoProvider for RustCryptoProvider { digest: &[u8], hash_alg: ShaAlgorithm, ) -> Result { - let public_key = - RsaPublicKey::from_pkcs1_der(public_key_der).map_err(|_| CryptoError::InvalidKey)?; + let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) + .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { ShaAlgorithm::SHA256 => Ok(public_key @@ -358,8 +359,8 @@ impl CryptoProvider for RustCryptoProvider { label: Option<&[u8]>, ) -> Result, CryptoError> { let mut rng = rand::rng(); - let public_key = - RsaPublicKey::from_pkcs1_der(public_key_der).map_err(|_| CryptoError::InvalidKey)?; + let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) + .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { ShaAlgorithm::SHA1 => { @@ -371,7 +372,7 @@ impl CryptoProvider for RustCryptoProvider { } public_key .encrypt(&mut rng, padding, data) - .map_err(|_| CryptoError::EncryptionFailed) + .map_err(|_| CryptoError::EncryptionFailed(None)) }, ShaAlgorithm::SHA256 => { let mut padding = Oaep::::new(); @@ -382,7 +383,7 @@ impl CryptoProvider for RustCryptoProvider { } public_key .encrypt(&mut rng, padding, data) - .map_err(|_| CryptoError::EncryptionFailed) + .map_err(|_| CryptoError::EncryptionFailed(None)) }, ShaAlgorithm::SHA384 => { let mut padding = Oaep::::new(); @@ -393,7 +394,7 @@ impl CryptoProvider for RustCryptoProvider { } public_key .encrypt(&mut rng, padding, data) - .map_err(|_| CryptoError::EncryptionFailed) + .map_err(|_| CryptoError::EncryptionFailed(None)) }, ShaAlgorithm::SHA512 => { let mut padding = Oaep::::new(); @@ -404,7 +405,7 @@ impl CryptoProvider for RustCryptoProvider { } public_key .encrypt(&mut rng, padding, data) - .map_err(|_| CryptoError::EncryptionFailed) + .map_err(|_| CryptoError::EncryptionFailed(None)) }, _ => Err(CryptoError::UnsupportedAlgorithm), } @@ -417,8 +418,8 @@ impl CryptoProvider for RustCryptoProvider { hash_alg: ShaAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { - let private_key = - RsaPrivateKey::from_pkcs1_der(private_key_der).map_err(|_| CryptoError::InvalidKey)?; + let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) + .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { ShaAlgorithm::SHA1 => { @@ -430,7 +431,7 @@ impl CryptoProvider for RustCryptoProvider { } private_key .decrypt(padding, data) - .map_err(|_| CryptoError::DecryptionFailed) + .map_err(|_| CryptoError::DecryptionFailed(None)) }, ShaAlgorithm::SHA256 => { let mut padding = Oaep::::new(); @@ -441,7 +442,7 @@ impl CryptoProvider for RustCryptoProvider { } private_key .decrypt(padding, data) - .map_err(|_| CryptoError::DecryptionFailed) + .map_err(|_| CryptoError::DecryptionFailed(None)) }, ShaAlgorithm::SHA384 => { let mut padding = Oaep::::new(); @@ -452,7 +453,7 @@ impl CryptoProvider for RustCryptoProvider { } private_key .decrypt(padding, data) - .map_err(|_| CryptoError::DecryptionFailed) + .map_err(|_| CryptoError::DecryptionFailed(None)) }, ShaAlgorithm::SHA512 => { let mut padding = Oaep::::new(); @@ -463,7 +464,7 @@ impl CryptoProvider for RustCryptoProvider { } private_key .decrypt(padding, data) - .map_err(|_| CryptoError::DecryptionFailed) + .map_err(|_| CryptoError::DecryptionFailed(None)) }, _ => Err(CryptoError::UnsupportedAlgorithm), } @@ -478,9 +479,9 @@ impl CryptoProvider for RustCryptoProvider { match curve { EllipticCurve::P256 => { let secret_key = P256SecretKey::from_pkcs8_der(private_key_der) - .map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::InvalidKey(None))?; let public_key = p256::PublicKey::from_sec1_bytes(public_key_sec1) - .map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::InvalidKey(None))?; let shared_secret = p256::elliptic_curve::ecdh::diffie_hellman( secret_key.to_nonzero_scalar(), public_key.as_affine(), @@ -489,9 +490,9 @@ impl CryptoProvider for RustCryptoProvider { }, EllipticCurve::P384 => { let secret_key = P384SecretKey::from_pkcs8_der(private_key_der) - .map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::InvalidKey(None))?; let public_key = p384::PublicKey::from_sec1_bytes(public_key_sec1) - .map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::InvalidKey(None))?; let shared_secret = p384::elliptic_curve::ecdh::diffie_hellman( secret_key.to_nonzero_scalar(), public_key.as_affine(), @@ -500,9 +501,9 @@ impl CryptoProvider for RustCryptoProvider { }, EllipticCurve::P521 => { let secret_key = P521SecretKey::from_pkcs8_der(private_key_der) - .map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::InvalidKey(None))?; let public_key = p521::PublicKey::from_sec1_bytes(public_key_sec1) - .map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::InvalidKey(None))?; let shared_secret = p521::elliptic_curve::ecdh::diffie_hellman( secret_key.to_nonzero_scalar(), public_key.as_affine(), @@ -519,8 +520,10 @@ impl CryptoProvider for RustCryptoProvider { ) -> Result, CryptoError> { let private_array: [u8; 32] = private_key .try_into() - .map_err(|_| CryptoError::InvalidKey)?; - let public_array: [u8; 32] = public_key.try_into().map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::InvalidKey(None))?; + let public_array: [u8; 32] = public_key + .try_into() + .map_err(|_| CryptoError::InvalidKey(None))?; let secret_key = x25519_dalek::StaticSecret::from(private_array); let public_key = x25519_dalek::PublicKey::from(public_array); @@ -551,7 +554,7 @@ impl CryptoProvider for RustCryptoProvider { let encryptor = Encryptor::::new_from_slices(key, iv)?; Ok(encryptor.encrypt_padded_vec::(data)) }, - _ => Err(CryptoError::InvalidKey), + _ => Err(CryptoError::InvalidKey(None)), }, AesMode::Ctr { counter_length } => { let mut ciphertext = data.to_vec(); @@ -592,14 +595,14 @@ impl CryptoProvider for RustCryptoProvider { let mut cipher = Ctr128BE::::new_from_slices(key, iv)?; cipher.try_apply_keystream(&mut ciphertext)?; }, - _ => return Err(CryptoError::InvalidKey), + _ => return Err(CryptoError::InvalidKey(None)), } Ok(ciphertext) }, AesMode::Gcm { tag_length } => { let variant = AesGcmVariant::new((key.len() * 8) as u16, tag_length, key)?; let nonce: &Array<_, _> = - &Nonce::::try_from(iv).map_err(|_| CryptoError::InvalidData)?; + &Nonce::::try_from(iv).map_err(|_| CryptoError::InvalidData(None))?; let plaintext = Payload { msg: data, @@ -623,7 +626,7 @@ impl CryptoProvider for RustCryptoProvider { AesGcmVariant::Aes192Gcm128(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes256Gcm128(v) => v.encrypt(nonce, plaintext), } - .map_err(|_| CryptoError::EncryptionFailed) + .map_err(|_| CryptoError::EncryptionFailed(None)) }, } } @@ -642,21 +645,21 @@ impl CryptoProvider for RustCryptoProvider { let decryptor = Decryptor::::new_from_slices(key, iv)?; decryptor .decrypt_padded_vec::(data) - .map_err(|_| CryptoError::DecryptionFailed) + .map_err(|_| CryptoError::DecryptionFailed(None)) }, 24 => { let decryptor = Decryptor::::new_from_slices(key, iv)?; decryptor .decrypt_padded_vec::(data) - .map_err(|_| CryptoError::DecryptionFailed) + .map_err(|_| CryptoError::DecryptionFailed(None)) }, 32 => { let decryptor = Decryptor::::new_from_slices(key, iv)?; decryptor .decrypt_padded_vec::(data) - .map_err(|_| CryptoError::DecryptionFailed) + .map_err(|_| CryptoError::DecryptionFailed(None)) }, - _ => Err(CryptoError::InvalidKey), + _ => Err(CryptoError::InvalidKey(None)), }, AesMode::Ctr { .. } => { // CTR decryption is the same as encryption @@ -665,7 +668,7 @@ impl CryptoProvider for RustCryptoProvider { AesMode::Gcm { tag_length } => { let variant = AesGcmVariant::new((key.len() * 8) as u16, tag_length, key)?; let nonce: &Array<_, _> = - &Nonce::::try_from(iv).map_err(|_| CryptoError::InvalidData)?; + &Nonce::::try_from(iv).map_err(|_| CryptoError::InvalidData(None))?; let ciphertext = Payload { msg: data, @@ -689,7 +692,7 @@ impl CryptoProvider for RustCryptoProvider { AesGcmVariant::Aes192Gcm128(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes256Gcm128(v) => v.decrypt(nonce, ciphertext), } - .map_err(|_| CryptoError::DecryptionFailed) + .map_err(|_| CryptoError::DecryptionFailed(None)) }, } } @@ -697,60 +700,66 @@ impl CryptoProvider for RustCryptoProvider { fn aes_kw_wrap(&self, kek: &[u8], key: &[u8]) -> Result, CryptoError> { match kek.len() { 16 => { - let kw = KwAes128::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey)?; + let kw = + KwAes128::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut buf = vec![0u8; key.len() + 8]; let result = kw .wrap_key(key, &mut buf) - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; Ok(result.to_vec()) }, 24 => { - let kw = KwAes192::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey)?; + let kw = + KwAes192::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut buf = vec![0u8; key.len() + 8]; let result = kw .wrap_key(key, &mut buf) - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; Ok(result.to_vec()) }, 32 => { - let kw = KwAes256::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey)?; + let kw = + KwAes256::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut buf = vec![0u8; key.len() + 8]; let result = kw .wrap_key(key, &mut buf) - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; Ok(result.to_vec()) }, - _ => Err(CryptoError::InvalidKey), + _ => Err(CryptoError::InvalidKey(None)), } } fn aes_kw_unwrap(&self, kek: &[u8], wrapped_key: &[u8]) -> Result, CryptoError> { match kek.len() { 16 => { - let kw = KwAes128::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey)?; + let kw = + KwAes128::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut buf = vec![0u8; wrapped_key.len()]; let result = kw .unwrap_key(wrapped_key, &mut buf) - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; Ok(result.to_vec()) }, 24 => { - let kw = KwAes192::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey)?; + let kw = + KwAes192::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut buf = vec![0u8; wrapped_key.len()]; let result = kw .unwrap_key(wrapped_key, &mut buf) - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; Ok(result.to_vec()) }, 32 => { - let kw = KwAes256::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey)?; + let kw = + KwAes256::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut buf = vec![0u8; wrapped_key.len()]; let result = kw .unwrap_key(wrapped_key, &mut buf) - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; Ok(result.to_vec()) }, - _ => Err(CryptoError::InvalidKey), + _ => Err(CryptoError::InvalidKey(None)), } } @@ -777,11 +786,11 @@ impl CryptoProvider for RustCryptoProvider { let info = &[info]; let okm = prk .expand(info, HkdfOutput(length)) - .map_err(|_| CryptoError::DerivationFailed)?; + .map_err(|_| CryptoError::DerivationFailed(None))?; let mut out = vec![0u8; length]; okm.fill(&mut out) - .map_err(|_| CryptoError::DerivationFailed)?; + .map_err(|_| CryptoError::DerivationFailed(None))?; Ok(out) } @@ -802,7 +811,7 @@ impl CryptoProvider for RustCryptoProvider { }; let mut out = vec![0; length]; - let iterations = NonZeroU32::new(iterations).ok_or(CryptoError::InvalidData)?; + let iterations = NonZeroU32::new(iterations).ok_or(CryptoError::InvalidData(None))?; pbkdf2::derive(algorithm, iterations, salt, password, &mut out); Ok(out) } @@ -821,12 +830,12 @@ impl CryptoProvider for RustCryptoProvider { length_bits: u16, ) -> Result, CryptoError> { let length_bytes = if length_bits == 0 { - hash_alg.hmac_algorithm().digest_algorithm().block_len() + hash_alg.block_len() } else { (length_bits / 8) as usize }; - if length_bytes > ring::digest::MAX_BLOCK_LEN { + if length_bytes > 128 { return Err(CryptoError::InvalidLength); } @@ -839,30 +848,30 @@ impl CryptoProvider for RustCryptoProvider { match curve { EllipticCurve::P256 => { let key = P256SecretKey::try_from_rng(&mut rng) - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; let pkcs8 = key .to_pkcs8_der() - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; let private_key = pkcs8.as_bytes().to_vec(); let public_key = key.public_key().to_sec1_bytes().to_vec(); Ok((private_key, public_key)) }, EllipticCurve::P384 => { let key = P384SecretKey::try_from_rng(&mut rng) - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; let pkcs8 = key .to_pkcs8_der() - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; let private_key = pkcs8.as_bytes().to_vec(); let public_key = key.public_key().to_sec1_bytes().to_vec(); Ok((private_key, public_key)) }, EllipticCurve::P521 => { let key = P521SecretKey::try_from_rng(&mut rng) - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; let pkcs8 = key .to_pkcs8_der() - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; let private_key = pkcs8.as_bytes().to_vec(); let public_key = key.public_key().to_sec1_bytes().to_vec(); Ok((private_key, public_key)) @@ -873,10 +882,10 @@ impl CryptoProvider for RustCryptoProvider { fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { let rng = &(*SYSTEM_RANDOM); let pkcs8 = - Ed25519KeyPair::generate_pkcs8(rng).map_err(|_| CryptoError::OperationFailed)?; + Ed25519KeyPair::generate_pkcs8(rng).map_err(|_| CryptoError::OperationFailed(None))?; let private_key = pkcs8.as_ref().to_vec(); - let key_pair = - Ed25519KeyPair::from_pkcs8(&private_key).map_err(|_| CryptoError::OperationFailed)?; + let key_pair = Ed25519KeyPair::from_pkcs8(&private_key) + .map_err(|_| CryptoError::OperationFailed(None))?; let public_key = key_pair.public_key().as_ref().to_vec(); Ok((private_key, public_key)) } @@ -904,27 +913,564 @@ impl CryptoProvider for RustCryptoProvider { { 3 }, - _ => return Err(CryptoError::InvalidData), + _ => return Err(CryptoError::InvalidData(None)), }; let exp = BoxedUint::from(exponent); let mut rng = rand::rng(); let rsa_private_key = RsaPrivateKey::new_with_exp(&mut rng, modulus_length as usize, exp) - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; let public_key = rsa_private_key .to_public_key() .to_pkcs1_der() - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; let private_key = rsa_private_key .to_pkcs1_der() - .map_err(|_| CryptoError::OperationFailed)?; + .map_err(|_| CryptoError::OperationFailed(None))?; Ok(( private_key.as_bytes().to_vec(), public_key.as_bytes().to_vec(), )) } + + fn import_rsa_public_key_pkcs1( + &self, + der: &[u8], + ) -> Result { + use der::Decode; + let public_key = + rsa::pkcs1::RsaPublicKey::from_der(der).map_err(|_| CryptoError::InvalidKey(None))?; + let modulus_length = public_key.modulus.as_bytes().len() * 8; + let public_exponent = public_key.public_exponent.as_bytes().to_vec(); + let key_data = public_key + .to_der() + .map_err(|_| CryptoError::InvalidKey(None))?; + Ok(super::RsaImportResult { + key_data, + modulus_length: modulus_length as u32, + public_exponent, + is_private: false, + }) + } + + fn import_rsa_private_key_pkcs1( + &self, + der: &[u8], + ) -> Result { + use der::Decode; + let private_key = + rsa::pkcs1::RsaPrivateKey::from_der(der).map_err(|_| CryptoError::InvalidKey(None))?; + let modulus_length = private_key.modulus.as_bytes().len() * 8; + let public_exponent = private_key.public_exponent.as_bytes().to_vec(); + let key_data = private_key + .to_der() + .map_err(|_| CryptoError::InvalidKey(None))?; + Ok(super::RsaImportResult { + key_data, + modulus_length: modulus_length as u32, + public_exponent, + is_private: true, + }) + } + + fn import_rsa_public_key_spki( + &self, + der: &[u8], + ) -> Result { + use der::Decode; + let spki = spki::SubjectPublicKeyInfoRef::try_from(der) + .map_err(|_| CryptoError::InvalidKey(None))?; + let public_key = rsa::pkcs1::RsaPublicKey::from_der(spki.subject_public_key.raw_bytes()) + .map_err(|_| CryptoError::InvalidKey(None))?; + let modulus_length = public_key.modulus.as_bytes().len() * 8; + let public_exponent = public_key.public_exponent.as_bytes().to_vec(); + let key_data = public_key + .to_der() + .map_err(|_| CryptoError::InvalidKey(None))?; + Ok(super::RsaImportResult { + key_data, + modulus_length: modulus_length as u32, + public_exponent, + is_private: false, + }) + } + + fn import_rsa_private_key_pkcs8( + &self, + der: &[u8], + ) -> Result { + use der::Decode; + let pk_info = + pkcs8::PrivateKeyInfoRef::from_der(der).map_err(|_| CryptoError::InvalidKey(None))?; + let private_key = rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key.as_bytes()) + .map_err(|_| CryptoError::InvalidKey(None))?; + let modulus_length = private_key.modulus.as_bytes().len() * 8; + let public_exponent = private_key.public_exponent.as_bytes().to_vec(); + let key_data = pk_info + .private_key + .to_der() + .map_err(|_| CryptoError::InvalidKey(None))?; + Ok(super::RsaImportResult { + key_data, + modulus_length: modulus_length as u32, + public_exponent, + is_private: true, + }) + } + + fn export_rsa_public_key_pkcs1(&self, key_data: &[u8]) -> Result, CryptoError> { + // key_data is already PKCS1 DER + Ok(key_data.to_vec()) + } + + fn export_rsa_public_key_spki(&self, key_data: &[u8]) -> Result, CryptoError> { + use der::{Decode, Encode}; + let public_key = rsa::pkcs1::RsaPublicKey::from_der(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?; + let spki = spki::SubjectPublicKeyInfo { + algorithm: spki::AlgorithmIdentifier:: { + oid: const_oid::db::rfc5912::RSA_ENCRYPTION, + parameters: Some(der::asn1::Null.into()), + }, + subject_public_key: spki::der::asn1::BitString::from_bytes( + &public_key + .to_der() + .map_err(|_| CryptoError::InvalidKey(None))?, + ) + .map_err(|_| CryptoError::InvalidKey(None))?, + }; + spki.to_der().map_err(|_| CryptoError::InvalidKey(None)) + } + + fn export_rsa_private_key_pkcs8(&self, key_data: &[u8]) -> Result, CryptoError> { + let private_key = + RsaPrivateKey::from_pkcs1_der(key_data).map_err(|_| CryptoError::InvalidKey(None))?; + private_key + .to_pkcs8_der() + .map(|doc| doc.as_bytes().to_vec()) + .map_err(|_| CryptoError::InvalidKey(None)) + } + + fn import_ec_public_key_sec1( + &self, + data: &[u8], + _curve: EllipticCurve, + ) -> Result { + Ok(super::EcImportResult { + key_data: data.to_vec(), + is_private: false, + }) + } + + fn import_ec_public_key_spki(&self, der: &[u8]) -> Result { + let spki = spki::SubjectPublicKeyInfoRef::try_from(der) + .map_err(|_| CryptoError::InvalidKey(None))?; + Ok(super::EcImportResult { + key_data: spki.subject_public_key.raw_bytes().to_vec(), + is_private: false, + }) + } + + fn import_ec_private_key_pkcs8( + &self, + der: &[u8], + ) -> Result { + Ok(super::EcImportResult { + key_data: der.to_vec(), + is_private: true, + }) + } + + fn import_ec_private_key_sec1( + &self, + data: &[u8], + curve: EllipticCurve, + ) -> Result { + // Convert SEC1 private key to PKCS8 + let pkcs8_der = match curve { + EllipticCurve::P256 => { + let key = + P256SecretKey::from_slice(data).map_err(|_| CryptoError::InvalidKey(None))?; + key.to_pkcs8_der() + .map_err(|_| CryptoError::InvalidKey(None))? + .as_bytes() + .to_vec() + }, + EllipticCurve::P384 => { + let key = + P384SecretKey::from_slice(data).map_err(|_| CryptoError::InvalidKey(None))?; + key.to_pkcs8_der() + .map_err(|_| CryptoError::InvalidKey(None))? + .as_bytes() + .to_vec() + }, + EllipticCurve::P521 => { + let key = + P521SecretKey::from_slice(data).map_err(|_| CryptoError::InvalidKey(None))?; + key.to_pkcs8_der() + .map_err(|_| CryptoError::InvalidKey(None))? + .as_bytes() + .to_vec() + }, + }; + Ok(super::EcImportResult { + key_data: pkcs8_der, + is_private: true, + }) + } + + fn export_ec_public_key_sec1( + &self, + key_data: &[u8], + curve: EllipticCurve, + is_private: bool, + ) -> Result, CryptoError> { + use elliptic_curve::sec1::ToEncodedPoint; + if is_private { + // Extract public key from PKCS8 private key + match curve { + EllipticCurve::P256 => { + let key = P256SecretKey::from_pkcs8_der(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?; + Ok(key.public_key().to_encoded_point(false).as_bytes().to_vec()) + }, + EllipticCurve::P384 => { + let key = P384SecretKey::from_pkcs8_der(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?; + Ok(key.public_key().to_encoded_point(false).as_bytes().to_vec()) + }, + EllipticCurve::P521 => { + let key = P521SecretKey::from_pkcs8_der(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?; + Ok(key.public_key().to_encoded_point(false).as_bytes().to_vec()) + }, + } + } else { + // key_data is already SEC1 encoded + Ok(key_data.to_vec()) + } + } + + fn export_ec_public_key_spki( + &self, + key_data: &[u8], + curve: EllipticCurve, + ) -> Result, CryptoError> { + use der::Encode; + use elliptic_curve::pkcs8::AssociatedOid; + let curve_oid = match curve { + EllipticCurve::P256 => p256::NistP256::OID, + EllipticCurve::P384 => p384::NistP384::OID, + EllipticCurve::P521 => p521::NistP521::OID, + }; + let spki = spki::SubjectPublicKeyInfo { + algorithm: spki::AlgorithmIdentifier:: { + oid: elliptic_curve::ALGORITHM_OID, + parameters: Some(curve_oid), + }, + subject_public_key: spki::der::asn1::BitString::from_bytes(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?, + }; + spki.to_der().map_err(|_| CryptoError::InvalidKey(None)) + } + + fn export_ec_private_key_pkcs8( + &self, + key_data: &[u8], + _curve: EllipticCurve, + ) -> Result, CryptoError> { + // key_data is already PKCS8 + Ok(key_data.to_vec()) + } + + fn import_okp_public_key_raw( + &self, + data: &[u8], + ) -> Result { + if data.len() != 32 { + return Err(CryptoError::InvalidKey(None)); + } + Ok(super::OkpImportResult { + key_data: data.to_vec(), + is_private: false, + }) + } + + fn import_okp_public_key_spki( + &self, + der: &[u8], + _expected_oid: &[u8], + ) -> Result { + let spki = spki::SubjectPublicKeyInfoRef::try_from(der) + .map_err(|_| CryptoError::InvalidKey(None))?; + Ok(super::OkpImportResult { + key_data: spki.subject_public_key.raw_bytes().to_vec(), + is_private: false, + }) + } + + fn import_okp_private_key_pkcs8( + &self, + der: &[u8], + _expected_oid: &[u8], + ) -> Result { + Ok(super::OkpImportResult { + key_data: der.to_vec(), + is_private: true, + }) + } + + fn export_okp_public_key_raw( + &self, + key_data: &[u8], + is_private: bool, + ) -> Result, CryptoError> { + if is_private { + // Extract public key from PKCS8 - for X25519/Ed25519 + use der::Decode; + let pk_info = pkcs8::PrivateKeyInfoRef::from_der(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?; + // The private key is wrapped in an OCTET STRING, skip the tag+length (2 bytes) + let private_key_bytes = pk_info.private_key.as_bytes(); + let seed = if private_key_bytes.len() > 2 && private_key_bytes[0] == 0x04 { + &private_key_bytes[2..] + } else { + private_key_bytes + }; + let bytes: [u8; 32] = seed.try_into().map_err(|_| CryptoError::InvalidKey(None))?; + let secret = x25519_dalek::StaticSecret::from(bytes); + let public = x25519_dalek::PublicKey::from(&secret); + Ok(public.as_bytes().to_vec()) + } else { + Ok(key_data.to_vec()) + } + } + + fn export_okp_public_key_spki( + &self, + key_data: &[u8], + oid: &[u8], + ) -> Result, CryptoError> { + use der::Encode; + let oid = const_oid::ObjectIdentifier::from_bytes(oid) + .map_err(|_| CryptoError::InvalidKey(None))?; + let spki = spki::SubjectPublicKeyInfo { + algorithm: spki::AlgorithmIdentifierOwned { + oid, + parameters: None, + }, + subject_public_key: spki::der::asn1::BitString::from_bytes(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?, + }; + spki.to_der().map_err(|_| CryptoError::InvalidKey(None)) + } + + fn export_okp_private_key_pkcs8( + &self, + key_data: &[u8], + _oid: &[u8], + ) -> Result, CryptoError> { + // key_data is already PKCS8 + Ok(key_data.to_vec()) + } + + fn import_rsa_jwk( + &self, + jwk: super::RsaJwkImport<'_>, + ) -> Result { + use der::{asn1::UintRef, Encode}; + let modulus = UintRef::new(jwk.n).map_err(|_| CryptoError::InvalidKey(None))?; + let public_exponent = UintRef::new(jwk.e).map_err(|_| CryptoError::InvalidKey(None))?; + let modulus_length = (modulus.as_bytes().len() * 8) as u32; + let pub_exp_bytes = public_exponent.as_bytes().to_vec(); + + if let (Some(d), Some(p), Some(q), Some(dp), Some(dq), Some(qi)) = + (jwk.d, jwk.p, jwk.q, jwk.dp, jwk.dq, jwk.qi) + { + let private_key = rsa::pkcs1::RsaPrivateKey { + modulus, + public_exponent, + private_exponent: UintRef::new(d).map_err(|_| CryptoError::InvalidKey(None))?, + prime1: UintRef::new(p).map_err(|_| CryptoError::InvalidKey(None))?, + prime2: UintRef::new(q).map_err(|_| CryptoError::InvalidKey(None))?, + exponent1: UintRef::new(dp).map_err(|_| CryptoError::InvalidKey(None))?, + exponent2: UintRef::new(dq).map_err(|_| CryptoError::InvalidKey(None))?, + coefficient: UintRef::new(qi).map_err(|_| CryptoError::InvalidKey(None))?, + other_prime_infos: None, + }; + Ok(super::RsaImportResult { + key_data: private_key + .to_der() + .map_err(|_| CryptoError::InvalidKey(None))?, + modulus_length, + public_exponent: pub_exp_bytes, + is_private: true, + }) + } else { + let public_key = rsa::pkcs1::RsaPublicKey { + modulus, + public_exponent, + }; + Ok(super::RsaImportResult { + key_data: public_key + .to_der() + .map_err(|_| CryptoError::InvalidKey(None))?, + modulus_length, + public_exponent: pub_exp_bytes, + is_private: false, + }) + } + } + + fn export_rsa_jwk( + &self, + key_data: &[u8], + is_private: bool, + ) -> Result { + use der::Decode; + if is_private { + let key = rsa::pkcs1::RsaPrivateKey::from_der(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?; + Ok(super::RsaJwkExport { + n: key.modulus.as_bytes().to_vec(), + e: key.public_exponent.as_bytes().to_vec(), + d: Some(key.private_exponent.as_bytes().to_vec()), + p: Some(key.prime1.as_bytes().to_vec()), + q: Some(key.prime2.as_bytes().to_vec()), + dp: Some(key.exponent1.as_bytes().to_vec()), + dq: Some(key.exponent2.as_bytes().to_vec()), + qi: Some(key.coefficient.as_bytes().to_vec()), + }) + } else { + let key = rsa::pkcs1::RsaPublicKey::from_der(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?; + Ok(super::RsaJwkExport { + n: key.modulus.as_bytes().to_vec(), + e: key.public_exponent.as_bytes().to_vec(), + d: None, + p: None, + q: None, + dp: None, + dq: None, + qi: None, + }) + } + } + + fn import_ec_jwk( + &self, + jwk: super::EcJwkImport<'_>, + curve: EllipticCurve, + ) -> Result { + if let Some(d) = jwk.d { + // Private key - convert to PKCS8 + let pkcs8_der = match curve { + EllipticCurve::P256 => { + let key = + P256SecretKey::from_slice(d).map_err(|_| CryptoError::InvalidKey(None))?; + key.to_pkcs8_der() + .map_err(|_| CryptoError::InvalidKey(None))? + .as_bytes() + .to_vec() + }, + EllipticCurve::P384 => { + let key = + P384SecretKey::from_slice(d).map_err(|_| CryptoError::InvalidKey(None))?; + key.to_pkcs8_der() + .map_err(|_| CryptoError::InvalidKey(None))? + .as_bytes() + .to_vec() + }, + EllipticCurve::P521 => { + let key = + P521SecretKey::from_slice(d).map_err(|_| CryptoError::InvalidKey(None))?; + key.to_pkcs8_der() + .map_err(|_| CryptoError::InvalidKey(None))? + .as_bytes() + .to_vec() + }, + }; + Ok(super::EcImportResult { + key_data: pkcs8_der, + is_private: true, + }) + } else { + // Public key - encode as SEC1 uncompressed point + let mut point = Vec::with_capacity(1 + jwk.x.len() + jwk.y.len()); + point.push(0x04); // uncompressed + point.extend_from_slice(jwk.x); + point.extend_from_slice(jwk.y); + Ok(super::EcImportResult { + key_data: point, + is_private: false, + }) + } + } + + fn export_ec_jwk( + &self, + key_data: &[u8], + curve: EllipticCurve, + is_private: bool, + ) -> Result { + let coord_len = match curve { + EllipticCurve::P256 => 32, + EllipticCurve::P384 => 48, + EllipticCurve::P521 => 66, + }; + if is_private { + // key_data is PKCS8 + use der::Decode; + let pk_info = pkcs8::PrivateKeyInfoRef::from_der(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?; + // Get private key bytes (skip OCTET STRING wrapper if present) + let priv_bytes = pk_info.private_key.as_bytes(); + let d = if priv_bytes.len() > 2 && priv_bytes[0] == 0x04 { + &priv_bytes[2..] + } else { + priv_bytes + }; + // Derive public key to get x, y + let (x, y) = match curve { + EllipticCurve::P256 => { + let sk = + P256SecretKey::from_slice(d).map_err(|_| CryptoError::InvalidKey(None))?; + let pk = sk.public_key(); + let pt = pk.to_encoded_point(false); + (pt.x().unwrap().to_vec(), pt.y().unwrap().to_vec()) + }, + EllipticCurve::P384 => { + let sk = + P384SecretKey::from_slice(d).map_err(|_| CryptoError::InvalidKey(None))?; + let pk = sk.public_key(); + let pt = pk.to_encoded_point(false); + (pt.x().unwrap().to_vec(), pt.y().unwrap().to_vec()) + }, + EllipticCurve::P521 => { + let sk = + P521SecretKey::from_slice(d).map_err(|_| CryptoError::InvalidKey(None))?; + let pk = sk.public_key(); + let pt = pk.to_encoded_point(false); + (pt.x().unwrap().to_vec(), pt.y().unwrap().to_vec()) + }, + }; + Ok(super::EcJwkExport { + x, + y, + d: Some(d.to_vec()), + }) + } else { + // key_data is SEC1 uncompressed point (0x04 || x || y) + if key_data.len() != 1 + 2 * coord_len || key_data[0] != 0x04 { + return Err(CryptoError::InvalidKey(None)); + } + let x = key_data[1..1 + coord_len].to_vec(); + let y = key_data[1 + coord_len..].to_vec(); + Ok(super::EcJwkExport { x, y, d: None }) + } + } } // Helper struct for HKDF output length diff --git a/modules/llrt_crypto/src/sha_hash.rs b/modules/llrt_crypto/src/sha_hash.rs index 503436ed23..5450c6ca46 100644 --- a/modules/llrt_crypto/src/sha_hash.rs +++ b/modules/llrt_crypto/src/sha_hash.rs @@ -1,13 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 use llrt_utils::{ bytes::{bytes_to_typed_array, ObjectBytes}, iterable_enum, result::ResultExt, }; -use ring::{digest, hmac}; use rquickjs::{function::Opt, prelude::This, Class, Ctx, Result, Value}; use super::{encoded_bytes, CRYPTO_PROVIDER}; @@ -127,24 +124,25 @@ impl ShaAlgorithm { } } - // Keep Ring compatibility for subtle crypto - pub fn hmac_algorithm(&self) -> &'static hmac::Algorithm { + /// Returns the block size in bytes for this hash algorithm + pub fn block_len(&self) -> usize { match self { - ShaAlgorithm::MD5 => panic!("MD5 HMAC not supported by Ring"), - ShaAlgorithm::SHA1 => &hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, - ShaAlgorithm::SHA256 => &hmac::HMAC_SHA256, - ShaAlgorithm::SHA384 => &hmac::HMAC_SHA384, - ShaAlgorithm::SHA512 => &hmac::HMAC_SHA512, + ShaAlgorithm::MD5 => 64, + ShaAlgorithm::SHA1 => 64, + ShaAlgorithm::SHA256 => 64, + ShaAlgorithm::SHA384 => 128, + ShaAlgorithm::SHA512 => 128, } } - pub fn digest_algorithm(&self) -> &'static digest::Algorithm { + /// Returns the digest/output size in bytes for this hash algorithm + pub fn digest_len(&self) -> usize { match self { - ShaAlgorithm::MD5 => panic!("MD5 digest not supported by Ring"), - ShaAlgorithm::SHA1 => &digest::SHA1_FOR_LEGACY_USE_ONLY, - ShaAlgorithm::SHA256 => &digest::SHA256, - ShaAlgorithm::SHA384 => &digest::SHA384, - ShaAlgorithm::SHA512 => &digest::SHA512, + ShaAlgorithm::MD5 => 16, + ShaAlgorithm::SHA1 => 20, + ShaAlgorithm::SHA256 => 32, + ShaAlgorithm::SHA384 => 48, + ShaAlgorithm::SHA512 => 64, } } diff --git a/modules/llrt_crypto/src/subtle/generate_key.rs b/modules/llrt_crypto/src/subtle/generate_key.rs index 0fc1498c37..46973d4d32 100644 --- a/modules/llrt_crypto/src/subtle/generate_key.rs +++ b/modules/llrt_crypto/src/subtle/generate_key.rs @@ -122,10 +122,10 @@ fn generate_symmetric_key(_ctx: &Ctx<'_>, length: usize) -> Result> { #[allow(dead_code)] pub fn get_hash_length(ctx: &Ctx, hash: &ShaAlgorithm, length: u16) -> Result { if length == 0 { - return Ok(hash.hmac_algorithm().digest_algorithm().block_len()); + return Ok(hash.block_len()); } - if !length.is_multiple_of(8) || (length / 8) > ring::digest::MAX_BLOCK_LEN.try_into().unwrap() { + if !length.is_multiple_of(8) || (length / 8) as usize > 128 { return Err(Exception::throw_message(ctx, "Invalid HMAC key length")); } diff --git a/modules/llrt_crypto/src/subtle/key_algorithm.rs b/modules/llrt_crypto/src/subtle/key_algorithm.rs index c0808100b7..212254d7e3 100644 --- a/modules/llrt_crypto/src/subtle/key_algorithm.rs +++ b/modules/llrt_crypto/src/subtle/key_algorithm.rs @@ -4,24 +4,25 @@ use std::rc::Rc; -use der::{ - asn1::{OctetStringRef, UintRef}, - Decode, Encode, -}; +#[cfg(feature = "_subtle-full")] +use der::{asn1::OctetStringRef, Decode, Encode}; +#[cfg(feature = "_subtle-full")] use llrt_encoding::bytes_from_b64_url_safe; use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt, str_enum}; +#[cfg(feature = "_subtle-full")] use pkcs8::PrivateKeyInfoRef; use rquickjs::{ atom::PredefinedAtom, Array, Ctx, Exception, FromJs, Object, Result, TypedArray, Value, }; -use rsa::pkcs8::EncodePrivateKey; +#[cfg(feature = "_subtle-full")] use spki::{AlgorithmIdentifier, ObjectIdentifier}; use crate::sha_hash::ShaAlgorithm; +#[cfg(feature = "_subtle-full")] +use super::algorithm_mismatch_error; use super::{ - algorithm_mismatch_error, algorithm_not_supported_error, crypto_key::KeyKind, - to_name_and_maybe_object, EllipticCurve, + algorithm_not_supported_error, crypto_key::KeyKind, to_name_and_maybe_object, EllipticCurve, }; #[derive(Clone, Copy, PartialEq)] @@ -310,12 +311,22 @@ impl KeyAlgorithm { value: Value<'js>, usages: Array<'js>, ) -> Result { + // When _rustcrypto is not enabled, Import mode is not supported + #[cfg(not(feature = "_subtle-full"))] + if matches!(mode, KeyAlgorithmMode::Import { .. }) { + return Err(Exception::throw_message( + ctx, + "Key import is not supported with this crypto provider", + )); + } + let (name, obj) = to_name_and_maybe_object(ctx, value)?; let mut public_usages = vec![]; let mut private_usages = vec![]; let algorithm_name = name.as_str(); let algorithm = match algorithm_name { "Ed25519" => { + #[cfg(feature = "_subtle-full")] let key_kind = if let KeyAlgorithmMode::Import { format, kind, data } = mode { import_okp_key( ctx, @@ -329,6 +340,8 @@ impl KeyAlgorithm { } else { None }; + #[cfg(not(feature = "_subtle-full"))] + let key_kind: Option<&KeyKind> = None; KeyUsage::classify_and_check_usages( ctx, @@ -341,6 +354,7 @@ impl KeyAlgorithm { KeyAlgorithm::Ed25519 }, "X25519" => { + #[cfg(feature = "_subtle-full")] let key_kind = if let KeyAlgorithmMode::Import { format, kind, data } = mode { import_okp_key( ctx, @@ -354,6 +368,8 @@ impl KeyAlgorithm { } else { None }; + #[cfg(not(feature = "_subtle-full"))] + let key_kind: Option<&KeyKind> = None; KeyUsage::classify_and_check_usages( ctx, @@ -366,14 +382,24 @@ impl KeyAlgorithm { KeyAlgorithm::X25519 }, "AES-CBC" | "AES-CTR" | "AES-GCM" | "AES-KW" => { - let mut key_kind = None; - let length = if let KeyAlgorithmMode::Import { data, format, kind } = mode { - let l = import_symmetric_key(ctx, format, kind, data, algorithm_name, None)?; - key_kind = Some(kind); - l - } else { - obj.or_throw(ctx)?.get_required("length", "algorithm")? - } as u16; + #[cfg(feature = "_subtle-full")] + let (length, key_kind) = { + let mut key_kind = None; + let length = if let KeyAlgorithmMode::Import { data, format, kind } = mode { + let l = + import_symmetric_key(ctx, format, kind, data, algorithm_name, None)?; + key_kind = Some(kind); + l + } else { + obj.or_throw(ctx)?.get_required("length", "algorithm")? + } as u16; + (length, key_kind) + }; + #[cfg(not(feature = "_subtle-full"))] + let (length, key_kind): (u16, Option<&KeyKind>) = { + let length: u16 = obj.or_throw(ctx)?.get_required("length", "algorithm")?; + (length, None) + }; if !matches!(length, 128 | 192 | 256) { return Err(Exception::throw_message( @@ -427,8 +453,12 @@ impl KeyAlgorithm { let obj = obj.or_throw(ctx)?; let hash = extract_sha_hash(ctx, &obj)?; - let mut length = obj.get_optional("length")?.unwrap_or_default(); + #[cfg(feature = "_subtle-full")] + let mut length: u16 = obj.get_optional("length")?.unwrap_or_default(); + #[cfg(not(feature = "_subtle-full"))] + let length: u16 = obj.get_optional("length")?.unwrap_or_default(); + #[cfg(feature = "_subtle-full")] let key_kind = if let KeyAlgorithmMode::Import { data, format, kind } = mode { let data_length = import_symmetric_key(ctx, format, kind, data, algorithm_name, Some(&hash))?; @@ -439,6 +469,8 @@ impl KeyAlgorithm { } else { None }; + #[cfg(not(feature = "_subtle-full"))] + let key_kind: Option<&KeyKind> = None; KeyUsage::classify_and_check_usages( ctx, @@ -455,6 +487,7 @@ impl KeyAlgorithm { let obj = obj.or_throw(ctx)?; let hash = extract_sha_hash(ctx, &obj)?; + #[cfg(feature = "_subtle-full")] let (modulus_length, public_exponent, key_kind) = if let KeyAlgorithmMode::Import { format, kind, data } = mode { let (mod_length, exp) = @@ -474,6 +507,25 @@ impl KeyAlgorithm { (modulus_length, public_exponent, None) }; + #[cfg(not(feature = "_subtle-full"))] + let (modulus_length, public_exponent, key_kind): ( + u32, + Box<[u8]>, + Option<&KeyKind>, + ) = { + let modulus_length = obj.get_required("modulusLength", "algorithm")?; + let public_exponent: TypedArray = + obj.get_required("publicExponent", "algorithm")?; + let public_exponent = public_exponent + .as_bytes() + .ok_or_else(|| { + Exception::throw_message(ctx, "Array buffer has been detached") + })? + .to_owned() + .into_boxed_slice(); + (modulus_length, public_exponent, None) + }; + KeyUsage::classify_and_check_usages( ctx, if name == "RSA-OAEP" { @@ -496,7 +548,8 @@ impl KeyAlgorithm { } }, "HKDF" => { - let (algorithm, key_kind) = match mode { + let (algorithm, key_kind): (KeyAlgorithm, Option<&mut KeyKind>) = match mode { + #[cfg(feature = "_subtle-full")] KeyAlgorithmMode::Import { format, kind, data } => { import_derive_key(ctx, format, kind, data, algorithm_name)?; @@ -525,7 +578,8 @@ impl KeyAlgorithm { }, "PBKDF2" => { - let (algorithm, key_kind) = match mode { + let (algorithm, key_kind): (KeyAlgorithm, Option<&mut KeyKind>) = match mode { + #[cfg(feature = "_subtle-full")] KeyAlgorithmMode::Import { format, kind, data } => { import_derive_key(ctx, format, kind, data, algorithm_name)?; (KeyAlgorithm::Pbkdf2Import, Some(kind)) @@ -619,9 +673,9 @@ impl KeyAlgorithm { #[allow(clippy::too_many_arguments)] fn from_ec<'js>( ctx: &Ctx<'js>, - mode: KeyAlgorithmMode<'_, 'js>, + #[allow(unused_variables)] mode: KeyAlgorithmMode<'_, 'js>, obj: std::result::Result, &str>, - algorithm_name: &str, + #[allow(unused_variables)] algorithm_name: &str, algorithm: EcAlgorithm, key_usages: &Array<'js>, private_usages: &mut Vec, @@ -632,12 +686,15 @@ impl KeyAlgorithm { let curve_name: String = obj.get_required("namedCurve", "algorithm")?; let curve = EllipticCurve::try_from(curve_name.as_str()).or_throw(ctx)?; + #[cfg(feature = "_subtle-full")] let key_kind = if let KeyAlgorithmMode::Import { format, kind, data } = mode { import_ec_key(ctx, format, kind, data, algorithm_name, &curve, &curve_name)?; Some(kind) } else { None }; + #[cfg(not(feature = "_subtle-full"))] + let key_kind: Option<&KeyKind> = None; KeyUsage::classify_and_check_usages( ctx, @@ -652,6 +709,7 @@ impl KeyAlgorithm { } } +#[cfg(feature = "_subtle-full")] fn import_derive_key<'js>( ctx: &Ctx<'js>, format: KeyFormatData<'js>, @@ -672,6 +730,7 @@ fn import_derive_key<'js>( Ok(()) } +#[cfg(feature = "_subtle-full")] fn import_rsa_key<'js>( ctx: &Ctx<'js>, format: KeyFormatData<'js>, @@ -680,6 +739,11 @@ fn import_rsa_key<'js>( algorithm_name: &str, hash: &ShaAlgorithm, ) -> Result<(u32, Box<[u8]>)> { + use crate::{ + provider::{CryptoProvider, RsaJwkImport}, + CRYPTO_PROVIDER, + }; + let validate_oid = |other_oid: const_oid::ObjectIdentifier| -> Result<()> { if other_oid != const_oid::db::rfc5912::RSA_ENCRYPTION { return algorithm_mismatch_error(ctx, algorithm_name); @@ -687,26 +751,6 @@ fn import_rsa_key<'js>( Ok(()) }; - fn public_key_info( - ctx: &Ctx<'_>, - kind: &mut KeyKind, - data: &mut Vec, - public_key: rsa::pkcs1::RsaPublicKey<'_>, - ) -> Result<(usize, Vec)> { - *data = public_key.to_der().or_throw(ctx)?; - *kind = KeyKind::Public; - let modulus_length = public_key.modulus.as_bytes().len() * 8; - let public_exponent = public_key.public_exponent.as_bytes().to_vec(); - Ok((modulus_length, public_exponent)) - } - - macro_rules! uint_ref_from_b64 { - ($name:ident,$ctx:expr,$bytes:expr) => { - let bytes = bytes_from_b64_url_safe($bytes).or_throw($ctx)?; - let $name = UintRef::new(&bytes).or_throw($ctx)?; - }; - } - let (modulus_length, public_exponent) = match format { KeyFormatData::Jwk(object) => { let kty: String = object.get_required("kty", "keyData")?; @@ -742,81 +786,84 @@ fn import_rsa_key<'js>( let n: String = object.get_required("n", "keyData")?; let e: String = object.get_required("e", "keyData")?; + let n_bytes = bytes_from_b64_url_safe(n.as_bytes()).or_throw(ctx)?; + let e_bytes = bytes_from_b64_url_safe(e.as_bytes()).or_throw(ctx)?; - uint_ref_from_b64!(modulus, ctx, n.as_bytes()); - uint_ref_from_b64!(public_exponent, ctx, e.as_bytes()); - - if let Some(d) = object.get_optional::<_, String>("d")? { + let result = if let Some(d) = object.get_optional::<_, String>("d")? { let p: String = object.get_required("p", "keyData")?; let q: String = object.get_required("q", "keyData")?; let dp: String = object.get_required("dp", "keyData")?; let dq: String = object.get_required("dq", "keyData")?; let qi: String = object.get_required("qi", "keyData")?; - uint_ref_from_b64!(private_exponent, ctx, d.as_bytes()); - uint_ref_from_b64!(prime1, ctx, p.as_bytes()); - uint_ref_from_b64!(prime2, ctx, q.as_bytes()); - uint_ref_from_b64!(exponent1, ctx, dp.as_bytes()); - uint_ref_from_b64!(exponent2, ctx, dq.as_bytes()); - uint_ref_from_b64!(coefficient, ctx, qi.as_bytes()); - - let modulus_length = modulus.as_bytes().len() * 8; - - let private_key = rsa::pkcs1::RsaPrivateKey { - modulus, - public_exponent, - private_exponent, - prime1, - prime2, - exponent1, - exponent2, - coefficient, - other_prime_infos: None, + let d_bytes = bytes_from_b64_url_safe(d.as_bytes()).or_throw(ctx)?; + let p_bytes = bytes_from_b64_url_safe(p.as_bytes()).or_throw(ctx)?; + let q_bytes = bytes_from_b64_url_safe(q.as_bytes()).or_throw(ctx)?; + let dp_bytes = bytes_from_b64_url_safe(dp.as_bytes()).or_throw(ctx)?; + let dq_bytes = bytes_from_b64_url_safe(dq.as_bytes()).or_throw(ctx)?; + let qi_bytes = bytes_from_b64_url_safe(qi.as_bytes()).or_throw(ctx)?; + + let jwk = RsaJwkImport { + n: &n_bytes, + e: &e_bytes, + d: Some(&d_bytes), + p: Some(&p_bytes), + q: Some(&q_bytes), + dp: Some(&dp_bytes), + dq: Some(&dq_bytes), + qi: Some(&qi_bytes), }; - - *data = private_key.to_der().or_throw(ctx)?; - *kind = KeyKind::Private; - (modulus_length, public_exponent.as_bytes().to_vec()) + CRYPTO_PROVIDER.import_rsa_jwk(jwk).or_throw(ctx)? } else { - let public_key = rsa::pkcs1::RsaPublicKey { - modulus, - public_exponent, + let jwk = RsaJwkImport { + n: &n_bytes, + e: &e_bytes, + d: None, + p: None, + q: None, + dp: None, + dq: None, + qi: None, }; - public_key_info(ctx, kind, data, public_key)? - } + CRYPTO_PROVIDER.import_rsa_jwk(jwk).or_throw(ctx)? + }; + + *data = result.key_data; + *kind = if result.is_private { + KeyKind::Private + } else { + KeyKind::Public + }; + (result.modulus_length as usize, result.public_exponent) }, KeyFormatData::Raw(object_bytes) => { - let public_key = - rsa::pkcs1::RsaPublicKey::from_der(object_bytes.as_bytes(ctx)?).or_throw(ctx)?; - public_key_info(ctx, kind, data, public_key)? + let result = CRYPTO_PROVIDER + .import_rsa_public_key_pkcs1(object_bytes.as_bytes(ctx)?) + .or_throw(ctx)?; + *data = result.key_data; + *kind = KeyKind::Public; + (result.modulus_length as usize, result.public_exponent) }, KeyFormatData::Pkcs8(object_bytes) => { let pk_info = PrivateKeyInfoRef::from_der(object_bytes.as_bytes(ctx)?).or_throw(ctx)?; - let object_identifier = pk_info.algorithm.oid; - validate_oid(object_identifier)?; - - let private_key = rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key.as_bytes()) + validate_oid(pk_info.algorithm.oid)?; + let result = CRYPTO_PROVIDER + .import_rsa_private_key_pkcs8(object_bytes.as_bytes(ctx)?) .or_throw(ctx)?; - - let public_exponent = private_key.public_exponent.as_bytes().to_vec(); - let modulus_length = private_key.modulus.as_bytes().len() * 8; - *data = pk_info.private_key.to_der().or_throw(ctx)?; + *data = result.key_data; *kind = KeyKind::Private; - - (modulus_length, public_exponent) + (result.modulus_length as usize, result.public_exponent) }, KeyFormatData::Spki(object_bytes) => { let pk_info = spki::SubjectPublicKeyInfoRef::try_from(object_bytes.as_bytes(ctx)?) .or_throw(ctx)?; - - let object_identifier = pk_info.algorithm.oid; - validate_oid(object_identifier)?; - - let public_key = - rsa::pkcs1::RsaPublicKey::from_der(pk_info.subject_public_key.raw_bytes()) - .or_throw(ctx)?; - - public_key_info(ctx, kind, data, public_key)? + validate_oid(pk_info.algorithm.oid)?; + let result = CRYPTO_PROVIDER + .import_rsa_public_key_spki(object_bytes.as_bytes(ctx)?) + .or_throw(ctx)?; + *data = result.key_data; + *kind = KeyKind::Public; + (result.modulus_length as usize, result.public_exponent) }, }; @@ -824,6 +871,7 @@ fn import_rsa_key<'js>( Ok((modulus_length as u32, public_exponent)) } +#[cfg(feature = "_subtle-full")] fn import_symmetric_key<'js>( ctx: &Ctx<'js>, format: KeyFormatData<'js>, @@ -878,6 +926,12 @@ fn import_symmetric_key<'js>( algorithm_mismatch_error(ctx, algorithm_name) } +// EC algorithm OID for validation +#[cfg(feature = "_subtle-full")] +const EC_ALGORITHM_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); + +#[cfg(feature = "_subtle-full")] fn import_ec_key<'js>( ctx: &Ctx<'js>, format: KeyFormatData<'js>, @@ -887,59 +941,24 @@ fn import_ec_key<'js>( curve: &EllipticCurve, curve_name: &str, ) -> Result<()> { + use crate::{ + provider::{CryptoProvider, EcJwkImport}, + CRYPTO_PROVIDER, + }; + let validate_oid = |other_oid: const_oid::ObjectIdentifier| -> Result<()> { - if other_oid != elliptic_curve::ALGORITHM_OID { + if other_oid != EC_ALGORITHM_OID { return algorithm_mismatch_error(ctx, algorithm_name); } Ok(()) }; - fn decode_to_curve( - ctx: &Ctx<'_>, - value: &str, - ) -> Result> { - let value_bytes = value.as_bytes(); - - let mut field_bytes = elliptic_curve::FieldBytes::::default(); - let mut bytes = bytes_from_b64_url_safe(value_bytes).or_throw(ctx)?; - if bytes.len() < field_bytes.len() { - bytes.resize(field_bytes.len() - bytes.len(), 0); - } - - field_bytes.copy_from_slice(&bytes); - - Ok(field_bytes) - } - - fn decode_jwk_to_ec_point_bytes( - ctx: &Ctx<'_>, - curve: &EllipticCurve, - x: &str, - y: &str, - ) -> Result> { - let point_bytes = match curve { - EllipticCurve::P256 => { - let x = decode_to_curve::(ctx, x)?; - let y = decode_to_curve::(ctx, y)?; - - p256::EncodedPoint::from_affine_coordinates(&x, &y, false).to_bytes() - }, - EllipticCurve::P384 => { - let x = decode_to_curve::(ctx, x)?; - let y = decode_to_curve::(ctx, y)?; - - p384::EncodedPoint::from_affine_coordinates(&x, &y, false).to_bytes() - }, - EllipticCurve::P521 => { - let x = decode_to_curve::(ctx, x)?; - let y = decode_to_curve::(ctx, y)?; - - p521::EncodedPoint::from_affine_coordinates(&x, &y, false).to_bytes() - }, - }; - - Ok(point_bytes.to_vec()) - } + // Get expected coordinate length for the curve + let coord_len = match curve { + EllipticCurve::P256 => 32, + EllipticCurve::P384 => 48, + EllipticCurve::P521 => 66, + }; match format { KeyFormatData::Jwk(object) => { @@ -956,64 +975,81 @@ fn import_ec_key<'js>( )); } - if let Some(d) = object.get_optional::<_, String>("d")? { - let private_key = match curve { - EllipticCurve::P256 => { - let d = decode_to_curve::(ctx, &d)?; - let key = p256::SecretKey::from_bytes(&d).or_throw(ctx)?; - key.to_pkcs8_der().or_throw(ctx)? - }, - EllipticCurve::P384 => { - let d = decode_to_curve::(ctx, &d)?; - let key = p384::SecretKey::from_bytes(&d).or_throw(ctx)?; - key.to_pkcs8_der().or_throw(ctx)? - }, - EllipticCurve::P521 => { - let d = decode_to_curve::(ctx, &d)?; - let key = p521::SecretKey::from_bytes(&d).or_throw(ctx)?; - key.to_pkcs8_der().or_throw(ctx)? - }, - }; + let x: String = object.get_required("x", "keyData")?; + let y: String = object.get_required("y", "keyData")?; + let mut x_bytes = bytes_from_b64_url_safe(x.as_bytes()).or_throw(ctx)?; + let mut y_bytes = bytes_from_b64_url_safe(y.as_bytes()).or_throw(ctx)?; + + // Pad to coordinate length if needed + if x_bytes.len() < coord_len { + let mut padded = vec![0u8; coord_len - x_bytes.len()]; + padded.extend_from_slice(&x_bytes); + x_bytes = padded; + } + if y_bytes.len() < coord_len { + let mut padded = vec![0u8; coord_len - y_bytes.len()]; + padded.extend_from_slice(&y_bytes); + y_bytes = padded; + } - *data = private_key.as_bytes().to_vec(); - *kind = KeyKind::Private; + let d_bytes = if let Some(d) = object.get_optional::<_, String>("d")? { + let mut d_bytes = bytes_from_b64_url_safe(d.as_bytes()).or_throw(ctx)?; + if d_bytes.len() < coord_len { + let mut padded = vec![0u8; coord_len - d_bytes.len()]; + padded.extend_from_slice(&d_bytes); + d_bytes = padded; + } + Some(d_bytes) } else { - *kind = KeyKind::Public; - let x: String = object.get_required("x", "keyData")?; - let y: String = object.get_required("y", "keyData")?; + None + }; - let point_bytes = decode_jwk_to_ec_point_bytes(ctx, curve, &x, &y)?; - *data = point_bytes; - } + let jwk = EcJwkImport { + x: &x_bytes, + y: &y_bytes, + d: d_bytes.as_deref(), + }; + + let result = CRYPTO_PROVIDER.import_ec_jwk(jwk, *curve).or_throw(ctx)?; + *data = result.key_data; + *kind = if result.is_private { + KeyKind::Private + } else { + KeyKind::Public + }; }, KeyFormatData::Raw(object_bytes) => { - let bytes = object_bytes.into_bytes(ctx)?; - if bytes.len() != 32 { - return Err(Exception::throw_type( - ctx, - &[algorithm_name, " keys must be 32 bytes long"].concat(), - )); - } - *data = bytes; + let bytes = object_bytes.as_bytes(ctx)?; + let result = CRYPTO_PROVIDER + .import_ec_public_key_sec1(bytes, *curve) + .or_throw(ctx)?; + *data = result.key_data; *kind = KeyKind::Public; }, KeyFormatData::Spki(object_bytes) => { let spki = spki::SubjectPublicKeyInfoRef::try_from(object_bytes.as_bytes(ctx)?) .or_throw(ctx)?; validate_oid(spki.algorithm.oid)?; - *data = spki.subject_public_key.raw_bytes().into(); + let result = CRYPTO_PROVIDER + .import_ec_public_key_spki(object_bytes.as_bytes(ctx)?) + .or_throw(ctx)?; + *data = result.key_data; *kind = KeyKind::Public; }, KeyFormatData::Pkcs8(object_bytes) => { let pkcs8 = PrivateKeyInfoRef::try_from(object_bytes.as_bytes(ctx)?).or_throw(ctx)?; validate_oid(pkcs8.algorithm.oid)?; - *data = object_bytes.into_bytes(ctx)?; + let result = CRYPTO_PROVIDER + .import_ec_private_key_pkcs8(object_bytes.as_bytes(ctx)?) + .or_throw(ctx)?; + *data = result.key_data; *kind = KeyKind::Private; }, }; Ok(()) } +#[cfg(feature = "_subtle-full")] fn import_okp_key<'js>( ctx: &Ctx<'js>, format: KeyFormatData<'js>, @@ -1105,6 +1141,7 @@ fn create_hash_object<'js>(ctx: &Ctx<'js>, hash: &ShaAlgorithm) -> Result(ctx: &Ctx<'_>, hash: &ShaAlgorithm) -> Result { Err(Exception::throw_message( ctx, diff --git a/modules/llrt_crypto/src/subtle/mod.rs b/modules/llrt_crypto/src/subtle/mod.rs index bf6ae5e204..0d84f17908 100644 --- a/modules/llrt_crypto/src/subtle/mod.rs +++ b/modules/llrt_crypto/src/subtle/mod.rs @@ -6,13 +6,17 @@ mod derive_algorithm; mod digest; mod encryption; mod encryption_algorithm; +#[cfg(feature = "_rustcrypto")] mod export_key; mod generate_key; +#[cfg(feature = "_rustcrypto")] mod import_key; +#[cfg(feature = "_rustcrypto")] mod key_algorithm; mod sign; mod sign_algorithm; mod verify; +#[cfg(feature = "_rustcrypto")] mod wrapping; pub use crypto_key::CryptoKey; @@ -21,35 +25,31 @@ pub use derive::subtle_derive_key; pub use digest::subtle_digest; pub use encryption::subtle_decrypt; pub use encryption::subtle_encrypt; +#[cfg(feature = "_rustcrypto")] pub use export_key::subtle_export_key; pub use generate_key::subtle_generate_key; +#[cfg(feature = "_rustcrypto")] pub use import_key::subtle_import_key; +#[cfg(feature = "_rustcrypto")] use key_algorithm::KeyAlgorithm; -use ring::digest::Digest; pub use sign::subtle_sign; pub use verify::subtle_verify; +#[cfg(feature = "_rustcrypto")] pub use wrapping::subtle_unwrap_key; +#[cfg(feature = "_rustcrypto")] pub use wrapping::subtle_wrap_key; -use aes::cipher::BlockModeDecrypt; -use aes::cipher::BlockModeEncrypt; - -use aes::{ - cipher::{ - block_padding::{Error as PaddingError, Pkcs7}, - consts::{U12, U13, U14, U15, U16}, - InvalidLength, KeyIvInit, StreamCipher, StreamCipherError, - }, - Aes128, Aes192, Aes256, -}; -use aes_gcm::{ - aead::{Aead, Payload}, - AesGcm, KeyInit, Nonce, -}; -use ctr::{Ctr128BE, Ctr32BE, Ctr64BE}; +// Stub implementations for limited crypto providers +#[cfg(not(feature = "_rustcrypto"))] +mod key_algorithm; +#[cfg(not(feature = "_rustcrypto"))] +use key_algorithm::KeyAlgorithm; + use llrt_utils::{object::ObjectExt, str_enum}; use rquickjs::{atom::PredefinedAtom, Ctx, Exception, Object, Result, Value}; +use crate::provider::{CryptoProvider, SimpleDigest}; + use crate::sha_hash::ShaAlgorithm; #[rquickjs::class] @@ -69,244 +69,276 @@ impl SubtleCrypto { } } -#[allow(dead_code)] -pub enum AesCbcEncVariant { - Aes128(cbc::Encryptor), - Aes192(cbc::Encryptor), - Aes256(cbc::Encryptor), -} +// AES variant types - only available with full subtle crypto support +#[cfg(feature = "_rustcrypto")] +mod aes_variants { + use aes::cipher::BlockModeDecrypt; + use aes::cipher::BlockModeEncrypt; + + use aes::{ + cipher::{ + block_padding::{Error as PaddingError, Pkcs7}, + consts::{U12, U13, U14, U15, U16}, + InvalidLength, KeyIvInit, StreamCipher, StreamCipherError, + }, + Aes128, Aes192, Aes256, + }; + use aes_gcm::{ + aead::{Aead, Payload}, + AesGcm, KeyInit, Nonce, + }; + use ctr::{Ctr128BE, Ctr32BE, Ctr64BE}; -#[allow(dead_code)] -impl AesCbcEncVariant { - pub fn new(key_len: u16, key: &[u8], iv: &[u8]) -> std::result::Result { - let variant: AesCbcEncVariant = match key_len { - 128 => Self::Aes128(cbc::Encryptor::new_from_slices(key, iv)?), - 192 => Self::Aes192(cbc::Encryptor::new_from_slices(key, iv)?), - 256 => Self::Aes256(cbc::Encryptor::new_from_slices(key, iv)?), - _ => return Err(InvalidLength), - }; - - Ok(variant) + #[allow(dead_code)] + pub enum AesCbcEncVariant { + Aes128(cbc::Encryptor), + Aes192(cbc::Encryptor), + Aes256(cbc::Encryptor), } - pub fn encrypt(self, data: &[u8]) -> Vec { - match self { - Self::Aes128(v) => v.encrypt_padded_vec::(data), - Self::Aes192(v) => v.encrypt_padded_vec::(data), - Self::Aes256(v) => v.encrypt_padded_vec::(data), + #[allow(dead_code)] + impl AesCbcEncVariant { + pub fn new( + key_len: u16, + key: &[u8], + iv: &[u8], + ) -> std::result::Result { + let variant: AesCbcEncVariant = match key_len { + 128 => Self::Aes128(cbc::Encryptor::new_from_slices(key, iv)?), + 192 => Self::Aes192(cbc::Encryptor::new_from_slices(key, iv)?), + 256 => Self::Aes256(cbc::Encryptor::new_from_slices(key, iv)?), + _ => return Err(InvalidLength), + }; + + Ok(variant) } - } -} - -#[allow(dead_code)] -pub enum AesCbcDecVariant { - Aes128(cbc::Decryptor), - Aes192(cbc::Decryptor), - Aes256(cbc::Decryptor), -} -#[allow(dead_code)] -impl AesCbcDecVariant { - pub fn new(key_len: u16, key: &[u8], iv: &[u8]) -> std::result::Result { - let variant: AesCbcDecVariant = match key_len { - 128 => Self::Aes128(cbc::Decryptor::new_from_slices(key, iv)?), - 192 => Self::Aes192(cbc::Decryptor::new_from_slices(key, iv)?), - 256 => Self::Aes256(cbc::Decryptor::new_from_slices(key, iv)?), - _ => return Err(InvalidLength), - }; - - Ok(variant) + pub fn encrypt(self, data: &[u8]) -> Vec { + match self { + Self::Aes128(v) => v.encrypt_padded_vec::(data), + Self::Aes192(v) => v.encrypt_padded_vec::(data), + Self::Aes256(v) => v.encrypt_padded_vec::(data), + } + } } - pub fn decrypt(self, data: &[u8]) -> std::result::Result, PaddingError> { - Ok(match self { - Self::Aes128(v) => v.decrypt_padded_vec::(data)?, - Self::Aes192(v) => v.decrypt_padded_vec::(data)?, - Self::Aes256(v) => v.decrypt_padded_vec::(data)?, - }) + #[allow(dead_code)] + pub enum AesCbcDecVariant { + Aes128(cbc::Decryptor), + Aes192(cbc::Decryptor), + Aes256(cbc::Decryptor), } -} -#[allow(dead_code)] -pub enum AesCtrVariant { - Aes128Ctr32(Ctr32BE), - Aes128Ctr64(Ctr64BE), - Aes128Ctr128(Ctr128BE), - Aes192Ctr32(Ctr32BE), - Aes192Ctr64(Ctr64BE), - Aes192Ctr128(Ctr128BE), - Aes256Ctr32(Ctr32BE), - Aes256Ctr64(Ctr64BE), - Aes256Ctr128(Ctr128BE), -} + #[allow(dead_code)] + impl AesCbcDecVariant { + pub fn new( + key_len: u16, + key: &[u8], + iv: &[u8], + ) -> std::result::Result { + let variant: AesCbcDecVariant = match key_len { + 128 => Self::Aes128(cbc::Decryptor::new_from_slices(key, iv)?), + 192 => Self::Aes192(cbc::Decryptor::new_from_slices(key, iv)?), + 256 => Self::Aes256(cbc::Decryptor::new_from_slices(key, iv)?), + _ => return Err(InvalidLength), + }; + + Ok(variant) + } -#[allow(dead_code)] -impl AesCtrVariant { - pub fn new( - key_len: u16, - encryption_length: u32, - key: &[u8], - counter: &[u8], - ) -> std::result::Result { - let variant: AesCtrVariant = match (key_len, encryption_length) { - (128, 32) => Self::Aes128Ctr32(Ctr32BE::new_from_slices(key, counter)?), - (128, 64) => Self::Aes128Ctr64(Ctr64BE::new_from_slices(key, counter)?), - (128, 128) => Self::Aes128Ctr128(Ctr128BE::new_from_slices(key, counter)?), - (192, 32) => Self::Aes192Ctr32(Ctr32BE::new_from_slices(key, counter)?), - (192, 64) => Self::Aes192Ctr64(Ctr64BE::new_from_slices(key, counter)?), - (192, 128) => Self::Aes192Ctr128(Ctr128BE::new_from_slices(key, counter)?), - (256, 32) => Self::Aes256Ctr32(Ctr32BE::new_from_slices(key, counter)?), - (256, 64) => Self::Aes256Ctr64(Ctr64BE::new_from_slices(key, counter)?), - (256, 128) => Self::Aes256Ctr128(Ctr128BE::new_from_slices(key, counter)?), - _ => return Err(InvalidLength), - }; - - Ok(variant) + pub fn decrypt(self, data: &[u8]) -> std::result::Result, PaddingError> { + Ok(match self { + Self::Aes128(v) => v.decrypt_padded_vec::(data)?, + Self::Aes192(v) => v.decrypt_padded_vec::(data)?, + Self::Aes256(v) => v.decrypt_padded_vec::(data)?, + }) + } } - pub fn encrypt(&mut self, data: &[u8]) -> std::result::Result, StreamCipherError> { - let mut ciphertext = data.to_vec(); - match self { - Self::Aes128Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes128Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes128Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes192Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes192Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes192Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes256Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes256Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes256Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, - } - Ok(ciphertext) + #[allow(dead_code)] + pub enum AesCtrVariant { + Aes128Ctr32(Ctr32BE), + Aes128Ctr64(Ctr64BE), + Aes128Ctr128(Ctr128BE), + Aes192Ctr32(Ctr32BE), + Aes192Ctr64(Ctr64BE), + Aes192Ctr128(Ctr128BE), + Aes256Ctr32(Ctr32BE), + Aes256Ctr64(Ctr64BE), + Aes256Ctr128(Ctr128BE), } - pub fn decrypt(&mut self, data: &[u8]) -> std::result::Result, StreamCipherError> { - let mut ciphertext = data.to_vec(); - match self { - Self::Aes128Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes128Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes128Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes192Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes192Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes192Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes256Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes256Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes256Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, + #[allow(dead_code)] + impl AesCtrVariant { + pub fn new( + key_len: u16, + encryption_length: u32, + key: &[u8], + counter: &[u8], + ) -> std::result::Result { + let variant: AesCtrVariant = match (key_len, encryption_length) { + (128, 32) => Self::Aes128Ctr32(Ctr32BE::new_from_slices(key, counter)?), + (128, 64) => Self::Aes128Ctr64(Ctr64BE::new_from_slices(key, counter)?), + (128, 128) => Self::Aes128Ctr128(Ctr128BE::new_from_slices(key, counter)?), + (192, 32) => Self::Aes192Ctr32(Ctr32BE::new_from_slices(key, counter)?), + (192, 64) => Self::Aes192Ctr64(Ctr64BE::new_from_slices(key, counter)?), + (192, 128) => Self::Aes192Ctr128(Ctr128BE::new_from_slices(key, counter)?), + (256, 32) => Self::Aes256Ctr32(Ctr32BE::new_from_slices(key, counter)?), + (256, 64) => Self::Aes256Ctr64(Ctr64BE::new_from_slices(key, counter)?), + (256, 128) => Self::Aes256Ctr128(Ctr128BE::new_from_slices(key, counter)?), + _ => return Err(InvalidLength), + }; + + Ok(variant) } - Ok(ciphertext) - } -} -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), -} + pub fn encrypt(&mut self, data: &[u8]) -> std::result::Result, StreamCipherError> { + let mut ciphertext = data.to_vec(); + match self { + Self::Aes128Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes128Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes128Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes192Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes192Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes192Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes256Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes256Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes256Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, + } + Ok(ciphertext) + } + + pub fn decrypt(&mut self, data: &[u8]) -> std::result::Result, StreamCipherError> { + let mut ciphertext = data.to_vec(); + match self { + Self::Aes128Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes128Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes128Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes192Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes192Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes192Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes256Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes256Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes256Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, + } + Ok(ciphertext) + } + } -#[allow(dead_code)] -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 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), } - 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(), - }; - let nonce: &ctr::cipher::Array<_, _> = - &Nonce::::try_from(nonce).map_err(|_| aes_gcm::Error)?; - match self { - Self::Aes128Gcm96(v) => v.encrypt(nonce, plaintext), - Self::Aes192Gcm96(v) => v.encrypt(nonce, plaintext), - Self::Aes256Gcm96(v) => v.encrypt(nonce, plaintext), - Self::Aes128Gcm104(v) => v.encrypt(nonce, plaintext), - Self::Aes192Gcm104(v) => v.encrypt(nonce, plaintext), - Self::Aes256Gcm104(v) => v.encrypt(nonce, plaintext), - Self::Aes128Gcm112(v) => v.encrypt(nonce, plaintext), - Self::Aes192Gcm112(v) => v.encrypt(nonce, plaintext), - Self::Aes256Gcm112(v) => v.encrypt(nonce, plaintext), - Self::Aes128Gcm120(v) => v.encrypt(nonce, plaintext), - Self::Aes192Gcm120(v) => v.encrypt(nonce, plaintext), - Self::Aes256Gcm120(v) => v.encrypt(nonce, plaintext), - Self::Aes128Gcm128(v) => v.encrypt(nonce, plaintext), - Self::Aes192Gcm128(v) => v.encrypt(nonce, plaintext), - Self::Aes256Gcm128(v) => v.encrypt(nonce, plaintext), + #[allow(dead_code)] + 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 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(), - }; - let nonce: &ctr::cipher::Array<_, _> = - &Nonce::::try_from(nonce).map_err(|_| aes_gcm::Error)?; - match self { - Self::Aes128Gcm96(v) => v.decrypt(nonce, ciphertext), - Self::Aes192Gcm96(v) => v.decrypt(nonce, ciphertext), - Self::Aes256Gcm96(v) => v.decrypt(nonce, ciphertext), - Self::Aes128Gcm104(v) => v.decrypt(nonce, ciphertext), - Self::Aes192Gcm104(v) => v.decrypt(nonce, ciphertext), - Self::Aes256Gcm104(v) => v.decrypt(nonce, ciphertext), - Self::Aes128Gcm112(v) => v.decrypt(nonce, ciphertext), - Self::Aes192Gcm112(v) => v.decrypt(nonce, ciphertext), - Self::Aes256Gcm112(v) => v.decrypt(nonce, ciphertext), - Self::Aes128Gcm120(v) => v.decrypt(nonce, ciphertext), - Self::Aes192Gcm120(v) => v.decrypt(nonce, ciphertext), - Self::Aes256Gcm120(v) => v.decrypt(nonce, ciphertext), - Self::Aes128Gcm128(v) => v.decrypt(nonce, ciphertext), - Self::Aes192Gcm128(v) => v.decrypt(nonce, ciphertext), - Self::Aes256Gcm128(v) => v.decrypt(nonce, ciphertext), + 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(), + }; + let nonce: &ctr::cipher::Array<_, _> = + &Nonce::::try_from(nonce).map_err(|_| aes_gcm::Error)?; + match self { + Self::Aes128Gcm96(v) => v.encrypt(nonce, plaintext), + Self::Aes192Gcm96(v) => v.encrypt(nonce, plaintext), + Self::Aes256Gcm96(v) => v.encrypt(nonce, plaintext), + Self::Aes128Gcm104(v) => v.encrypt(nonce, plaintext), + Self::Aes192Gcm104(v) => v.encrypt(nonce, plaintext), + Self::Aes256Gcm104(v) => v.encrypt(nonce, plaintext), + Self::Aes128Gcm112(v) => v.encrypt(nonce, plaintext), + Self::Aes192Gcm112(v) => v.encrypt(nonce, plaintext), + Self::Aes256Gcm112(v) => v.encrypt(nonce, plaintext), + Self::Aes128Gcm120(v) => v.encrypt(nonce, plaintext), + Self::Aes192Gcm120(v) => v.encrypt(nonce, plaintext), + Self::Aes256Gcm120(v) => v.encrypt(nonce, plaintext), + Self::Aes128Gcm128(v) => v.encrypt(nonce, plaintext), + Self::Aes192Gcm128(v) => v.encrypt(nonce, plaintext), + Self::Aes256Gcm128(v) => v.encrypt(nonce, 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(), + }; + let nonce: &ctr::cipher::Array<_, _> = + &Nonce::::try_from(nonce).map_err(|_| aes_gcm::Error)?; + match self { + Self::Aes128Gcm96(v) => v.decrypt(nonce, ciphertext), + Self::Aes192Gcm96(v) => v.decrypt(nonce, ciphertext), + Self::Aes256Gcm96(v) => v.decrypt(nonce, ciphertext), + Self::Aes128Gcm104(v) => v.decrypt(nonce, ciphertext), + Self::Aes192Gcm104(v) => v.decrypt(nonce, ciphertext), + Self::Aes256Gcm104(v) => v.decrypt(nonce, ciphertext), + Self::Aes128Gcm112(v) => v.decrypt(nonce, ciphertext), + Self::Aes192Gcm112(v) => v.decrypt(nonce, ciphertext), + Self::Aes256Gcm112(v) => v.decrypt(nonce, ciphertext), + Self::Aes128Gcm120(v) => v.decrypt(nonce, ciphertext), + Self::Aes192Gcm120(v) => v.decrypt(nonce, ciphertext), + Self::Aes256Gcm120(v) => v.decrypt(nonce, ciphertext), + Self::Aes128Gcm128(v) => v.decrypt(nonce, ciphertext), + Self::Aes192Gcm128(v) => v.decrypt(nonce, ciphertext), + Self::Aes256Gcm128(v) => v.decrypt(nonce, ciphertext), + } } } } +#[cfg(feature = "_rustcrypto")] +pub use aes_variants::*; + #[derive(Debug, Clone, Copy, PartialEq)] pub enum EllipticCurve { P256, @@ -318,6 +350,7 @@ str_enum!(EllipticCurve,P256 => "P-256", P384 => "P-384", P521 => "P-521"); pub enum EncryptionMode { Encryption, + #[allow(dead_code)] Wrapping(u8), //padding byte } @@ -326,7 +359,7 @@ pub fn rsa_hash_digest<'a>( key: &'a CryptoKey, data: &'a [u8], algorithm_name: &str, -) -> Result<(&'a ShaAlgorithm, Digest)> { +) -> Result<(&'a ShaAlgorithm, Vec)> { let hash = match &key.algorithm { KeyAlgorithm::Rsa { hash, .. } => hash, _ => return algorithm_mismatch_error(ctx, algorithm_name), @@ -341,7 +374,9 @@ pub fn rsa_hash_digest<'a>( )); } - let digest = ring::digest::digest(hash.digest_algorithm(), data); + let mut hasher = crate::CRYPTO_PROVIDER.digest(*hash); + hasher.update(data); + let digest = hasher.finalize(); Ok((hash, digest)) } @@ -405,3 +440,62 @@ pub fn algorithm_mismatch_error(ctx: &Ctx<'_>, expected_algorithm: &str) -> R pub fn algorithm_not_supported_error(ctx: &Ctx<'_>) -> Result { Err(Exception::throw_message(ctx, "Algorithm not supported")) } + +// Stub implementations for when full subtle crypto is not available +#[cfg(not(feature = "_rustcrypto"))] +pub async fn subtle_export_key<'js>( + ctx: Ctx<'js>, + _format: key_algorithm::KeyFormat, + _key: rquickjs::Class<'js, CryptoKey>, +) -> Result> { + Err(Exception::throw_message( + &ctx, + "exportKey is not supported with this crypto provider", + )) +} + +#[cfg(not(feature = "_rustcrypto"))] +pub async fn subtle_import_key<'js>( + ctx: Ctx<'js>, + _format: key_algorithm::KeyFormat, + _key_data: Value<'js>, + _algorithm: Value<'js>, + _extractable: bool, + _key_usages: rquickjs::Array<'js>, +) -> Result> { + Err(Exception::throw_message( + &ctx, + "importKey is not supported with this crypto provider", + )) +} + +#[cfg(not(feature = "_rustcrypto"))] +pub async fn subtle_wrap_key<'js>( + ctx: Ctx<'js>, + _format: key_algorithm::KeyFormat, + _key: rquickjs::Class<'js, CryptoKey>, + _wrapping_key: rquickjs::Class<'js, CryptoKey>, + _wrap_algo: encryption_algorithm::EncryptionAlgorithm, +) -> Result> { + Err(Exception::throw_message( + &ctx, + "wrapKey is not supported with this crypto provider", + )) +} + +#[cfg(not(feature = "_rustcrypto"))] +pub async fn subtle_unwrap_key<'js>( + _format: key_algorithm::KeyFormat, + wrapped_key: rquickjs::ArrayBuffer<'js>, + _unwrapping_key: rquickjs::Class<'js, CryptoKey>, + _unwrap_algo: encryption_algorithm::EncryptionAlgorithm, + _unwrapped_key_algo: Value<'js>, + _extractable: bool, + _key_usages: rquickjs::Array<'js>, +) -> Result> { + let ctx = wrapped_key.ctx().clone(); + Err(Exception::throw_message( + &ctx, + "unwrapKey is not supported with this crypto provider", + )) +} From 5baa875b438508589691b3910cb8ae66a2e62a34 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 18 Dec 2025 16:19:19 +0100 Subject: [PATCH 16/77] Fix check --- Makefile | 4 ++-- modules/llrt_crypto/src/lib.rs | 20 ++++++++++++++++++++ modules/llrt_crypto/src/provider/mod.rs | 5 +++++ modules/llrt_crypto/src/provider/openssl.rs | 4 ++-- modules/llrt_fetch/src/fetch.rs | 5 ++++- 5 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index b334ea2c38..1d99706549 100644 --- a/Makefile +++ b/Makefile @@ -266,7 +266,7 @@ deploy: cd example/infrastructure && yarn deploy --require-approval never check: - cargo clippy --all-targets --features "lambda,macro,no-sdk,uncompressed,crypto-rust,tls-ring,openssl-vendored" -- -D warnings + cargo clippy --all-targets --features "lambda,macro,no-sdk,uncompressed,crypto-rust,tls-ring" -- -D warnings check-crates: cargo metadata --no-deps --format-version 1 --quiet | \ @@ -276,4 +276,4 @@ check-crates: cargo check -p "$$crate"; \ done -.PHONY: libs check check-crates libs-arm64 libs-x64 toolchain clean-js release-linux release-darwin release-windows lambda stdlib stdlib-x64 stdlib-arm64 test test-ci run js run-release build release clean flame deploy +.PHONY: libs check check-all check-crates libs-arm64 libs-x64 toolchain clean-js release-linux release-darwin release-windows lambda stdlib stdlib-x64 stdlib-arm64 test test-ci run js run-release build release clean flame deploy diff --git a/modules/llrt_crypto/src/lib.rs b/modules/llrt_crypto/src/lib.rs index 6dbfe0216b..433a5bed30 100644 --- a/modules/llrt_crypto/src/lib.rs +++ b/modules/llrt_crypto/src/lib.rs @@ -1,5 +1,25 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 + +// Compile-time checks for conflicting crypto features +#[cfg(all(feature = "crypto-rust", feature = "crypto-openssl"))] +compile_error!("Features `crypto-rust` and `crypto-openssl` are mutually exclusive"); + +#[cfg(all(feature = "crypto-rust", feature = "crypto-ring"))] +compile_error!("Features `crypto-rust` and `crypto-ring` are mutually exclusive"); + +#[cfg(all(feature = "crypto-rust", feature = "crypto-graviola"))] +compile_error!("Features `crypto-rust` and `crypto-graviola` are mutually exclusive"); + +#[cfg(all(feature = "crypto-openssl", feature = "crypto-ring"))] +compile_error!("Features `crypto-openssl` and `crypto-ring` are mutually exclusive"); + +#[cfg(all(feature = "crypto-openssl", feature = "crypto-graviola"))] +compile_error!("Features `crypto-openssl` and `crypto-graviola` are mutually exclusive"); + +#[cfg(all(feature = "crypto-ring", feature = "crypto-graviola"))] +compile_error!("Features `crypto-ring` and `crypto-graviola` are mutually exclusive"); + mod crc32; mod md5_hash; mod sha_hash; diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index ed8a209c8a..9be87de29e 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -53,6 +53,7 @@ pub struct EcImportResult { } #[derive(Debug)] +#[allow(dead_code)] pub struct OkpImportResult { pub key_data: Vec, pub is_private: bool, @@ -73,6 +74,7 @@ pub struct RsaJwkImport<'a> { /// RSA JWK components for export #[derive(Debug)] +#[allow(dead_code)] pub struct RsaJwkExport { pub n: Vec, pub e: Vec, @@ -94,6 +96,7 @@ pub struct EcJwkImport<'a> { /// EC JWK components for export #[derive(Debug)] +#[allow(dead_code)] pub struct EcJwkExport { pub x: Vec, pub y: Vec, @@ -106,12 +109,14 @@ pub trait SimpleDigest { } #[derive(Debug, Clone, Copy)] +#[allow(dead_code)] pub enum AesMode { Ctr { counter_length: u32 }, Cbc, Gcm { tag_length: u8 }, } +#[allow(dead_code)] pub trait CryptoProvider { type Digest: SimpleDigest; type Hmac: HmacProvider; diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs index 1edb591ea2..76b5d237cb 100644 --- a/modules/llrt_crypto/src/provider/openssl.rs +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -145,7 +145,7 @@ impl CryptoProvider for OpenSslProvider { .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; let r = sig.r().to_vec(); let s = sig.s().to_vec(); - let coord_len = (group.degree() as usize + 7) / 8; + let coord_len = (group.degree() as usize).div_ceil(8); let mut result = vec![0u8; coord_len * 2]; result[coord_len - r.len()..coord_len].copy_from_slice(&r); result[coord_len * 2 - s.len()..].copy_from_slice(&s); @@ -880,7 +880,7 @@ impl CryptoProvider for OpenSslProvider { .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let bn = BigNum::from_slice(data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; - let ec_key = EcKey::from_private_components(&group, &bn, &group.generator()) + let ec_key = EcKey::from_private_components(&group, &bn, group.generator()) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_ec_key(ec_key) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; diff --git a/modules/llrt_fetch/src/fetch.rs b/modules/llrt_fetch/src/fetch.rs index 4b89685c10..f146b12f91 100644 --- a/modules/llrt_fetch/src/fetch.rs +++ b/modules/llrt_fetch/src/fetch.rs @@ -483,8 +483,11 @@ fn get_option<'js, V: FromJs<'js> + Sized>( mod tests { use std::io::Read; + #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] use llrt_http::HttpsModule; - use llrt_test::{call_test, test_async_with, ModuleEvaluator}; + use llrt_test::test_async_with; + #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] + use llrt_test::{call_test, ModuleEvaluator}; use rquickjs::{prelude::Promise, CatchResultExt}; use wiremock::{matchers, Mock, MockServer, ResponseTemplate}; From 3951df900aaa13dc53ff14aba8f8af4161333ebe Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 18 Dec 2025 16:43:29 +0100 Subject: [PATCH 17/77] Features --- llrt/Cargo.toml | 2 ++ llrt_core/Cargo.toml | 2 ++ llrt_modules/Cargo.toml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/llrt/Cargo.toml b/llrt/Cargo.toml index 1496573e84..4f2486ccb7 100644 --- a/llrt/Cargo.toml +++ b/llrt/Cargo.toml @@ -20,7 +20,9 @@ tls-openssl = ["llrt_core/tls-openssl"] # Crypto provider features crypto-rust = ["llrt_core/crypto-rust"] +crypto-ring = ["llrt_core/crypto-ring"] crypto-ring-rust = ["llrt_core/crypto-ring-rust"] +crypto-graviola = ["llrt_core/crypto-graviola"] crypto-graviola-rust = ["llrt_core/crypto-graviola-rust"] crypto-openssl = ["llrt_core/crypto-openssl"] diff --git a/llrt_core/Cargo.toml b/llrt_core/Cargo.toml index 2785fad9c3..613e90313f 100644 --- a/llrt_core/Cargo.toml +++ b/llrt_core/Cargo.toml @@ -20,7 +20,9 @@ tls-openssl = ["llrt_modules/tls-openssl", "dep:openssl"] # Crypto provider features crypto-rust = ["llrt_modules/crypto-rust"] +crypto-ring = ["llrt_modules/crypto-ring"] crypto-ring-rust = ["llrt_modules/crypto-ring-rust"] +crypto-graviola = ["llrt_modules/crypto-graviola"] crypto-graviola-rust = ["llrt_modules/crypto-graviola-rust"] crypto-openssl = ["llrt_modules/crypto-openssl"] diff --git a/llrt_modules/Cargo.toml b/llrt_modules/Cargo.toml index a7cf9ec601..9d946e4aa2 100644 --- a/llrt_modules/Cargo.toml +++ b/llrt_modules/Cargo.toml @@ -19,7 +19,9 @@ tls-openssl = ["llrt_http?/tls-openssl", "llrt_tls?/tls-openssl"] # Crypto provider features crypto-rust = ["llrt_crypto?/crypto-rust"] +crypto-ring = ["llrt_crypto?/crypto-ring"] crypto-ring-rust = ["llrt_crypto?/crypto-ring-rust"] +crypto-graviola = ["llrt_crypto?/crypto-graviola"] crypto-graviola-rust = ["llrt_crypto?/crypto-graviola-rust"] crypto-openssl = ["llrt_crypto?/crypto-openssl"] From abf8e1e22951030034b5bcedcc91faa96f3303e5 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Fri, 19 Dec 2025 09:16:36 +0100 Subject: [PATCH 18/77] Allow deadcode for providers with missing algos --- modules/llrt_crypto/src/provider/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index 9be87de29e..8aaff6f4b4 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -39,6 +39,7 @@ use crate::sha_hash::ShaAlgorithm; use crate::subtle::EllipticCurve; #[derive(Debug)] +#[allow(dead_code)] pub struct RsaImportResult { pub key_data: Vec, pub modulus_length: u32, @@ -47,6 +48,7 @@ pub struct RsaImportResult { } #[derive(Debug)] +#[allow(dead_code)] pub struct EcImportResult { pub key_data: Vec, pub is_private: bool, @@ -61,6 +63,7 @@ pub struct OkpImportResult { /// RSA JWK components for import (all values are raw bytes, not base64) #[derive(Debug)] +#[allow(dead_code)] pub struct RsaJwkImport<'a> { pub n: &'a [u8], // modulus pub e: &'a [u8], // public exponent @@ -88,6 +91,7 @@ pub struct RsaJwkExport { /// EC JWK components for import (all values are raw bytes) #[derive(Debug)] +#[allow(dead_code)] pub struct EcJwkImport<'a> { pub x: &'a [u8], pub y: &'a [u8], From dd53fd1e234900c3dffe08a04827e801011e6c28 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Fri, 19 Dec 2025 10:22:34 +0100 Subject: [PATCH 19/77] AES CPU feature detection --- modules/llrt_crypto/src/provider/mod.rs | 38 ++++++++++++++++++------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index 8aaff6f4b4..359fc1cb94 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -741,6 +741,22 @@ impl_hybrid_provider!( |m, k, iv, d, aad| rust::RustCryptoProvider.aes_decrypt(m, k, iv, d, aad) ); +#[cfg(feature = "crypto-graviola-rust")] +fn graviola_aes_supported() -> bool { + #[cfg(target_arch = "aarch64")] + { + std::arch::is_aarch64_feature_detected!("aes") + } + #[cfg(target_arch = "x86_64")] + { + std::arch::is_x86_feature_detected!("aes") + } + #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] + { + false + } +} + #[cfg(feature = "crypto-graviola-rust")] impl_hybrid_provider!( GraviolaRustProvider, @@ -748,17 +764,19 @@ impl_hybrid_provider!( graviola::GraviolaRustHmac, graviola::GraviolaRustDigest::new, graviola::GraviolaRustHmac::new, - |m: AesMode, k: &[u8], iv: &[u8], d: &[u8], aad: Option<&[u8]>| match m { - // Graviola only supports AES-128 and AES-256, fall back to RustCrypto for AES-192 - AesMode::Gcm { .. } if matches!(k.len(), 16 | 32) => - graviola::GraviolaProvider.aes_encrypt(m, k, iv, d, aad), - _ => rust::RustCryptoProvider.aes_encrypt(m, k, iv, d, aad), + |m: AesMode, k: &[u8], iv: &[u8], d: &[u8], aad: Option<&[u8]>| { + if graviola_aes_supported() && matches!(m, AesMode::Gcm { .. }) && matches!(k.len(), 16 | 32) { + graviola::GraviolaProvider.aes_encrypt(m, k, iv, d, aad) + } else { + rust::RustCryptoProvider.aes_encrypt(m, k, iv, d, aad) + } }, - |m: AesMode, k: &[u8], iv: &[u8], d: &[u8], aad: Option<&[u8]>| match m { - // Graviola only supports AES-128 and AES-256, fall back to RustCrypto for AES-192 - AesMode::Gcm { .. } if matches!(k.len(), 16 | 32) => - graviola::GraviolaProvider.aes_decrypt(m, k, iv, d, aad), - _ => rust::RustCryptoProvider.aes_decrypt(m, k, iv, d, aad), + |m: AesMode, k: &[u8], iv: &[u8], d: &[u8], aad: Option<&[u8]>| { + if graviola_aes_supported() && matches!(m, AesMode::Gcm { .. }) && matches!(k.len(), 16 | 32) { + graviola::GraviolaProvider.aes_decrypt(m, k, iv, d, aad) + } else { + rust::RustCryptoProvider.aes_decrypt(m, k, iv, d, aad) + } } ); From 1fcfc9297aec6d16b0a4df5093ac8a5fcd7feed6 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Fri, 19 Dec 2025 10:51:06 +0100 Subject: [PATCH 20/77] Format --- modules/llrt_crypto/src/provider/mod.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index 359fc1cb94..69e8c2b23a 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -765,14 +765,20 @@ impl_hybrid_provider!( graviola::GraviolaRustDigest::new, graviola::GraviolaRustHmac::new, |m: AesMode, k: &[u8], iv: &[u8], d: &[u8], aad: Option<&[u8]>| { - if graviola_aes_supported() && matches!(m, AesMode::Gcm { .. }) && matches!(k.len(), 16 | 32) { + if graviola_aes_supported() + && matches!(m, AesMode::Gcm { .. }) + && matches!(k.len(), 16 | 32) + { graviola::GraviolaProvider.aes_encrypt(m, k, iv, d, aad) } else { rust::RustCryptoProvider.aes_encrypt(m, k, iv, d, aad) } }, |m: AesMode, k: &[u8], iv: &[u8], d: &[u8], aad: Option<&[u8]>| { - if graviola_aes_supported() && matches!(m, AesMode::Gcm { .. }) && matches!(k.len(), 16 | 32) { + if graviola_aes_supported() + && matches!(m, AesMode::Gcm { .. }) + && matches!(k.len(), 16 | 32) + { graviola::GraviolaProvider.aes_decrypt(m, k, iv, d, aad) } else { rust::RustCryptoProvider.aes_decrypt(m, k, iv, d, aad) From a54f17431807f9bcaf86390dfb0b680b78f662d0 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Fri, 19 Dec 2025 11:09:11 +0100 Subject: [PATCH 21/77] Clippy --- llrt_core/src/runtime_client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/llrt_core/src/runtime_client.rs b/llrt_core/src/runtime_client.rs index 836a92d1e8..899d47d834 100644 --- a/llrt_core/src/runtime_client.rs +++ b/llrt_core/src/runtime_client.rs @@ -301,7 +301,7 @@ async fn next_invocation<'js, 'a>( if res.status() != StatusCode::OK { let res_bytes = res.collect().await.or_throw(ctx)?.to_bytes(); - let res_str = String::from_utf8_lossy(&res_bytes[..]); + let res_str = String::from_utf8_lossy(res_bytes.as_ref()); return Err(Exception::throw_message( ctx, &["Unexpected /invocation/next response: ", &res_str].concat(), @@ -379,7 +379,7 @@ async fn invoke_response<'js>( StatusCode::ACCEPTED => Ok(()), _ => { let res_bytes = res.collect().await.or_throw(ctx)?.to_bytes(); - let res_str = String::from_utf8_lossy(&res_bytes[..]); + let res_str = String::from_utf8_lossy(res_bytes.as_ref()); Err(Exception::throw_message( ctx, &["Unexpected /invocation/response response: ", &res_str].concat(), @@ -544,7 +544,7 @@ async fn post_error<'js>( let res = client.request(req).await.or_throw(ctx)?; if res.status() != StatusCode::ACCEPTED { let res_bytes = res.collect().await.or_throw(ctx)?.to_bytes(); - let res_str = String::from_utf8_lossy(&res_bytes[..]); + let res_str = String::from_utf8_lossy(res_bytes.as_ref()); return Err(Exception::throw_message( ctx, &["Unexpected ", path, " response: ", &res_str].concat(), From 2dc0a103d4144912ca6231655aa70a2200e5e2bb Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Fri, 19 Dec 2025 11:33:34 +0100 Subject: [PATCH 22/77] Disable test --- modules/llrt_crypto/src/provider/mod.rs | 40 +++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index 69e8c2b23a..a7ff05f0fe 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -852,7 +852,13 @@ mod tests { assert_eq!(result.len(), 32); } - // AES-GCM tests + // AES-GCM tests - only for providers that support AES + #[cfg(any( + feature = "crypto-rust", + feature = "crypto-openssl", + feature = "crypto-ring-rust", + feature = "crypto-graviola-rust" + ))] #[test] fn test_aes_gcm_128_roundtrip() { let p = provider(); @@ -886,6 +892,12 @@ mod tests { assert_eq!(decrypted, plaintext); } + #[cfg(any( + feature = "crypto-rust", + feature = "crypto-openssl", + feature = "crypto-ring-rust", + feature = "crypto-graviola-rust" + ))] #[test] fn test_aes_gcm_256_roundtrip() { let p = provider(); @@ -910,6 +922,12 @@ mod tests { assert_eq!(decrypted, plaintext); } + #[cfg(any( + feature = "crypto-rust", + feature = "crypto-openssl", + feature = "crypto-ring-rust", + feature = "crypto-graviola-rust" + ))] #[test] fn test_aes_gcm_wrong_key_fails() { let p = provider(); @@ -933,7 +951,13 @@ mod tests { assert!(result.is_err()); } - // Key generation tests + // Key generation tests - only for providers that support key generation + #[cfg(any( + feature = "crypto-rust", + feature = "crypto-openssl", + feature = "crypto-ring-rust", + feature = "crypto-graviola-rust" + ))] #[test] fn test_generate_aes_key_128() { let p = provider(); @@ -941,6 +965,12 @@ mod tests { assert_eq!(key.len(), 16); } + #[cfg(any( + feature = "crypto-rust", + feature = "crypto-openssl", + feature = "crypto-ring-rust", + feature = "crypto-graviola-rust" + ))] #[test] fn test_generate_aes_key_256() { let p = provider(); @@ -948,6 +978,12 @@ mod tests { assert_eq!(key.len(), 32); } + #[cfg(any( + feature = "crypto-rust", + feature = "crypto-openssl", + feature = "crypto-ring-rust", + feature = "crypto-graviola-rust" + ))] #[test] fn test_generate_hmac_key() { let p = provider(); From d77fcd2544581e9c496f626a6b0411c114652212 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Fri, 19 Dec 2025 21:36:43 +0100 Subject: [PATCH 23/77] Disable some tests --- .github/workflows/build-modules.yml | 8 ++++---- .github/workflows/build.yml | 3 +++ modules/llrt_fetch/src/fetch.rs | 12 ++++++++++-- tests/unit/crypto.subtle.test.ts | 14 +++++++++----- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index 6a3674a3ca..cb7fae1fc6 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -39,9 +39,9 @@ jobs: crates=$(cargo metadata --no-deps --format-version 1 --quiet | jq -r '.packages[] | select(.manifest_path | contains("modules/")) | .name') for crate in $crates; do echo "Compiling crate: $crate" - # Use TLS feature for crates that need TLS + # Use TLS feature for crates that need TLS, with --no-default-features to avoid conflicts if [ "$crate" = "llrt_fetch" ] || [ "$crate" = "llrt_http" ]; then - cargo build -p "$crate" --features "$TLS_FEATURE" + cargo build -p "$crate" --no-default-features --features "http1,http2,webpki-roots,$TLS_FEATURE" else cargo build -p "$crate" fi @@ -50,9 +50,9 @@ jobs: env: TLS_FEATURE: ${{ inputs.tls_feature }} run: | - cargo build -p llrt_modules --features "$TLS_FEATURE" + cargo build -p llrt_modules --no-default-features --features "base,$TLS_FEATURE" - name: Run tests all env: TLS_FEATURE: ${{ inputs.tls_feature }} run: | - cargo test -p llrt_modules --features "$TLS_FEATURE" + cargo test -p llrt_modules --no-default-features --features "base,$TLS_FEATURE" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5acb20fd26..ba0112dea4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,6 +87,8 @@ jobs: if: inputs.platform != 'windows' env: CARGO_FEATURES: ${{ inputs.cargo_features }} + # Set LIMITED_CRYPTO for providers that don't support full SubtleCrypto + LLRT_LIMITED_CRYPTO: ${{ contains(inputs.cargo_features, 'crypto-ring,') || contains(inputs.cargo_features, 'crypto-graviola,') && '1' || '0' }} run: | make test-ci 2>&1 - name: Run tests on windows @@ -94,6 +96,7 @@ jobs: shell: msys2 {0} env: CARGO_FEATURES: ${{ inputs.cargo_features }} + LLRT_LIMITED_CRYPTO: ${{ contains(inputs.cargo_features, 'crypto-ring,') || contains(inputs.cargo_features, 'crypto-graviola,') && '1' || '0' }} run: | make test-ci 2>&1 - name: Build Linux binaries diff --git a/modules/llrt_fetch/src/fetch.rs b/modules/llrt_fetch/src/fetch.rs index f146b12f91..4cd257df8d 100644 --- a/modules/llrt_fetch/src/fetch.rs +++ b/modules/llrt_fetch/src/fetch.rs @@ -866,7 +866,11 @@ mod tests { .await; } - #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] + #[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + all(feature = "tls-graviola", target_arch = "x86_64") + ))] #[tokio::test] async fn test_fetch_tls() { let mock_server = llrt_test_tls::MockServer::start().await.unwrap(); @@ -909,7 +913,11 @@ mod tests { .await; } - #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] + #[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + all(feature = "tls-graviola", target_arch = "x86_64") + ))] #[tokio::test] async fn test_fetch_ignore_certificate_errors() { let mock_server = llrt_test_tls::MockServer::start().await.unwrap(); diff --git a/tests/unit/crypto.subtle.test.ts b/tests/unit/crypto.subtle.test.ts index 270e5b180f..a30fa8487a 100644 --- a/tests/unit/crypto.subtle.test.ts +++ b/tests/unit/crypto.subtle.test.ts @@ -5,6 +5,10 @@ const ENCODER = new TextEncoder(); const TEST_MESSAGE = "This is test message."; const ENCODED_DATA = ENCODER.encode(TEST_MESSAGE); +// Limited crypto providers (crypto-ring, crypto-graviola) only support digest +const LIMITED_CRYPTO = process.env.LLRT_LIMITED_CRYPTO === "1"; +const fullCrypto = LIMITED_CRYPTO ? describe.skip : describe; + describe("SubtleCrypto digest", () => { it("should calculate correctly SHA-1/256/384/512 digest", async () => { const parameters: [string, number[]][] = [ @@ -53,7 +57,7 @@ describe("SubtleCrypto digest", () => { }); }); -describe("SubtleCrypto generateKey/sign/verify", () => { +fullCrypto("SubtleCrypto generateKey/sign/verify", () => { // Common test parameters const keyLengths = [128, 192, 256]; const hashAlgorithms = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]; @@ -281,7 +285,7 @@ describe("SubtleCrypto generateKey/sign/verify", () => { }, 60000); }); -describe("SubtleCrypto generateKey/encrypt/decrypt", () => { +fullCrypto("SubtleCrypto generateKey/encrypt/decrypt", () => { // Common key lengths and usages for AES algorithms const keyLengths = [128, 192, 256]; const commonUsages = ["encrypt", "decrypt", "wrapKey", "unwrapKey"]; @@ -490,7 +494,7 @@ describe("SubtleCrypto generateKey/encrypt/decrypt", () => { }, 60000); }); -describe("SubtleCrypto deriveBits/deriveKey", () => { +fullCrypto("SubtleCrypto deriveBits/deriveKey", () => { it("should be processing ECDH algorithm", async () => { const keyLengths = [128, 192, 256]; const algorithms = ["AES-CBC", "AES-CTR", "AES-GCM"]; @@ -788,7 +792,7 @@ describe("SubtleCrypto deriveBits/deriveKey", () => { }); }); -describe("SubtileCrypto import/export", () => { +fullCrypto("SubtileCrypto import/export", () => { it("should export and import keys", async () => { // Test different key algorithms and formats // Define reusable constants @@ -909,7 +913,7 @@ describe("SubtileCrypto import/export", () => { }, 30000); }); -describe("SubtileCrypto wrap/unwrap", () => { +fullCrypto("SubtileCrypto wrap/unwrap", () => { it("should wrap and unwrap keys for all supported algorithms", async () => { // Test parameters const HASH_ALGORITHMS = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]; From 01e5bb41e07d6c8c9a126094f153262431f5257d Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Fri, 19 Dec 2025 23:07:05 +0100 Subject: [PATCH 24/77] Disable tests --- .github/workflows/build.yml | 4 ++-- tests/unit/symbol-to-string-tag.test.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ba0112dea4..ab29b91391 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -88,7 +88,7 @@ jobs: env: CARGO_FEATURES: ${{ inputs.cargo_features }} # Set LIMITED_CRYPTO for providers that don't support full SubtleCrypto - LLRT_LIMITED_CRYPTO: ${{ contains(inputs.cargo_features, 'crypto-ring,') || contains(inputs.cargo_features, 'crypto-graviola,') && '1' || '0' }} + LLRT_LIMITED_CRYPTO: ${{ (contains(inputs.cargo_features, 'crypto-ring,') || contains(inputs.cargo_features, 'crypto-graviola,')) && '1' || '0' }} run: | make test-ci 2>&1 - name: Run tests on windows @@ -96,7 +96,7 @@ jobs: shell: msys2 {0} env: CARGO_FEATURES: ${{ inputs.cargo_features }} - LLRT_LIMITED_CRYPTO: ${{ contains(inputs.cargo_features, 'crypto-ring,') || contains(inputs.cargo_features, 'crypto-graviola,') && '1' || '0' }} + LLRT_LIMITED_CRYPTO: ${{ (contains(inputs.cargo_features, 'crypto-ring,') || contains(inputs.cargo_features, 'crypto-graviola,')) && '1' || '0' }} run: | make test-ci 2>&1 - name: Build Linux binaries diff --git a/tests/unit/symbol-to-string-tag.test.ts b/tests/unit/symbol-to-string-tag.test.ts index 9d8e7b06f0..499856a2b3 100644 --- a/tests/unit/symbol-to-string-tag.test.ts +++ b/tests/unit/symbol-to-string-tag.test.ts @@ -1,3 +1,5 @@ +const LIMITED_CRYPTO = process.env.LLRT_LIMITED_CRYPTO === "1"; + describe("Symbol.toStringTag", () => { describe("URL module", () => { it("URL should have correct Symbol.toStringTag", () => { @@ -110,6 +112,7 @@ describe("Symbol.toStringTag", () => { }); it("CryptoKey should have correct Symbol.toStringTag", async () => { + if (LIMITED_CRYPTO) return; const key = await crypto.subtle.generateKey( { name: "HMAC", hash: "SHA-256" }, false, From accc3ca567198066745adf9959631dfc440ef568 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Fri, 19 Dec 2025 23:13:24 +0100 Subject: [PATCH 25/77] Gates --- modules/llrt_fetch/src/fetch.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/llrt_fetch/src/fetch.rs b/modules/llrt_fetch/src/fetch.rs index 4cd257df8d..159505b922 100644 --- a/modules/llrt_fetch/src/fetch.rs +++ b/modules/llrt_fetch/src/fetch.rs @@ -483,10 +483,18 @@ fn get_option<'js, V: FromJs<'js> + Sized>( mod tests { use std::io::Read; - #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] + #[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + all(feature = "tls-graviola", target_arch = "x86_64") + ))] use llrt_http::HttpsModule; use llrt_test::test_async_with; - #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] + #[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + all(feature = "tls-graviola", target_arch = "x86_64") + ))] use llrt_test::{call_test, ModuleEvaluator}; use rquickjs::{prelude::Promise, CatchResultExt}; use wiremock::{matchers, Mock, MockServer, ResponseTemplate}; From 7a6622ee9070c5ecde78860bf436fc0ae7143dfb Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Sun, 21 Dec 2025 21:59:32 +0100 Subject: [PATCH 26/77] Ignore if kill fails due to process already killed --- llrt_core/src/modules/js/@llrt/test/index.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/llrt_core/src/modules/js/@llrt/test/index.ts b/llrt_core/src/modules/js/@llrt/test/index.ts index a85380f6c4..b556cf9513 100644 --- a/llrt_core/src/modules/js/@llrt/test/index.ts +++ b/llrt_core/src/modules/js/@llrt/test/index.ts @@ -293,7 +293,9 @@ class TestServer { } }); workerData.connectionTimeout = setTimeout(() => { - proc.kill(); + try { + proc.kill(); + } catch {} }, 5000); workerData.childProc = proc; } @@ -317,7 +319,10 @@ class TestServer { } } - handleData(socket: net.Socket, data: Buffer): { response: object | null; workerId: number } { + handleData( + socket: net.Socket, + data: Buffer + ): { response: object | null; workerId: number } { const message = JSON.parse(data as any) as SocketReqMsg; const { type } = message; @@ -463,7 +468,9 @@ class TestServer { "Test did not exit within 1s. It does not properly clean up created resources (servers, timeouts etc)" ); this.handleTestError(workerId, error, performance.now()); - workerData.childProc?.kill(); + try { + workerData.childProc?.kill(); + } catch {} }, 1000); workerData.childProc?.once("exit", () => { @@ -538,8 +545,9 @@ class TestServer { new Error(`Test timed out after ${workerData.currentTimeout}ms`), performance.now() ); - - workerData.childProc?.kill(); + try { + workerData.childProc?.kill(); + } catch {} workerData.childProc = undefined; this.handleWorkerCompleted(parseInt(id)); } From 2b6dd9998154f9f9ac7f14a2fad42d14bda598cd Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Mon, 22 Dec 2025 22:33:36 +0100 Subject: [PATCH 27/77] Fix CI --- .github/workflows/build-modules.yml | 2 +- .github/workflows/build.yml | 9 ++++++--- .github/workflows/ci.yml | 9 +++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index cb7fae1fc6..e949ee3565 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -41,7 +41,7 @@ jobs: echo "Compiling crate: $crate" # Use TLS feature for crates that need TLS, with --no-default-features to avoid conflicts if [ "$crate" = "llrt_fetch" ] || [ "$crate" = "llrt_http" ]; then - cargo build -p "$crate" --no-default-features --features "http1,http2,webpki-roots,$TLS_FEATURE" + cargo build -p "$crate" --no-default-features --features "http1,http2,webpki-roots,compression-rust,$TLS_FEATURE" else cargo build -p "$crate" fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ab29b91391..f75476382a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,6 +22,10 @@ on: required: false type: string description: "Cargo features to use (e.g. --no-default-features --features crypto-ring-rust,tls-ring,macro)" + limited_crypto: + required: false + type: boolean + default: false jobs: build: @@ -87,8 +91,7 @@ jobs: if: inputs.platform != 'windows' env: CARGO_FEATURES: ${{ inputs.cargo_features }} - # Set LIMITED_CRYPTO for providers that don't support full SubtleCrypto - LLRT_LIMITED_CRYPTO: ${{ (contains(inputs.cargo_features, 'crypto-ring,') || contains(inputs.cargo_features, 'crypto-graviola,')) && '1' || '0' }} + LLRT_LIMITED_CRYPTO: ${{ inputs.limited_crypto && '1' || '0' }} run: | make test-ci 2>&1 - name: Run tests on windows @@ -96,7 +99,7 @@ jobs: shell: msys2 {0} env: CARGO_FEATURES: ${{ inputs.cargo_features }} - LLRT_LIMITED_CRYPTO: ${{ (contains(inputs.cargo_features, 'crypto-ring,') || contains(inputs.cargo_features, 'crypto-graviola,')) && '1' || '0' }} + LLRT_LIMITED_CRYPTO: ${{ inputs.limited_crypto && '1' || '0' }} run: | make test-ci 2>&1 - name: Build Linux binaries diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d8390c0d6..653ffc94dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,20 +45,28 @@ jobs: crypto: - name: default features: "" + limited_crypto: false - name: crypto-rust+tls-ring features: "--no-default-features --features crypto-rust,tls-ring,macro" + limited_crypto: false - name: crypto-rust+tls-aws-lc features: "--no-default-features --features crypto-rust,tls-aws-lc,macro" + limited_crypto: false - name: crypto-ring+tls-ring features: "--no-default-features --features crypto-ring,tls-ring,macro" + limited_crypto: true - name: crypto-ring-rust+tls-ring features: "--no-default-features --features crypto-ring-rust,tls-ring,macro" + limited_crypto: false - name: crypto-graviola+tls-graviola features: "--no-default-features --features crypto-graviola,tls-graviola,macro" + limited_crypto: true - name: crypto-graviola-rust+tls-graviola features: "--no-default-features --features crypto-graviola-rust,tls-graviola,macro" + limited_crypto: false - name: crypto-openssl+tls-openssl features: "--no-default-features --features crypto-openssl,tls-openssl,macro" + limited_crypto: false exclude: # OpenSSL requires native compilation - exclude from cross-compile targets - os: ubuntu-latest @@ -111,6 +119,7 @@ jobs: arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} cargo_features: ${{ matrix.crypto.features }} + limited_crypto: ${{ matrix.crypto.limited_crypto }} modules: needs: From f003f97e4a7b8e23d5011ef4e61d774f5ae1bae7 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 27 Jan 2026 22:40:35 +0100 Subject: [PATCH 28/77] address some feedback --- .github/workflows/build-modules.yml | 47 ++- .github/workflows/ci.yml | 9 +- Cargo.lock | 14 + libs/llrt_compression/Cargo.toml | 2 +- libs/llrt_test_tls/Cargo.toml | 3 + libs/llrt_test_tls/src/server.rs | 82 ++++- llrt_core/Cargo.toml | 2 +- llrt_modules/Cargo.toml | 3 +- modules/llrt_crypto/src/provider/mod.rs | 12 +- modules/llrt_crypto/src/sha_hash.rs | 64 ++-- .../llrt_crypto/src/subtle/aes_variants.rs | 260 ++++++++++++++ modules/llrt_crypto/src/subtle/mod.rs | 329 +----------------- modules/llrt_crypto/src/subtle/stubs.rs | 65 ++++ modules/llrt_fetch/Cargo.toml | 12 +- modules/llrt_http/Cargo.toml | 5 +- modules/llrt_tls/Cargo.toml | 1 - 16 files changed, 533 insertions(+), 377 deletions(-) create mode 100644 modules/llrt_crypto/src/subtle/aes_variants.rs create mode 100644 modules/llrt_crypto/src/subtle/stubs.rs diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index e949ee3565..7456fcf610 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -14,14 +14,14 @@ on: toolchain: required: true type: string - tls_feature: + crypto: required: false type: string - default: "tls-ring" + default: "ring" jobs: build: - name: ${{ inputs.arch }}-${{ inputs.platform }}-${{ inputs.tls_feature }} + name: ${{ inputs.arch }}-${{ inputs.platform }}-${{ inputs.crypto }} runs-on: ${{ inputs.os }} steps: - name: Checkout @@ -30,11 +30,38 @@ jobs: uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ inputs.toolchain }} + - name: Map crypto feature to TLS and crypto features + id: features + shell: bash + run: | + case "${{ inputs.crypto }}" in + graviola) + echo "tls_feature=tls-graviola" >> $GITHUB_OUTPUT + echo "crypto_feature=crypto-graviola" >> $GITHUB_OUTPUT + ;; + ring) + echo "tls_feature=tls-ring" >> $GITHUB_OUTPUT + echo "crypto_feature=crypto-ring" >> $GITHUB_OUTPUT + ;; + openssl) + echo "tls_feature=tls-openssl" >> $GITHUB_OUTPUT + echo "crypto_feature=crypto-openssl" >> $GITHUB_OUTPUT + ;; + aws-lc) + echo "tls_feature=tls-aws-lc" >> $GITHUB_OUTPUT + echo "crypto_feature=crypto-ring" >> $GITHUB_OUTPUT + ;; + *) + echo "Unknown crypto feature: ${{ inputs.crypto }}" + exit 1 + ;; + esac - name: Run build crates shell: bash env: RUSTFLAGS: "" - TLS_FEATURE: ${{ inputs.tls_feature }} + TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} + CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} run: | crates=$(cargo metadata --no-deps --format-version 1 --quiet | jq -r '.packages[] | select(.manifest_path | contains("modules/")) | .name') for crate in $crates; do @@ -42,17 +69,21 @@ jobs: # Use TLS feature for crates that need TLS, with --no-default-features to avoid conflicts if [ "$crate" = "llrt_fetch" ] || [ "$crate" = "llrt_http" ]; then cargo build -p "$crate" --no-default-features --features "http1,http2,webpki-roots,compression-rust,$TLS_FEATURE" + elif [ "$crate" = "llrt_crypto" ]; then + cargo build -p "$crate" --no-default-features --features "$CRYPTO_FEATURE" else cargo build -p "$crate" fi done - name: Run build all env: - TLS_FEATURE: ${{ inputs.tls_feature }} + TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} + CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} run: | - cargo build -p llrt_modules --no-default-features --features "base,$TLS_FEATURE" + cargo build -p llrt_modules --no-default-features --features "base,$TLS_FEATURE,$CRYPTO_FEATURE" - name: Run tests all env: - TLS_FEATURE: ${{ inputs.tls_feature }} + TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} + CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} run: | - cargo test -p llrt_modules --no-default-features --features "base,$TLS_FEATURE" + cargo test -p llrt_modules --no-default-features --features "base,$TLS_FEATURE,$CRYPTO_FEATURE" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 653ffc94dd..1ee3fc0b35 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,9 +131,10 @@ jobs: - macos-latest - windows-latest tls: - - tls-ring - - tls-aws-lc - - tls-graviola + - ring + - aws-lc + - graviola + - openssl include: - os: ubuntu-latest platform: linux @@ -153,4 +154,4 @@ jobs: platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} - tls_feature: ${{ matrix.tls }} + crypto: ${{ matrix.tls }} diff --git a/Cargo.lock b/Cargo.lock index e23d5adba5..3a772713d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2168,6 +2168,7 @@ dependencies = [ "llrt_utils", "llrt_zlib", "once_cell", + "openssl", "rquickjs", "simd-json", "tokio", @@ -2316,9 +2317,11 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", + "openssl", "rustls", "rustls-graviola", "tokio", + "tokio-openssl", "tokio-rustls", ] @@ -3711,6 +3714,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-openssl" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59df6849caa43bb7567f9a36f863c447d95a11d5903c9cc334ba32576a27eadd" +dependencies = [ + "openssl", + "openssl-sys", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" diff --git a/libs/llrt_compression/Cargo.toml b/libs/llrt_compression/Cargo.toml index 2a79ba0b96..953c23c7a5 100644 --- a/libs/llrt_compression/Cargo.toml +++ b/libs/llrt_compression/Cargo.toml @@ -20,7 +20,7 @@ brotli-c = ["brotlic"] brotli-rust = ["brotli"] flate2-c = ["flate2/zlib-ng"] -flate2-rust = ["flate2/miniz_oxide"] +flate2-rust = ["flate2/rust_backend"] zstd-c = ["zstd"] zstd-rust = ["zstd"] # No pure rust implementation exists diff --git a/libs/llrt_test_tls/Cargo.toml b/libs/llrt_test_tls/Cargo.toml index 4a81294d91..3a933365aa 100644 --- a/libs/llrt_test_tls/Cargo.toml +++ b/libs/llrt_test_tls/Cargo.toml @@ -15,6 +15,7 @@ default = ["tls-ring"] tls-ring = ["rustls/ring"] tls-aws-lc = ["rustls/aws_lc_rs"] tls-graviola = ["dep:rustls-graviola"] +tls-openssl = ["dep:openssl", "dep:tokio-openssl"] [dependencies] http-body-util = { version = "0.1", default-features = false } @@ -27,5 +28,7 @@ rustls = { version = "0.23", features = [ "tls12", ], default-features = false } rustls-graviola = { version = "0.3", optional = true } +openssl = { version = "0.10", optional = true } +tokio-openssl = { version = "0.6", optional = true } tokio = { version = "1", features = ["net", "fs"], default-features = false } tokio-rustls = { version = "0.26", default-features = false } diff --git a/libs/llrt_test_tls/src/server.rs b/libs/llrt_test_tls/src/server.rs index 84810d8dfd..18ef3b5861 100644 --- a/libs/llrt_test_tls/src/server.rs +++ b/libs/llrt_test_tls/src/server.rs @@ -3,9 +3,7 @@ use std::sync::Arc; use hyper::service::service_fn; use hyper_util::rt::{TokioExecutor, TokioIo}; use hyper_util::server::conn::auto::Builder; -use rustls::ServerConfig; use tokio::net::TcpListener; -use tokio_rustls::TlsAcceptor; use crate::MockServerCerts; @@ -24,11 +22,15 @@ fn get_crypto_provider() -> Arc { Arc::new(rustls_graviola::default_provider()) } +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] pub(super) async fn run( listener: TcpListener, certs: MockServerCerts, shutdown_rx: tokio::sync::watch::Receiver<()>, ) -> Result<(), Box> { + use rustls::ServerConfig; + use tokio_rustls::TlsAcceptor; + let cert_chain = vec![certs.server_cert, certs.root_cert]; let mut server_config = ServerConfig::builder_with_provider(get_crypto_provider()) .with_safe_default_protocol_versions()? @@ -67,3 +69,79 @@ pub(super) async fn run( }); } } + +#[cfg(feature = "tls-openssl")] +pub(super) async fn run( + listener: TcpListener, + certs: MockServerCerts, + shutdown_rx: tokio::sync::watch::Receiver<()>, +) -> Result<(), Box> { + use openssl::ssl::{SslAcceptor, SslMethod}; + + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; + + // Convert rustls certs to OpenSSL format + let cert_der = certs.server_cert.as_ref(); + let key_der = match certs.server_key { + rustls::pki_types::PrivateKeyDer::Pkcs1(ref key) => key.secret_pkcs1_der().to_vec(), + rustls::pki_types::PrivateKeyDer::Pkcs8(ref key) => key.secret_pkcs8_der().to_vec(), + rustls::pki_types::PrivateKeyDer::Sec1(ref key) => key.secret_sec1_der().to_vec(), + _ => return Err("Unsupported key format".into()), + }; + + let cert = openssl::x509::X509::from_der(cert_der)?; + let pkey = openssl::pkey::PKey::private_key_from_der(&key_der)?; + + builder.set_certificate(&cert)?; + builder.set_private_key(&pkey)?; + + let root_cert = openssl::x509::X509::from_der(certs.root_cert.as_ref())?; + builder.add_extra_chain_cert(root_cert)?; + + builder.set_alpn_protos(b"\x02h2\x08http/1.1\x08http/1.0")?; + + let acceptor = builder.build(); + let service = service_fn(crate::api::echo); + + loop { + let (tcp_stream, _remote_addr) = listener.accept().await?; + + let mut shutdown_signal = shutdown_rx.clone(); + let acceptor = acceptor.clone(); + + tokio::spawn(async move { + let ssl = match openssl::ssl::Ssl::new(acceptor.context()) { + Ok(ssl) => ssl, + Err(err) => { + eprintln!("failed to create ssl: {err:#}"); + return; + }, + }; + + let tls_stream = match tokio_openssl::SslStream::new(ssl, tcp_stream) { + Ok(mut stream) => { + if let Err(err) = std::pin::Pin::new(&mut stream).accept().await { + eprintln!("failed to perform tls handshake: {err:#}"); + return; + } + stream + }, + Err(err) => { + eprintln!("failed to create ssl stream: {err:#}"); + return; + }, + }; + + let http_server = Builder::new(TokioExecutor::new()); + let conn = http_server.serve_connection(TokioIo::new(tls_stream), service); + tokio::pin!(conn); + + loop { + tokio::select! { + _ = conn.as_mut() => break, + _ = shutdown_signal.changed() => conn.as_mut().graceful_shutdown(), + } + } + }); + } +} diff --git a/llrt_core/Cargo.toml b/llrt_core/Cargo.toml index 613e90313f..43730508da 100644 --- a/llrt_core/Cargo.toml +++ b/llrt_core/Cargo.toml @@ -27,7 +27,7 @@ crypto-graviola-rust = ["llrt_modules/crypto-graviola-rust"] crypto-openssl = ["llrt_modules/crypto-openssl"] # OpenSSL vendored (builds OpenSSL from source) -openssl-vendored = ["llrt_modules/openssl-vendored", "openssl?/vendored"] +openssl-vendored = ["llrt_modules/openssl-vendored", "openssl/vendored"] [dependencies] bytes = { version = "1", default-features = false } diff --git a/llrt_modules/Cargo.toml b/llrt_modules/Cargo.toml index 9d946e4aa2..d42ac0a692 100644 --- a/llrt_modules/Cargo.toml +++ b/llrt_modules/Cargo.toml @@ -26,7 +26,7 @@ crypto-graviola-rust = ["llrt_crypto?/crypto-graviola-rust"] crypto-openssl = ["llrt_crypto?/crypto-openssl"] # OpenSSL vendored (builds OpenSSL from source) -openssl-vendored = ["llrt_http?/openssl-vendored", "llrt_crypto?/openssl-vendored"] +openssl-vendored = ["llrt_crypto?/openssl-vendored", "dep:openssl", "openssl/vendored"] base = [ "abort", @@ -134,6 +134,7 @@ llrt_intl = { version = "0.7.0-beta", path = "../modules/llrt_intl", optional = llrt_tls = { version = "0.7.0-beta", path = "../modules/llrt_tls", default-features = false, optional = true } llrt_tty = { version = "0.7.0-beta", path = "../modules/llrt_tty", optional = true } llrt_url = { version = "0.7.0-beta", path = "../modules/llrt_url", optional = true } +openssl = { version = "0.10", optional = true } llrt_util = { version = "0.7.0-beta", path = "../modules/llrt_util", optional = true } llrt_zlib = { version = "0.7.0-beta", path = "../modules/llrt_zlib", optional = true } diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index a7ff05f0fe..cbbea505ca 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -107,9 +107,11 @@ pub struct EcJwkExport { pub d: Option>, } -pub trait SimpleDigest { +pub trait SimpleDigest: Send { fn update(&mut self, data: &[u8]); - fn finalize(self) -> Vec; + fn finalize(self) -> Vec + where + Self: Sized; } #[derive(Debug, Clone, Copy)] @@ -362,9 +364,11 @@ pub trait CryptoProvider { ) -> Result; } -pub trait HmacProvider { +pub trait HmacProvider: Send { fn update(&mut self, data: &[u8]); - fn finalize(self) -> Vec; + fn finalize(self) -> Vec + where + Self: Sized; } #[derive(Debug)] diff --git a/modules/llrt_crypto/src/sha_hash.rs b/modules/llrt_crypto/src/sha_hash.rs index 5450c6ca46..38cf2505d4 100644 --- a/modules/llrt_crypto/src/sha_hash.rs +++ b/modules/llrt_crypto/src/sha_hash.rs @@ -5,20 +5,19 @@ use llrt_utils::{ iterable_enum, result::ResultExt, }; -use rquickjs::{function::Opt, prelude::This, Class, Ctx, Result, Value}; +use rquickjs::{function::Opt, prelude::This, Class, Ctx, Exception, Result, Value}; use super::{encoded_bytes, CRYPTO_PROVIDER}; -use crate::provider::{CryptoProvider, HmacProvider, SimpleDigest}; +use crate::provider::{CryptoProvider, DefaultProvider, HmacProvider, SimpleDigest}; + +type ProviderHmac = ::Hmac; +type ProviderDigest = ::Digest; #[rquickjs::class] #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] pub struct Hmac { #[qjs(skip_trace)] - algorithm: ShaAlgorithm, - #[qjs(skip_trace)] - key: Vec, - #[qjs(skip_trace)] - data: Vec, + inner: Option, } #[rquickjs::methods] @@ -26,18 +25,17 @@ impl Hmac { #[qjs(skip)] pub fn new<'js>(ctx: Ctx<'js>, algorithm: String, key_value: ObjectBytes<'js>) -> Result { let algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; - let key = key_value.as_bytes(&ctx)?.to_vec(); + let key = key_value.as_bytes(&ctx)?; + let hmac = CRYPTO_PROVIDER.hmac(algorithm, key); - Ok(Self { - algorithm, - key, - data: Vec::new(), - }) + Ok(Self { inner: Some(hmac) }) } - fn digest<'js>(&self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let mut hmac = CRYPTO_PROVIDER.hmac(self.algorithm, &self.key); - hmac.update(&self.data); + fn digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { + let hmac = self + .inner + .take() + .ok_or_else(|| Exception::throw_message(&ctx, "Digest already called"))?; let result = hmac.finalize(); match encoding.into_inner() { @@ -52,7 +50,11 @@ impl Hmac { bytes: ObjectBytes<'js>, ) -> Result> { let bytes = bytes.as_bytes(&ctx)?; - this.0.borrow_mut().data.extend_from_slice(bytes); + let mut borrowed = this.0.borrow_mut(); + if let Some(ref mut hmac) = borrowed.inner { + hmac.update(bytes); + } + drop(borrowed); Ok(this.0) } } @@ -61,9 +63,7 @@ impl Hmac { #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] pub struct Hash { #[qjs(skip_trace)] - algorithm: ShaAlgorithm, - #[qjs(skip_trace)] - data: Vec, + inner: Option, } #[rquickjs::methods] @@ -71,22 +71,24 @@ impl Hash { #[qjs(skip)] pub fn new(ctx: Ctx<'_>, algorithm: String) -> Result { let algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; + let digest = CRYPTO_PROVIDER.digest(algorithm); Ok(Self { - algorithm, - data: Vec::new(), + inner: Some(digest), }) } #[qjs(rename = "digest")] - fn hash_digest<'js>(&self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let mut digest_hasher = CRYPTO_PROVIDER.digest(self.algorithm); - digest_hasher.update(&self.data); - let digest = digest_hasher.finalize(); + fn hash_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { + let digest = self + .inner + .take() + .ok_or_else(|| Exception::throw_message(&ctx, "Digest already called"))?; + let result = digest.finalize(); match encoding.0 { - Some(encoding) => encoded_bytes(ctx, &digest, &encoding), - None => bytes_to_typed_array(ctx, &digest), + Some(encoding) => encoded_bytes(ctx, &result, &encoding), + None => bytes_to_typed_array(ctx, &result), } } @@ -97,7 +99,11 @@ impl Hash { bytes: ObjectBytes<'js>, ) -> Result> { let bytes = bytes.as_bytes(&ctx)?; - this.0.borrow_mut().data.extend_from_slice(bytes); + let mut borrowed = this.0.borrow_mut(); + if let Some(ref mut digest) = borrowed.inner { + digest.update(bytes); + } + drop(borrowed); Ok(this.0) } } diff --git a/modules/llrt_crypto/src/subtle/aes_variants.rs b/modules/llrt_crypto/src/subtle/aes_variants.rs new file mode 100644 index 0000000000..73c0a772e6 --- /dev/null +++ b/modules/llrt_crypto/src/subtle/aes_variants.rs @@ -0,0 +1,260 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! AES cipher variant types for SubtleCrypto operations. +//! Only available when the `_rustcrypto` feature is enabled. + +use aes::cipher::BlockModeDecrypt; +use aes::cipher::BlockModeEncrypt; + +use aes::{ + cipher::{ + block_padding::{Error as PaddingError, Pkcs7}, + consts::{U12, U13, U14, U15, U16}, + InvalidLength, KeyIvInit, StreamCipher, StreamCipherError, + }, + Aes128, Aes192, Aes256, +}; +use aes_gcm::{ + aead::{Aead, Payload}, + AesGcm, KeyInit, Nonce, +}; +use ctr::{Ctr128BE, Ctr32BE, Ctr64BE}; + +#[allow(dead_code)] +pub enum AesCbcEncVariant { + Aes128(cbc::Encryptor), + Aes192(cbc::Encryptor), + Aes256(cbc::Encryptor), +} + +#[allow(dead_code)] +impl AesCbcEncVariant { + pub fn new(key_len: u16, key: &[u8], iv: &[u8]) -> std::result::Result { + let variant: AesCbcEncVariant = match key_len { + 128 => Self::Aes128(cbc::Encryptor::new_from_slices(key, iv)?), + 192 => Self::Aes192(cbc::Encryptor::new_from_slices(key, iv)?), + 256 => Self::Aes256(cbc::Encryptor::new_from_slices(key, iv)?), + _ => return Err(InvalidLength), + }; + + Ok(variant) + } + + pub fn encrypt(self, data: &[u8]) -> Vec { + match self { + Self::Aes128(v) => v.encrypt_padded_vec::(data), + Self::Aes192(v) => v.encrypt_padded_vec::(data), + Self::Aes256(v) => v.encrypt_padded_vec::(data), + } + } +} + +#[allow(dead_code)] +pub enum AesCbcDecVariant { + Aes128(cbc::Decryptor), + Aes192(cbc::Decryptor), + Aes256(cbc::Decryptor), +} + +#[allow(dead_code)] +impl AesCbcDecVariant { + pub fn new(key_len: u16, key: &[u8], iv: &[u8]) -> std::result::Result { + let variant: AesCbcDecVariant = match key_len { + 128 => Self::Aes128(cbc::Decryptor::new_from_slices(key, iv)?), + 192 => Self::Aes192(cbc::Decryptor::new_from_slices(key, iv)?), + 256 => Self::Aes256(cbc::Decryptor::new_from_slices(key, iv)?), + _ => return Err(InvalidLength), + }; + + Ok(variant) + } + + pub fn decrypt(self, data: &[u8]) -> std::result::Result, PaddingError> { + Ok(match self { + Self::Aes128(v) => v.decrypt_padded_vec::(data)?, + Self::Aes192(v) => v.decrypt_padded_vec::(data)?, + Self::Aes256(v) => v.decrypt_padded_vec::(data)?, + }) + } +} + +#[allow(dead_code)] +pub enum AesCtrVariant { + Aes128Ctr32(Ctr32BE), + Aes128Ctr64(Ctr64BE), + Aes128Ctr128(Ctr128BE), + Aes192Ctr32(Ctr32BE), + Aes192Ctr64(Ctr64BE), + Aes192Ctr128(Ctr128BE), + Aes256Ctr32(Ctr32BE), + Aes256Ctr64(Ctr64BE), + Aes256Ctr128(Ctr128BE), +} + +#[allow(dead_code)] +impl AesCtrVariant { + pub fn new( + key_len: u16, + encryption_length: u32, + key: &[u8], + counter: &[u8], + ) -> std::result::Result { + let variant: AesCtrVariant = match (key_len, encryption_length) { + (128, 32) => Self::Aes128Ctr32(Ctr32BE::new_from_slices(key, counter)?), + (128, 64) => Self::Aes128Ctr64(Ctr64BE::new_from_slices(key, counter)?), + (128, 128) => Self::Aes128Ctr128(Ctr128BE::new_from_slices(key, counter)?), + (192, 32) => Self::Aes192Ctr32(Ctr32BE::new_from_slices(key, counter)?), + (192, 64) => Self::Aes192Ctr64(Ctr64BE::new_from_slices(key, counter)?), + (192, 128) => Self::Aes192Ctr128(Ctr128BE::new_from_slices(key, counter)?), + (256, 32) => Self::Aes256Ctr32(Ctr32BE::new_from_slices(key, counter)?), + (256, 64) => Self::Aes256Ctr64(Ctr64BE::new_from_slices(key, counter)?), + (256, 128) => Self::Aes256Ctr128(Ctr128BE::new_from_slices(key, counter)?), + _ => return Err(InvalidLength), + }; + + Ok(variant) + } + + pub fn encrypt(&mut self, data: &[u8]) -> std::result::Result, StreamCipherError> { + let mut ciphertext = data.to_vec(); + match self { + Self::Aes128Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes128Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes128Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes192Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes192Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes192Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes256Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes256Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes256Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, + } + Ok(ciphertext) + } + + pub fn decrypt(&mut self, data: &[u8]) -> std::result::Result, StreamCipherError> { + let mut ciphertext = data.to_vec(); + match self { + Self::Aes128Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes128Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes128Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes192Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes192Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes192Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes256Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes256Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, + Self::Aes256Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, + } + Ok(ciphertext) + } +} + +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), +} + +#[allow(dead_code)] +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(), + }; + let nonce: &ctr::cipher::Array<_, _> = + &Nonce::::try_from(nonce).map_err(|_| aes_gcm::Error)?; + match self { + Self::Aes128Gcm96(v) => v.encrypt(nonce, plaintext), + Self::Aes192Gcm96(v) => v.encrypt(nonce, plaintext), + Self::Aes256Gcm96(v) => v.encrypt(nonce, plaintext), + Self::Aes128Gcm104(v) => v.encrypt(nonce, plaintext), + Self::Aes192Gcm104(v) => v.encrypt(nonce, plaintext), + Self::Aes256Gcm104(v) => v.encrypt(nonce, plaintext), + Self::Aes128Gcm112(v) => v.encrypt(nonce, plaintext), + Self::Aes192Gcm112(v) => v.encrypt(nonce, plaintext), + Self::Aes256Gcm112(v) => v.encrypt(nonce, plaintext), + Self::Aes128Gcm120(v) => v.encrypt(nonce, plaintext), + Self::Aes192Gcm120(v) => v.encrypt(nonce, plaintext), + Self::Aes256Gcm120(v) => v.encrypt(nonce, plaintext), + Self::Aes128Gcm128(v) => v.encrypt(nonce, plaintext), + Self::Aes192Gcm128(v) => v.encrypt(nonce, plaintext), + Self::Aes256Gcm128(v) => v.encrypt(nonce, 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(), + }; + let nonce: &ctr::cipher::Array<_, _> = + &Nonce::::try_from(nonce).map_err(|_| aes_gcm::Error)?; + match self { + Self::Aes128Gcm96(v) => v.decrypt(nonce, ciphertext), + Self::Aes192Gcm96(v) => v.decrypt(nonce, ciphertext), + Self::Aes256Gcm96(v) => v.decrypt(nonce, ciphertext), + Self::Aes128Gcm104(v) => v.decrypt(nonce, ciphertext), + Self::Aes192Gcm104(v) => v.decrypt(nonce, ciphertext), + Self::Aes256Gcm104(v) => v.decrypt(nonce, ciphertext), + Self::Aes128Gcm112(v) => v.decrypt(nonce, ciphertext), + Self::Aes192Gcm112(v) => v.decrypt(nonce, ciphertext), + Self::Aes256Gcm112(v) => v.decrypt(nonce, ciphertext), + Self::Aes128Gcm120(v) => v.decrypt(nonce, ciphertext), + Self::Aes192Gcm120(v) => v.decrypt(nonce, ciphertext), + Self::Aes256Gcm120(v) => v.decrypt(nonce, ciphertext), + Self::Aes128Gcm128(v) => v.decrypt(nonce, ciphertext), + Self::Aes192Gcm128(v) => v.decrypt(nonce, ciphertext), + Self::Aes256Gcm128(v) => v.decrypt(nonce, ciphertext), + } + } +} diff --git a/modules/llrt_crypto/src/subtle/mod.rs b/modules/llrt_crypto/src/subtle/mod.rs index 0d84f17908..ec6b69e0be 100644 --- a/modules/llrt_crypto/src/subtle/mod.rs +++ b/modules/llrt_crypto/src/subtle/mod.rs @@ -69,273 +69,9 @@ impl SubtleCrypto { } } -// AES variant types - only available with full subtle crypto support +// AES variant types - only available when _rustcrypto feature is enabled #[cfg(feature = "_rustcrypto")] -mod aes_variants { - use aes::cipher::BlockModeDecrypt; - use aes::cipher::BlockModeEncrypt; - - use aes::{ - cipher::{ - block_padding::{Error as PaddingError, Pkcs7}, - consts::{U12, U13, U14, U15, U16}, - InvalidLength, KeyIvInit, StreamCipher, StreamCipherError, - }, - Aes128, Aes192, Aes256, - }; - use aes_gcm::{ - aead::{Aead, Payload}, - AesGcm, KeyInit, Nonce, - }; - use ctr::{Ctr128BE, Ctr32BE, Ctr64BE}; - - #[allow(dead_code)] - pub enum AesCbcEncVariant { - Aes128(cbc::Encryptor), - Aes192(cbc::Encryptor), - Aes256(cbc::Encryptor), - } - - #[allow(dead_code)] - impl AesCbcEncVariant { - pub fn new( - key_len: u16, - key: &[u8], - iv: &[u8], - ) -> std::result::Result { - let variant: AesCbcEncVariant = match key_len { - 128 => Self::Aes128(cbc::Encryptor::new_from_slices(key, iv)?), - 192 => Self::Aes192(cbc::Encryptor::new_from_slices(key, iv)?), - 256 => Self::Aes256(cbc::Encryptor::new_from_slices(key, iv)?), - _ => return Err(InvalidLength), - }; - - Ok(variant) - } - - pub fn encrypt(self, data: &[u8]) -> Vec { - match self { - Self::Aes128(v) => v.encrypt_padded_vec::(data), - Self::Aes192(v) => v.encrypt_padded_vec::(data), - Self::Aes256(v) => v.encrypt_padded_vec::(data), - } - } - } - - #[allow(dead_code)] - pub enum AesCbcDecVariant { - Aes128(cbc::Decryptor), - Aes192(cbc::Decryptor), - Aes256(cbc::Decryptor), - } - - #[allow(dead_code)] - impl AesCbcDecVariant { - pub fn new( - key_len: u16, - key: &[u8], - iv: &[u8], - ) -> std::result::Result { - let variant: AesCbcDecVariant = match key_len { - 128 => Self::Aes128(cbc::Decryptor::new_from_slices(key, iv)?), - 192 => Self::Aes192(cbc::Decryptor::new_from_slices(key, iv)?), - 256 => Self::Aes256(cbc::Decryptor::new_from_slices(key, iv)?), - _ => return Err(InvalidLength), - }; - - Ok(variant) - } - - pub fn decrypt(self, data: &[u8]) -> std::result::Result, PaddingError> { - Ok(match self { - Self::Aes128(v) => v.decrypt_padded_vec::(data)?, - Self::Aes192(v) => v.decrypt_padded_vec::(data)?, - Self::Aes256(v) => v.decrypt_padded_vec::(data)?, - }) - } - } - - #[allow(dead_code)] - pub enum AesCtrVariant { - Aes128Ctr32(Ctr32BE), - Aes128Ctr64(Ctr64BE), - Aes128Ctr128(Ctr128BE), - Aes192Ctr32(Ctr32BE), - Aes192Ctr64(Ctr64BE), - Aes192Ctr128(Ctr128BE), - Aes256Ctr32(Ctr32BE), - Aes256Ctr64(Ctr64BE), - Aes256Ctr128(Ctr128BE), - } - - #[allow(dead_code)] - impl AesCtrVariant { - pub fn new( - key_len: u16, - encryption_length: u32, - key: &[u8], - counter: &[u8], - ) -> std::result::Result { - let variant: AesCtrVariant = match (key_len, encryption_length) { - (128, 32) => Self::Aes128Ctr32(Ctr32BE::new_from_slices(key, counter)?), - (128, 64) => Self::Aes128Ctr64(Ctr64BE::new_from_slices(key, counter)?), - (128, 128) => Self::Aes128Ctr128(Ctr128BE::new_from_slices(key, counter)?), - (192, 32) => Self::Aes192Ctr32(Ctr32BE::new_from_slices(key, counter)?), - (192, 64) => Self::Aes192Ctr64(Ctr64BE::new_from_slices(key, counter)?), - (192, 128) => Self::Aes192Ctr128(Ctr128BE::new_from_slices(key, counter)?), - (256, 32) => Self::Aes256Ctr32(Ctr32BE::new_from_slices(key, counter)?), - (256, 64) => Self::Aes256Ctr64(Ctr64BE::new_from_slices(key, counter)?), - (256, 128) => Self::Aes256Ctr128(Ctr128BE::new_from_slices(key, counter)?), - _ => return Err(InvalidLength), - }; - - Ok(variant) - } - - pub fn encrypt(&mut self, data: &[u8]) -> std::result::Result, StreamCipherError> { - let mut ciphertext = data.to_vec(); - match self { - Self::Aes128Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes128Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes128Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes192Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes192Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes192Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes256Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes256Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes256Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, - } - Ok(ciphertext) - } - - pub fn decrypt(&mut self, data: &[u8]) -> std::result::Result, StreamCipherError> { - let mut ciphertext = data.to_vec(); - match self { - Self::Aes128Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes128Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes128Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes192Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes192Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes192Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes256Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes256Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, - Self::Aes256Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, - } - Ok(ciphertext) - } - } - - 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), - } - - #[allow(dead_code)] - 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(), - }; - let nonce: &ctr::cipher::Array<_, _> = - &Nonce::::try_from(nonce).map_err(|_| aes_gcm::Error)?; - match self { - Self::Aes128Gcm96(v) => v.encrypt(nonce, plaintext), - Self::Aes192Gcm96(v) => v.encrypt(nonce, plaintext), - Self::Aes256Gcm96(v) => v.encrypt(nonce, plaintext), - Self::Aes128Gcm104(v) => v.encrypt(nonce, plaintext), - Self::Aes192Gcm104(v) => v.encrypt(nonce, plaintext), - Self::Aes256Gcm104(v) => v.encrypt(nonce, plaintext), - Self::Aes128Gcm112(v) => v.encrypt(nonce, plaintext), - Self::Aes192Gcm112(v) => v.encrypt(nonce, plaintext), - Self::Aes256Gcm112(v) => v.encrypt(nonce, plaintext), - Self::Aes128Gcm120(v) => v.encrypt(nonce, plaintext), - Self::Aes192Gcm120(v) => v.encrypt(nonce, plaintext), - Self::Aes256Gcm120(v) => v.encrypt(nonce, plaintext), - Self::Aes128Gcm128(v) => v.encrypt(nonce, plaintext), - Self::Aes192Gcm128(v) => v.encrypt(nonce, plaintext), - Self::Aes256Gcm128(v) => v.encrypt(nonce, 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(), - }; - let nonce: &ctr::cipher::Array<_, _> = - &Nonce::::try_from(nonce).map_err(|_| aes_gcm::Error)?; - match self { - Self::Aes128Gcm96(v) => v.decrypt(nonce, ciphertext), - Self::Aes192Gcm96(v) => v.decrypt(nonce, ciphertext), - Self::Aes256Gcm96(v) => v.decrypt(nonce, ciphertext), - Self::Aes128Gcm104(v) => v.decrypt(nonce, ciphertext), - Self::Aes192Gcm104(v) => v.decrypt(nonce, ciphertext), - Self::Aes256Gcm104(v) => v.decrypt(nonce, ciphertext), - Self::Aes128Gcm112(v) => v.decrypt(nonce, ciphertext), - Self::Aes192Gcm112(v) => v.decrypt(nonce, ciphertext), - Self::Aes256Gcm112(v) => v.decrypt(nonce, ciphertext), - Self::Aes128Gcm120(v) => v.decrypt(nonce, ciphertext), - Self::Aes192Gcm120(v) => v.decrypt(nonce, ciphertext), - Self::Aes256Gcm120(v) => v.decrypt(nonce, ciphertext), - Self::Aes128Gcm128(v) => v.decrypt(nonce, ciphertext), - Self::Aes192Gcm128(v) => v.decrypt(nonce, ciphertext), - Self::Aes256Gcm128(v) => v.decrypt(nonce, ciphertext), - } - } - } -} - +mod aes_variants; #[cfg(feature = "_rustcrypto")] pub use aes_variants::*; @@ -441,61 +177,14 @@ pub fn algorithm_not_supported_error(ctx: &Ctx<'_>) -> Result { Err(Exception::throw_message(ctx, "Algorithm not supported")) } -// Stub implementations for when full subtle crypto is not available +// Stub implementations for when _rustcrypto feature is disabled #[cfg(not(feature = "_rustcrypto"))] -pub async fn subtle_export_key<'js>( - ctx: Ctx<'js>, - _format: key_algorithm::KeyFormat, - _key: rquickjs::Class<'js, CryptoKey>, -) -> Result> { - Err(Exception::throw_message( - &ctx, - "exportKey is not supported with this crypto provider", - )) -} - +mod stubs; #[cfg(not(feature = "_rustcrypto"))] -pub async fn subtle_import_key<'js>( - ctx: Ctx<'js>, - _format: key_algorithm::KeyFormat, - _key_data: Value<'js>, - _algorithm: Value<'js>, - _extractable: bool, - _key_usages: rquickjs::Array<'js>, -) -> Result> { - Err(Exception::throw_message( - &ctx, - "importKey is not supported with this crypto provider", - )) -} - +pub use stubs::subtle_export_key; #[cfg(not(feature = "_rustcrypto"))] -pub async fn subtle_wrap_key<'js>( - ctx: Ctx<'js>, - _format: key_algorithm::KeyFormat, - _key: rquickjs::Class<'js, CryptoKey>, - _wrapping_key: rquickjs::Class<'js, CryptoKey>, - _wrap_algo: encryption_algorithm::EncryptionAlgorithm, -) -> Result> { - Err(Exception::throw_message( - &ctx, - "wrapKey is not supported with this crypto provider", - )) -} - +pub use stubs::subtle_import_key; #[cfg(not(feature = "_rustcrypto"))] -pub async fn subtle_unwrap_key<'js>( - _format: key_algorithm::KeyFormat, - wrapped_key: rquickjs::ArrayBuffer<'js>, - _unwrapping_key: rquickjs::Class<'js, CryptoKey>, - _unwrap_algo: encryption_algorithm::EncryptionAlgorithm, - _unwrapped_key_algo: Value<'js>, - _extractable: bool, - _key_usages: rquickjs::Array<'js>, -) -> Result> { - let ctx = wrapped_key.ctx().clone(); - Err(Exception::throw_message( - &ctx, - "unwrapKey is not supported with this crypto provider", - )) -} +pub use stubs::subtle_unwrap_key; +#[cfg(not(feature = "_rustcrypto"))] +pub use stubs::subtle_wrap_key; diff --git a/modules/llrt_crypto/src/subtle/stubs.rs b/modules/llrt_crypto/src/subtle/stubs.rs new file mode 100644 index 0000000000..e7a85d2be3 --- /dev/null +++ b/modules/llrt_crypto/src/subtle/stubs.rs @@ -0,0 +1,65 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Stub implementations for SubtleCrypto operations when `_rustcrypto` feature is disabled. +//! These return errors indicating the operation is not supported. + +use rquickjs::{Ctx, Exception, Object, Result, Value}; + +use super::crypto_key::CryptoKey; +use super::encryption_algorithm; +use super::key_algorithm; + +pub async fn subtle_export_key<'js>( + ctx: Ctx<'js>, + _format: key_algorithm::KeyFormat, + _key: rquickjs::Class<'js, CryptoKey>, +) -> Result> { + Err(Exception::throw_message( + &ctx, + "exportKey is not supported with this crypto provider", + )) +} + +pub async fn subtle_import_key<'js>( + ctx: Ctx<'js>, + _format: key_algorithm::KeyFormat, + _key_data: Value<'js>, + _algorithm: Value<'js>, + _extractable: bool, + _key_usages: rquickjs::Array<'js>, +) -> Result> { + Err(Exception::throw_message( + &ctx, + "importKey is not supported with this crypto provider", + )) +} + +pub async fn subtle_wrap_key<'js>( + ctx: Ctx<'js>, + _format: key_algorithm::KeyFormat, + _key: rquickjs::Class<'js, CryptoKey>, + _wrapping_key: rquickjs::Class<'js, CryptoKey>, + _wrap_algo: encryption_algorithm::EncryptionAlgorithm, +) -> Result> { + Err(Exception::throw_message( + &ctx, + "wrapKey is not supported with this crypto provider", + )) +} + +pub async fn subtle_unwrap_key<'js>( + _format: key_algorithm::KeyFormat, + wrapped_key: rquickjs::ArrayBuffer<'js>, + _unwrapping_key: rquickjs::Class<'js, CryptoKey>, + _unwrap_algo: encryption_algorithm::EncryptionAlgorithm, + _unwrapped_key_algo: Value<'js>, + _extractable: bool, + _key_usages: rquickjs::Array<'js>, +) -> Result> { + let ctx = wrapped_key.ctx().clone(); + Err(Exception::throw_message( + &ctx, + "unwrapKey is not supported with this crypto provider", + )) +} diff --git a/modules/llrt_fetch/Cargo.toml b/modules/llrt_fetch/Cargo.toml index d0ac105dbd..307db18b43 100644 --- a/modules/llrt_fetch/Cargo.toml +++ b/modules/llrt_fetch/Cargo.toml @@ -23,9 +23,15 @@ compression-rust = ["llrt_compression/all-rust"] webpki-roots = ["llrt_http/webpki-roots"] native-roots = ["llrt_http/native-roots"] -tls-ring = ["llrt_http/tls-ring", "dep:llrt_test_tls", "llrt_test_tls?/tls-ring"] -tls-aws-lc = ["llrt_http/tls-aws-lc", "dep:llrt_test_tls", "llrt_test_tls?/tls-aws-lc"] -tls-graviola = ["llrt_http/tls-graviola", "dep:llrt_test_tls", "llrt_test_tls?/tls-graviola"] +tls-ring = ["llrt_http/tls-ring"] +tls-aws-lc = ["llrt_http/tls-aws-lc"] +tls-graviola = ["llrt_http/tls-graviola"] +tls-openssl = ["llrt_http/tls-openssl"] + +# Internal testing features +_test-tls-ring = ["tls-ring", "dep:llrt_test_tls", "llrt_test_tls/tls-ring"] +_test-tls-aws-lc = ["tls-aws-lc", "dep:llrt_test_tls", "llrt_test_tls/tls-aws-lc"] +_test-tls-graviola = ["tls-graviola", "dep:llrt_test_tls", "llrt_test_tls/tls-graviola"] [dependencies] bytes = { version = "1", default-features = false } diff --git a/modules/llrt_http/Cargo.toml b/modules/llrt_http/Cargo.toml index f4def95789..a88eebbacd 100644 --- a/modules/llrt_http/Cargo.toml +++ b/modules/llrt_http/Cargo.toml @@ -21,13 +21,12 @@ webpki-roots = ["llrt_tls/webpki-roots"] native-roots = ["llrt_tls/native-roots"] # TLS crypto backend features (rustls-based) -tls-ring = ["llrt_tls/tls-ring", "dep:hyper-rustls", "hyper-rustls?/ring", "dep:rustls"] -tls-aws-lc = ["llrt_tls/tls-aws-lc", "dep:hyper-rustls", "hyper-rustls?/aws-lc-rs", "dep:rustls"] +tls-ring = ["llrt_tls/tls-ring", "dep:hyper-rustls", "hyper-rustls/ring", "dep:rustls"] +tls-aws-lc = ["llrt_tls/tls-aws-lc", "dep:hyper-rustls", "hyper-rustls/aws-lc-rs", "dep:rustls"] tls-graviola = ["llrt_tls/tls-graviola", "dep:hyper-rustls", "dep:rustls"] # OpenSSL TLS backend tls-openssl = ["llrt_tls/tls-openssl", "dep:hyper-openssl", "dep:openssl"] -openssl-vendored = ["llrt_tls/openssl-vendored", "openssl?/vendored"] [dependencies] bytes = { version = "1", default-features = false } diff --git a/modules/llrt_tls/Cargo.toml b/modules/llrt_tls/Cargo.toml index e6b006859a..88398d7e46 100644 --- a/modules/llrt_tls/Cargo.toml +++ b/modules/llrt_tls/Cargo.toml @@ -24,7 +24,6 @@ tls-graviola = ["dep:rustls", "dep:rustls-graviola"] # OpenSSL TLS backend tls-openssl = ["dep:openssl"] -openssl-vendored = ["openssl?/vendored"] [dependencies] once_cell = { version = "1", features = ["std"], default-features = false } From b6908080041292649bde82ca5e1f4146a29217ad Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 27 Jan 2026 23:12:49 +0100 Subject: [PATCH 29/77] adjust tag length calculation and update public key retrieval method --- modules/llrt_crypto/src/provider/openssl.rs | 10 +++++----- modules/llrt_fetch/src/fetch.rs | 12 ++++++------ modules/llrt_http/Cargo.toml | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs index 76b5d237cb..58abab52dd 100644 --- a/modules/llrt_crypto/src/provider/openssl.rs +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -477,7 +477,7 @@ impl CryptoProvider for OpenSslProvider { ))) }, }; - let tag_len = tag_length as usize; + let tag_len = (tag_length / 8) as usize; let mut tag = vec![0u8; tag_len]; let ciphertext = symm::encrypt_aead( cipher, @@ -543,7 +543,7 @@ impl CryptoProvider for OpenSslProvider { ))) }, }; - let tag_len = tag_length as usize; + let tag_len = (tag_length / 8) as usize; if data.len() < tag_len { return Err(CryptoError::InvalidData(Some( "Data too short for GCM tag".into(), @@ -678,10 +678,10 @@ impl CryptoProvider for OpenSslProvider { let private_der = pkey .private_key_to_der() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; - let public_der = pkey - .public_key_to_der() + let public_raw = pkey + .raw_public_key() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; - Ok((private_der, public_der)) + Ok((private_der, public_raw)) } fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { diff --git a/modules/llrt_fetch/src/fetch.rs b/modules/llrt_fetch/src/fetch.rs index 159505b922..af66067e7d 100644 --- a/modules/llrt_fetch/src/fetch.rs +++ b/modules/llrt_fetch/src/fetch.rs @@ -875,9 +875,9 @@ mod tests { } #[cfg(any( - feature = "tls-ring", - feature = "tls-aws-lc", - all(feature = "tls-graviola", target_arch = "x86_64") + feature = "_test-tls-ring", + feature = "_test-tls-aws-lc", + all(feature = "_test-tls-graviola", target_arch = "x86_64") ))] #[tokio::test] async fn test_fetch_tls() { @@ -922,9 +922,9 @@ mod tests { } #[cfg(any( - feature = "tls-ring", - feature = "tls-aws-lc", - all(feature = "tls-graviola", target_arch = "x86_64") + feature = "_test-tls-ring", + feature = "_test-tls-aws-lc", + all(feature = "_test-tls-graviola", target_arch = "x86_64") ))] #[tokio::test] async fn test_fetch_ignore_certificate_errors() { diff --git a/modules/llrt_http/Cargo.toml b/modules/llrt_http/Cargo.toml index a88eebbacd..c3d62469c5 100644 --- a/modules/llrt_http/Cargo.toml +++ b/modules/llrt_http/Cargo.toml @@ -21,12 +21,12 @@ webpki-roots = ["llrt_tls/webpki-roots"] native-roots = ["llrt_tls/native-roots"] # TLS crypto backend features (rustls-based) -tls-ring = ["llrt_tls/tls-ring", "dep:hyper-rustls", "hyper-rustls/ring", "dep:rustls"] -tls-aws-lc = ["llrt_tls/tls-aws-lc", "dep:hyper-rustls", "hyper-rustls/aws-lc-rs", "dep:rustls"] -tls-graviola = ["llrt_tls/tls-graviola", "dep:hyper-rustls", "dep:rustls"] +tls-ring = ["http1", "llrt_tls/tls-ring", "dep:hyper-rustls", "hyper-rustls/ring", "dep:rustls"] +tls-aws-lc = ["http1", "llrt_tls/tls-aws-lc", "dep:hyper-rustls", "hyper-rustls/aws-lc-rs", "dep:rustls"] +tls-graviola = ["http1", "llrt_tls/tls-graviola", "dep:hyper-rustls", "dep:rustls"] # OpenSSL TLS backend -tls-openssl = ["llrt_tls/tls-openssl", "dep:hyper-openssl", "dep:openssl"] +tls-openssl = ["http1", "llrt_tls/tls-openssl", "dep:hyper-openssl", "dep:openssl"] [dependencies] bytes = { version = "1", default-features = false } From 797a6598142cd5fe349dff9a0780c890ab7e75d3 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 10:26:10 +0100 Subject: [PATCH 30/77] Correct modules --- .github/workflows/build.yml | 14 ------- .github/workflows/ci.yml | 82 ++++++++++--------------------------- 2 files changed, 21 insertions(+), 75 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f75476382a..e50b4ade74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,14 +18,6 @@ on: required: false type: string default: "nightly" - cargo_features: - required: false - type: string - description: "Cargo features to use (e.g. --no-default-features --features crypto-ring-rust,tls-ring,macro)" - limited_crypto: - required: false - type: boolean - default: false jobs: build: @@ -89,17 +81,11 @@ jobs: toolchain: ${{ inputs.toolchain }} - name: Run tests if: inputs.platform != 'windows' - env: - CARGO_FEATURES: ${{ inputs.cargo_features }} - LLRT_LIMITED_CRYPTO: ${{ inputs.limited_crypto && '1' || '0' }} run: | make test-ci 2>&1 - name: Run tests on windows if: inputs.platform == 'windows' shell: msys2 {0} - env: - CARGO_FEATURES: ${{ inputs.cargo_features }} - LLRT_LIMITED_CRYPTO: ${{ inputs.limited_crypto && '1' || '0' }} run: | make test-ci 2>&1 - name: Build Linux binaries diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ee3fc0b35..cfd0d80540 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,68 +29,13 @@ jobs: for i in {1..5}; do echo "console.log(123);" > "bundle/js/test$i.js" done - cargo clippy --all-targets --features "lambda,macro,no-sdk,uncompressed,crypto-rust,tls-ring,openssl-vendored" -- -D warnings - + cargo clippy --all-targets --all-features -- -D warnings build: needs: - check strategy: fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} matrix: - os: - - windows-latest - - ubuntu-latest - - ubuntu-24.04-arm - - macos-latest - crypto: - - name: default - features: "" - limited_crypto: false - - name: crypto-rust+tls-ring - features: "--no-default-features --features crypto-rust,tls-ring,macro" - limited_crypto: false - - name: crypto-rust+tls-aws-lc - features: "--no-default-features --features crypto-rust,tls-aws-lc,macro" - limited_crypto: false - - name: crypto-ring+tls-ring - features: "--no-default-features --features crypto-ring,tls-ring,macro" - limited_crypto: true - - name: crypto-ring-rust+tls-ring - features: "--no-default-features --features crypto-ring-rust,tls-ring,macro" - limited_crypto: false - - name: crypto-graviola+tls-graviola - features: "--no-default-features --features crypto-graviola,tls-graviola,macro" - limited_crypto: true - - name: crypto-graviola-rust+tls-graviola - features: "--no-default-features --features crypto-graviola-rust,tls-graviola,macro" - limited_crypto: false - - name: crypto-openssl+tls-openssl - features: "--no-default-features --features crypto-openssl,tls-openssl,macro" - limited_crypto: false - exclude: - # OpenSSL requires native compilation - exclude from cross-compile targets - - os: ubuntu-latest - crypto: - name: crypto-openssl+tls-openssl - - os: ubuntu-24.04-arm - crypto: - name: crypto-openssl+tls-openssl - - os: windows-latest - crypto: - name: crypto-openssl+tls-openssl - # Graviola only supports aarch64 - - os: ubuntu-latest - crypto: - name: crypto-graviola+tls-graviola - - os: ubuntu-latest - crypto: - name: crypto-graviola-rust+tls-graviola - - os: windows-latest - crypto: - name: crypto-graviola+tls-graviola - - os: windows-latest - crypto: - name: crypto-graviola-rust+tls-graviola include: - os: windows-latest platform: windows @@ -118,19 +63,19 @@ jobs: platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} - cargo_features: ${{ matrix.crypto.features }} - limited_crypto: ${{ matrix.crypto.limited_crypto }} - modules: needs: - check strategy: + fail-fast: false matrix: os: - ubuntu-latest + - ubuntu-24.04-arm + - macos-13 - macos-latest - windows-latest - tls: + crypto: - ring - aws-lc - graviola @@ -140,6 +85,14 @@ jobs: platform: linux arch: x86_64 toolchain: stable + - os: ubuntu-24.04-arm + platform: linux + arch: aarch64 + toolchain: stable + - os: macos-13 + platform: darwin + arch: x86_64 + toolchain: stable - os: macos-latest platform: darwin arch: aarch64 @@ -148,10 +101,17 @@ jobs: platform: windows arch: x86_64 toolchain: stable-x86_64-pc-windows-gnu + exclude: + # aws-lc has issues on Windows + - os: windows-latest + crypto: aws-lc + # graviola doesn't work on Windows + - os: windows-latest + crypto: graviola uses: ./.github/workflows/build-modules.yml with: os: ${{ matrix.os }} platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} - crypto: ${{ matrix.tls }} + crypto: ${{ matrix.crypto }} From 77205411839156db96f5cd6149975dc19363f170 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 10:27:16 +0100 Subject: [PATCH 31/77] Add crypto tests to build as well --- .github/workflows/build.yml | 33 ++++++++++++++++++++++++++++++++- .github/workflows/ci.yml | 21 ++++++++++++++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e50b4ade74..03ed3f2ed5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,11 +18,15 @@ on: required: false type: string default: "nightly" + crypto: + required: false + type: string + default: "" jobs: build: runs-on: ${{ inputs.os }} - name: build ${{ inputs.arch }}-${{ inputs.platform }} + name: build ${{ inputs.arch }}-${{ inputs.platform }}${{ inputs.crypto && format('-{0}', inputs.crypto) || '' }} steps: - name: Checkout uses: actions/checkout@v6 @@ -79,13 +83,40 @@ jobs: uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ inputs.toolchain }} + - name: Map crypto to features + id: features + if: inputs.crypto != '' + shell: bash + run: | + case "${{ inputs.crypto }}" in + graviola) + echo "cargo_features=--no-default-features --features lambda,macro,crypto-graviola-rust,tls-graviola" >> $GITHUB_OUTPUT + ;; + ring) + echo "cargo_features=--no-default-features --features lambda,macro,crypto-ring-rust,tls-ring" >> $GITHUB_OUTPUT + ;; + openssl) + echo "cargo_features=--no-default-features --features lambda,macro,crypto-openssl,tls-openssl" >> $GITHUB_OUTPUT + ;; + aws-lc) + echo "cargo_features=--no-default-features --features lambda,macro,crypto-rust,tls-aws-lc" >> $GITHUB_OUTPUT + ;; + *) + echo "Unknown crypto: ${{ inputs.crypto }}" + exit 1 + ;; + esac - name: Run tests if: inputs.platform != 'windows' + env: + CARGO_FEATURES: ${{ steps.features.outputs.cargo_features }} run: | make test-ci 2>&1 - name: Run tests on windows if: inputs.platform == 'windows' shell: msys2 {0} + env: + CARGO_FEATURES: ${{ steps.features.outputs.cargo_features }} run: | make test-ci 2>&1 - name: Build Linux binaries diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cfd0d80540..281b066a7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,17 @@ jobs: strategy: fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} matrix: + os: + - ubuntu-latest + - ubuntu-24.04-arm + - macos-13 + - macos-latest + - windows-latest + crypto: + - ring + - aws-lc + - graviola + - openssl include: - os: windows-latest platform: windows @@ -49,7 +60,7 @@ jobs: platform: linux arch: aarch64 toolchain: nightly - - os: macos-latest + - os: macos-13 platform: darwin arch: x86_64 toolchain: nightly @@ -57,12 +68,20 @@ jobs: platform: darwin arch: aarch64 toolchain: nightly + exclude: + # aws-lc has issues on Windows + - os: windows-latest + crypto: aws-lc + # graviola doesn't work on Windows + - os: windows-latest + crypto: graviola uses: ./.github/workflows/build.yml with: os: ${{ matrix.os }} platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} + crypto: ${{ matrix.crypto }} modules: needs: - check From fc22f3cb1c798ac002fe8b42e80f1e9006df9a76 Mon Sep 17 00:00:00 2001 From: richarddavison <89518095+richarddavison@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:39:12 +0100 Subject: [PATCH 32/77] Potential fix for code scanning alert no. 13: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/build-modules.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index 7456fcf610..8a8f1da937 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -1,4 +1,6 @@ name: Setup, Build & Test modules +permissions: + contents: read on: workflow_call: inputs: From 530f2f0186b1aa769633001971212166251a53eb Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 10:41:40 +0100 Subject: [PATCH 33/77] Trigger CI From f93ba64f424922d067a41b6a450623a41d8d8922 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 10:55:24 +0100 Subject: [PATCH 34/77] Fix CI matrix - use explicit includes --- .github/workflows/ci.yml | 186 +++++++++++++++++++++++++++++++-------- 1 file changed, 150 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 281b066a7f..8809799255 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,45 +36,102 @@ jobs: strategy: fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} matrix: - os: - - ubuntu-latest - - ubuntu-24.04-arm - - macos-13 - - macos-latest - - windows-latest - crypto: - - ring - - aws-lc - - graviola - - openssl include: - - os: windows-latest - platform: windows + # Ubuntu x64 + - os: ubuntu-latest + platform: linux arch: x86_64 - toolchain: nightly-x86_64-pc-windows-gnu + toolchain: nightly + crypto: ring + - os: ubuntu-latest + platform: linux + arch: x86_64 + toolchain: nightly + crypto: aws-lc + - os: ubuntu-latest + platform: linux + arch: x86_64 + toolchain: nightly + crypto: graviola - os: ubuntu-latest platform: linux arch: x86_64 toolchain: nightly + crypto: openssl + # Ubuntu arm64 - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: nightly + crypto: ring + - os: ubuntu-24.04-arm + platform: linux + arch: aarch64 + toolchain: nightly + crypto: aws-lc + - os: ubuntu-24.04-arm + platform: linux + arch: aarch64 + toolchain: nightly + crypto: graviola + - os: ubuntu-24.04-arm + platform: linux + arch: aarch64 + toolchain: nightly + crypto: openssl + # macOS x64 + - os: macos-13 + platform: darwin + arch: x86_64 + toolchain: nightly + crypto: ring + - os: macos-13 + platform: darwin + arch: x86_64 + toolchain: nightly + crypto: aws-lc + - os: macos-13 + platform: darwin + arch: x86_64 + toolchain: nightly + crypto: graviola - os: macos-13 platform: darwin arch: x86_64 toolchain: nightly + crypto: openssl + # macOS arm64 + - os: macos-latest + platform: darwin + arch: aarch64 + toolchain: nightly + crypto: ring - os: macos-latest platform: darwin arch: aarch64 toolchain: nightly - exclude: - # aws-lc has issues on Windows - - os: windows-latest crypto: aws-lc - # graviola doesn't work on Windows - - os: windows-latest + - os: macos-latest + platform: darwin + arch: aarch64 + toolchain: nightly crypto: graviola + - os: macos-latest + platform: darwin + arch: aarch64 + toolchain: nightly + crypto: openssl + # Windows x64 (no aws-lc, no graviola) + - os: windows-latest + platform: windows + arch: x86_64 + toolchain: nightly-x86_64-pc-windows-gnu + crypto: ring + - os: windows-latest + platform: windows + arch: x86_64 + toolchain: nightly-x86_64-pc-windows-gnu + crypto: openssl uses: ./.github/workflows/build.yml with: os: ${{ matrix.os }} @@ -88,45 +145,102 @@ jobs: strategy: fail-fast: false matrix: - os: - - ubuntu-latest - - ubuntu-24.04-arm - - macos-13 - - macos-latest - - windows-latest - crypto: - - ring - - aws-lc - - graviola - - openssl include: + # Ubuntu x64 + - os: ubuntu-latest + platform: linux + arch: x86_64 + toolchain: stable + crypto: ring + - os: ubuntu-latest + platform: linux + arch: x86_64 + toolchain: stable + crypto: aws-lc + - os: ubuntu-latest + platform: linux + arch: x86_64 + toolchain: stable + crypto: graviola - os: ubuntu-latest platform: linux arch: x86_64 toolchain: stable + crypto: openssl + # Ubuntu arm64 - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: stable + crypto: ring + - os: ubuntu-24.04-arm + platform: linux + arch: aarch64 + toolchain: stable + crypto: aws-lc + - os: ubuntu-24.04-arm + platform: linux + arch: aarch64 + toolchain: stable + crypto: graviola + - os: ubuntu-24.04-arm + platform: linux + arch: aarch64 + toolchain: stable + crypto: openssl + # macOS x64 + - os: macos-13 + platform: darwin + arch: x86_64 + toolchain: stable + crypto: ring + - os: macos-13 + platform: darwin + arch: x86_64 + toolchain: stable + crypto: aws-lc + - os: macos-13 + platform: darwin + arch: x86_64 + toolchain: stable + crypto: graviola - os: macos-13 platform: darwin arch: x86_64 toolchain: stable + crypto: openssl + # macOS arm64 + - os: macos-latest + platform: darwin + arch: aarch64 + toolchain: stable + crypto: ring - os: macos-latest platform: darwin arch: aarch64 toolchain: stable + crypto: aws-lc + - os: macos-latest + platform: darwin + arch: aarch64 + toolchain: stable + crypto: graviola + - os: macos-latest + platform: darwin + arch: aarch64 + toolchain: stable + crypto: openssl + # Windows x64 (no aws-lc, no graviola) - os: windows-latest platform: windows arch: x86_64 toolchain: stable-x86_64-pc-windows-gnu - exclude: - # aws-lc has issues on Windows - - os: windows-latest - crypto: aws-lc - # graviola doesn't work on Windows + crypto: ring - os: windows-latest - crypto: graviola + platform: windows + arch: x86_64 + toolchain: stable-x86_64-pc-windows-gnu + crypto: openssl uses: ./.github/workflows/build-modules.yml with: os: ${{ matrix.os }} From dda2642302af51a950c377608ed22024c6eda9ac Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 10:57:49 +0100 Subject: [PATCH 35/77] Fix permissions placement in build-modules.yml --- .github/workflows/build-modules.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index 8a8f1da937..ceebb1e32b 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -1,6 +1,4 @@ name: Setup, Build & Test modules -permissions: - contents: read on: workflow_call: inputs: @@ -21,6 +19,9 @@ on: type: string default: "ring" +permissions: + contents: read + jobs: build: name: ${{ inputs.arch }}-${{ inputs.platform }}-${{ inputs.crypto }} From 30fedfab3dc70015619df4155fc38bec15a32942 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 11:10:27 +0100 Subject: [PATCH 36/77] Trigger CI --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8809799255..d7ab882e5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -248,3 +248,4 @@ jobs: arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} crypto: ${{ matrix.crypto }} + From 89b06e00bc866fc43f4139d77493cd0d079b4abd Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 11:12:15 +0100 Subject: [PATCH 37/77] Revert ci.yml to main - crypto matrix needs build.yml merged first --- .github/workflows/ci.yml | 164 ++------------------------------------- 1 file changed, 5 insertions(+), 159 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7ab882e5d..8adc83ff60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,215 +37,61 @@ jobs: fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} matrix: include: - # Ubuntu x64 - - os: ubuntu-latest - platform: linux - arch: x86_64 - toolchain: nightly - crypto: ring - - os: ubuntu-latest - platform: linux - arch: x86_64 - toolchain: nightly - crypto: aws-lc - - os: ubuntu-latest - platform: linux + - os: windows-latest + platform: windows arch: x86_64 - toolchain: nightly - crypto: graviola + toolchain: nightly-x86_64-pc-windows-gnu - os: ubuntu-latest platform: linux arch: x86_64 toolchain: nightly - crypto: openssl - # Ubuntu arm64 - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: nightly - crypto: ring - - os: ubuntu-24.04-arm - platform: linux - arch: aarch64 - toolchain: nightly - crypto: aws-lc - - os: ubuntu-24.04-arm - platform: linux - arch: aarch64 - toolchain: nightly - crypto: graviola - - os: ubuntu-24.04-arm - platform: linux - arch: aarch64 - toolchain: nightly - crypto: openssl - # macOS x64 - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: nightly - crypto: ring - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: nightly - crypto: aws-lc - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: nightly - crypto: graviola - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: nightly - crypto: openssl - # macOS arm64 - - os: macos-latest - platform: darwin - arch: aarch64 - toolchain: nightly - crypto: ring - os: macos-latest platform: darwin - arch: aarch64 - toolchain: nightly - crypto: aws-lc - - os: macos-latest - platform: darwin - arch: aarch64 + arch: x86_64 toolchain: nightly - crypto: graviola - os: macos-latest platform: darwin arch: aarch64 toolchain: nightly - crypto: openssl - # Windows x64 (no aws-lc, no graviola) - - os: windows-latest - platform: windows - arch: x86_64 - toolchain: nightly-x86_64-pc-windows-gnu - crypto: ring - - os: windows-latest - platform: windows - arch: x86_64 - toolchain: nightly-x86_64-pc-windows-gnu - crypto: openssl uses: ./.github/workflows/build.yml with: os: ${{ matrix.os }} platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} - crypto: ${{ matrix.crypto }} modules: needs: - check strategy: - fail-fast: false matrix: include: - # Ubuntu x64 - - os: ubuntu-latest - platform: linux - arch: x86_64 - toolchain: stable - crypto: ring - - os: ubuntu-latest - platform: linux - arch: x86_64 - toolchain: stable - crypto: aws-lc - - os: ubuntu-latest - platform: linux - arch: x86_64 - toolchain: stable - crypto: graviola - os: ubuntu-latest platform: linux arch: x86_64 toolchain: stable - crypto: openssl - # Ubuntu arm64 - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: stable - crypto: ring - - os: ubuntu-24.04-arm - platform: linux - arch: aarch64 - toolchain: stable - crypto: aws-lc - - os: ubuntu-24.04-arm - platform: linux - arch: aarch64 - toolchain: stable - crypto: graviola - - os: ubuntu-24.04-arm - platform: linux - arch: aarch64 - toolchain: stable - crypto: openssl - # macOS x64 - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: stable - crypto: ring - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: stable - crypto: aws-lc - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: stable - crypto: graviola - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: stable - crypto: openssl - # macOS arm64 - - os: macos-latest - platform: darwin - arch: aarch64 - toolchain: stable - crypto: ring - os: macos-latest platform: darwin - arch: aarch64 - toolchain: stable - crypto: aws-lc - - os: macos-latest - platform: darwin - arch: aarch64 + arch: x86_64 toolchain: stable - crypto: graviola - os: macos-latest platform: darwin arch: aarch64 toolchain: stable - crypto: openssl - # Windows x64 (no aws-lc, no graviola) - - os: windows-latest - platform: windows - arch: x86_64 - toolchain: stable-x86_64-pc-windows-gnu - crypto: ring - os: windows-latest platform: windows arch: x86_64 toolchain: stable-x86_64-pc-windows-gnu - crypto: openssl uses: ./.github/workflows/build-modules.yml with: os: ${{ matrix.os }} platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} - crypto: ${{ matrix.crypto }} - From 9b9e3122d0f3ffc142729f4519d6c590cfb31219 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 11:13:37 +0100 Subject: [PATCH 38/77] Revert all workflows to main - will add crypto matrix after merge --- .github/workflows/build-modules.yml | 56 +++-------------------------- .github/workflows/build.yml | 33 +---------------- 2 files changed, 5 insertions(+), 84 deletions(-) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index ceebb1e32b..b18a95c191 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -14,17 +14,10 @@ on: toolchain: required: true type: string - crypto: - required: false - type: string - default: "ring" - -permissions: - contents: read jobs: build: - name: ${{ inputs.arch }}-${{ inputs.platform }}-${{ inputs.crypto }} + name: ${{ inputs.arch }}-${{ inputs.platform }} runs-on: ${{ inputs.os }} steps: - name: Checkout @@ -33,60 +26,19 @@ jobs: uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ inputs.toolchain }} - - name: Map crypto feature to TLS and crypto features - id: features - shell: bash - run: | - case "${{ inputs.crypto }}" in - graviola) - echo "tls_feature=tls-graviola" >> $GITHUB_OUTPUT - echo "crypto_feature=crypto-graviola" >> $GITHUB_OUTPUT - ;; - ring) - echo "tls_feature=tls-ring" >> $GITHUB_OUTPUT - echo "crypto_feature=crypto-ring" >> $GITHUB_OUTPUT - ;; - openssl) - echo "tls_feature=tls-openssl" >> $GITHUB_OUTPUT - echo "crypto_feature=crypto-openssl" >> $GITHUB_OUTPUT - ;; - aws-lc) - echo "tls_feature=tls-aws-lc" >> $GITHUB_OUTPUT - echo "crypto_feature=crypto-ring" >> $GITHUB_OUTPUT - ;; - *) - echo "Unknown crypto feature: ${{ inputs.crypto }}" - exit 1 - ;; - esac - name: Run build crates shell: bash env: RUSTFLAGS: "" - TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} - CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} run: | crates=$(cargo metadata --no-deps --format-version 1 --quiet | jq -r '.packages[] | select(.manifest_path | contains("modules/")) | .name') for crate in $crates; do echo "Compiling crate: $crate" - # Use TLS feature for crates that need TLS, with --no-default-features to avoid conflicts - if [ "$crate" = "llrt_fetch" ] || [ "$crate" = "llrt_http" ]; then - cargo build -p "$crate" --no-default-features --features "http1,http2,webpki-roots,compression-rust,$TLS_FEATURE" - elif [ "$crate" = "llrt_crypto" ]; then - cargo build -p "$crate" --no-default-features --features "$CRYPTO_FEATURE" - else - cargo build -p "$crate" - fi + cargo build -p "$crate" done - name: Run build all - env: - TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} - CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} run: | - cargo build -p llrt_modules --no-default-features --features "base,$TLS_FEATURE,$CRYPTO_FEATURE" + cargo build -p llrt_modules - name: Run tests all - env: - TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} - CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} run: | - cargo test -p llrt_modules --no-default-features --features "base,$TLS_FEATURE,$CRYPTO_FEATURE" + cargo test -p llrt_modules diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03ed3f2ed5..e50b4ade74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,15 +18,11 @@ on: required: false type: string default: "nightly" - crypto: - required: false - type: string - default: "" jobs: build: runs-on: ${{ inputs.os }} - name: build ${{ inputs.arch }}-${{ inputs.platform }}${{ inputs.crypto && format('-{0}', inputs.crypto) || '' }} + name: build ${{ inputs.arch }}-${{ inputs.platform }} steps: - name: Checkout uses: actions/checkout@v6 @@ -83,40 +79,13 @@ jobs: uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ inputs.toolchain }} - - name: Map crypto to features - id: features - if: inputs.crypto != '' - shell: bash - run: | - case "${{ inputs.crypto }}" in - graviola) - echo "cargo_features=--no-default-features --features lambda,macro,crypto-graviola-rust,tls-graviola" >> $GITHUB_OUTPUT - ;; - ring) - echo "cargo_features=--no-default-features --features lambda,macro,crypto-ring-rust,tls-ring" >> $GITHUB_OUTPUT - ;; - openssl) - echo "cargo_features=--no-default-features --features lambda,macro,crypto-openssl,tls-openssl" >> $GITHUB_OUTPUT - ;; - aws-lc) - echo "cargo_features=--no-default-features --features lambda,macro,crypto-rust,tls-aws-lc" >> $GITHUB_OUTPUT - ;; - *) - echo "Unknown crypto: ${{ inputs.crypto }}" - exit 1 - ;; - esac - name: Run tests if: inputs.platform != 'windows' - env: - CARGO_FEATURES: ${{ steps.features.outputs.cargo_features }} run: | make test-ci 2>&1 - name: Run tests on windows if: inputs.platform == 'windows' shell: msys2 {0} - env: - CARGO_FEATURES: ${{ steps.features.outputs.cargo_features }} run: | make test-ci 2>&1 - name: Build Linux binaries From 231fa5d6539234b4ae82d59295d75af39a56eb8d Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 11:40:15 +0100 Subject: [PATCH 39/77] Reset Cargo.lock to fix RustCrypto version conflicts --- Cargo.lock | 331 +++++++++++++++++++++++++---------------------------- 1 file changed, 156 insertions(+), 175 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a772713d6..50a5c77ac2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,9 +10,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" -version = "0.6.0-rc.8" +version = "0.6.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b86f658b0f536411ee61c10cec8376f83375b0c98bdc6a640e249f01549d0" +checksum = "03d2d54c4d9e7006f132f615a167865bff927a79ca63d8f637237575ce0a9795" dependencies = [ "crypto-common", "inout", @@ -124,9 +124,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.2" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ "event-listener", "event-listener-strategy", @@ -173,12 +173,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b59d472eab27ade8d770dcb11da7201c11234bef9f82ce7aa517be028d462b" -[[package]] -name = "base16ct" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" - [[package]] name = "base64" version = "0.22.1" @@ -197,9 +191,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.8.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bindgen" @@ -238,9 +232,9 @@ dependencies = [ [[package]] name = "block-padding" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +checksum = "41d28ed5f5f65056148fd25e1a596b5b6d9e772270abf9a9085d7cbfbf26c563" dependencies = [ "hybrid-array", ] @@ -286,9 +280,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" @@ -313,9 +307,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.54" +version = "1.2.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" dependencies = [ "find-msvc-tools", "jobserver", @@ -346,7 +340,7 @@ checksum = "99cbf41c6ec3c4b9eaf7f8f5c11a72cd7d3aa0428125c20d5ef4d09907a0f019" dependencies = [ "cfg-if", "cpufeatures", - "rand_core 0.10.0-rc-6", + "rand_core 0.10.0-rc-2", ] [[package]] @@ -401,9 +395,9 @@ dependencies = [ [[package]] name = "cipher" -version = "0.5.0-rc.6" +version = "0.5.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eba4d87abf4032a6d927f84b71af5086128a3349b929b4501c51a0fe0981a937" +checksum = "155e4a260750fa4f7754649f049748aacc31db238a358d85fd721002f230f92f" dependencies = [ "block-buffer", "crypto-common", @@ -423,18 +417,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.55" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e34525d5bbbd55da2bb745d34b36121baac88d07619a9a09cfcf4a6c0832785" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.55" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a20016a20a3da95bef50ec7238dbd09baeef4311dcdd38ec15aba69812fb61" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstyle", "clap_lex", @@ -442,15 +436,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" dependencies = [ "cc", ] @@ -512,12 +506,6 @@ dependencies = [ "rand 0.9.2", ] -[[package]] -name = "cpubits" -version = "0.1.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "251aaca52dbc19119279d9364222e188ffdf48006791040bfd002617ab0ebc41" - [[package]] name = "cpufeatures" version = "0.2.17" @@ -674,7 +662,7 @@ checksum = "6715836b4946e8585016e80b79c7561476aff3b22f7b756778e7b109d86086c6" dependencies = [ "hybrid-array", "num-traits", - "rand_core 0.10.0-rc-6", + "rand_core 0.10.0-rc-2", "serdect", "subtle", "zeroize", @@ -697,7 +685,7 @@ checksum = "fdd9b2855017318a49714c07ee8895b89d3510d54fa6d86be5835de74c389609" dependencies = [ "crypto-bigint", "libm", - "rand_core 0.10.0-rc-6", + "rand_core 0.10.0-rc-2", ] [[package]] @@ -711,9 +699,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "5.0.0-pre.5" +version = "5.0.0-pre.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a434aec7908df6ca86cda069864d7686aea8afad979aadc9e30e50ac3e40b45a" +checksum = "92419e1cdc506051ffd30713ad09d0ec6a24bba9197e12989de389e35b19c77a" dependencies = [ "cfg-if", "cpufeatures", @@ -801,9 +789,9 @@ dependencies = [ [[package]] name = "dlopen2" -version = "0.8.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff" dependencies = [ "dlopen2_derive", "libc", @@ -813,9 +801,9 @@ dependencies = [ [[package]] name = "dlopen2_derive" -version = "0.4.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" dependencies = [ "proc-macro2", "quote", @@ -863,13 +851,13 @@ version = "0.14.0-rc.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ecd2903524729de5d0cba7589121744513feadd56d71980cb480c48caceb11" dependencies = [ - "base16ct 0.3.0", + "base16ct", "crypto-bigint", "digest", "hkdf", "hybrid-array", "pkcs8", - "rand_core 0.10.0-rc-6", + "rand_core 0.10.0-rc-2", "rustcrypto-ff", "rustcrypto-group", "sec1", @@ -927,9 +915,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "flate2" @@ -1084,9 +1072,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -1107,9 +1095,9 @@ dependencies = [ [[package]] name = "ghash" -version = "0.6.0-rc.4" +version = "0.6.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2450dbd372f0b86224cdb9220351b1d5384e0aa0d29a50defd22b29a12f5e50" +checksum = "333de57ed9494a40df4bbb866752b100819dde0d18f2264c48f5a08a85fe673d" dependencies = [ "polyval", ] @@ -1132,9 +1120,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -1235,18 +1223,18 @@ dependencies = [ [[package]] name = "hkdf" -version = "0.13.0-rc.4" +version = "0.13.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1493605868fc7d216afa78a26956d56f5c0a12dbdb8ee4fe9e0b70a28ec7d57" +checksum = "cfbb4225acf2b5cc4e12d384672cd6d1f0cb980ff5859ffcf144db25b593a24d" dependencies = [ "hmac", ] [[package]] name = "hmac" -version = "0.13.0-rc.4" +version = "0.13.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9956e202a691c5c86c60303a421f66f93f44b29433407b7c43cf2bebadc750e" +checksum = "f1c597ac7d6cc8143e30e83ef70915e7f883b18d8bec2e2b2bce47f5bbb06d57" dependencies = [ "digest", ] @@ -1307,9 +1295,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41fb3dc24fe72c2e3a4685eed55917c2fb228851257f4a8f2d985da9443c3e5" +checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" dependencies = [ "subtle", "typenum", @@ -1397,9 +1385,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.65" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1467,9 +1455,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ "icu_collections", "icu_locale_core", @@ -1481,9 +1469,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" @@ -1529,9 +1517,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", @@ -1539,9 +1527,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +checksum = "c7357b6e7aa75618c7864ebd0634b115a7218b0615f4cb1df33ac3eca23943d4" dependencies = [ "block-padding", "hybrid-array", @@ -1574,9 +1562,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1690,9 +1678,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.16" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" @@ -2420,15 +2408,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "md-5" -version = "0.11.0-rc.4" +version = "0.11.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340c5c3970c137330f5289dc52a6f6c24db6d6839b6121efdeeb120061c30313" +checksum = "64dd2c9099caf8e29b629305199dddb1c6d981562b62c089afea54b0b4b5c333" dependencies = [ "cfg-if", "digest", @@ -2458,9 +2446,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", "log", @@ -2480,9 +2468,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" dependencies = [ "winapi", ] @@ -2584,15 +2572,15 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" [[package]] name = "openssl-src" -version = "300.5.4+3.5.4" +version = "300.5.5+3.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" dependencies = [ "cc", ] @@ -2649,7 +2637,7 @@ version = "0.14.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75296e7cb5d53c8a5083ff26b5707177962cd5851af961a56316e863f1ea757c" dependencies = [ - "base16ct 0.3.0", + "base16ct", "ecdsa", "elliptic-curve", "primefield", @@ -2826,9 +2814,9 @@ dependencies = [ [[package]] name = "pkcs8" -version = "0.11.0-rc.10" +version = "0.11.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b226d2cc389763951db8869584fd800cbbe2962bf454e2edeb5172b31ee99774" +checksum = "77089aec8290d0b7bb01b671b091095cf1937670725af4fd73d47249f03b12c0" dependencies = [ "der", "spki", @@ -2842,11 +2830,11 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polyval" -version = "0.7.0-rc.6" +version = "0.7.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c26fb288ad859274ad7e33746324ab027f70802d54bfadf07732b93bbe3ef7" +checksum = "1ad60831c19edda4b20878a676595c357e93a9b4e6dca2ba98d75b01066b317b" dependencies = [ - "cpubits", + "cfg-if", "cpufeatures", "universal-hash", ] @@ -2886,7 +2874,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c3ad342f52c70a953d95acb09a55450fdc07c2214283b81536c3f83f714568e" dependencies = [ "crypto-bigint", - "rand_core 0.10.0-rc-6", + "rand_core 0.10.0-rc-2", "rustcrypto-ff", "subtle", "zeroize", @@ -2934,9 +2922,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -2962,9 +2950,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -2993,7 +2981,7 @@ checksum = "be866deebbade98028b705499827ad6967c8bb1e21f96a2609913c8c076e9307" dependencies = [ "chacha20", "getrandom 0.3.4", - "rand_core 0.10.0-rc-6", + "rand_core 0.10.0-rc-2", ] [[package]] @@ -3017,9 +3005,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.0-rc-6" +version = "0.10.0-rc-2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70765ff7112b0fb2d272d24d9a2f907fc206211304328fe58b2db15a5649ef28" +checksum = "104a23e4e8b77312a823b6b5613edbac78397e2f34320bc7ac4277013ec4478e" [[package]] name = "rayon" @@ -3110,9 +3098,9 @@ dependencies = [ [[package]] name = "rfc6979" -version = "0.5.0-rc.4" +version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a478f26ee9327bce006ff44c03e9a80dba5cedfba171d37c03ca3669e70d461" +checksum = "63b8e2323084c987a72875b2fd682b7307d5cf14d47e3875bb5e89948e8809d4" dependencies = [ "hmac", "subtle", @@ -3126,7 +3114,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.17", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -3203,7 +3191,7 @@ dependencies = [ "pkcs1", "pkcs8", "rand 0.10.0-rc.5", - "rand_core 0.10.0-rc-6", + "rand_core 0.10.0-rc-2", "sha2", "signature", "spki", @@ -3232,7 +3220,7 @@ version = "0.14.0-pre.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9cd37111549306f79b09aa2618e15b1e8241b7178c286821e3dd71579db4db" dependencies = [ - "rand_core 0.10.0-rc-6", + "rand_core 0.10.0-rc-2", "subtle", ] @@ -3242,16 +3230,16 @@ version = "0.14.0-pre.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e394cd734b5f97dfc3484fa42aad7acd912961c2bcd96c99aa05b3d6cab7cafd" dependencies = [ - "rand_core 0.10.0-rc-6", + "rand_core 0.10.0-rc-2", "rustcrypto-ff", "subtle", ] [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", @@ -3309,18 +3297,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "aws-lc-rs", "ring", @@ -3366,11 +3354,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sec1" -version = "0.8.0-rc.13" +version = "0.8.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2400ed44a13193820aa528a19f376c3843141a8ce96ff34b11104cc79763f2" +checksum = "1dff52f6118bc9f0ac974a54a639d499ac26a6cad7a6e39bc0990c19625e793b" dependencies = [ - "base16ct 1.0.0", + "base16ct", "der", "hybrid-array", "subtle", @@ -3438,24 +3426,24 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", + "ryu", "serde", "serde_core", - "zmij", ] [[package]] name = "serdect" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06" +checksum = "d3ef0e35b322ddfaecbc60f34ab448e157e48531288ee49fafbb053696b8ffe2" dependencies = [ - "base16ct 1.0.0", + "base16ct", "serde", ] @@ -3472,9 +3460,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.11.0-rc.4" +version = "0.11.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7535f94fa3339fe9e5e9be6260a909e62af97f6e14b32345ccf79b92b8b81233" +checksum = "19d43dc0354d88b791216bb5c1bfbb60c0814460cc653ae0ebd71f286d0bd927" dependencies = [ "cfg-if", "cpufeatures", @@ -3510,29 +3498,28 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.8" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ - "errno", "libc", ] [[package]] name = "signature" -version = "3.0.0-rc.9" +version = "3.0.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad0ce3b3f8efd7406f22e2ca5d02be21cdf3b3d1d53ab141f784de8965c7c7e" +checksum = "2a0251c9d6468f4ba853b6352b190fb7c1e405087779917c238445eb03993826" dependencies = [ "digest", - "rand_core 0.10.0-rc-6", + "rand_core 0.10.0-rc-2", ] [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simd-json" @@ -3554,9 +3541,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" @@ -3590,9 +3577,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", "windows-sys 0.60.2", @@ -3622,9 +3609,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -3737,9 +3724,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -3750,18 +3737,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", "toml_datetime", @@ -3771,9 +3758,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] @@ -3836,9 +3823,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "universal-hash" -version = "0.6.0-rc.8" +version = "0.6.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82084f2919885ea910502c74f0a362827aaad3d28d142ca97448bfb39c7e271a" +checksum = "9ad6682ddb0189a4d3c2a5c54b8920ab6231ae911db53fc61a0709507bf1713b" dependencies = [ "crypto-common", "subtle", @@ -3939,18 +3926,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -3961,9 +3948,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3971,9 +3958,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ "bumpalo", "proc-macro2", @@ -3984,9 +3971,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] @@ -4002,9 +3989,9 @@ dependencies = [ [[package]] name = "whoami" -version = "2.1.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fae98cf96deed1b7572272dfc777713c249ae40aa1cf8862e091e8b745f5361" +checksum = "ace4d5c7b5ab3d99629156d4e0997edbe98a4beb6d5ba99e2cae830207a81983" dependencies = [ "libredox", ] @@ -4328,9 +4315,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -4360,9 +4347,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.51.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" @@ -4372,12 +4359,12 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "x25519-dalek" -version = "3.0.0-pre.5" +version = "3.0.0-pre.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40dac5a6478dc8baae2031cfc87d966e427a4fe5011e7f90785ff5c8aa9f2d06" +checksum = "8367a41efe370c38fa4af81968298cdd695311791e4797118a1621f04ed75859" dependencies = [ "curve25519-dalek", - "rand_core 0.10.0-rc-6", + "rand_core 0.10.0-rc-2", "zeroize", ] @@ -4406,18 +4393,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.35" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdea86ddd5568519879b8187e1cf04e24fce28f7fe046ceecbce472ff19a2572" +checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.35" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c15e1b46eff7c6c91195752e0eeed8ef040e391cdece7c25376957d5f15df22" +checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26" dependencies = [ "proc-macro2", "quote", @@ -4484,12 +4471,6 @@ dependencies = [ "syn", ] -[[package]] -name = "zmij" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" - [[package]] name = "zstd" version = "0.13.3" From 2c5bcd0ea6c87ced662175011deff8dead15bdc4 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 11:41:42 +0100 Subject: [PATCH 40/77] Add crypto provider matrix to CI Tests all crypto providers (ring, aws-lc, graviola, openssl) on: - Ubuntu x64 and arm64 - macOS x64 and arm64 - Windows x64 (ring and openssl only) Both build and modules jobs now test crypto variants. --- .github/workflows/build-modules.yml | 56 +++++++++- .github/workflows/build.yml | 33 +++++- .github/workflows/ci.yml | 163 +++++++++++++++++++++++++++- 3 files changed, 242 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index b18a95c191..ceebb1e32b 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -14,10 +14,17 @@ on: toolchain: required: true type: string + crypto: + required: false + type: string + default: "ring" + +permissions: + contents: read jobs: build: - name: ${{ inputs.arch }}-${{ inputs.platform }} + name: ${{ inputs.arch }}-${{ inputs.platform }}-${{ inputs.crypto }} runs-on: ${{ inputs.os }} steps: - name: Checkout @@ -26,19 +33,60 @@ jobs: uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ inputs.toolchain }} + - name: Map crypto feature to TLS and crypto features + id: features + shell: bash + run: | + case "${{ inputs.crypto }}" in + graviola) + echo "tls_feature=tls-graviola" >> $GITHUB_OUTPUT + echo "crypto_feature=crypto-graviola" >> $GITHUB_OUTPUT + ;; + ring) + echo "tls_feature=tls-ring" >> $GITHUB_OUTPUT + echo "crypto_feature=crypto-ring" >> $GITHUB_OUTPUT + ;; + openssl) + echo "tls_feature=tls-openssl" >> $GITHUB_OUTPUT + echo "crypto_feature=crypto-openssl" >> $GITHUB_OUTPUT + ;; + aws-lc) + echo "tls_feature=tls-aws-lc" >> $GITHUB_OUTPUT + echo "crypto_feature=crypto-ring" >> $GITHUB_OUTPUT + ;; + *) + echo "Unknown crypto feature: ${{ inputs.crypto }}" + exit 1 + ;; + esac - name: Run build crates shell: bash env: RUSTFLAGS: "" + TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} + CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} run: | crates=$(cargo metadata --no-deps --format-version 1 --quiet | jq -r '.packages[] | select(.manifest_path | contains("modules/")) | .name') for crate in $crates; do echo "Compiling crate: $crate" - cargo build -p "$crate" + # Use TLS feature for crates that need TLS, with --no-default-features to avoid conflicts + if [ "$crate" = "llrt_fetch" ] || [ "$crate" = "llrt_http" ]; then + cargo build -p "$crate" --no-default-features --features "http1,http2,webpki-roots,compression-rust,$TLS_FEATURE" + elif [ "$crate" = "llrt_crypto" ]; then + cargo build -p "$crate" --no-default-features --features "$CRYPTO_FEATURE" + else + cargo build -p "$crate" + fi done - name: Run build all + env: + TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} + CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} run: | - cargo build -p llrt_modules + cargo build -p llrt_modules --no-default-features --features "base,$TLS_FEATURE,$CRYPTO_FEATURE" - name: Run tests all + env: + TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} + CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} run: | - cargo test -p llrt_modules + cargo test -p llrt_modules --no-default-features --features "base,$TLS_FEATURE,$CRYPTO_FEATURE" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e50b4ade74..03ed3f2ed5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,11 +18,15 @@ on: required: false type: string default: "nightly" + crypto: + required: false + type: string + default: "" jobs: build: runs-on: ${{ inputs.os }} - name: build ${{ inputs.arch }}-${{ inputs.platform }} + name: build ${{ inputs.arch }}-${{ inputs.platform }}${{ inputs.crypto && format('-{0}', inputs.crypto) || '' }} steps: - name: Checkout uses: actions/checkout@v6 @@ -79,13 +83,40 @@ jobs: uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ inputs.toolchain }} + - name: Map crypto to features + id: features + if: inputs.crypto != '' + shell: bash + run: | + case "${{ inputs.crypto }}" in + graviola) + echo "cargo_features=--no-default-features --features lambda,macro,crypto-graviola-rust,tls-graviola" >> $GITHUB_OUTPUT + ;; + ring) + echo "cargo_features=--no-default-features --features lambda,macro,crypto-ring-rust,tls-ring" >> $GITHUB_OUTPUT + ;; + openssl) + echo "cargo_features=--no-default-features --features lambda,macro,crypto-openssl,tls-openssl" >> $GITHUB_OUTPUT + ;; + aws-lc) + echo "cargo_features=--no-default-features --features lambda,macro,crypto-rust,tls-aws-lc" >> $GITHUB_OUTPUT + ;; + *) + echo "Unknown crypto: ${{ inputs.crypto }}" + exit 1 + ;; + esac - name: Run tests if: inputs.platform != 'windows' + env: + CARGO_FEATURES: ${{ steps.features.outputs.cargo_features }} run: | make test-ci 2>&1 - name: Run tests on windows if: inputs.platform == 'windows' shell: msys2 {0} + env: + CARGO_FEATURES: ${{ steps.features.outputs.cargo_features }} run: | make test-ci 2>&1 - name: Build Linux binaries diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8adc83ff60..8809799255 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,61 +37,214 @@ jobs: fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} matrix: include: - - os: windows-latest - platform: windows + # Ubuntu x64 + - os: ubuntu-latest + platform: linux arch: x86_64 - toolchain: nightly-x86_64-pc-windows-gnu + toolchain: nightly + crypto: ring + - os: ubuntu-latest + platform: linux + arch: x86_64 + toolchain: nightly + crypto: aws-lc + - os: ubuntu-latest + platform: linux + arch: x86_64 + toolchain: nightly + crypto: graviola - os: ubuntu-latest platform: linux arch: x86_64 toolchain: nightly + crypto: openssl + # Ubuntu arm64 - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: nightly - - os: macos-latest + crypto: ring + - os: ubuntu-24.04-arm + platform: linux + arch: aarch64 + toolchain: nightly + crypto: aws-lc + - os: ubuntu-24.04-arm + platform: linux + arch: aarch64 + toolchain: nightly + crypto: graviola + - os: ubuntu-24.04-arm + platform: linux + arch: aarch64 + toolchain: nightly + crypto: openssl + # macOS x64 + - os: macos-13 platform: darwin arch: x86_64 toolchain: nightly + crypto: ring + - os: macos-13 + platform: darwin + arch: x86_64 + toolchain: nightly + crypto: aws-lc + - os: macos-13 + platform: darwin + arch: x86_64 + toolchain: nightly + crypto: graviola + - os: macos-13 + platform: darwin + arch: x86_64 + toolchain: nightly + crypto: openssl + # macOS arm64 + - os: macos-latest + platform: darwin + arch: aarch64 + toolchain: nightly + crypto: ring - os: macos-latest platform: darwin arch: aarch64 toolchain: nightly + crypto: aws-lc + - os: macos-latest + platform: darwin + arch: aarch64 + toolchain: nightly + crypto: graviola + - os: macos-latest + platform: darwin + arch: aarch64 + toolchain: nightly + crypto: openssl + # Windows x64 (no aws-lc, no graviola) + - os: windows-latest + platform: windows + arch: x86_64 + toolchain: nightly-x86_64-pc-windows-gnu + crypto: ring + - os: windows-latest + platform: windows + arch: x86_64 + toolchain: nightly-x86_64-pc-windows-gnu + crypto: openssl uses: ./.github/workflows/build.yml with: os: ${{ matrix.os }} platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} + crypto: ${{ matrix.crypto }} modules: needs: - check strategy: + fail-fast: false matrix: include: + # Ubuntu x64 + - os: ubuntu-latest + platform: linux + arch: x86_64 + toolchain: stable + crypto: ring - os: ubuntu-latest platform: linux arch: x86_64 toolchain: stable + crypto: aws-lc + - os: ubuntu-latest + platform: linux + arch: x86_64 + toolchain: stable + crypto: graviola + - os: ubuntu-latest + platform: linux + arch: x86_64 + toolchain: stable + crypto: openssl + # Ubuntu arm64 - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: stable - - os: macos-latest + crypto: ring + - os: ubuntu-24.04-arm + platform: linux + arch: aarch64 + toolchain: stable + crypto: aws-lc + - os: ubuntu-24.04-arm + platform: linux + arch: aarch64 + toolchain: stable + crypto: graviola + - os: ubuntu-24.04-arm + platform: linux + arch: aarch64 + toolchain: stable + crypto: openssl + # macOS x64 + - os: macos-13 + platform: darwin + arch: x86_64 + toolchain: stable + crypto: ring + - os: macos-13 + platform: darwin + arch: x86_64 + toolchain: stable + crypto: aws-lc + - os: macos-13 platform: darwin arch: x86_64 toolchain: stable + crypto: graviola + - os: macos-13 + platform: darwin + arch: x86_64 + toolchain: stable + crypto: openssl + # macOS arm64 + - os: macos-latest + platform: darwin + arch: aarch64 + toolchain: stable + crypto: ring - os: macos-latest platform: darwin arch: aarch64 toolchain: stable + crypto: aws-lc + - os: macos-latest + platform: darwin + arch: aarch64 + toolchain: stable + crypto: graviola + - os: macos-latest + platform: darwin + arch: aarch64 + toolchain: stable + crypto: openssl + # Windows x64 (no aws-lc, no graviola) + - os: windows-latest + platform: windows + arch: x86_64 + toolchain: stable-x86_64-pc-windows-gnu + crypto: ring - os: windows-latest platform: windows arch: x86_64 toolchain: stable-x86_64-pc-windows-gnu + crypto: openssl uses: ./.github/workflows/build-modules.yml with: os: ${{ matrix.os }} platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} + crypto: ${{ matrix.crypto }} From cd6ee781500f90ff2c3c9022e03958f3b1c91489 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 11:52:31 +0100 Subject: [PATCH 41/77] Gate --- modules/llrt_fetch/src/fetch.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/llrt_fetch/src/fetch.rs b/modules/llrt_fetch/src/fetch.rs index af66067e7d..e03536347f 100644 --- a/modules/llrt_fetch/src/fetch.rs +++ b/modules/llrt_fetch/src/fetch.rs @@ -484,16 +484,16 @@ mod tests { use std::io::Read; #[cfg(any( - feature = "tls-ring", - feature = "tls-aws-lc", - all(feature = "tls-graviola", target_arch = "x86_64") + feature = "_test-tls-ring", + feature = "_test-tls-aws-lc", + all(feature = "_test-tls-graviola", target_arch = "x86_64") ))] use llrt_http::HttpsModule; use llrt_test::test_async_with; #[cfg(any( - feature = "tls-ring", - feature = "tls-aws-lc", - all(feature = "tls-graviola", target_arch = "x86_64") + feature = "_test-tls-ring", + feature = "_test-tls-aws-lc", + all(feature = "_test-tls-graviola", target_arch = "x86_64") ))] use llrt_test::{call_test, ModuleEvaluator}; use rquickjs::{prelude::Promise, CatchResultExt}; From 7d32b235e23bc171ac53aec3d737e97497777216 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 12:06:07 +0100 Subject: [PATCH 42/77] Use default features for clippy --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8809799255..2fd2534096 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: for i in {1..5}; do echo "console.log(123);" > "bundle/js/test$i.js" done - cargo clippy --all-targets --all-features -- -D warnings + cargo clippy --all-targets -- -D warnings build: needs: - check From 386ba1b8080a109124a6318f14a379403d6e64c8 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 12:18:22 +0100 Subject: [PATCH 43/77] Fix feature --- .github/workflows/build-modules.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index ceebb1e32b..e71d64d4fa 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -69,9 +69,10 @@ jobs: crates=$(cargo metadata --no-deps --format-version 1 --quiet | jq -r '.packages[] | select(.manifest_path | contains("modules/")) | .name') for crate in $crates; do echo "Compiling crate: $crate" - # Use TLS feature for crates that need TLS, with --no-default-features to avoid conflicts - if [ "$crate" = "llrt_fetch" ] || [ "$crate" = "llrt_http" ]; then + if [ "$crate" = "llrt_fetch" ]; then cargo build -p "$crate" --no-default-features --features "http1,http2,webpki-roots,compression-rust,$TLS_FEATURE" + elif [ "$crate" = "llrt_http" ]; then + cargo build -p "$crate" --no-default-features --features "http1,http2,webpki-roots,$TLS_FEATURE" elif [ "$crate" = "llrt_crypto" ]; then cargo build -p "$crate" --no-default-features --features "$CRYPTO_FEATURE" else From c042d2dc04e8563c9e069b153681bc56e31b42e8 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 12:39:04 +0100 Subject: [PATCH 44/77] Temporary disable fail fast --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fd2534096..38fd5fe9e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: needs: - check strategy: - fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} + fail-fast: false #${{ startsWith(github.ref, 'refs/tags/') }} matrix: include: # Ubuntu x64 From 0e47c91ef8b629125706ccdb7eb3d1297e3cc092 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 13:23:21 +0100 Subject: [PATCH 45/77] Enhance TLS support by adding openssl-vendored feature and updating dependencies --- .github/workflows/build-modules.yml | 12 +++++++-- .github/workflows/build.yml | 2 +- .github/workflows/ci.yml | 2 +- libs/llrt_test_tls/Cargo.toml | 3 ++- libs/llrt_test_tls/src/server.rs | 31 +++++++++++++++++++++ modules/llrt_http/src/client.rs | 42 +++++++++++++++++++++++++++++ modules/llrt_http/src/lib.rs | 10 +++++-- 7 files changed, 95 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index e71d64d4fa..196f1fc749 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -49,6 +49,7 @@ jobs: openssl) echo "tls_feature=tls-openssl" >> $GITHUB_OUTPUT echo "crypto_feature=crypto-openssl" >> $GITHUB_OUTPUT + echo "extra_features=openssl-vendored" >> $GITHUB_OUTPUT ;; aws-lc) echo "tls_feature=tls-aws-lc" >> $GITHUB_OUTPUT @@ -65,6 +66,7 @@ jobs: RUSTFLAGS: "" TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} + EXTRA_FEATURES: ${{ steps.features.outputs.extra_features }} run: | crates=$(cargo metadata --no-deps --format-version 1 --quiet | jq -r '.packages[] | select(.manifest_path | contains("modules/")) | .name') for crate in $crates; do @@ -83,11 +85,17 @@ jobs: env: TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} + EXTRA_FEATURES: ${{ steps.features.outputs.extra_features }} run: | - cargo build -p llrt_modules --no-default-features --features "base,$TLS_FEATURE,$CRYPTO_FEATURE" + FEATURES="base,$TLS_FEATURE,$CRYPTO_FEATURE" + [ -n "$EXTRA_FEATURES" ] && FEATURES="$FEATURES,$EXTRA_FEATURES" + cargo build -p llrt_modules --no-default-features --features "$FEATURES" - name: Run tests all env: TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} + EXTRA_FEATURES: ${{ steps.features.outputs.extra_features }} run: | - cargo test -p llrt_modules --no-default-features --features "base,$TLS_FEATURE,$CRYPTO_FEATURE" + FEATURES="base,$TLS_FEATURE,$CRYPTO_FEATURE" + [ -n "$EXTRA_FEATURES" ] && FEATURES="$FEATURES,$EXTRA_FEATURES" + cargo test -p llrt_modules --no-default-features --features "$FEATURES" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03ed3f2ed5..cd6986ee30 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -96,7 +96,7 @@ jobs: echo "cargo_features=--no-default-features --features lambda,macro,crypto-ring-rust,tls-ring" >> $GITHUB_OUTPUT ;; openssl) - echo "cargo_features=--no-default-features --features lambda,macro,crypto-openssl,tls-openssl" >> $GITHUB_OUTPUT + echo "cargo_features=--no-default-features --features lambda,macro,crypto-openssl,tls-openssl,openssl-vendored" >> $GITHUB_OUTPUT ;; aws-lc) echo "cargo_features=--no-default-features --features lambda,macro,crypto-rust,tls-aws-lc" >> $GITHUB_OUTPUT diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38fd5fe9e9..2fd2534096 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: needs: - check strategy: - fail-fast: false #${{ startsWith(github.ref, 'refs/tags/') }} + fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} matrix: include: # Ubuntu x64 diff --git a/libs/llrt_test_tls/Cargo.toml b/libs/llrt_test_tls/Cargo.toml index 3a933365aa..a06630d8fb 100644 --- a/libs/llrt_test_tls/Cargo.toml +++ b/libs/llrt_test_tls/Cargo.toml @@ -22,6 +22,7 @@ http-body-util = { version = "0.1", default-features = false } hyper = { version = "1", features = ["server"], default-features = false } hyper-util = { version = "0.1", features = [ "server-auto", + "tokio", ], default-features = false } http = { version = "1", default-features = false } rustls = { version = "0.23", features = [ @@ -30,5 +31,5 @@ rustls = { version = "0.23", features = [ rustls-graviola = { version = "0.3", optional = true } openssl = { version = "0.10", optional = true } tokio-openssl = { version = "0.6", optional = true } -tokio = { version = "1", features = ["net", "fs"], default-features = false } +tokio = { version = "1", features = ["net", "fs", "rt", "macros"], default-features = false } tokio-rustls = { version = "0.26", default-features = false } diff --git a/libs/llrt_test_tls/src/server.rs b/libs/llrt_test_tls/src/server.rs index 18ef3b5861..e07b0b6d88 100644 --- a/libs/llrt_test_tls/src/server.rs +++ b/libs/llrt_test_tls/src/server.rs @@ -1,10 +1,41 @@ +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] use std::sync::Arc; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use hyper::service::service_fn; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use hyper_util::rt::{TokioExecutor, TokioIo}; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use hyper_util::server::conn::auto::Builder; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use tokio::net::TcpListener; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use crate::MockServerCerts; #[cfg(feature = "tls-ring")] diff --git a/modules/llrt_http/src/client.rs b/modules/llrt_http/src/client.rs index 5e742a3278..50e4cb4527 100644 --- a/modules/llrt_http/src/client.rs +++ b/modules/llrt_http/src/client.rs @@ -1,14 +1,56 @@ +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use std::convert::Infallible; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use bytes::Bytes; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use http_body_util::combinators::BoxBody; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use hyper_util::{ client::legacy::{connect::HttpConnector, Client}, rt::{TokioExecutor, TokioTimer}, }; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use llrt_dns_cache::CachedDnsResolver; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use once_cell::sync::Lazy; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use crate::get_pool_idle_timeout; #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] diff --git a/modules/llrt_http/src/lib.rs b/modules/llrt_http/src/lib.rs index 22fc4e420d..61531ae69a 100644 --- a/modules/llrt_http/src/lib.rs +++ b/modules/llrt_http/src/lib.rs @@ -13,6 +13,12 @@ use rquickjs::{ feature = "tls-openssl" ))] pub use self::agent::Agent; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] pub use self::client::*; pub use self::config::*; @@ -44,14 +50,14 @@ impl ModuleDef for HttpsModule { } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { - export_default(ctx, exports, |default| { + export_default(ctx, exports, |_default| { #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola", feature = "tls-openssl" ))] - Class::::define(default)?; + Class::::define(_default)?; Ok(()) }) From 226a645ce6edf1eb2a07008245ad07a3677eef96 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Wed, 28 Jan 2026 13:43:00 +0100 Subject: [PATCH 46/77] Do not fail fast --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fd2534096..38fd5fe9e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: needs: - check strategy: - fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} + fail-fast: false #${{ startsWith(github.ref, 'refs/tags/') }} matrix: include: # Ubuntu x64 From 389c4ab9294ca24f772d93a124971ab6b1e32f4b Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 08:52:38 +0100 Subject: [PATCH 47/77] Refactor CI workflows to conditionally include openssl-vendored feature and improve Windows support --- .github/workflows/build-modules.yml | 13 +++++-------- .github/workflows/build.yml | 6 +++++- .github/workflows/ci.yml | 16 +++++++++++++--- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index 196f1fc749..60ea3f50d6 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -49,7 +49,9 @@ jobs: openssl) echo "tls_feature=tls-openssl" >> $GITHUB_OUTPUT echo "crypto_feature=crypto-openssl" >> $GITHUB_OUTPUT - echo "extra_features=openssl-vendored" >> $GITHUB_OUTPUT + if [ "${{ inputs.platform }}" != "windows" ]; then + echo "extra_features=,openssl-vendored" >> $GITHUB_OUTPUT + fi ;; aws-lc) echo "tls_feature=tls-aws-lc" >> $GITHUB_OUTPUT @@ -66,7 +68,6 @@ jobs: RUSTFLAGS: "" TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} - EXTRA_FEATURES: ${{ steps.features.outputs.extra_features }} run: | crates=$(cargo metadata --no-deps --format-version 1 --quiet | jq -r '.packages[] | select(.manifest_path | contains("modules/")) | .name') for crate in $crates; do @@ -87,15 +88,11 @@ jobs: CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} EXTRA_FEATURES: ${{ steps.features.outputs.extra_features }} run: | - FEATURES="base,$TLS_FEATURE,$CRYPTO_FEATURE" - [ -n "$EXTRA_FEATURES" ] && FEATURES="$FEATURES,$EXTRA_FEATURES" - cargo build -p llrt_modules --no-default-features --features "$FEATURES" + cargo build -p llrt_modules --no-default-features --features "base,$TLS_FEATURE,$CRYPTO_FEATURE$EXTRA_FEATURES" - name: Run tests all env: TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} EXTRA_FEATURES: ${{ steps.features.outputs.extra_features }} run: | - FEATURES="base,$TLS_FEATURE,$CRYPTO_FEATURE" - [ -n "$EXTRA_FEATURES" ] && FEATURES="$FEATURES,$EXTRA_FEATURES" - cargo test -p llrt_modules --no-default-features --features "$FEATURES" + cargo test -p llrt_modules --no-default-features --features "base,$TLS_FEATURE,$CRYPTO_FEATURE$EXTRA_FEATURES" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd6986ee30..2026831b2f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -96,7 +96,11 @@ jobs: echo "cargo_features=--no-default-features --features lambda,macro,crypto-ring-rust,tls-ring" >> $GITHUB_OUTPUT ;; openssl) - echo "cargo_features=--no-default-features --features lambda,macro,crypto-openssl,tls-openssl,openssl-vendored" >> $GITHUB_OUTPUT + if [ "${{ inputs.platform }}" = "windows" ]; then + echo "cargo_features=--no-default-features --features lambda,macro,crypto-openssl,tls-openssl" >> $GITHUB_OUTPUT + else + echo "cargo_features=--no-default-features --features lambda,macro,crypto-openssl,tls-openssl,openssl-vendored" >> $GITHUB_OUTPUT + fi ;; aws-lc) echo "cargo_features=--no-default-features --features lambda,macro,crypto-rust,tls-aws-lc" >> $GITHUB_OUTPUT diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38fd5fe9e9..4d90bb38cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: needs: - check strategy: - fail-fast: false #${{ startsWith(github.ref, 'refs/tags/') }} + fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} matrix: include: # Ubuntu x64 @@ -121,7 +121,7 @@ jobs: arch: aarch64 toolchain: nightly crypto: openssl - # Windows x64 (no aws-lc, no graviola) + # Windows x64 (ring, openssl, graviola - no aws-lc) - os: windows-latest platform: windows arch: x86_64 @@ -132,6 +132,11 @@ jobs: arch: x86_64 toolchain: nightly-x86_64-pc-windows-gnu crypto: openssl + - os: windows-latest + platform: windows + arch: x86_64 + toolchain: nightly-x86_64-pc-windows-gnu + crypto: graviola uses: ./.github/workflows/build.yml with: os: ${{ matrix.os }} @@ -230,7 +235,7 @@ jobs: arch: aarch64 toolchain: stable crypto: openssl - # Windows x64 (no aws-lc, no graviola) + # Windows x64 (ring, openssl, graviola - no aws-lc) - os: windows-latest platform: windows arch: x86_64 @@ -241,6 +246,11 @@ jobs: arch: x86_64 toolchain: stable-x86_64-pc-windows-gnu crypto: openssl + - os: windows-latest + platform: windows + arch: x86_64 + toolchain: stable-x86_64-pc-windows-gnu + crypto: graviola uses: ./.github/workflows/build-modules.yml with: os: ${{ matrix.os }} From e270c75845792ccf470dcc2f8a4b60946f2d74d5 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 09:08:44 +0100 Subject: [PATCH 48/77] Refactor module imports and streamline feature conditionals for better readability --- modules/llrt_http/src/lib.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/modules/llrt_http/src/lib.rs b/modules/llrt_http/src/lib.rs index 61531ae69a..c276878f03 100644 --- a/modules/llrt_http/src/lib.rs +++ b/modules/llrt_http/src/lib.rs @@ -3,24 +3,21 @@ use llrt_utils::module::{export_default, ModuleInfo}; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, - Class, Ctx, Result, + Ctx, Result, }; +pub use self::config::*; + +mod client; +mod config; + #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola", feature = "tls-openssl" ))] -pub use self::agent::Agent; -#[cfg(any( - feature = "tls-ring", - feature = "tls-aws-lc", - feature = "tls-graviola", - feature = "tls-openssl" -))] -pub use self::client::*; -pub use self::config::*; +mod agent; #[cfg(any( feature = "tls-ring", @@ -28,11 +25,7 @@ pub use self::config::*; feature = "tls-graviola", feature = "tls-openssl" ))] -mod agent; -mod client; -mod config; - -// Here we should also add the http module. +pub use self::{agent::Agent, client::*}; pub struct HttpsModule; @@ -50,15 +43,16 @@ impl ModuleDef for HttpsModule { } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { - export_default(ctx, exports, |_default| { + export_default(ctx, exports, |default| { #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola", feature = "tls-openssl" ))] - Class::::define(_default)?; + rquickjs::Class::::define(default)?; + let _ = default; Ok(()) }) } From ed1a47a7b56b7504f97dedd6ec4ca52d7e1bf40a Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 10:09:26 +0100 Subject: [PATCH 49/77] remove redundant 'lambda' feature from cargo features --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2026831b2f..371f97ad4a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,20 +90,20 @@ jobs: run: | case "${{ inputs.crypto }}" in graviola) - echo "cargo_features=--no-default-features --features lambda,macro,crypto-graviola-rust,tls-graviola" >> $GITHUB_OUTPUT + echo "cargo_features=--no-default-features --features macro,crypto-graviola-rust,tls-graviola" >> $GITHUB_OUTPUT ;; ring) - echo "cargo_features=--no-default-features --features lambda,macro,crypto-ring-rust,tls-ring" >> $GITHUB_OUTPUT + echo "cargo_features=--no-default-features --features macro,crypto-ring-rust,tls-ring" >> $GITHUB_OUTPUT ;; openssl) if [ "${{ inputs.platform }}" = "windows" ]; then - echo "cargo_features=--no-default-features --features lambda,macro,crypto-openssl,tls-openssl" >> $GITHUB_OUTPUT + echo "cargo_features=--no-default-features --features macro,crypto-openssl,tls-openssl" >> $GITHUB_OUTPUT else - echo "cargo_features=--no-default-features --features lambda,macro,crypto-openssl,tls-openssl,openssl-vendored" >> $GITHUB_OUTPUT + echo "cargo_features=--no-default-features --features macro,crypto-openssl,tls-openssl,openssl-vendored" >> $GITHUB_OUTPUT fi ;; aws-lc) - echo "cargo_features=--no-default-features --features lambda,macro,crypto-rust,tls-aws-lc" >> $GITHUB_OUTPUT + echo "cargo_features=--no-default-features --features macro,crypto-rust,tls-aws-lc" >> $GITHUB_OUTPUT ;; *) echo "Unknown crypto: ${{ inputs.crypto }}" From 5f07fea7b3323b42d9874da5c58e1960e881245e Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 10:58:15 +0100 Subject: [PATCH 50/77] Implement OpenSSL-native SubtleCrypto export/import/wrap/unwrap - Add export_key_openssl.rs with OpenSSL-based key export - Add import_key_openssl.rs with OpenSSL-based key import - Add wrapping_openssl.rs with OpenSSL-based key wrapping - Update mod.rs to use OpenSSL implementations when openssl feature enabled - Stubs now only used for ring/graviola-only providers (no openssl, no rustcrypto) --- .../src/subtle/export_key_openssl.rs | 370 ++++++++++++++++++ .../src/subtle/import_key_openssl.rs | 70 ++++ modules/llrt_crypto/src/subtle/mod.rs | 36 +- .../src/subtle/wrapping_openssl.rs | 94 +++++ 4 files changed, 559 insertions(+), 11 deletions(-) create mode 100644 modules/llrt_crypto/src/subtle/export_key_openssl.rs create mode 100644 modules/llrt_crypto/src/subtle/import_key_openssl.rs create mode 100644 modules/llrt_crypto/src/subtle/wrapping_openssl.rs diff --git a/modules/llrt_crypto/src/subtle/export_key_openssl.rs b/modules/llrt_crypto/src/subtle/export_key_openssl.rs new file mode 100644 index 0000000000..b4b4335ef6 --- /dev/null +++ b/modules/llrt_crypto/src/subtle/export_key_openssl.rs @@ -0,0 +1,370 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! OpenSSL-based key export implementation. + +use der::{asn1::BitString, Encode}; +use llrt_encoding::bytes_to_b64_url_safe_string; +use llrt_utils::result::ResultExt; +use openssl::bn::{BigNum, BigNumRef}; +use openssl::ec::EcGroup; +use openssl::nid::Nid; +use openssl::pkey::{Id, PKey, Private, Public}; +use openssl::rsa::Rsa; +use rquickjs::{ArrayBuffer, Class, Ctx, Exception, Object, Result}; +use spki::{AlgorithmIdentifierOwned, SubjectPublicKeyInfoOwned}; + +use super::{ + crypto_key::KeyKind, + key_algorithm::{KeyAlgorithm, KeyFormat}, + CryptoKey, EllipticCurve, +}; + +pub fn algorithm_export_error(ctx: &Ctx<'_>, algorithm: &str, format: &str) -> Result { + Err(Exception::throw_message( + ctx, + &["Export of ", algorithm, " as ", format, " is not supported"].concat(), + )) +} + +pub enum ExportOutput<'js> { + Bytes(Vec), + Object(Object<'js>), +} + +pub async fn subtle_export_key<'js>( + ctx: Ctx<'js>, + format: KeyFormat, + key: Class<'js, CryptoKey>, +) -> Result> { + let key = key.borrow(); + + let export = export_key(&ctx, format, &key)?; + + Ok(match export { + ExportOutput::Bytes(bytes) => ArrayBuffer::new(ctx, bytes)?.into_object(), + ExportOutput::Object(object) => object, + }) +} + +pub fn export_key<'js>( + ctx: &Ctx<'js>, + format: KeyFormat, + key: &CryptoKey, +) -> Result> { + if !key.extractable { + return Err(Exception::throw_type( + ctx, + "The CryptoKey is non extractable", + )); + }; + let bytes = match format { + KeyFormat::Jwk => return Ok(ExportOutput::Object(export_jwk(ctx, key)?)), + KeyFormat::Raw => export_raw(ctx, key), + KeyFormat::Spki => export_spki(ctx, key), + KeyFormat::Pkcs8 => export_pkcs8(ctx, key), + }?; + Ok(ExportOutput::Bytes(bytes)) +} + +fn export_raw(ctx: &Ctx<'_>, key: &CryptoKey) -> Result> { + if key.kind == KeyKind::Private { + return Err(Exception::throw_type( + ctx, + "Private Crypto keys can't be exported as raw format", + )); + }; + if !matches!( + key.algorithm, + KeyAlgorithm::Aes { .. } + | KeyAlgorithm::Ec { .. } + | KeyAlgorithm::Hmac { .. } + | KeyAlgorithm::Rsa { .. } + | KeyAlgorithm::Ed25519 + | KeyAlgorithm::X25519 + ) { + return algorithm_export_error(ctx, &key.name, "raw"); + } + Ok(key.handle.to_vec()) +} + +fn export_pkcs8(ctx: &Ctx<'_>, key: &CryptoKey) -> Result> { + let handle = key.handle.as_ref(); + + if key.kind != KeyKind::Private { + return Err(Exception::throw_type( + ctx, + "Public or Secret Crypto keys can't be exported as pkcs8 format", + )); + } + + match &key.algorithm { + KeyAlgorithm::Ec { .. } => { + // Handle is already PKCS#8 DER + Ok(handle.to_vec()) + }, + KeyAlgorithm::Ed25519 => { + // Handle is already PKCS#8 DER + Ok(handle.to_vec()) + }, + KeyAlgorithm::X25519 => { + // Handle is raw 32-byte secret, wrap in PKCS#8 + let pkey = PKey::private_key_from_raw_bytes(handle, Id::X25519).or_throw(ctx)?; + pkey.private_key_to_der().or_throw(ctx) + }, + KeyAlgorithm::Rsa { .. } => { + // Handle is PKCS#1 DER, convert to PKCS#8 + let rsa = Rsa::::private_key_from_der(handle).or_throw(ctx)?; + let pkey = PKey::from_rsa(rsa).or_throw(ctx)?; + pkey.private_key_to_der().or_throw(ctx) + }, + _ => algorithm_export_error(ctx, &key.name, "pkcs8"), + } +} + +fn export_spki(ctx: &Ctx<'_>, key: &CryptoKey) -> Result> { + if key.kind != KeyKind::Public { + return Err(Exception::throw_type( + ctx, + "Private or Secret Crypto keys can't be exported as spki format", + )); + } + + let public_key_bytes = key.handle.as_ref(); + + match &key.algorithm { + KeyAlgorithm::X25519 => { + let key_info = SubjectPublicKeyInfoOwned { + algorithm: AlgorithmIdentifierOwned { + oid: const_oid::db::rfc8410::ID_X_25519, + parameters: None, + }, + subject_public_key: BitString::from_bytes(public_key_bytes).unwrap(), + }; + Ok(key_info.to_der().unwrap()) + }, + KeyAlgorithm::Ec { curve, .. } => { + let curve_oid = match curve { + EllipticCurve::P256 => const_oid::db::rfc5912::SECP_256_R_1, + EllipticCurve::P384 => const_oid::db::rfc5912::SECP_384_R_1, + EllipticCurve::P521 => const_oid::db::rfc5912::SECP_521_R_1, + }; + + let key_info = SubjectPublicKeyInfoOwned { + algorithm: AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5912::ID_EC_PUBLIC_KEY, + parameters: Some((&curve_oid).into()), + }, + subject_public_key: BitString::from_bytes(public_key_bytes).unwrap(), + }; + Ok(key_info.to_der().unwrap()) + }, + KeyAlgorithm::Ed25519 => { + let key_info = SubjectPublicKeyInfoOwned { + algorithm: AlgorithmIdentifierOwned { + oid: const_oid::db::rfc8410::ID_ED_25519, + parameters: None, + }, + subject_public_key: BitString::from_bytes(public_key_bytes).unwrap(), + }; + Ok(key_info.to_der().unwrap()) + }, + KeyAlgorithm::Rsa { .. } => { + let key_info = SubjectPublicKeyInfoOwned { + algorithm: AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5912::RSA_ENCRYPTION, + parameters: Some(der::asn1::AnyRef::from(der::asn1::Null).into()), + }, + subject_public_key: BitString::from_bytes(public_key_bytes).unwrap(), + }; + Ok(key_info.to_der().unwrap()) + }, + _ => algorithm_export_error(ctx, &key.name, "spki"), + } +} + +fn export_jwk<'js>(ctx: &Ctx<'js>, key: &CryptoKey) -> Result> { + let name = key.name.as_ref(); + let handle = key.handle.as_ref(); + let obj = Object::new(ctx.clone())?; + obj.set("key_ops", key.usages())?; + obj.set("ext", true)?; + + match &key.algorithm { + KeyAlgorithm::Aes { length } => { + let prefix = match length { + 128 => "A128", + 192 => "A192", + 256 => "A256", + _ => unreachable!(), + }; + let suffix = &name[("AES-".len())..]; + let alg = [prefix, suffix].concat(); + + obj.set("kty", "oct")?; + obj.set("k", bytes_to_b64_url_safe_string(handle))?; + obj.set("alg", alg)?; + }, + KeyAlgorithm::Hmac { hash, .. } => { + obj.set("kty", "oct")?; + obj.set("alg", ["HS", &hash.as_str()[4..]].concat())?; + obj.set("k", bytes_to_b64_url_safe_string(handle))?; + }, + KeyAlgorithm::Ec { curve, .. } => { + let nid = curve_to_nid(curve); + let group = EcGroup::from_curve_name(nid).or_throw(ctx)?; + + match key.kind { + KeyKind::Public => { + // Handle is SEC1 uncompressed point - parse it + let mut bn_ctx = openssl::bn::BigNumContext::new().or_throw(ctx)?; + let point = + openssl::ec::EcPoint::from_bytes(&group, handle, &mut bn_ctx).or_throw(ctx)?; + let mut x = BigNum::new().or_throw(ctx)?; + let mut y = BigNum::new().or_throw(ctx)?; + point + .affine_coordinates(&group, &mut x, &mut y, &mut bn_ctx) + .or_throw(ctx)?; + set_ec_public_coords(&obj, &x, &y, curve)?; + }, + KeyKind::Private => { + // Handle is PKCS#8 DER + let pkey = PKey::::private_key_from_der(handle).or_throw(ctx)?; + let ec_key = pkey.ec_key().or_throw(ctx)?; + let private_num = ec_key.private_key(); + let point = ec_key.public_key(); + let mut bn_ctx = openssl::bn::BigNumContext::new().or_throw(ctx)?; + let mut x = BigNum::new().or_throw(ctx)?; + let mut y = BigNum::new().or_throw(ctx)?; + point + .affine_coordinates(&group, &mut x, &mut y, &mut bn_ctx) + .or_throw(ctx)?; + set_ec_public_coords(&obj, &x, &y, curve)?; + obj.set("d", bn_to_b64_padded(private_num, curve_byte_len(curve)))?; + }, + _ => unreachable!(), + } + + obj.set("kty", "EC")?; + obj.set("crv", curve.as_str())?; + }, + KeyAlgorithm::Ed25519 => { + if key.kind == KeyKind::Private { + // Handle is PKCS#8 DER + let pkey = PKey::::private_key_from_der(handle).or_throw(ctx)?; + let raw_private = pkey.raw_private_key().or_throw(ctx)?; + let raw_public = pkey.raw_public_key().or_throw(ctx)?; + set_okp_jwk_props(name, &obj, Some(&raw_private), &raw_public)?; + } else { + // Handle is raw 32-byte public key + set_okp_jwk_props(name, &obj, None, handle)?; + } + }, + KeyAlgorithm::Rsa { hash, .. } => { + let alg_suffix = hash.as_numeric_str(); + let alg_prefix = match name { + "RSASSA-PKCS1-v1_5" => "RS", + "RSA-PSS" => "PS", + "RSA-OAEP" => "RSA-OAEP-", + _ => unreachable!(), + }; + let alg = [alg_prefix, alg_suffix].concat(); + + match key.kind { + KeyKind::Public => { + // Handle is PKCS#1 DER public key + let rsa = Rsa::::public_key_from_der_pkcs1(handle).or_throw(ctx)?; + obj.set("n", bn_to_b64(rsa.n()))?; + obj.set("e", bn_to_b64(rsa.e()))?; + }, + KeyKind::Private => { + // Handle is PKCS#1 DER private key + let rsa = Rsa::::private_key_from_der(handle).or_throw(ctx)?; + obj.set("n", bn_to_b64(rsa.n()))?; + obj.set("e", bn_to_b64(rsa.e()))?; + obj.set("d", bn_to_b64(rsa.d()))?; + obj.set("p", bn_to_b64(rsa.p().unwrap()))?; + obj.set("q", bn_to_b64(rsa.q().unwrap()))?; + obj.set("dp", bn_to_b64(rsa.dmp1().unwrap()))?; + obj.set("dq", bn_to_b64(rsa.dmq1().unwrap()))?; + obj.set("qi", bn_to_b64(rsa.iqmp().unwrap()))?; + }, + _ => unreachable!(), + } + + obj.set("kty", "RSA")?; + obj.set("alg", alg)?; + }, + KeyAlgorithm::X25519 => { + if key.kind == KeyKind::Private { + // Handle is raw 32-byte secret + let pkey = + PKey::private_key_from_raw_bytes(handle, Id::X25519).or_throw(ctx)?; + let raw_public = pkey.raw_public_key().or_throw(ctx)?; + set_okp_jwk_props(name, &obj, Some(handle), &raw_public)?; + } else { + // Handle is raw 32-byte public key + set_okp_jwk_props(name, &obj, None, handle)?; + } + }, + _ => return algorithm_export_error(ctx, &key.name, "jwk"), + }; + + Ok(obj) +} + +fn curve_to_nid(curve: &EllipticCurve) -> Nid { + match curve { + EllipticCurve::P256 => Nid::X9_62_PRIME256V1, + EllipticCurve::P384 => Nid::SECP384R1, + EllipticCurve::P521 => Nid::SECP521R1, + } +} + +fn curve_byte_len(curve: &EllipticCurve) -> usize { + match curve { + EllipticCurve::P256 => 32, + EllipticCurve::P384 => 48, + EllipticCurve::P521 => 66, + } +} + +fn bn_to_b64(bn: &BigNumRef) -> String { + bytes_to_b64_url_safe_string(&bn.to_vec()) +} + +fn bn_to_b64_padded(bn: &BigNumRef, len: usize) -> String { + let mut bytes = bn.to_vec(); + // Pad to expected length + while bytes.len() < len { + bytes.insert(0, 0); + } + bytes_to_b64_url_safe_string(&bytes) +} + +fn set_ec_public_coords( + obj: &Object<'_>, + x: &BigNum, + y: &BigNum, + curve: &EllipticCurve, +) -> Result<()> { + let len = curve_byte_len(curve); + obj.set("x", bn_to_b64_padded(x, len))?; + obj.set("y", bn_to_b64_padded(y, len))?; + Ok(()) +} + +fn set_okp_jwk_props( + crv: &str, + obj: &Object<'_>, + private_key: Option<&[u8]>, + public_key: &[u8], +) -> Result<()> { + obj.set("kty", "OKP")?; + obj.set("crv", crv)?; + obj.set("x", bytes_to_b64_url_safe_string(public_key))?; + if let Some(private_key) = private_key { + obj.set("d", bytes_to_b64_url_safe_string(private_key))?; + } + Ok(()) +} diff --git a/modules/llrt_crypto/src/subtle/import_key_openssl.rs b/modules/llrt_crypto/src/subtle/import_key_openssl.rs new file mode 100644 index 0000000000..57f7887df3 --- /dev/null +++ b/modules/llrt_crypto/src/subtle/import_key_openssl.rs @@ -0,0 +1,70 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! OpenSSL-based key import implementation. + +use llrt_utils::{bytes::ObjectBytes, object::ObjectExt}; +use rquickjs::{Array, Class, Ctx, FromJs, Result, Value}; + +use super::{ + crypto_key::KeyKind, + key_algorithm::{ + KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages, KeyFormat, KeyFormatData, + }, + CryptoKey, +}; + +pub async fn subtle_import_key<'js>( + ctx: Ctx<'js>, + format: KeyFormat, + key_data: Value<'js>, + algorithm: Value<'js>, + extractable: bool, + key_usages: Array<'js>, +) -> Result> { + let format = match format { + KeyFormat::Raw => KeyFormatData::Raw(ObjectBytes::from_js(&ctx, key_data)?), + KeyFormat::Pkcs8 => KeyFormatData::Pkcs8(ObjectBytes::from_js(&ctx, key_data)?), + KeyFormat::Spki => KeyFormatData::Spki(ObjectBytes::from_js(&ctx, key_data)?), + KeyFormat::Jwk => KeyFormatData::Jwk(key_data.into_object_or_throw(&ctx, "keyData")?), + }; + + import_key(ctx, format, algorithm, extractable, key_usages) +} + +pub fn import_key<'js>( + ctx: Ctx<'js>, + format: KeyFormatData<'js>, + algorithm: Value<'js>, + extractable: bool, + key_usages: Array<'js>, +) -> Result> { + let mut kind = KeyKind::Public; + let mut data = Vec::new(); + + let KeyAlgorithmWithUsages { + name, + algorithm: key_algorithm, + public_usages, + private_usages, + } = KeyAlgorithm::from_js( + &ctx, + KeyAlgorithmMode::Import { + kind: &mut kind, + data: &mut data, + format, + }, + algorithm, + key_usages, + )?; + + let usages = match kind { + KeyKind::Public | KeyKind::Secret => public_usages, + KeyKind::Private => private_usages, + }; + + Class::instance( + ctx, + CryptoKey::new(kind, name, extractable, key_algorithm, usages, data), + ) +} diff --git a/modules/llrt_crypto/src/subtle/mod.rs b/modules/llrt_crypto/src/subtle/mod.rs index ec6b69e0be..089783869f 100644 --- a/modules/llrt_crypto/src/subtle/mod.rs +++ b/modules/llrt_crypto/src/subtle/mod.rs @@ -8,16 +8,22 @@ mod encryption; mod encryption_algorithm; #[cfg(feature = "_rustcrypto")] mod export_key; +#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] +mod export_key_openssl; mod generate_key; #[cfg(feature = "_rustcrypto")] mod import_key; -#[cfg(feature = "_rustcrypto")] +#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] +mod import_key_openssl; +#[cfg(any(feature = "_rustcrypto", feature = "_subtle-full"))] mod key_algorithm; mod sign; mod sign_algorithm; mod verify; #[cfg(feature = "_rustcrypto")] mod wrapping; +#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] +mod wrapping_openssl; pub use crypto_key::CryptoKey; pub use derive::subtle_derive_bits; @@ -27,10 +33,14 @@ pub use encryption::subtle_decrypt; pub use encryption::subtle_encrypt; #[cfg(feature = "_rustcrypto")] pub use export_key::subtle_export_key; +#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] +pub use export_key_openssl::subtle_export_key; pub use generate_key::subtle_generate_key; #[cfg(feature = "_rustcrypto")] pub use import_key::subtle_import_key; -#[cfg(feature = "_rustcrypto")] +#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] +pub use import_key_openssl::subtle_import_key; +#[cfg(any(feature = "_rustcrypto", feature = "_subtle-full"))] use key_algorithm::KeyAlgorithm; pub use sign::subtle_sign; pub use verify::subtle_verify; @@ -38,11 +48,15 @@ pub use verify::subtle_verify; pub use wrapping::subtle_unwrap_key; #[cfg(feature = "_rustcrypto")] pub use wrapping::subtle_wrap_key; +#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] +pub use wrapping_openssl::subtle_unwrap_key; +#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] +pub use wrapping_openssl::subtle_wrap_key; -// Stub implementations for limited crypto providers -#[cfg(not(feature = "_rustcrypto"))] +// Stub implementations for limited crypto providers (not openssl, not rustcrypto) +#[cfg(not(any(feature = "_rustcrypto", feature = "_subtle-full")))] mod key_algorithm; -#[cfg(not(feature = "_rustcrypto"))] +#[cfg(not(any(feature = "_rustcrypto", feature = "_subtle-full")))] use key_algorithm::KeyAlgorithm; use llrt_utils::{object::ObjectExt, str_enum}; @@ -177,14 +191,14 @@ pub fn algorithm_not_supported_error(ctx: &Ctx<'_>) -> Result { Err(Exception::throw_message(ctx, "Algorithm not supported")) } -// Stub implementations for when _rustcrypto feature is disabled -#[cfg(not(feature = "_rustcrypto"))] +// Stub implementations for providers without rustcrypto or openssl +#[cfg(not(any(feature = "_rustcrypto", feature = "openssl")))] mod stubs; -#[cfg(not(feature = "_rustcrypto"))] +#[cfg(not(any(feature = "_rustcrypto", feature = "openssl")))] pub use stubs::subtle_export_key; -#[cfg(not(feature = "_rustcrypto"))] +#[cfg(not(any(feature = "_rustcrypto", feature = "openssl")))] pub use stubs::subtle_import_key; -#[cfg(not(feature = "_rustcrypto"))] +#[cfg(not(any(feature = "_rustcrypto", feature = "openssl")))] pub use stubs::subtle_unwrap_key; -#[cfg(not(feature = "_rustcrypto"))] +#[cfg(not(any(feature = "_rustcrypto", feature = "openssl")))] pub use stubs::subtle_wrap_key; diff --git a/modules/llrt_crypto/src/subtle/wrapping_openssl.rs b/modules/llrt_crypto/src/subtle/wrapping_openssl.rs new file mode 100644 index 0000000000..35e0775b36 --- /dev/null +++ b/modules/llrt_crypto/src/subtle/wrapping_openssl.rs @@ -0,0 +1,94 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! OpenSSL-based key wrapping implementation. + +use llrt_json::{parse::json_parse, stringify::json_stringify}; +use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt}; +use rquickjs::{Array, ArrayBuffer, Class, Ctx, Exception, Result, Value}; + +use super::{ + encryption::{self, encrypt_decrypt}, + encryption_algorithm::EncryptionAlgorithm, + export_key_openssl::{export_key, ExportOutput}, + import_key_openssl::import_key, + key_algorithm::{KeyFormat, KeyFormatData}, + CryptoKey, EncryptionMode, +}; + +pub async fn subtle_wrap_key<'js>( + ctx: Ctx<'js>, + format: KeyFormat, + key: Class<'js, CryptoKey>, + wrapping_key: Class<'js, CryptoKey>, + wrap_algo: EncryptionAlgorithm, +) -> Result> { + let key = key.borrow(); + + let export = export_key(&ctx, format, &key)?; + + let (bytes, padding) = match export { + ExportOutput::Bytes(bytes) => (bytes, 0), + ExportOutput::Object(value) => { + let json = json_stringify(&ctx, value.into_value())?.unwrap(); + (json.into_bytes(), b' ') + }, + }; + + let wrapping_key = wrapping_key.borrow(); + wrapping_key.check_validity("wrapKey").or_throw(&ctx)?; + + let bytes = encrypt_decrypt( + &ctx, + &wrap_algo, + &wrapping_key, + &bytes, + EncryptionMode::Wrapping(padding), + encryption::EncryptionOperation::Encrypt, + )?; + + ArrayBuffer::new(ctx, bytes) +} + +pub async fn subtle_unwrap_key<'js>( + format: KeyFormat, + wrapped_key: ArrayBuffer<'js>, + unwrapping_key: Class<'js, CryptoKey>, + unwrap_algo: EncryptionAlgorithm, + unwrapped_key_algo: Value<'js>, + extractable: bool, + key_usages: Array<'js>, +) -> Result> { + let unwrapping_key = unwrapping_key.borrow(); + let ctx = wrapped_key.ctx().clone(); + unwrapping_key.check_validity("unwrapKey").or_throw(&ctx)?; + + let bytes = wrapped_key + .as_bytes() + .ok_or_else(|| Exception::throw_message(&ctx, "ArrayBuffer is detached"))?; + + let padding = match format { + KeyFormat::Jwk => b' ', + _ => 0, + }; + + let bytes = encrypt_decrypt( + &ctx, + &unwrap_algo, + &unwrapping_key, + bytes, + EncryptionMode::Wrapping(padding), + encryption::EncryptionOperation::Decrypt, + )?; + + let key_format = match format { + KeyFormat::Jwk => { + KeyFormatData::Jwk(json_parse(&ctx, bytes)?.into_object_or_throw(&ctx, "wrappedKey")?) + }, + KeyFormat::Raw => KeyFormatData::Raw(ObjectBytes::Vec(bytes)), + KeyFormat::Spki => KeyFormatData::Spki(ObjectBytes::Vec(bytes)), + KeyFormat::Pkcs8 => KeyFormatData::Pkcs8(ObjectBytes::Vec(bytes)), + }; + + import_key(ctx, key_format, unwrapped_key_algo, extractable, key_usages) +} From 175852d2a18677397652ba953274b4f027ec937f Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 10:59:21 +0100 Subject: [PATCH 51/77] Format code --- modules/llrt_crypto/src/subtle/export_key_openssl.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/llrt_crypto/src/subtle/export_key_openssl.rs b/modules/llrt_crypto/src/subtle/export_key_openssl.rs index b4b4335ef6..4e1c71f078 100644 --- a/modules/llrt_crypto/src/subtle/export_key_openssl.rs +++ b/modules/llrt_crypto/src/subtle/export_key_openssl.rs @@ -218,8 +218,8 @@ fn export_jwk<'js>(ctx: &Ctx<'js>, key: &CryptoKey) -> Result> { KeyKind::Public => { // Handle is SEC1 uncompressed point - parse it let mut bn_ctx = openssl::bn::BigNumContext::new().or_throw(ctx)?; - let point = - openssl::ec::EcPoint::from_bytes(&group, handle, &mut bn_ctx).or_throw(ctx)?; + let point = openssl::ec::EcPoint::from_bytes(&group, handle, &mut bn_ctx) + .or_throw(ctx)?; let mut x = BigNum::new().or_throw(ctx)?; let mut y = BigNum::new().or_throw(ctx)?; point @@ -298,8 +298,7 @@ fn export_jwk<'js>(ctx: &Ctx<'js>, key: &CryptoKey) -> Result> { KeyAlgorithm::X25519 => { if key.kind == KeyKind::Private { // Handle is raw 32-byte secret - let pkey = - PKey::private_key_from_raw_bytes(handle, Id::X25519).or_throw(ctx)?; + let pkey = PKey::private_key_from_raw_bytes(handle, Id::X25519).or_throw(ctx)?; let raw_public = pkey.raw_public_key().or_throw(ctx)?; set_okp_jwk_props(name, &obj, Some(handle), &raw_public)?; } else { From 33d60d9cb966b3181bad229c5c2469103fd6f89e Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 11:26:21 +0100 Subject: [PATCH 52/77] Fix OpenSSL EC key format and Windows CI shell - Fix OpenSSL generate_ec_key to return SEC1 public key (consistent with RustCrypto) - Fix OpenSSL generate_ec_key to return PKCS#8 private key (consistent with RustCrypto) - Add shell: bash to build-modules.yml for Windows compatibility --- .github/workflows/build-modules.yml | 2 ++ modules/llrt_crypto/src/provider/openssl.rs | 15 +++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index 60ea3f50d6..bf5a269b39 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -83,6 +83,7 @@ jobs: fi done - name: Run build all + shell: bash env: TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} @@ -90,6 +91,7 @@ jobs: run: | cargo build -p llrt_modules --no-default-features --features "base,$TLS_FEATURE,$CRYPTO_FEATURE$EXTRA_FEATURES" - name: Run tests all + shell: bash env: TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs index 58abab52dd..ce9b6f22f1 100644 --- a/modules/llrt_crypto/src/provider/openssl.rs +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -663,13 +663,20 @@ impl CryptoProvider for OpenSslProvider { let group = get_ec_group(curve)?; let ec_key = EcKey::generate(&group) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; - let private_der = ec_key + let pkey = PKey::from_ec_key(ec_key.clone()) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + // Return PKCS#8 DER for private key (consistent with RustCrypto) + let private_der = pkey .private_key_to_der() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; - let public_der = ec_key - .public_key_to_der() + // Return SEC1 uncompressed point for public key (consistent with RustCrypto) + let mut bn_ctx = openssl::bn::BigNumContext::new() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; - Ok((private_der, public_der)) + let public_sec1 = ec_key + .public_key() + .to_bytes(&group, openssl::ec::PointConversionForm::UNCOMPRESSED, &mut bn_ctx) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + Ok((private_der, public_sec1)) } fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { From c6908cb83463c7e1c03545253c19ee0ac598ab99 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 11:27:55 +0100 Subject: [PATCH 53/77] Format code --- modules/llrt_crypto/src/provider/openssl.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs index ce9b6f22f1..155ffdd61f 100644 --- a/modules/llrt_crypto/src/provider/openssl.rs +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -674,7 +674,11 @@ impl CryptoProvider for OpenSslProvider { .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; let public_sec1 = ec_key .public_key() - .to_bytes(&group, openssl::ec::PointConversionForm::UNCOMPRESSED, &mut bn_ctx) + .to_bytes( + &group, + openssl::ec::PointConversionForm::UNCOMPRESSED, + &mut bn_ctx, + ) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; Ok((private_der, public_sec1)) } From 1495c40860d6593d202d2c0bf6d3f3fcb731ad85 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 11:52:21 +0100 Subject: [PATCH 54/77] Fix OpenSSL import_ec_jwk to return SEC1 for public keys Consistent with generate_ec_key which also returns SEC1 format for public keys. --- modules/llrt_crypto/src/provider/openssl.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs index 155ffdd61f..2ca38a43df 100644 --- a/modules/llrt_crypto/src/provider/openssl.rs +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -1169,12 +1169,19 @@ impl CryptoProvider for OpenSslProvider { is_private: true, }) } else { - let pkey = PKey::from_ec_key(pub_key) + // Return SEC1 uncompressed point for public key (consistent with generate_ec_key) + let mut ctx = openssl::bn::BigNumContext::new() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let sec1 = pub_key + .public_key() + .to_bytes( + &group, + openssl::ec::PointConversionForm::UNCOMPRESSED, + &mut ctx, + ) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::EcImportResult { - key_data: pkey - .public_key_to_der() - .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, + key_data: sec1, is_private: false, }) } From c12175cb92234171c2a02fb9f680015fbf142147 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 14:44:57 +0100 Subject: [PATCH 55/77] Unify export/import/wrap key implementations using provider trait - Add OkpJwkImport/OkpJwkExport structs and import_okp_jwk/export_okp_jwk methods to CryptoProvider trait - Implement OKP JWK methods in RustCrypto, OpenSSL, ring, and graviola providers - Create unified export_key.rs that uses CRYPTO_PROVIDER methods instead of inline RustCrypto code - Remove duplicate export_key_openssl.rs, import_key_openssl.rs, wrapping_openssl.rs - Simplify mod.rs feature gates to use _subtle-full instead of complex conditions - Fix EC private key export to use SecretKey::from_pkcs8_der for correct d value extraction This reduces code duplication by having shared format handling code call provider-specific methods for key component extraction. --- modules/llrt_crypto/src/provider/graviola.rs | 15 + modules/llrt_crypto/src/provider/mod.rs | 44 ++ modules/llrt_crypto/src/provider/openssl.rs | 73 ++++ modules/llrt_crypto/src/provider/ring.rs | 15 + modules/llrt_crypto/src/provider/rust.rs | 141 +++++-- modules/llrt_crypto/src/subtle/export_key.rs | 397 +++++------------- .../src/subtle/export_key_openssl.rs | 369 ---------------- .../src/subtle/import_key_openssl.rs | 70 --- modules/llrt_crypto/src/subtle/mod.rs | 50 +-- .../src/subtle/wrapping_openssl.rs | 94 ----- 10 files changed, 391 insertions(+), 877 deletions(-) delete mode 100644 modules/llrt_crypto/src/subtle/export_key_openssl.rs delete mode 100644 modules/llrt_crypto/src/subtle/import_key_openssl.rs delete mode 100644 modules/llrt_crypto/src/subtle/wrapping_openssl.rs diff --git a/modules/llrt_crypto/src/provider/graviola.rs b/modules/llrt_crypto/src/provider/graviola.rs index f0e41034ad..2349748f0e 100644 --- a/modules/llrt_crypto/src/provider/graviola.rs +++ b/modules/llrt_crypto/src/provider/graviola.rs @@ -475,6 +475,21 @@ impl CryptoProvider for GraviolaProvider { ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } + fn import_okp_jwk( + &self, + _jwk: super::OkpJwkImport<'_>, + _is_ed25519: bool, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_okp_jwk( + &self, + _key_data: &[u8], + _is_private: bool, + _is_ed25519: bool, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } } // Hybrid types for graviola-rust: Graviola for SHA256/384/512, RustCrypto for MD5/SHA1 diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index cbbea505ca..b36cfd4e37 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -107,6 +107,22 @@ pub struct EcJwkExport { pub d: Option>, } +/// OKP (Ed25519/X25519) JWK components for import +#[derive(Debug)] +#[allow(dead_code)] +pub struct OkpJwkImport<'a> { + pub x: &'a [u8], // public key + pub d: Option<&'a [u8]>, // private key +} + +/// OKP JWK components for export +#[derive(Debug)] +#[allow(dead_code)] +pub struct OkpJwkExport { + pub x: Vec, + pub d: Option>, +} + pub trait SimpleDigest: Send { fn update(&mut self, data: &[u8]); fn finalize(self) -> Vec @@ -362,6 +378,19 @@ pub trait CryptoProvider { curve: EllipticCurve, is_private: bool, ) -> Result; + + // OKP JWK import/export + fn import_okp_jwk( + &self, + jwk: OkpJwkImport<'_>, + is_ed25519: bool, + ) -> Result; + fn export_okp_jwk( + &self, + key_data: &[u8], + is_private: bool, + is_ed25519: bool, + ) -> Result; } pub trait HmacProvider: Send { @@ -730,6 +759,21 @@ macro_rules! impl_hybrid_provider { ) -> Result { rust::RustCryptoProvider.export_ec_jwk(d, c, p) } + fn import_okp_jwk( + &self, + j: OkpJwkImport<'_>, + is_ed25519: bool, + ) -> Result { + rust::RustCryptoProvider.import_okp_jwk(j, is_ed25519) + } + fn export_okp_jwk( + &self, + d: &[u8], + is_private: bool, + is_ed25519: bool, + ) -> Result { + rust::RustCryptoProvider.export_okp_jwk(d, is_private, is_ed25519) + } } }; } diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs index 2ca38a43df..fe326eb297 100644 --- a/modules/llrt_crypto/src/provider/openssl.rs +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -1239,4 +1239,77 @@ impl CryptoProvider for OpenSslProvider { }) } } + + fn import_okp_jwk( + &self, + jwk: super::OkpJwkImport<'_>, + is_ed25519: bool, + ) -> Result { + let id = if is_ed25519 { Id::ED25519 } else { Id::X25519 }; + if let Some(d) = jwk.d { + // Private key + let pkey = PKey::private_key_from_raw_bytes(d, id) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + if is_ed25519 { + // Ed25519: return PKCS8 DER + Ok(super::OkpImportResult { + key_data: pkey + .private_key_to_der() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, + is_private: true, + }) + } else { + // X25519: return raw bytes + Ok(super::OkpImportResult { + key_data: d.to_vec(), + is_private: true, + }) + } + } else { + // Public key - store raw bytes + Ok(super::OkpImportResult { + key_data: jwk.x.to_vec(), + is_private: false, + }) + } + } + + fn export_okp_jwk( + &self, + key_data: &[u8], + is_private: bool, + is_ed25519: bool, + ) -> Result { + if is_private { + if is_ed25519 { + // Ed25519: key_data is PKCS8 DER + let pkey = PKey::private_key_from_der(key_data) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let d = pkey + .raw_private_key() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let x = pkey + .raw_public_key() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::OkpJwkExport { x, d: Some(d) }) + } else { + // X25519: key_data is raw 32-byte secret + let pkey = PKey::private_key_from_raw_bytes(key_data, Id::X25519) + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + let x = pkey + .raw_public_key() + .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; + Ok(super::OkpJwkExport { + x, + d: Some(key_data.to_vec()), + }) + } + } else { + // Public key - key_data is raw bytes + Ok(super::OkpJwkExport { + x: key_data.to_vec(), + d: None, + }) + } + } } diff --git a/modules/llrt_crypto/src/provider/ring.rs b/modules/llrt_crypto/src/provider/ring.rs index 413a3b2371..fe71da2400 100644 --- a/modules/llrt_crypto/src/provider/ring.rs +++ b/modules/llrt_crypto/src/provider/ring.rs @@ -519,4 +519,19 @@ impl CryptoProvider for RingProvider { ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } + fn import_okp_jwk( + &self, + _jwk: super::OkpJwkImport<'_>, + _is_ed25519: bool, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + fn export_okp_jwk( + &self, + _key_data: &[u8], + _is_private: bool, + _is_ed25519: bool, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } } diff --git a/modules/llrt_crypto/src/provider/rust.rs b/modules/llrt_crypto/src/provider/rust.rs index 27ea19e2ad..757bac1c07 100644 --- a/modules/llrt_crypto/src/provider/rust.rs +++ b/modules/llrt_crypto/src/provider/rust.rs @@ -1421,46 +1421,43 @@ impl CryptoProvider for RustCryptoProvider { EllipticCurve::P521 => 66, }; if is_private { - // key_data is PKCS8 - use der::Decode; - let pk_info = pkcs8::PrivateKeyInfoRef::from_der(key_data) - .map_err(|_| CryptoError::InvalidKey(None))?; - // Get private key bytes (skip OCTET STRING wrapper if present) - let priv_bytes = pk_info.private_key.as_bytes(); - let d = if priv_bytes.len() > 2 && priv_bytes[0] == 0x04 { - &priv_bytes[2..] - } else { - priv_bytes - }; - // Derive public key to get x, y - let (x, y) = match curve { + // key_data is PKCS8 - use elliptic_curve's SecretKey to parse it + let (x, y, d) = match curve { EllipticCurve::P256 => { - let sk = - P256SecretKey::from_slice(d).map_err(|_| CryptoError::InvalidKey(None))?; + let sk = P256SecretKey::from_pkcs8_der(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?; let pk = sk.public_key(); let pt = pk.to_encoded_point(false); - (pt.x().unwrap().to_vec(), pt.y().unwrap().to_vec()) + ( + pt.x().unwrap().to_vec(), + pt.y().unwrap().to_vec(), + sk.to_bytes().to_vec(), + ) }, EllipticCurve::P384 => { - let sk = - P384SecretKey::from_slice(d).map_err(|_| CryptoError::InvalidKey(None))?; + let sk = P384SecretKey::from_pkcs8_der(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?; let pk = sk.public_key(); let pt = pk.to_encoded_point(false); - (pt.x().unwrap().to_vec(), pt.y().unwrap().to_vec()) + ( + pt.x().unwrap().to_vec(), + pt.y().unwrap().to_vec(), + sk.to_bytes().to_vec(), + ) }, EllipticCurve::P521 => { - let sk = - P521SecretKey::from_slice(d).map_err(|_| CryptoError::InvalidKey(None))?; + let sk = P521SecretKey::from_pkcs8_der(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?; let pk = sk.public_key(); let pt = pk.to_encoded_point(false); - (pt.x().unwrap().to_vec(), pt.y().unwrap().to_vec()) + ( + pt.x().unwrap().to_vec(), + pt.y().unwrap().to_vec(), + sk.to_bytes().to_vec(), + ) }, }; - Ok(super::EcJwkExport { - x, - y, - d: Some(d.to_vec()), - }) + Ok(super::EcJwkExport { x, y, d: Some(d) }) } else { // key_data is SEC1 uncompressed point (0x04 || x || y) if key_data.len() != 1 + 2 * coord_len || key_data[0] != 0x04 { @@ -1471,6 +1468,96 @@ impl CryptoProvider for RustCryptoProvider { Ok(super::EcJwkExport { x, y, d: None }) } } + + fn import_okp_jwk( + &self, + jwk: super::OkpJwkImport<'_>, + is_ed25519: bool, + ) -> Result { + if let Some(d) = jwk.d { + // Private key - for Ed25519 we need PKCS8, for X25519 we store raw + if is_ed25519 { + // Ed25519: construct PKCS8 from raw private key + use der::{ + asn1::{BitStringRef, OctetStringRef}, + Encode, + }; + let pk_info = pkcs8::PrivateKeyInfoRef { + algorithm: spki::AlgorithmIdentifier { + oid: const_oid::db::rfc8410::ID_ED_25519, + parameters: None, + }, + private_key: OctetStringRef::new(d) + .map_err(|_| CryptoError::InvalidKey(None))?, + public_key: Some( + BitStringRef::from_bytes(jwk.x) + .map_err(|_| CryptoError::InvalidKey(None))?, + ), + }; + let der = pk_info + .to_der() + .map_err(|_| CryptoError::InvalidKey(None))?; + Ok(super::OkpImportResult { + key_data: der, + is_private: true, + }) + } else { + // X25519: store raw 32-byte secret + Ok(super::OkpImportResult { + key_data: d.to_vec(), + is_private: true, + }) + } + } else { + // Public key - store raw bytes + Ok(super::OkpImportResult { + key_data: jwk.x.to_vec(), + is_private: false, + }) + } + } + + fn export_okp_jwk( + &self, + key_data: &[u8], + is_private: bool, + is_ed25519: bool, + ) -> Result { + if is_private { + if is_ed25519 { + // Ed25519: key_data is PKCS8 + use der::Decode; + let pk_info = pkcs8::PrivateKeyInfoRef::from_der(key_data) + .map_err(|_| CryptoError::InvalidKey(None))?; + let d = pk_info.private_key.as_bytes(); + let x = pk_info + .public_key + .ok_or(CryptoError::InvalidKey(None))? + .raw_bytes() + .to_vec(); + Ok(super::OkpJwkExport { + x, + d: Some(d.to_vec()), + }) + } else { + // X25519: key_data is raw 32-byte secret + let secret = x25519_dalek::StaticSecret::from( + <[u8; 32]>::try_from(key_data).map_err(|_| CryptoError::InvalidKey(None))?, + ); + let public = x25519_dalek::PublicKey::from(&secret); + Ok(super::OkpJwkExport { + x: public.as_bytes().to_vec(), + d: Some(key_data.to_vec()), + }) + } + } else { + // Public key - key_data is raw bytes + Ok(super::OkpJwkExport { + x: key_data.to_vec(), + d: None, + }) + } + } } // Helper struct for HKDF output length diff --git a/modules/llrt_crypto/src/subtle/export_key.rs b/modules/llrt_crypto/src/subtle/export_key.rs index 24e4b0b7a3..98ee578d50 100644 --- a/modules/llrt_crypto/src/subtle/export_key.rs +++ b/modules/llrt_crypto/src/subtle/export_key.rs @@ -1,25 +1,20 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use der::{ - asn1::{self, BitString, OctetStringRef}, - Decode, Encode, SecretDocument, -}; -use elliptic_curve::{ - sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}, - AffinePoint, CurveArithmetic, FieldBytesSize, -}; + +//! Unified key export implementation using CryptoProvider trait. + use llrt_encoding::bytes_to_b64_url_safe_string; use llrt_utils::result::ResultExt; -use pkcs8::PrivateKeyInfoRef; use rquickjs::{ArrayBuffer, Class, Ctx, Exception, Object, Result}; -use rsa::{ - pkcs1::DecodeRsaPrivateKey, - pkcs8::{AssociatedOid, DecodePrivateKey, EncodePrivateKey}, - RsaPrivateKey, -}; -use spki::{AlgorithmIdentifier, AlgorithmIdentifierOwned, SubjectPublicKeyInfo}; -use crate::subtle::CryptoKey; +use crate::provider::CryptoProvider; +use crate::CRYPTO_PROVIDER; + +use super::{ + crypto_key::KeyKind, + key_algorithm::{KeyAlgorithm, KeyFormat}, + CryptoKey, +}; pub fn algorithm_export_error(ctx: &Ctx<'_>, algorithm: &str, format: &str) -> Result { Err(Exception::throw_message( @@ -28,12 +23,6 @@ pub fn algorithm_export_error(ctx: &Ctx<'_>, algorithm: &str, format: &str) - )) } -use super::{ - crypto_key::KeyKind, - key_algorithm::{EcAlgorithm, KeyAlgorithm, KeyFormat}, - EllipticCurve, -}; - pub enum ExportOutput<'js> { Bytes(Vec), Object(Object<'js>), @@ -45,9 +34,7 @@ pub async fn subtle_export_key<'js>( key: Class<'js, CryptoKey>, ) -> Result> { let key = key.borrow(); - let export = export_key(&ctx, format, &key)?; - Ok(match export { ExportOutput::Bytes(bytes) => ArrayBuffer::new(ctx, bytes)?.into_object(), ExportOutput::Object(object) => object, @@ -64,7 +51,7 @@ pub fn export_key<'js>( ctx, "The CryptoKey is non extractable", )); - }; + } let bytes = match format { KeyFormat::Jwk => return Ok(ExportOutput::Object(export_jwk(ctx, key)?)), KeyFormat::Raw => export_raw(ctx, key), @@ -80,51 +67,53 @@ fn export_raw(ctx: &Ctx<'_>, key: &CryptoKey) -> Result> { ctx, "Private Crypto keys can't be exported as raw format", )); - }; - if !matches!( - key.algorithm, - KeyAlgorithm::Aes { .. } - | KeyAlgorithm::Ec { .. } - | KeyAlgorithm::Hmac { .. } - | KeyAlgorithm::Rsa { .. } - | KeyAlgorithm::Ed25519 - | KeyAlgorithm::X25519 - ) { - return algorithm_export_error(ctx, &key.name, "raw"); } - Ok(key.handle.to_vec()) + match &key.algorithm { + KeyAlgorithm::Aes { .. } | KeyAlgorithm::Hmac { .. } => Ok(key.handle.to_vec()), + KeyAlgorithm::Ec { curve, .. } => CRYPTO_PROVIDER + .export_ec_public_key_sec1(&key.handle, *curve, false) + .or_throw(ctx), + KeyAlgorithm::Ed25519 => CRYPTO_PROVIDER + .export_okp_public_key_raw(&key.handle, false) + .or_throw(ctx), + KeyAlgorithm::X25519 => CRYPTO_PROVIDER + .export_okp_public_key_raw(&key.handle, false) + .or_throw(ctx), + KeyAlgorithm::Rsa { .. } => CRYPTO_PROVIDER + .export_rsa_public_key_pkcs1(&key.handle) + .or_throw(ctx), + _ => algorithm_export_error(ctx, &key.name, "raw"), + } } fn export_pkcs8(ctx: &Ctx<'_>, key: &CryptoKey) -> Result> { - let handle = key.handle.as_ref(); - if key.kind != KeyKind::Private { return Err(Exception::throw_type( ctx, "Public or Secret Crypto keys can't be exported as pkcs8 format", )); } - - let bytes: Vec = match &key.algorithm { - KeyAlgorithm::Ec { .. } | KeyAlgorithm::Ed25519 => handle.into(), - KeyAlgorithm::X25519 => PrivateKeyInfoRef::new( - AlgorithmIdentifier { - oid: const_oid::db::rfc8410::ID_X_25519, - parameters: None, - }, - OctetStringRef::new(handle).or_throw(ctx)?, - ) - .to_der() - .or_throw(ctx)?, - KeyAlgorithm::Rsa { .. } => rsa_der_pkcs1_to_pkcs8(ctx, handle)?.as_bytes().to_vec(), - _ => return algorithm_export_error(ctx, &key.name, "pkcs8"), - }; - Ok(bytes) -} - -fn rsa_der_pkcs1_to_pkcs8(ctx: &Ctx, handle: &[u8]) -> Result { - let private_key = RsaPrivateKey::from_pkcs1_der(handle).or_throw(ctx)?; - private_key.to_pkcs8_der().or_throw(ctx) + match &key.algorithm { + KeyAlgorithm::Ec { curve, .. } => CRYPTO_PROVIDER + .export_ec_private_key_pkcs8(&key.handle, *curve) + .or_throw(ctx), + KeyAlgorithm::Ed25519 => CRYPTO_PROVIDER + .export_okp_private_key_pkcs8( + &key.handle, + const_oid::db::rfc8410::ID_ED_25519.as_bytes(), + ) + .or_throw(ctx), + KeyAlgorithm::X25519 => CRYPTO_PROVIDER + .export_okp_private_key_pkcs8( + &key.handle, + const_oid::db::rfc8410::ID_X_25519.as_bytes(), + ) + .or_throw(ctx), + KeyAlgorithm::Rsa { .. } => CRYPTO_PROVIDER + .export_rsa_private_key_pkcs8(&key.handle) + .or_throw(ctx), + _ => algorithm_export_error(ctx, &key.name, "pkcs8"), + } } fn export_spki(ctx: &Ctx<'_>, key: &CryptoKey) -> Result> { @@ -134,80 +123,28 @@ fn export_spki(ctx: &Ctx<'_>, key: &CryptoKey) -> Result> { "Private or Secret Crypto keys can't be exported as spki format", )); } - - let public_key_bytes = key.handle.as_ref(); - let bytes: Vec = match &key.algorithm { - KeyAlgorithm::X25519 => { - let key_info = spki::SubjectPublicKeyInfo { - algorithm: spki::AlgorithmIdentifierRef { - oid: const_oid::db::rfc8410::ID_X_25519, - parameters: None, - }, - subject_public_key: BitString::from_bytes(public_key_bytes).unwrap(), - }; - - key_info.to_der().unwrap() - }, - KeyAlgorithm::Ec { curve, algorithm } => { - let alg_id = AlgorithmIdentifierOwned { - oid: elliptic_curve::ALGORITHM_OID, - parameters: Some(match curve { - EllipticCurve::P256 => (&p256::NistP256::OID).into(), - EllipticCurve::P384 => (&p384::NistP384::OID).into(), - EllipticCurve::P521 => (&p521::NistP521::OID).into(), - }), - }; - let alg_id = match algorithm { - EcAlgorithm::Ecdh => AlgorithmIdentifier { - oid: const_oid::db::rfc5912::ID_EC_PUBLIC_KEY, - parameters: alg_id.parameters, - }, - _ => alg_id, - }; - - //unwrap ok, key is always valid after this stage - let key_info = SubjectPublicKeyInfo { - algorithm: alg_id, - subject_public_key: BitString::from_bytes(public_key_bytes).unwrap(), - }; - - key_info.to_der().unwrap() - }, - KeyAlgorithm::Ed25519 => { - let key_info = spki::SubjectPublicKeyInfo { - algorithm: spki::AlgorithmIdentifierOwned { - oid: const_oid::db::rfc8410::ID_ED_25519, - parameters: None, - }, - subject_public_key: BitString::from_bytes(public_key_bytes).unwrap(), - }; - key_info.to_der().unwrap() - }, - - KeyAlgorithm::Rsa { .. } => { - //unwrap ok, key is always valid after this stage - let key_info = spki::SubjectPublicKeyInfo { - algorithm: spki::AlgorithmIdentifier { - oid: const_oid::db::rfc5912::RSA_ENCRYPTION, - parameters: Some(asn1::AnyRef::from(asn1::Null)), - }, - subject_public_key: BitString::from_bytes(public_key_bytes).unwrap(), - }; - - key_info.to_der().unwrap() - }, - _ => return algorithm_export_error(ctx, &key.name, "spki"), - }; - - Ok(bytes) + match &key.algorithm { + KeyAlgorithm::Ec { curve, .. } => CRYPTO_PROVIDER + .export_ec_public_key_spki(&key.handle, *curve) + .or_throw(ctx), + KeyAlgorithm::Ed25519 => CRYPTO_PROVIDER + .export_okp_public_key_spki(&key.handle, const_oid::db::rfc8410::ID_ED_25519.as_bytes()) + .or_throw(ctx), + KeyAlgorithm::X25519 => CRYPTO_PROVIDER + .export_okp_public_key_spki(&key.handle, const_oid::db::rfc8410::ID_X_25519.as_bytes()) + .or_throw(ctx), + KeyAlgorithm::Rsa { .. } => CRYPTO_PROVIDER + .export_rsa_public_key_spki(&key.handle) + .or_throw(ctx), + _ => algorithm_export_error(ctx, &key.name, "spki"), + } } fn export_jwk<'js>(ctx: &Ctx<'js>, key: &CryptoKey) -> Result> { - let name = key.name.as_ref(); - let handle = key.handle.as_ref(); let obj = Object::new(ctx.clone())?; obj.set("key_ops", key.usages())?; obj.set("ext", true)?; + match &key.algorithm { KeyAlgorithm::Aes { length } => { let prefix = match length { @@ -216,185 +153,75 @@ fn export_jwk<'js>(ctx: &Ctx<'js>, key: &CryptoKey) -> Result> { 256 => "A256", _ => unreachable!(), }; - let suffix = &name[("AES-".len())..]; - let alg = [prefix, suffix].concat(); - - let k = bytes_to_b64_url_safe_string(handle); + let suffix = &key.name[("AES-".len())..]; obj.set("kty", "oct")?; - obj.set("k", k)?; - obj.set("alg", alg)? + obj.set("k", bytes_to_b64_url_safe_string(&key.handle))?; + obj.set("alg", [prefix, suffix].concat())?; }, KeyAlgorithm::Hmac { hash, .. } => { - let k = bytes_to_b64_url_safe_string(handle); obj.set("kty", "oct")?; obj.set("alg", ["HS", &hash.as_str()[4..]].concat())?; - obj.set("k", k)?; + obj.set("k", bytes_to_b64_url_safe_string(&key.handle))?; }, KeyAlgorithm::Ec { curve, .. } => { - fn set_public_key_coords( - obj: &Object<'_>, - public_key: elliptic_curve::PublicKey, - ) -> Result<()> - where - C: CurveArithmetic, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - let p = public_key.to_encoded_point(false); - let x = p.x().unwrap().as_slice(); - let y = p.y().unwrap().as_slice(); - obj.set("x", bytes_to_b64_url_safe_string(x))?; - obj.set("y", bytes_to_b64_url_safe_string(y))?; - Ok(()) - } - - fn set_private_key_props( - obj: &Object<'_>, - private_key: elliptic_curve::SecretKey, - ) -> Result<()> - where - C: elliptic_curve::Curve + elliptic_curve::CurveArithmetic, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - let public_key = private_key.public_key(); - set_public_key_coords(obj, public_key)?; - obj.set( - "d", - bytes_to_b64_url_safe_string(private_key.to_bytes().as_slice()), - )?; - Ok(()) - } - - match key.kind { - KeyKind::Public => match curve { - EllipticCurve::P256 => { - let public_key = p256::PublicKey::from_sec1_bytes(handle).or_throw(ctx)?; - set_public_key_coords(&obj, public_key)?; - }, - EllipticCurve::P384 => { - let public_key = p384::PublicKey::from_sec1_bytes(handle).or_throw(ctx)?; - set_public_key_coords(&obj, public_key)?; - }, - EllipticCurve::P521 => { - let public_key = p521::PublicKey::from_sec1_bytes(handle).or_throw(ctx)?; - set_public_key_coords(&obj, public_key)?; - }, - }, - KeyKind::Private => match curve { - EllipticCurve::P256 => { - let private_key = p256::SecretKey::from_pkcs8_der(handle).or_throw(ctx)?; - set_private_key_props(&obj, private_key)?; - }, - EllipticCurve::P384 => { - let private_key = p384::SecretKey::from_pkcs8_der(handle).or_throw(ctx)?; - set_private_key_props(&obj, private_key)?; - }, - EllipticCurve::P521 => { - let private_key = p521::SecretKey::from_pkcs8_der(handle).or_throw(ctx)?; - set_private_key_props(&obj, private_key)?; - }, - }, - _ => unreachable!(), - } - + let jwk = CRYPTO_PROVIDER + .export_ec_jwk(&key.handle, *curve, key.kind == KeyKind::Private) + .or_throw(ctx)?; obj.set("kty", "EC")?; obj.set("crv", curve.as_str())?; + obj.set("x", bytes_to_b64_url_safe_string(&jwk.x))?; + obj.set("y", bytes_to_b64_url_safe_string(&jwk.y))?; + if let Some(d) = jwk.d { + obj.set("d", bytes_to_b64_url_safe_string(&d))?; + } }, KeyAlgorithm::Ed25519 => { - if key.kind == KeyKind::Private { - let pki = PrivateKeyInfoRef::try_from(handle).or_throw(ctx)?; - let pub_key = pki.public_key.as_ref().unwrap(); - set_okp_jwk_props( - name, - &obj, - Some(pki.private_key.as_bytes()), - pub_key.raw_bytes(), - )?; - } else { - set_okp_jwk_props(name, &obj, None, handle)?; + let jwk = CRYPTO_PROVIDER + .export_okp_jwk(&key.handle, key.kind == KeyKind::Private, true) + .or_throw(ctx)?; + obj.set("kty", "OKP")?; + obj.set("crv", "Ed25519")?; + obj.set("x", bytes_to_b64_url_safe_string(&jwk.x))?; + if let Some(d) = jwk.d { + obj.set("d", bytes_to_b64_url_safe_string(&d))?; + } + }, + KeyAlgorithm::X25519 => { + let jwk = CRYPTO_PROVIDER + .export_okp_jwk(&key.handle, key.kind == KeyKind::Private, false) + .or_throw(ctx)?; + obj.set("kty", "OKP")?; + obj.set("crv", "X25519")?; + obj.set("x", bytes_to_b64_url_safe_string(&jwk.x))?; + if let Some(d) = jwk.d { + obj.set("d", bytes_to_b64_url_safe_string(&d))?; } }, KeyAlgorithm::Rsa { hash, .. } => { - let (n, e) = match key.kind { - KeyKind::Public => { - let public_key = rsa::pkcs1::RsaPublicKey::from_der(handle).or_throw(ctx)?; - let n = bytes_to_b64_url_safe_string(public_key.modulus.as_bytes()); - let e = bytes_to_b64_url_safe_string(public_key.public_exponent.as_bytes()); - (n, e) - }, - KeyKind::Private => { - let private_key = rsa::pkcs1::RsaPrivateKey::from_der(handle).or_throw(ctx)?; - let n = bytes_to_b64_url_safe_string(private_key.modulus.as_bytes()); - let e = bytes_to_b64_url_safe_string(private_key.public_exponent.as_bytes()); - let d = bytes_to_b64_url_safe_string(private_key.private_exponent.as_bytes()); - let p = bytes_to_b64_url_safe_string(private_key.prime1.as_bytes()); - let q = bytes_to_b64_url_safe_string(private_key.prime2.as_bytes()); - let dp = bytes_to_b64_url_safe_string(private_key.exponent1.as_bytes()); - let dq = bytes_to_b64_url_safe_string(private_key.exponent2.as_bytes()); - let qi = bytes_to_b64_url_safe_string(private_key.coefficient.as_bytes()); - obj.set("d", d)?; - obj.set("p", p)?; - obj.set("q", q)?; - obj.set("dp", dp)?; - obj.set("dq", dq)?; - obj.set("qi", qi)?; - (n, e) - }, - _ => { - unreachable!() - }, - }; - + let jwk = CRYPTO_PROVIDER + .export_rsa_jwk(&key.handle, key.kind == KeyKind::Private) + .or_throw(ctx)?; let alg_suffix = hash.as_numeric_str(); - - let alg_prefix = match name { + let alg_prefix = match key.name.as_ref() { "RSASSA-PKCS1-v1_5" => "RS", "RSA-PSS" => "PS", "RSA-OAEP" => "RSA-OAEP-", _ => unreachable!(), }; - - let alg = [alg_prefix, alg_suffix].concat(); - obj.set("kty", "RSA")?; - obj.set("n", n)?; - obj.set("e", e)?; - obj.set("alg", alg)?; - }, - KeyAlgorithm::X25519 => match key.kind { - KeyKind::Private => { - let array: [u8; 32] = handle.try_into().or_throw(ctx)?; - let secret = x25519_dalek::StaticSecret::from(array); - let public_key = x25519_dalek::PublicKey::from(&secret); - set_okp_jwk_props(name, &obj, Some(secret.as_bytes()), public_key.as_bytes())?; - }, - KeyKind::Public => { - let public_key = handle; - set_okp_jwk_props(name, &obj, None, public_key)?; - }, - _ => unreachable!(), + obj.set("n", bytes_to_b64_url_safe_string(&jwk.n))?; + obj.set("e", bytes_to_b64_url_safe_string(&jwk.e))?; + obj.set("alg", [alg_prefix, alg_suffix].concat())?; + if let Some(d) = jwk.d { + obj.set("d", bytes_to_b64_url_safe_string(&d))?; + obj.set("p", bytes_to_b64_url_safe_string(&jwk.p.unwrap()))?; + obj.set("q", bytes_to_b64_url_safe_string(&jwk.q.unwrap()))?; + obj.set("dp", bytes_to_b64_url_safe_string(&jwk.dp.unwrap()))?; + obj.set("dq", bytes_to_b64_url_safe_string(&jwk.dq.unwrap()))?; + obj.set("qi", bytes_to_b64_url_safe_string(&jwk.qi.unwrap()))?; + } }, - //cant be exported _ => return algorithm_export_error(ctx, &key.name, "jwk"), - }; - - Ok(obj) -} - -fn set_okp_jwk_props( - crv: &str, - obj: &Object<'_>, - private_key: Option<&[u8]>, - public_key: &[u8], -) -> Result<()> { - let x = bytes_to_b64_url_safe_string(public_key); - obj.set("kty", "OKP")?; - obj.set("crv", crv)?; - obj.set("x", x)?; - if let Some(private_key) = private_key { - let d = bytes_to_b64_url_safe_string(private_key); - obj.set("d", d)?; } - Ok(()) + Ok(obj) } diff --git a/modules/llrt_crypto/src/subtle/export_key_openssl.rs b/modules/llrt_crypto/src/subtle/export_key_openssl.rs deleted file mode 100644 index 4e1c71f078..0000000000 --- a/modules/llrt_crypto/src/subtle/export_key_openssl.rs +++ /dev/null @@ -1,369 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -//! OpenSSL-based key export implementation. - -use der::{asn1::BitString, Encode}; -use llrt_encoding::bytes_to_b64_url_safe_string; -use llrt_utils::result::ResultExt; -use openssl::bn::{BigNum, BigNumRef}; -use openssl::ec::EcGroup; -use openssl::nid::Nid; -use openssl::pkey::{Id, PKey, Private, Public}; -use openssl::rsa::Rsa; -use rquickjs::{ArrayBuffer, Class, Ctx, Exception, Object, Result}; -use spki::{AlgorithmIdentifierOwned, SubjectPublicKeyInfoOwned}; - -use super::{ - crypto_key::KeyKind, - key_algorithm::{KeyAlgorithm, KeyFormat}, - CryptoKey, EllipticCurve, -}; - -pub fn algorithm_export_error(ctx: &Ctx<'_>, algorithm: &str, format: &str) -> Result { - Err(Exception::throw_message( - ctx, - &["Export of ", algorithm, " as ", format, " is not supported"].concat(), - )) -} - -pub enum ExportOutput<'js> { - Bytes(Vec), - Object(Object<'js>), -} - -pub async fn subtle_export_key<'js>( - ctx: Ctx<'js>, - format: KeyFormat, - key: Class<'js, CryptoKey>, -) -> Result> { - let key = key.borrow(); - - let export = export_key(&ctx, format, &key)?; - - Ok(match export { - ExportOutput::Bytes(bytes) => ArrayBuffer::new(ctx, bytes)?.into_object(), - ExportOutput::Object(object) => object, - }) -} - -pub fn export_key<'js>( - ctx: &Ctx<'js>, - format: KeyFormat, - key: &CryptoKey, -) -> Result> { - if !key.extractable { - return Err(Exception::throw_type( - ctx, - "The CryptoKey is non extractable", - )); - }; - let bytes = match format { - KeyFormat::Jwk => return Ok(ExportOutput::Object(export_jwk(ctx, key)?)), - KeyFormat::Raw => export_raw(ctx, key), - KeyFormat::Spki => export_spki(ctx, key), - KeyFormat::Pkcs8 => export_pkcs8(ctx, key), - }?; - Ok(ExportOutput::Bytes(bytes)) -} - -fn export_raw(ctx: &Ctx<'_>, key: &CryptoKey) -> Result> { - if key.kind == KeyKind::Private { - return Err(Exception::throw_type( - ctx, - "Private Crypto keys can't be exported as raw format", - )); - }; - if !matches!( - key.algorithm, - KeyAlgorithm::Aes { .. } - | KeyAlgorithm::Ec { .. } - | KeyAlgorithm::Hmac { .. } - | KeyAlgorithm::Rsa { .. } - | KeyAlgorithm::Ed25519 - | KeyAlgorithm::X25519 - ) { - return algorithm_export_error(ctx, &key.name, "raw"); - } - Ok(key.handle.to_vec()) -} - -fn export_pkcs8(ctx: &Ctx<'_>, key: &CryptoKey) -> Result> { - let handle = key.handle.as_ref(); - - if key.kind != KeyKind::Private { - return Err(Exception::throw_type( - ctx, - "Public or Secret Crypto keys can't be exported as pkcs8 format", - )); - } - - match &key.algorithm { - KeyAlgorithm::Ec { .. } => { - // Handle is already PKCS#8 DER - Ok(handle.to_vec()) - }, - KeyAlgorithm::Ed25519 => { - // Handle is already PKCS#8 DER - Ok(handle.to_vec()) - }, - KeyAlgorithm::X25519 => { - // Handle is raw 32-byte secret, wrap in PKCS#8 - let pkey = PKey::private_key_from_raw_bytes(handle, Id::X25519).or_throw(ctx)?; - pkey.private_key_to_der().or_throw(ctx) - }, - KeyAlgorithm::Rsa { .. } => { - // Handle is PKCS#1 DER, convert to PKCS#8 - let rsa = Rsa::::private_key_from_der(handle).or_throw(ctx)?; - let pkey = PKey::from_rsa(rsa).or_throw(ctx)?; - pkey.private_key_to_der().or_throw(ctx) - }, - _ => algorithm_export_error(ctx, &key.name, "pkcs8"), - } -} - -fn export_spki(ctx: &Ctx<'_>, key: &CryptoKey) -> Result> { - if key.kind != KeyKind::Public { - return Err(Exception::throw_type( - ctx, - "Private or Secret Crypto keys can't be exported as spki format", - )); - } - - let public_key_bytes = key.handle.as_ref(); - - match &key.algorithm { - KeyAlgorithm::X25519 => { - let key_info = SubjectPublicKeyInfoOwned { - algorithm: AlgorithmIdentifierOwned { - oid: const_oid::db::rfc8410::ID_X_25519, - parameters: None, - }, - subject_public_key: BitString::from_bytes(public_key_bytes).unwrap(), - }; - Ok(key_info.to_der().unwrap()) - }, - KeyAlgorithm::Ec { curve, .. } => { - let curve_oid = match curve { - EllipticCurve::P256 => const_oid::db::rfc5912::SECP_256_R_1, - EllipticCurve::P384 => const_oid::db::rfc5912::SECP_384_R_1, - EllipticCurve::P521 => const_oid::db::rfc5912::SECP_521_R_1, - }; - - let key_info = SubjectPublicKeyInfoOwned { - algorithm: AlgorithmIdentifierOwned { - oid: const_oid::db::rfc5912::ID_EC_PUBLIC_KEY, - parameters: Some((&curve_oid).into()), - }, - subject_public_key: BitString::from_bytes(public_key_bytes).unwrap(), - }; - Ok(key_info.to_der().unwrap()) - }, - KeyAlgorithm::Ed25519 => { - let key_info = SubjectPublicKeyInfoOwned { - algorithm: AlgorithmIdentifierOwned { - oid: const_oid::db::rfc8410::ID_ED_25519, - parameters: None, - }, - subject_public_key: BitString::from_bytes(public_key_bytes).unwrap(), - }; - Ok(key_info.to_der().unwrap()) - }, - KeyAlgorithm::Rsa { .. } => { - let key_info = SubjectPublicKeyInfoOwned { - algorithm: AlgorithmIdentifierOwned { - oid: const_oid::db::rfc5912::RSA_ENCRYPTION, - parameters: Some(der::asn1::AnyRef::from(der::asn1::Null).into()), - }, - subject_public_key: BitString::from_bytes(public_key_bytes).unwrap(), - }; - Ok(key_info.to_der().unwrap()) - }, - _ => algorithm_export_error(ctx, &key.name, "spki"), - } -} - -fn export_jwk<'js>(ctx: &Ctx<'js>, key: &CryptoKey) -> Result> { - let name = key.name.as_ref(); - let handle = key.handle.as_ref(); - let obj = Object::new(ctx.clone())?; - obj.set("key_ops", key.usages())?; - obj.set("ext", true)?; - - match &key.algorithm { - KeyAlgorithm::Aes { length } => { - let prefix = match length { - 128 => "A128", - 192 => "A192", - 256 => "A256", - _ => unreachable!(), - }; - let suffix = &name[("AES-".len())..]; - let alg = [prefix, suffix].concat(); - - obj.set("kty", "oct")?; - obj.set("k", bytes_to_b64_url_safe_string(handle))?; - obj.set("alg", alg)?; - }, - KeyAlgorithm::Hmac { hash, .. } => { - obj.set("kty", "oct")?; - obj.set("alg", ["HS", &hash.as_str()[4..]].concat())?; - obj.set("k", bytes_to_b64_url_safe_string(handle))?; - }, - KeyAlgorithm::Ec { curve, .. } => { - let nid = curve_to_nid(curve); - let group = EcGroup::from_curve_name(nid).or_throw(ctx)?; - - match key.kind { - KeyKind::Public => { - // Handle is SEC1 uncompressed point - parse it - let mut bn_ctx = openssl::bn::BigNumContext::new().or_throw(ctx)?; - let point = openssl::ec::EcPoint::from_bytes(&group, handle, &mut bn_ctx) - .or_throw(ctx)?; - let mut x = BigNum::new().or_throw(ctx)?; - let mut y = BigNum::new().or_throw(ctx)?; - point - .affine_coordinates(&group, &mut x, &mut y, &mut bn_ctx) - .or_throw(ctx)?; - set_ec_public_coords(&obj, &x, &y, curve)?; - }, - KeyKind::Private => { - // Handle is PKCS#8 DER - let pkey = PKey::::private_key_from_der(handle).or_throw(ctx)?; - let ec_key = pkey.ec_key().or_throw(ctx)?; - let private_num = ec_key.private_key(); - let point = ec_key.public_key(); - let mut bn_ctx = openssl::bn::BigNumContext::new().or_throw(ctx)?; - let mut x = BigNum::new().or_throw(ctx)?; - let mut y = BigNum::new().or_throw(ctx)?; - point - .affine_coordinates(&group, &mut x, &mut y, &mut bn_ctx) - .or_throw(ctx)?; - set_ec_public_coords(&obj, &x, &y, curve)?; - obj.set("d", bn_to_b64_padded(private_num, curve_byte_len(curve)))?; - }, - _ => unreachable!(), - } - - obj.set("kty", "EC")?; - obj.set("crv", curve.as_str())?; - }, - KeyAlgorithm::Ed25519 => { - if key.kind == KeyKind::Private { - // Handle is PKCS#8 DER - let pkey = PKey::::private_key_from_der(handle).or_throw(ctx)?; - let raw_private = pkey.raw_private_key().or_throw(ctx)?; - let raw_public = pkey.raw_public_key().or_throw(ctx)?; - set_okp_jwk_props(name, &obj, Some(&raw_private), &raw_public)?; - } else { - // Handle is raw 32-byte public key - set_okp_jwk_props(name, &obj, None, handle)?; - } - }, - KeyAlgorithm::Rsa { hash, .. } => { - let alg_suffix = hash.as_numeric_str(); - let alg_prefix = match name { - "RSASSA-PKCS1-v1_5" => "RS", - "RSA-PSS" => "PS", - "RSA-OAEP" => "RSA-OAEP-", - _ => unreachable!(), - }; - let alg = [alg_prefix, alg_suffix].concat(); - - match key.kind { - KeyKind::Public => { - // Handle is PKCS#1 DER public key - let rsa = Rsa::::public_key_from_der_pkcs1(handle).or_throw(ctx)?; - obj.set("n", bn_to_b64(rsa.n()))?; - obj.set("e", bn_to_b64(rsa.e()))?; - }, - KeyKind::Private => { - // Handle is PKCS#1 DER private key - let rsa = Rsa::::private_key_from_der(handle).or_throw(ctx)?; - obj.set("n", bn_to_b64(rsa.n()))?; - obj.set("e", bn_to_b64(rsa.e()))?; - obj.set("d", bn_to_b64(rsa.d()))?; - obj.set("p", bn_to_b64(rsa.p().unwrap()))?; - obj.set("q", bn_to_b64(rsa.q().unwrap()))?; - obj.set("dp", bn_to_b64(rsa.dmp1().unwrap()))?; - obj.set("dq", bn_to_b64(rsa.dmq1().unwrap()))?; - obj.set("qi", bn_to_b64(rsa.iqmp().unwrap()))?; - }, - _ => unreachable!(), - } - - obj.set("kty", "RSA")?; - obj.set("alg", alg)?; - }, - KeyAlgorithm::X25519 => { - if key.kind == KeyKind::Private { - // Handle is raw 32-byte secret - let pkey = PKey::private_key_from_raw_bytes(handle, Id::X25519).or_throw(ctx)?; - let raw_public = pkey.raw_public_key().or_throw(ctx)?; - set_okp_jwk_props(name, &obj, Some(handle), &raw_public)?; - } else { - // Handle is raw 32-byte public key - set_okp_jwk_props(name, &obj, None, handle)?; - } - }, - _ => return algorithm_export_error(ctx, &key.name, "jwk"), - }; - - Ok(obj) -} - -fn curve_to_nid(curve: &EllipticCurve) -> Nid { - match curve { - EllipticCurve::P256 => Nid::X9_62_PRIME256V1, - EllipticCurve::P384 => Nid::SECP384R1, - EllipticCurve::P521 => Nid::SECP521R1, - } -} - -fn curve_byte_len(curve: &EllipticCurve) -> usize { - match curve { - EllipticCurve::P256 => 32, - EllipticCurve::P384 => 48, - EllipticCurve::P521 => 66, - } -} - -fn bn_to_b64(bn: &BigNumRef) -> String { - bytes_to_b64_url_safe_string(&bn.to_vec()) -} - -fn bn_to_b64_padded(bn: &BigNumRef, len: usize) -> String { - let mut bytes = bn.to_vec(); - // Pad to expected length - while bytes.len() < len { - bytes.insert(0, 0); - } - bytes_to_b64_url_safe_string(&bytes) -} - -fn set_ec_public_coords( - obj: &Object<'_>, - x: &BigNum, - y: &BigNum, - curve: &EllipticCurve, -) -> Result<()> { - let len = curve_byte_len(curve); - obj.set("x", bn_to_b64_padded(x, len))?; - obj.set("y", bn_to_b64_padded(y, len))?; - Ok(()) -} - -fn set_okp_jwk_props( - crv: &str, - obj: &Object<'_>, - private_key: Option<&[u8]>, - public_key: &[u8], -) -> Result<()> { - obj.set("kty", "OKP")?; - obj.set("crv", crv)?; - obj.set("x", bytes_to_b64_url_safe_string(public_key))?; - if let Some(private_key) = private_key { - obj.set("d", bytes_to_b64_url_safe_string(private_key))?; - } - Ok(()) -} diff --git a/modules/llrt_crypto/src/subtle/import_key_openssl.rs b/modules/llrt_crypto/src/subtle/import_key_openssl.rs deleted file mode 100644 index 57f7887df3..0000000000 --- a/modules/llrt_crypto/src/subtle/import_key_openssl.rs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -//! OpenSSL-based key import implementation. - -use llrt_utils::{bytes::ObjectBytes, object::ObjectExt}; -use rquickjs::{Array, Class, Ctx, FromJs, Result, Value}; - -use super::{ - crypto_key::KeyKind, - key_algorithm::{ - KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages, KeyFormat, KeyFormatData, - }, - CryptoKey, -}; - -pub async fn subtle_import_key<'js>( - ctx: Ctx<'js>, - format: KeyFormat, - key_data: Value<'js>, - algorithm: Value<'js>, - extractable: bool, - key_usages: Array<'js>, -) -> Result> { - let format = match format { - KeyFormat::Raw => KeyFormatData::Raw(ObjectBytes::from_js(&ctx, key_data)?), - KeyFormat::Pkcs8 => KeyFormatData::Pkcs8(ObjectBytes::from_js(&ctx, key_data)?), - KeyFormat::Spki => KeyFormatData::Spki(ObjectBytes::from_js(&ctx, key_data)?), - KeyFormat::Jwk => KeyFormatData::Jwk(key_data.into_object_or_throw(&ctx, "keyData")?), - }; - - import_key(ctx, format, algorithm, extractable, key_usages) -} - -pub fn import_key<'js>( - ctx: Ctx<'js>, - format: KeyFormatData<'js>, - algorithm: Value<'js>, - extractable: bool, - key_usages: Array<'js>, -) -> Result> { - let mut kind = KeyKind::Public; - let mut data = Vec::new(); - - let KeyAlgorithmWithUsages { - name, - algorithm: key_algorithm, - public_usages, - private_usages, - } = KeyAlgorithm::from_js( - &ctx, - KeyAlgorithmMode::Import { - kind: &mut kind, - data: &mut data, - format, - }, - algorithm, - key_usages, - )?; - - let usages = match kind { - KeyKind::Public | KeyKind::Secret => public_usages, - KeyKind::Private => private_usages, - }; - - Class::instance( - ctx, - CryptoKey::new(kind, name, extractable, key_algorithm, usages, data), - ) -} diff --git a/modules/llrt_crypto/src/subtle/mod.rs b/modules/llrt_crypto/src/subtle/mod.rs index 089783869f..ecaef81103 100644 --- a/modules/llrt_crypto/src/subtle/mod.rs +++ b/modules/llrt_crypto/src/subtle/mod.rs @@ -6,24 +6,18 @@ mod derive_algorithm; mod digest; mod encryption; mod encryption_algorithm; -#[cfg(feature = "_rustcrypto")] +#[cfg(feature = "_subtle-full")] mod export_key; -#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] -mod export_key_openssl; mod generate_key; -#[cfg(feature = "_rustcrypto")] +#[cfg(feature = "_subtle-full")] mod import_key; -#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] -mod import_key_openssl; -#[cfg(any(feature = "_rustcrypto", feature = "_subtle-full"))] +#[cfg(feature = "_subtle-full")] mod key_algorithm; mod sign; mod sign_algorithm; mod verify; -#[cfg(feature = "_rustcrypto")] +#[cfg(feature = "_subtle-full")] mod wrapping; -#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] -mod wrapping_openssl; pub use crypto_key::CryptoKey; pub use derive::subtle_derive_bits; @@ -31,32 +25,24 @@ pub use derive::subtle_derive_key; pub use digest::subtle_digest; pub use encryption::subtle_decrypt; pub use encryption::subtle_encrypt; -#[cfg(feature = "_rustcrypto")] +#[cfg(feature = "_subtle-full")] pub use export_key::subtle_export_key; -#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] -pub use export_key_openssl::subtle_export_key; pub use generate_key::subtle_generate_key; -#[cfg(feature = "_rustcrypto")] +#[cfg(feature = "_subtle-full")] pub use import_key::subtle_import_key; -#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] -pub use import_key_openssl::subtle_import_key; -#[cfg(any(feature = "_rustcrypto", feature = "_subtle-full"))] +#[cfg(feature = "_subtle-full")] use key_algorithm::KeyAlgorithm; pub use sign::subtle_sign; pub use verify::subtle_verify; -#[cfg(feature = "_rustcrypto")] +#[cfg(feature = "_subtle-full")] pub use wrapping::subtle_unwrap_key; -#[cfg(feature = "_rustcrypto")] +#[cfg(feature = "_subtle-full")] pub use wrapping::subtle_wrap_key; -#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] -pub use wrapping_openssl::subtle_unwrap_key; -#[cfg(all(feature = "openssl", not(feature = "_rustcrypto")))] -pub use wrapping_openssl::subtle_wrap_key; -// Stub implementations for limited crypto providers (not openssl, not rustcrypto) -#[cfg(not(any(feature = "_rustcrypto", feature = "_subtle-full")))] +// Stub implementations for limited crypto providers (no _subtle-full) +#[cfg(not(feature = "_subtle-full"))] mod key_algorithm; -#[cfg(not(any(feature = "_rustcrypto", feature = "_subtle-full")))] +#[cfg(not(feature = "_subtle-full"))] use key_algorithm::KeyAlgorithm; use llrt_utils::{object::ObjectExt, str_enum}; @@ -191,14 +177,14 @@ pub fn algorithm_not_supported_error(ctx: &Ctx<'_>) -> Result { Err(Exception::throw_message(ctx, "Algorithm not supported")) } -// Stub implementations for providers without rustcrypto or openssl -#[cfg(not(any(feature = "_rustcrypto", feature = "openssl")))] +// Stub implementations for providers without _subtle-full +#[cfg(not(feature = "_subtle-full"))] mod stubs; -#[cfg(not(any(feature = "_rustcrypto", feature = "openssl")))] +#[cfg(not(feature = "_subtle-full"))] pub use stubs::subtle_export_key; -#[cfg(not(any(feature = "_rustcrypto", feature = "openssl")))] +#[cfg(not(feature = "_subtle-full"))] pub use stubs::subtle_import_key; -#[cfg(not(any(feature = "_rustcrypto", feature = "openssl")))] +#[cfg(not(feature = "_subtle-full"))] pub use stubs::subtle_unwrap_key; -#[cfg(not(any(feature = "_rustcrypto", feature = "openssl")))] +#[cfg(not(feature = "_subtle-full"))] pub use stubs::subtle_wrap_key; diff --git a/modules/llrt_crypto/src/subtle/wrapping_openssl.rs b/modules/llrt_crypto/src/subtle/wrapping_openssl.rs deleted file mode 100644 index 35e0775b36..0000000000 --- a/modules/llrt_crypto/src/subtle/wrapping_openssl.rs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -//! OpenSSL-based key wrapping implementation. - -use llrt_json::{parse::json_parse, stringify::json_stringify}; -use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt}; -use rquickjs::{Array, ArrayBuffer, Class, Ctx, Exception, Result, Value}; - -use super::{ - encryption::{self, encrypt_decrypt}, - encryption_algorithm::EncryptionAlgorithm, - export_key_openssl::{export_key, ExportOutput}, - import_key_openssl::import_key, - key_algorithm::{KeyFormat, KeyFormatData}, - CryptoKey, EncryptionMode, -}; - -pub async fn subtle_wrap_key<'js>( - ctx: Ctx<'js>, - format: KeyFormat, - key: Class<'js, CryptoKey>, - wrapping_key: Class<'js, CryptoKey>, - wrap_algo: EncryptionAlgorithm, -) -> Result> { - let key = key.borrow(); - - let export = export_key(&ctx, format, &key)?; - - let (bytes, padding) = match export { - ExportOutput::Bytes(bytes) => (bytes, 0), - ExportOutput::Object(value) => { - let json = json_stringify(&ctx, value.into_value())?.unwrap(); - (json.into_bytes(), b' ') - }, - }; - - let wrapping_key = wrapping_key.borrow(); - wrapping_key.check_validity("wrapKey").or_throw(&ctx)?; - - let bytes = encrypt_decrypt( - &ctx, - &wrap_algo, - &wrapping_key, - &bytes, - EncryptionMode::Wrapping(padding), - encryption::EncryptionOperation::Encrypt, - )?; - - ArrayBuffer::new(ctx, bytes) -} - -pub async fn subtle_unwrap_key<'js>( - format: KeyFormat, - wrapped_key: ArrayBuffer<'js>, - unwrapping_key: Class<'js, CryptoKey>, - unwrap_algo: EncryptionAlgorithm, - unwrapped_key_algo: Value<'js>, - extractable: bool, - key_usages: Array<'js>, -) -> Result> { - let unwrapping_key = unwrapping_key.borrow(); - let ctx = wrapped_key.ctx().clone(); - unwrapping_key.check_validity("unwrapKey").or_throw(&ctx)?; - - let bytes = wrapped_key - .as_bytes() - .ok_or_else(|| Exception::throw_message(&ctx, "ArrayBuffer is detached"))?; - - let padding = match format { - KeyFormat::Jwk => b' ', - _ => 0, - }; - - let bytes = encrypt_decrypt( - &ctx, - &unwrap_algo, - &unwrapping_key, - bytes, - EncryptionMode::Wrapping(padding), - encryption::EncryptionOperation::Decrypt, - )?; - - let key_format = match format { - KeyFormat::Jwk => { - KeyFormatData::Jwk(json_parse(&ctx, bytes)?.into_object_or_throw(&ctx, "wrappedKey")?) - }, - KeyFormat::Raw => KeyFormatData::Raw(ObjectBytes::Vec(bytes)), - KeyFormat::Spki => KeyFormatData::Spki(ObjectBytes::Vec(bytes)), - KeyFormat::Pkcs8 => KeyFormatData::Pkcs8(ObjectBytes::Vec(bytes)), - }; - - import_key(ctx, key_format, unwrapped_key_algo, extractable, key_usages) -} From 98d6e52ce760ebf29e439ca4eb1a6d7b2910f151 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 15:08:04 +0100 Subject: [PATCH 56/77] Fix OpenSSL export_ec_jwk for public keys Public key handle is SEC1 format, not SPKI DER. Parse coordinates directly from SEC1 point. --- modules/llrt_crypto/src/provider/openssl.rs | 30 +++++++++------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs index fe326eb297..0f759e283a 100644 --- a/modules/llrt_crypto/src/provider/openssl.rs +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -1219,24 +1219,18 @@ impl CryptoProvider for OpenSslProvider { d: Some(ec_key.private_key().to_vec()), }) } else { - let pkey = PKey::public_key_from_der(key_data) - .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; - let ec_key = pkey - .ec_key() - .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; - let mut x = - BigNum::new().map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; - let mut y = - BigNum::new().map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; - ec_key - .public_key() - .affine_coordinates(&group, &mut x, &mut y, &mut ctx) - .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; - Ok(super::EcJwkExport { - x: x.to_vec(), - y: y.to_vec(), - d: None, - }) + // key_data is SEC1 uncompressed point (0x04 || x || y) + let coord_len = match curve { + EllipticCurve::P256 => 32, + EllipticCurve::P384 => 48, + EllipticCurve::P521 => 66, + }; + if key_data.len() != 1 + 2 * coord_len || key_data[0] != 0x04 { + return Err(CryptoError::InvalidKey(None)); + } + let x = key_data[1..1 + coord_len].to_vec(); + let y = key_data[1 + coord_len..].to_vec(); + Ok(super::EcJwkExport { x, y, d: None }) } } From 2a489f760c263902134bffafbe2895d61524abef Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 21:20:08 +0100 Subject: [PATCH 57/77] remove macos x86 from tests --- .github/workflows/ci.yml | 42 ---------------------------------------- 1 file changed, 42 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d90bb38cf..7f535080da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,27 +79,6 @@ jobs: arch: aarch64 toolchain: nightly crypto: openssl - # macOS x64 - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: nightly - crypto: ring - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: nightly - crypto: aws-lc - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: nightly - crypto: graviola - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: nightly - crypto: openssl # macOS arm64 - os: macos-latest platform: darwin @@ -193,27 +172,6 @@ jobs: arch: aarch64 toolchain: stable crypto: openssl - # macOS x64 - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: stable - crypto: ring - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: stable - crypto: aws-lc - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: stable - crypto: graviola - - os: macos-13 - platform: darwin - arch: x86_64 - toolchain: stable - crypto: openssl # macOS arm64 - os: macos-latest platform: darwin From 25efa7f7df7fc0612e9793612f80f907bdc0b200 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 21:31:36 +0100 Subject: [PATCH 58/77] Add MSYS2 OpenSSL setup for Windows modules job --- .github/workflows/build-modules.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index bf5a269b39..288404584b 100644 --- a/.github/workflows/build-modules.yml +++ b/.github/workflows/build-modules.yml @@ -33,6 +33,23 @@ jobs: uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ inputs.toolchain }} + - name: Install Windows OpenSSL + if: inputs.platform == 'windows' && inputs.crypto == 'openssl' + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + path-type: inherit + install: >- + mingw-w64-x86_64-openssl + mingw-w64-x86_64-pkgconf + - name: Set OpenSSL env for Windows + if: inputs.platform == 'windows' && inputs.crypto == 'openssl' + shell: bash + run: | + echo "OPENSSL_DIR=D:/a/_temp/msys64/mingw64" >> $GITHUB_ENV + echo "OPENSSL_LIB_DIR=D:/a/_temp/msys64/mingw64/lib" >> $GITHUB_ENV + echo "OPENSSL_INCLUDE_DIR=D:/a/_temp/msys64/mingw64/include" >> $GITHUB_ENV - name: Map crypto feature to TLS and crypto features id: features shell: bash From 0c2473553c8ef36d2e5bec88b63367bc73a5ebc4 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 22:05:48 +0100 Subject: [PATCH 59/77] Re-trigger CI (flaky child_process test) From d658162682a6860d12d3199f2d3a4266c267d9c4 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Thu, 29 Jan 2026 22:23:34 +0100 Subject: [PATCH 60/77] fix race --- tests/unit/child_process.test.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/unit/child_process.test.ts b/tests/unit/child_process.test.ts index c69f21ac0c..1b3ca59bf9 100644 --- a/tests/unit/child_process.test.ts +++ b/tests/unit/child_process.test.ts @@ -218,9 +218,16 @@ describe("spawn", () => { const exists = process.kill(detachedPid, 0); console.log("Process exists check:", exists); expect(exists).toBe(true); - const killResult = process.kill(detachedPid); - console.log("Kill result:", killResult); - done(); + process.kill(detachedPid, "SIGKILL"); + // Wait for process to actually terminate + const waitForDeath = () => { + if (process.kill(detachedPid, 0)) { + setTimeout(waitForDeath, 50); + } else { + done(); + } + }; + waitForDeath(); } catch (error) { done(error); } From b53da615e384a8494a4cbdaff4e77cb4d5ec370b Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Fri, 30 Jan 2026 10:44:52 +0100 Subject: [PATCH 61/77] Fix haning test --- tests/unit/child_process.test.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/tests/unit/child_process.test.ts b/tests/unit/child_process.test.ts index 1b3ca59bf9..041421bc88 100644 --- a/tests/unit/child_process.test.ts +++ b/tests/unit/child_process.test.ts @@ -206,28 +206,24 @@ describe("spawn", () => { let detachedPidString = ""; parentProc.stdout.on("data", (data) => { detachedPidString += data.toString(); - console.log("Got PID:", detachedPidString); + // Kill parent once we have the PID - parent would otherwise wait for detached child parentProc.kill(); }); - parentProc.on("exit", () => { + parentProc.on("error", (err) => { + done(err); + }); + + parentProc.on("close", () => { try { const detachedPid = parseInt(detachedPidString.trim()); - console.log("Parent exited, detached PID:", detachedPid); expect(detachedPid).toBeGreaterThan(0); + // Verify detached process survived parent termination const exists = process.kill(detachedPid, 0); - console.log("Process exists check:", exists); expect(exists).toBe(true); + // Clean up the detached process process.kill(detachedPid, "SIGKILL"); - // Wait for process to actually terminate - const waitForDeath = () => { - if (process.kill(detachedPid, 0)) { - setTimeout(waitForDeath, 50); - } else { - done(); - } - }; - waitForDeath(); + done(); } catch (error) { done(error); } From fe2b11cb5873a5ddde83712f6b49ceb0d80fc0da Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Fri, 30 Jan 2026 11:28:33 +0100 Subject: [PATCH 62/77] Skip test - debug --- tests/unit/child_process.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/child_process.test.ts b/tests/unit/child_process.test.ts index 041421bc88..d481afaad8 100644 --- a/tests/unit/child_process.test.ts +++ b/tests/unit/child_process.test.ts @@ -185,7 +185,7 @@ describe("spawn", () => { await testExitCode("266", 10); }); - it("should handle detached child process termination", (done) => { + it.skip("should handle detached child process termination", (done) => { // Use cross-platform long-running command const sleepCmd = IS_WINDOWS ? "spawn('ping', ['-n', '999', 'localhost']" From 45cd73c80bab9097dbb4fef94110bedb61819a87 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Fri, 30 Jan 2026 16:34:50 +0100 Subject: [PATCH 63/77] Add back test --- tests/unit/child_process.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/child_process.test.ts b/tests/unit/child_process.test.ts index d481afaad8..041421bc88 100644 --- a/tests/unit/child_process.test.ts +++ b/tests/unit/child_process.test.ts @@ -185,7 +185,7 @@ describe("spawn", () => { await testExitCode("266", 10); }); - it.skip("should handle detached child process termination", (done) => { + it("should handle detached child process termination", (done) => { // Use cross-platform long-running command const sleepCmd = IS_WINDOWS ? "spawn('ping', ['-n', '999', 'localhost']" From aa08e2cb8658b98aa03118294a06a3a78286340e Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Fri, 30 Jan 2026 16:35:12 +0100 Subject: [PATCH 64/77] Debug set worker to 1 and use destroy instead of close --- llrt_core/src/modules/js/@llrt/test/SocketClient.ts | 3 ++- llrt_core/src/modules/js/@llrt/test/index.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/llrt_core/src/modules/js/@llrt/test/SocketClient.ts b/llrt_core/src/modules/js/@llrt/test/SocketClient.ts index d72b7ba787..1ae349600f 100644 --- a/llrt_core/src/modules/js/@llrt/test/SocketClient.ts +++ b/llrt_core/src/modules/js/@llrt/test/SocketClient.ts @@ -84,7 +84,8 @@ class SocketClient extends EventEmitter { async close(): Promise { return new Promise((resolve) => { - this.socket.end(resolve); + this.socket.once("close", resolve); + this.socket.destroy(); }); } } diff --git a/llrt_core/src/modules/js/@llrt/test/index.ts b/llrt_core/src/modules/js/@llrt/test/index.ts index b556cf9513..91129a0ad5 100644 --- a/llrt_core/src/modules/js/@llrt/test/index.ts +++ b/llrt_core/src/modules/js/@llrt/test/index.ts @@ -757,6 +757,6 @@ class TestServer { } const testServer = new TestServer((globalThis as any).__testEntries, { - workerCount: undefined, + workerCount: 1, }); await testServer.start(); From 76c2e5df66bca57e4080d58b075079dc28f30461 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Mon, 2 Feb 2026 11:34:41 +0100 Subject: [PATCH 65/77] Debug tests --- llrt_core/src/modules/js/@llrt/test/SocketClient.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/llrt_core/src/modules/js/@llrt/test/SocketClient.ts b/llrt_core/src/modules/js/@llrt/test/SocketClient.ts index 1ae349600f..492876e46e 100644 --- a/llrt_core/src/modules/js/@llrt/test/SocketClient.ts +++ b/llrt_core/src/modules/js/@llrt/test/SocketClient.ts @@ -84,8 +84,10 @@ class SocketClient extends EventEmitter { async close(): Promise { return new Promise((resolve) => { - this.socket.once("close", resolve); - this.socket.destroy(); + this.socket.end(() => { + this.socket.destroy(); + resolve(); + }); }); } } From 62dd237a26ab5c55b0fa17ca3d0917e02011778c Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Mon, 2 Feb 2026 16:13:42 +0100 Subject: [PATCH 66/77] Debug --- tests/unit/child_process.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/child_process.test.ts b/tests/unit/child_process.test.ts index 041421bc88..6873f47632 100644 --- a/tests/unit/child_process.test.ts +++ b/tests/unit/child_process.test.ts @@ -114,7 +114,7 @@ describe("spawn", () => { it("should handle child process termination", (done) => { // Use cross-platform long-running command: Windows uses 'ping -n 999 localhost', Unix uses 'sleep 999' const command = IS_WINDOWS ? "ping -n 999 localhost" : "sleep 999"; - const child = spawn(command, { shell: true }); + const child = spawn(command); child.on("exit", (code, signal) => { try { From a069241592899229b6783292d1ab717929d44f0c Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Mon, 2 Feb 2026 16:27:59 +0100 Subject: [PATCH 67/77] fix --- tests/unit/child_process.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/unit/child_process.test.ts b/tests/unit/child_process.test.ts index 6873f47632..6d45b926ae 100644 --- a/tests/unit/child_process.test.ts +++ b/tests/unit/child_process.test.ts @@ -113,8 +113,9 @@ describe("spawn", () => { it("should handle child process termination", (done) => { // Use cross-platform long-running command: Windows uses 'ping -n 999 localhost', Unix uses 'sleep 999' - const command = IS_WINDOWS ? "ping -n 999 localhost" : "sleep 999"; - const child = spawn(command); + const child = IS_WINDOWS + ? spawn("ping", ["-n", "999", "localhost"]) + : spawn("sleep", ["999"]); child.on("exit", (code, signal) => { try { From b460a01148cd359220db91f754724854e1b1c112 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Mon, 2 Feb 2026 19:05:25 +0100 Subject: [PATCH 68/77] Use CPU count --- llrt_core/src/modules/js/@llrt/test/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llrt_core/src/modules/js/@llrt/test/index.ts b/llrt_core/src/modules/js/@llrt/test/index.ts index 91129a0ad5..b556cf9513 100644 --- a/llrt_core/src/modules/js/@llrt/test/index.ts +++ b/llrt_core/src/modules/js/@llrt/test/index.ts @@ -757,6 +757,6 @@ class TestServer { } const testServer = new TestServer((globalThis as any).__testEntries, { - workerCount: 1, + workerCount: undefined, }); await testServer.start(); From b674e0ec7eaa1d12cb23dbf5ef735d741f381423 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 3 Feb 2026 08:28:39 +0000 Subject: [PATCH 69/77] Refactor crypto module based on Sytten's review comments - Replace ShaAlgorithm with unified HashAlgorithm enum - Consolidate Hash and Hmac classes into hash.rs module - Move aes_variants.rs to provider/rust/ directory - Remove separate md5_hash.rs and sha_hash.rs files - Update all provider implementations to use HashAlgorithm - Update subtle/digest.rs to use HashAlgorithm --- modules/llrt_crypto/src/hash.rs | 178 ++++++++++++++++ modules/llrt_crypto/src/lib.rs | 18 +- modules/llrt_crypto/src/md5_hash.rs | 48 ----- modules/llrt_crypto/src/provider/graviola.rs | 48 ++--- modules/llrt_crypto/src/provider/mod.rs | 80 +++---- modules/llrt_crypto/src/provider/openssl.rs | 68 +++--- modules/llrt_crypto/src/provider/ring.rs | 44 ++-- .../{subtle => provider/rust}/aes_variants.rs | 0 .../src/provider/{rust.rs => rust/mod.rs} | 106 +++++----- modules/llrt_crypto/src/sha_hash.rs | 197 ------------------ modules/llrt_crypto/src/subtle/digest.rs | 10 +- .../llrt_crypto/src/subtle/generate_key.rs | 4 +- .../llrt_crypto/src/subtle/key_algorithm.rs | 22 +- modules/llrt_crypto/src/subtle/mod.rs | 12 +- modules/llrt_crypto/src/subtle/sign.rs | 2 +- .../llrt_crypto/src/subtle/sign_algorithm.rs | 4 +- 16 files changed, 379 insertions(+), 462 deletions(-) create mode 100644 modules/llrt_crypto/src/hash.rs delete mode 100644 modules/llrt_crypto/src/md5_hash.rs rename modules/llrt_crypto/src/{subtle => provider/rust}/aes_variants.rs (100%) rename modules/llrt_crypto/src/provider/{rust.rs => rust/mod.rs} (95%) delete mode 100644 modules/llrt_crypto/src/sha_hash.rs diff --git a/modules/llrt_crypto/src/hash.rs b/modules/llrt_crypto/src/hash.rs new file mode 100644 index 0000000000..fc82e85f92 --- /dev/null +++ b/modules/llrt_crypto/src/hash.rs @@ -0,0 +1,178 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use llrt_buffer::Buffer; +use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; +use rquickjs::{class::Trace, function::Opt, prelude::This, Class, Ctx, IntoJs, JsLifetime, Result, Value}; + +use super::encoded_bytes; +use crate::provider::{CryptoProvider, HmacProvider, SimpleDigest}; +use crate::CRYPTO_PROVIDER; + +#[derive(Debug, Clone, Copy)] +pub enum HashAlgorithm { + Md5, + Sha1, + Sha256, + Sha384, + Sha512, +} + +impl TryFrom<&str> for HashAlgorithm { + type Error = String; + fn try_from(s: &str) -> std::result::Result { + Ok(match s.to_ascii_uppercase().as_str() { + "MD5" => HashAlgorithm::Md5, + "MD-5" => HashAlgorithm::Md5, + "SHA1" => HashAlgorithm::Sha1, + "SHA-1" => HashAlgorithm::Sha1, + "SHA256" => HashAlgorithm::Sha256, + "SHA-256" => HashAlgorithm::Sha256, + "SHA384" => HashAlgorithm::Sha384, + "SHA-384" => HashAlgorithm::Sha384, + "SHA512" => HashAlgorithm::Sha512, + "SHA-512" => HashAlgorithm::Sha512, + _ => return Err(["'", s, "' not available"].concat()), + }) + } +} + +impl HashAlgorithm { + pub fn as_str(&self) -> &'static str { + match self { + HashAlgorithm::Md5 => "MD5", + HashAlgorithm::Sha1 => "SHA-1", + HashAlgorithm::Sha256 => "SHA-256", + HashAlgorithm::Sha384 => "SHA-384", + HashAlgorithm::Sha512 => "SHA-512", + } + } + + pub fn as_numeric_str(&self) -> &'static str { + match self { + HashAlgorithm::Md5 => "md5", + HashAlgorithm::Sha1 => "1", + HashAlgorithm::Sha256 => "256", + HashAlgorithm::Sha384 => "384", + HashAlgorithm::Sha512 => "512", + } + } + + pub fn digest_len(&self) -> usize { + match self { + HashAlgorithm::Md5 => 16, + HashAlgorithm::Sha1 => 20, + HashAlgorithm::Sha256 => 32, + HashAlgorithm::Sha384 => 48, + HashAlgorithm::Sha512 => 64, + } + } + + pub fn block_len(&self) -> usize { + match self { + HashAlgorithm::Md5 => 64, + HashAlgorithm::Sha1 => 64, + HashAlgorithm::Sha256 => 64, + HashAlgorithm::Sha384 => 128, + HashAlgorithm::Sha512 => 128, + } + } +} + +type ProviderDigest = ::Digest; +type ProviderHmac = ::Hmac; + +#[derive(Trace, JsLifetime)] +#[rquickjs::class] +pub struct Hash { + #[qjs(skip_trace)] + inner: Option, +} + +impl Hash { + pub fn new(ctx: Ctx<'_>, algorithm: String) -> Result { + let algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; + Ok(Self { + inner: Some(CRYPTO_PROVIDER.digest(algorithm)), + }) + } +} + +#[rquickjs::methods] +impl Hash { + #[qjs(rename = "digest")] + fn hash_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { + let digest = self + .inner + .take() + .ok_or_else(|| rquickjs::Exception::throw_message(&ctx, "Digest already called"))?; + let result = digest.finalize(); + let bytes: &[u8] = result.as_ref(); + + match encoding.0 { + Some(encoding) => encoded_bytes(ctx, bytes, &encoding), + None => Buffer(bytes.to_vec()).into_js(&ctx), + } + } + + #[qjs(rename = "update")] + fn hash_update<'js>( + this: This>, + ctx: Ctx<'js>, + bytes: ObjectBytes<'js>, + ) -> Result> { + let bytes = bytes.as_bytes(&ctx)?; + let mut borrowed = this.0.borrow_mut(); + if let Some(ref mut digest) = borrowed.inner { + digest.update(bytes); + } + drop(borrowed); + Ok(this.0) + } +} + +#[derive(Trace, JsLifetime)] +#[rquickjs::class] +pub struct Hmac { + #[qjs(skip_trace)] + inner: Option, +} + +impl Hmac { + pub fn new<'js>(ctx: Ctx<'js>, algorithm: String, key_value: ObjectBytes<'js>) -> Result { + let algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; + let key = key_value.as_bytes(&ctx)?; + let hmac = CRYPTO_PROVIDER.hmac(algorithm, key); + Ok(Self { inner: Some(hmac) }) + } +} + +#[rquickjs::methods] +impl Hmac { + fn digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { + let hmac = self + .inner + .take() + .ok_or_else(|| rquickjs::Exception::throw_message(&ctx, "Digest already called"))?; + let result = hmac.finalize(); + let bytes: &[u8] = result.as_ref(); + + match encoding.into_inner() { + Some(encoding) => encoded_bytes(ctx, bytes, &encoding), + None => Buffer(bytes.to_vec()).into_js(&ctx), + } + } + + fn update<'js>( + this: This>, + ctx: Ctx<'js>, + bytes: ObjectBytes<'js>, + ) -> Result> { + let bytes = bytes.as_bytes(&ctx)?; + let mut borrowed = this.0.borrow_mut(); + if let Some(ref mut hmac) = borrowed.inner { + hmac.update(bytes); + } + drop(borrowed); + Ok(this.0) + } +} diff --git a/modules/llrt_crypto/src/lib.rs b/modules/llrt_crypto/src/lib.rs index 433a5bed30..de91661db6 100644 --- a/modules/llrt_crypto/src/lib.rs +++ b/modules/llrt_crypto/src/lib.rs @@ -21,8 +21,7 @@ compile_error!("Features `crypto-openssl` and `crypto-graviola` are mutually exc compile_error!("Features `crypto-ring` and `crypto-graviola` are mutually exclusive"); mod crc32; -mod md5_hash; -mod sha_hash; +mod hash; mod subtle; mod provider; @@ -57,8 +56,7 @@ use subtle::{ use self::{ crc32::{Crc32, Crc32c}, - md5_hash::Md5, - sha_hash::{Hash, Hmac, ShaAlgorithm}, + hash::{Hash, Hmac}, }; static CRYPTO_PROVIDER: Lazy = @@ -292,18 +290,12 @@ impl ModuleDef for CryptoModule { declare.declare("createHmac")?; declare.declare("Crc32")?; declare.declare("Crc32c")?; - declare.declare("Md5")?; declare.declare("randomBytes")?; declare.declare("randomUUID")?; declare.declare("randomInt")?; declare.declare("randomFillSync")?; declare.declare("randomFill")?; declare.declare("getRandomValues")?; - - for sha_algorithm in ShaAlgorithm::iter() { - let class_name = sha_algorithm.class_name(); - declare.declare(class_name)?; - } declare.declare("crypto")?; declare.declare("webcrypto")?; declare.declare("default")?; @@ -313,14 +305,8 @@ impl ModuleDef for CryptoModule { fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { - for sha_algorithm in ShaAlgorithm::iter() { - let _class_name: &str = sha_algorithm.class_name(); - // ShaHash class removed - using Hash and Hmac instead - } - let crypto: Object = ctx.globals().get("crypto")?; - Class::::define(default)?; Class::::define(default)?; Class::::define(default)?; diff --git a/modules/llrt_crypto/src/md5_hash.rs b/modules/llrt_crypto/src/md5_hash.rs deleted file mode 100644 index 0538bd1e0c..0000000000 --- a/modules/llrt_crypto/src/md5_hash.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -use llrt_utils::bytes::{bytes_to_typed_array, ObjectBytes}; -use rquickjs::{function::Opt, prelude::This, Class, Ctx, Result, Value}; - -use super::{encoded_bytes, CRYPTO_PROVIDER}; -use crate::{ - provider::{CryptoProvider, SimpleDigest}, - sha_hash::ShaAlgorithm, -}; - -#[rquickjs::class] -#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] -pub struct Md5 { - #[qjs(skip_trace)] - hasher: ::Digest, -} - -#[rquickjs::methods] -impl Md5 { - #[qjs(constructor)] - fn new() -> Self { - Self { - hasher: CRYPTO_PROVIDER.digest(ShaAlgorithm::MD5), - } - } - - #[qjs(rename = "digest")] - fn md5_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let digest = std::mem::replace(&mut self.hasher, CRYPTO_PROVIDER.digest(ShaAlgorithm::MD5)) - .finalize(); - - match encoding.0 { - Some(encoding) => encoded_bytes(ctx, &digest, &encoding), - None => bytes_to_typed_array(ctx, &digest), - } - } - - #[qjs(rename = "update")] - fn md5_update<'js>( - this: This>, - ctx: Ctx<'js>, - bytes: ObjectBytes<'js>, - ) -> Result> { - this.0.borrow_mut().hasher.update(bytes.as_bytes(&ctx)?); - Ok(this.0) - } -} diff --git a/modules/llrt_crypto/src/provider/graviola.rs b/modules/llrt_crypto/src/provider/graviola.rs index 2349748f0e..59a2e4182a 100644 --- a/modules/llrt_crypto/src/provider/graviola.rs +++ b/modules/llrt_crypto/src/provider/graviola.rs @@ -12,7 +12,7 @@ use graviola::{ }; use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; -use crate::sha_hash::ShaAlgorithm; +use crate::hash::HashAlgorithm; use crate::subtle::EllipticCurve; pub struct GraviolaProvider; @@ -69,20 +69,20 @@ impl CryptoProvider for GraviolaProvider { type Digest = GraviolaDigest; type Hmac = GraviolaHmac; - fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { + fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { match algorithm { - ShaAlgorithm::SHA256 => GraviolaDigest::Sha256(Sha256::new()), - ShaAlgorithm::SHA384 => GraviolaDigest::Sha384(Sha384::new()), - ShaAlgorithm::SHA512 => GraviolaDigest::Sha512(Sha512::new()), + HashAlgorithm::Sha256 => GraviolaDigest::Sha256(Sha256::new()), + HashAlgorithm::Sha384 => GraviolaDigest::Sha384(Sha384::new()), + HashAlgorithm::Sha512 => GraviolaDigest::Sha512(Sha512::new()), _ => panic!("Unsupported digest algorithm for Graviola"), } } - fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { match algorithm { - ShaAlgorithm::SHA256 => GraviolaHmac::Sha256(Hmac::::new(key)), - ShaAlgorithm::SHA384 => GraviolaHmac::Sha384(Hmac::::new(key)), - ShaAlgorithm::SHA512 => GraviolaHmac::Sha512(Hmac::::new(key)), + HashAlgorithm::Sha256 => GraviolaHmac::Sha256(Hmac::::new(key)), + HashAlgorithm::Sha384 => GraviolaHmac::Sha384(Hmac::::new(key)), + HashAlgorithm::Sha512 => GraviolaHmac::Sha512(Hmac::::new(key)), _ => panic!("Unsupported HMAC algorithm for Graviola"), } } @@ -124,7 +124,7 @@ impl CryptoProvider for GraviolaProvider { _private_key_der: &[u8], _digest: &[u8], _salt_length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -135,7 +135,7 @@ impl CryptoProvider for GraviolaProvider { _signature: &[u8], _digest: &[u8], _salt_length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } @@ -144,7 +144,7 @@ impl CryptoProvider for GraviolaProvider { &self, _private_key_der: &[u8], _digest: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -154,7 +154,7 @@ impl CryptoProvider for GraviolaProvider { _public_key_der: &[u8], _signature: &[u8], _digest: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } @@ -163,7 +163,7 @@ impl CryptoProvider for GraviolaProvider { &self, _public_key_der: &[u8], _data: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) @@ -173,7 +173,7 @@ impl CryptoProvider for GraviolaProvider { &self, _private_key_der: &[u8], _data: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) @@ -267,7 +267,7 @@ impl CryptoProvider for GraviolaProvider { _salt: &[u8], _info: &[u8], _length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -278,7 +278,7 @@ impl CryptoProvider for GraviolaProvider { _salt: &[u8], _iterations: u32, _length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -292,13 +292,13 @@ impl CryptoProvider for GraviolaProvider { fn generate_hmac_key( &self, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, length_bits: u16, ) -> Result, CryptoError> { let length_bytes = if length_bits == 0 { match hash_alg { - ShaAlgorithm::SHA256 => 64, - ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 => 128, + HashAlgorithm::Sha256 => 64, + HashAlgorithm::Sha384 | HashAlgorithm::Sha512 => 128, _ => return Err(CryptoError::UnsupportedAlgorithm), } } else { @@ -501,9 +501,9 @@ pub enum GraviolaRustDigest { #[cfg(feature = "crypto-graviola-rust")] impl GraviolaRustDigest { - pub fn new(algorithm: ShaAlgorithm) -> Self { + pub fn new(algorithm: HashAlgorithm) -> Self { match algorithm { - ShaAlgorithm::SHA256 | ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 => { + HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::Sha512 => { Self::Graviola(GraviolaProvider.digest(algorithm)) }, _ => Self::Rust(super::rust::RustCryptoProvider.digest(algorithm)), @@ -535,9 +535,9 @@ pub enum GraviolaRustHmac { #[cfg(feature = "crypto-graviola-rust")] impl GraviolaRustHmac { - pub fn new(algorithm: ShaAlgorithm, key: &[u8]) -> Self { + pub fn new(algorithm: HashAlgorithm, key: &[u8]) -> Self { match algorithm { - ShaAlgorithm::SHA256 | ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 => { + HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::Sha512 => { Self::Graviola(GraviolaProvider.hmac(algorithm, key)) }, _ => Self::Rust(super::rust::RustCryptoProvider.hmac(algorithm, key)), diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index b36cfd4e37..ac6895056a 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -35,7 +35,7 @@ mod ring; #[cfg(feature = "_rustcrypto")] mod rust; -use crate::sha_hash::ShaAlgorithm; +use crate::hash::HashAlgorithm; use crate::subtle::EllipticCurve; #[derive(Debug)] @@ -144,10 +144,10 @@ pub trait CryptoProvider { type Hmac: HmacProvider; // Digest operations - fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest; + fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest; // HMAC operations - fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac; + fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac; // ECDSA operations fn ecdsa_sign( @@ -179,7 +179,7 @@ pub trait CryptoProvider { private_key_der: &[u8], digest: &[u8], salt_length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError>; fn rsa_pss_verify( &self, @@ -187,33 +187,33 @@ pub trait CryptoProvider { signature: &[u8], digest: &[u8], salt_length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result; fn rsa_pkcs1v15_sign( &self, private_key_der: &[u8], digest: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError>; fn rsa_pkcs1v15_verify( &self, public_key_der: &[u8], signature: &[u8], digest: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result; fn rsa_oaep_encrypt( &self, public_key_der: &[u8], data: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError>; fn rsa_oaep_decrypt( &self, private_key_der: &[u8], data: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError>; @@ -261,7 +261,7 @@ pub trait CryptoProvider { salt: &[u8], info: &[u8], length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError>; fn pbkdf2_derive_key( &self, @@ -269,13 +269,13 @@ pub trait CryptoProvider { salt: &[u8], iterations: u32, length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError>; fn generate_aes_key(&self, length_bits: u16) -> Result, CryptoError>; fn generate_hmac_key( &self, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, length_bits: u16, ) -> Result, CryptoError>; fn generate_ec_key(&self, curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError>; // (private, public) @@ -470,10 +470,10 @@ macro_rules! impl_hybrid_provider { impl CryptoProvider for $name { type Digest = $digest; type Hmac = $hmac; - fn digest(&self, alg: ShaAlgorithm) -> Self::Digest { + fn digest(&self, alg: HashAlgorithm) -> Self::Digest { $digest_fn(alg) } - fn hmac(&self, alg: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, alg: HashAlgorithm, key: &[u8]) -> Self::Hmac { $hmac_fn(alg, key) } fn ecdsa_sign( @@ -504,7 +504,7 @@ macro_rules! impl_hybrid_provider { k: &[u8], d: &[u8], s: usize, - a: ShaAlgorithm, + a: HashAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_pss_sign(k, d, s, a) } @@ -514,7 +514,7 @@ macro_rules! impl_hybrid_provider { s: &[u8], d: &[u8], sl: usize, - a: ShaAlgorithm, + a: HashAlgorithm, ) -> Result { rust::RustCryptoProvider.rsa_pss_verify(k, s, d, sl, a) } @@ -522,7 +522,7 @@ macro_rules! impl_hybrid_provider { &self, k: &[u8], d: &[u8], - a: ShaAlgorithm, + a: HashAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_pkcs1v15_sign(k, d, a) } @@ -531,7 +531,7 @@ macro_rules! impl_hybrid_provider { k: &[u8], s: &[u8], d: &[u8], - a: ShaAlgorithm, + a: HashAlgorithm, ) -> Result { rust::RustCryptoProvider.rsa_pkcs1v15_verify(k, s, d, a) } @@ -539,7 +539,7 @@ macro_rules! impl_hybrid_provider { &self, k: &[u8], d: &[u8], - a: ShaAlgorithm, + a: HashAlgorithm, l: Option<&[u8]>, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_oaep_encrypt(k, d, a, l) @@ -548,7 +548,7 @@ macro_rules! impl_hybrid_provider { &self, k: &[u8], d: &[u8], - a: ShaAlgorithm, + a: HashAlgorithm, l: Option<&[u8]>, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_oaep_decrypt(k, d, a, l) @@ -596,7 +596,7 @@ macro_rules! impl_hybrid_provider { s: &[u8], i: &[u8], l: usize, - a: ShaAlgorithm, + a: HashAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.hkdf_derive_key(k, s, i, l, a) } @@ -606,14 +606,14 @@ macro_rules! impl_hybrid_provider { s: &[u8], i: u32, l: usize, - a: ShaAlgorithm, + a: HashAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.pbkdf2_derive_key(p, s, i, l, a) } fn generate_aes_key(&self, b: u16) -> Result, CryptoError> { rust::RustCryptoProvider.generate_aes_key(b) } - fn generate_hmac_key(&self, a: ShaAlgorithm, b: u16) -> Result, CryptoError> { + fn generate_hmac_key(&self, a: HashAlgorithm, b: u16) -> Result, CryptoError> { rust::RustCryptoProvider.generate_hmac_key(a, b) } fn generate_ec_key(&self, c: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { @@ -861,7 +861,7 @@ mod tests { #[test] fn test_sha256_digest() { let p = provider(); - let mut digest = p.digest(ShaAlgorithm::SHA256); + let mut digest = p.digest(HashAlgorithm::Sha256); digest.update(b"hello world"); let result = digest.finalize(); assert_eq!(result.len(), 32); @@ -874,7 +874,7 @@ mod tests { #[test] fn test_sha384_digest() { let p = provider(); - let mut digest = p.digest(ShaAlgorithm::SHA384); + let mut digest = p.digest(HashAlgorithm::Sha384); digest.update(b"hello world"); let result = digest.finalize(); assert_eq!(result.len(), 48); @@ -883,7 +883,7 @@ mod tests { #[test] fn test_sha512_digest() { let p = provider(); - let mut digest = p.digest(ShaAlgorithm::SHA512); + let mut digest = p.digest(HashAlgorithm::Sha512); digest.update(b"hello world"); let result = digest.finalize(); assert_eq!(result.len(), 64); @@ -894,7 +894,7 @@ mod tests { fn test_hmac_sha256() { let p = provider(); let key = b"secret key"; - let mut hmac = p.hmac(ShaAlgorithm::SHA256, key); + let mut hmac = p.hmac(HashAlgorithm::Sha256, key); hmac.update(b"hello world"); let result = hmac.finalize(); assert_eq!(result.len(), 32); @@ -1035,7 +1035,7 @@ mod tests { #[test] fn test_generate_hmac_key() { let p = provider(); - let key = p.generate_hmac_key(ShaAlgorithm::SHA256, 256).unwrap(); + let key = p.generate_hmac_key(HashAlgorithm::Sha256, 256).unwrap(); assert_eq!(key.len(), 32); } @@ -1117,7 +1117,7 @@ mod tests { let info = b"info"; let derived = p - .hkdf_derive_key(ikm, salt, info, 32, ShaAlgorithm::SHA256) + .hkdf_derive_key(ikm, salt, info, 32, HashAlgorithm::Sha256) .unwrap(); assert_eq!(derived.len(), 32); @@ -1130,7 +1130,7 @@ mod tests { let salt = b"salt"; let derived = p - .pbkdf2_derive_key(password, salt, 1000, 32, ShaAlgorithm::SHA256) + .pbkdf2_derive_key(password, salt, 1000, 32, HashAlgorithm::Sha256) .unwrap(); assert_eq!(derived.len(), 32); @@ -1142,7 +1142,7 @@ mod tests { let (private_key, public_key) = p.generate_ec_key(EllipticCurve::P256).unwrap(); // Create a digest to sign - let mut digest = p.digest(ShaAlgorithm::SHA256); + let mut digest = p.digest(HashAlgorithm::Sha256); digest.update(b"message to sign"); let hash = digest.finalize(); @@ -1162,7 +1162,7 @@ mod tests { let p = provider(); let (private_key, public_key) = p.generate_ec_key(EllipticCurve::P384).unwrap(); - let mut digest = p.digest(ShaAlgorithm::SHA384); + let mut digest = p.digest(HashAlgorithm::Sha384); digest.update(b"message to sign"); let hash = digest.finalize(); @@ -1224,16 +1224,16 @@ mod tests { let p = provider(); let (private_key, public_key) = p.generate_rsa_key(2048, &[1, 0, 1]).unwrap(); - let mut digest = p.digest(ShaAlgorithm::SHA256); + let mut digest = p.digest(HashAlgorithm::Sha256); digest.update(b"message to sign"); let hash = digest.finalize(); let signature = p - .rsa_pss_sign(&private_key, &hash, 32, ShaAlgorithm::SHA256) + .rsa_pss_sign(&private_key, &hash, 32, HashAlgorithm::Sha256) .unwrap(); let valid = p - .rsa_pss_verify(&public_key, &signature, &hash, 32, ShaAlgorithm::SHA256) + .rsa_pss_verify(&public_key, &signature, &hash, 32, HashAlgorithm::Sha256) .unwrap(); assert!(valid); @@ -1244,16 +1244,16 @@ mod tests { let p = provider(); let (private_key, public_key) = p.generate_rsa_key(2048, &[1, 0, 1]).unwrap(); - let mut digest = p.digest(ShaAlgorithm::SHA256); + let mut digest = p.digest(HashAlgorithm::Sha256); digest.update(b"message to sign"); let hash = digest.finalize(); let signature = p - .rsa_pkcs1v15_sign(&private_key, &hash, ShaAlgorithm::SHA256) + .rsa_pkcs1v15_sign(&private_key, &hash, HashAlgorithm::Sha256) .unwrap(); let valid = p - .rsa_pkcs1v15_verify(&public_key, &signature, &hash, ShaAlgorithm::SHA256) + .rsa_pkcs1v15_verify(&public_key, &signature, &hash, HashAlgorithm::Sha256) .unwrap(); assert!(valid); @@ -1267,11 +1267,11 @@ mod tests { let plaintext = b"secret message"; let ciphertext = p - .rsa_oaep_encrypt(&public_key, plaintext, ShaAlgorithm::SHA256, None) + .rsa_oaep_encrypt(&public_key, plaintext, HashAlgorithm::Sha256, None) .unwrap(); let decrypted = p - .rsa_oaep_decrypt(&private_key, &ciphertext, ShaAlgorithm::SHA256, None) + .rsa_oaep_decrypt(&private_key, &ciphertext, HashAlgorithm::Sha256, None) .unwrap(); assert_eq!(decrypted, plaintext); diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs index 0f759e283a..7386aec240 100644 --- a/modules/llrt_crypto/src/provider/openssl.rs +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -18,7 +18,7 @@ use openssl::sign::{Signer, Verifier}; use openssl::symm::{self, Cipher}; use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; -use crate::sha_hash::ShaAlgorithm; +use crate::hash::HashAlgorithm; use crate::subtle::EllipticCurve; pub struct OpenSslProvider; @@ -71,23 +71,23 @@ impl HmacProvider for OpenSslHmac { } } -fn get_message_digest(alg: ShaAlgorithm) -> MessageDigest { +fn get_message_digest(alg: HashAlgorithm) -> MessageDigest { match alg { - ShaAlgorithm::MD5 => MessageDigest::md5(), - ShaAlgorithm::SHA1 => MessageDigest::sha1(), - ShaAlgorithm::SHA256 => MessageDigest::sha256(), - ShaAlgorithm::SHA384 => MessageDigest::sha384(), - ShaAlgorithm::SHA512 => MessageDigest::sha512(), + HashAlgorithm::Md5 => MessageDigest::md5(), + HashAlgorithm::Sha1 => MessageDigest::sha1(), + HashAlgorithm::Sha256 => MessageDigest::sha256(), + HashAlgorithm::Sha384 => MessageDigest::sha384(), + HashAlgorithm::Sha512 => MessageDigest::sha512(), } } -fn get_md(alg: ShaAlgorithm) -> &'static openssl::md::MdRef { +fn get_md(alg: HashAlgorithm) -> &'static openssl::md::MdRef { match alg { - ShaAlgorithm::MD5 => Md::md5(), - ShaAlgorithm::SHA1 => Md::sha1(), - ShaAlgorithm::SHA256 => Md::sha256(), - ShaAlgorithm::SHA384 => Md::sha384(), - ShaAlgorithm::SHA512 => Md::sha512(), + HashAlgorithm::Md5 => Md::md5(), + HashAlgorithm::Sha1 => Md::sha1(), + HashAlgorithm::Sha256 => Md::sha256(), + HashAlgorithm::Sha384 => Md::sha384(), + HashAlgorithm::Sha512 => Md::sha512(), } } @@ -109,19 +109,19 @@ impl CryptoProvider for OpenSslProvider { type Digest = OpenSslDigest; type Hmac = OpenSslHmac; - fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { + fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { let md = get_message_digest(algorithm); let hasher = Hasher::new(md).expect("Failed to create hasher"); match algorithm { - ShaAlgorithm::MD5 => OpenSslDigest::Md5(hasher), - ShaAlgorithm::SHA1 => OpenSslDigest::Sha1(hasher), - ShaAlgorithm::SHA256 => OpenSslDigest::Sha256(hasher), - ShaAlgorithm::SHA384 => OpenSslDigest::Sha384(hasher), - ShaAlgorithm::SHA512 => OpenSslDigest::Sha512(hasher), + HashAlgorithm::Md5 => OpenSslDigest::Md5(hasher), + HashAlgorithm::Sha1 => OpenSslDigest::Sha1(hasher), + HashAlgorithm::Sha256 => OpenSslDigest::Sha256(hasher), + HashAlgorithm::Sha384 => OpenSslDigest::Sha384(hasher), + HashAlgorithm::Sha512 => OpenSslDigest::Sha512(hasher), } } - fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { let md = get_message_digest(algorithm); let pkey = PKey::hmac(key).expect("Failed to create HMAC key"); let signer = unsafe { @@ -208,7 +208,7 @@ impl CryptoProvider for OpenSslProvider { private_key_der: &[u8], digest: &[u8], salt_length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let rsa = Rsa::private_key_from_der(private_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; @@ -240,7 +240,7 @@ impl CryptoProvider for OpenSslProvider { signature: &[u8], digest: &[u8], salt_length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result { let rsa = Rsa::public_key_from_der(public_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; @@ -268,7 +268,7 @@ impl CryptoProvider for OpenSslProvider { &self, private_key_der: &[u8], digest: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let rsa = Rsa::private_key_from_der(private_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; @@ -293,7 +293,7 @@ impl CryptoProvider for OpenSslProvider { public_key_der: &[u8], signature: &[u8], digest: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result { let rsa = Rsa::public_key_from_der(public_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; @@ -315,7 +315,7 @@ impl CryptoProvider for OpenSslProvider { &self, public_key_der: &[u8], data: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let rsa = Rsa::public_key_from_der(public_key_der) @@ -348,7 +348,7 @@ impl CryptoProvider for OpenSslProvider { &self, private_key_der: &[u8], data: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let rsa = Rsa::private_key_from_der(private_key_der) @@ -586,7 +586,7 @@ impl CryptoProvider for OpenSslProvider { salt: &[u8], info: &[u8], length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { use openssl::pkey_ctx::HkdfMode; let md = get_md(hash_alg); @@ -620,7 +620,7 @@ impl CryptoProvider for OpenSslProvider { salt: &[u8], iterations: u32, length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let md = get_message_digest(hash_alg); let mut out = vec![0u8; length]; @@ -639,16 +639,16 @@ impl CryptoProvider for OpenSslProvider { fn generate_hmac_key( &self, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, length_bits: u16, ) -> Result, CryptoError> { let length_bytes = if length_bits == 0 { match hash_alg { - ShaAlgorithm::MD5 => 16, - ShaAlgorithm::SHA1 => 20, - ShaAlgorithm::SHA256 => 32, - ShaAlgorithm::SHA384 => 48, - ShaAlgorithm::SHA512 => 64, + HashAlgorithm::Md5 => 16, + HashAlgorithm::Sha1 => 20, + HashAlgorithm::Sha256 => 32, + HashAlgorithm::Sha384 => 48, + HashAlgorithm::Sha512 => 64, } } else { (length_bits / 8) as usize diff --git a/modules/llrt_crypto/src/provider/ring.rs b/modules/llrt_crypto/src/provider/ring.rs index fe71da2400..e5fa9d74fa 100644 --- a/modules/llrt_crypto/src/provider/ring.rs +++ b/modules/llrt_crypto/src/provider/ring.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; -use crate::sha_hash::ShaAlgorithm; +use crate::hash::HashAlgorithm; use crate::subtle::EllipticCurve; use md5::{Digest, Md5 as Md5Hasher}; use ring::{digest, hmac}; @@ -147,33 +147,33 @@ impl CryptoProvider for RingProvider { type Digest = RingDigestType; type Hmac = RingHmacType; - fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { + fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { match algorithm { - ShaAlgorithm::MD5 => RingDigestType::Md5(RingMd5(Md5Hasher::new())), - ShaAlgorithm::SHA1 => { + HashAlgorithm::Md5 => RingDigestType::Md5(RingMd5(Md5Hasher::new())), + HashAlgorithm::Sha1 => { RingDigestType::Sha1(RingDigest::new(&digest::SHA1_FOR_LEGACY_USE_ONLY)) }, - ShaAlgorithm::SHA256 => RingDigestType::Sha256(RingDigest::new(&digest::SHA256)), - ShaAlgorithm::SHA384 => RingDigestType::Sha384(RingDigest::new(&digest::SHA384)), - ShaAlgorithm::SHA512 => RingDigestType::Sha512(RingDigest::new(&digest::SHA512)), + HashAlgorithm::Sha256 => RingDigestType::Sha256(RingDigest::new(&digest::SHA256)), + HashAlgorithm::Sha384 => RingDigestType::Sha384(RingDigest::new(&digest::SHA384)), + HashAlgorithm::Sha512 => RingDigestType::Sha512(RingDigest::new(&digest::SHA512)), } } - fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { match algorithm { - ShaAlgorithm::MD5 => { + HashAlgorithm::Md5 => { panic!("HMAC-MD5 not supported by Ring provider"); }, - ShaAlgorithm::SHA1 => RingHmacType::Sha1(RingHmacSha1(hmac::Context::with_key( + HashAlgorithm::Sha1 => RingHmacType::Sha1(RingHmacSha1(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, key), ))), - ShaAlgorithm::SHA256 => RingHmacType::Sha256(RingHmacSha256(hmac::Context::with_key( + HashAlgorithm::Sha256 => RingHmacType::Sha256(RingHmacSha256(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA256, key), ))), - ShaAlgorithm::SHA384 => RingHmacType::Sha384(RingHmacSha384(hmac::Context::with_key( + HashAlgorithm::Sha384 => RingHmacType::Sha384(RingHmacSha384(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA384, key), ))), - ShaAlgorithm::SHA512 => RingHmacType::Sha512(RingHmacSha512(hmac::Context::with_key( + HashAlgorithm::Sha512 => RingHmacType::Sha512(RingHmacSha512(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA512, key), ))), } @@ -216,7 +216,7 @@ impl CryptoProvider for RingProvider { _private_key_der: &[u8], _digest: &[u8], _salt_length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -227,7 +227,7 @@ impl CryptoProvider for RingProvider { _signature: &[u8], _digest: &[u8], _salt_length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } @@ -236,7 +236,7 @@ impl CryptoProvider for RingProvider { &self, _private_key_der: &[u8], _digest: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -246,7 +246,7 @@ impl CryptoProvider for RingProvider { _public_key_der: &[u8], _signature: &[u8], _digest: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } @@ -255,7 +255,7 @@ impl CryptoProvider for RingProvider { &self, _public_key_der: &[u8], _data: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) @@ -265,7 +265,7 @@ impl CryptoProvider for RingProvider { &self, _private_key_der: &[u8], _data: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) @@ -324,7 +324,7 @@ impl CryptoProvider for RingProvider { _salt: &[u8], _info: &[u8], _length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -335,7 +335,7 @@ impl CryptoProvider for RingProvider { _salt: &[u8], _iterations: u32, _length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -346,7 +346,7 @@ impl CryptoProvider for RingProvider { fn generate_hmac_key( &self, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, _length_bits: u16, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) diff --git a/modules/llrt_crypto/src/subtle/aes_variants.rs b/modules/llrt_crypto/src/provider/rust/aes_variants.rs similarity index 100% rename from modules/llrt_crypto/src/subtle/aes_variants.rs rename to modules/llrt_crypto/src/provider/rust/aes_variants.rs diff --git a/modules/llrt_crypto/src/provider/rust.rs b/modules/llrt_crypto/src/provider/rust/mod.rs similarity index 95% rename from modules/llrt_crypto/src/provider/rust.rs rename to modules/llrt_crypto/src/provider/rust/mod.rs index 757bac1c07..3ef2643433 100644 --- a/modules/llrt_crypto/src/provider/rust.rs +++ b/modules/llrt_crypto/src/provider/rust/mod.rs @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +mod aes_variants; + use std::num::NonZeroU32; use aes::cipher::{ @@ -56,10 +58,12 @@ use sha1::Sha1; use crate::{ provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}, random_byte_array, - sha_hash::ShaAlgorithm, - subtle::{AesGcmVariant, EllipticCurve}, + hash::HashAlgorithm, + subtle::EllipticCurve, }; +use aes_variants::AesGcmVariant; + impl From for CryptoError { fn from(_: aes::cipher::InvalidLength) -> Self { CryptoError::InvalidLength @@ -141,27 +145,27 @@ impl CryptoProvider for RustCryptoProvider { type Digest = RustDigest; type Hmac = RustHmac; - fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { + fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { match algorithm { - ShaAlgorithm::MD5 => RustDigest::Md5(md5::Md5::new()), - ShaAlgorithm::SHA1 => RustDigest::Sha1(Sha1::new()), - ShaAlgorithm::SHA256 => RustDigest::Sha256(Sha256::new()), - ShaAlgorithm::SHA384 => RustDigest::Sha384(Sha384::new()), - ShaAlgorithm::SHA512 => RustDigest::Sha512(Sha512::new()), + HashAlgorithm::Md5 => RustDigest::Md5(md5::Md5::new()), + HashAlgorithm::Sha1 => RustDigest::Sha1(Sha1::new()), + HashAlgorithm::Sha256 => RustDigest::Sha256(Sha256::new()), + HashAlgorithm::Sha384 => RustDigest::Sha384(Sha384::new()), + HashAlgorithm::Sha512 => RustDigest::Sha512(Sha512::new()), } } - fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { match algorithm { - ShaAlgorithm::MD5 => panic!("HMAC-MD5 not supported"), - ShaAlgorithm::SHA1 => RustHmac::Sha1(HmacImpl::::new_from_slice(key).unwrap()), - ShaAlgorithm::SHA256 => { + HashAlgorithm::Md5 => panic!("HMAC-MD5 not supported"), + HashAlgorithm::Sha1 => RustHmac::Sha1(HmacImpl::::new_from_slice(key).unwrap()), + HashAlgorithm::Sha256 => { RustHmac::Sha256(HmacImpl::::new_from_slice(key).unwrap()) }, - ShaAlgorithm::SHA384 => { + HashAlgorithm::Sha384 => { RustHmac::Sha384(HmacImpl::::new_from_slice(key).unwrap()) }, - ShaAlgorithm::SHA512 => { + HashAlgorithm::Sha512 => { RustHmac::Sha512(HmacImpl::::new_from_slice(key).unwrap()) }, } @@ -258,20 +262,20 @@ impl CryptoProvider for RustCryptoProvider { private_key_der: &[u8], digest: &[u8], salt_length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let mut rng = rand::rng(); let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - ShaAlgorithm::SHA256 => private_key + HashAlgorithm::Sha256 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed(None)), - ShaAlgorithm::SHA384 => private_key + HashAlgorithm::Sha384 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed(None)), - ShaAlgorithm::SHA512 => private_key + HashAlgorithm::Sha512 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed(None)), _ => Err(CryptoError::UnsupportedAlgorithm), @@ -284,19 +288,19 @@ impl CryptoProvider for RustCryptoProvider { signature: &[u8], digest: &[u8], salt_length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result { let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - ShaAlgorithm::SHA256 => Ok(public_key + HashAlgorithm::Sha256 => Ok(public_key .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), - ShaAlgorithm::SHA384 => Ok(public_key + HashAlgorithm::Sha384 => Ok(public_key .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), - ShaAlgorithm::SHA512 => Ok(public_key + HashAlgorithm::Sha512 => Ok(public_key .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), _ => Err(CryptoError::UnsupportedAlgorithm), @@ -307,20 +311,20 @@ impl CryptoProvider for RustCryptoProvider { &self, private_key_der: &[u8], digest: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let mut rng = rand::rng(); let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - ShaAlgorithm::SHA256 => private_key + HashAlgorithm::Sha256 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) .map_err(|_| CryptoError::SigningFailed(None)), - ShaAlgorithm::SHA384 => private_key + HashAlgorithm::Sha384 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) .map_err(|_| CryptoError::SigningFailed(None)), - ShaAlgorithm::SHA512 => private_key + HashAlgorithm::Sha512 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) .map_err(|_| CryptoError::SigningFailed(None)), _ => Err(CryptoError::UnsupportedAlgorithm), @@ -332,19 +336,19 @@ impl CryptoProvider for RustCryptoProvider { public_key_der: &[u8], signature: &[u8], digest: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result { let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - ShaAlgorithm::SHA256 => Ok(public_key + HashAlgorithm::Sha256 => Ok(public_key .verify(Pkcs1v15Sign::new::(), digest, signature) .is_ok()), - ShaAlgorithm::SHA384 => Ok(public_key + HashAlgorithm::Sha384 => Ok(public_key .verify(Pkcs1v15Sign::new::(), digest, signature) .is_ok()), - ShaAlgorithm::SHA512 => Ok(public_key + HashAlgorithm::Sha512 => Ok(public_key .verify(Pkcs1v15Sign::new::(), digest, signature) .is_ok()), _ => Err(CryptoError::UnsupportedAlgorithm), @@ -355,7 +359,7 @@ impl CryptoProvider for RustCryptoProvider { &self, public_key_der: &[u8], data: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let mut rng = rand::rng(); @@ -363,7 +367,7 @@ impl CryptoProvider for RustCryptoProvider { .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - ShaAlgorithm::SHA1 => { + HashAlgorithm::Sha1 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -374,7 +378,7 @@ impl CryptoProvider for RustCryptoProvider { .encrypt(&mut rng, padding, data) .map_err(|_| CryptoError::EncryptionFailed(None)) }, - ShaAlgorithm::SHA256 => { + HashAlgorithm::Sha256 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -385,7 +389,7 @@ impl CryptoProvider for RustCryptoProvider { .encrypt(&mut rng, padding, data) .map_err(|_| CryptoError::EncryptionFailed(None)) }, - ShaAlgorithm::SHA384 => { + HashAlgorithm::Sha384 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -396,7 +400,7 @@ impl CryptoProvider for RustCryptoProvider { .encrypt(&mut rng, padding, data) .map_err(|_| CryptoError::EncryptionFailed(None)) }, - ShaAlgorithm::SHA512 => { + HashAlgorithm::Sha512 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -415,14 +419,14 @@ impl CryptoProvider for RustCryptoProvider { &self, private_key_der: &[u8], data: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - ShaAlgorithm::SHA1 => { + HashAlgorithm::Sha1 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -433,7 +437,7 @@ impl CryptoProvider for RustCryptoProvider { .decrypt(padding, data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, - ShaAlgorithm::SHA256 => { + HashAlgorithm::Sha256 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -444,7 +448,7 @@ impl CryptoProvider for RustCryptoProvider { .decrypt(padding, data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, - ShaAlgorithm::SHA384 => { + HashAlgorithm::Sha384 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -455,7 +459,7 @@ impl CryptoProvider for RustCryptoProvider { .decrypt(padding, data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, - ShaAlgorithm::SHA512 => { + HashAlgorithm::Sha512 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -769,15 +773,15 @@ impl CryptoProvider for RustCryptoProvider { salt: &[u8], info: &[u8], length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { use ring::hkdf; let algorithm = match hash_alg { - ShaAlgorithm::SHA1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY, - ShaAlgorithm::SHA256 => hkdf::HKDF_SHA256, - ShaAlgorithm::SHA384 => hkdf::HKDF_SHA384, - ShaAlgorithm::SHA512 => hkdf::HKDF_SHA512, + HashAlgorithm::Sha1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY, + HashAlgorithm::Sha256 => hkdf::HKDF_SHA256, + HashAlgorithm::Sha384 => hkdf::HKDF_SHA384, + HashAlgorithm::Sha512 => hkdf::HKDF_SHA512, _ => return Err(CryptoError::UnsupportedAlgorithm), }; @@ -800,13 +804,13 @@ impl CryptoProvider for RustCryptoProvider { salt: &[u8], iterations: u32, length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let algorithm = match hash_alg { - ShaAlgorithm::SHA1 => pbkdf2::PBKDF2_HMAC_SHA1, - ShaAlgorithm::SHA256 => pbkdf2::PBKDF2_HMAC_SHA256, - ShaAlgorithm::SHA384 => pbkdf2::PBKDF2_HMAC_SHA384, - ShaAlgorithm::SHA512 => pbkdf2::PBKDF2_HMAC_SHA512, + HashAlgorithm::Sha1 => pbkdf2::PBKDF2_HMAC_SHA1, + HashAlgorithm::Sha256 => pbkdf2::PBKDF2_HMAC_SHA256, + HashAlgorithm::Sha384 => pbkdf2::PBKDF2_HMAC_SHA384, + HashAlgorithm::Sha512 => pbkdf2::PBKDF2_HMAC_SHA512, _ => return Err(CryptoError::UnsupportedAlgorithm), }; @@ -826,7 +830,7 @@ impl CryptoProvider for RustCryptoProvider { fn generate_hmac_key( &self, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, length_bits: u16, ) -> Result, CryptoError> { let length_bytes = if length_bits == 0 { diff --git a/modules/llrt_crypto/src/sha_hash.rs b/modules/llrt_crypto/src/sha_hash.rs deleted file mode 100644 index 38cf2505d4..0000000000 --- a/modules/llrt_crypto/src/sha_hash.rs +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -use llrt_utils::{ - bytes::{bytes_to_typed_array, ObjectBytes}, - iterable_enum, - result::ResultExt, -}; -use rquickjs::{function::Opt, prelude::This, Class, Ctx, Exception, Result, Value}; - -use super::{encoded_bytes, CRYPTO_PROVIDER}; -use crate::provider::{CryptoProvider, DefaultProvider, HmacProvider, SimpleDigest}; - -type ProviderHmac = ::Hmac; -type ProviderDigest = ::Digest; - -#[rquickjs::class] -#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] -pub struct Hmac { - #[qjs(skip_trace)] - inner: Option, -} - -#[rquickjs::methods] -impl Hmac { - #[qjs(skip)] - pub fn new<'js>(ctx: Ctx<'js>, algorithm: String, key_value: ObjectBytes<'js>) -> Result { - let algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; - let key = key_value.as_bytes(&ctx)?; - let hmac = CRYPTO_PROVIDER.hmac(algorithm, key); - - Ok(Self { inner: Some(hmac) }) - } - - fn digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let hmac = self - .inner - .take() - .ok_or_else(|| Exception::throw_message(&ctx, "Digest already called"))?; - let result = hmac.finalize(); - - match encoding.into_inner() { - Some(encoding) => encoded_bytes(ctx, &result, &encoding), - None => bytes_to_typed_array(ctx, &result), - } - } - - fn update<'js>( - this: This>, - ctx: Ctx<'js>, - bytes: ObjectBytes<'js>, - ) -> Result> { - let bytes = bytes.as_bytes(&ctx)?; - let mut borrowed = this.0.borrow_mut(); - if let Some(ref mut hmac) = borrowed.inner { - hmac.update(bytes); - } - drop(borrowed); - Ok(this.0) - } -} - -#[rquickjs::class] -#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] -pub struct Hash { - #[qjs(skip_trace)] - inner: Option, -} - -#[rquickjs::methods] -impl Hash { - #[qjs(skip)] - pub fn new(ctx: Ctx<'_>, algorithm: String) -> Result { - let algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; - let digest = CRYPTO_PROVIDER.digest(algorithm); - - Ok(Self { - inner: Some(digest), - }) - } - - #[qjs(rename = "digest")] - fn hash_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let digest = self - .inner - .take() - .ok_or_else(|| Exception::throw_message(&ctx, "Digest already called"))?; - let result = digest.finalize(); - - match encoding.0 { - Some(encoding) => encoded_bytes(ctx, &result, &encoding), - None => bytes_to_typed_array(ctx, &result), - } - } - - #[qjs(rename = "update")] - fn hash_update<'js>( - this: This>, - ctx: Ctx<'js>, - bytes: ObjectBytes<'js>, - ) -> Result> { - let bytes = bytes.as_bytes(&ctx)?; - let mut borrowed = this.0.borrow_mut(); - if let Some(ref mut digest) = borrowed.inner { - digest.update(bytes); - } - drop(borrowed); - Ok(this.0) - } -} - -#[derive(Debug, Clone, Copy)] -pub enum ShaAlgorithm { - MD5, - SHA1, - SHA256, - SHA384, - SHA512, -} - -iterable_enum!(ShaAlgorithm, MD5, SHA1, SHA256, SHA384, SHA512); - -impl ShaAlgorithm { - pub fn class_name(&self) -> &'static str { - match self { - ShaAlgorithm::MD5 => "Md5", - ShaAlgorithm::SHA1 => "Sha1", - ShaAlgorithm::SHA256 => "Sha256", - ShaAlgorithm::SHA384 => "Sha384", - ShaAlgorithm::SHA512 => "Sha512", - } - } - - /// Returns the block size in bytes for this hash algorithm - pub fn block_len(&self) -> usize { - match self { - ShaAlgorithm::MD5 => 64, - ShaAlgorithm::SHA1 => 64, - ShaAlgorithm::SHA256 => 64, - ShaAlgorithm::SHA384 => 128, - ShaAlgorithm::SHA512 => 128, - } - } - - /// Returns the digest/output size in bytes for this hash algorithm - pub fn digest_len(&self) -> usize { - match self { - ShaAlgorithm::MD5 => 16, - ShaAlgorithm::SHA1 => 20, - ShaAlgorithm::SHA256 => 32, - ShaAlgorithm::SHA384 => 48, - ShaAlgorithm::SHA512 => 64, - } - } - - pub fn as_str(&self) -> &'static str { - match self { - ShaAlgorithm::MD5 => "MD5", - ShaAlgorithm::SHA1 => "SHA-1", - ShaAlgorithm::SHA256 => "SHA-256", - ShaAlgorithm::SHA384 => "SHA-384", - ShaAlgorithm::SHA512 => "SHA-512", - } - } - - pub fn as_numeric_str(&self) -> &'static str { - match self { - ShaAlgorithm::MD5 => "md5", - ShaAlgorithm::SHA1 => "1", - ShaAlgorithm::SHA256 => "256", - ShaAlgorithm::SHA384 => "384", - ShaAlgorithm::SHA512 => "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() - } -} diff --git a/modules/llrt_crypto/src/subtle/digest.rs b/modules/llrt_crypto/src/subtle/digest.rs index 2841f60600..eb71c1e3aa 100644 --- a/modules/llrt_crypto/src/subtle/digest.rs +++ b/modules/llrt_crypto/src/subtle/digest.rs @@ -4,8 +4,8 @@ use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt}; use rquickjs::{ArrayBuffer, Ctx, Result, Value}; use crate::{ + hash::HashAlgorithm, provider::{CryptoProvider, SimpleDigest}, - sha_hash::ShaAlgorithm, CRYPTO_PROVIDER, }; @@ -20,13 +20,13 @@ pub async fn subtle_digest<'js>( algorithm.get_required::<_, String>("name", "algorithm")? }; - let sha_algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; - let bytes = digest(&sha_algorithm, data.as_bytes(&ctx)?); + let hash_algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; + let bytes = digest(&hash_algorithm, data.as_bytes(&ctx)?); ArrayBuffer::new(ctx, bytes) } -pub fn digest(sha_algorithm: &ShaAlgorithm, data: &[u8]) -> Vec { - let mut hasher = CRYPTO_PROVIDER.digest(*sha_algorithm); +pub fn digest(hash_algorithm: &HashAlgorithm, data: &[u8]) -> Vec { + let mut hasher = CRYPTO_PROVIDER.digest(*hash_algorithm); hasher.update(data); hasher.finalize() } diff --git a/modules/llrt_crypto/src/subtle/generate_key.rs b/modules/llrt_crypto/src/subtle/generate_key.rs index 46973d4d32..8ace80585b 100644 --- a/modules/llrt_crypto/src/subtle/generate_key.rs +++ b/modules/llrt_crypto/src/subtle/generate_key.rs @@ -4,7 +4,7 @@ use rquickjs::{object::Property, Array, Class, Ctx, Exception, Object, Result, V use crate::{provider::CryptoProvider, CRYPTO_PROVIDER}; -use crate::{sha_hash::ShaAlgorithm, subtle::CryptoKey}; +use crate::{hash::HashAlgorithm, subtle::CryptoKey}; use super::{ algorithm_not_supported_error, @@ -120,7 +120,7 @@ fn generate_symmetric_key(_ctx: &Ctx<'_>, length: usize) -> Result> { } #[allow(dead_code)] -pub fn get_hash_length(ctx: &Ctx, hash: &ShaAlgorithm, length: u16) -> Result { +pub fn get_hash_length(ctx: &Ctx, hash: &HashAlgorithm, length: u16) -> Result { if length == 0 { return Ok(hash.block_len()); } diff --git a/modules/llrt_crypto/src/subtle/key_algorithm.rs b/modules/llrt_crypto/src/subtle/key_algorithm.rs index 212254d7e3..1bffb8b802 100644 --- a/modules/llrt_crypto/src/subtle/key_algorithm.rs +++ b/modules/llrt_crypto/src/subtle/key_algorithm.rs @@ -17,7 +17,7 @@ use rquickjs::{ #[cfg(feature = "_subtle-full")] use spki::{AlgorithmIdentifier, ObjectIdentifier}; -use crate::sha_hash::ShaAlgorithm; +use crate::hash::HashAlgorithm; #[cfg(feature = "_subtle-full")] use super::algorithm_mismatch_error; @@ -176,12 +176,12 @@ impl KeyUsageAlgorithm { #[derive(Debug, Clone)] pub enum KeyDerivation { Hkdf { - hash: ShaAlgorithm, + hash: HashAlgorithm, salt: Box<[u8]>, info: Box<[u8]>, }, Pbkdf2 { - hash: ShaAlgorithm, + hash: HashAlgorithm, salt: Box<[u8]>, iterations: u32, }, @@ -239,13 +239,13 @@ pub enum KeyAlgorithm { X25519, Ed25519, Hmac { - hash: ShaAlgorithm, + hash: HashAlgorithm, length: u16, }, Rsa { modulus_length: u32, public_exponent: Rc>, - hash: ShaAlgorithm, + hash: HashAlgorithm, }, Derive(KeyDerivation), HkdfImport, @@ -737,7 +737,7 @@ fn import_rsa_key<'js>( kind: &mut KeyKind, data: &mut Vec, algorithm_name: &str, - hash: &ShaAlgorithm, + hash: &HashAlgorithm, ) -> Result<(u32, Box<[u8]>)> { use crate::{ provider::{CryptoProvider, RsaJwkImport}, @@ -878,7 +878,7 @@ fn import_symmetric_key<'js>( kind: &mut KeyKind, data: &mut Vec, algorithm_name: &str, - hash: Option<&ShaAlgorithm>, + hash: Option<&HashAlgorithm>, ) -> Result { *kind = KeyKind::Secret; @@ -1120,7 +1120,7 @@ fn import_okp_key<'js>( Ok(()) } -pub fn extract_sha_hash<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> Result { +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() @@ -1132,17 +1132,17 @@ pub fn extract_sha_hash<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> Result(ctx: &Ctx<'js>, hash: &ShaAlgorithm) -> Result> { +fn create_hash_object<'js>(ctx: &Ctx<'js>, hash: &HashAlgorithm) -> Result> { let hash_obj = Object::new(ctx.clone())?; hash_obj.set(PredefinedAtom::Name, hash.as_str())?; Ok(hash_obj) } #[cfg(feature = "_subtle-full")] -pub fn hash_mismatch_error(ctx: &Ctx<'_>, hash: &ShaAlgorithm) -> Result { +pub fn hash_mismatch_error(ctx: &Ctx<'_>, hash: &HashAlgorithm) -> Result { Err(Exception::throw_message( ctx, &["Algorithm hash expected to be ", hash.as_str()].concat(), diff --git a/modules/llrt_crypto/src/subtle/mod.rs b/modules/llrt_crypto/src/subtle/mod.rs index ecaef81103..c04514bf9c 100644 --- a/modules/llrt_crypto/src/subtle/mod.rs +++ b/modules/llrt_crypto/src/subtle/mod.rs @@ -50,7 +50,7 @@ use rquickjs::{atom::PredefinedAtom, Ctx, Exception, Object, Result, Value}; use crate::provider::{CryptoProvider, SimpleDigest}; -use crate::sha_hash::ShaAlgorithm; +use crate::hash::HashAlgorithm; #[rquickjs::class] #[derive(rquickjs::JsLifetime, rquickjs::class::Trace)] @@ -69,12 +69,6 @@ impl SubtleCrypto { } } -// AES variant types - only available when _rustcrypto feature is enabled -#[cfg(feature = "_rustcrypto")] -mod aes_variants; -#[cfg(feature = "_rustcrypto")] -pub use aes_variants::*; - #[derive(Debug, Clone, Copy, PartialEq)] pub enum EllipticCurve { P256, @@ -95,14 +89,14 @@ pub fn rsa_hash_digest<'a>( key: &'a CryptoKey, data: &'a [u8], algorithm_name: &str, -) -> Result<(&'a ShaAlgorithm, Vec)> { +) -> Result<(&'a HashAlgorithm, Vec)> { let hash = match &key.algorithm { KeyAlgorithm::Rsa { hash, .. } => hash, _ => return algorithm_mismatch_error(ctx, algorithm_name), }; if !matches!( hash, - ShaAlgorithm::SHA256 | ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 + HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::Sha512 ) { return Err(Exception::throw_message( ctx, diff --git a/modules/llrt_crypto/src/subtle/sign.rs b/modules/llrt_crypto/src/subtle/sign.rs index c89cec8dcd..ed4541fcbd 100644 --- a/modules/llrt_crypto/src/subtle/sign.rs +++ b/modules/llrt_crypto/src/subtle/sign.rs @@ -87,7 +87,7 @@ fn sign( // sign_fn: F, // ) -> Result> // where -// F: FnOnce(&ShaAlgorithm, &[u8], &rsa::RsaPrivateKey) -> Result>, +// F: FnOnce(&HashAlgorithm, &[u8], &rsa::RsaPrivateKey) -> Result>, // { // let (hash, digest) = rsa_hash_digest(ctx, key, data, algorithm_name)?; diff --git a/modules/llrt_crypto/src/subtle/sign_algorithm.rs b/modules/llrt_crypto/src/subtle/sign_algorithm.rs index ef3d292aee..20fd13ff97 100644 --- a/modules/llrt_crypto/src/subtle/sign_algorithm.rs +++ b/modules/llrt_crypto/src/subtle/sign_algorithm.rs @@ -3,7 +3,7 @@ use llrt_utils::{object::ObjectExt, result::ResultExt}; use rquickjs::{Ctx, FromJs, Result, Value}; -use crate::sha_hash::ShaAlgorithm; +use crate::hash::HashAlgorithm; use super::{ algorithm_not_supported_error, key_algorithm::extract_sha_hash, to_name_and_maybe_object, @@ -11,7 +11,7 @@ use super::{ #[derive(Debug)] pub enum SigningAlgorithm { - Ecdsa { hash: ShaAlgorithm }, + Ecdsa { hash: HashAlgorithm }, Ed25519, RsaPss { salt_length: u32 }, RsassaPkcs1v15, From 7f1514c8c9d85dcc3da4ddb08f1dfaf68cc1b9a7 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 3 Feb 2026 08:30:00 +0000 Subject: [PATCH 70/77] Revert "Refactor crypto module based on Sytten's review comments" This reverts commit b674e0ec7eaa1d12cb23dbf5ef735d741f381423. --- modules/llrt_crypto/src/hash.rs | 178 ---------------- modules/llrt_crypto/src/lib.rs | 18 +- modules/llrt_crypto/src/md5_hash.rs | 48 +++++ modules/llrt_crypto/src/provider/graviola.rs | 48 ++--- modules/llrt_crypto/src/provider/mod.rs | 80 +++---- modules/llrt_crypto/src/provider/openssl.rs | 68 +++--- modules/llrt_crypto/src/provider/ring.rs | 44 ++-- .../src/provider/{rust/mod.rs => rust.rs} | 106 +++++----- modules/llrt_crypto/src/sha_hash.rs | 197 ++++++++++++++++++ .../{provider/rust => subtle}/aes_variants.rs | 0 modules/llrt_crypto/src/subtle/digest.rs | 10 +- .../llrt_crypto/src/subtle/generate_key.rs | 4 +- .../llrt_crypto/src/subtle/key_algorithm.rs | 22 +- modules/llrt_crypto/src/subtle/mod.rs | 12 +- modules/llrt_crypto/src/subtle/sign.rs | 2 +- .../llrt_crypto/src/subtle/sign_algorithm.rs | 4 +- 16 files changed, 462 insertions(+), 379 deletions(-) delete mode 100644 modules/llrt_crypto/src/hash.rs create mode 100644 modules/llrt_crypto/src/md5_hash.rs rename modules/llrt_crypto/src/provider/{rust/mod.rs => rust.rs} (95%) create mode 100644 modules/llrt_crypto/src/sha_hash.rs rename modules/llrt_crypto/src/{provider/rust => subtle}/aes_variants.rs (100%) diff --git a/modules/llrt_crypto/src/hash.rs b/modules/llrt_crypto/src/hash.rs deleted file mode 100644 index fc82e85f92..0000000000 --- a/modules/llrt_crypto/src/hash.rs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -use llrt_buffer::Buffer; -use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; -use rquickjs::{class::Trace, function::Opt, prelude::This, Class, Ctx, IntoJs, JsLifetime, Result, Value}; - -use super::encoded_bytes; -use crate::provider::{CryptoProvider, HmacProvider, SimpleDigest}; -use crate::CRYPTO_PROVIDER; - -#[derive(Debug, Clone, Copy)] -pub enum HashAlgorithm { - Md5, - Sha1, - Sha256, - Sha384, - Sha512, -} - -impl TryFrom<&str> for HashAlgorithm { - type Error = String; - fn try_from(s: &str) -> std::result::Result { - Ok(match s.to_ascii_uppercase().as_str() { - "MD5" => HashAlgorithm::Md5, - "MD-5" => HashAlgorithm::Md5, - "SHA1" => HashAlgorithm::Sha1, - "SHA-1" => HashAlgorithm::Sha1, - "SHA256" => HashAlgorithm::Sha256, - "SHA-256" => HashAlgorithm::Sha256, - "SHA384" => HashAlgorithm::Sha384, - "SHA-384" => HashAlgorithm::Sha384, - "SHA512" => HashAlgorithm::Sha512, - "SHA-512" => HashAlgorithm::Sha512, - _ => return Err(["'", s, "' not available"].concat()), - }) - } -} - -impl HashAlgorithm { - pub fn as_str(&self) -> &'static str { - match self { - HashAlgorithm::Md5 => "MD5", - HashAlgorithm::Sha1 => "SHA-1", - HashAlgorithm::Sha256 => "SHA-256", - HashAlgorithm::Sha384 => "SHA-384", - HashAlgorithm::Sha512 => "SHA-512", - } - } - - pub fn as_numeric_str(&self) -> &'static str { - match self { - HashAlgorithm::Md5 => "md5", - HashAlgorithm::Sha1 => "1", - HashAlgorithm::Sha256 => "256", - HashAlgorithm::Sha384 => "384", - HashAlgorithm::Sha512 => "512", - } - } - - pub fn digest_len(&self) -> usize { - match self { - HashAlgorithm::Md5 => 16, - HashAlgorithm::Sha1 => 20, - HashAlgorithm::Sha256 => 32, - HashAlgorithm::Sha384 => 48, - HashAlgorithm::Sha512 => 64, - } - } - - pub fn block_len(&self) -> usize { - match self { - HashAlgorithm::Md5 => 64, - HashAlgorithm::Sha1 => 64, - HashAlgorithm::Sha256 => 64, - HashAlgorithm::Sha384 => 128, - HashAlgorithm::Sha512 => 128, - } - } -} - -type ProviderDigest = ::Digest; -type ProviderHmac = ::Hmac; - -#[derive(Trace, JsLifetime)] -#[rquickjs::class] -pub struct Hash { - #[qjs(skip_trace)] - inner: Option, -} - -impl Hash { - pub fn new(ctx: Ctx<'_>, algorithm: String) -> Result { - let algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; - Ok(Self { - inner: Some(CRYPTO_PROVIDER.digest(algorithm)), - }) - } -} - -#[rquickjs::methods] -impl Hash { - #[qjs(rename = "digest")] - fn hash_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let digest = self - .inner - .take() - .ok_or_else(|| rquickjs::Exception::throw_message(&ctx, "Digest already called"))?; - let result = digest.finalize(); - let bytes: &[u8] = result.as_ref(); - - match encoding.0 { - Some(encoding) => encoded_bytes(ctx, bytes, &encoding), - None => Buffer(bytes.to_vec()).into_js(&ctx), - } - } - - #[qjs(rename = "update")] - fn hash_update<'js>( - this: This>, - ctx: Ctx<'js>, - bytes: ObjectBytes<'js>, - ) -> Result> { - let bytes = bytes.as_bytes(&ctx)?; - let mut borrowed = this.0.borrow_mut(); - if let Some(ref mut digest) = borrowed.inner { - digest.update(bytes); - } - drop(borrowed); - Ok(this.0) - } -} - -#[derive(Trace, JsLifetime)] -#[rquickjs::class] -pub struct Hmac { - #[qjs(skip_trace)] - inner: Option, -} - -impl Hmac { - pub fn new<'js>(ctx: Ctx<'js>, algorithm: String, key_value: ObjectBytes<'js>) -> Result { - let algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; - let key = key_value.as_bytes(&ctx)?; - let hmac = CRYPTO_PROVIDER.hmac(algorithm, key); - Ok(Self { inner: Some(hmac) }) - } -} - -#[rquickjs::methods] -impl Hmac { - fn digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let hmac = self - .inner - .take() - .ok_or_else(|| rquickjs::Exception::throw_message(&ctx, "Digest already called"))?; - let result = hmac.finalize(); - let bytes: &[u8] = result.as_ref(); - - match encoding.into_inner() { - Some(encoding) => encoded_bytes(ctx, bytes, &encoding), - None => Buffer(bytes.to_vec()).into_js(&ctx), - } - } - - fn update<'js>( - this: This>, - ctx: Ctx<'js>, - bytes: ObjectBytes<'js>, - ) -> Result> { - let bytes = bytes.as_bytes(&ctx)?; - let mut borrowed = this.0.borrow_mut(); - if let Some(ref mut hmac) = borrowed.inner { - hmac.update(bytes); - } - drop(borrowed); - Ok(this.0) - } -} diff --git a/modules/llrt_crypto/src/lib.rs b/modules/llrt_crypto/src/lib.rs index de91661db6..433a5bed30 100644 --- a/modules/llrt_crypto/src/lib.rs +++ b/modules/llrt_crypto/src/lib.rs @@ -21,7 +21,8 @@ compile_error!("Features `crypto-openssl` and `crypto-graviola` are mutually exc compile_error!("Features `crypto-ring` and `crypto-graviola` are mutually exclusive"); mod crc32; -mod hash; +mod md5_hash; +mod sha_hash; mod subtle; mod provider; @@ -56,7 +57,8 @@ use subtle::{ use self::{ crc32::{Crc32, Crc32c}, - hash::{Hash, Hmac}, + md5_hash::Md5, + sha_hash::{Hash, Hmac, ShaAlgorithm}, }; static CRYPTO_PROVIDER: Lazy = @@ -290,12 +292,18 @@ impl ModuleDef for CryptoModule { declare.declare("createHmac")?; declare.declare("Crc32")?; declare.declare("Crc32c")?; + declare.declare("Md5")?; declare.declare("randomBytes")?; declare.declare("randomUUID")?; declare.declare("randomInt")?; declare.declare("randomFillSync")?; declare.declare("randomFill")?; declare.declare("getRandomValues")?; + + for sha_algorithm in ShaAlgorithm::iter() { + let class_name = sha_algorithm.class_name(); + declare.declare(class_name)?; + } declare.declare("crypto")?; declare.declare("webcrypto")?; declare.declare("default")?; @@ -305,8 +313,14 @@ impl ModuleDef for CryptoModule { fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { + for sha_algorithm in ShaAlgorithm::iter() { + let _class_name: &str = sha_algorithm.class_name(); + // ShaHash class removed - using Hash and Hmac instead + } + let crypto: Object = ctx.globals().get("crypto")?; + Class::::define(default)?; Class::::define(default)?; Class::::define(default)?; diff --git a/modules/llrt_crypto/src/md5_hash.rs b/modules/llrt_crypto/src/md5_hash.rs new file mode 100644 index 0000000000..0538bd1e0c --- /dev/null +++ b/modules/llrt_crypto/src/md5_hash.rs @@ -0,0 +1,48 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use llrt_utils::bytes::{bytes_to_typed_array, ObjectBytes}; +use rquickjs::{function::Opt, prelude::This, Class, Ctx, Result, Value}; + +use super::{encoded_bytes, CRYPTO_PROVIDER}; +use crate::{ + provider::{CryptoProvider, SimpleDigest}, + sha_hash::ShaAlgorithm, +}; + +#[rquickjs::class] +#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] +pub struct Md5 { + #[qjs(skip_trace)] + hasher: ::Digest, +} + +#[rquickjs::methods] +impl Md5 { + #[qjs(constructor)] + fn new() -> Self { + Self { + hasher: CRYPTO_PROVIDER.digest(ShaAlgorithm::MD5), + } + } + + #[qjs(rename = "digest")] + fn md5_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { + let digest = std::mem::replace(&mut self.hasher, CRYPTO_PROVIDER.digest(ShaAlgorithm::MD5)) + .finalize(); + + match encoding.0 { + Some(encoding) => encoded_bytes(ctx, &digest, &encoding), + None => bytes_to_typed_array(ctx, &digest), + } + } + + #[qjs(rename = "update")] + fn md5_update<'js>( + this: This>, + ctx: Ctx<'js>, + bytes: ObjectBytes<'js>, + ) -> Result> { + this.0.borrow_mut().hasher.update(bytes.as_bytes(&ctx)?); + Ok(this.0) + } +} diff --git a/modules/llrt_crypto/src/provider/graviola.rs b/modules/llrt_crypto/src/provider/graviola.rs index 59a2e4182a..2349748f0e 100644 --- a/modules/llrt_crypto/src/provider/graviola.rs +++ b/modules/llrt_crypto/src/provider/graviola.rs @@ -12,7 +12,7 @@ use graviola::{ }; use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; -use crate::hash::HashAlgorithm; +use crate::sha_hash::ShaAlgorithm; use crate::subtle::EllipticCurve; pub struct GraviolaProvider; @@ -69,20 +69,20 @@ impl CryptoProvider for GraviolaProvider { type Digest = GraviolaDigest; type Hmac = GraviolaHmac; - fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { + fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { match algorithm { - HashAlgorithm::Sha256 => GraviolaDigest::Sha256(Sha256::new()), - HashAlgorithm::Sha384 => GraviolaDigest::Sha384(Sha384::new()), - HashAlgorithm::Sha512 => GraviolaDigest::Sha512(Sha512::new()), + ShaAlgorithm::SHA256 => GraviolaDigest::Sha256(Sha256::new()), + ShaAlgorithm::SHA384 => GraviolaDigest::Sha384(Sha384::new()), + ShaAlgorithm::SHA512 => GraviolaDigest::Sha512(Sha512::new()), _ => panic!("Unsupported digest algorithm for Graviola"), } } - fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { match algorithm { - HashAlgorithm::Sha256 => GraviolaHmac::Sha256(Hmac::::new(key)), - HashAlgorithm::Sha384 => GraviolaHmac::Sha384(Hmac::::new(key)), - HashAlgorithm::Sha512 => GraviolaHmac::Sha512(Hmac::::new(key)), + ShaAlgorithm::SHA256 => GraviolaHmac::Sha256(Hmac::::new(key)), + ShaAlgorithm::SHA384 => GraviolaHmac::Sha384(Hmac::::new(key)), + ShaAlgorithm::SHA512 => GraviolaHmac::Sha512(Hmac::::new(key)), _ => panic!("Unsupported HMAC algorithm for Graviola"), } } @@ -124,7 +124,7 @@ impl CryptoProvider for GraviolaProvider { _private_key_der: &[u8], _digest: &[u8], _salt_length: usize, - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -135,7 +135,7 @@ impl CryptoProvider for GraviolaProvider { _signature: &[u8], _digest: &[u8], _salt_length: usize, - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } @@ -144,7 +144,7 @@ impl CryptoProvider for GraviolaProvider { &self, _private_key_der: &[u8], _digest: &[u8], - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -154,7 +154,7 @@ impl CryptoProvider for GraviolaProvider { _public_key_der: &[u8], _signature: &[u8], _digest: &[u8], - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } @@ -163,7 +163,7 @@ impl CryptoProvider for GraviolaProvider { &self, _public_key_der: &[u8], _data: &[u8], - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) @@ -173,7 +173,7 @@ impl CryptoProvider for GraviolaProvider { &self, _private_key_der: &[u8], _data: &[u8], - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) @@ -267,7 +267,7 @@ impl CryptoProvider for GraviolaProvider { _salt: &[u8], _info: &[u8], _length: usize, - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -278,7 +278,7 @@ impl CryptoProvider for GraviolaProvider { _salt: &[u8], _iterations: u32, _length: usize, - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -292,13 +292,13 @@ impl CryptoProvider for GraviolaProvider { fn generate_hmac_key( &self, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, length_bits: u16, ) -> Result, CryptoError> { let length_bytes = if length_bits == 0 { match hash_alg { - HashAlgorithm::Sha256 => 64, - HashAlgorithm::Sha384 | HashAlgorithm::Sha512 => 128, + ShaAlgorithm::SHA256 => 64, + ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 => 128, _ => return Err(CryptoError::UnsupportedAlgorithm), } } else { @@ -501,9 +501,9 @@ pub enum GraviolaRustDigest { #[cfg(feature = "crypto-graviola-rust")] impl GraviolaRustDigest { - pub fn new(algorithm: HashAlgorithm) -> Self { + pub fn new(algorithm: ShaAlgorithm) -> Self { match algorithm { - HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::Sha512 => { + ShaAlgorithm::SHA256 | ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 => { Self::Graviola(GraviolaProvider.digest(algorithm)) }, _ => Self::Rust(super::rust::RustCryptoProvider.digest(algorithm)), @@ -535,9 +535,9 @@ pub enum GraviolaRustHmac { #[cfg(feature = "crypto-graviola-rust")] impl GraviolaRustHmac { - pub fn new(algorithm: HashAlgorithm, key: &[u8]) -> Self { + pub fn new(algorithm: ShaAlgorithm, key: &[u8]) -> Self { match algorithm { - HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::Sha512 => { + ShaAlgorithm::SHA256 | ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 => { Self::Graviola(GraviolaProvider.hmac(algorithm, key)) }, _ => Self::Rust(super::rust::RustCryptoProvider.hmac(algorithm, key)), diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index ac6895056a..b36cfd4e37 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -35,7 +35,7 @@ mod ring; #[cfg(feature = "_rustcrypto")] mod rust; -use crate::hash::HashAlgorithm; +use crate::sha_hash::ShaAlgorithm; use crate::subtle::EllipticCurve; #[derive(Debug)] @@ -144,10 +144,10 @@ pub trait CryptoProvider { type Hmac: HmacProvider; // Digest operations - fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest; + fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest; // HMAC operations - fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac; + fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac; // ECDSA operations fn ecdsa_sign( @@ -179,7 +179,7 @@ pub trait CryptoProvider { private_key_der: &[u8], digest: &[u8], salt_length: usize, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result, CryptoError>; fn rsa_pss_verify( &self, @@ -187,33 +187,33 @@ pub trait CryptoProvider { signature: &[u8], digest: &[u8], salt_length: usize, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result; fn rsa_pkcs1v15_sign( &self, private_key_der: &[u8], digest: &[u8], - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result, CryptoError>; fn rsa_pkcs1v15_verify( &self, public_key_der: &[u8], signature: &[u8], digest: &[u8], - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result; fn rsa_oaep_encrypt( &self, public_key_der: &[u8], data: &[u8], - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError>; fn rsa_oaep_decrypt( &self, private_key_der: &[u8], data: &[u8], - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError>; @@ -261,7 +261,7 @@ pub trait CryptoProvider { salt: &[u8], info: &[u8], length: usize, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result, CryptoError>; fn pbkdf2_derive_key( &self, @@ -269,13 +269,13 @@ pub trait CryptoProvider { salt: &[u8], iterations: u32, length: usize, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result, CryptoError>; fn generate_aes_key(&self, length_bits: u16) -> Result, CryptoError>; fn generate_hmac_key( &self, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, length_bits: u16, ) -> Result, CryptoError>; fn generate_ec_key(&self, curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError>; // (private, public) @@ -470,10 +470,10 @@ macro_rules! impl_hybrid_provider { impl CryptoProvider for $name { type Digest = $digest; type Hmac = $hmac; - fn digest(&self, alg: HashAlgorithm) -> Self::Digest { + fn digest(&self, alg: ShaAlgorithm) -> Self::Digest { $digest_fn(alg) } - fn hmac(&self, alg: HashAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, alg: ShaAlgorithm, key: &[u8]) -> Self::Hmac { $hmac_fn(alg, key) } fn ecdsa_sign( @@ -504,7 +504,7 @@ macro_rules! impl_hybrid_provider { k: &[u8], d: &[u8], s: usize, - a: HashAlgorithm, + a: ShaAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_pss_sign(k, d, s, a) } @@ -514,7 +514,7 @@ macro_rules! impl_hybrid_provider { s: &[u8], d: &[u8], sl: usize, - a: HashAlgorithm, + a: ShaAlgorithm, ) -> Result { rust::RustCryptoProvider.rsa_pss_verify(k, s, d, sl, a) } @@ -522,7 +522,7 @@ macro_rules! impl_hybrid_provider { &self, k: &[u8], d: &[u8], - a: HashAlgorithm, + a: ShaAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_pkcs1v15_sign(k, d, a) } @@ -531,7 +531,7 @@ macro_rules! impl_hybrid_provider { k: &[u8], s: &[u8], d: &[u8], - a: HashAlgorithm, + a: ShaAlgorithm, ) -> Result { rust::RustCryptoProvider.rsa_pkcs1v15_verify(k, s, d, a) } @@ -539,7 +539,7 @@ macro_rules! impl_hybrid_provider { &self, k: &[u8], d: &[u8], - a: HashAlgorithm, + a: ShaAlgorithm, l: Option<&[u8]>, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_oaep_encrypt(k, d, a, l) @@ -548,7 +548,7 @@ macro_rules! impl_hybrid_provider { &self, k: &[u8], d: &[u8], - a: HashAlgorithm, + a: ShaAlgorithm, l: Option<&[u8]>, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_oaep_decrypt(k, d, a, l) @@ -596,7 +596,7 @@ macro_rules! impl_hybrid_provider { s: &[u8], i: &[u8], l: usize, - a: HashAlgorithm, + a: ShaAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.hkdf_derive_key(k, s, i, l, a) } @@ -606,14 +606,14 @@ macro_rules! impl_hybrid_provider { s: &[u8], i: u32, l: usize, - a: HashAlgorithm, + a: ShaAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.pbkdf2_derive_key(p, s, i, l, a) } fn generate_aes_key(&self, b: u16) -> Result, CryptoError> { rust::RustCryptoProvider.generate_aes_key(b) } - fn generate_hmac_key(&self, a: HashAlgorithm, b: u16) -> Result, CryptoError> { + fn generate_hmac_key(&self, a: ShaAlgorithm, b: u16) -> Result, CryptoError> { rust::RustCryptoProvider.generate_hmac_key(a, b) } fn generate_ec_key(&self, c: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { @@ -861,7 +861,7 @@ mod tests { #[test] fn test_sha256_digest() { let p = provider(); - let mut digest = p.digest(HashAlgorithm::Sha256); + let mut digest = p.digest(ShaAlgorithm::SHA256); digest.update(b"hello world"); let result = digest.finalize(); assert_eq!(result.len(), 32); @@ -874,7 +874,7 @@ mod tests { #[test] fn test_sha384_digest() { let p = provider(); - let mut digest = p.digest(HashAlgorithm::Sha384); + let mut digest = p.digest(ShaAlgorithm::SHA384); digest.update(b"hello world"); let result = digest.finalize(); assert_eq!(result.len(), 48); @@ -883,7 +883,7 @@ mod tests { #[test] fn test_sha512_digest() { let p = provider(); - let mut digest = p.digest(HashAlgorithm::Sha512); + let mut digest = p.digest(ShaAlgorithm::SHA512); digest.update(b"hello world"); let result = digest.finalize(); assert_eq!(result.len(), 64); @@ -894,7 +894,7 @@ mod tests { fn test_hmac_sha256() { let p = provider(); let key = b"secret key"; - let mut hmac = p.hmac(HashAlgorithm::Sha256, key); + let mut hmac = p.hmac(ShaAlgorithm::SHA256, key); hmac.update(b"hello world"); let result = hmac.finalize(); assert_eq!(result.len(), 32); @@ -1035,7 +1035,7 @@ mod tests { #[test] fn test_generate_hmac_key() { let p = provider(); - let key = p.generate_hmac_key(HashAlgorithm::Sha256, 256).unwrap(); + let key = p.generate_hmac_key(ShaAlgorithm::SHA256, 256).unwrap(); assert_eq!(key.len(), 32); } @@ -1117,7 +1117,7 @@ mod tests { let info = b"info"; let derived = p - .hkdf_derive_key(ikm, salt, info, 32, HashAlgorithm::Sha256) + .hkdf_derive_key(ikm, salt, info, 32, ShaAlgorithm::SHA256) .unwrap(); assert_eq!(derived.len(), 32); @@ -1130,7 +1130,7 @@ mod tests { let salt = b"salt"; let derived = p - .pbkdf2_derive_key(password, salt, 1000, 32, HashAlgorithm::Sha256) + .pbkdf2_derive_key(password, salt, 1000, 32, ShaAlgorithm::SHA256) .unwrap(); assert_eq!(derived.len(), 32); @@ -1142,7 +1142,7 @@ mod tests { let (private_key, public_key) = p.generate_ec_key(EllipticCurve::P256).unwrap(); // Create a digest to sign - let mut digest = p.digest(HashAlgorithm::Sha256); + let mut digest = p.digest(ShaAlgorithm::SHA256); digest.update(b"message to sign"); let hash = digest.finalize(); @@ -1162,7 +1162,7 @@ mod tests { let p = provider(); let (private_key, public_key) = p.generate_ec_key(EllipticCurve::P384).unwrap(); - let mut digest = p.digest(HashAlgorithm::Sha384); + let mut digest = p.digest(ShaAlgorithm::SHA384); digest.update(b"message to sign"); let hash = digest.finalize(); @@ -1224,16 +1224,16 @@ mod tests { let p = provider(); let (private_key, public_key) = p.generate_rsa_key(2048, &[1, 0, 1]).unwrap(); - let mut digest = p.digest(HashAlgorithm::Sha256); + let mut digest = p.digest(ShaAlgorithm::SHA256); digest.update(b"message to sign"); let hash = digest.finalize(); let signature = p - .rsa_pss_sign(&private_key, &hash, 32, HashAlgorithm::Sha256) + .rsa_pss_sign(&private_key, &hash, 32, ShaAlgorithm::SHA256) .unwrap(); let valid = p - .rsa_pss_verify(&public_key, &signature, &hash, 32, HashAlgorithm::Sha256) + .rsa_pss_verify(&public_key, &signature, &hash, 32, ShaAlgorithm::SHA256) .unwrap(); assert!(valid); @@ -1244,16 +1244,16 @@ mod tests { let p = provider(); let (private_key, public_key) = p.generate_rsa_key(2048, &[1, 0, 1]).unwrap(); - let mut digest = p.digest(HashAlgorithm::Sha256); + let mut digest = p.digest(ShaAlgorithm::SHA256); digest.update(b"message to sign"); let hash = digest.finalize(); let signature = p - .rsa_pkcs1v15_sign(&private_key, &hash, HashAlgorithm::Sha256) + .rsa_pkcs1v15_sign(&private_key, &hash, ShaAlgorithm::SHA256) .unwrap(); let valid = p - .rsa_pkcs1v15_verify(&public_key, &signature, &hash, HashAlgorithm::Sha256) + .rsa_pkcs1v15_verify(&public_key, &signature, &hash, ShaAlgorithm::SHA256) .unwrap(); assert!(valid); @@ -1267,11 +1267,11 @@ mod tests { let plaintext = b"secret message"; let ciphertext = p - .rsa_oaep_encrypt(&public_key, plaintext, HashAlgorithm::Sha256, None) + .rsa_oaep_encrypt(&public_key, plaintext, ShaAlgorithm::SHA256, None) .unwrap(); let decrypted = p - .rsa_oaep_decrypt(&private_key, &ciphertext, HashAlgorithm::Sha256, None) + .rsa_oaep_decrypt(&private_key, &ciphertext, ShaAlgorithm::SHA256, None) .unwrap(); assert_eq!(decrypted, plaintext); diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs index 7386aec240..0f759e283a 100644 --- a/modules/llrt_crypto/src/provider/openssl.rs +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -18,7 +18,7 @@ use openssl::sign::{Signer, Verifier}; use openssl::symm::{self, Cipher}; use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; -use crate::hash::HashAlgorithm; +use crate::sha_hash::ShaAlgorithm; use crate::subtle::EllipticCurve; pub struct OpenSslProvider; @@ -71,23 +71,23 @@ impl HmacProvider for OpenSslHmac { } } -fn get_message_digest(alg: HashAlgorithm) -> MessageDigest { +fn get_message_digest(alg: ShaAlgorithm) -> MessageDigest { match alg { - HashAlgorithm::Md5 => MessageDigest::md5(), - HashAlgorithm::Sha1 => MessageDigest::sha1(), - HashAlgorithm::Sha256 => MessageDigest::sha256(), - HashAlgorithm::Sha384 => MessageDigest::sha384(), - HashAlgorithm::Sha512 => MessageDigest::sha512(), + ShaAlgorithm::MD5 => MessageDigest::md5(), + ShaAlgorithm::SHA1 => MessageDigest::sha1(), + ShaAlgorithm::SHA256 => MessageDigest::sha256(), + ShaAlgorithm::SHA384 => MessageDigest::sha384(), + ShaAlgorithm::SHA512 => MessageDigest::sha512(), } } -fn get_md(alg: HashAlgorithm) -> &'static openssl::md::MdRef { +fn get_md(alg: ShaAlgorithm) -> &'static openssl::md::MdRef { match alg { - HashAlgorithm::Md5 => Md::md5(), - HashAlgorithm::Sha1 => Md::sha1(), - HashAlgorithm::Sha256 => Md::sha256(), - HashAlgorithm::Sha384 => Md::sha384(), - HashAlgorithm::Sha512 => Md::sha512(), + ShaAlgorithm::MD5 => Md::md5(), + ShaAlgorithm::SHA1 => Md::sha1(), + ShaAlgorithm::SHA256 => Md::sha256(), + ShaAlgorithm::SHA384 => Md::sha384(), + ShaAlgorithm::SHA512 => Md::sha512(), } } @@ -109,19 +109,19 @@ impl CryptoProvider for OpenSslProvider { type Digest = OpenSslDigest; type Hmac = OpenSslHmac; - fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { + fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { let md = get_message_digest(algorithm); let hasher = Hasher::new(md).expect("Failed to create hasher"); match algorithm { - HashAlgorithm::Md5 => OpenSslDigest::Md5(hasher), - HashAlgorithm::Sha1 => OpenSslDigest::Sha1(hasher), - HashAlgorithm::Sha256 => OpenSslDigest::Sha256(hasher), - HashAlgorithm::Sha384 => OpenSslDigest::Sha384(hasher), - HashAlgorithm::Sha512 => OpenSslDigest::Sha512(hasher), + ShaAlgorithm::MD5 => OpenSslDigest::Md5(hasher), + ShaAlgorithm::SHA1 => OpenSslDigest::Sha1(hasher), + ShaAlgorithm::SHA256 => OpenSslDigest::Sha256(hasher), + ShaAlgorithm::SHA384 => OpenSslDigest::Sha384(hasher), + ShaAlgorithm::SHA512 => OpenSslDigest::Sha512(hasher), } } - fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { let md = get_message_digest(algorithm); let pkey = PKey::hmac(key).expect("Failed to create HMAC key"); let signer = unsafe { @@ -208,7 +208,7 @@ impl CryptoProvider for OpenSslProvider { private_key_der: &[u8], digest: &[u8], salt_length: usize, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { let rsa = Rsa::private_key_from_der(private_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; @@ -240,7 +240,7 @@ impl CryptoProvider for OpenSslProvider { signature: &[u8], digest: &[u8], salt_length: usize, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result { let rsa = Rsa::public_key_from_der(public_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; @@ -268,7 +268,7 @@ impl CryptoProvider for OpenSslProvider { &self, private_key_der: &[u8], digest: &[u8], - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { let rsa = Rsa::private_key_from_der(private_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; @@ -293,7 +293,7 @@ impl CryptoProvider for OpenSslProvider { public_key_der: &[u8], signature: &[u8], digest: &[u8], - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result { let rsa = Rsa::public_key_from_der(public_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; @@ -315,7 +315,7 @@ impl CryptoProvider for OpenSslProvider { &self, public_key_der: &[u8], data: &[u8], - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let rsa = Rsa::public_key_from_der(public_key_der) @@ -348,7 +348,7 @@ impl CryptoProvider for OpenSslProvider { &self, private_key_der: &[u8], data: &[u8], - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let rsa = Rsa::private_key_from_der(private_key_der) @@ -586,7 +586,7 @@ impl CryptoProvider for OpenSslProvider { salt: &[u8], info: &[u8], length: usize, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { use openssl::pkey_ctx::HkdfMode; let md = get_md(hash_alg); @@ -620,7 +620,7 @@ impl CryptoProvider for OpenSslProvider { salt: &[u8], iterations: u32, length: usize, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { let md = get_message_digest(hash_alg); let mut out = vec![0u8; length]; @@ -639,16 +639,16 @@ impl CryptoProvider for OpenSslProvider { fn generate_hmac_key( &self, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, length_bits: u16, ) -> Result, CryptoError> { let length_bytes = if length_bits == 0 { match hash_alg { - HashAlgorithm::Md5 => 16, - HashAlgorithm::Sha1 => 20, - HashAlgorithm::Sha256 => 32, - HashAlgorithm::Sha384 => 48, - HashAlgorithm::Sha512 => 64, + ShaAlgorithm::MD5 => 16, + ShaAlgorithm::SHA1 => 20, + ShaAlgorithm::SHA256 => 32, + ShaAlgorithm::SHA384 => 48, + ShaAlgorithm::SHA512 => 64, } } else { (length_bits / 8) as usize diff --git a/modules/llrt_crypto/src/provider/ring.rs b/modules/llrt_crypto/src/provider/ring.rs index e5fa9d74fa..fe71da2400 100644 --- a/modules/llrt_crypto/src/provider/ring.rs +++ b/modules/llrt_crypto/src/provider/ring.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; -use crate::hash::HashAlgorithm; +use crate::sha_hash::ShaAlgorithm; use crate::subtle::EllipticCurve; use md5::{Digest, Md5 as Md5Hasher}; use ring::{digest, hmac}; @@ -147,33 +147,33 @@ impl CryptoProvider for RingProvider { type Digest = RingDigestType; type Hmac = RingHmacType; - fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { + fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { match algorithm { - HashAlgorithm::Md5 => RingDigestType::Md5(RingMd5(Md5Hasher::new())), - HashAlgorithm::Sha1 => { + ShaAlgorithm::MD5 => RingDigestType::Md5(RingMd5(Md5Hasher::new())), + ShaAlgorithm::SHA1 => { RingDigestType::Sha1(RingDigest::new(&digest::SHA1_FOR_LEGACY_USE_ONLY)) }, - HashAlgorithm::Sha256 => RingDigestType::Sha256(RingDigest::new(&digest::SHA256)), - HashAlgorithm::Sha384 => RingDigestType::Sha384(RingDigest::new(&digest::SHA384)), - HashAlgorithm::Sha512 => RingDigestType::Sha512(RingDigest::new(&digest::SHA512)), + ShaAlgorithm::SHA256 => RingDigestType::Sha256(RingDigest::new(&digest::SHA256)), + ShaAlgorithm::SHA384 => RingDigestType::Sha384(RingDigest::new(&digest::SHA384)), + ShaAlgorithm::SHA512 => RingDigestType::Sha512(RingDigest::new(&digest::SHA512)), } } - fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { match algorithm { - HashAlgorithm::Md5 => { + ShaAlgorithm::MD5 => { panic!("HMAC-MD5 not supported by Ring provider"); }, - HashAlgorithm::Sha1 => RingHmacType::Sha1(RingHmacSha1(hmac::Context::with_key( + ShaAlgorithm::SHA1 => RingHmacType::Sha1(RingHmacSha1(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, key), ))), - HashAlgorithm::Sha256 => RingHmacType::Sha256(RingHmacSha256(hmac::Context::with_key( + ShaAlgorithm::SHA256 => RingHmacType::Sha256(RingHmacSha256(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA256, key), ))), - HashAlgorithm::Sha384 => RingHmacType::Sha384(RingHmacSha384(hmac::Context::with_key( + ShaAlgorithm::SHA384 => RingHmacType::Sha384(RingHmacSha384(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA384, key), ))), - HashAlgorithm::Sha512 => RingHmacType::Sha512(RingHmacSha512(hmac::Context::with_key( + ShaAlgorithm::SHA512 => RingHmacType::Sha512(RingHmacSha512(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA512, key), ))), } @@ -216,7 +216,7 @@ impl CryptoProvider for RingProvider { _private_key_der: &[u8], _digest: &[u8], _salt_length: usize, - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -227,7 +227,7 @@ impl CryptoProvider for RingProvider { _signature: &[u8], _digest: &[u8], _salt_length: usize, - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } @@ -236,7 +236,7 @@ impl CryptoProvider for RingProvider { &self, _private_key_der: &[u8], _digest: &[u8], - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -246,7 +246,7 @@ impl CryptoProvider for RingProvider { _public_key_der: &[u8], _signature: &[u8], _digest: &[u8], - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } @@ -255,7 +255,7 @@ impl CryptoProvider for RingProvider { &self, _public_key_der: &[u8], _data: &[u8], - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) @@ -265,7 +265,7 @@ impl CryptoProvider for RingProvider { &self, _private_key_der: &[u8], _data: &[u8], - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) @@ -324,7 +324,7 @@ impl CryptoProvider for RingProvider { _salt: &[u8], _info: &[u8], _length: usize, - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -335,7 +335,7 @@ impl CryptoProvider for RingProvider { _salt: &[u8], _iterations: u32, _length: usize, - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -346,7 +346,7 @@ impl CryptoProvider for RingProvider { fn generate_hmac_key( &self, - _hash_alg: HashAlgorithm, + _hash_alg: ShaAlgorithm, _length_bits: u16, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) diff --git a/modules/llrt_crypto/src/provider/rust/mod.rs b/modules/llrt_crypto/src/provider/rust.rs similarity index 95% rename from modules/llrt_crypto/src/provider/rust/mod.rs rename to modules/llrt_crypto/src/provider/rust.rs index 3ef2643433..757bac1c07 100644 --- a/modules/llrt_crypto/src/provider/rust/mod.rs +++ b/modules/llrt_crypto/src/provider/rust.rs @@ -1,8 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -mod aes_variants; - use std::num::NonZeroU32; use aes::cipher::{ @@ -58,12 +56,10 @@ use sha1::Sha1; use crate::{ provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}, random_byte_array, - hash::HashAlgorithm, - subtle::EllipticCurve, + sha_hash::ShaAlgorithm, + subtle::{AesGcmVariant, EllipticCurve}, }; -use aes_variants::AesGcmVariant; - impl From for CryptoError { fn from(_: aes::cipher::InvalidLength) -> Self { CryptoError::InvalidLength @@ -145,27 +141,27 @@ impl CryptoProvider for RustCryptoProvider { type Digest = RustDigest; type Hmac = RustHmac; - fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { + fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { match algorithm { - HashAlgorithm::Md5 => RustDigest::Md5(md5::Md5::new()), - HashAlgorithm::Sha1 => RustDigest::Sha1(Sha1::new()), - HashAlgorithm::Sha256 => RustDigest::Sha256(Sha256::new()), - HashAlgorithm::Sha384 => RustDigest::Sha384(Sha384::new()), - HashAlgorithm::Sha512 => RustDigest::Sha512(Sha512::new()), + ShaAlgorithm::MD5 => RustDigest::Md5(md5::Md5::new()), + ShaAlgorithm::SHA1 => RustDigest::Sha1(Sha1::new()), + ShaAlgorithm::SHA256 => RustDigest::Sha256(Sha256::new()), + ShaAlgorithm::SHA384 => RustDigest::Sha384(Sha384::new()), + ShaAlgorithm::SHA512 => RustDigest::Sha512(Sha512::new()), } } - fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { match algorithm { - HashAlgorithm::Md5 => panic!("HMAC-MD5 not supported"), - HashAlgorithm::Sha1 => RustHmac::Sha1(HmacImpl::::new_from_slice(key).unwrap()), - HashAlgorithm::Sha256 => { + ShaAlgorithm::MD5 => panic!("HMAC-MD5 not supported"), + ShaAlgorithm::SHA1 => RustHmac::Sha1(HmacImpl::::new_from_slice(key).unwrap()), + ShaAlgorithm::SHA256 => { RustHmac::Sha256(HmacImpl::::new_from_slice(key).unwrap()) }, - HashAlgorithm::Sha384 => { + ShaAlgorithm::SHA384 => { RustHmac::Sha384(HmacImpl::::new_from_slice(key).unwrap()) }, - HashAlgorithm::Sha512 => { + ShaAlgorithm::SHA512 => { RustHmac::Sha512(HmacImpl::::new_from_slice(key).unwrap()) }, } @@ -262,20 +258,20 @@ impl CryptoProvider for RustCryptoProvider { private_key_der: &[u8], digest: &[u8], salt_length: usize, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { let mut rng = rand::rng(); let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - HashAlgorithm::Sha256 => private_key + ShaAlgorithm::SHA256 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed(None)), - HashAlgorithm::Sha384 => private_key + ShaAlgorithm::SHA384 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed(None)), - HashAlgorithm::Sha512 => private_key + ShaAlgorithm::SHA512 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed(None)), _ => Err(CryptoError::UnsupportedAlgorithm), @@ -288,19 +284,19 @@ impl CryptoProvider for RustCryptoProvider { signature: &[u8], digest: &[u8], salt_length: usize, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result { let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - HashAlgorithm::Sha256 => Ok(public_key + ShaAlgorithm::SHA256 => Ok(public_key .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), - HashAlgorithm::Sha384 => Ok(public_key + ShaAlgorithm::SHA384 => Ok(public_key .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), - HashAlgorithm::Sha512 => Ok(public_key + ShaAlgorithm::SHA512 => Ok(public_key .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), _ => Err(CryptoError::UnsupportedAlgorithm), @@ -311,20 +307,20 @@ impl CryptoProvider for RustCryptoProvider { &self, private_key_der: &[u8], digest: &[u8], - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { let mut rng = rand::rng(); let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - HashAlgorithm::Sha256 => private_key + ShaAlgorithm::SHA256 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) .map_err(|_| CryptoError::SigningFailed(None)), - HashAlgorithm::Sha384 => private_key + ShaAlgorithm::SHA384 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) .map_err(|_| CryptoError::SigningFailed(None)), - HashAlgorithm::Sha512 => private_key + ShaAlgorithm::SHA512 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) .map_err(|_| CryptoError::SigningFailed(None)), _ => Err(CryptoError::UnsupportedAlgorithm), @@ -336,19 +332,19 @@ impl CryptoProvider for RustCryptoProvider { public_key_der: &[u8], signature: &[u8], digest: &[u8], - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result { let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - HashAlgorithm::Sha256 => Ok(public_key + ShaAlgorithm::SHA256 => Ok(public_key .verify(Pkcs1v15Sign::new::(), digest, signature) .is_ok()), - HashAlgorithm::Sha384 => Ok(public_key + ShaAlgorithm::SHA384 => Ok(public_key .verify(Pkcs1v15Sign::new::(), digest, signature) .is_ok()), - HashAlgorithm::Sha512 => Ok(public_key + ShaAlgorithm::SHA512 => Ok(public_key .verify(Pkcs1v15Sign::new::(), digest, signature) .is_ok()), _ => Err(CryptoError::UnsupportedAlgorithm), @@ -359,7 +355,7 @@ impl CryptoProvider for RustCryptoProvider { &self, public_key_der: &[u8], data: &[u8], - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let mut rng = rand::rng(); @@ -367,7 +363,7 @@ impl CryptoProvider for RustCryptoProvider { .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - HashAlgorithm::Sha1 => { + ShaAlgorithm::SHA1 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -378,7 +374,7 @@ impl CryptoProvider for RustCryptoProvider { .encrypt(&mut rng, padding, data) .map_err(|_| CryptoError::EncryptionFailed(None)) }, - HashAlgorithm::Sha256 => { + ShaAlgorithm::SHA256 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -389,7 +385,7 @@ impl CryptoProvider for RustCryptoProvider { .encrypt(&mut rng, padding, data) .map_err(|_| CryptoError::EncryptionFailed(None)) }, - HashAlgorithm::Sha384 => { + ShaAlgorithm::SHA384 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -400,7 +396,7 @@ impl CryptoProvider for RustCryptoProvider { .encrypt(&mut rng, padding, data) .map_err(|_| CryptoError::EncryptionFailed(None)) }, - HashAlgorithm::Sha512 => { + ShaAlgorithm::SHA512 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -419,14 +415,14 @@ impl CryptoProvider for RustCryptoProvider { &self, private_key_der: &[u8], data: &[u8], - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - HashAlgorithm::Sha1 => { + ShaAlgorithm::SHA1 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -437,7 +433,7 @@ impl CryptoProvider for RustCryptoProvider { .decrypt(padding, data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, - HashAlgorithm::Sha256 => { + ShaAlgorithm::SHA256 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -448,7 +444,7 @@ impl CryptoProvider for RustCryptoProvider { .decrypt(padding, data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, - HashAlgorithm::Sha384 => { + ShaAlgorithm::SHA384 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -459,7 +455,7 @@ impl CryptoProvider for RustCryptoProvider { .decrypt(padding, data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, - HashAlgorithm::Sha512 => { + ShaAlgorithm::SHA512 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -773,15 +769,15 @@ impl CryptoProvider for RustCryptoProvider { salt: &[u8], info: &[u8], length: usize, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { use ring::hkdf; let algorithm = match hash_alg { - HashAlgorithm::Sha1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY, - HashAlgorithm::Sha256 => hkdf::HKDF_SHA256, - HashAlgorithm::Sha384 => hkdf::HKDF_SHA384, - HashAlgorithm::Sha512 => hkdf::HKDF_SHA512, + ShaAlgorithm::SHA1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY, + ShaAlgorithm::SHA256 => hkdf::HKDF_SHA256, + ShaAlgorithm::SHA384 => hkdf::HKDF_SHA384, + ShaAlgorithm::SHA512 => hkdf::HKDF_SHA512, _ => return Err(CryptoError::UnsupportedAlgorithm), }; @@ -804,13 +800,13 @@ impl CryptoProvider for RustCryptoProvider { salt: &[u8], iterations: u32, length: usize, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, ) -> Result, CryptoError> { let algorithm = match hash_alg { - HashAlgorithm::Sha1 => pbkdf2::PBKDF2_HMAC_SHA1, - HashAlgorithm::Sha256 => pbkdf2::PBKDF2_HMAC_SHA256, - HashAlgorithm::Sha384 => pbkdf2::PBKDF2_HMAC_SHA384, - HashAlgorithm::Sha512 => pbkdf2::PBKDF2_HMAC_SHA512, + ShaAlgorithm::SHA1 => pbkdf2::PBKDF2_HMAC_SHA1, + ShaAlgorithm::SHA256 => pbkdf2::PBKDF2_HMAC_SHA256, + ShaAlgorithm::SHA384 => pbkdf2::PBKDF2_HMAC_SHA384, + ShaAlgorithm::SHA512 => pbkdf2::PBKDF2_HMAC_SHA512, _ => return Err(CryptoError::UnsupportedAlgorithm), }; @@ -830,7 +826,7 @@ impl CryptoProvider for RustCryptoProvider { fn generate_hmac_key( &self, - hash_alg: HashAlgorithm, + hash_alg: ShaAlgorithm, length_bits: u16, ) -> Result, CryptoError> { let length_bytes = if length_bits == 0 { diff --git a/modules/llrt_crypto/src/sha_hash.rs b/modules/llrt_crypto/src/sha_hash.rs new file mode 100644 index 0000000000..38cf2505d4 --- /dev/null +++ b/modules/llrt_crypto/src/sha_hash.rs @@ -0,0 +1,197 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use llrt_utils::{ + bytes::{bytes_to_typed_array, ObjectBytes}, + iterable_enum, + result::ResultExt, +}; +use rquickjs::{function::Opt, prelude::This, Class, Ctx, Exception, Result, Value}; + +use super::{encoded_bytes, CRYPTO_PROVIDER}; +use crate::provider::{CryptoProvider, DefaultProvider, HmacProvider, SimpleDigest}; + +type ProviderHmac = ::Hmac; +type ProviderDigest = ::Digest; + +#[rquickjs::class] +#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] +pub struct Hmac { + #[qjs(skip_trace)] + inner: Option, +} + +#[rquickjs::methods] +impl Hmac { + #[qjs(skip)] + pub fn new<'js>(ctx: Ctx<'js>, algorithm: String, key_value: ObjectBytes<'js>) -> Result { + let algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; + let key = key_value.as_bytes(&ctx)?; + let hmac = CRYPTO_PROVIDER.hmac(algorithm, key); + + Ok(Self { inner: Some(hmac) }) + } + + fn digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { + let hmac = self + .inner + .take() + .ok_or_else(|| Exception::throw_message(&ctx, "Digest already called"))?; + let result = hmac.finalize(); + + match encoding.into_inner() { + Some(encoding) => encoded_bytes(ctx, &result, &encoding), + None => bytes_to_typed_array(ctx, &result), + } + } + + fn update<'js>( + this: This>, + ctx: Ctx<'js>, + bytes: ObjectBytes<'js>, + ) -> Result> { + let bytes = bytes.as_bytes(&ctx)?; + let mut borrowed = this.0.borrow_mut(); + if let Some(ref mut hmac) = borrowed.inner { + hmac.update(bytes); + } + drop(borrowed); + Ok(this.0) + } +} + +#[rquickjs::class] +#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] +pub struct Hash { + #[qjs(skip_trace)] + inner: Option, +} + +#[rquickjs::methods] +impl Hash { + #[qjs(skip)] + pub fn new(ctx: Ctx<'_>, algorithm: String) -> Result { + let algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; + let digest = CRYPTO_PROVIDER.digest(algorithm); + + Ok(Self { + inner: Some(digest), + }) + } + + #[qjs(rename = "digest")] + fn hash_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { + let digest = self + .inner + .take() + .ok_or_else(|| Exception::throw_message(&ctx, "Digest already called"))?; + let result = digest.finalize(); + + match encoding.0 { + Some(encoding) => encoded_bytes(ctx, &result, &encoding), + None => bytes_to_typed_array(ctx, &result), + } + } + + #[qjs(rename = "update")] + fn hash_update<'js>( + this: This>, + ctx: Ctx<'js>, + bytes: ObjectBytes<'js>, + ) -> Result> { + let bytes = bytes.as_bytes(&ctx)?; + let mut borrowed = this.0.borrow_mut(); + if let Some(ref mut digest) = borrowed.inner { + digest.update(bytes); + } + drop(borrowed); + Ok(this.0) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum ShaAlgorithm { + MD5, + SHA1, + SHA256, + SHA384, + SHA512, +} + +iterable_enum!(ShaAlgorithm, MD5, SHA1, SHA256, SHA384, SHA512); + +impl ShaAlgorithm { + pub fn class_name(&self) -> &'static str { + match self { + ShaAlgorithm::MD5 => "Md5", + ShaAlgorithm::SHA1 => "Sha1", + ShaAlgorithm::SHA256 => "Sha256", + ShaAlgorithm::SHA384 => "Sha384", + ShaAlgorithm::SHA512 => "Sha512", + } + } + + /// Returns the block size in bytes for this hash algorithm + pub fn block_len(&self) -> usize { + match self { + ShaAlgorithm::MD5 => 64, + ShaAlgorithm::SHA1 => 64, + ShaAlgorithm::SHA256 => 64, + ShaAlgorithm::SHA384 => 128, + ShaAlgorithm::SHA512 => 128, + } + } + + /// Returns the digest/output size in bytes for this hash algorithm + pub fn digest_len(&self) -> usize { + match self { + ShaAlgorithm::MD5 => 16, + ShaAlgorithm::SHA1 => 20, + ShaAlgorithm::SHA256 => 32, + ShaAlgorithm::SHA384 => 48, + ShaAlgorithm::SHA512 => 64, + } + } + + pub fn as_str(&self) -> &'static str { + match self { + ShaAlgorithm::MD5 => "MD5", + ShaAlgorithm::SHA1 => "SHA-1", + ShaAlgorithm::SHA256 => "SHA-256", + ShaAlgorithm::SHA384 => "SHA-384", + ShaAlgorithm::SHA512 => "SHA-512", + } + } + + pub fn as_numeric_str(&self) -> &'static str { + match self { + ShaAlgorithm::MD5 => "md5", + ShaAlgorithm::SHA1 => "1", + ShaAlgorithm::SHA256 => "256", + ShaAlgorithm::SHA384 => "384", + ShaAlgorithm::SHA512 => "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() + } +} diff --git a/modules/llrt_crypto/src/provider/rust/aes_variants.rs b/modules/llrt_crypto/src/subtle/aes_variants.rs similarity index 100% rename from modules/llrt_crypto/src/provider/rust/aes_variants.rs rename to modules/llrt_crypto/src/subtle/aes_variants.rs diff --git a/modules/llrt_crypto/src/subtle/digest.rs b/modules/llrt_crypto/src/subtle/digest.rs index eb71c1e3aa..2841f60600 100644 --- a/modules/llrt_crypto/src/subtle/digest.rs +++ b/modules/llrt_crypto/src/subtle/digest.rs @@ -4,8 +4,8 @@ use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt}; use rquickjs::{ArrayBuffer, Ctx, Result, Value}; use crate::{ - hash::HashAlgorithm, provider::{CryptoProvider, SimpleDigest}, + sha_hash::ShaAlgorithm, CRYPTO_PROVIDER, }; @@ -20,13 +20,13 @@ pub async fn subtle_digest<'js>( algorithm.get_required::<_, String>("name", "algorithm")? }; - let hash_algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; - let bytes = digest(&hash_algorithm, data.as_bytes(&ctx)?); + let sha_algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; + let bytes = digest(&sha_algorithm, data.as_bytes(&ctx)?); ArrayBuffer::new(ctx, bytes) } -pub fn digest(hash_algorithm: &HashAlgorithm, data: &[u8]) -> Vec { - let mut hasher = CRYPTO_PROVIDER.digest(*hash_algorithm); +pub fn digest(sha_algorithm: &ShaAlgorithm, data: &[u8]) -> Vec { + let mut hasher = CRYPTO_PROVIDER.digest(*sha_algorithm); hasher.update(data); hasher.finalize() } diff --git a/modules/llrt_crypto/src/subtle/generate_key.rs b/modules/llrt_crypto/src/subtle/generate_key.rs index 8ace80585b..46973d4d32 100644 --- a/modules/llrt_crypto/src/subtle/generate_key.rs +++ b/modules/llrt_crypto/src/subtle/generate_key.rs @@ -4,7 +4,7 @@ use rquickjs::{object::Property, Array, Class, Ctx, Exception, Object, Result, V use crate::{provider::CryptoProvider, CRYPTO_PROVIDER}; -use crate::{hash::HashAlgorithm, subtle::CryptoKey}; +use crate::{sha_hash::ShaAlgorithm, subtle::CryptoKey}; use super::{ algorithm_not_supported_error, @@ -120,7 +120,7 @@ fn generate_symmetric_key(_ctx: &Ctx<'_>, length: usize) -> Result> { } #[allow(dead_code)] -pub fn get_hash_length(ctx: &Ctx, hash: &HashAlgorithm, length: u16) -> Result { +pub fn get_hash_length(ctx: &Ctx, hash: &ShaAlgorithm, length: u16) -> Result { if length == 0 { return Ok(hash.block_len()); } diff --git a/modules/llrt_crypto/src/subtle/key_algorithm.rs b/modules/llrt_crypto/src/subtle/key_algorithm.rs index 1bffb8b802..212254d7e3 100644 --- a/modules/llrt_crypto/src/subtle/key_algorithm.rs +++ b/modules/llrt_crypto/src/subtle/key_algorithm.rs @@ -17,7 +17,7 @@ use rquickjs::{ #[cfg(feature = "_subtle-full")] use spki::{AlgorithmIdentifier, ObjectIdentifier}; -use crate::hash::HashAlgorithm; +use crate::sha_hash::ShaAlgorithm; #[cfg(feature = "_subtle-full")] use super::algorithm_mismatch_error; @@ -176,12 +176,12 @@ impl KeyUsageAlgorithm { #[derive(Debug, Clone)] pub enum KeyDerivation { Hkdf { - hash: HashAlgorithm, + hash: ShaAlgorithm, salt: Box<[u8]>, info: Box<[u8]>, }, Pbkdf2 { - hash: HashAlgorithm, + hash: ShaAlgorithm, salt: Box<[u8]>, iterations: u32, }, @@ -239,13 +239,13 @@ pub enum KeyAlgorithm { X25519, Ed25519, Hmac { - hash: HashAlgorithm, + hash: ShaAlgorithm, length: u16, }, Rsa { modulus_length: u32, public_exponent: Rc>, - hash: HashAlgorithm, + hash: ShaAlgorithm, }, Derive(KeyDerivation), HkdfImport, @@ -737,7 +737,7 @@ fn import_rsa_key<'js>( kind: &mut KeyKind, data: &mut Vec, algorithm_name: &str, - hash: &HashAlgorithm, + hash: &ShaAlgorithm, ) -> Result<(u32, Box<[u8]>)> { use crate::{ provider::{CryptoProvider, RsaJwkImport}, @@ -878,7 +878,7 @@ fn import_symmetric_key<'js>( kind: &mut KeyKind, data: &mut Vec, algorithm_name: &str, - hash: Option<&HashAlgorithm>, + hash: Option<&ShaAlgorithm>, ) -> Result { *kind = KeyKind::Secret; @@ -1120,7 +1120,7 @@ fn import_okp_key<'js>( Ok(()) } -pub fn extract_sha_hash<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> Result { +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() @@ -1132,17 +1132,17 @@ pub fn extract_sha_hash<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> Result(ctx: &Ctx<'js>, hash: &HashAlgorithm) -> Result> { +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) } #[cfg(feature = "_subtle-full")] -pub fn hash_mismatch_error(ctx: &Ctx<'_>, hash: &HashAlgorithm) -> Result { +pub fn hash_mismatch_error(ctx: &Ctx<'_>, hash: &ShaAlgorithm) -> Result { Err(Exception::throw_message( ctx, &["Algorithm hash expected to be ", hash.as_str()].concat(), diff --git a/modules/llrt_crypto/src/subtle/mod.rs b/modules/llrt_crypto/src/subtle/mod.rs index c04514bf9c..ecaef81103 100644 --- a/modules/llrt_crypto/src/subtle/mod.rs +++ b/modules/llrt_crypto/src/subtle/mod.rs @@ -50,7 +50,7 @@ use rquickjs::{atom::PredefinedAtom, Ctx, Exception, Object, Result, Value}; use crate::provider::{CryptoProvider, SimpleDigest}; -use crate::hash::HashAlgorithm; +use crate::sha_hash::ShaAlgorithm; #[rquickjs::class] #[derive(rquickjs::JsLifetime, rquickjs::class::Trace)] @@ -69,6 +69,12 @@ impl SubtleCrypto { } } +// AES variant types - only available when _rustcrypto feature is enabled +#[cfg(feature = "_rustcrypto")] +mod aes_variants; +#[cfg(feature = "_rustcrypto")] +pub use aes_variants::*; + #[derive(Debug, Clone, Copy, PartialEq)] pub enum EllipticCurve { P256, @@ -89,14 +95,14 @@ pub fn rsa_hash_digest<'a>( key: &'a CryptoKey, data: &'a [u8], algorithm_name: &str, -) -> Result<(&'a HashAlgorithm, Vec)> { +) -> Result<(&'a ShaAlgorithm, Vec)> { let hash = match &key.algorithm { KeyAlgorithm::Rsa { hash, .. } => hash, _ => return algorithm_mismatch_error(ctx, algorithm_name), }; if !matches!( hash, - HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::Sha512 + ShaAlgorithm::SHA256 | ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 ) { return Err(Exception::throw_message( ctx, diff --git a/modules/llrt_crypto/src/subtle/sign.rs b/modules/llrt_crypto/src/subtle/sign.rs index ed4541fcbd..c89cec8dcd 100644 --- a/modules/llrt_crypto/src/subtle/sign.rs +++ b/modules/llrt_crypto/src/subtle/sign.rs @@ -87,7 +87,7 @@ fn sign( // sign_fn: F, // ) -> Result> // where -// F: FnOnce(&HashAlgorithm, &[u8], &rsa::RsaPrivateKey) -> Result>, +// F: FnOnce(&ShaAlgorithm, &[u8], &rsa::RsaPrivateKey) -> Result>, // { // let (hash, digest) = rsa_hash_digest(ctx, key, data, algorithm_name)?; diff --git a/modules/llrt_crypto/src/subtle/sign_algorithm.rs b/modules/llrt_crypto/src/subtle/sign_algorithm.rs index 20fd13ff97..ef3d292aee 100644 --- a/modules/llrt_crypto/src/subtle/sign_algorithm.rs +++ b/modules/llrt_crypto/src/subtle/sign_algorithm.rs @@ -3,7 +3,7 @@ use llrt_utils::{object::ObjectExt, result::ResultExt}; use rquickjs::{Ctx, FromJs, Result, Value}; -use crate::hash::HashAlgorithm; +use crate::sha_hash::ShaAlgorithm; use super::{ algorithm_not_supported_error, key_algorithm::extract_sha_hash, to_name_and_maybe_object, @@ -11,7 +11,7 @@ use super::{ #[derive(Debug)] pub enum SigningAlgorithm { - Ecdsa { hash: HashAlgorithm }, + Ecdsa { hash: ShaAlgorithm }, Ed25519, RsaPss { salt_length: u32 }, RsassaPkcs1v15, From 199696ba64ec7b8faeaadf6d731b88c6f718fb16 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 3 Feb 2026 08:50:05 +0000 Subject: [PATCH 71/77] - Replace ShaAlgorithm with unified HashAlgorithm enum - Consolidate Hash and Hmac classes into hash.rs module - Move aes_variants.rs to provider/rust/ directory - Remove separate md5_hash.rs and sha_hash.rs files - Update all provider implementations to use HashAlgorithm - Update subtle/digest.rs to use HashAlgorithm --- llrt_core/Cargo.toml | 2 +- llrt_modules/Cargo.toml | 2 +- modules/llrt_crypto/Cargo.toml | 1 - modules/llrt_crypto/src/hash.rs | 180 ++++++++++++++++ modules/llrt_crypto/src/lib.rs | 18 +- modules/llrt_crypto/src/md5_hash.rs | 48 ----- modules/llrt_crypto/src/provider/graviola.rs | 48 ++--- modules/llrt_crypto/src/provider/mod.rs | 80 +++---- modules/llrt_crypto/src/provider/openssl.rs | 68 +++--- modules/llrt_crypto/src/provider/ring.rs | 44 ++-- .../{subtle => provider/rust}/aes_variants.rs | 0 .../src/provider/{rust.rs => rust/mod.rs} | 106 +++++----- modules/llrt_crypto/src/sha_hash.rs | 197 ------------------ modules/llrt_crypto/src/subtle/digest.rs | 10 +- .../llrt_crypto/src/subtle/generate_key.rs | 4 +- .../llrt_crypto/src/subtle/key_algorithm.rs | 24 +-- modules/llrt_crypto/src/subtle/mod.rs | 12 +- modules/llrt_crypto/src/subtle/sign.rs | 2 +- .../llrt_crypto/src/subtle/sign_algorithm.rs | 4 +- 19 files changed, 384 insertions(+), 466 deletions(-) create mode 100644 modules/llrt_crypto/src/hash.rs delete mode 100644 modules/llrt_crypto/src/md5_hash.rs rename modules/llrt_crypto/src/{subtle => provider/rust}/aes_variants.rs (100%) rename modules/llrt_crypto/src/provider/{rust.rs => rust/mod.rs} (95%) delete mode 100644 modules/llrt_crypto/src/sha_hash.rs diff --git a/llrt_core/Cargo.toml b/llrt_core/Cargo.toml index 43730508da..8abaac6ac5 100644 --- a/llrt_core/Cargo.toml +++ b/llrt_core/Cargo.toml @@ -27,7 +27,7 @@ crypto-graviola-rust = ["llrt_modules/crypto-graviola-rust"] crypto-openssl = ["llrt_modules/crypto-openssl"] # OpenSSL vendored (builds OpenSSL from source) -openssl-vendored = ["llrt_modules/openssl-vendored", "openssl/vendored"] +openssl-vendored = ["openssl/vendored"] [dependencies] bytes = { version = "1", default-features = false } diff --git a/llrt_modules/Cargo.toml b/llrt_modules/Cargo.toml index d42ac0a692..289344d59c 100644 --- a/llrt_modules/Cargo.toml +++ b/llrt_modules/Cargo.toml @@ -26,7 +26,7 @@ crypto-graviola-rust = ["llrt_crypto?/crypto-graviola-rust"] crypto-openssl = ["llrt_crypto?/crypto-openssl"] # OpenSSL vendored (builds OpenSSL from source) -openssl-vendored = ["llrt_crypto?/openssl-vendored", "dep:openssl", "openssl/vendored"] +openssl-vendored = ["dep:openssl", "openssl/vendored"] base = [ "abort", diff --git a/modules/llrt_crypto/Cargo.toml b/modules/llrt_crypto/Cargo.toml index d82340cce0..86d1b95c3a 100644 --- a/modules/llrt_crypto/Cargo.toml +++ b/modules/llrt_crypto/Cargo.toml @@ -48,7 +48,6 @@ crypto-ring-rust = ["_rustcrypto"] crypto-graviola = ["graviola"] crypto-graviola-rust = ["graviola", "_rustcrypto"] crypto-openssl = ["openssl-sys", "openssl", "_subtle-full"] -openssl-vendored = ["openssl-sys?/vendored", "openssl?/vendored"] [dependencies] crc32c = { version = "0.6", default-features = false } diff --git a/modules/llrt_crypto/src/hash.rs b/modules/llrt_crypto/src/hash.rs new file mode 100644 index 0000000000..3290d2f152 --- /dev/null +++ b/modules/llrt_crypto/src/hash.rs @@ -0,0 +1,180 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use llrt_buffer::Buffer; +use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; +use rquickjs::{ + class::Trace, function::Opt, prelude::This, Class, Ctx, IntoJs, JsLifetime, Result, Value, +}; + +use super::encoded_bytes; +use crate::provider::{CryptoProvider, HmacProvider, SimpleDigest}; +use crate::CRYPTO_PROVIDER; + +#[derive(Debug, Clone, Copy)] +pub enum HashAlgorithm { + Md5, + Sha1, + Sha256, + Sha384, + Sha512, +} + +impl TryFrom<&str> for HashAlgorithm { + type Error = String; + fn try_from(s: &str) -> std::result::Result { + Ok(match s.to_ascii_uppercase().as_str() { + "MD5" => HashAlgorithm::Md5, + "MD-5" => HashAlgorithm::Md5, + "SHA1" => HashAlgorithm::Sha1, + "SHA-1" => HashAlgorithm::Sha1, + "SHA256" => HashAlgorithm::Sha256, + "SHA-256" => HashAlgorithm::Sha256, + "SHA384" => HashAlgorithm::Sha384, + "SHA-384" => HashAlgorithm::Sha384, + "SHA512" => HashAlgorithm::Sha512, + "SHA-512" => HashAlgorithm::Sha512, + _ => return Err(["'", s, "' not available"].concat()), + }) + } +} + +impl HashAlgorithm { + pub fn as_str(&self) -> &'static str { + match self { + HashAlgorithm::Md5 => "MD5", + HashAlgorithm::Sha1 => "SHA-1", + HashAlgorithm::Sha256 => "SHA-256", + HashAlgorithm::Sha384 => "SHA-384", + HashAlgorithm::Sha512 => "SHA-512", + } + } + + pub fn as_numeric_str(&self) -> &'static str { + match self { + HashAlgorithm::Md5 => "md5", + HashAlgorithm::Sha1 => "1", + HashAlgorithm::Sha256 => "256", + HashAlgorithm::Sha384 => "384", + HashAlgorithm::Sha512 => "512", + } + } + + pub fn digest_len(&self) -> usize { + match self { + HashAlgorithm::Md5 => 16, + HashAlgorithm::Sha1 => 20, + HashAlgorithm::Sha256 => 32, + HashAlgorithm::Sha384 => 48, + HashAlgorithm::Sha512 => 64, + } + } + + pub fn block_len(&self) -> usize { + match self { + HashAlgorithm::Md5 => 64, + HashAlgorithm::Sha1 => 64, + HashAlgorithm::Sha256 => 64, + HashAlgorithm::Sha384 => 128, + HashAlgorithm::Sha512 => 128, + } + } +} + +type ProviderDigest = ::Digest; +type ProviderHmac = ::Hmac; + +#[derive(Trace, JsLifetime)] +#[rquickjs::class] +pub struct Hash { + #[qjs(skip_trace)] + inner: Option, +} + +impl Hash { + pub fn new(ctx: Ctx<'_>, algorithm: String) -> Result { + let algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; + Ok(Self { + inner: Some(CRYPTO_PROVIDER.digest(algorithm)), + }) + } +} + +#[rquickjs::methods] +impl Hash { + #[qjs(rename = "digest")] + fn hash_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { + let digest = self + .inner + .take() + .ok_or_else(|| rquickjs::Exception::throw_message(&ctx, "Digest already called"))?; + let result = digest.finalize(); + let bytes: &[u8] = result.as_ref(); + + match encoding.0 { + Some(encoding) => encoded_bytes(ctx, bytes, &encoding), + None => Buffer(bytes.to_vec()).into_js(&ctx), + } + } + + #[qjs(rename = "update")] + fn hash_update<'js>( + this: This>, + ctx: Ctx<'js>, + bytes: ObjectBytes<'js>, + ) -> Result> { + let bytes = bytes.as_bytes(&ctx)?; + let mut borrowed = this.0.borrow_mut(); + if let Some(ref mut digest) = borrowed.inner { + digest.update(bytes); + } + drop(borrowed); + Ok(this.0) + } +} + +#[derive(Trace, JsLifetime)] +#[rquickjs::class] +pub struct Hmac { + #[qjs(skip_trace)] + inner: Option, +} + +impl Hmac { + pub fn new<'js>(ctx: Ctx<'js>, algorithm: String, key_value: ObjectBytes<'js>) -> Result { + let algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; + let key = key_value.as_bytes(&ctx)?; + let hmac = CRYPTO_PROVIDER.hmac(algorithm, key); + Ok(Self { inner: Some(hmac) }) + } +} + +#[rquickjs::methods] +impl Hmac { + fn digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { + let hmac = self + .inner + .take() + .ok_or_else(|| rquickjs::Exception::throw_message(&ctx, "Digest already called"))?; + let result = hmac.finalize(); + let bytes: &[u8] = result.as_ref(); + + match encoding.into_inner() { + Some(encoding) => encoded_bytes(ctx, bytes, &encoding), + None => Buffer(bytes.to_vec()).into_js(&ctx), + } + } + + fn update<'js>( + this: This>, + ctx: Ctx<'js>, + bytes: ObjectBytes<'js>, + ) -> Result> { + let bytes = bytes.as_bytes(&ctx)?; + let mut borrowed = this.0.borrow_mut(); + if let Some(ref mut hmac) = borrowed.inner { + hmac.update(bytes); + } + drop(borrowed); + Ok(this.0) + } +} diff --git a/modules/llrt_crypto/src/lib.rs b/modules/llrt_crypto/src/lib.rs index 433a5bed30..de91661db6 100644 --- a/modules/llrt_crypto/src/lib.rs +++ b/modules/llrt_crypto/src/lib.rs @@ -21,8 +21,7 @@ compile_error!("Features `crypto-openssl` and `crypto-graviola` are mutually exc compile_error!("Features `crypto-ring` and `crypto-graviola` are mutually exclusive"); mod crc32; -mod md5_hash; -mod sha_hash; +mod hash; mod subtle; mod provider; @@ -57,8 +56,7 @@ use subtle::{ use self::{ crc32::{Crc32, Crc32c}, - md5_hash::Md5, - sha_hash::{Hash, Hmac, ShaAlgorithm}, + hash::{Hash, Hmac}, }; static CRYPTO_PROVIDER: Lazy = @@ -292,18 +290,12 @@ impl ModuleDef for CryptoModule { declare.declare("createHmac")?; declare.declare("Crc32")?; declare.declare("Crc32c")?; - declare.declare("Md5")?; declare.declare("randomBytes")?; declare.declare("randomUUID")?; declare.declare("randomInt")?; declare.declare("randomFillSync")?; declare.declare("randomFill")?; declare.declare("getRandomValues")?; - - for sha_algorithm in ShaAlgorithm::iter() { - let class_name = sha_algorithm.class_name(); - declare.declare(class_name)?; - } declare.declare("crypto")?; declare.declare("webcrypto")?; declare.declare("default")?; @@ -313,14 +305,8 @@ impl ModuleDef for CryptoModule { fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { - for sha_algorithm in ShaAlgorithm::iter() { - let _class_name: &str = sha_algorithm.class_name(); - // ShaHash class removed - using Hash and Hmac instead - } - let crypto: Object = ctx.globals().get("crypto")?; - Class::::define(default)?; Class::::define(default)?; Class::::define(default)?; diff --git a/modules/llrt_crypto/src/md5_hash.rs b/modules/llrt_crypto/src/md5_hash.rs deleted file mode 100644 index 0538bd1e0c..0000000000 --- a/modules/llrt_crypto/src/md5_hash.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -use llrt_utils::bytes::{bytes_to_typed_array, ObjectBytes}; -use rquickjs::{function::Opt, prelude::This, Class, Ctx, Result, Value}; - -use super::{encoded_bytes, CRYPTO_PROVIDER}; -use crate::{ - provider::{CryptoProvider, SimpleDigest}, - sha_hash::ShaAlgorithm, -}; - -#[rquickjs::class] -#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] -pub struct Md5 { - #[qjs(skip_trace)] - hasher: ::Digest, -} - -#[rquickjs::methods] -impl Md5 { - #[qjs(constructor)] - fn new() -> Self { - Self { - hasher: CRYPTO_PROVIDER.digest(ShaAlgorithm::MD5), - } - } - - #[qjs(rename = "digest")] - fn md5_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let digest = std::mem::replace(&mut self.hasher, CRYPTO_PROVIDER.digest(ShaAlgorithm::MD5)) - .finalize(); - - match encoding.0 { - Some(encoding) => encoded_bytes(ctx, &digest, &encoding), - None => bytes_to_typed_array(ctx, &digest), - } - } - - #[qjs(rename = "update")] - fn md5_update<'js>( - this: This>, - ctx: Ctx<'js>, - bytes: ObjectBytes<'js>, - ) -> Result> { - this.0.borrow_mut().hasher.update(bytes.as_bytes(&ctx)?); - Ok(this.0) - } -} diff --git a/modules/llrt_crypto/src/provider/graviola.rs b/modules/llrt_crypto/src/provider/graviola.rs index 2349748f0e..616b91c464 100644 --- a/modules/llrt_crypto/src/provider/graviola.rs +++ b/modules/llrt_crypto/src/provider/graviola.rs @@ -11,8 +11,8 @@ use graviola::{ hashing::{hmac::Hmac, Hash, HashContext, Sha256, Sha384, Sha512}, }; +use crate::hash::HashAlgorithm; use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; -use crate::sha_hash::ShaAlgorithm; use crate::subtle::EllipticCurve; pub struct GraviolaProvider; @@ -69,20 +69,20 @@ impl CryptoProvider for GraviolaProvider { type Digest = GraviolaDigest; type Hmac = GraviolaHmac; - fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { + fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { match algorithm { - ShaAlgorithm::SHA256 => GraviolaDigest::Sha256(Sha256::new()), - ShaAlgorithm::SHA384 => GraviolaDigest::Sha384(Sha384::new()), - ShaAlgorithm::SHA512 => GraviolaDigest::Sha512(Sha512::new()), + HashAlgorithm::Sha256 => GraviolaDigest::Sha256(Sha256::new()), + HashAlgorithm::Sha384 => GraviolaDigest::Sha384(Sha384::new()), + HashAlgorithm::Sha512 => GraviolaDigest::Sha512(Sha512::new()), _ => panic!("Unsupported digest algorithm for Graviola"), } } - fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { match algorithm { - ShaAlgorithm::SHA256 => GraviolaHmac::Sha256(Hmac::::new(key)), - ShaAlgorithm::SHA384 => GraviolaHmac::Sha384(Hmac::::new(key)), - ShaAlgorithm::SHA512 => GraviolaHmac::Sha512(Hmac::::new(key)), + HashAlgorithm::Sha256 => GraviolaHmac::Sha256(Hmac::::new(key)), + HashAlgorithm::Sha384 => GraviolaHmac::Sha384(Hmac::::new(key)), + HashAlgorithm::Sha512 => GraviolaHmac::Sha512(Hmac::::new(key)), _ => panic!("Unsupported HMAC algorithm for Graviola"), } } @@ -124,7 +124,7 @@ impl CryptoProvider for GraviolaProvider { _private_key_der: &[u8], _digest: &[u8], _salt_length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -135,7 +135,7 @@ impl CryptoProvider for GraviolaProvider { _signature: &[u8], _digest: &[u8], _salt_length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } @@ -144,7 +144,7 @@ impl CryptoProvider for GraviolaProvider { &self, _private_key_der: &[u8], _digest: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -154,7 +154,7 @@ impl CryptoProvider for GraviolaProvider { _public_key_der: &[u8], _signature: &[u8], _digest: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } @@ -163,7 +163,7 @@ impl CryptoProvider for GraviolaProvider { &self, _public_key_der: &[u8], _data: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) @@ -173,7 +173,7 @@ impl CryptoProvider for GraviolaProvider { &self, _private_key_der: &[u8], _data: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) @@ -267,7 +267,7 @@ impl CryptoProvider for GraviolaProvider { _salt: &[u8], _info: &[u8], _length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -278,7 +278,7 @@ impl CryptoProvider for GraviolaProvider { _salt: &[u8], _iterations: u32, _length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -292,13 +292,13 @@ impl CryptoProvider for GraviolaProvider { fn generate_hmac_key( &self, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, length_bits: u16, ) -> Result, CryptoError> { let length_bytes = if length_bits == 0 { match hash_alg { - ShaAlgorithm::SHA256 => 64, - ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 => 128, + HashAlgorithm::Sha256 => 64, + HashAlgorithm::Sha384 | HashAlgorithm::Sha512 => 128, _ => return Err(CryptoError::UnsupportedAlgorithm), } } else { @@ -501,9 +501,9 @@ pub enum GraviolaRustDigest { #[cfg(feature = "crypto-graviola-rust")] impl GraviolaRustDigest { - pub fn new(algorithm: ShaAlgorithm) -> Self { + pub fn new(algorithm: HashAlgorithm) -> Self { match algorithm { - ShaAlgorithm::SHA256 | ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 => { + HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::Sha512 => { Self::Graviola(GraviolaProvider.digest(algorithm)) }, _ => Self::Rust(super::rust::RustCryptoProvider.digest(algorithm)), @@ -535,9 +535,9 @@ pub enum GraviolaRustHmac { #[cfg(feature = "crypto-graviola-rust")] impl GraviolaRustHmac { - pub fn new(algorithm: ShaAlgorithm, key: &[u8]) -> Self { + pub fn new(algorithm: HashAlgorithm, key: &[u8]) -> Self { match algorithm { - ShaAlgorithm::SHA256 | ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 => { + HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::Sha512 => { Self::Graviola(GraviolaProvider.hmac(algorithm, key)) }, _ => Self::Rust(super::rust::RustCryptoProvider.hmac(algorithm, key)), diff --git a/modules/llrt_crypto/src/provider/mod.rs b/modules/llrt_crypto/src/provider/mod.rs index b36cfd4e37..ac6895056a 100644 --- a/modules/llrt_crypto/src/provider/mod.rs +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -35,7 +35,7 @@ mod ring; #[cfg(feature = "_rustcrypto")] mod rust; -use crate::sha_hash::ShaAlgorithm; +use crate::hash::HashAlgorithm; use crate::subtle::EllipticCurve; #[derive(Debug)] @@ -144,10 +144,10 @@ pub trait CryptoProvider { type Hmac: HmacProvider; // Digest operations - fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest; + fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest; // HMAC operations - fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac; + fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac; // ECDSA operations fn ecdsa_sign( @@ -179,7 +179,7 @@ pub trait CryptoProvider { private_key_der: &[u8], digest: &[u8], salt_length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError>; fn rsa_pss_verify( &self, @@ -187,33 +187,33 @@ pub trait CryptoProvider { signature: &[u8], digest: &[u8], salt_length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result; fn rsa_pkcs1v15_sign( &self, private_key_der: &[u8], digest: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError>; fn rsa_pkcs1v15_verify( &self, public_key_der: &[u8], signature: &[u8], digest: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result; fn rsa_oaep_encrypt( &self, public_key_der: &[u8], data: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError>; fn rsa_oaep_decrypt( &self, private_key_der: &[u8], data: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError>; @@ -261,7 +261,7 @@ pub trait CryptoProvider { salt: &[u8], info: &[u8], length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError>; fn pbkdf2_derive_key( &self, @@ -269,13 +269,13 @@ pub trait CryptoProvider { salt: &[u8], iterations: u32, length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError>; fn generate_aes_key(&self, length_bits: u16) -> Result, CryptoError>; fn generate_hmac_key( &self, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, length_bits: u16, ) -> Result, CryptoError>; fn generate_ec_key(&self, curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError>; // (private, public) @@ -470,10 +470,10 @@ macro_rules! impl_hybrid_provider { impl CryptoProvider for $name { type Digest = $digest; type Hmac = $hmac; - fn digest(&self, alg: ShaAlgorithm) -> Self::Digest { + fn digest(&self, alg: HashAlgorithm) -> Self::Digest { $digest_fn(alg) } - fn hmac(&self, alg: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, alg: HashAlgorithm, key: &[u8]) -> Self::Hmac { $hmac_fn(alg, key) } fn ecdsa_sign( @@ -504,7 +504,7 @@ macro_rules! impl_hybrid_provider { k: &[u8], d: &[u8], s: usize, - a: ShaAlgorithm, + a: HashAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_pss_sign(k, d, s, a) } @@ -514,7 +514,7 @@ macro_rules! impl_hybrid_provider { s: &[u8], d: &[u8], sl: usize, - a: ShaAlgorithm, + a: HashAlgorithm, ) -> Result { rust::RustCryptoProvider.rsa_pss_verify(k, s, d, sl, a) } @@ -522,7 +522,7 @@ macro_rules! impl_hybrid_provider { &self, k: &[u8], d: &[u8], - a: ShaAlgorithm, + a: HashAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_pkcs1v15_sign(k, d, a) } @@ -531,7 +531,7 @@ macro_rules! impl_hybrid_provider { k: &[u8], s: &[u8], d: &[u8], - a: ShaAlgorithm, + a: HashAlgorithm, ) -> Result { rust::RustCryptoProvider.rsa_pkcs1v15_verify(k, s, d, a) } @@ -539,7 +539,7 @@ macro_rules! impl_hybrid_provider { &self, k: &[u8], d: &[u8], - a: ShaAlgorithm, + a: HashAlgorithm, l: Option<&[u8]>, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_oaep_encrypt(k, d, a, l) @@ -548,7 +548,7 @@ macro_rules! impl_hybrid_provider { &self, k: &[u8], d: &[u8], - a: ShaAlgorithm, + a: HashAlgorithm, l: Option<&[u8]>, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_oaep_decrypt(k, d, a, l) @@ -596,7 +596,7 @@ macro_rules! impl_hybrid_provider { s: &[u8], i: &[u8], l: usize, - a: ShaAlgorithm, + a: HashAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.hkdf_derive_key(k, s, i, l, a) } @@ -606,14 +606,14 @@ macro_rules! impl_hybrid_provider { s: &[u8], i: u32, l: usize, - a: ShaAlgorithm, + a: HashAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.pbkdf2_derive_key(p, s, i, l, a) } fn generate_aes_key(&self, b: u16) -> Result, CryptoError> { rust::RustCryptoProvider.generate_aes_key(b) } - fn generate_hmac_key(&self, a: ShaAlgorithm, b: u16) -> Result, CryptoError> { + fn generate_hmac_key(&self, a: HashAlgorithm, b: u16) -> Result, CryptoError> { rust::RustCryptoProvider.generate_hmac_key(a, b) } fn generate_ec_key(&self, c: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { @@ -861,7 +861,7 @@ mod tests { #[test] fn test_sha256_digest() { let p = provider(); - let mut digest = p.digest(ShaAlgorithm::SHA256); + let mut digest = p.digest(HashAlgorithm::Sha256); digest.update(b"hello world"); let result = digest.finalize(); assert_eq!(result.len(), 32); @@ -874,7 +874,7 @@ mod tests { #[test] fn test_sha384_digest() { let p = provider(); - let mut digest = p.digest(ShaAlgorithm::SHA384); + let mut digest = p.digest(HashAlgorithm::Sha384); digest.update(b"hello world"); let result = digest.finalize(); assert_eq!(result.len(), 48); @@ -883,7 +883,7 @@ mod tests { #[test] fn test_sha512_digest() { let p = provider(); - let mut digest = p.digest(ShaAlgorithm::SHA512); + let mut digest = p.digest(HashAlgorithm::Sha512); digest.update(b"hello world"); let result = digest.finalize(); assert_eq!(result.len(), 64); @@ -894,7 +894,7 @@ mod tests { fn test_hmac_sha256() { let p = provider(); let key = b"secret key"; - let mut hmac = p.hmac(ShaAlgorithm::SHA256, key); + let mut hmac = p.hmac(HashAlgorithm::Sha256, key); hmac.update(b"hello world"); let result = hmac.finalize(); assert_eq!(result.len(), 32); @@ -1035,7 +1035,7 @@ mod tests { #[test] fn test_generate_hmac_key() { let p = provider(); - let key = p.generate_hmac_key(ShaAlgorithm::SHA256, 256).unwrap(); + let key = p.generate_hmac_key(HashAlgorithm::Sha256, 256).unwrap(); assert_eq!(key.len(), 32); } @@ -1117,7 +1117,7 @@ mod tests { let info = b"info"; let derived = p - .hkdf_derive_key(ikm, salt, info, 32, ShaAlgorithm::SHA256) + .hkdf_derive_key(ikm, salt, info, 32, HashAlgorithm::Sha256) .unwrap(); assert_eq!(derived.len(), 32); @@ -1130,7 +1130,7 @@ mod tests { let salt = b"salt"; let derived = p - .pbkdf2_derive_key(password, salt, 1000, 32, ShaAlgorithm::SHA256) + .pbkdf2_derive_key(password, salt, 1000, 32, HashAlgorithm::Sha256) .unwrap(); assert_eq!(derived.len(), 32); @@ -1142,7 +1142,7 @@ mod tests { let (private_key, public_key) = p.generate_ec_key(EllipticCurve::P256).unwrap(); // Create a digest to sign - let mut digest = p.digest(ShaAlgorithm::SHA256); + let mut digest = p.digest(HashAlgorithm::Sha256); digest.update(b"message to sign"); let hash = digest.finalize(); @@ -1162,7 +1162,7 @@ mod tests { let p = provider(); let (private_key, public_key) = p.generate_ec_key(EllipticCurve::P384).unwrap(); - let mut digest = p.digest(ShaAlgorithm::SHA384); + let mut digest = p.digest(HashAlgorithm::Sha384); digest.update(b"message to sign"); let hash = digest.finalize(); @@ -1224,16 +1224,16 @@ mod tests { let p = provider(); let (private_key, public_key) = p.generate_rsa_key(2048, &[1, 0, 1]).unwrap(); - let mut digest = p.digest(ShaAlgorithm::SHA256); + let mut digest = p.digest(HashAlgorithm::Sha256); digest.update(b"message to sign"); let hash = digest.finalize(); let signature = p - .rsa_pss_sign(&private_key, &hash, 32, ShaAlgorithm::SHA256) + .rsa_pss_sign(&private_key, &hash, 32, HashAlgorithm::Sha256) .unwrap(); let valid = p - .rsa_pss_verify(&public_key, &signature, &hash, 32, ShaAlgorithm::SHA256) + .rsa_pss_verify(&public_key, &signature, &hash, 32, HashAlgorithm::Sha256) .unwrap(); assert!(valid); @@ -1244,16 +1244,16 @@ mod tests { let p = provider(); let (private_key, public_key) = p.generate_rsa_key(2048, &[1, 0, 1]).unwrap(); - let mut digest = p.digest(ShaAlgorithm::SHA256); + let mut digest = p.digest(HashAlgorithm::Sha256); digest.update(b"message to sign"); let hash = digest.finalize(); let signature = p - .rsa_pkcs1v15_sign(&private_key, &hash, ShaAlgorithm::SHA256) + .rsa_pkcs1v15_sign(&private_key, &hash, HashAlgorithm::Sha256) .unwrap(); let valid = p - .rsa_pkcs1v15_verify(&public_key, &signature, &hash, ShaAlgorithm::SHA256) + .rsa_pkcs1v15_verify(&public_key, &signature, &hash, HashAlgorithm::Sha256) .unwrap(); assert!(valid); @@ -1267,11 +1267,11 @@ mod tests { let plaintext = b"secret message"; let ciphertext = p - .rsa_oaep_encrypt(&public_key, plaintext, ShaAlgorithm::SHA256, None) + .rsa_oaep_encrypt(&public_key, plaintext, HashAlgorithm::Sha256, None) .unwrap(); let decrypted = p - .rsa_oaep_decrypt(&private_key, &ciphertext, ShaAlgorithm::SHA256, None) + .rsa_oaep_decrypt(&private_key, &ciphertext, HashAlgorithm::Sha256, None) .unwrap(); assert_eq!(decrypted, plaintext); diff --git a/modules/llrt_crypto/src/provider/openssl.rs b/modules/llrt_crypto/src/provider/openssl.rs index 0f759e283a..454f420539 100644 --- a/modules/llrt_crypto/src/provider/openssl.rs +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -17,8 +17,8 @@ use openssl::rsa::{Padding, Rsa}; use openssl::sign::{Signer, Verifier}; use openssl::symm::{self, Cipher}; +use crate::hash::HashAlgorithm; use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; -use crate::sha_hash::ShaAlgorithm; use crate::subtle::EllipticCurve; pub struct OpenSslProvider; @@ -71,23 +71,23 @@ impl HmacProvider for OpenSslHmac { } } -fn get_message_digest(alg: ShaAlgorithm) -> MessageDigest { +fn get_message_digest(alg: HashAlgorithm) -> MessageDigest { match alg { - ShaAlgorithm::MD5 => MessageDigest::md5(), - ShaAlgorithm::SHA1 => MessageDigest::sha1(), - ShaAlgorithm::SHA256 => MessageDigest::sha256(), - ShaAlgorithm::SHA384 => MessageDigest::sha384(), - ShaAlgorithm::SHA512 => MessageDigest::sha512(), + HashAlgorithm::Md5 => MessageDigest::md5(), + HashAlgorithm::Sha1 => MessageDigest::sha1(), + HashAlgorithm::Sha256 => MessageDigest::sha256(), + HashAlgorithm::Sha384 => MessageDigest::sha384(), + HashAlgorithm::Sha512 => MessageDigest::sha512(), } } -fn get_md(alg: ShaAlgorithm) -> &'static openssl::md::MdRef { +fn get_md(alg: HashAlgorithm) -> &'static openssl::md::MdRef { match alg { - ShaAlgorithm::MD5 => Md::md5(), - ShaAlgorithm::SHA1 => Md::sha1(), - ShaAlgorithm::SHA256 => Md::sha256(), - ShaAlgorithm::SHA384 => Md::sha384(), - ShaAlgorithm::SHA512 => Md::sha512(), + HashAlgorithm::Md5 => Md::md5(), + HashAlgorithm::Sha1 => Md::sha1(), + HashAlgorithm::Sha256 => Md::sha256(), + HashAlgorithm::Sha384 => Md::sha384(), + HashAlgorithm::Sha512 => Md::sha512(), } } @@ -109,19 +109,19 @@ impl CryptoProvider for OpenSslProvider { type Digest = OpenSslDigest; type Hmac = OpenSslHmac; - fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { + fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { let md = get_message_digest(algorithm); let hasher = Hasher::new(md).expect("Failed to create hasher"); match algorithm { - ShaAlgorithm::MD5 => OpenSslDigest::Md5(hasher), - ShaAlgorithm::SHA1 => OpenSslDigest::Sha1(hasher), - ShaAlgorithm::SHA256 => OpenSslDigest::Sha256(hasher), - ShaAlgorithm::SHA384 => OpenSslDigest::Sha384(hasher), - ShaAlgorithm::SHA512 => OpenSslDigest::Sha512(hasher), + HashAlgorithm::Md5 => OpenSslDigest::Md5(hasher), + HashAlgorithm::Sha1 => OpenSslDigest::Sha1(hasher), + HashAlgorithm::Sha256 => OpenSslDigest::Sha256(hasher), + HashAlgorithm::Sha384 => OpenSslDigest::Sha384(hasher), + HashAlgorithm::Sha512 => OpenSslDigest::Sha512(hasher), } } - fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { let md = get_message_digest(algorithm); let pkey = PKey::hmac(key).expect("Failed to create HMAC key"); let signer = unsafe { @@ -208,7 +208,7 @@ impl CryptoProvider for OpenSslProvider { private_key_der: &[u8], digest: &[u8], salt_length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let rsa = Rsa::private_key_from_der(private_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; @@ -240,7 +240,7 @@ impl CryptoProvider for OpenSslProvider { signature: &[u8], digest: &[u8], salt_length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result { let rsa = Rsa::public_key_from_der(public_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; @@ -268,7 +268,7 @@ impl CryptoProvider for OpenSslProvider { &self, private_key_der: &[u8], digest: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let rsa = Rsa::private_key_from_der(private_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; @@ -293,7 +293,7 @@ impl CryptoProvider for OpenSslProvider { public_key_der: &[u8], signature: &[u8], digest: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result { let rsa = Rsa::public_key_from_der(public_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; @@ -315,7 +315,7 @@ impl CryptoProvider for OpenSslProvider { &self, public_key_der: &[u8], data: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let rsa = Rsa::public_key_from_der(public_key_der) @@ -348,7 +348,7 @@ impl CryptoProvider for OpenSslProvider { &self, private_key_der: &[u8], data: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let rsa = Rsa::private_key_from_der(private_key_der) @@ -586,7 +586,7 @@ impl CryptoProvider for OpenSslProvider { salt: &[u8], info: &[u8], length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { use openssl::pkey_ctx::HkdfMode; let md = get_md(hash_alg); @@ -620,7 +620,7 @@ impl CryptoProvider for OpenSslProvider { salt: &[u8], iterations: u32, length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let md = get_message_digest(hash_alg); let mut out = vec![0u8; length]; @@ -639,16 +639,16 @@ impl CryptoProvider for OpenSslProvider { fn generate_hmac_key( &self, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, length_bits: u16, ) -> Result, CryptoError> { let length_bytes = if length_bits == 0 { match hash_alg { - ShaAlgorithm::MD5 => 16, - ShaAlgorithm::SHA1 => 20, - ShaAlgorithm::SHA256 => 32, - ShaAlgorithm::SHA384 => 48, - ShaAlgorithm::SHA512 => 64, + HashAlgorithm::Md5 => 16, + HashAlgorithm::Sha1 => 20, + HashAlgorithm::Sha256 => 32, + HashAlgorithm::Sha384 => 48, + HashAlgorithm::Sha512 => 64, } } else { (length_bits / 8) as usize diff --git a/modules/llrt_crypto/src/provider/ring.rs b/modules/llrt_crypto/src/provider/ring.rs index fe71da2400..e1cbedbce9 100644 --- a/modules/llrt_crypto/src/provider/ring.rs +++ b/modules/llrt_crypto/src/provider/ring.rs @@ -1,8 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +use crate::hash::HashAlgorithm; use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; -use crate::sha_hash::ShaAlgorithm; use crate::subtle::EllipticCurve; use md5::{Digest, Md5 as Md5Hasher}; use ring::{digest, hmac}; @@ -147,33 +147,33 @@ impl CryptoProvider for RingProvider { type Digest = RingDigestType; type Hmac = RingHmacType; - fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { + fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { match algorithm { - ShaAlgorithm::MD5 => RingDigestType::Md5(RingMd5(Md5Hasher::new())), - ShaAlgorithm::SHA1 => { + HashAlgorithm::Md5 => RingDigestType::Md5(RingMd5(Md5Hasher::new())), + HashAlgorithm::Sha1 => { RingDigestType::Sha1(RingDigest::new(&digest::SHA1_FOR_LEGACY_USE_ONLY)) }, - ShaAlgorithm::SHA256 => RingDigestType::Sha256(RingDigest::new(&digest::SHA256)), - ShaAlgorithm::SHA384 => RingDigestType::Sha384(RingDigest::new(&digest::SHA384)), - ShaAlgorithm::SHA512 => RingDigestType::Sha512(RingDigest::new(&digest::SHA512)), + HashAlgorithm::Sha256 => RingDigestType::Sha256(RingDigest::new(&digest::SHA256)), + HashAlgorithm::Sha384 => RingDigestType::Sha384(RingDigest::new(&digest::SHA384)), + HashAlgorithm::Sha512 => RingDigestType::Sha512(RingDigest::new(&digest::SHA512)), } } - fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { match algorithm { - ShaAlgorithm::MD5 => { + HashAlgorithm::Md5 => { panic!("HMAC-MD5 not supported by Ring provider"); }, - ShaAlgorithm::SHA1 => RingHmacType::Sha1(RingHmacSha1(hmac::Context::with_key( + HashAlgorithm::Sha1 => RingHmacType::Sha1(RingHmacSha1(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, key), ))), - ShaAlgorithm::SHA256 => RingHmacType::Sha256(RingHmacSha256(hmac::Context::with_key( + HashAlgorithm::Sha256 => RingHmacType::Sha256(RingHmacSha256(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA256, key), ))), - ShaAlgorithm::SHA384 => RingHmacType::Sha384(RingHmacSha384(hmac::Context::with_key( + HashAlgorithm::Sha384 => RingHmacType::Sha384(RingHmacSha384(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA384, key), ))), - ShaAlgorithm::SHA512 => RingHmacType::Sha512(RingHmacSha512(hmac::Context::with_key( + HashAlgorithm::Sha512 => RingHmacType::Sha512(RingHmacSha512(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA512, key), ))), } @@ -216,7 +216,7 @@ impl CryptoProvider for RingProvider { _private_key_der: &[u8], _digest: &[u8], _salt_length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -227,7 +227,7 @@ impl CryptoProvider for RingProvider { _signature: &[u8], _digest: &[u8], _salt_length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } @@ -236,7 +236,7 @@ impl CryptoProvider for RingProvider { &self, _private_key_der: &[u8], _digest: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -246,7 +246,7 @@ impl CryptoProvider for RingProvider { _public_key_der: &[u8], _signature: &[u8], _digest: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } @@ -255,7 +255,7 @@ impl CryptoProvider for RingProvider { &self, _public_key_der: &[u8], _data: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) @@ -265,7 +265,7 @@ impl CryptoProvider for RingProvider { &self, _private_key_der: &[u8], _data: &[u8], - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) @@ -324,7 +324,7 @@ impl CryptoProvider for RingProvider { _salt: &[u8], _info: &[u8], _length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -335,7 +335,7 @@ impl CryptoProvider for RingProvider { _salt: &[u8], _iterations: u32, _length: usize, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } @@ -346,7 +346,7 @@ impl CryptoProvider for RingProvider { fn generate_hmac_key( &self, - _hash_alg: ShaAlgorithm, + _hash_alg: HashAlgorithm, _length_bits: u16, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) diff --git a/modules/llrt_crypto/src/subtle/aes_variants.rs b/modules/llrt_crypto/src/provider/rust/aes_variants.rs similarity index 100% rename from modules/llrt_crypto/src/subtle/aes_variants.rs rename to modules/llrt_crypto/src/provider/rust/aes_variants.rs diff --git a/modules/llrt_crypto/src/provider/rust.rs b/modules/llrt_crypto/src/provider/rust/mod.rs similarity index 95% rename from modules/llrt_crypto/src/provider/rust.rs rename to modules/llrt_crypto/src/provider/rust/mod.rs index 757bac1c07..f0f39ee8b9 100644 --- a/modules/llrt_crypto/src/provider/rust.rs +++ b/modules/llrt_crypto/src/provider/rust/mod.rs @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +mod aes_variants; + use std::num::NonZeroU32; use aes::cipher::{ @@ -54,12 +56,14 @@ use rsa::{ use sha1::Sha1; use crate::{ + hash::HashAlgorithm, provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}, random_byte_array, - sha_hash::ShaAlgorithm, - subtle::{AesGcmVariant, EllipticCurve}, + subtle::EllipticCurve, }; +use aes_variants::AesGcmVariant; + impl From for CryptoError { fn from(_: aes::cipher::InvalidLength) -> Self { CryptoError::InvalidLength @@ -141,27 +145,27 @@ impl CryptoProvider for RustCryptoProvider { type Digest = RustDigest; type Hmac = RustHmac; - fn digest(&self, algorithm: ShaAlgorithm) -> Self::Digest { + fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { match algorithm { - ShaAlgorithm::MD5 => RustDigest::Md5(md5::Md5::new()), - ShaAlgorithm::SHA1 => RustDigest::Sha1(Sha1::new()), - ShaAlgorithm::SHA256 => RustDigest::Sha256(Sha256::new()), - ShaAlgorithm::SHA384 => RustDigest::Sha384(Sha384::new()), - ShaAlgorithm::SHA512 => RustDigest::Sha512(Sha512::new()), + HashAlgorithm::Md5 => RustDigest::Md5(md5::Md5::new()), + HashAlgorithm::Sha1 => RustDigest::Sha1(Sha1::new()), + HashAlgorithm::Sha256 => RustDigest::Sha256(Sha256::new()), + HashAlgorithm::Sha384 => RustDigest::Sha384(Sha384::new()), + HashAlgorithm::Sha512 => RustDigest::Sha512(Sha512::new()), } } - fn hmac(&self, algorithm: ShaAlgorithm, key: &[u8]) -> Self::Hmac { + fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { match algorithm { - ShaAlgorithm::MD5 => panic!("HMAC-MD5 not supported"), - ShaAlgorithm::SHA1 => RustHmac::Sha1(HmacImpl::::new_from_slice(key).unwrap()), - ShaAlgorithm::SHA256 => { + HashAlgorithm::Md5 => panic!("HMAC-MD5 not supported"), + HashAlgorithm::Sha1 => RustHmac::Sha1(HmacImpl::::new_from_slice(key).unwrap()), + HashAlgorithm::Sha256 => { RustHmac::Sha256(HmacImpl::::new_from_slice(key).unwrap()) }, - ShaAlgorithm::SHA384 => { + HashAlgorithm::Sha384 => { RustHmac::Sha384(HmacImpl::::new_from_slice(key).unwrap()) }, - ShaAlgorithm::SHA512 => { + HashAlgorithm::Sha512 => { RustHmac::Sha512(HmacImpl::::new_from_slice(key).unwrap()) }, } @@ -258,20 +262,20 @@ impl CryptoProvider for RustCryptoProvider { private_key_der: &[u8], digest: &[u8], salt_length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let mut rng = rand::rng(); let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - ShaAlgorithm::SHA256 => private_key + HashAlgorithm::Sha256 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed(None)), - ShaAlgorithm::SHA384 => private_key + HashAlgorithm::Sha384 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed(None)), - ShaAlgorithm::SHA512 => private_key + HashAlgorithm::Sha512 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed(None)), _ => Err(CryptoError::UnsupportedAlgorithm), @@ -284,19 +288,19 @@ impl CryptoProvider for RustCryptoProvider { signature: &[u8], digest: &[u8], salt_length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result { let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - ShaAlgorithm::SHA256 => Ok(public_key + HashAlgorithm::Sha256 => Ok(public_key .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), - ShaAlgorithm::SHA384 => Ok(public_key + HashAlgorithm::Sha384 => Ok(public_key .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), - ShaAlgorithm::SHA512 => Ok(public_key + HashAlgorithm::Sha512 => Ok(public_key .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), _ => Err(CryptoError::UnsupportedAlgorithm), @@ -307,20 +311,20 @@ impl CryptoProvider for RustCryptoProvider { &self, private_key_der: &[u8], digest: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let mut rng = rand::rng(); let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - ShaAlgorithm::SHA256 => private_key + HashAlgorithm::Sha256 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) .map_err(|_| CryptoError::SigningFailed(None)), - ShaAlgorithm::SHA384 => private_key + HashAlgorithm::Sha384 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) .map_err(|_| CryptoError::SigningFailed(None)), - ShaAlgorithm::SHA512 => private_key + HashAlgorithm::Sha512 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) .map_err(|_| CryptoError::SigningFailed(None)), _ => Err(CryptoError::UnsupportedAlgorithm), @@ -332,19 +336,19 @@ impl CryptoProvider for RustCryptoProvider { public_key_der: &[u8], signature: &[u8], digest: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result { let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - ShaAlgorithm::SHA256 => Ok(public_key + HashAlgorithm::Sha256 => Ok(public_key .verify(Pkcs1v15Sign::new::(), digest, signature) .is_ok()), - ShaAlgorithm::SHA384 => Ok(public_key + HashAlgorithm::Sha384 => Ok(public_key .verify(Pkcs1v15Sign::new::(), digest, signature) .is_ok()), - ShaAlgorithm::SHA512 => Ok(public_key + HashAlgorithm::Sha512 => Ok(public_key .verify(Pkcs1v15Sign::new::(), digest, signature) .is_ok()), _ => Err(CryptoError::UnsupportedAlgorithm), @@ -355,7 +359,7 @@ impl CryptoProvider for RustCryptoProvider { &self, public_key_der: &[u8], data: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let mut rng = rand::rng(); @@ -363,7 +367,7 @@ impl CryptoProvider for RustCryptoProvider { .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - ShaAlgorithm::SHA1 => { + HashAlgorithm::Sha1 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -374,7 +378,7 @@ impl CryptoProvider for RustCryptoProvider { .encrypt(&mut rng, padding, data) .map_err(|_| CryptoError::EncryptionFailed(None)) }, - ShaAlgorithm::SHA256 => { + HashAlgorithm::Sha256 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -385,7 +389,7 @@ impl CryptoProvider for RustCryptoProvider { .encrypt(&mut rng, padding, data) .map_err(|_| CryptoError::EncryptionFailed(None)) }, - ShaAlgorithm::SHA384 => { + HashAlgorithm::Sha384 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -396,7 +400,7 @@ impl CryptoProvider for RustCryptoProvider { .encrypt(&mut rng, padding, data) .map_err(|_| CryptoError::EncryptionFailed(None)) }, - ShaAlgorithm::SHA512 => { + HashAlgorithm::Sha512 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -415,14 +419,14 @@ impl CryptoProvider for RustCryptoProvider { &self, private_key_der: &[u8], data: &[u8], - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { - ShaAlgorithm::SHA1 => { + HashAlgorithm::Sha1 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -433,7 +437,7 @@ impl CryptoProvider for RustCryptoProvider { .decrypt(padding, data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, - ShaAlgorithm::SHA256 => { + HashAlgorithm::Sha256 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -444,7 +448,7 @@ impl CryptoProvider for RustCryptoProvider { .decrypt(padding, data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, - ShaAlgorithm::SHA384 => { + HashAlgorithm::Sha384 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -455,7 +459,7 @@ impl CryptoProvider for RustCryptoProvider { .decrypt(padding, data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, - ShaAlgorithm::SHA512 => { + HashAlgorithm::Sha512 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { @@ -769,15 +773,15 @@ impl CryptoProvider for RustCryptoProvider { salt: &[u8], info: &[u8], length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { use ring::hkdf; let algorithm = match hash_alg { - ShaAlgorithm::SHA1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY, - ShaAlgorithm::SHA256 => hkdf::HKDF_SHA256, - ShaAlgorithm::SHA384 => hkdf::HKDF_SHA384, - ShaAlgorithm::SHA512 => hkdf::HKDF_SHA512, + HashAlgorithm::Sha1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY, + HashAlgorithm::Sha256 => hkdf::HKDF_SHA256, + HashAlgorithm::Sha384 => hkdf::HKDF_SHA384, + HashAlgorithm::Sha512 => hkdf::HKDF_SHA512, _ => return Err(CryptoError::UnsupportedAlgorithm), }; @@ -800,13 +804,13 @@ impl CryptoProvider for RustCryptoProvider { salt: &[u8], iterations: u32, length: usize, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let algorithm = match hash_alg { - ShaAlgorithm::SHA1 => pbkdf2::PBKDF2_HMAC_SHA1, - ShaAlgorithm::SHA256 => pbkdf2::PBKDF2_HMAC_SHA256, - ShaAlgorithm::SHA384 => pbkdf2::PBKDF2_HMAC_SHA384, - ShaAlgorithm::SHA512 => pbkdf2::PBKDF2_HMAC_SHA512, + HashAlgorithm::Sha1 => pbkdf2::PBKDF2_HMAC_SHA1, + HashAlgorithm::Sha256 => pbkdf2::PBKDF2_HMAC_SHA256, + HashAlgorithm::Sha384 => pbkdf2::PBKDF2_HMAC_SHA384, + HashAlgorithm::Sha512 => pbkdf2::PBKDF2_HMAC_SHA512, _ => return Err(CryptoError::UnsupportedAlgorithm), }; @@ -826,7 +830,7 @@ impl CryptoProvider for RustCryptoProvider { fn generate_hmac_key( &self, - hash_alg: ShaAlgorithm, + hash_alg: HashAlgorithm, length_bits: u16, ) -> Result, CryptoError> { let length_bytes = if length_bits == 0 { diff --git a/modules/llrt_crypto/src/sha_hash.rs b/modules/llrt_crypto/src/sha_hash.rs deleted file mode 100644 index 38cf2505d4..0000000000 --- a/modules/llrt_crypto/src/sha_hash.rs +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -use llrt_utils::{ - bytes::{bytes_to_typed_array, ObjectBytes}, - iterable_enum, - result::ResultExt, -}; -use rquickjs::{function::Opt, prelude::This, Class, Ctx, Exception, Result, Value}; - -use super::{encoded_bytes, CRYPTO_PROVIDER}; -use crate::provider::{CryptoProvider, DefaultProvider, HmacProvider, SimpleDigest}; - -type ProviderHmac = ::Hmac; -type ProviderDigest = ::Digest; - -#[rquickjs::class] -#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] -pub struct Hmac { - #[qjs(skip_trace)] - inner: Option, -} - -#[rquickjs::methods] -impl Hmac { - #[qjs(skip)] - pub fn new<'js>(ctx: Ctx<'js>, algorithm: String, key_value: ObjectBytes<'js>) -> Result { - let algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; - let key = key_value.as_bytes(&ctx)?; - let hmac = CRYPTO_PROVIDER.hmac(algorithm, key); - - Ok(Self { inner: Some(hmac) }) - } - - fn digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let hmac = self - .inner - .take() - .ok_or_else(|| Exception::throw_message(&ctx, "Digest already called"))?; - let result = hmac.finalize(); - - match encoding.into_inner() { - Some(encoding) => encoded_bytes(ctx, &result, &encoding), - None => bytes_to_typed_array(ctx, &result), - } - } - - fn update<'js>( - this: This>, - ctx: Ctx<'js>, - bytes: ObjectBytes<'js>, - ) -> Result> { - let bytes = bytes.as_bytes(&ctx)?; - let mut borrowed = this.0.borrow_mut(); - if let Some(ref mut hmac) = borrowed.inner { - hmac.update(bytes); - } - drop(borrowed); - Ok(this.0) - } -} - -#[rquickjs::class] -#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] -pub struct Hash { - #[qjs(skip_trace)] - inner: Option, -} - -#[rquickjs::methods] -impl Hash { - #[qjs(skip)] - pub fn new(ctx: Ctx<'_>, algorithm: String) -> Result { - let algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; - let digest = CRYPTO_PROVIDER.digest(algorithm); - - Ok(Self { - inner: Some(digest), - }) - } - - #[qjs(rename = "digest")] - fn hash_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let digest = self - .inner - .take() - .ok_or_else(|| Exception::throw_message(&ctx, "Digest already called"))?; - let result = digest.finalize(); - - match encoding.0 { - Some(encoding) => encoded_bytes(ctx, &result, &encoding), - None => bytes_to_typed_array(ctx, &result), - } - } - - #[qjs(rename = "update")] - fn hash_update<'js>( - this: This>, - ctx: Ctx<'js>, - bytes: ObjectBytes<'js>, - ) -> Result> { - let bytes = bytes.as_bytes(&ctx)?; - let mut borrowed = this.0.borrow_mut(); - if let Some(ref mut digest) = borrowed.inner { - digest.update(bytes); - } - drop(borrowed); - Ok(this.0) - } -} - -#[derive(Debug, Clone, Copy)] -pub enum ShaAlgorithm { - MD5, - SHA1, - SHA256, - SHA384, - SHA512, -} - -iterable_enum!(ShaAlgorithm, MD5, SHA1, SHA256, SHA384, SHA512); - -impl ShaAlgorithm { - pub fn class_name(&self) -> &'static str { - match self { - ShaAlgorithm::MD5 => "Md5", - ShaAlgorithm::SHA1 => "Sha1", - ShaAlgorithm::SHA256 => "Sha256", - ShaAlgorithm::SHA384 => "Sha384", - ShaAlgorithm::SHA512 => "Sha512", - } - } - - /// Returns the block size in bytes for this hash algorithm - pub fn block_len(&self) -> usize { - match self { - ShaAlgorithm::MD5 => 64, - ShaAlgorithm::SHA1 => 64, - ShaAlgorithm::SHA256 => 64, - ShaAlgorithm::SHA384 => 128, - ShaAlgorithm::SHA512 => 128, - } - } - - /// Returns the digest/output size in bytes for this hash algorithm - pub fn digest_len(&self) -> usize { - match self { - ShaAlgorithm::MD5 => 16, - ShaAlgorithm::SHA1 => 20, - ShaAlgorithm::SHA256 => 32, - ShaAlgorithm::SHA384 => 48, - ShaAlgorithm::SHA512 => 64, - } - } - - pub fn as_str(&self) -> &'static str { - match self { - ShaAlgorithm::MD5 => "MD5", - ShaAlgorithm::SHA1 => "SHA-1", - ShaAlgorithm::SHA256 => "SHA-256", - ShaAlgorithm::SHA384 => "SHA-384", - ShaAlgorithm::SHA512 => "SHA-512", - } - } - - pub fn as_numeric_str(&self) -> &'static str { - match self { - ShaAlgorithm::MD5 => "md5", - ShaAlgorithm::SHA1 => "1", - ShaAlgorithm::SHA256 => "256", - ShaAlgorithm::SHA384 => "384", - ShaAlgorithm::SHA512 => "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() - } -} diff --git a/modules/llrt_crypto/src/subtle/digest.rs b/modules/llrt_crypto/src/subtle/digest.rs index 2841f60600..eb71c1e3aa 100644 --- a/modules/llrt_crypto/src/subtle/digest.rs +++ b/modules/llrt_crypto/src/subtle/digest.rs @@ -4,8 +4,8 @@ use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt}; use rquickjs::{ArrayBuffer, Ctx, Result, Value}; use crate::{ + hash::HashAlgorithm, provider::{CryptoProvider, SimpleDigest}, - sha_hash::ShaAlgorithm, CRYPTO_PROVIDER, }; @@ -20,13 +20,13 @@ pub async fn subtle_digest<'js>( algorithm.get_required::<_, String>("name", "algorithm")? }; - let sha_algorithm = ShaAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; - let bytes = digest(&sha_algorithm, data.as_bytes(&ctx)?); + let hash_algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; + let bytes = digest(&hash_algorithm, data.as_bytes(&ctx)?); ArrayBuffer::new(ctx, bytes) } -pub fn digest(sha_algorithm: &ShaAlgorithm, data: &[u8]) -> Vec { - let mut hasher = CRYPTO_PROVIDER.digest(*sha_algorithm); +pub fn digest(hash_algorithm: &HashAlgorithm, data: &[u8]) -> Vec { + let mut hasher = CRYPTO_PROVIDER.digest(*hash_algorithm); hasher.update(data); hasher.finalize() } diff --git a/modules/llrt_crypto/src/subtle/generate_key.rs b/modules/llrt_crypto/src/subtle/generate_key.rs index 46973d4d32..8ace80585b 100644 --- a/modules/llrt_crypto/src/subtle/generate_key.rs +++ b/modules/llrt_crypto/src/subtle/generate_key.rs @@ -4,7 +4,7 @@ use rquickjs::{object::Property, Array, Class, Ctx, Exception, Object, Result, V use crate::{provider::CryptoProvider, CRYPTO_PROVIDER}; -use crate::{sha_hash::ShaAlgorithm, subtle::CryptoKey}; +use crate::{hash::HashAlgorithm, subtle::CryptoKey}; use super::{ algorithm_not_supported_error, @@ -120,7 +120,7 @@ fn generate_symmetric_key(_ctx: &Ctx<'_>, length: usize) -> Result> { } #[allow(dead_code)] -pub fn get_hash_length(ctx: &Ctx, hash: &ShaAlgorithm, length: u16) -> Result { +pub fn get_hash_length(ctx: &Ctx, hash: &HashAlgorithm, length: u16) -> Result { if length == 0 { return Ok(hash.block_len()); } diff --git a/modules/llrt_crypto/src/subtle/key_algorithm.rs b/modules/llrt_crypto/src/subtle/key_algorithm.rs index 212254d7e3..5a9f5373c4 100644 --- a/modules/llrt_crypto/src/subtle/key_algorithm.rs +++ b/modules/llrt_crypto/src/subtle/key_algorithm.rs @@ -17,7 +17,7 @@ use rquickjs::{ #[cfg(feature = "_subtle-full")] use spki::{AlgorithmIdentifier, ObjectIdentifier}; -use crate::sha_hash::ShaAlgorithm; +use crate::hash::HashAlgorithm; #[cfg(feature = "_subtle-full")] use super::algorithm_mismatch_error; @@ -176,12 +176,12 @@ impl KeyUsageAlgorithm { #[derive(Debug, Clone)] pub enum KeyDerivation { Hkdf { - hash: ShaAlgorithm, + hash: HashAlgorithm, salt: Box<[u8]>, info: Box<[u8]>, }, Pbkdf2 { - hash: ShaAlgorithm, + hash: HashAlgorithm, salt: Box<[u8]>, iterations: u32, }, @@ -239,13 +239,13 @@ pub enum KeyAlgorithm { X25519, Ed25519, Hmac { - hash: ShaAlgorithm, + hash: HashAlgorithm, length: u16, }, Rsa { modulus_length: u32, public_exponent: Rc>, - hash: ShaAlgorithm, + hash: HashAlgorithm, }, Derive(KeyDerivation), HkdfImport, @@ -311,7 +311,7 @@ impl KeyAlgorithm { value: Value<'js>, usages: Array<'js>, ) -> Result { - // When _rustcrypto is not enabled, Import mode is not supported + // When _subtle-full is not enabled, Import mode is not supported #[cfg(not(feature = "_subtle-full"))] if matches!(mode, KeyAlgorithmMode::Import { .. }) { return Err(Exception::throw_message( @@ -737,7 +737,7 @@ fn import_rsa_key<'js>( kind: &mut KeyKind, data: &mut Vec, algorithm_name: &str, - hash: &ShaAlgorithm, + hash: &HashAlgorithm, ) -> Result<(u32, Box<[u8]>)> { use crate::{ provider::{CryptoProvider, RsaJwkImport}, @@ -878,7 +878,7 @@ fn import_symmetric_key<'js>( kind: &mut KeyKind, data: &mut Vec, algorithm_name: &str, - hash: Option<&ShaAlgorithm>, + hash: Option<&HashAlgorithm>, ) -> Result { *kind = KeyKind::Secret; @@ -1120,7 +1120,7 @@ fn import_okp_key<'js>( Ok(()) } -pub fn extract_sha_hash<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> Result { +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() @@ -1132,17 +1132,17 @@ pub fn extract_sha_hash<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> Result(ctx: &Ctx<'js>, hash: &ShaAlgorithm) -> Result> { +fn create_hash_object<'js>(ctx: &Ctx<'js>, hash: &HashAlgorithm) -> Result> { let hash_obj = Object::new(ctx.clone())?; hash_obj.set(PredefinedAtom::Name, hash.as_str())?; Ok(hash_obj) } #[cfg(feature = "_subtle-full")] -pub fn hash_mismatch_error(ctx: &Ctx<'_>, hash: &ShaAlgorithm) -> Result { +pub fn hash_mismatch_error(ctx: &Ctx<'_>, hash: &HashAlgorithm) -> Result { Err(Exception::throw_message( ctx, &["Algorithm hash expected to be ", hash.as_str()].concat(), diff --git a/modules/llrt_crypto/src/subtle/mod.rs b/modules/llrt_crypto/src/subtle/mod.rs index ecaef81103..c04514bf9c 100644 --- a/modules/llrt_crypto/src/subtle/mod.rs +++ b/modules/llrt_crypto/src/subtle/mod.rs @@ -50,7 +50,7 @@ use rquickjs::{atom::PredefinedAtom, Ctx, Exception, Object, Result, Value}; use crate::provider::{CryptoProvider, SimpleDigest}; -use crate::sha_hash::ShaAlgorithm; +use crate::hash::HashAlgorithm; #[rquickjs::class] #[derive(rquickjs::JsLifetime, rquickjs::class::Trace)] @@ -69,12 +69,6 @@ impl SubtleCrypto { } } -// AES variant types - only available when _rustcrypto feature is enabled -#[cfg(feature = "_rustcrypto")] -mod aes_variants; -#[cfg(feature = "_rustcrypto")] -pub use aes_variants::*; - #[derive(Debug, Clone, Copy, PartialEq)] pub enum EllipticCurve { P256, @@ -95,14 +89,14 @@ pub fn rsa_hash_digest<'a>( key: &'a CryptoKey, data: &'a [u8], algorithm_name: &str, -) -> Result<(&'a ShaAlgorithm, Vec)> { +) -> Result<(&'a HashAlgorithm, Vec)> { let hash = match &key.algorithm { KeyAlgorithm::Rsa { hash, .. } => hash, _ => return algorithm_mismatch_error(ctx, algorithm_name), }; if !matches!( hash, - ShaAlgorithm::SHA256 | ShaAlgorithm::SHA384 | ShaAlgorithm::SHA512 + HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::Sha512 ) { return Err(Exception::throw_message( ctx, diff --git a/modules/llrt_crypto/src/subtle/sign.rs b/modules/llrt_crypto/src/subtle/sign.rs index c89cec8dcd..ed4541fcbd 100644 --- a/modules/llrt_crypto/src/subtle/sign.rs +++ b/modules/llrt_crypto/src/subtle/sign.rs @@ -87,7 +87,7 @@ fn sign( // sign_fn: F, // ) -> Result> // where -// F: FnOnce(&ShaAlgorithm, &[u8], &rsa::RsaPrivateKey) -> Result>, +// F: FnOnce(&HashAlgorithm, &[u8], &rsa::RsaPrivateKey) -> Result>, // { // let (hash, digest) = rsa_hash_digest(ctx, key, data, algorithm_name)?; diff --git a/modules/llrt_crypto/src/subtle/sign_algorithm.rs b/modules/llrt_crypto/src/subtle/sign_algorithm.rs index ef3d292aee..20fd13ff97 100644 --- a/modules/llrt_crypto/src/subtle/sign_algorithm.rs +++ b/modules/llrt_crypto/src/subtle/sign_algorithm.rs @@ -3,7 +3,7 @@ use llrt_utils::{object::ObjectExt, result::ResultExt}; use rquickjs::{Ctx, FromJs, Result, Value}; -use crate::sha_hash::ShaAlgorithm; +use crate::hash::HashAlgorithm; use super::{ algorithm_not_supported_error, key_algorithm::extract_sha_hash, to_name_and_maybe_object, @@ -11,7 +11,7 @@ use super::{ #[derive(Debug)] pub enum SigningAlgorithm { - Ecdsa { hash: ShaAlgorithm }, + Ecdsa { hash: HashAlgorithm }, Ed25519, RsaPss { salt_length: u32 }, RsassaPkcs1v15, From 1f0f5e7c3ec4ee4ee463bec0f96e67087174656b Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 3 Feb 2026 09:12:48 +0000 Subject: [PATCH 72/77] Use miniz --- libs/llrt_compression/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/llrt_compression/Cargo.toml b/libs/llrt_compression/Cargo.toml index 953c23c7a5..2a79ba0b96 100644 --- a/libs/llrt_compression/Cargo.toml +++ b/libs/llrt_compression/Cargo.toml @@ -20,7 +20,7 @@ brotli-c = ["brotlic"] brotli-rust = ["brotli"] flate2-c = ["flate2/zlib-ng"] -flate2-rust = ["flate2/rust_backend"] +flate2-rust = ["flate2/miniz_oxide"] zstd-c = ["zstd"] zstd-rust = ["zstd"] # No pure rust implementation exists From 198c1a894a329096a4357ee37210c533aebd826d Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 3 Feb 2026 09:14:16 +0000 Subject: [PATCH 73/77] Refactor --- modules/llrt_crypto/src/subtle/crypto_key.rs | 2 +- .../llrt_crypto/src/subtle/key_algorithm.rs | 763 ++++++++++++------ 2 files changed, 506 insertions(+), 259 deletions(-) diff --git a/modules/llrt_crypto/src/subtle/crypto_key.rs b/modules/llrt_crypto/src/subtle/crypto_key.rs index 1d47999c5a..67d81d5c18 100644 --- a/modules/llrt_crypto/src/subtle/crypto_key.rs +++ b/modules/llrt_crypto/src/subtle/crypto_key.rs @@ -11,7 +11,7 @@ use rquickjs::{ use super::key_algorithm::KeyAlgorithm; -#[derive(PartialEq)] +#[derive(PartialEq, Clone, Copy)] pub enum KeyKind { Secret, Private, diff --git a/modules/llrt_crypto/src/subtle/key_algorithm.rs b/modules/llrt_crypto/src/subtle/key_algorithm.rs index 5a9f5373c4..ede85f8db8 100644 --- a/modules/llrt_crypto/src/subtle/key_algorithm.rs +++ b/modules/llrt_crypto/src/subtle/key_algorithm.rs @@ -304,6 +304,450 @@ pub struct KeyAlgorithmWithUsages { pub private_usages: Vec, } +fn from_ed25519<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + algorithm_name: &str, + usages: &Array<'js>, + private_usages: &mut Vec, + public_usages: &mut Vec, +) -> Result { + #[cfg(feature = "_subtle-full")] + #[inline] + fn import<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + algorithm_name: &str, + ) -> Result> { + if let KeyAlgorithmMode::Import { format, kind, data } = mode { + import_okp_key( + ctx, + format, + kind, + data, + const_oid::db::rfc8410::ID_ED_25519, + algorithm_name, + )?; + Ok(Some(*kind)) + } else { + Ok(None) + } + } + + #[cfg(not(feature = "_subtle-full"))] + #[inline] + fn import<'js>( + _ctx: &Ctx<'js>, + _mode: KeyAlgorithmMode<'_, 'js>, + _algorithm_name: &str, + ) -> Result> { + Ok(None) + } + + let key_kind = import(ctx, mode, algorithm_name)?; + KeyUsage::classify_and_check_usages( + ctx, + KeyUsageAlgorithm::Sign, + usages, + private_usages, + public_usages, + key_kind.as_ref(), + )?; + Ok(KeyAlgorithm::Ed25519) +} + +fn from_x25519<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + algorithm_name: &str, + usages: &Array<'js>, + private_usages: &mut Vec, + public_usages: &mut Vec, +) -> Result { + #[cfg(feature = "_subtle-full")] + #[inline] + fn import<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + algorithm_name: &str, + ) -> Result> { + if let KeyAlgorithmMode::Import { format, kind, data } = mode { + import_okp_key( + ctx, + format, + kind, + data, + const_oid::db::rfc8410::ID_X_25519, + algorithm_name, + )?; + Ok(Some(*kind)) + } else { + Ok(None) + } + } + + #[cfg(not(feature = "_subtle-full"))] + #[inline] + fn import<'js>( + _ctx: &Ctx<'js>, + _mode: KeyAlgorithmMode<'_, 'js>, + _algorithm_name: &str, + ) -> Result> { + Ok(None) + } + + let key_kind = import(ctx, mode, algorithm_name)?; + KeyUsage::classify_and_check_usages( + ctx, + KeyUsageAlgorithm::Derive, + usages, + private_usages, + public_usages, + key_kind.as_ref(), + )?; + Ok(KeyAlgorithm::X25519) +} + +fn from_aes<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + obj: std::result::Result, &str>, + algorithm_name: &str, + usages: &Array<'js>, + private_usages: &mut Vec, + public_usages: &mut Vec, +) -> Result { + #[cfg(feature = "_subtle-full")] + #[inline] + fn import<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + obj: std::result::Result, &str>, + algorithm_name: &str, + ) -> Result<(u16, Option)> { + if let KeyAlgorithmMode::Import { data, format, kind } = mode { + let length = + import_symmetric_key(ctx, format, kind, data, algorithm_name, None)? as u16; + Ok((length, Some(*kind))) + } else { + let length: u16 = obj.or_throw(ctx)?.get_required("length", "algorithm")?; + Ok((length, None)) + } + } + + #[cfg(not(feature = "_subtle-full"))] + #[inline] + fn import<'js>( + ctx: &Ctx<'js>, + _mode: KeyAlgorithmMode<'_, 'js>, + obj: std::result::Result, &str>, + _algorithm_name: &str, + ) -> Result<(u16, Option)> { + let length: u16 = obj.or_throw(ctx)?.get_required("length", "algorithm")?; + Ok((length, None)) + } + + let (length, key_kind) = import(ctx, mode, obj, algorithm_name)?; + + if !matches!(length, 128 | 192 | 256) { + return Err(Exception::throw_message( + ctx, + &format!( + "Algorithm 'length' must be one of: 128, 192, or 256 = {}", + length + ), + )); + } + + KeyUsage::classify_and_check_usages( + ctx, + if algorithm_name == "AES-KW" { + KeyUsageAlgorithm::AesKw + } else { + KeyUsageAlgorithm::Symmetric + }, + usages, + private_usages, + public_usages, + key_kind.as_ref(), + )?; + + Ok(KeyAlgorithm::Aes { length }) +} + +fn from_hmac<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + obj: std::result::Result, &str>, + algorithm_name: &str, + usages: &Array<'js>, + private_usages: &mut Vec, + public_usages: &mut Vec, +) -> Result { + let obj = obj.or_throw(ctx)?; + let hash = extract_sha_hash(ctx, &obj)?; + let mut length: u16 = obj.get_optional("length")?.unwrap_or_default(); + + #[cfg(feature = "_subtle-full")] + #[inline] + fn import<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + algorithm_name: &str, + hash: &HashAlgorithm, + length: &mut u16, + ) -> Result> { + if let KeyAlgorithmMode::Import { data, format, kind } = mode { + let data_length = + import_symmetric_key(ctx, format, kind, data, algorithm_name, Some(hash))?; + if *length == 0 { + *length = data_length as u16; + } + Ok(Some(*kind)) + } else { + Ok(None) + } + } + + #[cfg(not(feature = "_subtle-full"))] + #[inline] + fn import<'js>( + _ctx: &Ctx<'js>, + _mode: KeyAlgorithmMode<'_, 'js>, + _algorithm_name: &str, + _hash: &HashAlgorithm, + _length: &mut u16, + ) -> Result> { + Ok(None) + } + + let key_kind = import(ctx, mode, algorithm_name, &hash, &mut length)?; + + KeyUsage::classify_and_check_usages( + ctx, + KeyUsageAlgorithm::Hmac, + usages, + private_usages, + public_usages, + key_kind.as_ref(), + )?; + + Ok(KeyAlgorithm::Hmac { hash, length }) +} + +fn from_rsa<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + obj: std::result::Result, &str>, + algorithm_name: &str, + usages: &Array<'js>, + private_usages: &mut Vec, + public_usages: &mut Vec, +) -> Result { + let obj = obj.or_throw(ctx)?; + let hash = extract_sha_hash(ctx, &obj)?; + + #[cfg(feature = "_subtle-full")] + #[inline] + fn import<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + obj: &Object<'js>, + algorithm_name: &str, + hash: &HashAlgorithm, + ) -> Result<(u32, Box<[u8]>, Option)> { + if let KeyAlgorithmMode::Import { format, kind, data } = mode { + let (mod_length, exp) = import_rsa_key(ctx, format, kind, data, algorithm_name, hash)?; + Ok((mod_length, exp, Some(*kind))) + } else { + let modulus_length = obj.get_required("modulusLength", "algorithm")?; + let public_exponent: TypedArray = + obj.get_required("publicExponent", "algorithm")?; + let public_exponent = public_exponent + .as_bytes() + .ok_or_else(|| Exception::throw_message(ctx, "Array buffer has been detached"))? + .to_owned() + .into_boxed_slice(); + Ok((modulus_length, public_exponent, None)) + } + } + + #[cfg(not(feature = "_subtle-full"))] + #[inline] + fn import<'js>( + ctx: &Ctx<'js>, + _mode: KeyAlgorithmMode<'_, 'js>, + obj: &Object<'js>, + _algorithm_name: &str, + _hash: &HashAlgorithm, + ) -> Result<(u32, Box<[u8]>, Option)> { + let modulus_length = obj.get_required("modulusLength", "algorithm")?; + let public_exponent: TypedArray = obj.get_required("publicExponent", "algorithm")?; + let public_exponent = public_exponent + .as_bytes() + .ok_or_else(|| Exception::throw_message(ctx, "Array buffer has been detached"))? + .to_owned() + .into_boxed_slice(); + Ok((modulus_length, public_exponent, None)) + } + + let (modulus_length, public_exponent, key_kind) = + import(ctx, mode, &obj, algorithm_name, &hash)?; + + KeyUsage::classify_and_check_usages( + ctx, + if algorithm_name == "RSA-OAEP" { + KeyUsageAlgorithm::RsaOaep + } else { + KeyUsageAlgorithm::Sign + }, + usages, + private_usages, + public_usages, + key_kind.as_ref(), + )?; + + Ok(KeyAlgorithm::Rsa { + modulus_length, + public_exponent: Rc::new(public_exponent), + hash, + }) +} + +fn from_hkdf<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + obj: std::result::Result, &str>, + algorithm_name: &str, + usages: &Array<'js>, + private_usages: &mut Vec, + public_usages: &mut Vec, +) -> Result { + #[cfg(feature = "_subtle-full")] + #[inline] + fn import<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + obj: std::result::Result, &str>, + algorithm_name: &str, + ) -> Result<(KeyAlgorithm, Option)> { + match mode { + KeyAlgorithmMode::Import { format, kind, data } => { + import_derive_key(ctx, format, kind, data, algorithm_name)?; + Ok((KeyAlgorithm::HkdfImport, Some(*kind))) + }, + KeyAlgorithmMode::Derive => { + let obj = obj.or_throw(ctx)?; + Ok(( + KeyAlgorithm::Derive(KeyDerivation::for_hkdf_object(ctx, obj)?), + None, + )) + }, + _ => algorithm_not_supported_error(ctx), + } + } + + #[cfg(not(feature = "_subtle-full"))] + #[inline] + fn import<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + obj: std::result::Result, &str>, + _algorithm_name: &str, + ) -> Result<(KeyAlgorithm, Option)> { + match mode { + KeyAlgorithmMode::Derive => { + let obj = obj.or_throw(ctx)?; + Ok(( + KeyAlgorithm::Derive(KeyDerivation::for_hkdf_object(ctx, obj)?), + None, + )) + }, + _ => algorithm_not_supported_error(ctx), + } + } + + let (algorithm, key_kind) = import(ctx, mode, obj, algorithm_name)?; + + KeyUsage::classify_and_check_usages( + ctx, + KeyUsageAlgorithm::Derive, + usages, + private_usages, + public_usages, + key_kind.as_ref(), + )?; + + Ok(algorithm) +} + +fn from_pbkdf2<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + obj: std::result::Result, &str>, + algorithm_name: &str, + usages: &Array<'js>, + private_usages: &mut Vec, + public_usages: &mut Vec, +) -> Result { + #[cfg(feature = "_subtle-full")] + #[inline] + fn import<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + obj: std::result::Result, &str>, + algorithm_name: &str, + ) -> Result<(KeyAlgorithm, Option)> { + match mode { + KeyAlgorithmMode::Import { format, kind, data } => { + import_derive_key(ctx, format, kind, data, algorithm_name)?; + Ok((KeyAlgorithm::Pbkdf2Import, Some(*kind))) + }, + KeyAlgorithmMode::Derive => { + let obj = obj.or_throw(ctx)?; + Ok(( + KeyAlgorithm::Derive(KeyDerivation::for_pbkf2_object(&ctx, obj)?), + None, + )) + }, + _ => algorithm_not_supported_error(ctx), + } + } + + #[cfg(not(feature = "_subtle-full"))] + #[inline] + fn import<'js>( + ctx: &Ctx<'js>, + mode: KeyAlgorithmMode<'_, 'js>, + obj: std::result::Result, &str>, + _algorithm_name: &str, + ) -> Result<(KeyAlgorithm, Option)> { + match mode { + KeyAlgorithmMode::Derive => { + let obj = obj.or_throw(ctx)?; + Ok(( + KeyAlgorithm::Derive(KeyDerivation::for_pbkf2_object(&ctx, obj)?), + None, + )) + }, + _ => algorithm_not_supported_error(ctx), + } + } + + let (algorithm, key_kind) = import(ctx, mode, obj, algorithm_name)?; + + KeyUsage::classify_and_check_usages( + ctx, + KeyUsageAlgorithm::Derive, + usages, + private_usages, + public_usages, + key_kind.as_ref(), + )?; + + Ok(algorithm) +} + impl KeyAlgorithm { pub fn from_js<'js>( ctx: &Ctx<'js>, @@ -325,107 +769,31 @@ impl KeyAlgorithm { let mut private_usages = vec![]; let algorithm_name = name.as_str(); let algorithm = match algorithm_name { - "Ed25519" => { - #[cfg(feature = "_subtle-full")] - let key_kind = if let KeyAlgorithmMode::Import { format, kind, data } = mode { - import_okp_key( - ctx, - format, - kind, - data, - const_oid::db::rfc8410::ID_ED_25519, - algorithm_name, - )?; - Some(kind) - } else { - None - }; - #[cfg(not(feature = "_subtle-full"))] - let key_kind: Option<&KeyKind> = None; - - KeyUsage::classify_and_check_usages( - ctx, - KeyUsageAlgorithm::Sign, - &usages, - &mut private_usages, - &mut public_usages, - key_kind.as_deref(), - )?; - KeyAlgorithm::Ed25519 - }, - "X25519" => { - #[cfg(feature = "_subtle-full")] - let key_kind = if let KeyAlgorithmMode::Import { format, kind, data } = mode { - import_okp_key( - ctx, - format, - kind, - data, - const_oid::db::rfc8410::ID_X_25519, - algorithm_name, - )?; - Some(kind) - } else { - None - }; - #[cfg(not(feature = "_subtle-full"))] - let key_kind: Option<&KeyKind> = None; - - KeyUsage::classify_and_check_usages( - ctx, - KeyUsageAlgorithm::Derive, - &usages, - &mut private_usages, - &mut public_usages, - key_kind.as_deref(), - )?; - KeyAlgorithm::X25519 - }, - "AES-CBC" | "AES-CTR" | "AES-GCM" | "AES-KW" => { - #[cfg(feature = "_subtle-full")] - let (length, key_kind) = { - let mut key_kind = None; - let length = if let KeyAlgorithmMode::Import { data, format, kind } = mode { - let l = - import_symmetric_key(ctx, format, kind, data, algorithm_name, None)?; - key_kind = Some(kind); - l - } else { - obj.or_throw(ctx)?.get_required("length", "algorithm")? - } as u16; - (length, key_kind) - }; - #[cfg(not(feature = "_subtle-full"))] - let (length, key_kind): (u16, Option<&KeyKind>) = { - let length: u16 = obj.or_throw(ctx)?.get_required("length", "algorithm")?; - (length, None) - }; - - if !matches!(length, 128 | 192 | 256) { - return Err(Exception::throw_message( - ctx, - &format!( - "Algorithm 'length' must be one of: 128, 192, or 256 = {}", - length - ), - )); - } - - KeyUsage::classify_and_check_usages( - ctx, - if name == "AES-KW" { - KeyUsageAlgorithm::AesKw - } else { - KeyUsageAlgorithm::Symmetric - }, - &usages, - &mut private_usages, - &mut public_usages, - key_kind.as_deref(), - )?; - - KeyAlgorithm::Aes { length } - }, + "Ed25519" => from_ed25519( + ctx, + mode, + algorithm_name, + &usages, + &mut private_usages, + &mut public_usages, + )?, + "X25519" => from_x25519( + ctx, + mode, + algorithm_name, + &usages, + &mut private_usages, + &mut public_usages, + )?, + "AES-CBC" | "AES-CTR" | "AES-GCM" | "AES-KW" => from_aes( + ctx, + mode, + obj, + algorithm_name, + &usages, + &mut private_usages, + &mut public_usages, + )?, "ECDH" => Self::from_ec( ctx, mode, @@ -437,7 +805,6 @@ impl KeyAlgorithm { &mut public_usages, KeyUsageAlgorithm::Derive, )?, - "ECDSA" => Self::from_ec( ctx, mode, @@ -449,162 +816,42 @@ impl KeyAlgorithm { &mut public_usages, KeyUsageAlgorithm::Sign, )?, - "HMAC" => { - let obj = obj.or_throw(ctx)?; - let hash = extract_sha_hash(ctx, &obj)?; - - #[cfg(feature = "_subtle-full")] - let mut length: u16 = obj.get_optional("length")?.unwrap_or_default(); - #[cfg(not(feature = "_subtle-full"))] - let length: u16 = obj.get_optional("length")?.unwrap_or_default(); - - #[cfg(feature = "_subtle-full")] - let key_kind = if let KeyAlgorithmMode::Import { data, format, kind } = mode { - let data_length = - import_symmetric_key(ctx, format, kind, data, algorithm_name, Some(&hash))?; - if length == 0 { - length = data_length as u16 - } - Some(kind) - } else { - None - }; - #[cfg(not(feature = "_subtle-full"))] - let key_kind: Option<&KeyKind> = None; - - KeyUsage::classify_and_check_usages( - ctx, - KeyUsageAlgorithm::Hmac, - &usages, - &mut private_usages, - &mut public_usages, - key_kind.as_deref(), - )?; - - KeyAlgorithm::Hmac { hash, length } - }, - "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-v1_5" => { - let obj = obj.or_throw(ctx)?; - let hash = extract_sha_hash(ctx, &obj)?; - - #[cfg(feature = "_subtle-full")] - let (modulus_length, public_exponent, key_kind) = - if let KeyAlgorithmMode::Import { format, kind, data } = mode { - let (mod_length, exp) = - import_rsa_key(ctx, format, kind, data, algorithm_name, &hash)?; - (mod_length, exp, Some(kind)) - } else { - let modulus_length = obj.get_required("modulusLength", "algorithm")?; - let public_exponent: TypedArray = - obj.get_required("publicExponent", "algorithm")?; - let public_exponent = public_exponent - .as_bytes() - .ok_or_else(|| { - Exception::throw_message(ctx, "Array buffer has been detached") - })? - .to_owned() - .into_boxed_slice(); - (modulus_length, public_exponent, None) - }; - - #[cfg(not(feature = "_subtle-full"))] - let (modulus_length, public_exponent, key_kind): ( - u32, - Box<[u8]>, - Option<&KeyKind>, - ) = { - let modulus_length = obj.get_required("modulusLength", "algorithm")?; - let public_exponent: TypedArray = - obj.get_required("publicExponent", "algorithm")?; - let public_exponent = public_exponent - .as_bytes() - .ok_or_else(|| { - Exception::throw_message(ctx, "Array buffer has been detached") - })? - .to_owned() - .into_boxed_slice(); - (modulus_length, public_exponent, None) - }; - - KeyUsage::classify_and_check_usages( - ctx, - if name == "RSA-OAEP" { - KeyUsageAlgorithm::RsaOaep - } else { - KeyUsageAlgorithm::Sign - }, - &usages, - &mut private_usages, - &mut public_usages, - key_kind.as_deref(), - )?; - - let public_exponent = Rc::new(public_exponent); - - KeyAlgorithm::Rsa { - modulus_length, - public_exponent, - hash, - } - }, - "HKDF" => { - let (algorithm, key_kind): (KeyAlgorithm, Option<&mut KeyKind>) = match mode { - #[cfg(feature = "_subtle-full")] - KeyAlgorithmMode::Import { format, kind, data } => { - import_derive_key(ctx, format, kind, data, algorithm_name)?; - - (KeyAlgorithm::HkdfImport, Some(kind)) - }, - KeyAlgorithmMode::Derive => { - let obj = obj.or_throw(ctx)?; - ( - KeyAlgorithm::Derive(KeyDerivation::for_hkdf_object(ctx, obj)?), - None, - ) - }, - _ => { - return algorithm_not_supported_error(ctx); - }, - }; - KeyUsage::classify_and_check_usages( - ctx, - KeyUsageAlgorithm::Derive, - &usages, - &mut private_usages, - &mut public_usages, - key_kind.as_deref(), - )?; - algorithm - }, - - "PBKDF2" => { - let (algorithm, key_kind): (KeyAlgorithm, Option<&mut KeyKind>) = match mode { - #[cfg(feature = "_subtle-full")] - KeyAlgorithmMode::Import { format, kind, data } => { - import_derive_key(ctx, format, kind, data, algorithm_name)?; - (KeyAlgorithm::Pbkdf2Import, Some(kind)) - }, - KeyAlgorithmMode::Derive => { - let obj = obj.or_throw(ctx)?; - ( - KeyAlgorithm::Derive(KeyDerivation::for_pbkf2_object(&ctx, obj)?), - None, - ) - }, - _ => { - return algorithm_not_supported_error(ctx); - }, - }; - KeyUsage::classify_and_check_usages( - ctx, - KeyUsageAlgorithm::Derive, - &usages, - &mut private_usages, - &mut public_usages, - key_kind.as_deref(), - )?; - algorithm - }, + "HMAC" => from_hmac( + ctx, + mode, + obj, + algorithm_name, + &usages, + &mut private_usages, + &mut public_usages, + )?, + "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-v1_5" => from_rsa( + ctx, + mode, + obj, + algorithm_name, + &usages, + &mut private_usages, + &mut public_usages, + )?, + "HKDF" => from_hkdf( + ctx, + mode, + obj, + algorithm_name, + &usages, + &mut private_usages, + &mut public_usages, + )?, + "PBKDF2" => from_pbkdf2( + ctx, + mode, + obj, + algorithm_name, + &usages, + &mut private_usages, + &mut public_usages, + )?, _ => return algorithm_not_supported_error(ctx), }; From 9e8dd8686d8bd4db386fcb5692d9a470035b2f20 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 3 Feb 2026 10:01:57 +0000 Subject: [PATCH 74/77] Move gate to lib --- modules/llrt_http/src/client.rs | 45 +++------------------------------ modules/llrt_http/src/lib.rs | 6 +++++ 2 files changed, 9 insertions(+), 42 deletions(-) diff --git a/modules/llrt_http/src/client.rs b/modules/llrt_http/src/client.rs index 50e4cb4527..d60a2727c6 100644 --- a/modules/llrt_http/src/client.rs +++ b/modules/llrt_http/src/client.rs @@ -1,56 +1,14 @@ -#[cfg(any( - feature = "tls-ring", - feature = "tls-aws-lc", - feature = "tls-graviola", - feature = "tls-openssl" -))] use std::convert::Infallible; -#[cfg(any( - feature = "tls-ring", - feature = "tls-aws-lc", - feature = "tls-graviola", - feature = "tls-openssl" -))] use bytes::Bytes; -#[cfg(any( - feature = "tls-ring", - feature = "tls-aws-lc", - feature = "tls-graviola", - feature = "tls-openssl" -))] use http_body_util::combinators::BoxBody; -#[cfg(any( - feature = "tls-ring", - feature = "tls-aws-lc", - feature = "tls-graviola", - feature = "tls-openssl" -))] use hyper_util::{ client::legacy::{connect::HttpConnector, Client}, rt::{TokioExecutor, TokioTimer}, }; -#[cfg(any( - feature = "tls-ring", - feature = "tls-aws-lc", - feature = "tls-graviola", - feature = "tls-openssl" -))] use llrt_dns_cache::CachedDnsResolver; -#[cfg(any( - feature = "tls-ring", - feature = "tls-aws-lc", - feature = "tls-graviola", - feature = "tls-openssl" -))] use once_cell::sync::Lazy; -#[cfg(any( - feature = "tls-ring", - feature = "tls-aws-lc", - feature = "tls-graviola", - feature = "tls-openssl" -))] use crate::get_pool_idle_timeout; #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] @@ -148,7 +106,10 @@ mod openssl_client { let mut https = HttpsConnector::with_connector(cache_dns_connector, connector)?; https.set_callback(|ssl, _| { + #[cfg(feature = "http2")] ssl.set_alpn_protos(b"\x02h2\x08http/1.1")?; + #[cfg(not(feature = "http2"))] + ssl.set_alpn_protos(b"\x08http/1.1")?; Ok(()) }); diff --git a/modules/llrt_http/src/lib.rs b/modules/llrt_http/src/lib.rs index c276878f03..9f2b5703e4 100644 --- a/modules/llrt_http/src/lib.rs +++ b/modules/llrt_http/src/lib.rs @@ -8,6 +8,12 @@ use rquickjs::{ pub use self::config::*; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] mod client; mod config; From 7fbf1a5409957713ddb30f715c4d0cbc7d1f1ac4 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 3 Feb 2026 10:08:10 +0000 Subject: [PATCH 75/77] Cleanup test feature --- modules/llrt_fetch/Cargo.toml | 15 +++++---------- modules/llrt_fetch/src/fetch.rs | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/modules/llrt_fetch/Cargo.toml b/modules/llrt_fetch/Cargo.toml index 307db18b43..47299fb9c5 100644 --- a/modules/llrt_fetch/Cargo.toml +++ b/modules/llrt_fetch/Cargo.toml @@ -23,15 +23,10 @@ compression-rust = ["llrt_compression/all-rust"] webpki-roots = ["llrt_http/webpki-roots"] native-roots = ["llrt_http/native-roots"] -tls-ring = ["llrt_http/tls-ring"] -tls-aws-lc = ["llrt_http/tls-aws-lc"] -tls-graviola = ["llrt_http/tls-graviola"] -tls-openssl = ["llrt_http/tls-openssl"] - -# Internal testing features -_test-tls-ring = ["tls-ring", "dep:llrt_test_tls", "llrt_test_tls/tls-ring"] -_test-tls-aws-lc = ["tls-aws-lc", "dep:llrt_test_tls", "llrt_test_tls/tls-aws-lc"] -_test-tls-graviola = ["tls-graviola", "dep:llrt_test_tls", "llrt_test_tls/tls-graviola"] +tls-ring = ["llrt_http/tls-ring", "llrt_test_tls/tls-ring"] +tls-aws-lc = ["llrt_http/tls-aws-lc", "llrt_test_tls/tls-aws-lc"] +tls-graviola = ["llrt_http/tls-graviola", "llrt_test_tls/tls-graviola"] +tls-openssl = ["llrt_http/tls-openssl", "llrt_test_tls/tls-openssl"] [dependencies] bytes = { version = "1", default-features = false } @@ -46,7 +41,6 @@ llrt_context = { version = "0.7.0-beta", path = "../../libs/llrt_context" } llrt_encoding = { version = "0.7.0-beta", path = "../../libs/llrt_encoding" } llrt_http = { version = "0.7.0-beta", default-features = false, path = "../llrt_http" } llrt_json = { version = "0.7.0-beta", path = "../../libs/llrt_json" } -llrt_test_tls = { path = "../../libs/llrt_test_tls", default-features = false, optional = true } llrt_url = { version = "0.7.0-beta", path = "../llrt_url" } llrt_utils = { version = "0.7.0-beta", path = "../../libs/llrt_utils", default-features = false } once_cell = { version = "1", features = ["std"], default-features = false } @@ -71,4 +65,5 @@ tracing = { version = "0.1", default-features = false } [dev-dependencies] llrt_compression = { version = "0.7.0-beta", path = "../../libs/llrt_compression" } llrt_test = { path = "../../libs/llrt_test" } +llrt_test_tls = { path = "../../libs/llrt_test_tls", default-features = false } wiremock = { version = "0.6", default-features = false } diff --git a/modules/llrt_fetch/src/fetch.rs b/modules/llrt_fetch/src/fetch.rs index e03536347f..159505b922 100644 --- a/modules/llrt_fetch/src/fetch.rs +++ b/modules/llrt_fetch/src/fetch.rs @@ -484,16 +484,16 @@ mod tests { use std::io::Read; #[cfg(any( - feature = "_test-tls-ring", - feature = "_test-tls-aws-lc", - all(feature = "_test-tls-graviola", target_arch = "x86_64") + feature = "tls-ring", + feature = "tls-aws-lc", + all(feature = "tls-graviola", target_arch = "x86_64") ))] use llrt_http::HttpsModule; use llrt_test::test_async_with; #[cfg(any( - feature = "_test-tls-ring", - feature = "_test-tls-aws-lc", - all(feature = "_test-tls-graviola", target_arch = "x86_64") + feature = "tls-ring", + feature = "tls-aws-lc", + all(feature = "tls-graviola", target_arch = "x86_64") ))] use llrt_test::{call_test, ModuleEvaluator}; use rquickjs::{prelude::Promise, CatchResultExt}; @@ -875,9 +875,9 @@ mod tests { } #[cfg(any( - feature = "_test-tls-ring", - feature = "_test-tls-aws-lc", - all(feature = "_test-tls-graviola", target_arch = "x86_64") + feature = "tls-ring", + feature = "tls-aws-lc", + all(feature = "tls-graviola", target_arch = "x86_64") ))] #[tokio::test] async fn test_fetch_tls() { @@ -922,9 +922,9 @@ mod tests { } #[cfg(any( - feature = "_test-tls-ring", - feature = "_test-tls-aws-lc", - all(feature = "_test-tls-graviola", target_arch = "x86_64") + feature = "tls-ring", + feature = "tls-aws-lc", + all(feature = "tls-graviola", target_arch = "x86_64") ))] #[tokio::test] async fn test_fetch_ignore_certificate_errors() { From 8c992fe373cac65ca7b36f3200b6ae80cc4188b0 Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 3 Feb 2026 10:12:57 +0000 Subject: [PATCH 76/77] Use cow and avoid clone data --- modules/llrt_crypto/src/subtle/encryption.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/llrt_crypto/src/subtle/encryption.rs b/modules/llrt_crypto/src/subtle/encryption.rs index b1014d83fb..bb3ca6b9af 100644 --- a/modules/llrt_crypto/src/subtle/encryption.rs +++ b/modules/llrt_crypto/src/subtle/encryption.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; @@ -164,14 +166,13 @@ pub fn encrypt_decrypt( match operation { EncryptionOperation::Encrypt => { // Pad data to multiple of 8 bytes if needed - let padded_data = if !data.len().is_multiple_of(8) { + let mut padded_data = Cow::Borrowed(data); + if !data.len().is_multiple_of(8) { let pad_len = 8 - (data.len() % 8); let mut padded = data.to_vec(); padded.extend(std::iter::repeat_n(padding, pad_len)); - padded - } else { - data.to_vec() - }; + padded_data = Cow::Owned(padded) + } CRYPTO_PROVIDER .aes_kw_wrap(handle, &padded_data) .or_throw(ctx)? From 1d4e5a28a3ac58634ebef3e0424c21fc3d423dda Mon Sep 17 00:00:00 2001 From: Richard Davison Date: Tue, 3 Feb 2026 10:16:29 +0000 Subject: [PATCH 77/77] Correct names --- libs/llrt_compression/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/llrt_compression/Cargo.toml b/libs/llrt_compression/Cargo.toml index 2a79ba0b96..953c23c7a5 100644 --- a/libs/llrt_compression/Cargo.toml +++ b/libs/llrt_compression/Cargo.toml @@ -20,7 +20,7 @@ brotli-c = ["brotlic"] brotli-rust = ["brotli"] flate2-c = ["flate2/zlib-ng"] -flate2-rust = ["flate2/miniz_oxide"] +flate2-rust = ["flate2/rust_backend"] zstd-c = ["zstd"] zstd-rust = ["zstd"] # No pure rust implementation exists