diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 42bbb13f..00000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,3 +0,0 @@ -[alias] -clippy-ci = "clippy --all-features --all-targets --all" -clippy-msrv-ci = "clippy --all-features --lib --all" # Only check --lib target w/ MSRV toolchain. diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index e5903e0b..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: -- package-ecosystem: github-actions - directory: "/" - schedule: - interval: weekly diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml deleted file mode 100644 index 9d7be827..00000000 --- a/.github/workflows/audit.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: cargo deny -permissions: - contents: read -on: - schedule: - - cron: '0 0 * * 0' - push: - paths: - - '**/Cargo.toml' - - '**/Cargo.lock' - pull_request: - -jobs: - audit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - uses: EmbarkStudios/cargo-deny-action@v2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 0dd8924a..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,310 +0,0 @@ -on: - push: - branches: ['main', 'ci/*'] - pull_request: - merge_group: - schedule: - - cron: '0 18 * * *' - workflow_dispatch: - -name: CI -permissions: - contents: read - -env: - RUSTFLAGS: -D warnings - -jobs: - clippy-build-std: - name: Clippy (-Zbuild-std) - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - - uses: dtolnay/rust-toolchain@nightly - with: - components: clippy - - - name: Clippy (tvOS) - run: | - rustup component add rust-src --toolchain nightly-aarch64-apple-darwin - cargo +nightly clippy -Zbuild-std --target aarch64-apple-tvos - - - name: Clippy (watchOS) - run: | - rustup component add rust-src --toolchain nightly-aarch64-apple-darwin - cargo +nightly clippy -Zbuild-std --target aarch64-apple-watchos - - - name: Clippy (visionOS) - run: | - rustup component add rust-src --toolchain nightly-aarch64-apple-darwin - cargo +nightly clippy -Zbuild-std --target aarch64-apple-visionos - - clippy: - name: Clippy (stable) - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: - - ubuntu-latest - - macos-latest - - windows-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - - name: Clippy (${{ matrix.os }}) - run: cargo clippy-ci - - - name: Clippy (Android) - if: matrix.os == 'ubuntu-latest' - run: | - rustup target add aarch64-linux-android - cargo install cargo-ndk - cargo ndk -t arm64-v8a clippy-ci - - - name: Clippy (iOS) - if: matrix.os == 'macos-latest' - run: | - rustup target add x86_64-apple-ios - cargo clippy-ci --target x86_64-apple-ios - - - name: Clippy (WASM) - if: matrix.os == 'ubuntu-latest' - run: | - rustup target add wasm32-wasip1 - cargo clippy --lib --target wasm32-wasip1 -- -D warnings - - # Update any flexible dependencies that may have SemVer applicable changes - # to ensure the project keeps building with the whole version range. This is: - # - windows-sys 0.52, which `rustls-platform-verifier` also should accept at 0.59. - - name: Clippy (dependency version checks) - run: | - cargo update -p windows-sys@0.52.0 - cargo clippy-ci - - clippy-msrv: - name: Clippy (MSRV) - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: - - ubuntu-latest - - macos-latest - - windows-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - - uses: dtolnay/rust-toolchain@master - with: - toolchain: "1.85.0" # MSRV - components: clippy - - - name: Install cargo-ndk. - run: | - cargo install cargo-ndk --locked --version 2.12.7 - rustup target add aarch64-linux-android - - - name: Clippy (${{ matrix.os }}) - run: cargo clippy-msrv-ci - - - name: Clippy (Android) - if: matrix.os == 'ubuntu-latest' - run: | - cargo ndk -t arm64-v8a clippy-msrv-ci - - - name: Clippy (iOS) - if: matrix.os == 'macos-latest' - run: | - rustup target add aarch64-apple-ios - cargo clippy-msrv-ci --target aarch64-apple-ios - - # TODO: Consider WASM. See note on "clippy" job. - - test: - name: Test - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: - - ubuntu-latest - - macos-latest - - windows-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - - uses: dtolnay/rust-toolchain@stable - - - name: Test (${{ matrix.os }}) - run: cargo test - - name: Update Android security manifest - if: matrix.os == 'ubuntu-latest' - run: cargo test --manifest-path android-release-support/Cargo.toml --test codegen -- --ignored - - test_android: - name: "Test (Android)" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - # Turn on Linux KVM features/support for faster Android emulation. - # References: - # - https://github.com/DeterminateSystems/nix-installer-action/blob/de22e16c4711fca50c816cc9081563429d1cf563/src/main.ts#L756 - # - https://github.com/ReactiveCircus/android-emulator-runner#running-hardware-accelerated-emulators-on-linux-runners - - name: Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '17' - - - name: Run Android tests - uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # 2.38.0 - with: - api-level: 28 # Android 9, Pie. - arch: x86_64 - profile: pixel - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - working-directory: ./android - script: | - rustup target add x86_64-linux-android - cargo install cargo-ndk - env | grep '^JAVA' - touch emulator.log - chmod 770 emulator.log - adb logcat --clear - adb logcat | grep 'rustls' | tee emulator.log & - ./gradlew connectedDebugAndroidTest - - - name: Upload Android test results - uses: actions/upload-artifact@v4 - # Upload test results if they fail - if: failure() - with: - name: android-test-results - retention-days: 7 - path: | - ./android/emulator.log - /Users/runner/work/rustls-platform-verifier/rustls-platform-verifier/android/rustls-platform-verifier/build/outputs/androidTest-results/connected/test-result.pb - - test_ios: - name: "Test iOS (Catalyst)" - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Run iOS tests - run: | - rustup target add aarch64-apple-ios-macabi - cargo test --target aarch64-apple-ios-macabi - - test-freebsd: - name: Test (FreeBSD) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: test on freebsd - uses: vmactions/freebsd-vm@v1 - # Settings adopted from https://github.com/quinn-rs/quinn - with: - usesh: true - mem: 4096 - copyback: false - prepare: | - pkg install -y curl - curl https://sh.rustup.rs -sSf --output rustup.sh - sh rustup.sh -y --profile minimal --default-toolchain stable - echo "~~~~ rustc --version ~~~~" - $HOME/.cargo/bin/rustc --version - echo "~~~~ freebsd-version ~~~~" - freebsd-version - run: $HOME/.cargo/bin/cargo test - - fmt: - name: Rustfmt - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt - - - run: cargo fmt --all -- --check - - android_fmt: - name: Ktlint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Ktlint - run: | - cd ./android - ./gradlew ktlint - - verify_android: - name: Verify Android artifacts - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Verify release artifact - run: ./ci/verify_android_release.sh - - semver: - name: Check semver compatibility - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Check semver - uses: obi1kenobi/cargo-semver-checks-action@v2 - - docs: - name: Check for documentation errors - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Install rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Build documentation - run: cargo doc --locked --all-features --no-deps --document-private-items - env: - RUSTDOCFLAGS: -Dwarnings --cfg=docsrs diff --git a/.gitignore b/.gitignore index 1263ab66..03f1d64c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,13 +2,7 @@ .DS_Store /.idea -/android/verification/ - -# Ignore all generated Maven local repository files and folders -/android-release-support/maven/org/rustls/rustls-platform-verifier/* -# These two must be kept since the state must be shared between normal branches and the Maven archive one. -!/android-release-support/maven/org/rustls/rustls-platform-verifier/maven-metadata.xml -!/android-release-support/maven/org/rustls/rustls-platform-verifier/maven-metadata-local.xml +/android/ # Nix /result diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index 028e6f5c..00000000 --- a/CHANGELOG +++ /dev/null @@ -1 +0,0 @@ -The detailed list of changes in each release can be found at https://github.com/rustls/rustls-platform-verifier/releases. \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 611a5704..00000000 --- a/Cargo.lock +++ /dev/null @@ -1,2034 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" -dependencies = [ - "memchr", -] - -[[package]] -name = "android_log-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" - -[[package]] -name = "android_logger" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" -dependencies = [ - "android_log-sys", - "env_filter", - "log", -] - -[[package]] -name = "asn1-rs" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror", - "time", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure 0.13.2", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "aws-lc-rs" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", - "pkg-config", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" - -[[package]] -name = "bstr" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" -dependencies = [ - "memchr", - "regex-automata", - "serde_core", -] - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "bytes" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" - -[[package]] -name = "cc" -version = "1.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" - -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - -[[package]] -name = "chacha20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" -dependencies = [ - "cfg-if", - "cpufeatures", - "rand_core", -] - -[[package]] -name = "cmake" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" -dependencies = [ - "cc", -] - -[[package]] -name = "combine" -version = "4.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "console" -version = "0.16.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96a4956774c13c126a8b5af4daa79384f4d826534c95a02d76afb39e2ab64e3" -dependencies = [ - "encode_unicode", - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" -dependencies = [ - "libc", -] - -[[package]] -name = "csv" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" -dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde_core", -] - -[[package]] -name = "csv-core" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" -dependencies = [ - "memchr", -] - -[[package]] -name = "data-encoding" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" - -[[package]] -name = "defmt" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" -dependencies = [ - "defmt-parser", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "defmt-parser" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" -dependencies = [ - "thiserror", -] - -[[package]] -name = "der-parser" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom", - "num-bigint 0.4.8", - "num-traits", - "rusticata-macros", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" - -[[package]] -name = "displaydoc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - -[[package]] -name = "env_filter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -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-channel" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" - -[[package]] -name = "futures-task" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" - -[[package]] -name = "futures-util" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "rand_core", - "wasm-bindgen", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "http" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" - -[[package]] -name = "icu_properties" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" - -[[package]] -name = "icu_provider" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "ipnet" -version = "2.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jiff" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" -dependencies = [ - "defmt", - "jiff-core", - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", -] - -[[package]] -name = "jiff-core" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" -dependencies = [ - "defmt", - "log", -] - -[[package]] -name = "jiff-static" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" -dependencies = [ - "jiff-core", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys", - "log", - "simd_cesu8", - "thiserror", - "walkdir", - "windows-link", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.119", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "jobserver" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" -dependencies = [ - "getrandom 0.4.3", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.189" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" - -[[package]] -name = "litemap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" - -[[package]] -name = "log" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" - -[[package]] -name = "lru-slab" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "mio" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "num-integer" -version = "0.1.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "oid-registry" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" -dependencies = [ - "asn1-rs", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkg-config" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" - -[[package]] -name = "portable-atomic" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" - -[[package]] -name = "portable-atomic-util" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "potential_utf" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quinn" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc" -dependencies = [ - "aws-lc-rs", - "bytes", - "getrandom 0.4.3", - "lru-slab", - "rand", - "rand_pcg", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.61.2", -] - -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" -dependencies = [ - "chacha20", - "getrandom 0.4.3", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - -[[package]] -name = "rand_pcg" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" -dependencies = [ - "rand_core", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "reqwest" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" -dependencies = [ - "base64 0.23.1", - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde", - "serde_json", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom", -] - -[[package]] -name = "rustls" -version = "0.23.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" -dependencies = [ - "aws-lc-rs", - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-platform-verifier" -version = "0.7.0" -dependencies = [ - "android_logger", - "base64 0.22.1", - "core-foundation", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android 0.1.1", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" -dependencies = [ - "core-foundation", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -dependencies = [ - "rustls", - "similar-asserts", - "tokio", - "webpki-ccadb", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - -[[package]] -name = "rustls-webpki" -version = "0.103.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" -dependencies = [ - "aws-lc-rs", - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags 2.13.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "serde_json" -version = "1.0.151" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "simd_cesu8" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" -dependencies = [ - "rustc_version", - "simdutf8", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "similar" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f66ca1f7aca2474dc10c942eb22feffc897735f54cd1db90138c2fddb490987" -dependencies = [ - "bstr", - "unicode-segmentation", -] - -[[package]] -name = "similar-asserts" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "997e6ca38e97437973fc9f7f50a50d1274cacd874341a4960fea90067291038c" -dependencies = [ - "console", - "similar", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" - -[[package]] -name = "socket2" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "synstructure" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "thiserror" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "time" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "time-macros" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" - -[[package]] -name = "tokio" -version = "1.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" -dependencies = [ - "bitflags 2.13.2", - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", - "url", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "unicode-ident" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" - -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasm-bindgen" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 3.0.6", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-ccadb" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b22a93e00e215769c99c46f74e90a20153f97a21c0f9881530d27ae2738c9010" -dependencies = [ - "csv", - "hex", - "jiff", - "num-bigint 0.5.1", - "reqwest", - "rustls-pki-types", - "rustls-webpki", - "serde", - "serde_json", - "url", - "x509-parser", - "yasna", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "writeable" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" - -[[package]] -name = "x509-parser" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" -dependencies = [ - "asn1-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "rusticata-macros", - "thiserror", - "time", -] - -[[package]] -name = "yasna" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", - "synstructure 0.14.0", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", - "synstructure 0.14.0", -] - -[[package]] -name = "zeroize" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" - -[[package]] -name = "zerotrie" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 91d9d305..00000000 --- a/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[workspace] -members = [ - "android-release-support", - "rustls-platform-verifier", -] -resolver = "2" diff --git a/LICENSE-APACHE b/LICENSE-APACHE deleted file mode 100644 index 261eeb9e..00000000 --- a/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT deleted file mode 100644 index 99641226..00000000 --- a/LICENSE-MIT +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 1Password - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index f77c9ba0..3c933e99 100644 --- a/README.md +++ b/README.md @@ -1,329 +1,7 @@ -# rustls-platform-verifier +# rustls-platform-verifier Maven Archive -[![crates.io version](https://img.shields.io/crates/v/rustls-platform-verifier.svg)](https://crates.io/crates/rustls-platform-verifier) -[![crate documentation](https://docs.rs/rustls-platform-verifier/badge.svg)](https://docs.rs/rustls-platform-verifier) -![MSRV](https://img.shields.io/badge/rustc-1.85+-blue.svg) -[![crates.io downloads](https://img.shields.io/crates/d/rustls-platform-verifier.svg)](https://crates.io/crates/rustls-platform-verifier) -![CI](https://github.com/1Password/rustls-platform-verifier/workflows/CI/badge.svg) +This branch hosts a Maven package repository for the native Android component of `rustls-platform-verifier`. Source code and other parts of +this repository's normal branches are not included here. -A Rust library to verify the validity of TLS certificates based on the operating system's certificate facilities. -On operating systems that don't have these, `webpki` and/or `rustls-native-certs` is used instead. - -This crate is advantageous over `rustls-native-certs` on its own for a few reasons: -- Improved correctness and security, as the OSes [CA constraints](https://support.apple.com/en-us/HT212865) will be taken into account. -- Better integration with OS certificate stores and enterprise CA deployments. -- Revocation support via verifying validity via OCSP and CRLs. -- Less I/O and memory overhead because all the platform CAs don't need to be loaded and parsed. - -This library supports the following platforms and flows: - -| OS | Certificate Store | Verification Method | Revocation Support | -|----------------|-----------------------------------------------|--------------------------------------|--------------------| -| Windows | Windows platform certificate store | Windows API certificate verification | Yes | -| macOS (10.14+) | macOS platform roots and keychain certificate | macOS `Security.framework` | Yes | -| iOS | iOS platform roots and keychain certificates | iOS `Security.framework` | Yes | -| Android | Android System Trust Store | Android Trust Manager | Sometimes[^1] | -| Linux | System CA bundle, or user-provided certs[^3] | webpki | No[^2] | -| WASM | webpki roots | webpki | No[^2] | - -[^1]: On Android, revocation checking requires API version >= 24 (e.g. at least Android 7.0, August 2016). -When available, revocation checking is only performed for the end-entity certificate. If a stapled OCSP -response for the end-entity cert isn't provided, and the certificate omits both a OCSP responder URL and -CRL distribution point to fetch revocation information from, revocation checking may fail. - -[^2]: The fall-back webpki verifier configured for Linux/WASM does not support providing CRLs for revocation -checking. If you require revocation checking on these platforms, prefer constructing your own -`WebPkiServerVerifier`, providing necessary CRLs. See the Rustls [`ServerCertVerifierBuilder`] docs for more -information. - -[^3]: On Linux the [rustls-native-certs] and [openssl-probe] crates are used to try and discover the system CA bundle. -Users may wish to augment these certificates with [webpki-roots] using [`Verifier::new_with_extra_roots`] in case -a system CA bundle is unavailable. - -[`ServerCertVerifierBuilder`]: https://docs.rs/rustls/latest/rustls/client/struct.ServerCertVerifierBuilder.html -[`Verifier::new_with_extra_roots`]: https://docs.rs/rustls-platform-verifier/latest/rustls_platform_verifier/struct.Verifier.html#method.new_with_extra_roots -[rustls-native-certs]: https://github.com/rustls/rustls-native-certs -[openssl-probe]: https://github.com/alexcrichton/openssl-probe -[webpki-roots]: https://github.com/rustls/webpki-roots - -## Deployment Considerations - -When choosing to use `rustls-platform-verifier` or another trust store option, these differences are important to consider. They -are primarily about root certificate availability: - -| Backend | Updates | Roots used | Supports system-local roots | -|-------------------------------------------------|---------------------------------|-------------------------------------------------------------------------------------------------------|------------------------------| -| `rustls-platform-verifier` (non-Linux/BSD) | Updated by OS | System store, with full (dis)trust decisions from every source available. | Yes | -| `rustls-native-certs` + `webpki` | Updated by OS | System store, with no (dis)trust decisions. All roots are treated equally regardless of their status. | Yes, with exceptions | -| `webpki-roots` + `webpki` | Static, manual updates required | Hardcoded Mozilla CA roots, limited support for constrained roots. | No | - -**In general**: It is the opinion of the `rustls` and `rustls-platform-verifier` teams that this is the best default available for client-side libraries and applications -making connections to TLS servers when running on common operating systems. This is because it gets both live trust information (new roots, explicit markers, and auto-managed CRLs) -and better matches the common expectation of apps running on that platform (to use proxies, for example). Otherwise, it becomes your maintenance burden to -ship updates right away in order to handle increasing numbers of positive and negative trust events in the WebPKI/certificate ecosystem, or risk availability and security concerns. - -#### Linux/BSD -As of the time of writing, `rustls-platform-verifier` on these OSes only loads the trust stores from the OS once upon startup. This is the same behavior as `rustls-native-certs`, but the -abstraction allows better behavior on the other platforms without extra work for downstreams. - -#### Other - -Alternatively, there is a clear answer to use static `webpki-roots` in your application instead if you are deploying containerized applications frequently, where root store changes -will make it to production faster and any possibly used trust root is static by definition. - -Even though platform verifiers are sometimes implemented in memory-unsafe languages, it is very unlikely that Rust apps using this library will become a point of weakness. -This is due to either using a smaller set of servers or just being less exposed then other critical functions of the operating system, default web browser, etc. -But if your activity is identical or close to one of the following examples that process large amounts of untrusted input, a 100% Rust option like `webpki` is a more secure option: -- Seeing how many TLS servers `rustls` with a specific configuration can connect to. -- Harvesting data from various untrusted TLS endpoints exposed on the internet. -- Extracting info from a known-evil endpoint. -- Scanning all TLS certificates on the open internet. - -`rustls-platform-verifier` is widely deployed by several applications that use the `rustls` stack, such as 1Password, Bitwarden, Signal, and `rustup`, on a wide set of OSes. -This means that it has received lots of exposure to edge cases and has real-world experience/expertise invested into it to ensure optimal compatibility and security. - -## Installation and setup -On most platforms, no setup should be required beyond adding the dependency via `cargo`: -```toml -rustls-platform-verifier = "0.5" -``` - -To get a rustls `ClientConfig` configured to use the platform verifier use: - -```rust -use rustls::ClientConfig; -use rustls_platform_verifier::ConfigVerifierExt; -let config = ClientConfig::with_platform_verifier(); -``` - -This crate will use the [rustls process-default crypto provider](https://docs.rs/rustls/latest/rustls/crypto/struct.CryptoProvider.html#using-the-per-process-default-cryptoprovider). To construct a `ClientConfig` with a different `CryptoProvider`, use: - -```rust -use rustls::ClientConfig; -use rustls_platform_verifier::BuilderVerifierExt; -let arc_crypto_provider = std::sync::Arc::new(rustls::crypto::aws_lc_rs::default_provider()); -let config = ClientConfig::builder_with_provider(arc_crypto_provider) - .with_safe_default_protocol_versions() - .unwrap() - .with_platform_verifier() - .unwrap() - .with_no_client_auth(); -``` - -### Android -Some manual setup is required, outside of `cargo`, to use this crate on Android. In order to -use Android's certificate verifier, the crate needs to call into the JVM. A small Kotlin -component must be included in your app's build to support `rustls-platform-verifier`. - -#### Gradle Setup - -`rustls-platform-verifier` distributes the required native components in a Maven-compatible format via GitHub, but the project must be setup to locate them -automatically and correctly. These steps assume you are using `.gradle` Groovy files because they're the most common, but if you are using -Kotlin scripts (`.gradle.kts`) for configuration instead, an example snippet is included towards the end of this section. - -Each snippet includes a [`ValueSource`](https://docs.gradle.org/current/javadoc/org/gradle/api/provider/ValueSource.html) implementation that obtains a -Cargo-synchronized dependency version performantly, and is also friendly to Gradle's configuration cache. The version can be be selected manually instead, -but runtime crashes may occur if a SemVer incompatible version is used. - -Inside of your project's `build.gradle` file, add the following code and Maven repository definition: - -`$PATH_TO_LOCK_FILE` is the relative path to the Cargo lockfile of your crate or workspace (`Cargo.lock`). - -```groovy - -repositories { - maven { - url = "https://github.com/rustls/rustls-platform-verifier/raw/maven-archive/android-release-support/maven/" - } -} - -abstract class RustlsVersion implements ValueSource { - interface Params extends ValueSourceParameters { - RegularFileProperty getLockFile() - } - - static final String CRATE_NAME = "rustls-platform-verifier-android" - - @Override - String obtain() { - def lockFile = parameters.lockFile.get().asFile - def lines = lockFile.readLines() - def idx = lines.findIndexOf { it.trim() == "name = \"$CRATE_NAME\"" } - def version = idx < 0 ? null : lines.drop(idx + 1) - .find { it.stripLeading().startsWith("version = ") } - ?.find(/"([^"]*)"/) { match, v -> v } - if (!version) throw new GradleException("$CRATE_NAME not found in $lockFile") - return version - } -} - -def rustlsPlatformVerifierVersion = providers.of(RustlsVersion) { spec -> - spec.parameters.lockFile.set(layout.projectDirectory.file($PATH_TO_LOCK_FILE)) -} - -configurations.configureEach { configuration -> - configuration.resolutionStrategy.eachDependency { details -> - if (details.requested.group == "org.rustls" && details.requested.name == "rustls-platform-verifier") { - details.useVersion(rustlsPlatformVerifierVersion.get()) - details.because("native component version must be identical to version of ${RustlsVersion.CRATE_NAME}") - } - } -} -``` - -Then, wherever you declare your dependencies, add the following: -```groovy -implementation "rustls:rustls-platform-verifier" -``` - -The dependency intentionally has no static version, it is only resolved dynamically at configuration time by the build script. - -Cargo automatically handles finding the downloaded crate in the correct location for your project. It also handles updating the version when -new releases of `rustls-platform-verifier` are published. If you only use published releases, no extra maintenance should be required. - -These script snippets can be tweaked as best suits your project, but the `cargo metadata` invocation must be included so that the Android -implementation part can be located on-disk. - -##### Kotlin and Gradle - -`build.gradle.kts`: -```kotlin - -repositories { - maven { - url = uri("https://github.com/rustls/rustls-platform-verifier/raw/maven-archive/android-release-support/maven/") - } -} - -abstract class RustlsVersion : ValueSource { - interface Params : ValueSourceParameters { - val lockFile: RegularFileProperty - } - - companion object { - const val CRATE_NAME = "rustls-platform-verifier-android" - } - - override fun obtain(): String { - val version = parameters.lockFile.get().asFile.readLines().let { lines -> - val nameIdx = lines.indexOfFirst { it.trim() == "name = \"$CRATE_NAME\"" } - if (nameIdx < 0) { - null - } else { - lines.drop(nameIdx + 1) - .firstOrNull { it.trimStart().startsWith("version = ") } - ?.substringAfter('"', "") - ?.substringBefore('"', "") - ?.takeIf { it.isNotEmpty() } - } - } - return version?: error("$CRATE_NAME not found in Cargo.lock") - } -} - -val rustlsPlatformVerifierVersion = providers.of(RustlsVersion::class.java) { - parameters.lockFile.set(layout.projectDirectory.file($PATH_TO_LOCK_FILE)) -} - -configurations.configureEach { - resolutionStrategy.eachDependency { - if (requested.group == "org.rustls" && requested.name == "rustls-platform-verifier") { - useVersion(rustlsPlatformVerifierVersion.get()) - because("native component version must be identical to version of ${RustlsVersion.CRATE_NAME}") - } - } -} - -dependencies { - // `rustls-platform-verifier` is a Rust crate, but it also has a Kotlin component. - implementation(libs.rustls.platform.verifier) -} -``` - -`libs.version.toml`: -```toml -# We keep the dependency unversioned because its version is selected dynamically during configuration. -rustls-platform-verifier = { group = "rustls", name = "rustls-platform-verifier" } -``` - -#### Proguard - -If your Android application makes use of Proguard for optimizations, its important to make sure that the Android verifier component isn't optimized -out because it looks like dead code. Proguard is unable to see any JNI usage, so your rules must manually opt into keeping it. The following rule -can do this for you: -```text --keep, includedescriptorclasses class org.rustls.platformverifier.** { *; } -``` - -#### Crate initialization - -In order for the crate to call into the JVM, it needs handles from Android. These -are provided by one of the `init_with_env`, `init_with_refs` or `init_with_runtime` functions. These give `rustls-platform-verifier` -the resources it needs to make calls into the Android certificate verifier. - -As an example, if your Rust Android component which the "native" Android -part of your app calls at startup has an initialization, like this: - -```rust ,ignore -use jni::jni_mangle; -use jni::objects::{JClass, JObject}; -use jni::EnvUnowned; - -#[jni_mangle("com.orgname.application.Application")] -pub fn init<'caller>( - mut unowned_env: EnvUnowned<'caller>, - _class: JClass<'caller>, - context: JObject<'caller>, -) { - // ... initialize your app's other parts here. -} -``` - -In the simplest case, you should to insert a call to `rustls_platform_verifier::android::init_with_env()` here, -before any networking has a chance to run. This only needs to be called once and -the verifier will be valid for the lifetime of your app's process. - -```rust ,ignore -use jni::errors::ThrowRuntimeExAndDefault; -use jni::jni_mangle; -use jni::objects::{JClass, JObject}; -use jni::EnvUnowned; - -#[jni_mangle("com.orgname.application.Application")] -pub fn init<'caller>( - mut unowned_env: EnvUnowned<'caller>, - _class: JClass<'caller>, - context: JObject<'caller>, -) { - // ... initialize your app's other parts here. - - // Then, initialize the certificate verifier for future use. - unowned_env - .with_env(|env| rustls_platform_verifier::android::init_with_env(env, context)) - .resolve::(); -} -``` - -In more advanced cases, such as where your code already stores long-lived handles into -the Android environment, you can alternatively use `init_with_runtime`. This function takes -a `&'static` reference to something that implements the `android::Runtime` trait, which the -crate then uses to obtain the access when required to the JVM. - -## Credits -Made with ❤️ by the [1Password](https://1password.com/) and `rustls` teams. Portions of the Android and Windows verifier -implementations were adapted and referenced from Chromium's previous verifier implementations as well. - -#### License - - -Licensed under either of Apache License, Version -2.0 or MIT license at your option. - - -
- - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in this crate by you, as defined in the Apache-2.0 license, shall -be dual licensed as above, without any additional terms or conditions. - +See the [Gradle setup documentation](https://github.com/rustls/rustls-platform-verifier#gradle-setup) to learn how to include this repository +in your Android app or library's build. diff --git a/admin/MAINTENANCE.md b/admin/MAINTENANCE.md deleted file mode 100644 index dd5c926c..00000000 --- a/admin/MAINTENANCE.md +++ /dev/null @@ -1,31 +0,0 @@ -## How to handle certificate expiry - -When CI starts spuriously failing, it is usually caused by the certificates inside `src/tests/vertification_real_world` reaching their max issuance lifetime and becoming expired. While most -of our tested platforms are able to handle this better by mocking out the verification time, some can't. At the time of writing these are: -- Android ([1](https://github.com/rustls/rustls-platform-verifier/issues/59), [2](https://github.com/rustls/rustls-platform-verifier/issues/183)) -- Windows ([1](https://github.com/rustls/rustls-platform-verifier/issues/117)) - -The other case that can cause failures (much less often) is the mock certificates expiring. Due to platform verifier security restrictions, we can't place absurdly high/unlimited expiry dates -on our mock CA and the certificates issued by it. As such, they will expire about every 2 years and need updated by hand. - -Thankfully, updating these has become easy: -- If the `verification_real_world` tests are failing, do the following: - 1. Run `cargo run --example update-certs` - 2. Using your tool of choice, update the hardcoded time in `verification_time` to match the current datetime. - 3. Commit your changes and push up a fix branch/PR. -- If the `verification_mock` tests are failing, do the following: - 1. Run `cd rustls-platform-verifier/src/tests/verification_mock` - 2. Run `go run ca.go` - 3. Using your tool of choice, update the hardcoded time in `verification_time` to match the current datetime. - 4. Commit your changes and push up a fix branch/PR. - -## Updated CRL host list - -In order to facilitate Android downloading CRLs over unsecured HTTP, we have a list of CRL hosts in -`/android/rustls-platform-verifier/src/main/res/xml/network_security_config.xml`. This list is populated -by the codegen test in `/android-release-support/tests/codegen.rs` in CI. - -To update: - - 1. Run `cargo test --manifest-path android-release-support/Cargo.toml --test codegen -- --ignored` - 2. Commit the changes to `network_security_config.xml` and push up a fix branch/PR. diff --git a/admin/RELEASING.md b/admin/RELEASING.md deleted file mode 100644 index 9c5c5bf2..00000000 --- a/admin/RELEASING.md +++ /dev/null @@ -1,44 +0,0 @@ -# How-to release `rustls-platform-verifier` - -This document records the steps to publish new versions of the crate since it requires non-trivial preparation and ordering -that needs to be accounted for due to the Android component's distribution. - -The Rustls repo also has [RELEASING] guidance for more information (e.g. on best practices for creating a GitHub release with a changelog) -and other steps. - -In the release preparation PR, the releaser may include the following checklist in the description so post-merge actions can be tracked: -```markdown -### Post-merge steps - -- [ ] Generate Android Maven artifacts locally -- [ ] Create and push Git tag -- [ ] `cargo publish` for each required crate, based on release steps -- [ ] Create companion GitHub release -``` - -## Steps - -1. Update main crate's version in `rustls-platform-verifier/Cargo.toml`. -2. If any non-test changes have been made to the `android` directory since the last release: - 1. Update Android artifact version in `android-release-support/Cargo.toml`, and in the main crate if creating an incompatible SemVer release. - 2. Commit version increase changes on the release branch - * We typically name these branches `rel-xxx` where `xxx` is the major version. - * We typically leave these branches around for future maintenance releases. - 3. Run `ci/package_android_release.sh` in a UNIX compatible shell - 4. Commit the Maven metadata updates on their own: `git commit -am "Bump Maven release to x.x.x"`. Copy the new commit's short ID. - 5. **Ensure that all version changes are committed to the correct branch before proceeding**. All version increases should be checked in prior - to publishing on crates.io. - 6. Checkout the Maven storage branch: `git checkout maven-archive`. The newly built artifacts are now ready to check in. - 7. Add the new artifacts to storage: `git add . && git commit -m "Prepare Maven release x.x.x"` - 8. Sync the Maven metadata to make the new artifacts visible: `git cherry-pick $MAVEN_BUMP_COMMIT_ID` - 9. Publish the new changes: - * `git push && git checkout rel-xxx` - * Publish the new Android marker version: `cargo publish -p rustls-platform-verifier-android` - -3. Commit main crate's version increase on the release branch -4. **Ensure that all version changes are committed to the correct branch before proceeding**. All version increases should be checked in prior - to publishing on crates.io. -5. Publish the main crate's new version: `cargo publish -p rustls-platform-verifier` -6. Follow the remaining steps in [RELEASING] to create the appropiate version tag. - -[RELEASING]: https://github.com/rustls/rustls/blob/main/RELEASING.md diff --git a/android-release-support/Cargo.toml b/android-release-support/Cargo.toml deleted file mode 100644 index 0ea6279c..00000000 --- a/android-release-support/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "rustls-platform-verifier-android" -version = "0.1.1" -description = "The internal JVM support component of the rustls-platform-verifier crate. You shouldn't depend on this directly." -repository = "https://github.com/rustls/rustls-platform-verifier" -license = "MIT OR Apache-2.0" -edition = "2021" - -include = [ - "src/*", -] - -[dependencies] - -[dev-dependencies] -rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] } -similar-asserts = "2" -tokio = { version = "1", features = ["macros"] } -webpki-ccadb = "0.2.2" diff --git a/android-release-support/LICENSE-APACHE b/android-release-support/LICENSE-APACHE deleted file mode 120000 index 965b606f..00000000 --- a/android-release-support/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE \ No newline at end of file diff --git a/android-release-support/LICENSE-MIT b/android-release-support/LICENSE-MIT deleted file mode 120000 index 76219eb7..00000000 --- a/android-release-support/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT \ No newline at end of file diff --git a/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.aar b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.aar new file mode 100644 index 00000000..7b43101c Binary files /dev/null and b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.aar differ diff --git a/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.aar.sha1 b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.aar.sha1 new file mode 100644 index 00000000..7dd3ea65 --- /dev/null +++ b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.aar.sha1 @@ -0,0 +1 @@ +d47d4f1397843908b7130aec2a32043532179e51 org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.aar \ No newline at end of file diff --git a/android-release-support/pom-template.xml b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.pom similarity index 94% rename from android-release-support/pom-template.xml rename to android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.pom index 04e43c69..204bb324 100644 --- a/android-release-support/pom-template.xml +++ b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.pom @@ -4,7 +4,7 @@ 4.0.0 org.rustls rustls-platform-verifier - $VERSION + 0.1.0 aar The internal JVM support component of the rustls-platform-verifier Rust crate \ No newline at end of file diff --git a/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.pom.sha1 b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.pom.sha1 new file mode 100644 index 00000000..3d0485a1 --- /dev/null +++ b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.pom.sha1 @@ -0,0 +1 @@ +8c821e411aeacf2422207310f081a1314ec308ac org/rustls/rustls-platform-verifier/0.1.0/rustls-platform-verifier-0.1.0.pom \ No newline at end of file diff --git a/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.aar b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.aar new file mode 100644 index 00000000..8acc8b5f Binary files /dev/null and b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.aar differ diff --git a/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.aar.sha1 b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.aar.sha1 new file mode 100644 index 00000000..c3e618a7 --- /dev/null +++ b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.aar.sha1 @@ -0,0 +1 @@ +2b7b3f8a939f98d1bc9a63cebf9bbf722de2fbd9 org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.aar \ No newline at end of file diff --git a/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.pom b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.pom new file mode 100644 index 00000000..4cfeddd7 --- /dev/null +++ b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.pom @@ -0,0 +1,10 @@ + + + 4.0.0 + org.rustls + rustls-platform-verifier + 0.1.1 + aar + The internal JVM support component of the rustls-platform-verifier Rust crate + \ No newline at end of file diff --git a/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.pom.sha1 b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.pom.sha1 new file mode 100644 index 00000000..1ae7a456 --- /dev/null +++ b/android-release-support/maven/org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.pom.sha1 @@ -0,0 +1 @@ +87e5707521957cf21990213144a74578c0d78bbf org/rustls/rustls-platform-verifier/0.1.1/rustls-platform-verifier-0.1.1.pom \ No newline at end of file diff --git a/android-release-support/src/lib.rs b/android-release-support/src/lib.rs deleted file mode 100644 index 6bb66dbc..00000000 --- a/android-release-support/src/lib.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! # rustls-platform-verifier-android -//! -//! This crate is an implementation detail of the actual [rustls-platform-verifier](https://github.com/rustls/rustls-platform-verifier) crate. -//! -//! It contains no Rust code and is solely intended as a convenient mechanism to synchronize a SemVer version managed by `cargo` to Gradle in -//! Android build systems in such a way that a SemVer incompatible version of the native component is never used. -//! -//! Other crates should not directly depend on this crate in any way, as nothing about it is considered stable and it is probably useless elsewhere. -//! -//! ## Details -//! -//! Note: Everything in this section is subject to change at any time. Semver may not be followed. -//! -//! ### Why? -//! -//! It is the best known middle ground between several tradeoffs. The important ones, in priority order, are: -//! - Automatically keeping component versions in sync -//! - Allowing well-tested and well-known `cargo` dependency management patterns to apply everywhere -//! - Providing a smooth developer experience as an Android consumer of `rustls-platform-verifier` -//! -//! Firstly, what alternatives are available for distributing the component? The other known ones are: -//! - Source distribution in some form (here, it will be through crates.io) -//! - Maven Central (or another hosted package registry) -//! - Bundling Android release artifacts inside crates.io releases -//! -//! Starting with the first, its infeasible due to toolchain syncing requirements. If the Android component is built as part of the host -//! app's Gradle build, then it becomes subject to any Gradle or Android Gradle Plugin incompatibilities/requirements. In practice this means -//! the AGP version between this project and the main application have to match all the time. Sometimes this works, but it becomes challenging/unfeasible -//! during yearly toolchain/SDK upgrades and is not maintainable long term. -//! -//! Next, Maven Central. This is considered the standard way of distributing public Android dependencies. There are two downsides to this -//! approach: version synchronization and publishing overhead. Version syncing is the hardest part: There's not a good way to know what version -//! a crate is that doesn't hurt the Cargo part of the build or damage functionality. So instead of making assumptions at runtime, we would need to do -//! clunky and manual version counting with an extra error case. Less importantly, the admin overhead of Maven Central is non-zero so its good to avoid -//! if possible for such a small need. -//! -//! It is also worth calling out a third set of much worse options: requiring users to manually download and install the Android component -//! on each update, which magnifies the version syncing problem with lots of user overhead and then deleting the component outright. A rewrite -//! could be done with raw JNI calls, but this would easily be 3x the size of the existing implementation and require huge amounts of `unsafe` -//! to review then audit. -//! -//! ### The solution -//! -//! The current design was built after running into several painpoints with the previous attempted distribution implementations and the need to start including -//! more than just Android code in releases. To produce the release, we rely on packaging scripts to build the Android component into a prebuilt AAR file. -//! Next, a [on-disk Maven repository](https://maven.apache.org/repositories/local.html) is hosted inside of this repository with a special branch on GitHub. -//! Using GitHub's ability to serve raw files, this local repository creates an emulated Maven package repository that can be queried and downloaded from like a hosted registry. -//! -//! The remaining parts are filled in during the packaging/release process, with artifacts being pushed from release branches into the special archive branch. -//! The main crate ensures it always uses a compatible version from this local repository by declaring a standard platform-specific dependency on this shim crate. -//! Cargo lockfile resolution takes care of the rest. -//! -//! On [the Gradle side](https://github.com/rustls/rustls-platform-verifier/tree/main#gradle-setup), we instruct users to include a small code snippet in their `settings.gradle` file -//! to dynamically resolve a correct Android library's version to download like any other. When the snippet is run, it finds the version inside the workspace's `Cargo.lock` -//! and provides that to Gradle's version resolution. When the lockfile is changed, the configuration cache is invalidated and the version is calculated again. -//! This happens after any version updates (semver, Git refs, etc). -//! -//! ## Summary -//! -//! In summary, the selected distribution method avoids most of the previous pitfalls while still balancing a good experience for `cargo` and Gradle users. Some of its -//! positive properties include: -//! - Full compatibility with Cargo's dependency management, including Git patching[^1] -//! - No version checking or manual synchronization required -//! - Painless and harmless to integrate into an Android app's build system -//! - Low maintenance for the main crate maintainers' -//! -//! [^1]: The Git reference being used must have an equivalent Maven repository branch inside of it and the Maven repository URL must be switched too. diff --git a/android-release-support/tests/codegen.rs b/android-release-support/tests/codegen.rs deleted file mode 100644 index 870fefd7..00000000 --- a/android-release-support/tests/codegen.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::env; -use std::fmt::Write; -use std::fs; - -use webpki_ccadb::{crl_hosts, RootStore}; - -/// Regenerates the Android network security config listing hosts that serve CRLs. -/// -/// The generated file is checked in; this test fails with a diff whenever the -/// data returned by CCADB no longer matches it. -/// -/// Note that this reaches out to the network to query the CCADB. -#[ignore] // Ignored by default because it requires network access and is slow. -#[tokio::test] -async fn update_security_config() -> Result<(), Box> { - let mut hosts = crl_hosts(RootStore::Chrome) - .await? - .into_iter() - .collect::>(); - hosts.sort(); - - let mut out = String::from(HEADER); - for host in hosts { - writeln!( - out, - " {host}" - ) - .unwrap(); - } - out.push_str(FOOTER); - - let stored = fs::read_to_string(OUTPUT).unwrap_or_default(); - fs::write(OUTPUT, &out)?; - if stored != out { - println!("run `cargo test --manifest-path android-release-support/Cargo.toml --test codegen -- --ignored` to update the checked-in file"); - similar_asserts::assert_eq!(stored, out); - } - - Ok(()) -} - -const HEADER: &str = "\ - - - - - -"; - -const FOOTER: &str = "\ - - -"; - -const OUTPUT: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/../android/rustls-platform-verifier/src/main/res/xml/network_security_config.xml" -); diff --git a/android/.gitignore b/android/.gitignore deleted file mode 100644 index 6c988242..00000000 --- a/android/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea/ -.DS_Store -/build -/captures -.externalNativeBuild -.cxx -local.properties -emulator.log \ No newline at end of file diff --git a/android/.run/All Tests.run.xml b/android/.run/All Tests.run.xml deleted file mode 100644 index f50b23c0..00000000 --- a/android/.run/All Tests.run.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index 6d52ea5e..00000000 --- a/android/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - dependencies { - classpath libs.kotlin.gradle.plugin - classpath libs.android.gradle.plugin - } - - repositories { - google() - mavenCentral() - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index cd0519bb..00000000 --- a/android/gradle.properties +++ /dev/null @@ -1,23 +0,0 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app"s APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true -# Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official -# Enables namespacing of each library's R class so that its R class includes only the -# resources declared in the library itself and none from the library's dependencies, -# thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file diff --git a/android/gradle/libraries.versions.toml b/android/gradle/libraries.versions.toml deleted file mode 100644 index 6cf1e1d1..00000000 --- a/android/gradle/libraries.versions.toml +++ /dev/null @@ -1,6 +0,0 @@ -[versions] -kotlin = "2.2.10" - -[libraries] -android-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version = "8.3.2" } -kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index afba1092..00000000 Binary files a/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 7a04a2dc..00000000 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip -networkTimeout=10000 -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew deleted file mode 100755 index 65dcd68d..00000000 --- a/android/gradlew +++ /dev/null @@ -1,244 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat deleted file mode 100644 index 6689b85b..00000000 --- a/android/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/android/rustls-platform-verifier/.gitignore b/android/rustls-platform-verifier/.gitignore deleted file mode 100644 index 71252b62..00000000 --- a/android/rustls-platform-verifier/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build -/src/androidTest/jniLibs/ \ No newline at end of file diff --git a/android/rustls-platform-verifier/build.gradle b/android/rustls-platform-verifier/build.gradle deleted file mode 100644 index 8a89d097..00000000 --- a/android/rustls-platform-verifier/build.gradle +++ /dev/null @@ -1,109 +0,0 @@ -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' -} - -def isTest = gradle.startParameter.taskNames.any { it.contains("Test") } - -static def getOsArch() { - final String hostArch = System.getProperty("os.arch") - - if (("x86" == hostArch) || ("x86_64" == hostArch)) { - return hostArch - } else if (hostArch.contains("i386")) { - return "x86" - } else if (("ia64" == hostArch) || ("amd64" == hostArch)) { - return "x86_64" - } else if ("arm" == hostArch) { - return "armeabi-v7a" - } else if ("aarch64" == hostArch) { - return "arm64-v8a" - } - - return "UNSUPPORTED_HOST_ARCH" -} - -android { - compileSdk 33 - - defaultConfig { - minSdk 22 - targetSdk 33 - - buildConfigField "boolean", "TEST", "$isTest" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - namespace "org.rustls.platformverifier" - - buildTypes { - release { - minifyEnabled true - proguardFiles "proguard-rules.pro" - } - - debug { - debuggable true - } - } - - task buildTestLib(type: Exec) { - workingDir "../../" - commandLine "cargo", "ndk", "-t", getOsArch(), "-o", "android/rustls-platform-verifier/src/androidTest/jniLibs", "rustc", "-p", "rustls-platform-verifier", "--features", "ffi-testing", "--crate-type", "cdylib" - } - - // Only compile the test library if this package is being built for testing by itself. - tasks.whenTaskAdded { task -> - if (project.name.startsWith(gradle.rootProject.name) && task.name.contains("connectedDebugAndroidTest")) { - task.dependsOn([buildTestLib]) - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } - sourceSets { - main { - jni { - srcDirs 'src\\androidTest\\jni', 'src\\androidTest\\jniLibs' - } - } - } - buildFeatures { - buildConfig true - } -} - -configurations { - ktlint -} - -task ktlint(type: JavaExec, group: "verification") { - description = "Check Kotlin code style." - mainClass = "com.pinterest.ktlint.Main" - classpath = configurations.ktlint - args "src/**/*.kt" -} - -check.dependsOn ktlint - -task ktlintFormat(type: JavaExec, group: "formatting") { - description = "Fix Kotlin code style deviations." - mainClass = "com.pinterest.ktlint.Main" - classpath = configurations.ktlint - args "-F", "src/**/*.kt" -} - -dependencies { - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.3.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${libs.versions.kotlin.get()}" - - ktlint 'com.pinterest:ktlint:0.50.0' -} diff --git a/android/rustls-platform-verifier/proguard-rules.pro b/android/rustls-platform-verifier/proguard-rules.pro deleted file mode 100644 index 1bb20d54..00000000 --- a/android/rustls-platform-verifier/proguard-rules.pro +++ /dev/null @@ -1,41 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - --keepattributes SourceFile,LineNumberTable,MethodAttributes -# Everything is called via the JNI, so code must not be removed or renamed -# in a way Rust can't see. --dontobfuscate - -# We need this function (and class) in all builds for Rust to call because its the JNI entrypoint -# for the verifier functionality. --keep class org.rustls.platformverifier.CertificateVerifier { - verifyCertificateChain( - android.content.Context, - java.lang.String, - java.lang.String, - java.lang.String[], - byte[], - long, - byte[][] - ); -} - -# We need these classes so Rust can load their class definitions at runtime -# and access their fields at runtime. --keep class org.rustls.platformverifier.StatusCode { *; } --keep class org.rustls.platformverifier.VerificationResult { *; } - -# Note: We don't explicitly tell Proguard to remove test-only methods. They are instead -# removed as dead code because `javac` removes all references to them when not building -# in a test configuration. - -# This can be uncommented during development if needed to quickly check if -# a few test-only methods/fields are being removed by build time. -# -whyareyoukeeping class org.rustls.platformverifier.CertificateVerifier { -# private java.security.KeyStore mockKeystore; -# addMockRoot(byte[]); -#} diff --git a/android/rustls-platform-verifier/src/androidTest/java/org/rustls/platformverifier/CertificateVerifierTests.kt b/android/rustls-platform-verifier/src/androidTest/java/org/rustls/platformverifier/CertificateVerifierTests.kt deleted file mode 100644 index 238b636d..00000000 --- a/android/rustls-platform-verifier/src/androidTest/java/org/rustls/platformverifier/CertificateVerifierTests.kt +++ /dev/null @@ -1,72 +0,0 @@ -package org.rustls.platformverifier - -import android.content.Context -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.BeforeClass -import org.junit.Test -import org.junit.runner.RunWith - -private const val SUCCESS_MARKER: String = "success" -private const val FAILURE_MSG: String = "A test failed. Check the logs above for Rust panics." - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class CertificateVerifierTests { - private external fun mockTests(applicationContext: Context): String - private external fun realWorldTests(applicationContext: Context): String - private external fun verifyMockRootUsage(applicationContext: Context): String - - companion object { - @BeforeClass - @JvmStatic - fun init() { - System.loadLibrary("rustls_platform_verifier") - } - } - - @Test - fun runMockTestSuite() { - val context = InstrumentationRegistry.getInstrumentation().targetContext - val result = mockTests(context) - assertEquals(FAILURE_MSG, SUCCESS_MARKER, result) - } - - @Test - fun runRealWorldTestSuite() { - val context = InstrumentationRegistry.getInstrumentation().targetContext - val result = realWorldTests(context) - assertEquals(FAILURE_MSG, SUCCESS_MARKER, result) - } - - @Test - fun runVerifyMockRootUsage() { - val context = InstrumentationRegistry.getInstrumentation().targetContext - val result = verifyMockRootUsage(context) - assertEquals(FAILURE_MSG, SUCCESS_MARKER, result) - } - - // Note: - // - // - Full negative path (`CertificateVerifier`'s flow for unknown roots, - // are already exercised via `runMockTestSuite`). - // - // - Full positive path (`CertificateVerifier`'s flow for known roots, - // partial-chain revocation checks) already exercised via `runRealWorldTestSuite`. - @Test - fun runTestIsPublicRoot() { - val rootCAs = CertificateVerifier.getSystemRootCAs() - - // Positive - can ID known roots - assertTrue(rootCAs.isNotEmpty()) - for (ca in rootCAs) { - assertTrue(CertificateVerifier.isKnownRoot(ca)) - } - } -} diff --git a/android/rustls-platform-verifier/src/main/AndroidManifest.xml b/android/rustls-platform-verifier/src/main/AndroidManifest.xml deleted file mode 100644 index acb84c07..00000000 --- a/android/rustls-platform-verifier/src/main/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/android/rustls-platform-verifier/src/main/java/org/rustls/platformverifier/CertificateVerifier.kt b/android/rustls-platform-verifier/src/main/java/org/rustls/platformverifier/CertificateVerifier.kt deleted file mode 100644 index 618f5eef..00000000 --- a/android/rustls-platform-verifier/src/main/java/org/rustls/platformverifier/CertificateVerifier.kt +++ /dev/null @@ -1,460 +0,0 @@ -package org.rustls.platformverifier - -import android.annotation.SuppressLint -import android.content.Context -import android.net.http.X509TrustManagerExtensions -import android.os.Build -import android.util.Log -import java.io.ByteArrayInputStream -import java.io.File -import java.security.KeyStore -import java.security.KeyStoreException -import java.security.MessageDigest -import java.security.PublicKey -import java.security.cert.CertPathValidator -import java.security.cert.CertPathValidatorException -import java.security.cert.CertificateException -import java.security.cert.CertificateExpiredException -import java.security.cert.CertificateFactory -import java.security.cert.CertificateNotYetValidException -import java.security.cert.CertificateParsingException -import java.security.cert.PKIXBuilderParameters -import java.security.cert.PKIXRevocationChecker -import java.security.cert.X509Certificate -import java.util.Date -import java.util.EnumSet -import javax.net.ssl.TrustManagerFactory -import javax.net.ssl.X509TrustManager -import javax.security.auth.x500.X500Principal - -// If this is updated, update the Rust definition too. -// Marked private as this is not meant to be used in Android code. -private enum class StatusCode(val value: Int) { - Ok(0), - Unavailable(1), - Expired(2), - UnknownCert(3), - Revoked(4), - InvalidEncoding(5), - InvalidExtension(6), -} - -// Marked private as this is not meant to be used in Android code. -private class VerificationResult( - status: StatusCode, - @Suppress("unused") val message: String? = null, -) { - @Suppress("unused") - private val code: Int = status.value -} - -// NOTE: All TrustManager and certificate validation methods are not thread safe. These -// are all guarded by Kotlin's `Synchronized` accessors to prevent undefined behavior. - -// Only JNI and test code calls this, so unused code warnings are suppressed. -// Internal for test code - no other Kotlin code should use this object directly. -@Suppress("unused") -// We want to show a difference between Kotlin-side logs and those in Rust code -@SuppressLint("LongLogTag") -internal object CertificateVerifier { - private const val TAG = "rustls-platform-verifier-android" - - private fun createTrustManager(keystore: KeyStore?): X509TrustManagerExtensions? { - // This can never throw since the default algorithm is used. - val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) - - factory.init(keystore) - - val availableTrustManagers = try { - factory.trustManagers - } catch (e: RuntimeException) { - Log.w(TAG, "exception thrown creating a TrustManager: $e") - return null - } - - for (manager in availableTrustManagers) { - if (manager is X509TrustManager) { - // Kotlin ensures this can't throw at runtime since it knows that - // it must be the correct type by now. - return X509TrustManagerExtensions(manager) - } - } - - Log.e(TAG, "failed to find a usable trust manager") - return null - } - - private fun makeLazyTrustManager(keystore: KeyStore?): Lazy { - // Ensure the keystore is loaded. Since all of the trust managers are initialized in a - // `Lazy`, this will only run once. - keystore?.load(null) - - return lazy { createTrustManager(keystore) } - } - - // -- Test only -- - // Ideally, all of this will be optimized out at compile time due to not being accessed - // in release builds. - - @get:Synchronized - private val mockKeystore: KeyStore = KeyStore.getInstance(KeyStore.getDefaultType()) - - @get:Synchronized - private var mockTrustManager: Lazy = - makeLazyTrustManager(mockKeystore) - - @JvmStatic - private fun addMockRoot(root: ByteArray) { - if (!BuildConfig.TEST) { - throw Exception("attempted to add a mock root outside a test!") - } - - val alias = "root_${mockKeystore.size()}" - // Throwing here is fine since test roots should always be well-formed - val cert = certFactory.generateCertificate(ByteArrayInputStream(root)) - mockKeystore.setCertificateEntry(alias, cert) - - reloadMockData() - } - - @JvmStatic - private fun clearMockRoots() { - // Reload to get a completely fresh internal state - mockKeystore.load(null) - reloadMockData() - } - - @JvmStatic - private fun reloadMockData() { - if (mockTrustManager.isInitialized()) { - mockTrustManager = makeLazyTrustManager(mockKeystore) - } - } - - // Get a list of the system's root CAs. - // Function is public for testing only. - @JvmStatic - fun getSystemRootCAs(): List { - val rootCAs = mutableListOf() - - val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) - factory.init(systemKeystore) - - val availableTrustManagers = try { - factory.trustManagers - } catch (e: RuntimeException) { - Log.w(TAG, "exception thrown creating a TrustManager: $e") - return rootCAs - } - - availableTrustManagers.forEach { trustManager -> - if (trustManager is X509TrustManager) { - rootCAs.addAll(trustManager.acceptedIssuers) - } - } - - return rootCAs - } - - // -- End testing requirements -- - - private val certFactory: CertificateFactory = CertificateFactory.getInstance("X.509") - - private var systemTrustAnchorCache = hashSetOf>() - - @get:Synchronized - private var systemCertificateDirectory: File? = System.getenv("ANDROID_ROOT")?.let { rootPath -> - File("$rootPath/etc/security/cacerts") - } - - @get:Synchronized - private val systemKeystore: KeyStore? = try { - KeyStore.getInstance("AndroidCAStore") - } catch (_: KeyStoreException) { - null - } - - @get:Synchronized - private val systemTrustManager: Lazy = - makeLazyTrustManager(systemKeystore) - - @JvmStatic - private fun verifyCertificateChain( - @Suppress("UNUSED_PARAMETER") context: Context, - serverName: String, - authMethod: String, - allowedEkus: Array, - ocspResponse: ByteArray?, - time: Long, - certChain: Array, - ): VerificationResult { - // Convert the array of (supposedly) DER bytes into certificates. - val certificateChain = mutableListOf() - certChain.forEach { certBytes -> - val certificate = try { - certFactory.generateCertificate(ByteArrayInputStream(certBytes)) - } catch (e: CertificateException) { - return VerificationResult(StatusCode.InvalidEncoding) - } - certificateChain.add(certificate as X509Certificate) - } - - // Will never throw `ArrayIndexOutOfBoundsException` because `rustls`'s `ServerCertVerifier` trait - // has a mandatory `end_entity` parameter in `verify_server_cert`. - val endEntity = certificateChain[0] - - // Check that the certificate is valid at the point of time provided by `rustls`. - try { - endEntity.checkValidity(Date(time)) - } catch (e: CertificateExpiredException) { - return VerificationResult(StatusCode.Expired) - } catch (e: CertificateNotYetValidException) { - return VerificationResult(StatusCode.Expired) - } - - // Check that this certificate can be used in a TLS server. - if (!verifyCertUsage(endEntity, allowedEkus)) { - return VerificationResult(StatusCode.InvalidExtension) - } - - // Select the trust manager to use. - // - // We select them as follows: - // - If built for release, only use the system trust manager. This should let all test-related - // code be optimized out. - // - If built for tests: - // - If the mock CA store has any values, use the mock trust manager. - // - Otherwise, use the system trust manager. - val (trustManager, keystore) = if (!BuildConfig.TEST) { - val trustManager = - systemTrustManager.value ?: return VerificationResult(StatusCode.Unavailable) - Pair(trustManager, systemKeystore) - } else { - if (mockKeystore.size() != 0) { - val trustManager = mockTrustManager.value!! - Pair(trustManager, mockKeystore) - } else { - val trustManager = - systemTrustManager.value ?: return VerificationResult(StatusCode.Unavailable) - Pair(trustManager, systemKeystore) - } - } - - // Verify that the certificate chain is valid and correct, and nothing more. - // - // NOTE: This does not validate `serverName` is valid for the end-entity certificate. - // That is handled in Rust as Android/Java do not currently provide a RFC 6125 compliant - // hostname verifier. Additionally, even the RFC 2818 verifier is not available until API 24. - // - // `serverName` is only used for pinning/CT requirements. - // - // Returns the "the properly ordered chain used for verification as a list of X509Certificates.", - // meaning a list from end-entity certificate to trust-anchor. - val validChain = try { - trustManager.checkServerTrusted(certificateChain.toTypedArray(), authMethod, serverName) - } catch (e: CertificateException) { - // In test configurations we may see `checkServerTrusted` fail once vendored test - // certificates pass their expiry date. We try to avoid that by using a fixed - // verification time when calling `endEntity.checkValidity` above, however we can't - // fix the time for the `checkServerTrusted` call. - // - // To make diagnosing CI test failures easier we try to find the root cause of - // checkServerTrusted failing, returning a different `StatusCode` as appropriate. - if (BuildConfig.TEST) { - var rootCause: Throwable? = e - while (rootCause?.cause != null && rootCause.cause != rootCause) { - rootCause = rootCause.cause - } - return when (rootCause) { - is CertificateExpiredException, is CertificateNotYetValidException -> VerificationResult( - StatusCode.Expired, - rootCause.toString(), - ) - - else -> VerificationResult(StatusCode.UnknownCert, rootCause.toString()) - } - } - // In non-test configurations we should have caught expiry errors earlier and - // can simply return an unknown cert error without digging through the exception - // cause chain. - return VerificationResult(StatusCode.UnknownCert, e.toString()) - } - - // TEST ONLY: Mock test suite cannot attempt to check revocation status if no OSCP data has been stapled, - // because Android requires certificates to an specify OCSP responder for network fetch in this case. - // If in testing w/o OCSP stapled, short-circuit here - only prior checks apply. - if (BuildConfig.TEST && (mockKeystore.size() != 0) && (ocspResponse == null)) { - return VerificationResult(StatusCode.Ok) - } - - // Try to check the revocation status of the cert, if it is supported. - // - // This is supported at >= API 24, but we're supporting 22 (Android 5) for the best - // compatibility. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - // Note: - // - // 1. Android does not provide any way only to attempt to validate revocation from cached - // data like the other platforms do. This means it will always use the network for - // certificates which had no stapled response. - // - // 2: Likely because of 1, Android requires all issued certificates to have some form of - // revocation included in their authority information. This doesn't work universally as - // issuing certificates in use may omit authority access information (for example the - // Let's Encrypt R3 Intermediate Certificate). - // - // Given these constraints, the best option is to only check revocation information - // at the end-entity depth. We will prefer OCSP (to use stapled information if possible). - // If there is no stapled OCSP response, Android may use the network to attempt to fetch - // one. If OCSP checking fails, it may fall back to fetching CRLs. We allow "soft" - // failures, for example transient network errors. - // - // In the case of a non-public root, such as an internal CA or self-signed certificate, - // we opt to skip revocation checks entirely. The only exception is if the server - // provided stapled OCSP data, which is an explicit signal and won't introduce non-ideal - // platform behavior when attempting validation. - // - // This is because these are cases where a user or administrator has explicitly opted to - // trust a certificate they (at least believe) have control over. These certificates rarely - // contain revocation information as well, so these cases don't lose much. - // See https://github.com/rustls/rustls-platform-verifier/issues/69 as well. - if (ocspResponse == null && !isKnownRoot(validChain.last())) { - // Chain validation must have succeeded by this point. - return VerificationResult(StatusCode.Ok) - } - - val parameters = PKIXBuilderParameters(keystore, null) - - val validator = CertPathValidator.getInstance("PKIX") - val revocationChecker = validator.revocationChecker as PKIXRevocationChecker - - revocationChecker.options = EnumSet.of( - PKIXRevocationChecker.Option.SOFT_FAIL, - PKIXRevocationChecker.Option.ONLY_END_ENTITY, - ) - - // Use the OCSP data `rustls` provided, if present. - // Its expected that the server only sends revocation data for its own leaf certificate. - // - // If this field is set, then Android will use it and skip any networking to - // attempt a fetch for that certificate. Otherwise, it will attempt to fetch it from the network. - // Ref: https://cs.android.com/android/platform/superproject/+/master:libcore/ojluni/src/main/java/sun/security/provider/certpath/RevocationChecker.java;l=694 - ocspResponse?.let { providedResponse -> - revocationChecker.ocspResponses = mapOf(endEntity to providedResponse) - } - - // Use the custom revocation definition. - // "Note that when a `PKIXRevocationChecker` is added to `PKIXParameters`, it clones the `PKIXRevocationChecker`; - // thus any subsequent modifications to the `PKIXRevocationChecker` have no effect." - // - https://developer.android.com/reference/java/security/cert/PKIXRevocationChecker - parameters.certPathCheckers = listOf(revocationChecker) - // "When supplying a revocation checker in this manner, it will be used to check revocation - // irrespective of the setting of the `RevocationEnabled` flag." - // - https://developer.android.com/reference/java/security/cert/PKIXRevocationChecker - parameters.isRevocationEnabled = false - - // Validate the revocation status of the end entity certificate. - try { - validator.validate(certFactory.generateCertPath(validChain), parameters) - } catch (e: CertPathValidatorException) { - return VerificationResult(StatusCode.Revoked, e.toString()) - } - } else { - // This is allowed to be skipped since revocation checking is best-effort. - Log.w(TAG, "did not attempt to validate OCSP due to Android version") - } - - return VerificationResult(StatusCode.Ok) - } - - private fun verifyCertUsage(certificate: X509Certificate, allowedEkus: Array): Boolean { - val ekus = try { - certificate.extendedKeyUsage - } - // This should be unreachable, but could happen. - catch (_: CertificateParsingException) { - return false - } catch (_: NullPointerException) { - // According to Chromium's implementation, this can crash when the EKU data is malformed. - Log.w(TAG, "exception handling certificate EKU") - return false - } ?: return true // If the list is empty, we have nothing to do. - - return ekus.any { allowedEkus.contains(it) } - } - - // Android hashes a principal using the first four bytes of its MD5 digest, encoded in - // lowercase hex and reversed. - // - // Ref: https://source.chromium.org/chromium/chromium/src/+/main:net/android/java/src/org/chromium/net/X509Util.java;l=339 - private fun hashPrincipal(principal: X500Principal): String { - val hexDigits = "0123456789abcdef".toCharArray() - val digest = MessageDigest.getInstance("MD5").digest(principal.encoded) - val hexChars = CharArray(8) - - for (i in 0..3) { - // Kotlin doesn't support bitwise operators for bytes, only Int and Long. - val digestByte = digest[3 - i].toInt() - hexChars[2 * i] = hexDigits[(digestByte shr 4) and 0xf] - hexChars[2 * i + 1] = hexDigits[digestByte and 0xf] - } - - return String(hexChars) - } - - // Check if CA root is known or not. - // Known means installed in root CA store, either a preset public CA or a custom one installed by an enterprise/user. - // - // Ref: https://source.chromium.org/chromium/chromium/src/+/main:net/android/java/src/org/chromium/net/X509Util.java;l=351 - fun isKnownRoot(root: X509Certificate): Boolean { - // System keystore and cert directory must be non-null to perform checking - systemKeystore?.let { loadedSystemKeystore -> - systemCertificateDirectory?.let { loadedSystemCertificateDirectory -> - - // Check the in-memory cache first - val key = Pair(root.subjectX500Principal, root.publicKey) - if (systemTrustAnchorCache.contains(key)) { - return true - } - - // System trust anchors are stored under a hash of the principal. - // In case of collisions, append number. - val hash = hashPrincipal(root.subjectX500Principal) - var i = 0 - while (true) { - val alias = "$hash.$i" - i += 1 - - if (!File(loadedSystemCertificateDirectory, alias).exists()) { - break - } - - val anchor = loadedSystemKeystore.getCertificate("system:$alias") - - // It's possible for `anchor` to be `null` if the user deleted a trust anchor. - // Continue iterating as there may be further collisions after the deleted anchor. - if (anchor == null) { - continue - // This should never happen - } else if (anchor !is X509Certificate) { - // SAFETY: This logs a unique identifier (hash value) only in cases where a file within the - // system's root trust store is not a valid X509 certificate (extremely unlikely error). - // The hash doesn't tell us any sensitive information about the invalid cert or reveal any of - // its contents - it just lets us ID the bad file if a user is having TLS failure issues. - Log.e(TAG, "anchor is not a certificate, alias: $alias") - continue - // If subject and public key match, it's a system root. - } else { - if ((root.subjectX500Principal == anchor.subjectX500Principal) && (root.publicKey == anchor.publicKey)) { - systemTrustAnchorCache.add(key) - return true - } - } - } - } - } - - // Not found in cache or store: non-public - return false - } -} diff --git a/android/rustls-platform-verifier/src/main/res/xml/network_security_config.xml b/android/rustls-platform-verifier/src/main/res/xml/network_security_config.xml deleted file mode 100644 index b245638d..00000000 --- a/android/rustls-platform-verifier/src/main/res/xml/network_security_config.xml +++ /dev/null @@ -1,387 +0,0 @@ - - - - - - abitabdv.crl.certum.pl - abitabev.crl.certum.pl - abitabov.crl.certum.pl - alpiro.crl.sectigo.com - anticdv.crl.certum.pl - antrustovsslg2r30ca.crl.certum.pl - autontrustprodvtlsg2r31ca.crl.certum.pl - bitcert.crl.sectigo.com - btdvtlsr35g2ca.crl.certum.pl - c.cf-b.ssl.com - c.cf-i.ssl.com - c.pki.goog - ca1.agid.gov.it - cdn.d-trust-cloudcrl.net - cdp.disig.sk - cdp.elektronicznypodpis.pl - cdp.geotrust.com - cdp.rapidssl.com - cdp.thawte.com - cdp1.disig.sk - cdp1.pca.dfn.de - cdpa.digitalcertvalidation.com - cdpb.digitalcertvalidation.com - cdpc.digitalcertvalidation.com - cdpd.digitalcertvalidation.com - cdpe.digitalcertvalidation.com - cdpf.digitalcertvalidation.com - cdpg.digitalcertvalidation.com - cdph.digitalcertvalidation.com - cdpi.digitalcertvalidation.com - certcloud.crl.trust-provider.com - certdata.crl.sectigo.com - certera.crl.sectigo.com - certificates.godaddy.com - certum-crl.wt.trustasia.com - certum.crl.sheca.com - certumdvtlsg2e39ca.crl.certum.pl - certumdvtlsg2r39ca.crl.certum.pl - certumevtlsg2e39ca.crl.certum.pl - certumevtlsg2r39ca.crl.certum.pl - certumovtlsg2e39ca.crl.certum.pl - certumovtlsg2r39ca.crl.certum.pl - cfcadveccca.crl.certum.pl - cfcadvrsaca.crl.certum.pl - cfcaeveccca.crl.certum.pl - cfcaevrsaca.crl.certum.pl - cfcaoveccca.crl.certum.pl - cfcaovrsaca.crl.certum.pl - cnssldvtlsg2r36ca.crl.certum.pl - cnsslovtlsg2r36ca.crl.certum.pl - crl-01.r1.ica.amazontrust.com - crl-01.r1.ica.amznts.eu - crl-c.emsign.com - crl-pro.litessl.com - crl.anf.es - crl.apple.com - crl.businessid.telesec.de - crl.buypass.no - crl.catrust.oemssl.cn - crl.certigna.fr - crl.certsign.ro - crl.certum.pl - crl.cfca.com.cn - crl.cntrus.oemssl.cn - crl.cnwebtrust.oemssl.cn - crl.comodoca.com - crl.crlocsp.cn - crl.cybertrust.ne.jp - crl.d-trust.net - crl.dhimyotis.com - crl.digicert.cn - crl.digicert.eu - crl.e-szigno.hu - crl.e2m01.amazontrust.com - crl.e2m01.eu.amazontrust.com - crl.e2m02.amazontrust.com - crl.e2m03.amazontrust.com - crl.e2m04.amazontrust.com - crl.e2s14.amazontrust.com - crl.e2s15.amazontrust.com - crl.e2s16.amazontrust.com - crl.e2s17.amazontrust.com - crl.e2s18.amazontrust.com - crl.e2s19.amazontrust.com - crl.e2s20.amazontrust.com - crl.e2s21.amazontrust.com - crl.e3m01.amazontrust.com - crl.e3m01.eu.amazontrust.com - crl.e3m02.amazontrust.com - crl.e3m03.amazontrust.com - crl.e3m04.amazontrust.com - crl.e3s22.amazontrust.com - crl.e3s23.amazontrust.com - crl.e3s24.amazontrust.com - crl.e3s25.amazontrust.com - crl.e3s26.amazontrust.com - crl.e3s27.amazontrust.com - crl.e3s28.amazontrust.com - crl.e3s29.amazontrust.com - crl.emsign.com - crl.ensuredca.com - crl.firmaprofesional.com - crl.gdca.com.cn - crl.global.sheca.com - crl.globalsign.com - crl.globalsign.net - crl.godaddy.com - crl.harica.gr - crl.izenpe.com - crl.litessl.com - crl.netsolssl.com - crl.oem.trustca.net - crl.pcsp.telesec.de - crl.pki.goog - crl.quovadisglobal.com - crl.r1001.amazontrust.com - crl.r2001.amazontrust.com - crl.r2m01.amazontrust.com - crl.r2m01.eu.amazontrust.com - crl.r2m02.amazontrust.com - crl.r2m03.amazontrust.com - crl.r2m04.amazontrust.com - crl.r2s18.amazontrust.com - crl.r2s19.amazontrust.com - crl.r2s20.amazontrust.com - crl.r2s21.amazontrust.com - crl.r2s22.amazontrust.com - crl.r2s23.amazontrust.com - crl.r2s24.amazontrust.com - crl.r2s25.amazontrust.com - crl.r3001.amazontrust.com - crl.r4001.amazontrust.com - crl.r4m01.amazontrust.com - crl.r4m02.amazontrust.com - crl.r4m03.amazontrust.com - crl.r4m04.amazontrust.com - crl.root-e1.certainly.com - crl.root-r1.certainly.com - crl.rootca1.amazontrust.com - crl.rootca2.amazontrust.com - crl.rootca3.amazontrust.com - crl.rootca4.amazontrust.com - crl.rztrust.oemssl.cn - crl.sbca.telesec.de - crl.sectigo.com - crl.sectigochina.com - crl.serverid.telesec.de - crl.starfieldtech.com - crl.swisssign.ch - crl.trust-provider.cn - crl.trust-provider.com - crl.tuntrust.tn - crl.usertrust.com - crl.wt.trustasia.com - crl.xinchacha.oemssl.cn - crl.zw.oemssl.cn - crl05.actalis.it - crl06.actalis.it - crl09.actalis.it - crl1.ecert.gov.hk - crl13.actalis.it - crl14.actalis.it - crl15.actalis.it - crl2.microsoft.com - crl3.digicert.com - crl4.digicert.com - crls.certainly.com - crls.ssl.com - cyberfolks2.crl.certum.pl - depo.kamusm.gov.tr - digitaltrust.crl.sectigo.com - dnencrypt.crl.sectigo.com - dvtlsca2025-crl.e-szigno.hu - dvtlsca2026-crl.e-szigno.hu - e5.c.lencr.org - e6.c.lencr.org - e7.c.lencr.org - e8.c.lencr.org - ec2ca2017-crl1.e-szigno.hu - ec2ca2017-crl2.e-szigno.hu - ec2ca2017-crl3.e-szigno.hu - ec2sslca2017-crl1.e-szigno.hu - ec2sslca2017-crl2.e-szigno.hu - ec2sslca2017-crl3.e-szigno.hu - ec3ca2017-crl1.e-szigno.hu - ec3ca2017-crl2.e-szigno.hu - ec3ca2017-crl3.e-szigno.hu - ec3sslca2017-crl1.e-szigno.hu - ec3sslca2017-crl2.e-szigno.hu - ec3sslca2017-crl3.e-szigno.hu - eca.hinet.net - edvtlsca2023-crl.e-szigno.hu - edvtlsca2025-crl.e-szigno.hu - edvtlsca2026-crl.e-szigno.hu - emudhra.crl.sectigo.com - eosslca2017-crl1.e-szigno.hu - eosslca2017-crl2.e-szigno.hu - eosslca2017-crl3.e-szigno.hu - eovtlsca2023-crl.e-szigno.hu - eovtlsca2025-crl.e-szigno.hu - eovtlsca2026-crl.e-szigno.hu - eqca2017-crl1.e-szigno.hu - eqca2017-crl2.e-szigno.hu - eqca2017-crl3.e-szigno.hu - eqcpca2017-crl1.e-szigno.hu - eqcpca2017-crl2.e-szigno.hu - eqcpca2017-crl3.e-szigno.hu - eqoca2017-crl1.e-szigno.hu - eqoca2017-crl2.e-szigno.hu - eqoca2017-crl3.e-szigno.hu - eqtlsca2018-crl1.e-szigno.hu - eqtlsca2018-crl2.e-szigno.hu - eqtlsca2018-crl3.e-szigno.hu - eqtlsca2023-crl.e-szigno.hu - eqtlsca2025-crl.e-szigno.hu - eqtlsca2026-crl.e-szigno.hu - esafer.crl.sectigo.com - eue2m1.crl.root.amznts.eu - eue3m1.crl.root.amznts.eu - eur2m1.crl.root.amznts.eu - eveccca.crl.certum.pl - evrsaca.crl.certum.pl - gdcadv.crl.certum.pl - gdcaev.crl.certum.pl - gdcaov.crl.certum.pl - gdcatrustauthdvtlsg3r31ca.crl.certum.pl - gdcatrustauthevtlsg3r31ca.crl.certum.pl - gdcatrustauthovtlsg3r31ca.crl.certum.pl - geant.crl.sectigo.com - genious.crl.sectigo.com - geossldvtlsr34g2ca.crl.certum.pl - geosslevtlsr34g2ca.crl.certum.pl - geosslovtlsr34g2ca.crl.certum.pl - globaltrust.crl.sectigo.com - globessl.crl.sectigo.com - gogetssldv.crl.certum.pl - gogetsslev.crl.certum.pl - gogetsslov.crl.certum.pl - homepldvtlsg2r35ca.crl.certum.pl - homepldvtlsg2r36ca.crl.certum.pl - homeplovtlsg2r35ca.crl.certum.pl - homeplovtlsg2r36ca.crl.certum.pl - httpcrl.trust.telia.com - ica.navercloudtrust.com - ica.navercorp.com - issauth.crl.sectigo.com - itrusdv2.crl.certum.pl - itrusdvtlsr35g2ca.crl.certum.pl - itrusov2.crl.certum.pl - itrusovtlsr35g2ca.crl.certum.pl - itso.crl.sectigo.com - joyssldvtlsg2r33ca.crl.certum.pl - joysslovtlsg2r33ca.crl.certum.pl - kingnettechdv.crl.certum.pl - kingnettechev.crl.certum.pl - kingnettechov.crl.certum.pl - kr.crl.digicert.com - lh2.crl.certum.pl - namecheap.crl.sectigo.com - nazwassldvtlsg2e29ca.crl.certum.pl - nazwassldvtlsg2r29ca.crl.certum.pl - netartcadv2.crl.certum.pl - netartssldvtlsg2e31ca.crl.certum.pl - nijimo.crl.sectigo.com - nyalabscadv.crl.certum.pl - nyatworkdv.crl.certum.pl - omitsecurity.crl.sectigo.com - onesign.crl.sectigo.com - osslca2016-crl1.e-szigno.hu - osslca2016-crl2.e-szigno.hu - osslca2016-crl3.e-szigno.hu - ovtlsca2025-crl.e-szigno.hu - ovtlsca2026-crl.e-szigno.hu - pki-crl.atos.net - pki.telesec.de - pkipro.certsign.ro - positiwise.crl.sectigo.com - psw.crl.sectigo.com - public.wisekey.com - qiduodv.crl.certum.pl - qtlsca2018-crl1.e-szigno.hu - qtlsca2018-crl2.e-szigno.hu - qtlsca2018-crl3.e-szigno.hu - qtlsca2026-crl.e-szigno.hu - r10.c.lencr.org - r11.c.lencr.org - r12.c.lencr.org - r13.c.lencr.org - rca.navercloudtrust.com - rca.navercorp.com - repo.pubcert.jprs.jp - repo1.secomtrust.net - repository.secomtrust.net - repository.tls.hinet.net - rootca.twca.com.tw - rootglobaldv.crl.certum.pl - rootnetworksdv2.crl.certum.pl - shenzhendv.crl.certum.pl - shenzhenev.crl.certum.pl - shenzhenov.crl.certum.pl - shoperdvtlsg2r34ca.crl.certum.pl - shuididv.crl.certum.pl - shuidiev.crl.certum.pl - shuidiov.crl.certum.pl - soomadv.crl.certum.pl - ssl2ca2016-crl1.e-szigno.hu - ssl2ca2016-crl2.e-szigno.hu - ssl2ca2016-crl3.e-szigno.hu - sslca2014-crl1.e-szigno.hu - sslca2014-crl2.e-szigno.hu - sslca2014-crl3.e-szigno.hu - sslcom.crl.certum.pl - ssllimiteddv.crl.certum.pl - sslserver.twca.com.tw - subca.crl.certum.pl - titrust.crl.sectigo.com - tlcdvtlsr34g2ca.crl.certum.pl - tlcevtlsr34g2ca.crl.certum.pl - tlcovtlsr34g2ca.crl.certum.pl - tlsrootca2025-crl.e-szigno.hu - trustasia.crl.certum.pl - trustasiadv.crl.certum.pl - trustasiadvtlsr35g2ca.crl.certum.pl - trustasiaev.crl.certum.pl - trustasiaevtlse35g2ca.crl.certum.pl - trustasiaevtlsr35g2ca.crl.certum.pl - trustasiaov.crl.certum.pl - trustasiaovtlse35g2ca.crl.certum.pl - trustasiaovtlsr35g2ca.crl.certum.pl - trustocean.crl.certum.pl - tstlser20.crl.telesec.de - tstlsrr23.crl.telesec.de - unitedtrustov.crl.certum.pl - validation.identrust.com - verokey.crl.sectigo.com - vtrusdvtlsg3r31ca.crl.certum.pl - vtrusovtlsg3r31ca.crl.certum.pl - webnic.crl.sectigo.com - whitessldvtlsg2e36ca.crl.certum.pl - wosign.crl.certum.pl - wotrus-dvca.crl.certum.pl - wotrus-evca.crl.certum.pl - wotrus-ovca.crl.certum.pl - wotrus.crl.sectigo.com - wtca-cafiles.itrus.com.cn - wtca-crl.itrus.com.cn - www.accv.es - www.cert.fnmt.es - www.d-trust.net - www.microsoft.com - x1.c.lencr.org - x2.c.lencr.org - xcctrustdvtlsg3r31ca.crl.certum.pl - xcctrustevtlsg3r31ca.crl.certum.pl - xcctrustovtlsg3r31ca.crl.certum.pl - xinchacha2dv.crl.certum.pl - xinchacha2ov.crl.certum.pl - xinchachatrustdvtlsg2r34ca.crl.certum.pl - xinchachatrustevtlsg2r34ca.crl.certum.pl - xinchachatrustovtlsg2r34ca.crl.certum.pl - xinnetdvtlsr34g2ca.crl.certum.pl - xinnetevtlsr34g2ca.crl.certum.pl - xinnetovtlsr34g2ca.crl.certum.pl - ye.c.lencr.org - ye1.c.lencr.org - ye2.c.lencr.org - yektadv.crl.certum.pl - yektaov.crl.certum.pl - yr.c.lencr.org - yr1.c.lencr.org - yr2.c.lencr.org - zerossl.crl.sectigo.com - ziwit.crl.sectigo.com - zycatrustdvtlsg3r31ca.crl.certum.pl - zycatrustevtlsg3r31ca.crl.certum.pl - zycatrustovtlsg3r31ca.crl.certum.pl - - diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index e00d83fe..00000000 --- a/android/settings.gradle +++ /dev/null @@ -1,22 +0,0 @@ -pluginManagement { - repositories { - gradlePluginPortal() - google() - mavenCentral() - } -} -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - } - - versionCatalogs { - libs { - from(files("gradle/libraries.versions.toml")) - } - } -} -rootProject.name = "rustls" -include ':rustls-platform-verifier' diff --git a/ci/package_android_release.sh b/ci/package_android_release.sh deleted file mode 100755 index bbda1d25..00000000 --- a/ci/package_android_release.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash - -# This script's purpose is to automate the build + packaging steps for the pre-compiled Android verifier component. -# It works with template files and directories inside the `android-release-support/` part of the repository to setup -# a Maven local repository and then add the pre-compiled AAR file into it for distribution. The results of this packaging -# are then published to dedicated artifacts Git branch on GitHub, emulating an actual online Mavan package repository. -# -# Gradle and other clients download the artifacts from thier native build systems later on with the requested files lining up -# with the structure of the Git repo's contents. This idea was originally inspired by https://github.com/RiV-chain/github-publish-maven-action. - -set -euo pipefail - -if ! type mvn > /dev/null; then - echo "The maven CLI, mvn, is required to run this script." - echo "Download it from: https://maven.apache.org/download.cgi" - exit 1 -fi - -version=$(grep -m 1 "version = " android-release-support/Cargo.toml | tr -d "version= " | tr -d '"') - -echo "Packaging v$version of the Android support component" - -pushd ./android - -./gradlew assembleRelease - -popd - -package_name="rustls-platform-verifier" - -artifact_name="$package_name-release.aar" - -pushd ./android-release-support - -artifact_path="../android/$package_name/build/outputs/aar/$artifact_name" - -cp ./pom-template.xml ./maven/pom.xml - -# This sequence is meant to workaround the incompatibilites between macOS's sed -# command and the GNU command. Referenced from the following: -# https://stackoverflow.com/questions/5694228/sed-in-place-flag-that-works-both-on-mac-bsd-and-linux -sed -i.bak "s/\$VERSION/$version/" ./maven/pom.xml -rm ./maven/pom.xml.bak - -mvn install:install-file -Dfile="$artifact_path" -Dpackaging="aar" -DpomFile="./maven/pom.xml" -DlocalRepositoryPath="./maven/" - -rm ./maven/pom.xml - -pushd ./maven/ - -artifacts_folder="org/rustls/$package_name/$version" - -rm "$artifacts_folder/_remote.repositories" - -sha1sum "$artifacts_folder/$package_name-$version.aar" > "$artifacts_folder/$package_name-$version.aar.sha1" -sha1sum "$artifacts_folder/$package_name-$version.pom" > "$artifacts_folder/$package_name-$version.pom.sha1" - diff --git a/ci/verify_android_release.sh b/ci/verify_android_release.sh deleted file mode 100755 index abd0fa14..00000000 --- a/ci/verify_android_release.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -# This script's purpose is to verify that no test-only code is present inside of the release-mode Android artifact. -# It is validating that `javac` is performing the dead-code elimiation we expect and that `proguard` is deleting the -# unreferenced test code. This can be ran both locally and in CI. -# -# It accomplishes this goal by building the artifact and then running a decompiler on it to look for names we expect or do not. - -set -euo pipefail - -mkdir -p ./android/verification - -pushd ./android/ - -./gradlew clean -./gradlew assembleRelease - -pushd ./verification - -if [ ! -f "./bin/jadx" ]; then - echo "Decompiler not yet installed, downloading jadx" - curl -L https://github.com/skylot/jadx/releases/download/v1.4.7/jadx-1.4.7.zip --output jadx.zip - unzip jadx.zip - echo "jadx downloaded" -fi - -./bin/jadx -d decompiled ../rustls-platform-verifier/build/outputs/aar/rustls-platform-verifier-release.aar - -if grep -r -q "mock" ./decompiled; then - echo "❌ Test-only code exists in release artifact! Please review changes made to locate the cause". - exit 1 -else - echo "✅ No test-only code found in release artifact" -fi - -if grep -r -q "verifyCertificateChain" ./decompiled; then - echo "✅ JNI entrypoint present in release artifact" -else - echo "❌ JNI entrypoint not found in release artifact! Please review changes made to optimization rules which might cause this" - exit 1 -fi diff --git a/deny.toml b/deny.toml deleted file mode 100644 index 7a687b71..00000000 --- a/deny.toml +++ /dev/null @@ -1,18 +0,0 @@ -[advisories] -yanked = "deny" - -[licenses] -allow = [ - "Apache-2.0", - "BSD-3-Clause", - "CDLA-Permissive-2.0", - "ISC", - "MIT", - "Unicode-3.0", -] -exceptions = [{ allow = ["ISC", "MIT", "OpenSSL"], name = "ring" }] - -[[licenses.clarify]] -name = "ring" -expression = "ISC AND MIT AND OpenSSL" -license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] diff --git a/rustls-platform-verifier/Cargo.toml b/rustls-platform-verifier/Cargo.toml deleted file mode 100644 index 6e8f46b6..00000000 --- a/rustls-platform-verifier/Cargo.toml +++ /dev/null @@ -1,64 +0,0 @@ -[package] -name = "rustls-platform-verifier" -version = "0.7.0" -description = "rustls-platform-verifier supports verifying TLS certificates in rustls with the operating system verifier" -keywords = ["tls", "certificate", "verification", "os", "native"] -repository = "https://github.com/rustls/rustls-platform-verifier" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.85" - -[lib] -name = "rustls_platform_verifier" - -[features] -# Enables a C interface to use for testing where `cargo` can't be used. -# This feature is not stable, nor is the interface exported when it is enabled. -# Do not rely on this or use it in production. -ffi-testing = ["android_logger", "rustls/aws-lc-rs"] -# Enables APIs that expose lower-level verifier types for debugging purposes. -dbg = [] -# Enables `log::debug` base64-encoded logging of all end-entity certificates processed -# by the platform's verifier. -cert-logging = ["base64"] -# Used for nicely documenting the Android-specific APIs. This feature is not stable. -docsrs = ["jni", "once_cell"] - -[dependencies] -rustls = { version = "0.23.27", default-features = false, features = ["std"] } -log = { version = "0.4" } -base64 = { version = "0.22", optional = true } # Only used when the `cert-logging` feature is enabled. -jni = { version = "0.22.4", default-features = false, optional = true } # Only used during doc generation -once_cell = { version = "1.9", optional = true } # Only used during doc generation - -[target.'cfg(all(unix, not(target_os = "android"), not(target_vendor = "apple"), not(target_arch = "wasm32")))'.dependencies] -rustls-native-certs = "0.8" -webpki = { package = "rustls-webpki", version = "0.103", default-features = false } - -[target.'cfg(target_os = "android")'.dependencies] -once_cell = "1.9" -rustls-platform-verifier-android = { path = "../android-release-support", version = "0.1.0" } -jni = { version = "0.22", default-features = false } -webpki = { package = "rustls-webpki", version = "0.103", default-features = false } -android_logger = { version = "0.15", optional = true } # Only used during testing. - -[target.'cfg(target_arch = "wasm32")'.dependencies] -webpki = { package = "rustls-webpki", version = "0.103", default-features = false } -webpki-root-certs = "1" - -[target.'cfg(any(target_vendor = "apple"))'.dependencies] -core-foundation = "0.10" -core-foundation-sys = "0.8" -security-framework = "3.5.0" -security-framework-sys = "2.15" - -[target.'cfg(windows)'.dependencies] -windows-sys = { version = ">=0.52.0, <0.62.0", default-features = false, features = ["Win32_Foundation", "Win32_Security_Cryptography"] } - -[dev-dependencies] -rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] } -webpki-root-certs = "1" - -[package.metadata.docs.rs] -rustdoc-args = ["--cfg", "docsrs"] -features = ["dbg", "docsrs"] diff --git a/rustls-platform-verifier/LICENSE-APACHE b/rustls-platform-verifier/LICENSE-APACHE deleted file mode 120000 index 965b606f..00000000 --- a/rustls-platform-verifier/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE \ No newline at end of file diff --git a/rustls-platform-verifier/LICENSE-MIT b/rustls-platform-verifier/LICENSE-MIT deleted file mode 120000 index 76219eb7..00000000 --- a/rustls-platform-verifier/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT \ No newline at end of file diff --git a/rustls-platform-verifier/README.md b/rustls-platform-verifier/README.md deleted file mode 120000 index 32d46ee8..00000000 --- a/rustls-platform-verifier/README.md +++ /dev/null @@ -1 +0,0 @@ -../README.md \ No newline at end of file diff --git a/rustls-platform-verifier/examples/update-certs.rs b/rustls-platform-verifier/examples/update-certs.rs deleted file mode 100644 index 1970f97d..00000000 --- a/rustls-platform-verifier/examples/update-certs.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::{fs, io::Write, net::TcpStream, sync::Arc}; - -use rustls::{pki_types::ServerName, ClientConfig, ClientConnection, RootCertStore, Stream}; -use webpki_root_certs::TLS_SERVER_ROOT_CERTS; - -fn main() -> Result<(), Box> { - let mut roots = RootCertStore::empty(); - let (_, ignored) = roots.add_parsable_certificates(TLS_SERVER_ROOT_CERTS.iter().cloned()); - assert_eq!(ignored, 0, "{ignored} root certificates were ignored"); - let config = Arc::new( - ClientConfig::builder() - .with_root_certificates(roots) - .with_no_client_auth(), - ); - - for &host in HOSTS { - let server_name = ServerName::try_from(host)?; - let mut conn = ClientConnection::new(config.clone(), server_name)?; - let mut sock = TcpStream::connect((host, 443))?; - let mut stream = Stream::new(&mut conn, &mut sock); - - eprintln!("connecting to {host}..."); - if let Err(err) = stream.write_all(b"GET / HTTP/1.1\r\n\r\n") { - eprintln!("failed to write to {host}: {err}"); - } - - let Some(certs) = conn.peer_certificates() else { - eprintln!("no certificates received for {host}"); - continue; - }; - - for (i, der) in certs.iter().enumerate() { - let host_name = host.replace('.', "_"); - let fname = format!( - "{}/src/tests/verification_real_world/{host_name}_valid_{}.crt", - env!("CARGO_MANIFEST_DIR"), - i + 1 - ); - fs::write(&fname, der.as_ref())?; - eprintln!("wrote certificate to {fname}"); - } - } - - Ok(()) -} - -// We use two different CAs for better coverage and... -const HOSTS: &[&str] = &[ - // This host is using EC-based certificates for coverage. - "letsencrypt.org", - // This host is using RSA-based certificates for coverage. - "aws.amazon.com", -]; diff --git a/rustls-platform-verifier/src/android.rs b/rustls-platform-verifier/src/android.rs deleted file mode 100644 index 1e36bedf..00000000 --- a/rustls-platform-verifier/src/android.rs +++ /dev/null @@ -1,250 +0,0 @@ -//! On Android, initialization must be done before any verification is attempted. -//! -//!
-//! Some manual setup is required outside of cargo to use this crate on Android. In order to use -//! Android’s certificate verifier, the crate needs to call into the JVM. A small Kotlin component -//! must be included in your app’s build to support rustls-platform-verifier. -//! -//! See the [crate's Android section][crate#android] for more details. -//!
-//! -//! # Examples -//! -//! ``` -//! // A typical entrypoint signature for obtaining the necessary pointers -//! pub fn android_init(raw_env: *mut c_void, raw_context: *mut c_void) -> Result<(), jni::errors::Error> { -//! let mut env = unsafe { JNIEnv::from_raw(raw_env as *mut jni::sys::JNIEnv).unwrap() }; -//! let context = unsafe { JObject::from_raw(raw_context as jni::sys::jobject) }; -//! rustls_platform_verifier::android::init_with_env(&mut env, context)?; -//! } -//! ``` - -use jni::errors::Error as JNIError; -use jni::objects::{Global, JClass, JClassLoader, JObject}; -use jni::strings::JNIStr; -use jni::{jni_sig, jni_str, Env, JavaVM}; -use once_cell::sync::OnceCell; - -static GLOBAL: OnceCell = OnceCell::new(); - -/// A layer to access the Android runtime which is hosting the current -/// application process. -/// -/// Generally this trait should be implemented in your Rust app component's FFI -/// initialization layer. -pub trait Runtime: Send + Sync { - /// Returns a handle to the current process' JVM. - fn java_vm(&self) -> &JavaVM; - /// Returns a reference to the current app's [Context]. - /// - /// [Context]: - fn context(&self) -> &Global>; - /// Returns a reference to the class returned by the current JVM's `getClassLoader` call. - fn class_loader(&self) -> &Global>; -} - -enum GlobalStorage { - Internal { - java_vm: JavaVM, - context: Global>, - loader: Global>, - }, - External(&'static dyn Runtime), -} - -impl GlobalStorage { - fn vm(&self) -> &JavaVM { - match self { - GlobalStorage::Internal { java_vm, .. } => java_vm, - GlobalStorage::External(runtime) => runtime.java_vm(), - } - } - - fn context(&self, env: &mut Env) -> Result { - let context = match self { - Self::Internal { context, .. } => context, - Self::External(global) => global.context(), - }; - - let loader = match self { - Self::Internal { loader, .. } => loader, - Self::External(global) => global.class_loader(), - }; - - Ok(GlobalContext { - context: env.new_global_ref(context)?, - loader: env.new_global_ref(loader)?, - }) - } -} - -pub(super) struct GlobalContext { - /// The Android application [Context](https://developer.android.com/reference/android/app/Application). - pub(super) context: Global>, - loader: Global>, -} - -fn global() -> &'static GlobalStorage { - GLOBAL - .get() - .expect("Expect rustls-platform-verifier to be initialized") -} - -/// Initialize given a typical Android NDK [`Env`] and [`JObject`] context. -/// -/// This method will setup and store an environment locally. This is useful if nothing else in your -/// application needs to access the Android runtime. -pub fn init_with_env(env: &mut Env, context: JObject) -> Result<(), JNIError> { - GLOBAL.get_or_try_init(|| -> Result<_, JNIError> { - let loader = env - .call_method( - &context, - jni_str!("getClassLoader"), - jni_sig!(() -> JClassLoader), - &[], - )? - .l()?; - let loader = env.cast_local::(loader)?; - - Ok(GlobalStorage::Internal { - java_vm: env.get_java_vm()?, - context: env.new_global_ref(context)?, - loader: env.new_global_ref(loader)?, - }) - })?; - Ok(()) -} - -/// Initialize with a runtime that can dynamically serve references to -/// the JVM, context, and class loader. -/// -/// This is the most flexible option, and is useful for advanced use cases. -/// -/// This function will never panic. -pub fn init_with_runtime(runtime: &'static dyn Runtime) { - GLOBAL.get_or_init(|| GlobalStorage::External(runtime)); -} - -/// Initialize with references to the JVM, context, and class loader. -/// -/// This is useful when you're already interacting with `jni-rs` wrapped objects and want to use -/// global references to objects for efficiency. -/// -/// This function will never panic. -/// -/// # Examples -/// -/// ``` -/// pub fn android_init(raw_env: *mut c_void, raw_context: *mut c_void) -> Result<(), jni::errors::Error> { -/// let mut env = unsafe { jni::EnvUnowned::from_raw(raw_env as *mut jni::sys::JNIEnv).unwrap() }; -/// let context = unsafe { JObject::from_raw(raw_context as jni::sys::jobject) }; -/// let loader = env.get_object_class(&context)?.get_class_loader(env)?; -/// -/// env.with_env(|env| { -/// rustls_platform_verifier::android::init_with_refs( -/// env.get_java_vm(), -/// env.new_global_ref(context)?, -/// env.new_global_ref(loader)?, -/// ); -/// }); -/// } -/// ``` -pub fn init_with_refs( - java_vm: JavaVM, - context: Global>, - loader: Global>, -) { - GLOBAL.get_or_init(|| GlobalStorage::Internal { - java_vm, - context, - loader, - }); -} - -/// Wrapper for JNI errors that will log and clear exceptions -/// It should generally be preferred to `jni::errors::Error` -#[derive(Debug)] -pub(super) struct Error; - -impl From for Error { - #[track_caller] - fn from(cause: JNIError) -> Self { - if let JNIError::JavaException = cause { - let _ = global() - .vm() - .with_top_local_frame(|env| -> Result<(), JNIError> { - env.exception_describe(); - env.exception_clear(); - Ok(()) - }); - } - - Self - } -} - -pub(super) struct LocalContext<'a, 'env> { - pub(super) env: &'a mut Env<'env>, - pub(super) global: GlobalContext, -} - -impl<'env> LocalContext<'_, 'env> { - /// Load a class from the application class loader - /// - /// This should be used instead of `JNIEnv::find_class` to ensure all classes - /// in the application can be found. - fn load_class(&mut self, name: &'static JNIStr) -> Result, Error> { - let name = self.env.new_string(name.to_str())?; - self.global - .loader - .load_class(self.env, name) - .map_err(Error::from) - } -} - -/// Borrow the Android application context and execute the closure -/// `with_context, ensuring locals are properly freed and exceptions -/// are cleared. -pub(super) fn with_context(f: F) -> Result -where - F: FnOnce(&mut LocalContext) -> Result, -{ - let global = global(); - // Use `attach_current_thread_for_scope()` to avoid permanently attaching any threads. - // See https://github.com/rustls/rustls-platform-verifier/pull/185. - global.vm().attach_current_thread_for_scope(|env| { - let global_context = global.context(env)?; - let mut context = LocalContext { - env, - global: global_context, - }; - f(&mut context) - }) -} - -/// Loads and caches a class on first use -pub(super) struct CachedClass { - name: &'static JNIStr, - class: OnceCell>>, -} - -impl CachedClass { - /// Creates a lazily initialized class reference to the class with `name`. - pub(super) const fn new(name: &'static JNIStr) -> Self { - Self { - name, - class: OnceCell::new(), - } - } - - /// Gets the cached class reference, loaded on first use - pub(super) fn get(&self, cx: &mut LocalContext) -> Result<&JClass<'static>, Error> { - let class = self.class.get_or_try_init(|| -> Result<_, Error> { - let class = cx.load_class(self.name)?; - - Ok(cx.env.new_global_ref(class)?) - })?; - - Ok(class) - } -} diff --git a/rustls-platform-verifier/src/lib.rs b/rustls-platform-verifier/src/lib.rs deleted file mode 100644 index 3c1da291..00000000 --- a/rustls-platform-verifier/src/lib.rs +++ /dev/null @@ -1,92 +0,0 @@ -#![cfg_attr(docsrs, feature(doc_cfg))] -#![doc = include_str!("../README.md")] -#![warn(missing_docs)] - -use std::sync::Arc; - -#[cfg(feature = "dbg")] -use rustls::crypto::CryptoProvider; -#[cfg(feature = "dbg")] -use rustls::pki_types::CertificateDer; -use rustls::{client::WantsClientCert, ClientConfig, ConfigBuilder, WantsVerifier}; - -mod verification; -pub use verification::Verifier; - -// Build the Android module when generating docs so that -// the Android-specific functions are included regardless of -// the host. -#[cfg(any(all(doc, docsrs), target_os = "android"))] -#[cfg_attr(docsrs, doc(cfg(target_os = "android")))] -pub mod android; - -/// Fixures and data to support testing the server -/// certificate verifier. -#[cfg(any(test, feature = "ffi-testing"))] -mod tests; - -// Re-export any exported functions that are required for -// tests to run in a platform-native environment. -#[cfg(feature = "ffi-testing")] -#[cfg_attr(feature = "ffi-testing", allow(unused_imports))] -pub use tests::ffi::*; - -/// Exposed for debugging certificate issues with standalone tools. -/// -/// This is not intended for production use, you should use [`BuilderVerifierExt`] or -/// [`ConfigVerifierExt`] instead. -#[cfg(feature = "dbg")] -pub fn verifier_for_dbg( - root: CertificateDer<'static>, - crypto_provider: Arc, -) -> Arc { - Arc::new(Verifier::new_with_fake_root(root, crypto_provider)) -} - -/// Extension trait to help configure [`ClientConfig`]s with the platform verifier. -pub trait BuilderVerifierExt { - /// Configures the `ClientConfig` with the platform verifier. - /// - /// ```rust - /// use rustls::ClientConfig; - /// use rustls_platform_verifier::BuilderVerifierExt; - /// let config = ClientConfig::builder() - /// .with_platform_verifier() - /// .unwrap() - /// .with_no_client_auth(); - /// ``` - fn with_platform_verifier( - self, - ) -> Result, rustls::Error>; -} - -impl BuilderVerifierExt for ConfigBuilder { - fn with_platform_verifier( - self, - ) -> Result, rustls::Error> { - let verifier = Verifier::new(self.crypto_provider().clone())?; - Ok(self - .dangerous() - .with_custom_certificate_verifier(Arc::new(verifier))) - } -} - -/// Extension trait to help build a [`ClientConfig`] with the platform verifier. -pub trait ConfigVerifierExt { - /// Build a [`ClientConfig`] with the platform verifier and the default `CryptoProvider`. - /// - /// ```rust - /// use rustls::ClientConfig; - /// use rustls_platform_verifier::ConfigVerifierExt; - /// let config = ClientConfig::with_platform_verifier(); - /// ``` - fn with_platform_verifier() -> Result; -} - -impl ConfigVerifierExt for ClientConfig { - fn with_platform_verifier() -> Result { - Ok(ClientConfig::builder() - .with_platform_verifier()? - .with_no_client_auth()) - } -} diff --git a/rustls-platform-verifier/src/tests/ffi.rs b/rustls-platform-verifier/src/tests/ffi.rs deleted file mode 100644 index c35da370..00000000 --- a/rustls-platform-verifier/src/tests/ffi.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! Thin wrappers ontop the existing test suites that allow them to be ran -//! in the context of a platform-native environment as required by the verifier implementation. -#![allow(missing_docs)] - -#[cfg(target_os = "android")] -pub use android::*; -#[cfg(target_os = "android")] -mod android { - //! Tests which run inside the context of a Android device, typically an emulator. - //! - //! Note: These tests run inside the same application context, so they share the same mock test - //! store. This will remain non-problematic as long as roots are different enough (for the use case) and - //! real roots are never removed from the store. - //! - //! It is intentional that the tests run sequentially, as dropping a `Verifier` will reset its mock - //! root store. - use crate::tests; - use jni::{ - objects::{JClass, JObject, JString}, - sys::jstring, - EnvUnowned, Outcome, - }; - use std::sync::Once; - - static ANDROID_INIT: Once = Once::new(); - - /// A marker that the Kotlin test runner looks for to determine - /// if a set of integration tests passed or not. - const SUCCESS_MARKER: &str = "success"; - - fn run_android_test<'caller>( - env: &mut EnvUnowned<'caller>, - cx: JObject, - suite_name: &'static str, - test_cases: &'static [fn()], - ) -> JString<'caller> { - let outcome = env - .with_env(|env| { - // These can't fail, and even if they did, Android will crash the process like we want. - ANDROID_INIT.call_once(|| { - let log_filter = android_logger::FilterBuilder::new() - .parse("trace") - .filter_module("jni", log::LevelFilter::Off) - .build(); - - android_logger::init_once( - android_logger::Config::default() - .with_max_level(log::Level::Trace.to_level_filter()) - .with_filter(log_filter), - ); - crate::android::init_with_env(env, cx).unwrap(); - }); - - for test in test_cases { - test(); - } - - env.new_string(SUCCESS_MARKER) - }) - .into_outcome(); - - match outcome { - Outcome::Ok(success) => success, - Outcome::Err(_) | Outcome::Panic(_) => { - panic!("failed to run test suite '{suite_name}'") - } - } - } - - #[export_name = "Java_org_rustls_platformverifier_CertificateVerifierTests_mockTests"] - pub extern "C" fn rustls_platform_verifier_mock_test_suite( - mut env: EnvUnowned<'_>, - _class: JClass, - cx: JObject, - ) -> jstring { - log::info!("running mock test suite..."); - - run_android_test( - &mut env, - cx, - "mock tests", - tests::verification_mock::ALL_TEST_CASES, - ) - .into_raw() - } - - #[export_name = "Java_org_rustls_platformverifier_CertificateVerifierTests_verifyMockRootUsage"] - pub extern "C" fn rustls_platform_verifier_verify_mock_root_usage( - mut env: EnvUnowned<'_>, - _class: JClass, - cx: JObject, - ) -> jstring { - log::info!("verifying mock roots are not used by default..."); - - run_android_test( - &mut env, - cx, - "mock root verification", - &[tests::verification_mock::verification_without_mock_root], - ) - .into_raw() - } - - #[export_name = "Java_org_rustls_platformverifier_CertificateVerifierTests_realWorldTests"] - pub extern "C" fn rustls_platform_verifier_real_world_test_suite( - mut env: EnvUnowned<'_>, - _class: JClass, - cx: JObject, - ) -> jstring { - log::info!("running real world suite..."); - - run_android_test( - &mut env, - cx, - "real world", - tests::verification_real_world::ALL_TEST_CASES, - ) - .into_raw() - } -} - -#[cfg(not(target_os = "android"))] -mod dummy { - //! A module to prevent dead-code warnings all over - //! the `tests` module due to the weird combination of - //! feature flags and `--all-features`. These test case - //! lists are only used via the FFI. - - use crate::tests; - - #[allow(dead_code)] - fn dummy() { - #[cfg(any( - windows, - target_os = "android", - target_vendor = "apple", - target_os = "linux" - ))] - let _ = tests::verification_mock::ALL_TEST_CASES; - let _ = tests::verification_real_world::ALL_TEST_CASES; - } -} diff --git a/rustls-platform-verifier/src/tests/mod.rs b/rustls-platform-verifier/src/tests/mod.rs deleted file mode 100644 index f464a75e..00000000 --- a/rustls-platform-verifier/src/tests/mod.rs +++ /dev/null @@ -1,60 +0,0 @@ -#[cfg(feature = "ffi-testing")] -pub mod ffi; - -use std::{error::Error as StdError, sync::Arc}; - -mod verification_real_world; - -mod verification_mock; - -use rustls::{ - crypto::CryptoProvider, - pki_types, CertificateError, - Error::{self as TlsError, InvalidCertificate}, -}; - -struct TestCase<'a, E: StdError> { - /// The name of the server we're connecting to. - pub reference_id: &'a str, - - /// The certificates presented by the TLS server, in the same order. - pub chain: &'a [&'a [u8]], - - /// The stapled OCSP response given to us by Rustls, if any. - pub stapled_ocsp: Option<&'a [u8]>, - - /// The time to use as the current time for verification. - pub verification_time: pki_types::UnixTime, - - pub expected_result: Result<(), TlsError>, - - /// An error that should be present inside an expected `CertificateError::Other` variant. - /// - /// Set this if the error being tested uses `CertificateError::Other` and not statically known - /// variants in [TlsError] - #[allow(dead_code)] - pub other_error: Option, -} - -pub fn assert_cert_error_eq( - result: &Result<(), TlsError>, - expected: &Result<(), TlsError>, - expected_err: Option<&E>, -) { - // If the expected error is an "Other" CertificateError we can't directly assert equality, we rely - // on the test caller to provide the correct value to compare. - if let Err(InvalidCertificate(CertificateError::Other(err))) = &expected { - let expected_err = expected_err.expect("error not provided for `Other` case handling"); - let err: &E = err - .0 - .downcast_ref() - .expect("incorrect `Other` inner error kind"); - assert_eq!(err, expected_err); - } else { - assert_eq!(result, expected); - } -} - -fn test_provider() -> Arc { - Arc::new(rustls::crypto::aws_lc_rs::default_provider()) -} diff --git a/rustls-platform-verifier/src/tests/verification_mock/ca.go b/rustls-platform-verifier/src/tests/verification_mock/ca.go deleted file mode 100644 index ec3b4de1..00000000 --- a/rustls-platform-verifier/src/tests/verification_mock/ca.go +++ /dev/null @@ -1,314 +0,0 @@ -// Generates the test data files used in the tests in verification_mock.rs. -// -// After re-generating mock certificates be sure to also update the fixed -// verification timestamp in `mod.rs`'s `verification_time` fn to match -// the current time. -// -// The primary point of this program is to fully automate the creation of the -// test data, with minimal tool dependencies (e.g. no OpenSSL), with low effort. -// -// This program isn't run as part of the build. Instead, it generates data files -// that are valid for a long time, so they don't need to be regenerated to avoid -// expiration. -// -// Files generated by this program are named "A-B-ee_C[-D].{crt, ocsp}" where -// A is the (subject) name the root certificate, B is the (subject) name of the -// intermediate certificate, C is the (subjectAltName DNS name) name of the -// end-entity certificate, and D is some distinguishing feature (e.g. "revoked"). -// -// When this program was first written, it was thought that such conventions, -// and the structure of the program, would make it easy to create certificates -// that are similar but slightly different, e.g. same hostname, same issuer -// name, but different roots. It's still to be determined if this structure -// actually facilitates that. -// -// The other goal of this program is to serve as a model for the `webpki` -// crate's planned self-contained all-Rust test suite. In particular, this -// program was originally developed to accelerate the Rust test data generator -// for the `webpki` crate. - -package main - -import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "errors" - "fmt" - "io/ioutil" - "math/big" - "net" - "os" - "strings" - "time" - - "golang.org/x/crypto/ocsp" -) - -const ( - OneDay = time.Hour * 24 - OneYear = OneDay * 365 -) - -func main() { - err := doIt() - if err != nil { - fmt.Fprintf(os.Stderr, "error: %v\n", err) - os.Exit(1) - } -} - -func doIt() error { - now := time.Now().Truncate(time.Minute).UTC() - - // "ee_1" -> "::1" is IPv6 localhost, omitting ":" characters b/c invalid for file paths on Windows - end_entities := [3]string{"ee_example.com", "ee_127.0.0.1", "ee_1"} - - var err error = nil - - root1_key, err := generateRoot("root1", now) - if err != nil { - return err - } - - root1_int1_key, err := generateInt("root1-int1", 2, now, root1_key) - if err != nil { - return err - } - - for _, ee := range end_entities { - err = generateEndEntity("root1-int1-"+ee+"-good", 1, now, root1_int1_key) - if err != nil { - return err - } - - err = generateEndEntity("root1-int1-"+ee+"-revoked", 2, now, root1_int1_key) - if err != nil { - return err - } - - err = generateEndEntity("root1-int1-"+ee+"-wrong_eku", 3, now, root1_int1_key) - if err != nil { - return err - } - } - - return nil -} - -// Generates a binary DER X.509 file with name `eeName` + ".crt". The certificate will have -// the given serial number (which should be unique per issuer), OCSP status (ocsp.Good, -// ocsp.Revoked, etc.), signed by the given key. -func generateEndEntity(eeName string, serial int64, now time.Time, caKey crypto.Signer) error { - nameParts := strings.Split(eeName, "-") - caName := nameParts[0] + "-" + nameParts[1] - eeBaseName := nameParts[2] - label := nameParts[3] - - caCert, err := readCert(caName) - if err != nil { - return err - } - eePubKey, err := generatePubKey() - if err != nil { - return err - } - - // macOS requirements reference: https://support.apple.com/en-us/HT210176 - template := x509.Certificate{ - NotBefore: now.Add(-OneDay), - // macOS >=10.15 requires that certificates must have a - // validity period of 825 days or fewer. - NotAfter: now.Add(2 * OneYear), - // macOS >=10.15 requires that server certificates must have the - // id-kp-serverAuth OID present in the EKU. - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - } - - switch eeBaseName { - case "ee_example.com": - template.SerialNumber = big.NewInt(serial) - template.DNSNames = []string{"example.com"} - case "ee_127.0.0.1": // IPv4 localhost - template.SerialNumber = big.NewInt(serial) - template.IPAddresses = []net.IP{net.IPv4(127, 0, 0, 1)} - case "ee_1": // IPv6 localhost, e.g. "::1" - template.SerialNumber = big.NewInt(serial) - template.IPAddresses = []net.IP{net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}} - default: - return errors.New("Unrecognized end entity certificate:" + eeName) - } - - ocspStatus := ocsp.Unknown // Don't generate an OCSP response. - - switch label { - case "good": - ocspStatus = ocsp.Good - case "revoked": - ocspStatus = ocsp.Revoked - case "wrong_eku": - template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection} - } - - cert, err := x509.CreateCertificate(rand.Reader, &template, caCert, eePubKey, caKey) - if err != nil { - return err - } - err = ioutil.WriteFile(eeName+".crt", cert, 0666) - if err != nil { - return err - } - - if ocspStatus != ocsp.Unknown { - err = generateOCSPResponse(eeName, ocspStatus, now, caKey) - if err != nil { - return err - } - } - - return nil -} - -// Generates a binary DER X.509 file with name `intName` + ".crt". -func generateInt(intName string, serial int64, now time.Time, caKey crypto.Signer) (crypto.Signer, error) { - nameParts := strings.Split(intName, "-") - caName := nameParts[0] - - caCert, err := readCert(caName) - if err != nil { - return nil, err - } - intKey, err := generateKey() - if err != nil { - return nil, err - } - - template := x509.Certificate{ - Subject: pkix.Name{ - Organization: []string{intName}, - }, - NotBefore: now.Add(-OneDay), - NotAfter: now.Add(OneYear), - IsCA: true, - KeyUsage: x509.KeyUsageCertSign, - BasicConstraintsValid: true, - SerialNumber: big.NewInt(serial), - } - - cert, err := x509.CreateCertificate(rand.Reader, &template, caCert, intKey.Public(), caKey) - if err != nil { - return nil, err - } - err = ioutil.WriteFile(intName+".crt", cert, 0666) - if err != nil { - return nil, err - } - - return intKey, nil -} - -func generateRoot(name string, now time.Time) (crypto.Signer, error) { - caKey, err := generateKey() - if err != nil { - return nil, err - } - template := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{name}, - }, - NotBefore: now.Add(-OneDay), - NotAfter: now.Add(OneYear), - IsCA: true, - KeyUsage: x509.KeyUsageCertSign, - BasicConstraintsValid: true, - } - - cert, err := x509.CreateCertificate(rand.Reader, &template, &template, caKey.Public(), caKey) - if err != nil { - return nil, err - } - return caKey, ioutil.WriteFile(name+".crt", cert, 0666) -} - -func generateOCSPResponse(name string, status int, now time.Time, caKey crypto.Signer) error { - nameParts := strings.Split(name, "-") - caName := nameParts[0] + "-" + nameParts[1] - - caCert, err := readCert(caName) - if err != nil { - return err - } - eeCert, err := readCert(name) - if err != nil { - return err - } - - // It seems we must have `thisUpdate >= eeCert.NotBefore` or else - // Windows won't trust the OCSP response. In particular, if the - // response is `revoked` but this date is too early, then it will - // not consider the response revoked! - thisUpdate := eeCert.NotBefore.Add(1) - - template := ocsp.Response{ - Status: status, - SerialNumber: eeCert.SerialNumber, - ThisUpdate: thisUpdate, - NextUpdate: thisUpdate.Add(1 * OneYear), - } - - if status == ocsp.Revoked { - template.RevokedAt = thisUpdate - } - - response, err := ocsp.CreateResponse(caCert, caCert, template, caKey) - if err != nil { - return err - } - - return ioutil.WriteFile(name+".ocsp", response, 0666) -} - -func generateKey() (crypto.Signer, error) { - key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - if err != nil { - return nil, err - } - return key, nil -} - -func generatePubKey() (*ecdsa.PublicKey, error) { - privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - if err != nil { - return nil, err - } - return &privateKey.PublicKey, nil -} - -func readCert(name string) (*x509.Certificate, error) { - der, err := ioutil.ReadFile(name + ".crt") - if err != nil { - return nil, err - } - return x509.ParseCertificate(der) -} - -func readKey(name string) (*ecdsa.PrivateKey, error) { - pkcs8, err := ioutil.ReadFile(name + ".p8") - if err != nil { - return nil, err - } - privateKey, err := x509.ParsePKCS8PrivateKey(pkcs8) - if err != nil { - return nil, err - } - switch k := privateKey.(type) { - case *ecdsa.PrivateKey: - return k, nil - default: - return nil, errors.New("Unexpected private key type") - } -} diff --git a/rustls-platform-verifier/src/tests/verification_mock/go.mod b/rustls-platform-verifier/src/tests/verification_mock/go.mod deleted file mode 100644 index fd0986f3..00000000 --- a/rustls-platform-verifier/src/tests/verification_mock/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module briansmith.org/webpki-test-certs - -go 1.17 - -require golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 diff --git a/rustls-platform-verifier/src/tests/verification_mock/go.sum b/rustls-platform-verifier/src/tests/verification_mock/go.sum deleted file mode 100644 index 666a1bd6..00000000 --- a/rustls-platform-verifier/src/tests/verification_mock/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= -golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= diff --git a/rustls-platform-verifier/src/tests/verification_mock/mod.rs b/rustls-platform-verifier/src/tests/verification_mock/mod.rs deleted file mode 100644 index 83f272dc..00000000 --- a/rustls-platform-verifier/src/tests/verification_mock/mod.rs +++ /dev/null @@ -1,412 +0,0 @@ -//! Tests of certificate verification that require our own test CA to be -//! trusted. -//! -//! # Re-generating the test data -//! -//! `cd src/tests/verification_mock && go run ca.go` -//! -//! # Repeatability and Self-containedness -//! -//! These tests are only supported on platforms where we have implemented the -//! ability to trust a CA (only) for the duration of a test, without modifying -//! the operating system's trust store--i.e. without affecting the security of -//! any parts of the system outside of these tests. See the `#![cfg(...)]` -//! immediately below to see which platforms run these tests. - -#![cfg(all( - any(windows, unix, target_os = "android"), - // These OSes require a simulator runtime and bundle. - not(target_os = "tvos"), - not(target_os = "watchos"), - not(target_os = "visionos") -))] - -use core::time::Duration; -use std::convert::TryFrom; -use std::net::IpAddr; -#[cfg(not(any(target_vendor = "apple", windows)))] -use std::net::{Ipv4Addr, Ipv6Addr}; -use std::sync::Arc; - -use rustls::client::danger::ServerCertVerifier; -use rustls::pki_types; -#[cfg(not(any(target_vendor = "apple", windows)))] -use rustls::pki_types::{DnsName, ServerName}; -use rustls::{CertificateError, Error as TlsError, OtherError}; - -use super::TestCase; -use crate::tests::{assert_cert_error_eq, test_provider}; -use crate::verification::{EkuError, Verifier}; - -macro_rules! mock_root_test_cases { - { $( $name:ident [ $target:meta ] => $test_case:expr ),+ , } => { - mock_root_test_cases!(@ $($name [ $target ] => $test_case),+,); - - #[cfg(test)] - mod tests { - $( - #[cfg($target)] - #[test] - pub fn $name() { - super::$name() - } - )+ - } - - #[cfg(feature = "ffi-testing")] - pub static ALL_TEST_CASES: &'static [fn()] = &[ - $( - #[cfg($target)] - $name, - )+ - - ]; - }; - - {@ $( $name:ident [ $target:meta ] => $test_case:expr ),+ , } => { - $( - #[cfg($target)] - pub(super) fn $name() { - test_with_mock_root(&$test_case, Roots::OnlyExtra); - #[cfg(all($target, not(target_os = "android")))] - test_with_mock_root(&$test_case, Roots::ExtraAndPlatform); - } - )+ - }; -} - -macro_rules! no_error { - () => { - None:: - }; -} - -const ROOT1: pki_types::CertificateDer<'static> = - pki_types::CertificateDer::from_slice(include_bytes!("root1.crt")); -const ROOT1_INT1: &[u8] = include_bytes!("root1-int1.crt"); -const ROOT1_INT1_EXAMPLE_COM_GOOD: &[u8] = include_bytes!("root1-int1-ee_example.com-good.crt"); -const ROOT1_INT1_LOCALHOST_IPV4_GOOD: &[u8] = include_bytes!("root1-int1-ee_127.0.0.1-good.crt"); -const ROOT1_INT1_LOCALHOST_IPV6_GOOD: &[u8] = include_bytes!("root1-int1-ee_1-good.crt"); - -const EXAMPLE_COM: &str = "example.com"; -const LOCALHOST_IPV4: &str = "127.0.0.1"; -const LOCALHOST_IPV6: &str = "::1"; - -#[cfg(any(test, feature = "ffi-testing"))] -#[cfg_attr(feature = "ffi-testing", allow(dead_code))] -pub(super) fn verification_without_mock_root() { - let crypto_provider = test_provider(); - - // Since Rustls 0.22 constructing a webpki verifier (like the one backing Verifier on unix - // systems) without any roots produces `OtherError(NoRootAnchors)` - since our FreeBSD CI - // runner fails to find any roots with openssl-probe we need to provide webpki-root-certs here - // or the test will fail with the `OtherError` instead of the expected `CertificateError`. - #[cfg(target_os = "freebsd")] - let verifier = Verifier::new_with_extra_roots( - webpki_root_certs::TLS_SERVER_ROOT_CERTS.iter().cloned(), - crypto_provider, - ) - .unwrap(); - - #[cfg(not(target_os = "freebsd"))] - let verifier = Verifier::new(crypto_provider).unwrap(); - - let server_name = pki_types::ServerName::try_from(EXAMPLE_COM).unwrap(); - let end_entity = pki_types::CertificateDer::from(ROOT1_INT1_EXAMPLE_COM_GOOD); - let intermediates = [pki_types::CertificateDer::from(ROOT1_INT1)]; - - // Fails because the server cert has no trust root in Windows, and can't since it uses a self-signed CA. - // Similarly on UNIX platforms using the Webpki verifier, it can't fetch extra certificates through - // AIA chasing or other mechanisms, and so we know this test will correctly verify an unknown - // root in a chain fails validation. - let result = verifier.verify_server_cert( - &end_entity, - &intermediates, - &server_name, - &[], - verification_time(), - ); - - assert_eq!( - result.map(|_| ()), - Err(TlsError::InvalidCertificate( - CertificateError::UnknownIssuer - )) - ); -} - -#[test] -fn test_verification_without_mock_root() { - verification_without_mock_root() -} - -// Note: Android does not currently support IP address hosts, so these tests are disabled for -// Android. -// Verifies that our test trust anchor(s) are not trusted when `Verifier::new()` -// is used. -mock_root_test_cases! { - valid_no_stapling_dns [ any(windows, unix) ] => TestCase { - reference_id: EXAMPLE_COM, - chain: &[ROOT1_INT1_EXAMPLE_COM_GOOD, ROOT1_INT1], - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Ok(()), - other_error: no_error!(), - }, - valid_no_stapling_dns_trailing_label [ any(windows, unix) ] => TestCase { - // XXX: `pki_types` validates that the label is still valid and that cases such as two `.` characters - // are still correctly rejected. - reference_id: "example.com.", - chain: &[ROOT1_INT1_EXAMPLE_COM_GOOD, ROOT1_INT1], - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Ok(()), - other_error: no_error!(), - }, - valid_no_stapling_ipv4 [ any(windows, unix) ] => TestCase { - reference_id: LOCALHOST_IPV4, - chain: &[ROOT1_INT1_LOCALHOST_IPV4_GOOD, ROOT1_INT1], - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Ok(()), - other_error: no_error!(), - }, - valid_no_stapling_ipv6 [ any(windows, unix) ] => TestCase { - reference_id: LOCALHOST_IPV6, - chain: &[ROOT1_INT1_LOCALHOST_IPV6_GOOD, ROOT1_INT1], - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Ok(()), - other_error: no_error!(), - }, - valid_stapled_good_dns [ any(windows, unix) ] => TestCase { - reference_id: EXAMPLE_COM, - chain: &[ROOT1_INT1_EXAMPLE_COM_GOOD, ROOT1_INT1], - stapled_ocsp: Some(include_bytes!("root1-int1-ee_example.com-good.ocsp")), - verification_time: verification_time(), - expected_result: Ok(()), - other_error: no_error!(), - }, - valid_stapled_good_ipv4 [ any(windows, unix) ] => TestCase { - reference_id: LOCALHOST_IPV4, - chain: &[ROOT1_INT1_LOCALHOST_IPV4_GOOD, ROOT1_INT1], - stapled_ocsp: Some(include_bytes!("root1-int1-ee_127.0.0.1-good.ocsp")), - verification_time: verification_time(), - expected_result: Ok(()), - other_error: no_error!(), - }, - valid_stapled_good_ipv6 [ any(windows, unix) ] => TestCase { - reference_id: LOCALHOST_IPV6, - chain: &[ROOT1_INT1_LOCALHOST_IPV6_GOOD, ROOT1_INT1], - stapled_ocsp: Some(include_bytes!("root1-int1-ee_1-good.ocsp")), - verification_time: verification_time(), - expected_result: Ok(()), - other_error: no_error!(), - }, - - // The revocation tests use a separate certificate from the one used in the "good" case to deal - // with operating systems with validation data caches (e.g. Windows). - // Linux is not included, since the webpki verifier does not presently support OCSP revocation - // checking. - - // Check that self-signed certificates, which may or may not be revokved, do not return any - // kind of revocation error. It is expected that non-public certificates without revocation information - // have no revocation checking performed across platforms. - revoked_dns [ any(windows, target_os = "android", target_vendor = "apple") ] => TestCase { - reference_id: EXAMPLE_COM, - chain: &[include_bytes!("root1-int1-ee_example.com-revoked.crt"), ROOT1_INT1], - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Ok(()), - other_error: no_error!(), - }, - stapled_revoked_dns [ any(windows, target_os = "android", target_vendor = "apple") ] => TestCase { - reference_id: EXAMPLE_COM, - chain: &[include_bytes!("root1-int1-ee_example.com-revoked.crt"), ROOT1_INT1], - stapled_ocsp: Some(include_bytes!("root1-int1-ee_example.com-revoked.ocsp")), - verification_time: verification_time(), - expected_result: Err(TlsError::InvalidCertificate(CertificateError::Revoked)), - other_error: no_error!(), - }, - stapled_revoked_ipv4 [ any(windows, target_os = "android", target_vendor = "apple") ] => TestCase { - reference_id: LOCALHOST_IPV4, - chain: &[include_bytes!("root1-int1-ee_127.0.0.1-revoked.crt"), ROOT1_INT1], - stapled_ocsp: Some(include_bytes!("root1-int1-ee_127.0.0.1-revoked.ocsp")), - verification_time: verification_time(), - expected_result: Err(TlsError::InvalidCertificate(CertificateError::Revoked)), - other_error: no_error!(), - }, - stapled_revoked_ipv6 [ any(windows, target_os = "android", target_vendor = "apple") ] => TestCase { - reference_id: LOCALHOST_IPV6, - chain: &[include_bytes!("root1-int1-ee_1-revoked.crt"), ROOT1_INT1], - stapled_ocsp: Some(include_bytes!("root1-int1-ee_1-revoked.ocsp")), - verification_time: verification_time(), - expected_result: Err(TlsError::InvalidCertificate(CertificateError::Revoked)), - other_error: no_error!(), - }, - // Validation fails with no intermediate (that can't be fetched - // with AIA because there's no AIA issuer field in the certificate). - // (AIA is an extension that allows downloading of missing data, - // like missing certificates, during validation; see - // https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.2.1). - ee_only_dns [ any(windows, unix) ] => TestCase { - reference_id: EXAMPLE_COM, - chain: &[ROOT1_INT1_EXAMPLE_COM_GOOD], - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Err(TlsError::InvalidCertificate(CertificateError::UnknownIssuer)), - other_error: no_error!(), - }, - ee_only_ipv4 [ any(windows, unix) ] => TestCase { - reference_id: LOCALHOST_IPV4, - chain: &[ROOT1_INT1_LOCALHOST_IPV4_GOOD], - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Err(TlsError::InvalidCertificate(CertificateError::UnknownIssuer)), - other_error: no_error!(), - }, - ee_only_ipv6 [ any(windows, unix) ] => TestCase { - reference_id: LOCALHOST_IPV6, - chain: &[ROOT1_INT1_LOCALHOST_IPV6_GOOD], - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Err(TlsError::InvalidCertificate(CertificateError::UnknownIssuer)), - other_error: no_error!(), - }, - // Validation fails when the certificate isn't valid for the reference ID. - domain_mismatch_dns [ any(windows, unix) ] => TestCase { - reference_id: "example.org", - chain: &[ROOT1_INT1_EXAMPLE_COM_GOOD, ROOT1_INT1], - stapled_ocsp: None, - verification_time: verification_time(), - #[cfg(not(any(target_vendor = "apple", windows)))] - expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForNameContext { - expected: ServerName::DnsName(DnsName::try_from("example.org").unwrap()), - presented: vec!["DnsName(\"example.com\")".to_owned()] - })), - #[cfg(any(target_vendor = "apple", windows))] - expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForName)), - other_error: no_error!(), - }, - domain_mismatch_ipv4 [ any(windows, unix) ] => TestCase { - reference_id: "198.168.0.1", - chain: &[ROOT1_INT1_LOCALHOST_IPV4_GOOD, ROOT1_INT1], - stapled_ocsp: None, - verification_time: verification_time(), - #[cfg(not(any(target_vendor = "apple", windows)))] - expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForNameContext { - expected: ServerName::IpAddress(pki_types::IpAddr::V4(Ipv4Addr::from([198, 168, 0, 1]).into())), - presented: vec!["IpAddress(127.0.0.1)".to_owned()], - })), - #[cfg(any(target_vendor = "apple", windows))] - expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForName)), - other_error: no_error!(), - }, - domain_mismatch_ipv6 [ any(windows, unix) ] => TestCase { - reference_id: "::ffff:c6a8:1", - chain: &[ROOT1_INT1_LOCALHOST_IPV6_GOOD, ROOT1_INT1], - stapled_ocsp: None, - verification_time: verification_time(), - #[cfg(not(any(target_vendor = "apple", windows)))] - expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForNameContext { - expected: ServerName::IpAddress(pki_types::IpAddr::V6(Ipv6Addr::from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 198, 168, 0, 1]).into())), - presented: vec!["IpAddress(0::1)".to_owned()], - })), - #[cfg(any(target_vendor = "apple", windows))] - expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForName)), - other_error: no_error!(), - }, - wrong_eku_dns [ any(windows, unix) ] => TestCase { - reference_id: EXAMPLE_COM, - chain: &[include_bytes!("root1-int1-ee_example.com-wrong_eku.crt"), ROOT1_INT1], - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Err(TlsError::InvalidCertificate( - CertificateError::Other(OtherError(Arc::from(EkuError))))), - other_error: Some(EkuError), - }, - wrong_eku_ipv4 [ any(windows, unix) ] => TestCase { - reference_id: LOCALHOST_IPV4, - chain: &[include_bytes!("root1-int1-ee_127.0.0.1-wrong_eku.crt"), ROOT1_INT1], - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Err(TlsError::InvalidCertificate( - CertificateError::Other(OtherError(Arc::from(EkuError))))), - other_error: Some(EkuError), - }, - wrong_eku_ipv6 [ any(windows, unix) ] => TestCase { - reference_id: LOCALHOST_IPV6, - chain: &[include_bytes!("root1-int1-ee_1-wrong_eku.crt"), ROOT1_INT1], - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Err(TlsError::InvalidCertificate( - CertificateError::Other(OtherError(Arc::from(EkuError))))), - other_error: Some(EkuError), - }, -} - -fn test_with_mock_root( - test_case: &TestCase, - root_src: Roots, -) { - log::info!("verifying {:?}", test_case.expected_result); - - let provider = test_provider(); - let verifier = match root_src { - Roots::OnlyExtra => Verifier::new_with_fake_root(ROOT1, provider), // TODO: time - #[cfg(not(target_os = "android"))] - Roots::ExtraAndPlatform => Verifier::new_with_extra_roots([ROOT1], provider).unwrap(), - }; - let mut chain = test_case - .chain - .iter() - .map(|bytes| pki_types::CertificateDer::from(*bytes)); - - let end_entity = chain.next().unwrap(); - let intermediates: Vec> = chain.collect(); - - let server_name = pki_types::ServerName::try_from(test_case.reference_id).unwrap(); - - if test_case.reference_id.parse::().is_ok() { - assert!(matches!(server_name, pki_types::ServerName::IpAddress(_))); - } else { - assert!(matches!(server_name, pki_types::ServerName::DnsName(_))); - } - - let result = verifier.verify_server_cert( - &end_entity, - &intermediates, - &server_name, - test_case.stapled_ocsp.unwrap_or(&[]), - test_case.verification_time, - ); - - assert_cert_error_eq( - &result.map(|_| ()), - &test_case.expected_result, - test_case.other_error.as_ref(), - ); - // TODO: get into specifics of errors returned when it fails. -} - -enum Roots { - /// Test with only extra roots, without loading the platform trust store. - /// - /// We want to keep things reproducible given the background-managed nature of trust roots on platforms. - OnlyExtra, - /// Test with loading the extra roots and the platform trust store. - /// - /// Right now, not all platforms are supported. - #[cfg(not(target_os = "android"))] - ExtraAndPlatform, -} - -/// Return a fixed [`pki_types::UnixTime`] for certificate validation purposes. -/// -/// We fix the "now" value used for certificate validation to a fixed point in time at which -/// we know the test certificates are valid. This must be updated if the mock certificates -/// are regenerated. -pub(crate) fn verification_time() -> pki_types::UnixTime { - // Wed, Sep 9 2026 11:52 UTC - pki_types::UnixTime::since_unix_epoch(Duration::from_secs(1_788_954_730)) -} diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-good.crt b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-good.crt deleted file mode 100644 index 510b3e2c..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-good.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-good.ocsp b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-good.ocsp deleted file mode 100644 index b55d9b9c..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-good.ocsp and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-revoked.crt b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-revoked.crt deleted file mode 100644 index b113904b..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-revoked.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-revoked.ocsp b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-revoked.ocsp deleted file mode 100644 index a804705e..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-revoked.ocsp and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-wrong_eku.crt b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-wrong_eku.crt deleted file mode 100644 index 6c522583..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_1-wrong_eku.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-good.crt b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-good.crt deleted file mode 100644 index bbf745ce..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-good.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-good.ocsp b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-good.ocsp deleted file mode 100644 index 5bdd13cc..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-good.ocsp and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-revoked.crt b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-revoked.crt deleted file mode 100644 index 40904cd7..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-revoked.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-revoked.ocsp b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-revoked.ocsp deleted file mode 100644 index d739981b..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-revoked.ocsp and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-wrong_eku.crt b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-wrong_eku.crt deleted file mode 100644 index 9c190311..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_127.0.0.1-wrong_eku.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-good.crt b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-good.crt deleted file mode 100644 index b7ca8ed2..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-good.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-good.ocsp b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-good.ocsp deleted file mode 100644 index 4196cc7d..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-good.ocsp and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-revoked.crt b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-revoked.crt deleted file mode 100644 index 5c6f7ff5..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-revoked.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-revoked.ocsp b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-revoked.ocsp deleted file mode 100644 index 20de8ba7..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-revoked.ocsp and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-wrong_eku.crt b/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-wrong_eku.crt deleted file mode 100644 index b041eb03..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1-ee_example.com-wrong_eku.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1-int1.crt b/rustls-platform-verifier/src/tests/verification_mock/root1-int1.crt deleted file mode 100644 index 4b0bc94d..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1-int1.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_mock/root1.crt b/rustls-platform-verifier/src/tests/verification_mock/root1.crt deleted file mode 100644 index addcbe34..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_mock/root1.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_real_world/aws_amazon_com_valid_1.crt b/rustls-platform-verifier/src/tests/verification_real_world/aws_amazon_com_valid_1.crt deleted file mode 100644 index 3e6174d2..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_real_world/aws_amazon_com_valid_1.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_real_world/aws_amazon_com_valid_2.crt b/rustls-platform-verifier/src/tests/verification_real_world/aws_amazon_com_valid_2.crt deleted file mode 100644 index 46289c19..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_real_world/aws_amazon_com_valid_2.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_real_world/aws_amazon_com_valid_3.crt b/rustls-platform-verifier/src/tests/verification_real_world/aws_amazon_com_valid_3.crt deleted file mode 100644 index 1dfb0e70..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_real_world/aws_amazon_com_valid_3.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_real_world/aws_amazon_com_valid_4.crt b/rustls-platform-verifier/src/tests/verification_real_world/aws_amazon_com_valid_4.crt deleted file mode 100644 index 75df0cc7..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_real_world/aws_amazon_com_valid_4.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_real_world/letsencrypt_org_valid_1.crt b/rustls-platform-verifier/src/tests/verification_real_world/letsencrypt_org_valid_1.crt deleted file mode 100644 index b63ff84e..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_real_world/letsencrypt_org_valid_1.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_real_world/letsencrypt_org_valid_2.crt b/rustls-platform-verifier/src/tests/verification_real_world/letsencrypt_org_valid_2.crt deleted file mode 100644 index 16d0fc4b..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_real_world/letsencrypt_org_valid_2.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_real_world/letsencrypt_org_valid_3.crt b/rustls-platform-verifier/src/tests/verification_real_world/letsencrypt_org_valid_3.crt deleted file mode 100644 index 7354d428..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_real_world/letsencrypt_org_valid_3.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_real_world/letsencrypt_org_valid_4.crt b/rustls-platform-verifier/src/tests/verification_real_world/letsencrypt_org_valid_4.crt deleted file mode 100644 index 6e068804..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_real_world/letsencrypt_org_valid_4.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_real_world/mod.rs b/rustls-platform-verifier/src/tests/verification_real_world/mod.rs deleted file mode 100644 index ea8fb7cc..00000000 --- a/rustls-platform-verifier/src/tests/verification_real_world/mod.rs +++ /dev/null @@ -1,290 +0,0 @@ -//! Certificate verification tests that use real-world certificates and the -//! "real" (non-mock) Rustls configuration returned. -//! -//! # Repeatability and Self-containedness -//! -//! These tests are written to reduce the time-bomb nature of testing with -//! real certificates, which expire and/or can be revoked over time. For -//! example, rather than connecting to the TLS server over the network, -//! these tests operate on a locally-cached copy of the certificates -//! downloaded at a point in time. However, there are some inherent -//! limitations of what we can do when using real-world certificates. We -//! accept that the benefit of having these tests outweigh this downside. -//! If we encounter cases where these tests are flaky we'll spend additional -//! effort -//! -//! * If these certificates are ever revoked then it is possible that, even if -//! with the measures mentioned in the next paragraphs, the operating system -//! might learn of the revocation externally and cause the tests to fail. -//! -//! * Some operating systems, Windows in particular, download the set of -//! trusted roots dynamically as-needed. If there is a failure during that -//! fetching then the trust anchors for these certificates might not be -//! trusted by the operating system's root store. -//! -//! XXX: These tests should be using a stapled OCSP responses so that the -//! (operating-system-based) verifier doesn't try to fetch an OCSP -//! response or CRL certificate. However, until we can fix the validation -//! at a specific point in time, we can't do this, as the OCSP responses -//! will generally expire within a matter of days of being produced. Also, -//! we'd need to upgrade to a version of Rustls that supports passing in -//! stapled OCSP responses for each certificate in the chain. Most certificate -//! verifiers that do fetching of OCSP responses will "fail open"; that is, if -//! a networking error causes the fetch of the OCSP response to fail, then -//! they will continue roughly as though they received a "Good" response. -//! Thus we don't expect these tests to be flaky w.r.t. that, except for -//! potentially poor performance. - -use core::time::Duration; - -use rustls::client::danger::ServerCertVerifier; -use rustls::pki_types; -#[cfg(not(any(target_vendor = "apple", windows)))] -use rustls::pki_types::{DnsName, ServerName}; -use rustls::{CertificateError, Error as TlsError}; - -use super::TestCase; -use crate::tests::{assert_cert_error_eq, test_provider}; -use crate::Verifier; - -// This is the certificate chain presented by one server for -// `aws.amazon.com` when this test was updated 2025-08-13. -// -// Use this to template view the certificate using OpenSSL: -// ```sh -// openssl x509 -inform der -text -in aws_amazon_com_valid_1.crt | less -// ``` -// -// You can update these cert files with `examples/update-certs.rs` -const VALID_AWS_AMAZON_COM_CHAIN: &[&[u8]] = &[ - include_bytes!("aws_amazon_com_valid_1.crt"), - include_bytes!("aws_amazon_com_valid_2.crt"), - include_bytes!("aws_amazon_com_valid_3.crt"), - // XXX: This certificate is included for testing in environments that might need - // a cross-signed root certificate instead of the just the server-provided one. - include_bytes!("aws_amazon_com_valid_4.crt"), -]; - -/// Returns a list of names valid for [VALID_AWS_AMAZON_COM_CHAIN], in a format -/// expected by `CertificateError::NotValidForContext`. -#[cfg(not(any(target_vendor = "apple", windows)))] -fn valid_aws_chain_names() -> Vec { - const VALID_AWS_NAMES: &[&str] = &[ - "aws.amazon.com", - "aws-us-west-2.amazon.com", - "www.aws.amazon.com", - "1.aws-lbr.amazonaws.com", - "amazonaws-china.com", - "www.amazonaws-china.com", - "aws-us-east-1.amazon.com", - ]; - - VALID_AWS_NAMES - .iter() - .copied() - .map(|name| format!("DnsName(\"{name}\")")) - .collect() -} - -const AWS_AMAZON_COM: &str = "aws.amazon.com"; - -// Domain names for which `VALID_AWS_AMAZON_COM_CHAIN` isn't valid. -const VALID_UNRELATED_DOMAIN: &str = "my.1password.com"; -const VALID_UNRELATED_SUBDOMAIN: &str = "www.amazon.com"; - -const LETSENCRYPT_ORG: &str = "letsencrypt.org"; - -const VALID_LETSENCRYPT_ORG_CHAIN: &[&[u8]] = &[ - include_bytes!("letsencrypt_org_valid_1.crt"), - include_bytes!("letsencrypt_org_valid_2.crt"), - include_bytes!("letsencrypt_org_valid_3.crt"), - include_bytes!("letsencrypt_org_valid_4.crt"), -]; - -macro_rules! real_world_test_cases { - { $( $name:ident => $test_case:expr ),+ , } => { - real_world_test_cases!(@ $($name => $test_case),+,); - - #[cfg(test)] - mod tests { - $( - #[test] - pub fn $name() { - super::$name() - } - )+ - - } - - #[cfg(feature = "ffi-testing")] - pub static ALL_TEST_CASES: &'static [fn()] = &[ - $($name),+ - ]; - }; - - {@ $( $name:ident => $test_case:expr ),+ , } => { - $( - pub(super) fn $name() { - real_world_test(&$test_case); - } - )+ - } -} - -macro_rules! no_error { - () => { - None:: - }; -} - -fn real_world_test(test_case: &TestCase) { - log::info!( - "verifying ref ID {:?} expected {:?}", - test_case.reference_id, - test_case.expected_result - ); - - let crypto_provider = test_provider(); - - // On BSD systems openssl-probe fails to find the system CA bundle, - // so we must provide extra roots from webpki-root-cert. - #[cfg(target_os = "freebsd")] - let verifier = Verifier::new_with_extra_roots( - webpki_root_certs::TLS_SERVER_ROOT_CERTS.iter().cloned(), - crypto_provider, - ) - .unwrap(); - - #[cfg(not(target_os = "freebsd"))] - let verifier = Verifier::new(crypto_provider).unwrap(); - - let mut chain = test_case - .chain - .iter() - .map(|bytes| pki_types::CertificateDer::from(*bytes)); - - let end_entity_cert = chain.next().unwrap(); - let intermediates: Vec> = chain.collect(); - - let server_name = pki_types::ServerName::try_from(test_case.reference_id).unwrap(); - - let stapled_ocsp = test_case.stapled_ocsp.unwrap_or(&[]); - - let result = verifier - .verify_server_cert( - &end_entity_cert, - &intermediates, - &server_name, - stapled_ocsp, - test_case.verification_time, - ) - .map(|_| ()); - - assert_cert_error_eq( - &result.map(|_| ()), - &test_case.expected_result, - None::<&std::convert::Infallible>, - ); - // TODO: get into specifics of errors returned when it fails. -} - -// Prefer to staple the OCSP response for the end-entity certificate for -// performance and repeatability. -real_world_test_cases! { - // The certificate is valid for *.aws.amazon.com. - aws_amazon_com_valid => TestCase { - reference_id: AWS_AMAZON_COM, - chain: VALID_AWS_AMAZON_COM_CHAIN, - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Ok(()), - other_error: no_error!(), - }, - // Same as above but without stapled OCSP. - aws_amazon_com_valid_no_stapled => TestCase { - reference_id: AWS_AMAZON_COM, - chain: VALID_AWS_AMAZON_COM_CHAIN, - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Ok(()), - other_error: no_error!(), - }, - // Valid also for www.amazon.amazon.com (extra subdomain). - _aws_amazon_com_valid => TestCase { - reference_id: "www.aws.amazon.com", - chain: VALID_AWS_AMAZON_COM_CHAIN, - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Ok(()), - other_error: no_error!(), - }, - // The certificate isn't valid for an unrelated subdomain. - unrelated_domain_invalid => TestCase { - reference_id: VALID_UNRELATED_SUBDOMAIN, - chain: VALID_AWS_AMAZON_COM_CHAIN, - stapled_ocsp: None, - verification_time: verification_time(), - #[cfg(not(any(target_vendor = "apple", windows)))] - expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForNameContext { - expected: ServerName::DnsName(DnsName::try_from(VALID_UNRELATED_SUBDOMAIN).unwrap()), - presented: valid_aws_chain_names(), - })), - #[cfg(any(target_vendor = "apple", windows))] - expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForName)), - other_error: no_error!(), - }, - // The certificate chain for the unrelated domain is not valid for - // my.1password.com. - unrelated_chain_not_valid_for_my_1password_com => TestCase { - reference_id: VALID_UNRELATED_DOMAIN, - chain: VALID_AWS_AMAZON_COM_CHAIN, - stapled_ocsp: None, - verification_time: verification_time(), - #[cfg(not(any(target_vendor = "apple", windows)))] - expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForNameContext { - expected: ServerName::DnsName(DnsName::try_from(VALID_UNRELATED_DOMAIN).unwrap()), - presented: valid_aws_chain_names(), - })), - #[cfg(any(target_vendor = "apple", windows))] - expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForName)), - other_error: no_error!(), - }, - letsencrypt => TestCase { - reference_id: LETSENCRYPT_ORG, - chain: VALID_LETSENCRYPT_ORG_CHAIN, - stapled_ocsp: None, - verification_time: verification_time(), - expected_result: Ok(()), - other_error: no_error!(), - }, - - // OCSP stapling works. - // - // XXX: This test is commented-out because it is a time-bomb due to the - // short lifetime of the OCSP responses for the certificate. - // - // TODO: If/when we can validate a certificate for a specific point in time - // during a test, re-enable this and have it test the certificate validity - // at a point in time where the OCSP response is valid. - // - // revoked_badssl_com_stapled => TestCase { - // reference_id: "revoked.badssl.com", - // chain: &[ - // include_bytes!("revoked_badssl_com_1.crt"), - // include_bytes!("revoked_badssl_com_2.crt"), - // ], - // stapled_ocsp: Some(include_bytes!("revoked_badssl_com_1.ocsp")), - // // XXX: We only do OCSP stapling on Windows. - // valid: !cfg!(windows), - // }, -} - -/// Return a fixed [`pki_types::UnixTime`] for certificate validation purposes. -/// -/// We fix the "now" value used for certificate validation to a fixed point in time at which -/// we know the test certificates are valid. This must be updated if the mock certificates -/// are regenerated. -pub(crate) fn verification_time() -> pki_types::UnixTime { - // Wed, 12 Aug 2026 10:23 UTC - pki_types::UnixTime::since_unix_epoch(Duration::from_secs(1_786_530_173)) -} diff --git a/rustls-platform-verifier/src/tests/verification_real_world/revoked_badssl_com_1.crt b/rustls-platform-verifier/src/tests/verification_real_world/revoked_badssl_com_1.crt deleted file mode 100644 index 63e7692a..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_real_world/revoked_badssl_com_1.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_real_world/revoked_badssl_com_1.ocsp b/rustls-platform-verifier/src/tests/verification_real_world/revoked_badssl_com_1.ocsp deleted file mode 100644 index 5693ed6f..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_real_world/revoked_badssl_com_1.ocsp and /dev/null differ diff --git a/rustls-platform-verifier/src/tests/verification_real_world/revoked_badssl_com_2.crt b/rustls-platform-verifier/src/tests/verification_real_world/revoked_badssl_com_2.crt deleted file mode 100644 index ff02b2d3..00000000 Binary files a/rustls-platform-verifier/src/tests/verification_real_world/revoked_badssl_com_2.crt and /dev/null differ diff --git a/rustls-platform-verifier/src/verification/android.rs b/rustls-platform-verifier/src/verification/android.rs deleted file mode 100644 index 3916a5fa..00000000 --- a/rustls-platform-verifier/src/verification/android.rs +++ /dev/null @@ -1,340 +0,0 @@ -use jni::{ - jni_sig, jni_str, - objects::{JByteArray, JObject, JObjectArray, JString, JValue}, - signature::MethodSignature, - Env, -}; -use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier}; -use rustls::crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}; -use rustls::pki_types; -use rustls::Error::InvalidCertificate; -use rustls::{ - CertificateError, DigitallySignedStruct, Error as TlsError, OtherError, SignatureScheme, -}; -use std::sync::Arc; - -use super::{log_server_cert, ALLOWED_EKUS}; -use crate::android::{with_context, CachedClass}; - -static CERT_VERIFIER_CLASS: CachedClass = - CachedClass::new(jni_str!("org.rustls.platformverifier.CertificateVerifier")); - -// Note: Keep these in sync with the Kotlin enum. -#[derive(Debug)] -enum VerifierStatus { - Ok, - Unavailable, - Expired, - UnknownCert, - Revoked, - InvalidEncoding, - InvalidExtension, -} - -// Android's certificate verifier ignores this outright and this is considered the -// official recommendation. See https://bugs.chromium.org/p/chromium/issues/detail?id=627154. -const AUTH_TYPE: &str = "RSA"; - -/// A TLS certificate verifier that utilizes the Android platform verifier. -#[derive(Debug)] -pub struct Verifier { - /// Testing only: The root CA certificate to trust. - #[cfg(any(test, feature = "ffi-testing"))] - test_only_root_ca_override: Option>, - crypto_provider: Arc, -} - -#[cfg(any(test, feature = "ffi-testing"))] -impl Drop for Verifier { - fn drop(&mut self) { - with_context::<_, ()>(|cx| { - let cert_verifier_class = CERT_VERIFIER_CLASS.get(cx)?; - cx.env - .call_static_method( - cert_verifier_class, - jni_str!("clearMockRoots"), - jni_sig!(() -> void), - &[], - )? - .v()?; - Ok(()) - }) - .expect("failed to clear test roots") - } -} - -impl Verifier { - /// Creates a new instance of a TLS certificate verifier that utilizes the - /// Android certificate facilities. - #[cfg_attr(docsrs, doc(cfg(all())))] - pub fn new(crypto_provider: Arc) -> Result { - Ok(Self { - #[cfg(any(test, feature = "ffi-testing"))] - test_only_root_ca_override: None, - crypto_provider, - }) - } - - /// Creates a test-only TLS certificate verifier which trusts our fake root CA cert. - #[cfg(any(test, feature = "ffi-testing"))] - pub(crate) fn new_with_fake_root( - root: pki_types::CertificateDer<'static>, - crypto_provider: Arc, - ) -> Self { - Self { - test_only_root_ca_override: Some(root), - crypto_provider, - } - } - - fn verify_certificate( - &self, - end_entity: &pki_types::CertificateDer<'_>, - intermediates: &[pki_types::CertificateDer<'_>], - server_name: &pki_types::ServerName, - ocsp_response: Option<&[u8]>, - now: pki_types::UnixTime, - ) -> Result<(), TlsError> { - let certificate_chain = std::iter::once(end_entity) - .chain(intermediates) - .map(|cert| cert.as_ref()) - .enumerate(); - - // Convert the unix timestamp into milliseconds, expressed as - // an i64 to later be converted into a Java Long used for a Date - // constructor. - let now: i64 = (now.as_secs() * 1000) - .try_into() - .map_err(|_| TlsError::FailedToGetCurrentTime)?; - - let verification_result = with_context(|cx| { - let cert_verifier_class = CERT_VERIFIER_CLASS.get(cx)?; - - let cert_list = { - let array = JObjectArray::::new( - cx.env, - intermediates.len() + 1, - &JByteArray::null(), - )?; - - for (idx, cert) in certificate_chain { - let cert_buffer = cx.env.byte_array_from_slice(cert)?; - array.set_element(cx.env, idx, cert_buffer)?; - } - - array - }; - - let allowed_ekus = { - let array = - JObjectArray::::new(cx.env, ALLOWED_EKUS.len(), &JString::null())?; - - for (idx, eku) in ALLOWED_EKUS.iter().enumerate() { - let eku = cx.env.new_string(eku.to_str().expect( - "ALLOWED_EKUS entries are ASCII constants -- always valid UTF-8", - ))?; - array.set_element(cx.env, idx, eku)? - } - - array - }; - - let ocsp_response = match ocsp_response { - Some(b) => cx.env.byte_array_from_slice(b)?, - None => JByteArray::null(), - }; - - #[cfg(any(test, feature = "ffi-testing"))] - { - if let Some(mock_root) = &self.test_only_root_ca_override { - let mock_root = cx.env.byte_array_from_slice(mock_root)?; - cx.env - .call_static_method( - cert_verifier_class, - jni_str!("addMockRoot"), - jni_sig!((byte[]) -> void), - &[JValue::from(&mock_root)], - )? - .v() - .expect("failed to add test root") - } - } - - const VERIFIER_CALL: MethodSignature<'static, 'static> = jni_sig!( - ( - android.content.Context, - JString, - JString, - JString[], - byte[], - jlong, - byte[][] - ) -> org.rustls.platformverifier.VerificationResult - ); - - let server_name = server_name.to_str(); - // Android's verifier doesn't require this but trim trailing `.` labels for consistency across platforms. - let server_name = server_name.strip_suffix('.').unwrap_or(&server_name); - - let server_name = cx.env.new_string(server_name)?; - let auth_type = cx.env.new_string(AUTH_TYPE)?; - - let result = cx - .env - .call_static_method( - cert_verifier_class, - jni_str!("verifyCertificateChain"), - VERIFIER_CALL, - &[ - JValue::from(cx.global.context.as_ref()), - JValue::from(&server_name), - JValue::from(&auth_type), - JValue::from(&JObject::from(allowed_ekus)), - JValue::from(&ocsp_response), - JValue::Long(now), - JValue::from(&JObject::from(cert_list)), - ], - )? - .l()?; - - Ok(extract_result_info(cx.env, result)) - }); - - match verification_result { - Ok((status, maybe_msg)) => { - // `maybe_msg` is safe to log as its exactly what the system told us. - // - // The branches which unwrap it will never fail since the Kotlin side always sets it - // for the variants. - match status { - VerifierStatus::Ok => { - // If everything else was OK, check the hostname. - rustls::client::verify_server_name( - &rustls::server::ParsedCertificate::try_from(end_entity)?, - server_name, - ) - } - VerifierStatus::Unavailable => Err(TlsError::General(String::from( - "No system trust stores available", - ))), - VerifierStatus::Expired => Err(InvalidCertificate(CertificateError::Expired)), - VerifierStatus::UnknownCert => { - log::warn!("certificate was not trusted: {}", maybe_msg.unwrap()); - Err(InvalidCertificate(CertificateError::UnknownIssuer)) - } - VerifierStatus::Revoked => { - log::warn!("certificate was revoked: {}", maybe_msg.unwrap()); - Err(InvalidCertificate(CertificateError::Revoked)) - } - VerifierStatus::InvalidEncoding => { - Err(InvalidCertificate(CertificateError::BadEncoding)) - } - VerifierStatus::InvalidExtension => Err(InvalidCertificate( - CertificateError::Other(OtherError(std::sync::Arc::new(super::EkuError))), - )), - } - } - Err(e) => Err(TlsError::General(format!( - "failed to call native verifier: {e:?}", - ))), - } - } -} - -fn extract_result_info(env: &mut Env<'_>, result: JObject<'_>) -> (VerifierStatus, Option) { - let status_code = env - .get_field(&result, jni_str!("code"), jni_sig!(jint)) - .and_then(|code| code.i()) - .unwrap(); - - let status = match status_code { - 0 => VerifierStatus::Ok, - 1 => VerifierStatus::Unavailable, - 2 => VerifierStatus::Expired, - 3 => VerifierStatus::UnknownCert, - 4 => VerifierStatus::Revoked, - 5 => VerifierStatus::InvalidEncoding, - 6 => VerifierStatus::InvalidExtension, - i => unreachable!("unknown status code: {i}"), - }; - - // Extract the `String?`. - let msg = env - .get_field(result, jni_str!("message"), jni_sig!(java.lang.String)) - .and_then(|m| m.l()) - .map(|s| { - if s.is_null() { - None - } else { - env.cast_local::(s) - .and_then(|s| s.try_to_string(env)) - .ok() - } - }) - .unwrap(); - (status, msg) -} - -#[cfg_attr(docsrs, doc(cfg(all())))] -impl ServerCertVerifier for Verifier { - fn verify_server_cert( - &self, - end_entity: &pki_types::CertificateDer<'_>, - intermediates: &[pki_types::CertificateDer<'_>], - server_name: &pki_types::ServerName, - ocsp_response: &[u8], - now: pki_types::UnixTime, - ) -> Result { - log_server_cert(end_entity); - - let ocsp_data = if !ocsp_response.is_empty() { - Some(ocsp_response) - } else { - None - }; - - match self.verify_certificate(end_entity, intermediates, server_name, ocsp_data, now) { - Ok(()) => Ok(rustls::client::danger::ServerCertVerified::assertion()), - Err(e) => { - // This error only tells us what the system errored with, so it doesn't leak anything - // sensitive. - log::error!("failed to verify TLS certificate: {}", e); - Err(e) - } - } - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls12_signature( - message, - cert, - dss, - &self.crypto_provider.signature_verification_algorithms, - ) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls13_signature( - message, - cert, - dss, - &self.crypto_provider.signature_verification_algorithms, - ) - } - - fn supported_verify_schemes(&self) -> Vec { - self.crypto_provider - .signature_verification_algorithms - .supported_schemes() - } -} diff --git a/rustls-platform-verifier/src/verification/apple.rs b/rustls-platform-verifier/src/verification/apple.rs deleted file mode 100644 index b4e34e7d..00000000 --- a/rustls-platform-verifier/src/verification/apple.rs +++ /dev/null @@ -1,313 +0,0 @@ -use std::sync::Arc; - -use core_foundation::date::CFDate; -use core_foundation_sys::date::kCFAbsoluteTimeIntervalSince1970; -use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier}; -use rustls::crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}; -use rustls::pki_types; -use rustls::{ - CertificateError, DigitallySignedStruct, Error as TlsError, OtherError, SignatureScheme, -}; -use security_framework::{ - certificate::SecCertificate, policy::SecPolicy, secure_transport::SslProtocolSide, - trust::SecTrust, -}; - -use super::log_server_cert; -use crate::verification::invalid_certificate; - -mod errors { - pub(super) use security_framework_sys::base::{ - errSecCertificateRevoked, errSecCreateChainFailed, errSecHostNameMismatch, - errSecInvalidExtendedKeyUsage, - }; -} - -#[allow(clippy::as_conversions)] -fn system_time_to_cfdate(time: pki_types::UnixTime) -> Result { - // SAFETY: The interval is defined by macOS externally, but is always present and never modified at runtime - // since its a global variable. - // - // See https://developer.apple.com/documentation/corefoundation/kcfabsolutetimeintervalsince1970. - let unix_adjustment = unsafe { kCFAbsoluteTimeIntervalSince1970 as u64 }; - - // Convert a system timestamp based off the UNIX epoch into the - // Apple epoch used by all `CFAbsoluteTime` values. - // Subtracting Durations with sub() will panic on overflow - time.as_secs() - .checked_sub(unix_adjustment) - .ok_or(TlsError::FailedToGetCurrentTime) - .map(|epoch| CFDate::new(epoch as f64)) -} - -/// A TLS certificate verifier that utilizes the Apple platform certificate facilities. -#[derive(Debug)] -pub struct Verifier { - /// Extra trust anchors to add to the verifier above and beyond those provided by - /// the system-provided trust stores. - extra_roots: Vec, - /// Testing only: The root CA certificate to trust. - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - test_only_root_ca_override: Option, - crypto_provider: Arc, -} - -impl Verifier { - /// Creates a new instance of a TLS certificate verifier that utilizes the Apple certificate - /// facilities. - #[cfg_attr(docsrs, doc(cfg(all())))] - pub fn new(crypto_provider: Arc) -> Result { - Ok(Self { - extra_roots: Vec::new(), - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - test_only_root_ca_override: None, - crypto_provider, - }) - } - - /// Creates a new instance of a TLS certificate verifier that utilizes the Apple certificate - /// facilities with the addition of extra root certificates to trust. - /// - /// See [Verifier::new] for the external requirements the verifier needs. - #[cfg_attr(docsrs, doc(cfg(not(target_os = "android"))))] - pub fn new_with_extra_roots( - roots: impl IntoIterator>, - crypto_provider: Arc, - ) -> Result { - let extra_roots = roots - .into_iter() - .map(|root| { - SecCertificate::from_der(&root) - .map_err(|_| TlsError::InvalidCertificate(CertificateError::BadEncoding)) - }) - .collect::, _>>()?; - Ok(Self { - extra_roots, - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - test_only_root_ca_override: None, - crypto_provider, - }) - } - - /// Creates a test-only TLS certificate verifier which trusts our fake root CA cert. - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - pub(crate) fn new_with_fake_root( - root: pki_types::CertificateDer<'static>, - crypto_provider: Arc, - ) -> Self { - Self { - extra_roots: Vec::new(), - test_only_root_ca_override: Some(SecCertificate::from_der(root.as_ref()).unwrap()), - crypto_provider, - } - } - - fn verify_certificate( - &self, - end_entity: &pki_types::CertificateDer<'_>, - intermediates: &[pki_types::CertificateDer<'_>], - server_name: &str, - ocsp_response: Option<&[u8]>, - now: pki_types::UnixTime, - ) -> Result<(), TlsError> { - let certificates: Vec = std::iter::once(end_entity.as_ref()) - .chain(intermediates.iter().map(|cert| cert.as_ref())) - .map(|cert| { - SecCertificate::from_der(cert) - .map_err(|_| TlsError::InvalidCertificate(CertificateError::BadEncoding)) - }) - .collect::, _>>()?; - - // Create our verification policy suitable for verifying TLS chains. - // This uses the "default" verification engine and parameters, the same as Windows. - // - // The protocol side should be set to `server` for a client to verify server TLS - // certificates. - // - // The server name will be required to match what the end-entity certificate reports - // - // Ref: https://developer.apple.com/documentation/security/1392592-secpolicycreatessl - let policy = SecPolicy::create_ssl(SslProtocolSide::SERVER, Some(server_name)); - - // Create our trust evaluation context/chain. - // - // Apple requires that the certificate to be verified is always first in the array, and we - // always place the end-entity certificate at the start. - // - // Ref: https://developer.apple.com/documentation/security/1401555-sectrustcreatewithcertificates - let mut trust_evaluation = SecTrust::create_with_certificates(&certificates, &[policy]) - .map_err(|e| TlsError::General(e.to_string()))?; - - // Tell the system that we want to consider the certificates evaluation at the point - // in time that `rustls` provided. - let now = system_time_to_cfdate(now)?; - trust_evaluation - .set_trust_verify_date(&now) - .map_err(|e| invalid_certificate(e.to_string()))?; - - // If we have OCSP response data, make sure the system makes use of it. - if let Some(ocsp_response) = ocsp_response { - trust_evaluation - .set_trust_ocsp_response(std::iter::once(ocsp_response)) - .map_err(|e| invalid_certificate(e.to_string()))?; - } - - #[cfg(not(any(test, feature = "ffi-testing", feature = "dbg")))] - let extra_roots = self.extra_roots.as_slice(); - - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - let extra_roots: Vec<_> = self - .extra_roots - .iter() - .chain(self.test_only_root_ca_override.as_ref()) - .cloned() - .collect(); - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - let extra_roots = extra_roots.as_slice(); - - // If any extra roots were provided by the user (or tests), provide them to the trust - // evaluation regardless of their system trust settings or status. - if !extra_roots.is_empty() { - trust_evaluation - .set_anchor_certificates(extra_roots) - .map_err(|e| TlsError::Other(OtherError(Arc::new(e))))?; - - // We want to trust both the system-installed and the extra roots. This must be set - // since calling `SecTrustSetAnchorCertificates` "disables the trusting of any - // anchors other than the ones specified by this function call" by default. - trust_evaluation - .set_trust_anchor_certificates_only(false) - .map_err(|e| TlsError::Other(OtherError(Arc::new(e))))?; - } - - // When testing, support using fake roots and ignoring default roots present on the system for - // consistency/reproducibility reasons. - // - // XXX: This does not currently limit revocation from fetching information online, or prevent - // the downloading of root CAs. - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - { - if self.test_only_root_ca_override.is_some() { - // XXX: The test root was already provided to the trust evaluation as an extra root. - // We only need to stop use of the default system-installed roots. - - // As per [Apple's docs], building and verifying a certificate chain will - // search through the system and keychain to find certificates that it - // needs to try and construct a trust chain back to the root. - // - // `SecTrustSetAnchorCertificatesOnly` must be called after setting custom - // anchor certificates, which "disables trusting any other anchors than the ones passed in - // with the `SecTrustSetAnchorCertificates` function". - // - // [Apple's docs]: https://developer.apple.com/documentation/security/certificate_key_and_trust_services/trust/creating_a_trust_object - trust_evaluation - .set_trust_anchor_certificates_only(true) - .expect("failed to tell trust to only use provided anchors"); - } - } - - let trust_error = match trust_evaluation.evaluate_with_error() { - Ok(()) => return Ok(()), - Err(e) => e, - }; - - let err_code = trust_error.code(); - - let err = err_code - .try_into() - .map_err(|_| ()) - .and_then(|code| { - // Only map the errors we need for tests. - match code { - errors::errSecHostNameMismatch => Ok(TlsError::InvalidCertificate( - CertificateError::NotValidForName, - )), - errors::errSecCreateChainFailed => Ok(TlsError::InvalidCertificate( - CertificateError::UnknownIssuer, - )), - errors::errSecInvalidExtendedKeyUsage => Ok(TlsError::InvalidCertificate( - CertificateError::Other(OtherError(Arc::new(super::EkuError))), - )), - errors::errSecCertificateRevoked => { - Ok(TlsError::InvalidCertificate(CertificateError::Revoked)) - } - _ => Err(()), - } - }) - // Fallback to an error containing the description and specific error code so that - // the exact error cause can be looked up easily. - .unwrap_or_else(|_| invalid_certificate(format!("{trust_error}: {err_code}"))); - - Err(err) - } -} - -#[cfg_attr(docsrs, doc(cfg(all())))] -impl ServerCertVerifier for Verifier { - fn verify_server_cert( - &self, - end_entity: &pki_types::CertificateDer<'_>, - intermediates: &[pki_types::CertificateDer<'_>], - server_name: &pki_types::ServerName, - ocsp_response: &[u8], - now: pki_types::UnixTime, - ) -> Result { - log_server_cert(end_entity); - - // Convert IP addresses to name strings to ensure match check on leaf certificate. - // Ref: https://developer.apple.com/documentation/security/1392592-secpolicycreatessl - let server = server_name.to_str(); - // Apple's verifier doesn't require this but trim trailing `.` labels for consistency across platforms. - let server = server.strip_suffix('.').unwrap_or(&server); - - let ocsp_data = if !ocsp_response.is_empty() { - Some(ocsp_response) - } else { - None - }; - - match self.verify_certificate(end_entity, intermediates, server, ocsp_data, now) { - Ok(()) => Ok(rustls::client::danger::ServerCertVerified::assertion()), - Err(e) => { - // This error only tells us what the system errored with, so it doesn't leak anything - // sensitive. - log::error!("failed to verify TLS certificate: {}", e); - Err(e) - } - } - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls12_signature( - message, - cert, - dss, - &self.crypto_provider.signature_verification_algorithms, - ) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls13_signature( - message, - cert, - dss, - &self.crypto_provider.signature_verification_algorithms, - ) - } - - fn supported_verify_schemes(&self) -> Vec { - self.crypto_provider - .signature_verification_algorithms - .supported_schemes() - } -} diff --git a/rustls-platform-verifier/src/verification/mod.rs b/rustls-platform-verifier/src/verification/mod.rs deleted file mode 100644 index 9d947ff2..00000000 --- a/rustls-platform-verifier/src/verification/mod.rs +++ /dev/null @@ -1,86 +0,0 @@ -#[cfg(any(windows, target_vendor = "apple"))] -use std::sync::Arc; - -#[cfg(all( - any(unix, target_arch = "wasm32"), - not(target_os = "android"), - not(target_vendor = "apple"), -))] -mod others; - -#[cfg(all( - any(unix, target_arch = "wasm32"), - not(target_os = "android"), - not(target_vendor = "apple"), -))] -pub use others::Verifier; - -#[cfg(target_vendor = "apple")] -mod apple; - -#[cfg(target_vendor = "apple")] -pub use apple::Verifier; - -#[cfg(target_os = "android")] -pub(crate) mod android; - -#[cfg(target_os = "android")] -pub use android::Verifier; - -#[cfg(windows)] -mod windows; - -#[cfg(windows)] -pub use windows::Verifier; - -/// An EKU was invalid for the use case of verifying a server certificate. -/// -/// This error is used primarily for tests. -#[cfg_attr(windows, allow(dead_code))] // not used by windows verifier -#[derive(Debug, PartialEq)] -pub(crate) struct EkuError; - -impl std::fmt::Display for EkuError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("certificate had invalid extensions") - } -} - -impl std::error::Error for EkuError {} - -// Log the certificate we are verifying so that we can try and find what may be wrong with it -// if we need to debug a user's situation. -fn log_server_cert(_end_entity: &rustls::pki_types::CertificateDer<'_>) { - #[cfg(feature = "cert-logging")] - { - use base64::Engine; - log::debug!( - "verifying certificate: {}", - base64::engine::general_purpose::STANDARD.encode(_end_entity.as_ref()) - ); - } -} - -// Unknown certificate error shorthand. Used when we need to construct an "Other" certificate -// error with a platform specific error message. -#[cfg(any(windows, target_vendor = "apple"))] -fn invalid_certificate(reason: impl Into) -> rustls::Error { - rustls::Error::InvalidCertificate(rustls::CertificateError::Other(rustls::OtherError( - Arc::from(Box::from(reason.into())), - ))) -} - -/// List of EKUs that one or more of that *must* be in the end-entity certificate. -/// -/// Legacy server-gated crypto OIDs are assumed to no longer be in use. -/// -/// Currently supported: -/// - id-kp-serverAuth -// TODO: Chromium also allows for `OID_ANY_EKU` on Android. -#[cfg(target_os = "windows")] -// XXX: Windows requires that we NUL terminate EKU strings. -// See https://github.com/rustls/rustls-platform-verifier/issues/126#issuecomment-2306232794. -const ALLOWED_EKUS: &[windows_sys::core::PCSTR] = - &[windows_sys::Win32::Security::Cryptography::szOID_PKIX_KP_SERVER_AUTH]; -#[cfg(target_os = "android")] -pub const ALLOWED_EKUS: &[&std::ffi::CStr] = &[c"1.3.6.1.5.5.7.3.1"]; diff --git a/rustls-platform-verifier/src/verification/others.rs b/rustls-platform-verifier/src/verification/others.rs deleted file mode 100644 index 6e4796fb..00000000 --- a/rustls-platform-verifier/src/verification/others.rs +++ /dev/null @@ -1,184 +0,0 @@ -use std::fmt::Debug; -use std::sync::Arc; - -use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; -use rustls::client::WebPkiServerVerifier; -use rustls::pki_types; -use rustls::{ - crypto::CryptoProvider, CertificateError, DigitallySignedStruct, Error as TlsError, OtherError, - SignatureScheme, -}; - -use super::log_server_cert; - -/// A TLS certificate verifier that uses the system's root store and WebPKI. -#[derive(Debug)] -pub struct Verifier { - // We currently keep one set of certificates per-verifier so that - // recreating the verifier will pull fresh root certificates from disk, - // picking up on any changes that might have been made since. - inner: Arc, -} - -impl Verifier { - /// Creates a new verifier whose certificate validation is provided by - /// WebPKI, using root certificates provided by the platform. - #[cfg_attr(docsrs, doc(cfg(all())))] - pub fn new(crypto_provider: Arc) -> Result { - Self::new_inner([], None, crypto_provider) - } - - /// Creates a new verifier whose certificate validation is provided by - /// WebPKI, using root certificates provided by the platform and augmented by - /// the provided extra root certificates. - #[cfg_attr(docsrs, doc(cfg(not(target_os = "android"))))] - pub fn new_with_extra_roots( - extra_roots: impl IntoIterator>, - crypto_provider: Arc, - ) -> Result { - Self::new_inner(extra_roots, None, crypto_provider) - } - - /// Creates a test-only TLS certificate verifier which trusts our fake root CA cert. - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - pub(crate) fn new_with_fake_root( - root: pki_types::CertificateDer<'static>, - crypto_provider: Arc, - ) -> Self { - Self::new_inner([], Some(root), crypto_provider) - .expect("failed to create verifier with fake root") - } - - /// Creates a new verifier whose certificate validation is provided by - /// WebPKI, using root certificates provided by the platform and augmented by - /// the provided extra root certificates. - fn new_inner( - extra_roots: impl IntoIterator>, - #[allow(unused)] // test_root is only used in tests - test_root: Option>, - crypto_provider: Arc, - ) -> Result { - let mut root_store = rustls::RootCertStore::empty(); - - // For testing only: load fake root cert, instead of native/WebPKI roots - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - { - if let Some(test_root) = test_root { - root_store.add(test_root)?; - return Ok(Self { - inner: WebPkiServerVerifier::builder_with_provider( - root_store.into(), - crypto_provider.clone(), - ) - .build() - .map_err(|e| TlsError::Other(OtherError(Arc::new(e))))?, - }); - } - } - - // While we ignore invalid certificates from the system, we forward errors from - // parsing the extra roots to the caller. - for cert in extra_roots { - root_store.add(cert)?; - } - - #[cfg(all( - unix, - not(target_os = "android"), - not(target_vendor = "apple"), - not(target_arch = "wasm32"), - ))] - { - let result = rustls_native_certs::load_native_certs(); - let (added, ignored) = root_store.add_parsable_certificates(result.certs); - if ignored > 0 { - log::warn!("{ignored} platform CA root certificates were ignored due to errors"); - } - - for error in result.errors { - log::warn!("Error loading CA root certificate: {error}"); - } - - // Don't return an error if this fails when other roots have already been loaded via - // `new_with_extra_roots`. It leads to extra failure cases where connections would otherwise still work. - if root_store.is_empty() { - return Err(rustls::Error::General( - "No CA certificates were loaded from the system".to_owned(), - )); - } else { - log::debug!("Loaded {added} CA root certificates from the system"); - } - } - - #[cfg(target_arch = "wasm32")] - { - root_store.add_parsable_certificates( - webpki_root_certs::TLS_SERVER_ROOT_CERTS.iter().cloned(), - ); - }; - - Ok(Self { - inner: WebPkiServerVerifier::builder_with_provider(root_store.into(), crypto_provider) - .build() - .map_err(|e| TlsError::Other(OtherError(Arc::new(e))))?, - }) - } -} - -#[cfg_attr(docsrs, doc(cfg(all())))] -impl ServerCertVerifier for Verifier { - fn verify_server_cert( - &self, - end_entity: &pki_types::CertificateDer<'_>, - intermediates: &[pki_types::CertificateDer<'_>], - server_name: &pki_types::ServerName, - ocsp_response: &[u8], - now: pki_types::UnixTime, - ) -> Result { - log_server_cert(end_entity); - - self.inner - .verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now) - .map_err(map_webpki_errors) - // This only contains information from the system or other public - // bits of the TLS handshake, so it can't leak anything. - .map_err(|e| { - log::error!("failed to verify TLS certificate: {}", e); - e - }) - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - self.inner.verify_tls12_signature(message, cert, dss) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - self.inner.verify_tls13_signature(message, cert, dss) - } - - fn supported_verify_schemes(&self) -> Vec { - self.inner.supported_verify_schemes() - } -} - -fn map_webpki_errors(err: TlsError) -> TlsError { - match &err { - TlsError::InvalidCertificate(CertificateError::InvalidPurpose) - | TlsError::InvalidCertificate(CertificateError::InvalidPurposeContext { .. }) => { - TlsError::InvalidCertificate(CertificateError::Other(OtherError(Arc::new( - super::EkuError, - )))) - } - _ => err, - } -} diff --git a/rustls-platform-verifier/src/verification/windows.rs b/rustls-platform-verifier/src/verification/windows.rs deleted file mode 100644 index 24fadc9b..00000000 --- a/rustls-platform-verifier/src/verification/windows.rs +++ /dev/null @@ -1,798 +0,0 @@ -//! `Verifier` implementation for Windows targets. -//! -//! The design of the rustls-native-certs crate for Windows doesn't work -//! completely enough. In general it is hard to emulate enough of what -//! Windows does to be compatible with all users' configurations, especially -//! when corporate MitM proxies or custom CAs or complex trust policies are -//! used. Instead, delegate to Windows's own certificate validation engine -//! directly. -//! -//! This implementation was modeled on: -//! * Chromium's [cert_verify_proc_win.cc] and [x509_util_win.cc] -//! * Golang's [root_windows.go] -//! * [Microsoft's Documentation] and [Microsoft's Example] -//! -//! [cert_verify_proc_win.cc]: -//! [x509_util_win.cc]: -//! [root_windows.go]: -//! [Microsoft's Documentation]: -//! [Microsoft's Example]: - -use std::{ - convert::TryInto, - mem::{self, MaybeUninit}, - os::raw::c_void, - ptr::{self, NonNull}, - sync::Arc, -}; - -use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier}; -use rustls::crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}; -use rustls::pki_types; -use rustls::{ - CertificateError, DigitallySignedStruct, Error as TlsError, Error::InvalidCertificate, - SignatureScheme, -}; -use windows_sys::Win32::{ - Foundation::{ - CERT_E_CN_NO_MATCH, CERT_E_EXPIRED, CERT_E_INVALID_NAME, CERT_E_UNTRUSTEDROOT, - CERT_E_WRONG_USAGE, CRYPT_E_REVOKED, FILETIME, TRUE, - }, - Security::Cryptography::{ - CertAddEncodedCertificateToStore, CertCloseStore, CertCreateCertificateChainEngine, - CertFreeCertificateChain, CertFreeCertificateChainEngine, CertFreeCertificateContext, - CertGetCertificateChain, CertOpenStore, CertSetCertificateContextProperty, - CertVerifyCertificateChainPolicy, HTTPSPolicyCallbackData, AUTHTYPE_SERVER, - CERT_CHAIN_CACHE_END_CERT, CERT_CHAIN_CONTEXT, - CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS, CERT_CHAIN_POLICY_PARA, - CERT_CHAIN_POLICY_SSL, CERT_CHAIN_POLICY_STATUS, - CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT, CERT_CHAIN_REVOCATION_CHECK_END_CERT, - CERT_CONTEXT, CERT_OCSP_RESPONSE_PROP_ID, CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG, - CERT_STORE_ADD_ALWAYS, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, CERT_STORE_PROV_MEMORY, - CERT_STRONG_SIGN_PARA, CERT_TRUST_IS_PARTIAL_CHAIN, CERT_TRUST_IS_UNTRUSTED_ROOT, - CERT_USAGE_MATCH, CRYPT_INTEGER_BLOB, CTL_USAGE, HCERTSTORE, USAGE_MATCH_TYPE_AND, - X509_ASN_ENCODING, - }, -}; - -use super::{log_server_cert, ALLOWED_EKUS}; - -// The `windows-sys` definition for `CERT_CHAIN_PARA` does not take old OS versions -// into account so we define it ourselves for better OS backwards compat. -// In the future a compile-time size assertion can be added against the upstream type to help stay in sync. -#[allow(non_camel_case_types, non_snake_case)] -#[repr(C)] -struct CERT_CHAIN_PARA { - pub cbSize: u32, - pub RequestedUsage: CERT_USAGE_MATCH, - pub RequestedIssuancePolicy: CERT_USAGE_MATCH, - pub dwUrlRetrievalTimeout: u32, - pub fCheckRevocationFreshnessTime: i32, // BOOL - pub dwRevocationFreshnessTime: u32, - pub pftCacheResync: *mut FILETIME, - // XXX: `pStrongSignPara` and `dwStrongSignFlags` might or might not be defined on the current system. It started - // being available in Windows 8. See https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_para - #[cfg(not(target_vendor = "win7"))] - pub pStrongSignPara: *const CERT_STRONG_SIGN_PARA, - #[cfg(not(target_vendor = "win7"))] - pub dwStrongSignFlags: u32, -} - -// Same workaround with CERT_CHAIN_PARA -#[allow(non_camel_case_types, non_snake_case)] -#[repr(C)] -#[derive(Clone, Copy)] -pub struct CERT_CHAIN_ENGINE_CONFIG { - pub cbSize: u32, - pub hRestrictedRoot: HCERTSTORE, - pub hRestrictedTrust: HCERTSTORE, - pub hRestrictedOther: HCERTSTORE, - pub cAdditionalStore: u32, - pub rghAdditionalStore: *mut HCERTSTORE, - pub dwFlags: u32, - pub dwUrlRetrievalTimeout: u32, - pub MaximumCachedCertificates: u32, - pub CycleDetectionModulus: u32, - pub hExclusiveRoot: HCERTSTORE, - pub hExclusiveTrustedPeople: HCERTSTORE, - // XXX: `dwExclusiveFlags` started being available in Windows 8 and Windows Server 2012 - // See https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_engine_config - #[cfg(not(target_vendor = "win7"))] - pub dwExclusiveFlags: u32, -} - -use crate::verification::invalid_certificate; - -// SAFETY: see method implementation -unsafe impl ZeroedWithSize for CERT_CHAIN_PARA { - fn zeroed_with_size() -> Self { - // SAFETY: `CERT_CHAIN_PARA` only contains pointers and integers, which are safe to zero. - // Additionally, MSDN states you *MUST* zero all unused fields. - let mut new: Self = unsafe { mem::zeroed() }; - new.cbSize = Self::SIZE; - new - } -} - -// SAFETY: see method implementation -unsafe impl ZeroedWithSize for HTTPSPolicyCallbackData { - fn zeroed_with_size() -> Self { - // SAFETY: zeroed is needed here since it contains a union. - let mut new: Self = unsafe { mem::zeroed() }; - new.Anonymous.cbSize = Self::SIZE; - new - } -} - -// SAFETY: see method implementation -unsafe impl ZeroedWithSize for CERT_CHAIN_POLICY_PARA { - fn zeroed_with_size() -> Self { - // SAFETY: This structure only contains integers and pointers. - let mut new: Self = unsafe { mem::zeroed() }; - new.cbSize = Self::SIZE; - new - } -} - -// SAFETY: see method implementation -unsafe impl ZeroedWithSize for CERT_CHAIN_ENGINE_CONFIG { - fn zeroed_with_size() -> Self { - // SAFETY: This structure only contains integers and pointers. - let mut new: Self = unsafe { mem::zeroed() }; - new.cbSize = Self::SIZE; - new - } -} - -struct CertChain { - inner: NonNull, -} - -impl CertChain { - fn verify_chain_policy( - &self, - mut server_null_terminated: Vec, - ) -> Result { - let mut extra_params = HTTPSPolicyCallbackData::zeroed_with_size(); - extra_params.dwAuthType = AUTHTYPE_SERVER; - // `server_null_terminated` outlives `extra_params`. - extra_params.pwszServerName = server_null_terminated.as_mut_ptr(); - - let mut params = CERT_CHAIN_POLICY_PARA::zeroed_with_size(); - // Ignore any errors when trying to obtain OCSP revocation information. - // This is also done in OpenSSL, Secure Transport from Apple, etc. - params.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS; - // `extra_params` outlives `params`. - params.pvExtraPolicyPara = NonNull::from(&mut extra_params).cast::().as_ptr(); - - let mut status: MaybeUninit = MaybeUninit::uninit(); - - // SAFETY: The certificate chain is non-null, `params` is valid for reads, and its valid to write to `status`. - let res = unsafe { - CertVerifyCertificateChainPolicy( - CERT_CHAIN_POLICY_SSL, - self.inner.as_ptr(), - ¶ms, - status.as_mut_ptr(), - ) - }; - - // This should rarely, if ever, be false since it would imply no TLS verification - // is currently possible on the system: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certverifycertificatechainpolicy#return-value - if res != TRUE { - return Err(TlsError::General(String::from( - "TLS certificate verification was unavailable on the system!", - ))); - } - - // SAFETY: The verification call was checked to have succeeded, so the status - // is written correctly and initialized. - let status = unsafe { status.assume_init() }; - Ok(status) - } -} - -impl Drop for CertChain { - fn drop(&mut self) { - // SAFETY: The pointer is guaranteed to be non-null. - unsafe { CertFreeCertificateChain(self.inner.as_ptr()) } - } -} - -/// A representation of a certificate. -/// -/// The `CertificateStore` must be opened with the correct flags to ensure the -/// certificate may outlive it; see the `CertificateStore` documentation. -struct Certificate { - inner: NonNull, -} - -impl Certificate { - /// Sets the specified property of this certificate context. - /// - /// ### Safety - /// `prop_data` must be a valid pointer for the property type. - unsafe fn set_property( - &mut self, - prop_id: u32, - prop_data: *const c_void, - ) -> Result<(), TlsError> { - // SAFETY: `cert` points to a valid certificate context and the OCSP data is valid to read. - call_with_last_error(|| { - (CertSetCertificateContextProperty( - self.inner.as_ptr(), - prop_id, - CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG, - prop_data, - ) == TRUE) - .then_some(()) - }) - } -} - -impl Drop for Certificate { - fn drop(&mut self) { - // SAFETY: The certificate context is non-null and points to a valid location. - unsafe { CertFreeCertificateContext(self.inner.as_ptr()) }; - } -} - -#[derive(Debug)] -struct CertEngine { - inner: NonNull, // HCERTENGINECONTEXT -} - -impl CertEngine { - fn new_with_extra_roots( - roots: impl IntoIterator>, - ) -> Result { - let mut exclusive_store = CertificateStore::new()?; - for root in roots { - exclusive_store.add_cert(&root)?; - } - - let mut config = CERT_CHAIN_ENGINE_CONFIG::zeroed_with_size(); - config.hExclusiveRoot = exclusive_store.inner.as_ptr(); - - let mut engine = EnginePtr::NULL; - - // XXX: Due to the redefinition of `CERT_CHAIN_ENGINE_CONFIG`, we need to do pointer casts - // in order to pass our expanded structure into `CertCreateCertificateChainEngine`. - // See also `CERT_CHAIN_PARA` casting below. - let config = NonNull::from(&config).cast().as_ptr(); - // SAFETY: `engine` is valid to be written to and the config is valid to be read. - let res = unsafe { CertCreateCertificateChainEngine(config, &mut engine) }; - - #[allow(clippy::as_conversions)] - let engine = call_with_last_error(|| match NonNull::new(engine as *mut c_void) { - Some(c) if res == TRUE => Some(c), - _ => None, - })?; - Ok(Self { inner: engine }) - } - - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - fn new_with_fake_root(root: &[u8]) -> Result { - use windows_sys::Win32::Security::Cryptography::{ - CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL, CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE, - }; - - let mut root_store = CertificateStore::new()?; - root_store.add_cert(root)?; - - let mut config = CERT_CHAIN_ENGINE_CONFIG::zeroed_with_size(); - // We use these flags for the following reasons: - // - // - CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL is used in an attempt to stop Windows from using the internet to - // fetch anything during the tests, regardless of what test data is used. - // - // - CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE is used as a minor performance optimization to allow Windows to reuse - // data inside of a test and avoid any extra parsing, etc, it might need to do pulling directly from the store each time. - // - // Ref: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_engine_config - config.dwFlags = CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL | CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE; - config.hExclusiveRoot = root_store.inner.as_ptr(); - - let mut engine = EnginePtr::NULL; - // Same workaround with as above when creating the engine. - let config = NonNull::from(&config).cast().as_ptr(); - // SAFETY: `engine` is valid to be written to and the config is valid to be read. - let res = unsafe { CertCreateCertificateChainEngine(config, &mut engine) }; - - #[allow(clippy::as_conversions)] - let engine = call_with_last_error(|| match NonNull::new(engine as *mut c_void) { - Some(c) if res == TRUE => Some(c), - _ => None, - })?; - - Ok(Self { inner: engine }) - } -} - -impl Drop for CertEngine { - fn drop(&mut self) { - // SAFETY: The engine pointer is guaranteed to be non-null. - unsafe { CertFreeCertificateChainEngine(EnginePtr::from_raw(self.inner)) }; - } -} - -// SAFETY: We know no other threads is mutating the `CertEngine`, because it would require `unsafe`. -// Across the FFI, `CertGetCertificateChain` don't mutate it either. -unsafe impl Sync for CertEngine {} -// SAFETY: All methods of `CertEngine`, including `Drop`, are safe to be called from other -// threads, because all contained resources are owned by Windows and we only maintain reference counted handles to them. -unsafe impl Send for CertEngine {} - -/// An in-memory Windows certificate store. -/// -/// # Safety -/// -/// `CertificateStore` creates `Certificate` objects that may outlive the -/// `CertificateStore`. This is only safe to do if the certificate store is -/// constructed with `CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG`. -struct CertificateStore { - inner: NonNull, // HCERTSTORE - // In production code, this is always `None`. - // - // During tests, we set this to `Some` as the tests use a - // custom verification engine that only uses specific roots. - engine: Option, // HCERTENGINECONTEXT -} - -impl Drop for CertificateStore { - fn drop(&mut self) { - // SAFETY: See the `CertificateStore` documentation. - unsafe { CertCloseStore(self.inner.as_ptr(), 0) }; - } -} - -impl CertificateStore { - /// Creates a new, in-memory certificate store. - fn new() -> Result { - let store = call_with_last_error(|| { - // SAFETY: Called with valid constants and result is checked to be non-null. - // The `CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG` flag is critical; - // see the `CertificateStore` documentation for more info. - NonNull::new(unsafe { - CertOpenStore( - CERT_STORE_PROV_MEMORY, - 0, // Set to zero since this uses `PROV_MEMORY`. - 0, // This field shouldn't be used. - CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, - ptr::null(), - ) - }) - })?; - - // Use the system's default root store and rules. - Ok(Self { - inner: store, - engine: None, - }) - } - - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - fn new_with_fake_root(root: &[u8]) -> Result { - let mut inner = Self::new()?; - - let mut root_store = CertificateStore::new()?; - root_store.add_cert(root)?; - - let engine = CertEngine::new_with_fake_root(root)?; - inner.engine = Some(engine); - - Ok(inner) - } - - /// Adds the provided certificate to the store. - /// - /// The certificate must be encoded as ASN.1 DER. - /// - /// Errors if the certificate was malformed and couldn't be added. - fn add_cert(&mut self, cert: &[u8]) -> Result { - let mut cert_context: *mut CERT_CONTEXT = ptr::null_mut(); - - // SAFETY: `inner` is a valid certificate store, and `cert` is a valid a byte array valid - // for reads, the correct length is being provided, and `cert_context` is valid to write to. - let res = unsafe { - CertAddEncodedCertificateToStore( - self.inner.as_ptr(), - X509_ASN_ENCODING, - cert.as_ptr(), - cert.len() - .try_into() - .map_err(|_| InvalidCertificate(CertificateError::BadEncoding))?, - CERT_STORE_ADD_ALWAYS, - &mut cert_context, - ) - }; - - // SAFETY: Constructing a `Certificate` is only safe if the store was - // created with the right flags; see the `CertificateStore` docs. - match (res, NonNull::new(cert_context)) { - (TRUE, Some(cert)) => Ok(Certificate { inner: cert }), - _ => Err(InvalidCertificate(CertificateError::BadEncoding)), - } - } - - fn new_chain_in( - &self, - certificate: &Certificate, - now: pki_types::UnixTime, - engine: Option<&CertEngine>, - ) -> Result { - let mut cert_chain = ptr::null_mut(); - - let mut parameters = CERT_CHAIN_PARA::zeroed_with_size(); - - #[allow(clippy::as_conversions)] - // https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_usage_match - let usage = CERT_USAGE_MATCH { - dwType: USAGE_MATCH_TYPE_AND, - Usage: CTL_USAGE { - cUsageIdentifier: ALLOWED_EKUS.len() as u32, - rgpszUsageIdentifier: ALLOWED_EKUS.as_ptr() as *mut windows_sys::core::PSTR, - }, - }; - parameters.RequestedUsage = usage; - - #[allow(clippy::as_conversions)] - let time = { - /// Seconds between Jan 1st, 1601 and Jan 1, 1970. - const UNIX_ADJUSTMENT: std::time::Duration = - std::time::Duration::from_secs(11_644_473_600); - - let since_unix_epoch = now.as_secs(); - - // Convert the duration from the UNIX epoch to the Window one, and then convert - // the result into a `FILETIME` structure. - - let since_windows_epoch = since_unix_epoch + UNIX_ADJUSTMENT.as_secs(); - let intervals = (since_windows_epoch * 1_000_000_000) / 100; - - FILETIME { - dwLowDateTime: (intervals & u32::MAX as u64) as u32, - dwHighDateTime: (intervals >> 32) as u32, - } - }; - - // `CERT_CHAIN_REVOCATION_CHECK_END_CERT` only checks revocation for end cert. See the crate's revocation documentation - // for more details. - // `CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT` accumulates network retrievals timeouts - // to limit network time and improve performance. - // `CERT_CHAIN_CACHE_END_CERT` speeds up the common case of multiple connections to same server. - const FLAGS: u32 = CERT_CHAIN_REVOCATION_CHECK_END_CERT - | CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT - | CERT_CHAIN_CACHE_END_CERT; - - // Lowering URL retrieval timeout from default 15s to 10s to account for higher internet speeds - parameters.dwUrlRetrievalTimeout = 10 * 1000; // milliseconds - - // SAFETY: `cert` points to a valid certificate context, parameters is valid for reads, `cert_chain` is valid - // for writes, and the certificate store is valid and initialized. - let res = unsafe { - // XXX: Due to the redefinition of `CERT_CHAIN_PARA`, we need to do pointer casts - // in order to pass our expanded structure into `CertGetCertificateChain`. - // This is safe because the OS uses `cbSize` to know if the extra parameters - // are present or not. As we set `cbSize` correctly, the fields can be read from correctly. - let parameters = NonNull::from(¶meters).cast().as_ptr(); - - CertGetCertificateChain( - match engine { - Some(eng) => EnginePtr::from_raw(eng.inner), - None => EnginePtr::NULL, - }, - certificate.inner.as_ptr(), - &time, - self.inner.as_ptr(), - parameters, - FLAGS, - ptr::null_mut(), - &mut cert_chain, - ) - }; - - // XXX: Windows will internally map the chain's `TrustStatus.dwErrorStatus` to a `dwError` when - // a chain policy is verified, so we only check for errors there. - call_with_last_error(|| match NonNull::new(cert_chain) { - Some(c) if res == TRUE => Some(CertChain { inner: c }), - _ => None, - }) - } -} - -// `windows-sys` >= 0.60 -impl EnginePtr for *mut c_void { - fn from_raw(val: NonNull) -> Self { - val.as_ptr() - } - - const NULL: Self = ptr::null_mut(); -} - -// `windows-sys` 0.52-0.59 -impl EnginePtr for isize { - #[allow(clippy::as_conversions)] - fn from_raw(val: NonNull) -> Self { - val.as_ptr() as isize - } - - const NULL: Self = 0; -} - -/// An abstraction trait over the different ways various `windows-sys` versions represent -/// the type of `HCERTCHAINENGINE`. -trait EnginePtr: Sized { - fn from_raw(val: NonNull) -> Self; - - const NULL: Self; -} - -fn call_with_last_error Option>(mut call: F) -> Result { - if let Some(res) = call() { - Ok(res) - } else { - Err(TlsError::General( - std::io::Error::last_os_error().to_string(), - )) - } -} - -/// A TLS certificate verifier that utilizes the Windows certificate facilities. -#[derive(Debug)] -pub struct Verifier { - /// Testing only: The root CA certificate to trust. - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - test_only_root_ca_override: Option>, - crypto_provider: Arc, - /// Extra trust anchors to add to the verifier above and beyond those provided by - /// the system-provided trust stores. - extra_roots: Option, -} - -impl Verifier { - /// Creates a new instance of a TLS certificate verifier that utilizes the - /// Windows certificate facilities. - #[cfg_attr(docsrs, doc(cfg(all())))] - pub fn new(crypto_provider: Arc) -> Result { - Ok(Self { - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - test_only_root_ca_override: None, - crypto_provider, - extra_roots: None, - }) - } - - /// Creates a new instance of a TLS certificate verifier that utilizes the - /// Windows certificate facilities and augmented by the provided extra root certificates. - #[cfg_attr(docsrs, doc(cfg(not(target_os = "android"))))] - pub fn new_with_extra_roots( - roots: impl IntoIterator>, - crypto_provider: Arc, - ) -> Result { - let cert_engine = CertEngine::new_with_extra_roots(roots)?; - Ok(Self { - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - test_only_root_ca_override: None, - crypto_provider, - extra_roots: Some(cert_engine), - }) - } - - /// Creates a test-only TLS certificate verifier which trusts our fake root CA cert. - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - pub(crate) fn new_with_fake_root( - root: pki_types::CertificateDer<'static>, - crypto_provider: Arc, - ) -> Self { - Self { - test_only_root_ca_override: Some(root), - crypto_provider, - extra_roots: None, - } - } - - /// Verifies a certificate and its chain for the specified `server`. - /// - /// Return `Ok(())` if the certificate was valid. - fn verify_certificate( - &self, - primary_cert: &[u8], - intermediate_certs: &[&[u8]], - server: &[u8], - ocsp_data: Option<&[u8]>, - now: pki_types::UnixTime, - ) -> Result<(), TlsError> { - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - let mut store = match self.test_only_root_ca_override.as_ref() { - Some(test_only_root_ca_override) => { - CertificateStore::new_with_fake_root(test_only_root_ca_override)? - } - None => CertificateStore::new()?, - }; - - #[cfg(not(any(test, feature = "ffi-testing", feature = "dbg")))] - let mut store = CertificateStore::new()?; - - let mut primary_cert = store.add_cert(primary_cert)?; - - for cert in intermediate_certs.iter().copied() { - store.add_cert(cert)?; - } - - if let Some(ocsp_data) = ocsp_data { - #[allow(clippy::as_conversions)] - let data = CRYPT_INTEGER_BLOB { - cbData: ocsp_data.len().try_into().map_err(|_| { - invalid_certificate("Malformed OCSP response stapled to server certificate") - })?, - pbData: ocsp_data.as_ptr() as *mut u8, - }; - - // SAFETY: `data` is a valid pointer and matches the property ID. - unsafe { - primary_cert.set_property( - CERT_OCSP_RESPONSE_PROP_ID, - NonNull::from(&data).cast::().as_ptr(), - )?; - } - } - - // Encode UTF-16, null-terminated - let server: Vec = server - .iter() - .map(|c| u16::from(*c)) - .chain(Some(0)) - .collect(); - - let mut cert_chain = store.new_chain_in(&primary_cert, now, store.engine.as_ref())?; - - // We only use `TrustStatus` here because it hasn't had verification performed on it. - // SAFETY: The pointer is guaranteed to be non-null. - let cert_error_status = unsafe { *cert_chain.inner.as_ptr() } - .TrustStatus - .dwErrorStatus; - - let extra_roots_may_needed = - (cert_error_status & (CERT_TRUST_IS_PARTIAL_CHAIN | CERT_TRUST_IS_UNTRUSTED_ROOT)) != 0; - - // If we have extra roots and building the chain gave us an error, we try to build a - // new one with the extra roots. - if extra_roots_may_needed && self.extra_roots.is_some() { - let mut store = CertificateStore::new()?; - - for cert in intermediate_certs.iter().copied() { - store.add_cert(cert)?; - } - - cert_chain = store.new_chain_in(&primary_cert, now, self.extra_roots.as_ref())?; - } - - let status = cert_chain.verify_chain_policy(server)?; - - if status.dwError == 0 { - return Ok(()); - } - - // Only map the errors we have tests for. - #[allow(clippy::as_conversions)] - let win_error = status.dwError as i32; - Err(match win_error { - CERT_E_CN_NO_MATCH | CERT_E_INVALID_NAME => { - InvalidCertificate(CertificateError::NotValidForName) - } - CRYPT_E_REVOKED => InvalidCertificate(CertificateError::Revoked), - CERT_E_EXPIRED => InvalidCertificate(CertificateError::Expired), - CERT_E_UNTRUSTEDROOT => InvalidCertificate(CertificateError::UnknownIssuer), - CERT_E_WRONG_USAGE => InvalidCertificate(CertificateError::InvalidPurpose), - error_num => { - let err = std::io::Error::from_raw_os_error(error_num); - // The included error message has both the description and raw OS error code. - invalid_certificate(err.to_string()) - } - }) - } -} - -#[cfg_attr(docsrs, doc(cfg(all())))] -impl ServerCertVerifier for Verifier { - fn verify_server_cert( - &self, - end_entity: &pki_types::CertificateDer<'_>, - intermediates: &[pki_types::CertificateDer<'_>], - server_name: &pki_types::ServerName, - ocsp_response: &[u8], - now: pki_types::UnixTime, - ) -> Result { - log_server_cert(end_entity); - - let name = server_name.to_str(); - // Trim trailing `.` labels to remove compatibility hazards with the Windows verifier. - // It performs exact quality comparisions in most cases (see https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-httpspolicycallbackdata) - // which causes problems with hostnames that are considered equivalent for security purposes in other verifier and TLS implementations. - // - // Ref: https://github.com/rustls/rustls-platform-verifier/issues/240 - let name = name.strip_suffix('.').unwrap_or(&name); - - let intermediate_certs: Vec<&[u8]> = intermediates.iter().map(|c| c.as_ref()).collect(); - - let ocsp_data = if !ocsp_response.is_empty() { - Some(ocsp_response) - } else { - None - }; - - match self.verify_certificate( - end_entity.as_ref(), - &intermediate_certs, - name.as_bytes(), - ocsp_data, - now, - ) { - Ok(()) => Ok(rustls::client::danger::ServerCertVerified::assertion()), - Err(e) => { - // SAFETY: - // Errors are our own custom errors, WinAPI errors, or static strings. - log::error!("failed to verify TLS certificate: {}", e); - Err(e) - } - } - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls12_signature( - message, - cert, - dss, - &self.crypto_provider.signature_verification_algorithms, - ) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls13_signature( - message, - cert, - dss, - &self.crypto_provider.signature_verification_algorithms, - ) - } - - fn supported_verify_schemes(&self) -> Vec { - self.crypto_provider - .signature_verification_algorithms - .supported_schemes() - } -} - -/// A trait to represent an object that can be safely created with all zero values -/// and have a size assigned to it. -/// -/// # Safety -/// -/// This has the same safety requirements as [std::mem::zeroed]. -unsafe trait ZeroedWithSize: Sized { - const SIZE: u32 = { - let size = core::mem::size_of::(); - - // NB: `TryInto` isn't stable in const yet. - #[allow(clippy::as_conversions)] - if size <= u32::MAX as usize { - size as u32 - } else { - panic!("structure was larger then DWORD") - } - }; - - /// Returns a zeroed structure with its structure size (`cbSize`) field set to the correct value. - fn zeroed_with_size() -> Self; -}