diff --git a/.github/workflows/build-modules.yml b/.github/workflows/build-modules.yml index b18a95c191..288404584b 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,85 @@ 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 + 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 + if [ "${{ inputs.platform }}" != "windows" ]; then + echo "extra_features=,openssl-vendored" >> $GITHUB_OUTPUT + fi + ;; + 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" + 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 + cargo build -p "$crate" + fi done - name: Run build all + shell: bash + 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 + 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 }} + EXTRA_FEATURES: ${{ steps.features.outputs.extra_features }} run: | - cargo test -p llrt_modules + 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 e50b4ade74..371f97ad4a 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,44 @@ 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 macro,crypto-graviola-rust,tls-graviola" >> $GITHUB_OUTPUT + ;; + ring) + 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 macro,crypto-openssl,tls-openssl" >> $GITHUB_OUTPUT + else + 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 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..7f535080da 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 @@ -37,61 +37,182 @@ 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 + 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 arm64 - os: macos-latest platform: darwin - arch: x86_64 + 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 (ring, openssl, graviola - no aws-lc) + - 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 + - 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 }} 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 arm64 - os: macos-latest platform: darwin - arch: x86_64 + 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 (ring, openssl, graviola - no aws-lc) + - 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 - 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 }} platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} + crypto: ${{ matrix.crypto }} diff --git a/Cargo.lock b/Cargo.lock index d8b057160d..50a5c77ac2 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" @@ -318,7 +340,7 @@ checksum = "99cbf41c6ec3c4b9eaf7f8f5c11a72cd7d3aa0428125c20d5ef4d09907a0f019" dependencies = [ "cfg-if", "cpufeatures", - "rand_core", + "rand_core 0.10.0-rc-2", ] [[package]] @@ -331,7 +353,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -473,6 +495,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 = "cpufeatures" version = "0.2.17" @@ -629,7 +662,7 @@ checksum = "6715836b4946e8585016e80b79c7561476aff3b22f7b756778e7b109d86086c6" dependencies = [ "hybrid-array", "num-traits", - "rand_core", + "rand_core 0.10.0-rc-2", "serdect", "subtle", "zeroize", @@ -637,9 +670,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", ] @@ -652,7 +685,7 @@ checksum = "fdd9b2855017318a49714c07ee8895b89d3510d54fa6d86be5835de74c389609" dependencies = [ "crypto-bigint", "libm", - "rand_core", + "rand_core 0.10.0-rc-2", ] [[package]] @@ -733,9 +766,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", @@ -786,6 +819,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" @@ -818,7 +857,7 @@ dependencies = [ "hkdf", "hybrid-array", "pkcs8", - "rand_core", + "rand_core 0.10.0-rc-2", "rustcrypto-ff", "rustcrypto-group", "sec1", @@ -912,6 +951,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" @@ -921,6 +975,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" @@ -1048,6 +1108,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.12" @@ -1098,6 +1168,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" @@ -1220,6 +1327,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" @@ -1269,7 +1395,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core", ] [[package]] @@ -1476,6 +1602,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" @@ -1483,7 +1673,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -1512,6 +1702,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" @@ -1670,10 +1875,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", @@ -1703,6 +1909,8 @@ dependencies = [ "der", "ecdsa", "elliptic-curve", + "graviola", + "hmac", "llrt_buffer", "llrt_context", "llrt_encoding", @@ -1711,14 +1919,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", ] @@ -1814,7 +2025,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rand", + "rand 0.10.0-rc.5", "rquickjs", "tokio", "tracing", @@ -1853,12 +2064,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", ] @@ -1943,6 +2156,7 @@ dependencies = [ "llrt_utils", "llrt_zlib", "once_cell", + "openssl", "rquickjs", "simd-json", "tokio", @@ -1967,7 +2181,7 @@ dependencies = [ "llrt_stream", "llrt_test", "llrt_utils", - "rand", + "rand 0.10.0-rc.5", "rquickjs", "tokio", "tracing", @@ -1981,7 +2195,7 @@ dependencies = [ "itoa", "llrt_test", "llrt_utils", - "rand", + "rand 0.10.0-rc.5", "rquickjs", "ryu", ] @@ -2002,7 +2216,7 @@ dependencies = [ "users", "whoami", "windows-registry", - "windows-result 0.4.1", + "windows-result", "windows-version", ] @@ -2014,7 +2228,7 @@ dependencies = [ "llrt_utils", "memchr", "once_cell", - "rand", + "rand 0.10.0-rc.5", "rquickjs", ] @@ -2091,8 +2305,11 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", + "openssl", "rustls", + "rustls-graviola", "tokio", + "tokio-openssl", "tokio-rustls", ] @@ -2114,7 +2331,9 @@ name = "llrt_tls" version = "0.7.0-beta" dependencies = [ "once_cell", + "openssl", "rustls", + "rustls-graviola", "rustls-native-certs", "tracing", "webpki-roots", @@ -2256,6 +2475,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" @@ -2306,12 +2544,60 @@ 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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +[[package]] +name = "openssl-src" +version = "300.5.5+3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + [[package]] name = "outref" version = "0.5.2" @@ -2389,9 +2675,15 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link 0.2.1", + "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" @@ -2478,6 +2770,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" @@ -2536,6 +2848,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" @@ -2553,7 +2874,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c3ad342f52c70a953d95acb09a55450fdc07c2214283b81536c3f83f714568e" dependencies = [ "crypto-bigint", - "rand_core", + "rand_core 0.10.0-rc-2", "rustcrypto-ff", "subtle", "zeroize", @@ -2577,6 +2898,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.103" @@ -2620,6 +2963,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" @@ -2628,7 +2981,26 @@ checksum = "be866deebbade98028b705499827ad6967c8bb1e21f96a2609913c8c076e9307" dependencies = [ "chacha20", "getrandom 0.3.4", - "rand_core", + "rand_core 0.10.0-rc-2", +] + +[[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]] @@ -2818,8 +3190,8 @@ dependencies = [ "digest", "pkcs1", "pkcs8", - "rand", - "rand_core", + "rand 0.10.0-rc.5", + "rand_core 0.10.0-rc-2", "sha2", "signature", "spki", @@ -2848,7 +3220,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-2", "subtle", ] @@ -2858,7 +3230,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-2", "rustcrypto-ff", "subtle", ] @@ -2882,6 +3254,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", @@ -2890,6 +3263,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" @@ -2926,6 +3310,7 @@ version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -3062,6 +3447,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.3" @@ -3116,7 +3512,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0251c9d6468f4ba853b6352b190fb7c1e405087779917c238445eb03993826" dependencies = [ "digest", - "rand_core", + "rand_core 0.10.0-rc-2", ] [[package]] @@ -3305,6 +3701,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" @@ -3358,6 +3765,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" @@ -3458,6 +3871,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -3472,6 +3887,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" @@ -3613,7 +4034,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 +4045,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 +4056,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 +4067,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 +4094,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 +4106,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 +4116,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 +4127,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 +4136,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 +4163,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 +4188,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 +4205,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 +4214,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]] @@ -3980,7 +4364,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8367a41efe370c38fa4af81968298cdd695311791e4797118a1621f04ed75859" dependencies = [ "curve25519-dalek", - "rand_core", + "rand_core 0.10.0-rc-2", "zeroize", ] diff --git a/Makefile b/Makefile index 9d47a6a93e..1d99706549 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 - cargo $(TOOLCHAIN) -Z build-std -Z build-std-features test --target $(CURRENT_TARGET) --all-features -- --nocapture --show-output +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) --features lambda -- --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 @@ -261,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" -- -D warnings check-crates: cargo metadata --no-deps --format-version 1 --quiet | \ @@ -271,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/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/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 0f8161a1aa..a06630d8fb 100644 --- a/libs/llrt_test_tls/Cargo.toml +++ b/libs/llrt_test_tls/Cargo.toml @@ -10,16 +10,26 @@ 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"] +tls-openssl = ["dep:openssl", "dep:tokio-openssl"] + [dependencies] 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 = [ - "ring", "tls12", ], default-features = false } -tokio = { version = "1", features = ["net", "fs"], 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", "rt", "macros"], default-features = false } tokio-rustls = { version = "0.26", default-features = false } 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 96112dbc58..e07b0b6d88 100644 --- a/libs/llrt_test_tls/src/server.rs +++ b/libs/llrt_test_tls/src/server.rs @@ -1,21 +1,70 @@ +#[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; -use rustls::ServerConfig; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use tokio::net::TcpListener; -use tokio_rustls::TlsAcceptor; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] use crate::MockServerCerts; +#[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()) +} + +#[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() + 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()]; @@ -51,3 +100,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/Cargo.toml b/llrt/Cargo.toml index 90997af72e..4f2486ccb7 100644 --- a/llrt/Cargo.toml +++ b/llrt/Cargo.toml @@ -5,13 +5,30 @@ 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 = ["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"] + +# 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 } @@ -19,7 +36,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..8abaac6ac5 100644 --- a/llrt_core/Cargo.toml +++ b/llrt_core/Cargo.toml @@ -5,13 +5,30 @@ 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 = ["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"] + +# OpenSSL vendored (builds OpenSSL from source) +openssl-vendored = ["openssl/vendored"] + [dependencies] bytes = { version = "1", default-features = false } chrono = { version = "0.4", features = ["std", "clock"], default-features = false } @@ -26,7 +43,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 +61,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 +70,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/modules/js/@llrt/test/SocketClient.ts b/llrt_core/src/modules/js/@llrt/test/SocketClient.ts index d72b7ba787..492876e46e 100644 --- a/llrt_core/src/modules/js/@llrt/test/SocketClient.ts +++ b/llrt_core/src/modules/js/@llrt/test/SocketClient.ts @@ -84,7 +84,10 @@ class SocketClient extends EventEmitter { async close(): Promise { return new Promise((resolve) => { - this.socket.end(resolve); + this.socket.end(() => { + this.socket.destroy(); + resolve(); + }); }); } } 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)); } 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(), diff --git a/llrt_core/src/vm.rs b/llrt_core/src/vm.rs index b1a2d9660f..fc17c84181 100644 --- a/llrt_core/src/vm.rs +++ b/llrt_core/src/vm.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 use std::{env, result::Result as StdResult}; -use ring::rand::SecureRandom; use rquickjs::{ context::EvalOptions, loader::FileResolver, prelude::Func, AsyncContext, AsyncRuntime, CatchResultExt, Ctx, Error, Result, Value, @@ -22,7 +21,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 +83,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/llrt_modules/Cargo.toml b/llrt_modules/Cargo.toml index 076e25dbf5..289344d59c 100644 --- a/llrt_modules/Cargo.toml +++ b/llrt_modules/Cargo.toml @@ -8,9 +8,26 @@ 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", "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 +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"] + +# OpenSSL vendored (builds OpenSSL from source) +openssl-vendored = ["dep:openssl", "openssl/vendored"] + base = [ "abort", "assert", @@ -54,9 +71,14 @@ 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"] +https = [ + "llrt_http", + "llrt_http?/webpki-roots", + "llrt_http?/http1", + "llrt_http?/http2", +] navigator = ["llrt_navigator"] net = ["llrt_net"] os = ["llrt_os"] @@ -79,9 +101,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 +113,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_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", 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,9 +131,10 @@ 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 } +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/Cargo.toml b/modules/llrt_crypto/Cargo.toml index 865701db01..86d1b95c3a 100644 --- a/modules/llrt_crypto/Cargo.toml +++ b/modules/llrt_crypto/Cargo.toml @@ -12,10 +12,20 @@ name = "llrt_crypto" path = "src/lib.rs" [features] -default = ["subtle-rs"] +default = ["crypto-rust"] -subtle-rs = [ +# Internal feature: enables DER parsing for SubtleCrypto key import/export +_subtle-full = [ "llrt_json", + "spki", + "der", + "const-oid", + "pkcs8", +] + +# Internal feature: enables RustCrypto dependencies for key import/export +_rustcrypto = [ + "_subtle-full", "aes", "aes-gcm", "aes-kw", @@ -28,12 +38,17 @@ subtle-rs = [ "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"] + [dependencies] crc32c = { version = "0.6", default-features = false } crc32fast = { version = "1", default-features = false } @@ -48,11 +63,11 @@ 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 + +# optional +llrt_json = { version = "0.7.0-beta", path = "../../libs/llrt_json", optional = true } aes = { version = "0.9.0-rc.2", optional = true } aes-gcm = { version = "0.11.0-rc.2", features = [ "alloc", @@ -72,8 +87,8 @@ 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 } -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", @@ -107,6 +122,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/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 22ba50a9fc..de91661db6 100644 --- a/modules/llrt_crypto/src/lib.rs +++ b/modules/llrt_crypto/src/lib.rs @@ -1,11 +1,31 @@ // 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; -#[cfg(feature = "subtle-rs")] +mod hash; mod subtle; +mod provider; + use std::slice; use llrt_buffer::Buffer; @@ -20,17 +40,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::{Constructor, Opt}, + 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, @@ -39,11 +56,11 @@ use subtle::{ use self::{ crc32::{Crc32, Crc32c}, - md5_hash::Md5, - sha_hash::{Hash, Hmac, ShaAlgorithm, ShaHash}, + hash::{Hash, Hmac}, }; -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 +80,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 +151,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 +185,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 +259,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)?; @@ -279,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")?; @@ -300,23 +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(); - 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 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 6686196cfa..0000000000 --- a/modules/llrt_crypto/src/md5_hash.rs +++ /dev/null @@ -1,45 +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 md5::{Digest as Md5Digest, Md5 as MdHasher}; -use rquickjs::{function::Opt, prelude::This, Class, Ctx, Result, Value}; - -use super::encoded_bytes; - -#[rquickjs::class] -#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] -pub struct Md5 { - #[qjs(skip_trace)] - hasher: MdHasher, -} - -#[rquickjs::methods] -impl Md5 { - #[qjs(constructor)] - fn new() -> Self { - Self { - hasher: MdHasher::new(), - } - } - - #[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(); - - match encoding.0 { - Some(encoding) => encoded_bytes(ctx, bytes, &encoding), - None => bytes_to_typed_array(ctx, bytes), - } - } - - #[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 new file mode 100644 index 0000000000..616b91c464 --- /dev/null +++ b/modules/llrt_crypto/src/provider/graviola.rs @@ -0,0 +1,562 @@ +// 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::hash::HashAlgorithm; +use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; +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: HashAlgorithm) -> Self::Digest { + match algorithm { + 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: HashAlgorithm, 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)), + _ => 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: HashAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pss_verify( + &self, + _public_key_der: &[u8], + _signature: &[u8], + _digest: &[u8], + _salt_length: usize, + _hash_alg: HashAlgorithm, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pkcs1v15_sign( + &self, + _private_key_der: &[u8], + _digest: &[u8], + _hash_alg: HashAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pkcs1v15_verify( + &self, + _public_key_der: &[u8], + _signature: &[u8], + _digest: &[u8], + _hash_alg: HashAlgorithm, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_oaep_encrypt( + &self, + _public_key_der: &[u8], + _data: &[u8], + _hash_alg: HashAlgorithm, + _label: Option<&[u8]>, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_oaep_decrypt( + &self, + _private_key_der: &[u8], + _data: &[u8], + _hash_alg: HashAlgorithm, + _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(None))?; + if !matches!(key.len(), 16 | 32) { + return Err(CryptoError::InvalidKey(None)); + } + 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(None))?; + if !matches!(key.len(), 16 | 32) { + return Err(CryptoError::InvalidKey(None)); + } + if data.len() < 16 { + return Err(CryptoError::InvalidData(None)); + } + 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(None))?; + 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: HashAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn pbkdf2_derive_key( + &self, + _password: &[u8], + _salt: &[u8], + _iterations: u32, + _length: usize, + _hash_alg: HashAlgorithm, + ) -> 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: HashAlgorithm, + length_bits: u16, + ) -> Result, CryptoError> { + let length_bytes = if length_bits == 0 { + match hash_alg { + HashAlgorithm::Sha256 => 64, + HashAlgorithm::Sha384 | HashAlgorithm::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) + } + + 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) + } + 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 +#[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: HashAlgorithm) -> Self { + match algorithm { + HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::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: HashAlgorithm, key: &[u8]) -> Self { + match algorithm { + HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::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 new file mode 100644 index 0000000000..ac6895056a --- /dev/null +++ b/modules/llrt_crypto/src/provider/mod.rs @@ -0,0 +1,1280 @@ +// 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; + +#[cfg(feature = "crypto-openssl")] +mod openssl; + +#[cfg(any(feature = "crypto-ring", feature = "crypto-ring-rust"))] +mod ring; + +#[cfg(feature = "_rustcrypto")] +mod rust; + +use crate::hash::HashAlgorithm; +use crate::subtle::EllipticCurve; + +#[derive(Debug)] +#[allow(dead_code)] +pub struct RsaImportResult { + pub key_data: Vec, + pub modulus_length: u32, + pub public_exponent: Vec, + pub is_private: bool, +} + +#[derive(Debug)] +#[allow(dead_code)] +pub struct EcImportResult { + pub key_data: Vec, + pub is_private: bool, +} + +#[derive(Debug)] +#[allow(dead_code)] +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)] +#[allow(dead_code)] +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)] +#[allow(dead_code)] +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)] +#[allow(dead_code)] +pub struct EcJwkImport<'a> { + pub x: &'a [u8], + pub y: &'a [u8], + pub d: Option<&'a [u8]>, +} + +/// EC JWK components for export +#[derive(Debug)] +#[allow(dead_code)] +pub struct EcJwkExport { + pub x: Vec, + pub y: Vec, + 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 + where + Self: Sized; +} + +#[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; + + // Digest operations + fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest; + + // HMAC operations + fn hmac(&self, algorithm: HashAlgorithm, 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: HashAlgorithm, + ) -> Result, CryptoError>; + fn rsa_pss_verify( + &self, + public_key_der: &[u8], + signature: &[u8], + digest: &[u8], + salt_length: usize, + hash_alg: HashAlgorithm, + ) -> Result; + fn rsa_pkcs1v15_sign( + &self, + private_key_der: &[u8], + digest: &[u8], + hash_alg: HashAlgorithm, + ) -> Result, CryptoError>; + fn rsa_pkcs1v15_verify( + &self, + public_key_der: &[u8], + signature: &[u8], + digest: &[u8], + hash_alg: HashAlgorithm, + ) -> Result; + fn rsa_oaep_encrypt( + &self, + public_key_der: &[u8], + data: &[u8], + hash_alg: HashAlgorithm, + label: Option<&[u8]>, + ) -> Result, CryptoError>; + fn rsa_oaep_decrypt( + &self, + private_key_der: &[u8], + data: &[u8], + hash_alg: HashAlgorithm, + 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: HashAlgorithm, + ) -> Result, CryptoError>; + fn pbkdf2_derive_key( + &self, + password: &[u8], + salt: &[u8], + iterations: u32, + length: usize, + hash_alg: HashAlgorithm, + ) -> Result, CryptoError>; + + fn generate_aes_key(&self, length_bits: u16) -> Result, CryptoError>; + fn generate_hmac_key( + &self, + hash_alg: HashAlgorithm, + 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>; + + // 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; + + // 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 { + fn update(&mut self, data: &[u8]); + fn finalize(self) -> Vec + where + Self: Sized; +} + +#[derive(Debug)] +#[allow(dead_code)] +pub enum CryptoError { + InvalidKey(Option>), + InvalidData(Option>), + InvalidSignature(Option>), + InvalidLength, + SigningFailed(Option>), + VerificationFailed, + OperationFailed(Option>), + UnsupportedAlgorithm, + 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(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(None) => write!(f, "Signing failed"), + CryptoError::SigningFailed(Some(msg)) => write!(f, "Signing failed: {}", msg), + CryptoError::VerificationFailed => write!(f, "Verification 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(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), + } + } +} + +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; + +#[cfg(feature = "crypto-ring-rust")] +pub type DefaultProvider = RingRustProvider; + +#[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"))] +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: HashAlgorithm) -> Self::Digest { + $digest_fn(alg) + } + fn hmac(&self, alg: HashAlgorithm, 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: HashAlgorithm, + ) -> 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: HashAlgorithm, + ) -> Result { + rust::RustCryptoProvider.rsa_pss_verify(k, s, d, sl, a) + } + fn rsa_pkcs1v15_sign( + &self, + k: &[u8], + d: &[u8], + a: HashAlgorithm, + ) -> Result, CryptoError> { + rust::RustCryptoProvider.rsa_pkcs1v15_sign(k, d, a) + } + fn rsa_pkcs1v15_verify( + &self, + k: &[u8], + s: &[u8], + d: &[u8], + a: HashAlgorithm, + ) -> Result { + rust::RustCryptoProvider.rsa_pkcs1v15_verify(k, s, d, a) + } + fn rsa_oaep_encrypt( + &self, + k: &[u8], + d: &[u8], + a: HashAlgorithm, + l: Option<&[u8]>, + ) -> Result, CryptoError> { + rust::RustCryptoProvider.rsa_oaep_encrypt(k, d, a, l) + } + fn rsa_oaep_decrypt( + &self, + k: &[u8], + d: &[u8], + a: HashAlgorithm, + 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: HashAlgorithm, + ) -> 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: 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: HashAlgorithm, 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) + } + 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) + } + 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) + } + } + }; +} + +#[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")] +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, + graviola::GraviolaRustDigest, + graviola::GraviolaRustHmac, + 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) + { + 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) + { + graviola::GraviolaProvider.aes_decrypt(m, k, iv, d, aad) + } else { + 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(all(feature = "crypto-graviola", not(feature = "crypto-graviola-rust")))] + 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(HashAlgorithm::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(HashAlgorithm::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(HashAlgorithm::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(HashAlgorithm::Sha256, key); + hmac.update(b"hello world"); + let result = hmac.finalize(); + assert_eq!(result.len(), 32); + } + + // 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(); + 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); + } + + #[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(); + 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); + } + + #[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(); + 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 - 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(); + let key = p.generate_aes_key(128).unwrap(); + 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(); + let key = p.generate_aes_key(256).unwrap(); + 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(); + let key = p.generate_hmac_key(HashAlgorithm::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, HashAlgorithm::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, HashAlgorithm::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(HashAlgorithm::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(HashAlgorithm::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(HashAlgorithm::Sha256); + digest.update(b"message to sign"); + let hash = digest.finalize(); + + let signature = p + .rsa_pss_sign(&private_key, &hash, 32, HashAlgorithm::Sha256) + .unwrap(); + + let valid = p + .rsa_pss_verify(&public_key, &signature, &hash, 32, HashAlgorithm::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(HashAlgorithm::Sha256); + digest.update(b"message to sign"); + let hash = digest.finalize(); + + let signature = p + .rsa_pkcs1v15_sign(&private_key, &hash, HashAlgorithm::Sha256) + .unwrap(); + + let valid = p + .rsa_pkcs1v15_verify(&public_key, &signature, &hash, HashAlgorithm::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, HashAlgorithm::Sha256, None) + .unwrap(); + + let decrypted = p + .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 new file mode 100644 index 0000000000..454f420539 --- /dev/null +++ b/modules/llrt_crypto/src/provider/openssl.rs @@ -0,0 +1,1309 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! 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::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::hash::HashAlgorithm; +use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; +use crate::subtle::EllipticCurve; + +pub struct OpenSslProvider; + +pub enum OpenSslDigest { + Md5(Hasher), + Sha1(Hasher), + Sha256(Hasher), + Sha384(Hasher), + Sha512(Hasher), +} + +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() + }, + } + } +} + +pub struct OpenSslHmac { + signer: Signer<'static>, +} + +impl HmacProvider for OpenSslHmac { + fn update(&mut self, data: &[u8]) { + let _ = self.signer.update(data); + } + + fn finalize(self) -> Vec { + self.signer.sign_to_vec().unwrap_or_default() + } +} + +fn get_message_digest(alg: HashAlgorithm) -> MessageDigest { + match alg { + HashAlgorithm::Md5 => MessageDigest::md5(), + HashAlgorithm::Sha1 => MessageDigest::sha1(), + HashAlgorithm::Sha256 => MessageDigest::sha256(), + HashAlgorithm::Sha384 => MessageDigest::sha384(), + HashAlgorithm::Sha512 => MessageDigest::sha512(), + } +} + +fn get_md(alg: HashAlgorithm) -> &'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(), + } +} + +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; + + 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 { + 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: 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 { + std::mem::transmute::, Signer<'static>>( + Signer::new(md, &pkey).expect("Failed to create signer"), + ) + }; + OpenSslHmac { signer } + } + + fn ecdsa_sign( + &self, + curve: EllipticCurve, + private_key_der: &[u8], + digest: &[u8], + ) -> Result, CryptoError> { + 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).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); + Ok(result) + } + + fn ecdsa_verify( + &self, + curve: EllipticCurve, + public_key_sec1: &[u8], + signature: &[u8], + digest: &[u8], + ) -> Result { + 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> { + 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( + &self, + public_key_bytes: &[u8], + signature: &[u8], + data: &[u8], + ) -> Result { + 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( + &self, + private_key_der: &[u8], + digest: &[u8], + salt_length: usize, + 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())))?; + 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( + &self, + public_key_der: &[u8], + signature: &[u8], + digest: &[u8], + salt_length: usize, + hash_alg: HashAlgorithm, + ) -> Result { + 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( + &self, + private_key_der: &[u8], + digest: &[u8], + 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())))?; + 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( + &self, + public_key_der: &[u8], + signature: &[u8], + digest: &[u8], + hash_alg: HashAlgorithm, + ) -> Result { + 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( + &self, + public_key_der: &[u8], + data: &[u8], + hash_alg: HashAlgorithm, + label: Option<&[u8]>, + ) -> Result, CryptoError> { + 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( + &self, + private_key_der: &[u8], + data: &[u8], + hash_alg: HashAlgorithm, + label: Option<&[u8]>, + ) -> Result, CryptoError> { + 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( + &self, + curve: EllipticCurve, + private_key_der: &[u8], + public_key_sec1: &[u8], + ) -> Result, CryptoError> { + 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( + &self, + private_key: &[u8], + public_key: &[u8], + ) -> Result, CryptoError> { + 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( + &self, + mode: AesMode, + key: &[u8], + iv: &[u8], + data: &[u8], + additional_data: Option<&[u8]>, + ) -> Result, CryptoError> { + 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 / 8) 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( + &self, + mode: AesMode, + key: &[u8], + iv: &[u8], + data: &[u8], + additional_data: Option<&[u8]>, + ) -> Result, CryptoError> { + 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 / 8) 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> { + 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> { + 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( + &self, + key: &[u8], + salt: &[u8], + info: &[u8], + length: usize, + hash_alg: HashAlgorithm, + ) -> Result, CryptoError> { + 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( + &self, + password: &[u8], + salt: &[u8], + iterations: u32, + length: usize, + hash_alg: HashAlgorithm, + ) -> Result, CryptoError> { + 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> { + 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( + &self, + hash_alg: HashAlgorithm, + 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, + } + } 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> { + let group = get_ec_group(curve)?; + let ec_key = EcKey::generate(&group) + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + 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())))?; + // 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())))?; + 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> { + 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_raw = pkey + .raw_public_key() + .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; + Ok((private_der, public_raw)) + } + + fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { + 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( + &self, + modulus_length: u32, + public_exponent: &[u8], + ) -> Result<(Vec, Vec), CryptoError> { + 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 { + // 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: sec1, + 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 { + // 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 }) + } + } + + 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 new file mode 100644 index 0000000000..e1cbedbce9 --- /dev/null +++ b/modules/llrt_crypto/src/provider/ring.rs @@ -0,0 +1,537 @@ +// 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::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: HashAlgorithm) -> Self::Digest { + match algorithm { + HashAlgorithm::Md5 => RingDigestType::Md5(RingMd5(Md5Hasher::new())), + HashAlgorithm::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)), + } + } + + fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { + match algorithm { + HashAlgorithm::Md5 => { + panic!("HMAC-MD5 not supported by Ring provider"); + }, + HashAlgorithm::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( + &hmac::Key::new(hmac::HMAC_SHA256, key), + ))), + HashAlgorithm::Sha384 => RingHmacType::Sha384(RingHmacSha384(hmac::Context::with_key( + &hmac::Key::new(hmac::HMAC_SHA384, key), + ))), + HashAlgorithm::Sha512 => RingHmacType::Sha512(RingHmacSha512(hmac::Context::with_key( + &hmac::Key::new(hmac::HMAC_SHA512, key), + ))), + } + } + + 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: HashAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pss_verify( + &self, + _public_key_der: &[u8], + _signature: &[u8], + _digest: &[u8], + _salt_length: usize, + _hash_alg: HashAlgorithm, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pkcs1v15_sign( + &self, + _private_key_der: &[u8], + _digest: &[u8], + _hash_alg: HashAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_pkcs1v15_verify( + &self, + _public_key_der: &[u8], + _signature: &[u8], + _digest: &[u8], + _hash_alg: HashAlgorithm, + ) -> Result { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_oaep_encrypt( + &self, + _public_key_der: &[u8], + _data: &[u8], + _hash_alg: HashAlgorithm, + _label: Option<&[u8]>, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn rsa_oaep_decrypt( + &self, + _private_key_der: &[u8], + _data: &[u8], + _hash_alg: HashAlgorithm, + _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> { + 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) + } + + 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: HashAlgorithm, + ) -> Result, CryptoError> { + Err(CryptoError::UnsupportedAlgorithm) + } + + fn pbkdf2_derive_key( + &self, + _password: &[u8], + _salt: &[u8], + _iterations: u32, + _length: usize, + _hash_alg: HashAlgorithm, + ) -> 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: HashAlgorithm, + _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) + } + + 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) + } + 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/aes_variants.rs b/modules/llrt_crypto/src/provider/rust/aes_variants.rs new file mode 100644 index 0000000000..73c0a772e6 --- /dev/null +++ b/modules/llrt_crypto/src/provider/rust/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/provider/rust/mod.rs b/modules/llrt_crypto/src/provider/rust/mod.rs new file mode 100644 index 0000000000..f0f39ee8b9 --- /dev/null +++ b/modules/llrt_crypto/src/provider/rust/mod.rs @@ -0,0 +1,1574 @@ +// 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::{ + block_padding::Pkcs7, BlockModeDecrypt, BlockModeEncrypt, KeyIvInit, StreamCipher, + StreamCipherError, +}; +use aes_gcm::{ + aead::{Aead, Payload}, + KeyInit, Nonce, +}; +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::{ + ecdsa::{ + Signature as P256Signature, SigningKey as P256SigningKey, VerifyingKey as P256VerifyingKey, + }, + SecretKey as P256SecretKey, +}; +use p384::{ + ecdsa::{ + Signature as P384Signature, SigningKey as P384SigningKey, VerifyingKey as P384VerifyingKey, + }, + SecretKey as P384SecretKey, +}; +use p521::{ + ecdsa::{Signature as P521Signature, VerifyingKey as P521VerifyingKey}, + SecretKey as P521SecretKey, +}; +use pkcs8::{DecodePrivateKey, EncodePrivateKey}; +use ring::{ + pbkdf2, + rand::SystemRandom, + signature::{Ed25519KeyPair, KeyPair, UnparsedPublicKey}, +}; +use rsa::pkcs1::{ + DecodeRsaPrivateKey, DecodeRsaPublicKey, EncodeRsaPrivateKey, EncodeRsaPublicKey, +}; +use rsa::signature::hazmat::PrehashSigner; +use rsa::{ + pss::Pss, + sha2::{Digest, Sha256, Sha384, Sha512}, + BoxedUint, Oaep, Pkcs1v15Sign, RsaPrivateKey, RsaPublicKey, +}; +use sha1::Sha1; + +use crate::{ + hash::HashAlgorithm, + provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}, + random_byte_array, + subtle::EllipticCurve, +}; + +use aes_variants::AesGcmVariant; + +impl From for CryptoError { + fn from(_: aes::cipher::InvalidLength) -> Self { + CryptoError::InvalidLength + } +} + +impl From for CryptoError { + fn from(_: StreamCipherError) -> Self { + CryptoError::OperationFailed(None) + } +} + +// Digest implementation using sha2/md5 crates +pub enum RustDigest { + Md5(md5::Md5), + Sha1(Sha1), + Sha256(Sha256), + Sha384(Sha384), + Sha512(Sha512), +} + +impl SimpleDigest for RustDigest { + fn update(&mut self, data: &[u8]) { + 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 { + 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 using hmac crate +pub enum RustHmac { + Sha1(HmacImpl), + Sha256(HmacImpl), + Sha384(HmacImpl), + Sha512(HmacImpl), +} + +impl HmacProvider for RustHmac { + fn update(&mut self, data: &[u8]) { + 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 { + 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(), + } + } +} + +// Main Crypto Provider +#[derive(Default)] +pub struct RustCryptoProvider; + +pub static SYSTEM_RANDOM: Lazy = Lazy::new(SystemRandom::new); + +impl CryptoProvider for RustCryptoProvider { + type Digest = RustDigest; + type Hmac = RustHmac; + + fn digest(&self, algorithm: HashAlgorithm) -> 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()), + } + } + + fn hmac(&self, algorithm: HashAlgorithm, 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 => { + RustHmac::Sha256(HmacImpl::::new_from_slice(key).unwrap()) + }, + HashAlgorithm::Sha384 => { + RustHmac::Sha384(HmacImpl::::new_from_slice(key).unwrap()) + }, + HashAlgorithm::Sha512 => { + RustHmac::Sha512(HmacImpl::::new_from_slice(key).unwrap()) + }, + } + } + + fn ecdsa_sign( + &self, + curve: EllipticCurve, + private_key_der: &[u8], + digest: &[u8], + ) -> Result, CryptoError> { + match curve { + EllipticCurve::P256 => { + let secret_key = P256SecretKey::from_pkcs8_der(private_key_der) + .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(None))?; + Ok(signature.to_bytes().to_vec()) + }, + EllipticCurve::P384 => { + let secret_key = P384SecretKey::from_pkcs8_der(private_key_der) + .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(None))?; + Ok(signature.to_bytes().to_vec()) + }, + EllipticCurve::P521 => { + let secret_key = P521SecretKey::from_pkcs8_der(private_key_der) + .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(None))?; + 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(None))?; + let sig = P256Signature::from_slice(signature) + .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(None))?; + let sig = P384Signature::from_slice(signature) + .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(None))?; + let sig = P521Signature::from_slice(signature) + .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(None))?; + 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: 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 { + HashAlgorithm::Sha256 => private_key + .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) + .map_err(|_| CryptoError::SigningFailed(None)), + HashAlgorithm::Sha384 => private_key + .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) + .map_err(|_| CryptoError::SigningFailed(None)), + HashAlgorithm::Sha512 => private_key + .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) + .map_err(|_| CryptoError::SigningFailed(None)), + _ => Err(CryptoError::UnsupportedAlgorithm), + } + } + + fn rsa_pss_verify( + &self, + public_key_der: &[u8], + signature: &[u8], + digest: &[u8], + salt_length: usize, + hash_alg: HashAlgorithm, + ) -> Result { + let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) + .map_err(|_| CryptoError::InvalidKey(None))?; + + match hash_alg { + HashAlgorithm::Sha256 => Ok(public_key + .verify(Pss::::new_with_salt(salt_length), digest, signature) + .is_ok()), + HashAlgorithm::Sha384 => Ok(public_key + .verify(Pss::::new_with_salt(salt_length), digest, signature) + .is_ok()), + HashAlgorithm::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: 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 { + HashAlgorithm::Sha256 => private_key + .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) + .map_err(|_| CryptoError::SigningFailed(None)), + HashAlgorithm::Sha384 => private_key + .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) + .map_err(|_| CryptoError::SigningFailed(None)), + HashAlgorithm::Sha512 => private_key + .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) + .map_err(|_| CryptoError::SigningFailed(None)), + _ => Err(CryptoError::UnsupportedAlgorithm), + } + } + + fn rsa_pkcs1v15_verify( + &self, + public_key_der: &[u8], + signature: &[u8], + digest: &[u8], + hash_alg: HashAlgorithm, + ) -> Result { + let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) + .map_err(|_| CryptoError::InvalidKey(None))?; + + match hash_alg { + HashAlgorithm::Sha256 => Ok(public_key + .verify(Pkcs1v15Sign::new::(), digest, signature) + .is_ok()), + HashAlgorithm::Sha384 => Ok(public_key + .verify(Pkcs1v15Sign::new::(), digest, signature) + .is_ok()), + HashAlgorithm::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: HashAlgorithm, + label: Option<&[u8]>, + ) -> Result, CryptoError> { + let mut rng = rand::rng(); + let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) + .map_err(|_| CryptoError::InvalidKey(None))?; + + match hash_alg { + HashAlgorithm::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(None)) + }, + HashAlgorithm::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(None)) + }, + HashAlgorithm::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(None)) + }, + HashAlgorithm::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(None)) + }, + _ => Err(CryptoError::UnsupportedAlgorithm), + } + } + + fn rsa_oaep_decrypt( + &self, + private_key_der: &[u8], + data: &[u8], + 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 { + HashAlgorithm::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(None)) + }, + HashAlgorithm::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(None)) + }, + HashAlgorithm::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(None)) + }, + HashAlgorithm::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(None)) + }, + _ => Err(CryptoError::UnsupportedAlgorithm), + } + } + + 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(None))?; + let public_key = p256::PublicKey::from_sec1_bytes(public_key_sec1) + .map_err(|_| CryptoError::InvalidKey(None))?; + 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(None))?; + let public_key = p384::PublicKey::from_sec1_bytes(public_key_sec1) + .map_err(|_| CryptoError::InvalidKey(None))?; + 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(None))?; + let public_key = p521::PublicKey::from_sec1_bytes(public_key_sec1) + .map_err(|_| CryptoError::InvalidKey(None))?; + 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(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); + 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(None)), + }, + 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(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(None))?; + + 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(None)) + }, + } + } + + 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(None)) + }, + 24 => { + let decryptor = Decryptor::::new_from_slices(key, iv)?; + decryptor + .decrypt_padded_vec::(data) + .map_err(|_| CryptoError::DecryptionFailed(None)) + }, + 32 => { + let decryptor = Decryptor::::new_from_slices(key, iv)?; + decryptor + .decrypt_padded_vec::(data) + .map_err(|_| CryptoError::DecryptionFailed(None)) + }, + _ => Err(CryptoError::InvalidKey(None)), + }, + 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(None))?; + + 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(None)) + }, + } + } + + 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(None))?; + let mut buf = vec![0u8; key.len() + 8]; + let result = kw + .wrap_key(key, &mut buf) + .map_err(|_| CryptoError::OperationFailed(None))?; + Ok(result.to_vec()) + }, + 24 => { + 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(None))?; + Ok(result.to_vec()) + }, + 32 => { + 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(None))?; + Ok(result.to_vec()) + }, + _ => 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(None))?; + let mut buf = vec![0u8; wrapped_key.len()]; + let result = kw + .unwrap_key(wrapped_key, &mut buf) + .map_err(|_| CryptoError::OperationFailed(None))?; + Ok(result.to_vec()) + }, + 24 => { + 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(None))?; + Ok(result.to_vec()) + }, + 32 => { + 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(None))?; + Ok(result.to_vec()) + }, + _ => Err(CryptoError::InvalidKey(None)), + } + } + + fn hkdf_derive_key( + &self, + key: &[u8], + salt: &[u8], + info: &[u8], + length: usize, + hash_alg: HashAlgorithm, + ) -> 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, + _ => 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(None))?; + + let mut out = vec![0u8; length]; + okm.fill(&mut out) + .map_err(|_| CryptoError::DerivationFailed(None))?; + Ok(out) + } + + fn pbkdf2_derive_key( + &self, + password: &[u8], + salt: &[u8], + iterations: u32, + length: usize, + hash_alg: HashAlgorithm, + ) -> 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, + _ => return Err(CryptoError::UnsupportedAlgorithm), + }; + + let mut out = vec![0; length]; + let iterations = NonZeroU32::new(iterations).ok_or(CryptoError::InvalidData(None))?; + 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: HashAlgorithm, + length_bits: u16, + ) -> Result, CryptoError> { + let length_bytes = if length_bits == 0 { + hash_alg.block_len() + } else { + (length_bits / 8) as usize + }; + + if length_bytes > 128 { + return Err(CryptoError::InvalidLength); + } + + Ok(random_byte_array(length_bytes)) + } + + fn generate_ec_key(&self, curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { + let mut rng = rand::rng(); + + match curve { + EllipticCurve::P256 => { + let key = P256SecretKey::try_from_rng(&mut rng) + .map_err(|_| CryptoError::OperationFailed(None))?; + let pkcs8 = key + .to_pkcs8_der() + .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(None))?; + let pkcs8 = key + .to_pkcs8_der() + .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(None))?; + let pkcs8 = key + .to_pkcs8_der() + .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)) + }, + } + } + + fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { + let rng = &(*SYSTEM_RANDOM); + let pkcs8 = + 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(None))?; + 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 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() + .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(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(None))?; + + let public_key = rsa_private_key + .to_public_key() + .to_pkcs1_der() + .map_err(|_| CryptoError::OperationFailed(None))?; + let private_key = rsa_private_key + .to_pkcs1_der() + .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 elliptic_curve's SecretKey to parse it + let (x, y, d) = match curve { + EllipticCurve::P256 => { + 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(), + sk.to_bytes().to_vec(), + ) + }, + EllipticCurve::P384 => { + 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(), + sk.to_bytes().to_vec(), + ) + }, + EllipticCurve::P521 => { + 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(), + sk.to_bytes().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 { + 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 }) + } + } + + 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 +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 deleted file mode 100644 index 56a34939b2..0000000000 --- a/modules/llrt_crypto/src/sha_hash.rs +++ /dev/null @@ -1,234 +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 ring::{ - digest::{self, Context as DigestContext}, - hmac::{self, Context as HmacContext}, -}; -use rquickjs::{function::Opt, prelude::This, Class, Ctx, Result, Value}; - -use super::encoded_bytes; - -#[rquickjs::class] -#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] -pub struct Hmac { - #[qjs(skip_trace)] - context: HmacContext, -} - -#[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 algorithm = *algorithm.hmac_algorithm(); - - Ok(Self { - context: HmacContext::with_key(&hmac::Key::new(algorithm, key_value.as_bytes(&ctx)?)), - }) - } - - fn digest<'js>(&self, ctx: Ctx<'js>, encoding: Opt) -> Result> { - let signature = self.context.clone().sign(); - let bytes: &[u8] = signature.as_ref(); - - match encoding.into_inner() { - Some(encoding) => encoded_bytes(ctx, bytes, &encoding), - None => bytes_to_typed_array(ctx, bytes), - } - } - - fn update<'js>( - this: This>, - ctx: Ctx<'js>, - bytes: ObjectBytes<'js>, - ) -> Result> { - let bytes = bytes.as_bytes(&ctx)?; - this.0.borrow_mut().context.update(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, -} - -#[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 algorithm = algorithm.digest_algorithm(); - - Ok(Self { - context: DigestContext::new(algorithm), - }) - } - - #[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(); - - match encoding.0 { - Some(encoding) => encoded_bytes(ctx, bytes, &encoding), - None => bytes_to_typed_array(ctx, bytes), - } - } - - #[qjs(rename = "update")] - fn hash_update<'js>( - this: This>, - ctx: Ctx<'js>, - bytes: ObjectBytes<'js>, - ) -> Result> { - let bytes = bytes.as_bytes(&ctx)?; - this.0.borrow_mut().context.update(bytes); - Ok(this.0) - } -} - -#[derive(Debug, Clone)] -pub enum ShaAlgorithm { - SHA1, - SHA256, - SHA384, - SHA512, -} - -iterable_enum!(ShaAlgorithm, SHA1, SHA256, SHA384, SHA512); - -impl ShaAlgorithm { - pub fn class_name(&self) -> &'static str { - match self { - ShaAlgorithm::SHA1 => "Sha1", - ShaAlgorithm::SHA256 => "Sha256", - ShaAlgorithm::SHA384 => "Sha384", - ShaAlgorithm::SHA512 => "Sha512", - } - } - pub fn hmac_algorithm(&self) -> &'static hmac::Algorithm { - match self { - ShaAlgorithm::SHA1 => &hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, - ShaAlgorithm::SHA256 => &hmac::HMAC_SHA256, - ShaAlgorithm::SHA384 => &hmac::HMAC_SHA384, - ShaAlgorithm::SHA512 => &hmac::HMAC_SHA512, - } - } - - pub fn digest_algorithm(&self) -> &'static digest::Algorithm { - match self { - ShaAlgorithm::SHA1 => &digest::SHA1_FOR_LEGACY_USE_ONLY, - ShaAlgorithm::SHA256 => &digest::SHA256, - ShaAlgorithm::SHA384 => &digest::SHA384, - ShaAlgorithm::SHA512 => &digest::SHA512, - } - } - - pub fn as_str(&self) -> &'static str { - match self { - ShaAlgorithm::SHA1 => "SHA-1", - ShaAlgorithm::SHA256 => "SHA-256", - ShaAlgorithm::SHA384 => "SHA-384", - ShaAlgorithm::SHA512 => "SHA-512", - } - } - - pub fn as_numeric_str(&self) -> &'static str { - match self { - 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() - } -} - -#[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/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/derive.rs b/modules/llrt_crypto/src/subtle/derive.rs index 43aa979758..38d943d4a2 100644 --- a/modules/llrt_crypto/src/subtle/derive.rs +++ b/modules/llrt_crypto/src/subtle/derive.rs @@ -1,16 +1,9 @@ // 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}, -}; +use crate::{provider::CryptoProvider, subtle::CryptoKey, CRYPTO_PROVIDER}; use super::{ algorithm_mismatch_error, algorithm_not_supported_error, @@ -21,14 +14,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 +33,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,82 +45,34 @@ 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, "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"); } - 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() + 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 + CRYPTO_PROVIDER + .hkdf_derive_key(&base_key.handle, salt, info, out_length, *hash) + .or_throw(ctx) }, DeriveAlgorithm::Derive(KeyDerivation::Pbkdf2 { hash, @@ -145,28 +82,12 @@ 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)?; + 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/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 a358e91815..eb71c1e3aa 100644 --- a/modules/llrt_crypto/src/subtle/digest.rs +++ b/modules/llrt_crypto/src/subtle/digest.rs @@ -1,10 +1,13 @@ // 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::{ + hash::HashAlgorithm, + provider::{CryptoProvider, SimpleDigest}, + CRYPTO_PROVIDER, +}; pub async fn subtle_digest<'js>( ctx: Ctx<'js>, @@ -17,16 +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 hash = sha_algorithm.digest_algorithm(); - let mut context = Context::new(hash); - context.update(data); - let digest = context.finish(); - - digest.as_ref().to_vec() +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/encryption.rs b/modules/llrt_crypto/src/subtle/encryption.rs index 1d1fb99ad5..bb3ca6b9af 100644 --- a/modules/llrt_crypto/src/subtle/encryption.rs +++ b/modules/llrt_crypto/src/subtle/encryption.rs @@ -1,28 +1,19 @@ +use std::borrow::Cow; + // 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::sha_hash::ShaAlgorithm; - -pub(super) enum OaepPadding { - Sha256(Oaep), - Sha384(Oaep), - Sha512(Oaep), -} +use crate::{ + provider::{AesMode, CryptoProvider}, + CRYPTO_PROVIDER, +}; 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 +71,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,19 +117,39 @@ 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 => { + let tag_len = (*tag_length as usize) / 8; + if data.len() < tag_len { + return Err(Exception::throw_message(ctx, "Invalid ciphertext length")); + } + // Pass the full data (ciphertext + tag) to the decrypt function + CRYPTO_PROVIDER + .aes_decrypt( + AesMode::Gcm { + tag_length: *tag_length, + }, + handle, + iv, + data, + aad, + ) + .or_throw(ctx)? + }, } }, EncryptionAlgorithm::AesKw => { @@ -136,130 +163,54 @@ pub fn encrypt_decrypt( 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) - }; - - 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 => { + // Pad data to multiple of 8 bytes if needed + 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_data = Cow::Owned(padded) } + CRYPTO_PROVIDER + .aes_kw_wrap(handle, &padded_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 => { + 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 } }, - _ => 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)?, } }, }; 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 => OaepPadding::Sha256(Oaep::new()), - ShaAlgorithm::SHA384 => OaepPadding::Sha384(Oaep::new()), - ShaAlgorithm::SHA512 => OaepPadding::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()), - } - } - } - - Ok(padding) -} 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/generate_key.rs b/modules/llrt_crypto/src/subtle/generate_key.rs index 1a8abc744f..8ace80585b 100644 --- a/modules/llrt_crypto/src/subtle/generate_key.rs +++ b/modules/llrt_crypto/src/subtle/generate_key.rs @@ -1,24 +1,15 @@ // 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::{sha_hash::ShaAlgorithm, CryptoKey, SYSTEM_RANDOM}; +use crate::{provider::CryptoProvider, CRYPTO_PROVIDER}; + +use crate::{hash::HashAlgorithm, subtle::CryptoKey}; use super::{ algorithm_not_supported_error, crypto_key::KeyKind, key_algorithm::{KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages}, - EllipticCurve, }; pub async fn subtle_generate_key<'js>( @@ -85,130 +76,56 @@ 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![]; - }, - 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(); + let key = CRYPTO_PROVIDER + .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).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_symmetric_key(ctx: &Ctx<'_>, length: usize) -> Result> { - let mut key = vec![0u8; length]; - SYSTEM_RANDOM.fill(&mut key).or_throw(ctx)?; - Ok(key) +#[allow(dead_code)] +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 { +#[allow(dead_code)] +pub fn get_hash_length(ctx: &Ctx, hash: &HashAlgorithm, 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/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/key_algorithm.rs b/modules/llrt_crypto/src/subtle/key_algorithm.rs index c0808100b7..ede85f8db8 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; +use crate::hash::HashAlgorithm; +#[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)] @@ -175,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, }, @@ -238,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, @@ -303,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>, @@ -310,96 +755,45 @@ impl KeyAlgorithm { value: Value<'js>, usages: Array<'js>, ) -> Result { + // 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( + 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" => { - 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 - }; - - KeyUsage::classify_and_check_usages( - ctx, - KeyUsageAlgorithm::Sign, - &usages, - &mut private_usages, - &mut public_usages, - key_kind.as_deref(), - )?; - KeyAlgorithm::Ed25519 - }, - "X25519" => { - 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 - }; - - 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" => { - 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; - - 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, @@ -411,7 +805,6 @@ impl KeyAlgorithm { &mut public_usages, KeyUsageAlgorithm::Derive, )?, - "ECDSA" => Self::from_ec( ctx, mode, @@ -423,134 +816,42 @@ impl KeyAlgorithm { &mut public_usages, KeyUsageAlgorithm::Sign, )?, - "HMAC" => { - let obj = obj.or_throw(ctx)?; - let hash = extract_sha_hash(ctx, &obj)?; - - let mut length = obj.get_optional("length")?.unwrap_or_default(); - - 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 - }; - - 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)?; - - 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) - }; - - 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) = match mode { - 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) = match mode { - 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), }; @@ -619,9 +920,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 +933,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 +956,7 @@ impl KeyAlgorithm { } } +#[cfg(feature = "_subtle-full")] fn import_derive_key<'js>( ctx: &Ctx<'js>, format: KeyFormatData<'js>, @@ -672,14 +977,20 @@ fn import_derive_key<'js>( Ok(()) } +#[cfg(feature = "_subtle-full")] fn import_rsa_key<'js>( ctx: &Ctx<'js>, format: KeyFormatData<'js>, kind: &mut KeyKind, data: &mut Vec, algorithm_name: &str, - hash: &ShaAlgorithm, + hash: &HashAlgorithm, ) -> 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 +998,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 +1033,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,13 +1118,14 @@ 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>, kind: &mut KeyKind, data: &mut Vec, algorithm_name: &str, - hash: Option<&ShaAlgorithm>, + hash: Option<&HashAlgorithm>, ) -> Result { *kind = KeyKind::Secret; @@ -878,6 +1173,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 +1188,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 +1222,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>, @@ -1084,7 +1367,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() @@ -1096,16 +1379,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) } -pub fn hash_mismatch_error(ctx: &Ctx<'_>, hash: &ShaAlgorithm) -> Result { +#[cfg(feature = "_subtle-full")] +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 480e1f1bc3..c04514bf9c 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 = "_subtle-full")] mod export_key; mod generate_key; +#[cfg(feature = "_subtle-full")] mod import_key; +#[cfg(feature = "_subtle-full")] mod key_algorithm; mod sign; mod sign_algorithm; mod verify; +#[cfg(feature = "_subtle-full")] mod wrapping; pub use crypto_key::CryptoKey; @@ -21,36 +25,32 @@ pub use derive::subtle_derive_key; pub use digest::subtle_digest; pub use encryption::subtle_decrypt; pub use encryption::subtle_encrypt; +#[cfg(feature = "_subtle-full")] pub use export_key::subtle_export_key; pub use generate_key::subtle_generate_key; +#[cfg(feature = "_subtle-full")] pub use import_key::subtle_import_key; +#[cfg(feature = "_subtle-full")] use key_algorithm::KeyAlgorithm; -use ring::digest::Digest; pub use sign::subtle_sign; pub use verify::subtle_verify; +#[cfg(feature = "_subtle-full")] pub use wrapping::subtle_unwrap_key; +#[cfg(feature = "_subtle-full")] pub use wrapping::subtle_wrap_key; -use aes::cipher::BlockModeDecrypt; -use aes::cipher::BlockModeEncrypt; +// Stub implementations for limited crypto providers (no _subtle-full) +#[cfg(not(feature = "_subtle-full"))] +mod key_algorithm; +#[cfg(not(feature = "_subtle-full"))] +use key_algorithm::KeyAlgorithm; -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}; use llrt_utils::{object::ObjectExt, str_enum}; use rquickjs::{atom::PredefinedAtom, Ctx, Exception, Object, Result, Value}; -use crate::sha_hash::ShaAlgorithm; +use crate::provider::{CryptoProvider, SimpleDigest}; + +use crate::hash::HashAlgorithm; #[rquickjs::class] #[derive(rquickjs::JsLifetime, rquickjs::class::Trace)] @@ -69,238 +69,7 @@ impl SubtleCrypto { } } -pub enum AesCbcEncVariant { - Aes128(cbc::Encryptor), - Aes192(cbc::Encryptor), - Aes256(cbc::Encryptor), -} - -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), - } - } -} - -pub enum AesCbcDecVariant { - Aes128(cbc::Decryptor), - Aes192(cbc::Decryptor), - Aes256(cbc::Decryptor), -} - -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)?, - }) - } -} - -pub enum AesCtrVariant { - Aes128Ctr32(Ctr32BE), - Aes128Ctr64(Ctr64BE), - Aes128Ctr128(Ctr128BE), - Aes192Ctr32(Ctr32BE), - Aes192Ctr64(Ctr64BE), - Aes192Ctr128(Ctr128BE), - Aes256Ctr32(Ctr32BE), - Aes256Ctr64(Ctr64BE), - Aes256Ctr128(Ctr128BE), -} - -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), -} - -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), - } - } -} - -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum EllipticCurve { P256, P384, @@ -311,6 +80,7 @@ str_enum!(EllipticCurve,P256 => "P-256", P384 => "P-384", P521 => "P-521"); pub enum EncryptionMode { Encryption, + #[allow(dead_code)] Wrapping(u8), //padding byte } @@ -319,14 +89,14 @@ pub fn rsa_hash_digest<'a>( key: &'a CryptoKey, data: &'a [u8], algorithm_name: &str, -) -> Result<(&'a ShaAlgorithm, Digest)> { +) -> 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, @@ -334,17 +104,39 @@ 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)) } -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>( @@ -378,3 +170,15 @@ 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 providers without _subtle-full +#[cfg(not(feature = "_subtle-full"))] +mod stubs; +#[cfg(not(feature = "_subtle-full"))] +pub use stubs::subtle_export_key; +#[cfg(not(feature = "_subtle-full"))] +pub use stubs::subtle_import_key; +#[cfg(not(feature = "_subtle-full"))] +pub use stubs::subtle_unwrap_key; +#[cfg(not(feature = "_subtle-full"))] +pub use stubs::subtle_wrap_key; diff --git a/modules/llrt_crypto/src/subtle/sign.rs b/modules/llrt_crypto/src/subtle/sign.rs index 405d518fe9..ed4541fcbd 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)? }, }) } @@ -154,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, 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_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)? }, }) } diff --git a/modules/llrt_fetch/Cargo.toml b/modules/llrt_fetch/Cargo.toml index 6c019ae97e..47299fb9c5 100644 --- a/modules/llrt_fetch/Cargo.toml +++ b/modules/llrt_fetch/Cargo.toml @@ -23,6 +23,11 @@ compression-rust = ["llrt_compression/all-rust"] webpki-roots = ["llrt_http/webpki-roots"] native-roots = ["llrt_http/native-roots"] +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 } either = { version = "1", default-features = false } @@ -60,5 +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" } +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 d95359d36c..159505b922 100644 --- a/modules/llrt_fetch/src/fetch.rs +++ b/modules/llrt_fetch/src/fetch.rs @@ -483,8 +483,19 @@ fn get_option<'js, V: FromJs<'js> + Sized>( mod tests { use std::io::Read; + #[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + all(feature = "tls-graviola", target_arch = "x86_64") + ))] 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", + 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}; @@ -863,6 +874,11 @@ mod tests { .await; } + #[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(); @@ -905,6 +921,11 @@ mod tests { .await; } + #[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/modules/llrt_http/Cargo.toml b/modules/llrt_http/Cargo.toml index 00e492fca8..c3d62469c5 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 = ["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 = ["http1", "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..d60a2727c6 100644 --- a/modules/llrt_http/src/client.rs +++ b/modules/llrt_http/src/client.rs @@ -2,54 +2,127 @@ 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_pool_idle_timeout; + +#[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] +use crate::get_http_version; + +// 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, _| { + #[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(()) + }); + + 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_http/src/lib.rs b/modules/llrt_http/src/lib.rs index 1f80f80d7a..9f2b5703e4 100644 --- a/modules/llrt_http/src/lib.rs +++ b/modules/llrt_http/src/lib.rs @@ -3,23 +3,46 @@ use llrt_utils::module::{export_default, ModuleInfo}; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, - Class, Ctx, Result, + Ctx, Result, }; -pub use self::agent::Agent; -pub use self::client::*; pub use self::config::*; -mod agent; +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] mod client; mod config; -// Here we should also add the http module. +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] +mod agent; + +#[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" +))] +pub use self::{agent::Agent, client::*}; 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,8 +50,15 @@ impl ModuleDef for HttpsModule { fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { - Class::::define(default)?; + #[cfg(any( + feature = "tls-ring", + feature = "tls-aws-lc", + feature = "tls-graviola", + feature = "tls-openssl" + ))] + rquickjs::Class::::define(default)?; + let _ = default; Ok(()) }) } 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..8c88f029a4 100644 --- a/modules/llrt_tls/src/lib.rs +++ b/modules/llrt_tls/src/lib.rs @@ -1,9 +1,39 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -pub use self::config::*; -mod config; +// 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(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/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 new file mode 100644 index 0000000000..60f21267ce --- /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) +} + +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..ab3b2a874a 100644 --- a/modules/llrt_tls/src/config.rs +++ b/modules/llrt_tls/src/rustls_config.rs @@ -1,10 +1,10 @@ // 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; use rustls::{ - crypto::ring, pki_types::{pem::PemObject, CertificateDer}, ClientConfig, RootCertStore, SupportedProtocolVersion, }; @@ -13,6 +13,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 +75,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 diff --git a/tests/unit/child_process.test.ts b/tests/unit/child_process.test.ts index c69f21ac0c..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, { shell: true }); + const child = IS_WINDOWS + ? spawn("ping", ["-n", "999", "localhost"]) + : spawn("sleep", ["999"]); child.on("exit", (code, signal) => { try { @@ -206,20 +207,23 @@ 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); - const killResult = process.kill(detachedPid); - console.log("Kill result:", killResult); + // Clean up the detached process + process.kill(detachedPid, "SIGKILL"); done(); } catch (error) { done(error); 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"]; 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,