diff --git a/.clippy.toml b/.clippy.toml deleted file mode 100644 index cc94ec53e13..00000000000 --- a/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -upper-case-acronyms-aggressive = true diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000000..0133c3dea72 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: ["cpu", "ctz", "djc"] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 94b3292535d..67998e11163 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -27,3 +27,5 @@ A clear and concise description of what you expected to happen. **Additional context** Add any other context about the problem here. +For example, consider including verbose logs or a packet capture. For help +with this [see the manual](https://docs.rs/rustls/latest/rustls/manual/_03_howto/index.html#debugging). diff --git a/.github/typos.toml b/.github/typos.toml new file mode 100644 index 00000000000..5a8f4c2f3c3 --- /dev/null +++ b/.github/typos.toml @@ -0,0 +1,15 @@ +[default.extend-words] +# encrypter as an active verb +encrypter = "encrypter" + +# "type", but side-stepping the keyword and avoiding the very ugly r#type +typ = "typ" + +# pn -> packet number in quic parlance +pn = "pn" + +# as in Josh +Aas = "Aas" + +[files] +extend-exclude = ["*.bin", "*.json", "*.json.in", "*.svg", "macros.html", "test-ca/"] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aa6dca058b3..c891cc8f941 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,19 +5,23 @@ permissions: on: push: - branches-ignore: - - 'gh-readonly-queue/**' + branches: ['main', 'rel-*', 'ci/*'] tags: - '**' pull_request: merge_group: schedule: - cron: '0 18 * * *' + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true +env: + RUSTFLAGS: --deny warnings + AWS_LC_SYS_NO_JITTER_ENTROPY: 1 + jobs: build: name: Build+test @@ -30,6 +34,8 @@ jobs: - nightly os: - ubuntu-latest + # aka ubuntu-latest-arm + - ubuntu-24.04-arm - windows-latest - macos-latest exclude: @@ -47,7 +53,7 @@ jobs: - os: ${{ github.event_name == 'merge_group' && 'macos-latest' }} steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false @@ -62,13 +68,13 @@ jobs: - name: Install ninja-build tool for aws-lc-fips-sys on Windows if: runner.os == 'Windows' - uses: seanmiddleditch/gha-setup-ninja@v5 + uses: seanmiddleditch/gha-setup-ninja@v6 - name: Install golang for aws-lc-fips-sys on macos if: runner.os == 'MacOS' - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: - go-version: "1.22.2" + go-version: "stable" - name: cargo build (debug; default features) run: cargo build --locked @@ -77,41 +83,28 @@ jobs: # this is required for fips on windows. # nb. "--all-targets" does not include doctests - name: cargo test (release; all features) - run: cargo test --release --locked --all-features --all-targets + run: cargo test --locked --release --all-features --all-targets env: RUST_BACKTRACE: 1 # nb. this is separate since `--doc` option cannot be combined with other target option(s) ref: # - https://doc.rust-lang.org/cargo/commands/cargo-test.html - name: cargo test --doc (release; all-features) - run: cargo test --release --locked --all-features --doc - env: - RUST_BACKTRACE: 1 - - - name: cargo test (debug; aws-lc-rs) - run: cargo test --no-default-features --features aws_lc_rs,tls12,read_buf,logging,std --all-targets + run: cargo test --locked --release --all-features --doc env: RUST_BACKTRACE: 1 - - name: cargo test (release; fips) - run: cargo test --release --no-default-features --features fips,tls12,read_buf,logging,std --all-targets - env: - RUST_BACKTRACE: 1 - - - name: cargo build (debug; rustls-provider-example) - run: cargo build --locked -p rustls-provider-example - - - name: cargo build (debug; rustls-provider-example lib in no-std mode) - run: cargo build --locked -p rustls-provider-example --no-default-features - - - name: cargo test (debug; rustls-provider-example; all features) - run: cargo test --all-features -p rustls-provider-example + - name: cargo build (debug; no-std) + run: | + cargo build --locked --lib -p rustls $(admin/all-features-except std,brotli rustls) + cargo build --locked --lib -p rustls-ring --no-default-features + cargo build --locked --lib -p rustls-aws-lc-rs --no-default-features --features aws-lc-sys - name: cargo build (debug; rustls-provider-test) run: cargo build --locked -p rustls-provider-test - name: cargo test (debug; rustls-provider-test; all features) - run: cargo test --all-features -p rustls-provider-test + run: cargo test --locked --all-features -p rustls-provider-test - name: cargo package --all-features -p rustls run: cargo package --all-features -p rustls @@ -121,20 +114,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false - uses: dtolnay/rust-toolchain@master with: - toolchain: "1.71" - - # zlib-rs is optional and requires a later MSRV - - run: cargo check --locked --lib $(admin/all-features-except zlib rustls) -p rustls - - - uses: dtolnay/rust-toolchain@master - with: - toolchain: "1.75" + toolchain: "1.85" - run: cargo check --locked --lib --all-features -p rustls @@ -143,7 +129,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false @@ -153,18 +139,10 @@ jobs: target: x86_64-unknown-none - name: cargo build (debug; default features) - run: cargo build --locked - working-directory: rustls - - # this target does _not_ include the libstd crate in its sysroot - # it will catch unwanted usage of libstd in _dependencies_ - - name: cargo build (debug; no default features; no-std) - run: cargo build --locked --no-default-features --target x86_64-unknown-none - working-directory: rustls - - - name: cargo build (debug; no default features; no-std, hashbrown) - run: cargo build --locked --no-default-features --features hashbrown --target x86_64-unknown-none - working-directory: rustls + run: | + cargo build --locked -p rustls + cargo build --locked -p rustls-ring + cargo build --locked -p rustls-aws-lc-rs - name: cargo test (debug; default features) run: cargo test --locked @@ -173,20 +151,15 @@ jobs: RUST_BACKTRACE: 1 - name: cargo test (debug; no default features) - run: cargo test --locked --no-default-features - working-directory: rustls - - - name: cargo test (debug; no default features; tls12) - run: cargo test --locked --no-default-features --features tls12,std - working-directory: rustls - - - name: cargo test (debug; no default features; aws-lc-rs,tls12) - run: cargo test --no-default-features --features aws_lc_rs,tls12,std - working-directory: rustls + run: | + cargo test --locked -p rustls --no-default-features + cargo test --locked -p rustls-ring --no-default-features + cargo test --locked -p rustls-aws-lc-rs --no-default-features --features aws-lc-sys - - name: cargo test (debug; no default features; fips,tls12) - run: cargo test --no-default-features --features fips,tls12,std - working-directory: rustls + - name: cargo test (debug; no default features; std) + run: | + cargo test --locked -p rustls-ring --no-default-features --features std + cargo test --locked -p rustls-aws-lc-rs --no-default-features --features std --features aws-lc-sys - name: cargo test (release; no run) run: cargo test --locked --release --no-run @@ -197,7 +170,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false @@ -205,10 +178,9 @@ jobs: uses: dtolnay/rust-toolchain@stable - name: Install golang toolchain - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: - go-version: "1.21" - cache: false + go-version: "stable" - name: Run test suite (ring) working-directory: bogo @@ -228,28 +200,27 @@ jobs: env: BOGO_SHIM_PROVIDER: aws-lc-rs-fips - - name: Run test suite (post-quantum) - working-directory: bogo - run: ./runme - env: - BOGO_SHIM_PROVIDER: post-quantum - fuzz: name: Smoke-test fuzzing targets runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false - - name: Install nightly toolchain - uses: dtolnay/rust-toolchain@nightly + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable - name: Install cargo fuzz - run: cargo install cargo-fuzz + uses: taiki-e/cache-cargo-install-action@v3 + with: + tool: cargo-fuzz - name: Smoke-test fuzz targets + env: + # cargo fuzz uses a bunch of -Z options + RUSTC_BOOTSTRAP: 1 run: | cargo fuzz build for target in $(cargo fuzz list) ; do @@ -261,12 +232,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false - - name: Install nightly toolchain - uses: dtolnay/rust-toolchain@nightly + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable - name: Smoke-test benchmark program (ring) run: cargo run -p rustls-bench --profile=bench --locked --features ring -- --multiplier 0.1 @@ -281,17 +252,20 @@ jobs: run: cargo bench --locked --all-features env: RUSTFLAGS: --cfg=bench + # unit-benchmarks are permanantly unstable + RUSTC_BOOTSTRAP: 1 docs: name: Check for documentation errors runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false - name: Install rust toolchain + # use nightly to mirror docs.rs uses: dtolnay/rust-toolchain@nightly - name: cargo doc (rustls; all features) @@ -305,29 +279,37 @@ jobs: ./admin/pull-readme git diff --exit-code + - name: Spell check code + uses: crate-ci/typos@v1 + with: + config: .github/typos.toml + + coverage: name: Measure coverage runs-on: ubuntu-latest if: github.event_name != 'merge_group' steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false - name: Install rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@nightly with: components: llvm-tools - name: Install cargo-llvm-cov - run: cargo install cargo-llvm-cov + uses: taiki-e/cache-cargo-install-action@v3 + with: + tool: cargo-llvm-cov - name: Measure coverage run: ./admin/coverage --lcov --output-path final.info - name: Report to codecov.io - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 with: files: final.info token: ${{ secrets.CODECOV_TOKEN }} @@ -338,12 +320,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false - name: Install rust toolchain - uses: dtolnay/rust-toolchain@nightly + uses: dtolnay/rust-toolchain@stable - name: Install cargo-minimal-versions uses: taiki-e/install-action@cargo-minimal-versions @@ -355,6 +337,9 @@ jobs: - name: Check direct-minimal-versions run: cargo minimal-versions --direct --ignore-private check working-directory: rustls/ + env: + # `cargo minimal-versions` uses unstable cargo features + RUSTC_BOOTSTRAP: 1 cross: name: cross-target testing @@ -367,7 +352,7 @@ jobs: - armv7-linux-androideabi - i686-linux-android - thumbv7neon-linux-androideabi - # Other standard 32-bit (Linux) targets (SKIP bindgen for i686 only) + # Other standard 32-bit (Linux) targets - i586-unknown-linux-gnu - i686-unknown-linux-gnu # exotic Linux targets: @@ -379,25 +364,28 @@ jobs: - aarch64-linux-android steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false - name: Install rust toolchain uses: dtolnay/rust-toolchain@stable - - name: Install cross (cross-rs) from GitHub - run: cargo install cross --git https://github.com/cross-rs/cross - - name: Install bindgen feature & CLI for aws-lc-sys (as needed for many cross targets) - if: ${{ matrix.target != 'i686-unknown-linux-gnu' }} - run: cargo add --dev --features bindgen 'aws-lc-sys@>0.20' --package rustls --verbose && cargo install bindgen-cli --verbose + - name: Install cross + uses: taiki-e/cache-cargo-install-action@v3 + with: + tool: cross + git: https://github.com/cross-rs/cross + # known-working main in feb 2025, bump as needed + rev: c7dee4d - run: cross test --package rustls --target ${{ matrix.target }} + - run: cross test --package rustls-test --features aws-lc-rs --no-default-features --target ${{ matrix.target }} semver: name: Check semver compatibility runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false @@ -409,7 +397,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false - name: Install rust toolchain @@ -428,30 +416,28 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false - name: Install rust nightly toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@nightly with: components: rustfmt - toolchain: nightly-2024-02-21 - name: Check formatting (unstable) run: cargo fmt --all -- --check --config-path .rustfmt.unstable.toml - continue-on-error: true - name: Check formatting (unstable, connect-tests workspace) run: cargo fmt --all --manifest-path=connect-tests/Cargo.toml -- --check --config-path .rustfmt.unstable.toml - continue-on-error: true - name: Check formatting (unstable, fuzz workspace) run: cargo fmt --all --manifest-path=fuzz/Cargo.toml -- --check --config-path .rustfmt.unstable.toml - continue-on-error: true + - if: ${{ failure() }} + run: echo "Nightly formatting check failed. Please run \`cargo +nightly fmt-unstable\`" clippy: name: Clippy runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false @@ -463,17 +449,14 @@ jobs: uses: dtolnay/rust-toolchain@stable with: components: clippy - # - we want to be free of any warnings, so deny them - # - disable incompatible_msrv as it does not understand that we apply our - # MSRV to the just the core crate. - - run: ./admin/clippy -- --deny warnings --allow clippy::incompatible_msrv + - run: ./admin/clippy -- --deny warnings clippy-nightly: name: Clippy (Nightly) runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false @@ -485,7 +468,7 @@ jobs: uses: dtolnay/rust-toolchain@nightly with: components: clippy - # do not deny warnings, as nightly clippy sometimes has false negatives + # Do not deny warnings, as nightly clippy sometimes has false negatives. - run: ./admin/clippy check-external-types: @@ -493,15 +476,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false - name: Install rust toolchain uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2024-06-30 + toolchain: nightly-2025-10-18 # ^ sync with https://github.com/awslabs/cargo-check-external-types/blob/main/rust-toolchain.toml - - run: cargo install --locked cargo-check-external-types + - name: Install cargo-check-external-types + uses: taiki-e/cache-cargo-install-action@v3 + with: + tool: cargo-check-external-types - name: run cargo-check-external-types for rustls/ working-directory: rustls/ run: cargo check-external-types @@ -513,7 +499,7 @@ jobs: VERSION: openssl-3.4.0 steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false @@ -521,7 +507,7 @@ jobs: uses: dtolnay/rust-toolchain@stable - name: Cache ${{ env.VERSION }} - uses: actions/cache@v4 + uses: actions/cache@v5 id: cache-openssl with: path: ${{ env.VERSION }} @@ -551,3 +537,9 @@ jobs: run: cargo test --locked -- --include-ignored env: RUST_BACKTRACE: 1 + + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: EmbarkStudios/cargo-deny-action@v2 diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index af7f2fea25d..d85767cf45d 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -1,4 +1,6 @@ name: CIFuzz +permissions: + contents: read on: [pull_request] concurrency: @@ -24,7 +26,7 @@ jobs: dry-run: false language: rust - name: Upload Crash - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: failure() && steps.build.outcome == 'success' with: name: artifacts diff --git a/.github/workflows/daily-tests.yml b/.github/workflows/daily-tests.yml index e526a2e409d..d2a714fa384 100644 --- a/.github/workflows/daily-tests.yml +++ b/.github/workflows/daily-tests.yml @@ -31,7 +31,7 @@ jobs: rust: stable steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false @@ -46,7 +46,7 @@ jobs: - name: Install ninja-build tool for aws-lc-fips-sys on Windows if: runner.os == 'Windows' - uses: seanmiddleditch/gha-setup-ninja@v5 + uses: seanmiddleditch/gha-setup-ninja@v6 - name: Build example programs run: cargo build --locked -p rustls-examples @@ -75,7 +75,7 @@ jobs: rust: stable steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false @@ -90,7 +90,7 @@ jobs: - name: Install ninja-build tool for aws-lc-fips-sys on Windows if: runner.os == 'Windows' - uses: seanmiddleditch/gha-setup-ninja@v5 + uses: seanmiddleditch/gha-setup-ninja@v6 - name: Check simple client run: cargo run --locked -p rustls-examples --bin simpleclient @@ -101,15 +101,6 @@ jobs: - name: Check simple 0rtt client run: cargo run --locked -p rustls-examples --bin simple_0rtt_client - - name: Check unbuffered client - run: cargo run --locked -p rustls-examples --bin unbuffered-client - - - name: Check unbuffered tokio client - run: cargo run --locked -p rustls-examples --bin unbuffered-async-client - - - name: Check unbuffered async-std client - run: cargo run --locked -p rustls-examples --bin unbuffered-async-client --features=async-std - # Test the server_acceptor binary builds - we invoke with --help since it # will run a server process that doesn't exit when invoked with no args - name: Check server acceptor @@ -125,19 +116,23 @@ jobs: cargo run --locked -p rustls-examples --bin ech-client -- --host min-ng.test.defo.ie --path "echstat.php?format=json" public.test.defo.ie min-ng.test.defo.ie | grep '"SSL_ECH_STATUS": "success"' - - name: Check provider-example client - run: cargo run --locked -p rustls-provider-example --example client + - name: Check ech-client (tls-ech.dev) + run: > + cargo run --locked -p rustls-examples --bin ech-client -- --path "/" public.tls-ech.dev tls-ech.dev | + grep 'You are using ECH.' - name: Check rustls-post-quantum client run: cargo run --locked -p rustls-post-quantum --example client | grep 'kex=X25519MLKEM768' + - name: Smoke test for secp256r1mlkem768 interop + run: cargo run --locked -p rustls-examples --bin tlsclient-mio -- --http --key-exchange secp256r1mlkem768 --verbose openquantumsafe.org feature-powerset: name: Feature Powerset runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false @@ -153,10 +148,5 @@ jobs: --package rustls --feature-powerset --no-dev-deps - --group-features aws_lc_rs,aws-lc-rs - --group-features fips,aws_lc_rs - --mutually-exclusive-features fips,ring - --mutually-exclusive-features custom_provider,aws_lc_rs - --mutually-exclusive-features custom_provider,ring env: RUSTFLAGS: --deny warnings diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 82f7a17d9ac..31b5f7e172b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,6 +5,7 @@ permissions: on: workflow_dispatch: + pull_request: push: branches: - main @@ -17,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false @@ -39,16 +40,16 @@ jobs: - name: cargo doc # keep features in sync with Cargo.toml `[package.metadata.docs.rs]` section - run: cargo doc --locked --features read_buf,ring --no-deps --package rustls + run: cargo doc --locked --features brotli,hashbrown,log,zlib --no-deps --package rustls env: - RUSTDOCFLAGS: -Dwarnings --cfg=docsrs --html-after-content tag.html + RUSTDOCFLAGS: -Dwarnings --cfg=rustls_docsrs --html-after-content tag.html - name: Generate other pages run: | cd website && zola build --output-dir ../target/website/ - name: Restore lychee cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .lycheecache key: cache-lychee-${{ github.sha }} @@ -71,17 +72,18 @@ jobs: # lockfile causes deployment step to go wrong, due to permissions rm -f target/doc/.lock # move the result into website root - mv target/doc/rustls target/website/docs + mv target/doc/* target/website/ + mv target/website/rustls target/website/docs - name: Package and upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v5 with: path: ./target/website/ deploy: name: Deploy runs-on: ubuntu-latest - if: github.repository == 'rustls/rustls' + if: github.repository == 'rustls/rustls' && github.ref == 'refs/heads/main' needs: generate permissions: pages: write @@ -92,4 +94,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.lycheeignore b/.lycheeignore index 7b7e55f13b4..cec975d6c65 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -1,3 +1,2 @@ ^file\:\/\/\/.*\/target\/doc\/index\.html$ -^http:\/\/www\.isg\.rhul\.ac\.uk\/tls\/Lucky13.html$ ^http:\/\/www\.adobe\.com/$ diff --git a/.rustfmt.toml b/.rustfmt.toml index e70b64a4ae9..6388c7aeea2 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1 +1,3 @@ -chain_width=40 +# keep in sync with .rustfmt.unstable.toml +chain_width = 40 +style_edition = "2024" diff --git a/.rustfmt.unstable.toml b/.rustfmt.unstable.toml index 521dae776b2..1a3d4f9bb79 100644 --- a/.rustfmt.unstable.toml +++ b/.rustfmt.unstable.toml @@ -1,5 +1,6 @@ # keep in sync with .rustfmt.toml chain_width = 40 +style_edition = "2024" # format imports group_imports = "StdExternalCrate" diff --git a/.taplo.toml b/.taplo.toml new file mode 100644 index 00000000000..f819ac88b10 --- /dev/null +++ b/.taplo.toml @@ -0,0 +1,3 @@ +[formatting] +align_comments = false +column_width = 110 diff --git a/BENCHMARKING.md b/BENCHMARKING.md index 2af41f8e0f3..d93ccd86393 100644 --- a/BENCHMARKING.md +++ b/BENCHMARKING.md @@ -6,11 +6,14 @@ information on how to run them. ## Throughput and memory usage benchmarks These benchmarks measure the throughput and memory footprint you get from rustls. They have been -used in the past to compare performance against OpenSSL (see the results of [December -2023](https://github.com/aochagavia/rustls-bench-results) and [July -2019](https://jbp.io/2019/07/01/rustls-vs-openssl-performance.html)). You can also use them to -evaluate rustls' performance on different hardware (e.g. a bare-metal server with support for -AVX-512 instructions vs. a cloud VM with a consumer-grade CPU). +used in the past to compare performance against OpenSSL: + +- See [the most up-to-date reports](https://rustls.dev/perf/). +- See the [historical results from December 2023](https://github.com/aochagavia/rustls-bench-results). +- See the [historical results from July 2019](https://jbp.io/2019/07/01/rustls-vs-openssl-performance.html). + +You can also use them to evaluate rustls' performance on different hardware (e.g. a bare-metal server +with support for AVX-512 instructions vs. a cloud VM with a consumer-grade CPU). The measured aspects are: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8494f34780a..86964dbb9ad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,7 +59,7 @@ If you're *looking* for security bugs, this crate is set up for ## Testing - Features involving additions to the public API should have (at least) - API-level tests (see [`rustls/tests/api.rs`](rustls/tests/api.rs)). + API-level tests (see [`rustls-test/tests/api/`](rustls-test/tests/api/)). - Protocol additions should have some coverage -- consider enabling corresponding tests in the bogo suite, or writing some ad hoc tests. @@ -70,23 +70,12 @@ are unlikely to be accepted. Generally any test that relies on a `CryptoProvider` anywhere, should be run against all `CryptoProvider`s, such that -`cargo test --all-features` runs the test several times. To achieve that -we have two methods: +`cargo test --all-features` runs the test several times. -- For unit tests, see `rustls/src/test_macros.rs` which provides the - `test_for_each_provider!` macro. This can be placed around normal - tests and instantiates the tests once per provider. - - Note that rustfmt does not format code inside a macro invocation: - when developing test code, sed `test_for_each_provider! {` to `mod tests {`, - format the code, then sed it back. - -- For integration tests -- where the amount of test code is more significant, - and lack of rustfmt support is more painful -- we instantiate the tests - by importing them multiple times, and then the tests resolve the provider - module to use via `super::provider`. - For example, see `rustls/tests/runners/unbuffered.rs` and - `rustls/tests/unbuffered.rs`. +For integration tests -- where the amount of test code is more significant, +we instantiate the tests by importing them multiple times, and then the tests +resolve the provider module to use via `super::provider`. For example, see +`rustls-test/tests/api.rs` and `rustls-test/tests/api/kx.rs`. ## Style guide @@ -144,6 +133,22 @@ Note that we usually also practice top-down ordering here; where these are in conflict, make a choice that you think makes sense. For getters and setters, the order should typically mirror the order of the fields in the type definition. +#### Attribute ordering + +Order attributes so that documentation appears first, and the attributes with the +most effect on the meaning and function of the type appear last. For example: + +```rust +/// Doc comment always first +#[cfg(feature-gates)] +#[allow(lint-configuration)] +#[non_exhaustive] +#[derive(Clone, Debug)] +pub struct Foo; +``` + +Prefer to write `derive`d traits in alphabetical order. + ### Functions #### Consider avoiding short single-use functions @@ -361,6 +366,15 @@ don't provide additional type safety. Using the [newtype idiom](https://doc.rust-lang.org/rust-by-example/generics/new_types.html) is one alternative when an abstraction boundary is worth the added complexity. +#### Type exhaustiveness + +Public enums should be marked as _either_ `#[non_exhaustive]` or `#[allow(clippy::exhaustive_enums)]`. +The latter is suitable for enums that are already complete by definition. For example: +`enum CoinFlip { Heads, Tails }` is complete. Err on the side of marking something `#[non_exhaustive]`. + +The same applies to structs, with the detail that no manual marking is needed for +structures with at least one private field. + ## Design and Architecture Some general concepts about how the library should fit together: diff --git a/Cargo.lock b/Cargo.lock index 04db29f222e..fca8c7b4eb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,62 +1,12 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] +version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -76,6 +26,15 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "anes" version = "0.1.6" @@ -84,9 +43,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.18" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -99,49 +58,50 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "asn1" -version = "0.20.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8b84b4ea1de2bf1dcd2a759737ddb328fb6695b2a95eb7e44fed67e3406f32" +checksum = "c9795210620c0cb3f9a7ce4f882808c38e1ef7b347c90591dceae0886e031fb1" dependencies = [ "asn1_derive", "itoa", @@ -149,9 +109,9 @@ dependencies = [ [[package]] name = "asn1-rs" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -159,19 +119,19 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 1.0.69", + "thiserror", "time", ] [[package]] name = "asn1-rs-derive" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", "synstructure", ] @@ -183,164 +143,29 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] name = "asn1_derive" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a200809d0138620b3dba989f1d08d0620e76248bc1e62a2ec1b2df5eb1ee08ad" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.95", -] - -[[package]] -name = "async-attributes" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.3.1", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", -] - -[[package]] -name = "async-io" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" -dependencies = [ - "async-lock", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener 5.4.0", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +checksum = "909e307f1cc32bb8bccbd98f446e6d1bf03fa30f7b53a4337da7181ad30fa11a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", -] - -[[package]] -name = "async-std" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" -dependencies = [ - "async-attributes", - "async-channel 1.9.0", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", + "syn", ] -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - [[package]] name = "async-trait" -version = "0.1.85" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] @@ -351,85 +176,54 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-fips-sys" -version = "0.13.0" +version = "0.13.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59057b878509d88952425fe694a2806e468612bde2d71943f3cd8034935b5032" +checksum = "d3d619165468401dec3caa3366ebffbcb83f2f31883e5b3932f8e2dec2ddc568" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", - "libc", - "paste", "regex", ] [[package]] name = "aws-lc-rs" -version = "1.12.0" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f409eb70b561706bf8abba8ca9c112729c481595893fd06a2dd9af8ed8441148" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-fips-sys", "aws-lc-sys", - "paste", + "untrusted 0.7.1", "zeroize", ] [[package]] name = "aws-lc-sys" -version = "0.24.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923ded50f602b3007e5e63e3f094c479d9c8a9b42d7f4034e4afe456aa48bfd2" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ - "bindgen", "cc", "cmake", "dunce", "fs_extra", - "paste", -] - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", ] -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - [[package]] name = "bencher" version = "0.1.5" @@ -438,16 +232,14 @@ checksum = "7dfdb4953a096c551ce9ace855a604d702e6e62d77fac690575ae347571717f5" [[package]] name = "bindgen" -version = "0.69.5" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ "bitflags", "cexpr", "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -455,37 +247,14 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.95", - "which", + "syn", ] [[package]] name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "blocking" -version = "1.6.1" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" -dependencies = [ - "async-channel 2.3.1", - "async-task", - "futures-io", - "futures-lite", - "piper", -] +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "bogo" @@ -493,15 +262,18 @@ version = "0.1.0" dependencies = [ "base64", "env_logger", - "rustls 0.23.21", - "rustls-post-quantum", + "nix", + "rustls 0.24.0-dev.0", + "rustls-aws-lc-rs", + "rustls-ring", + "rustls-webpki 0.104.0-alpha.7", ] [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -510,9 +282,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -520,9 +292,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -532,9 +304,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cast" @@ -544,10 +316,11 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.7" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -564,32 +337,25 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "chacha20" -version = "0.9.1" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "chacha20poly1305" -version = "0.10.1" +name = "chacha20" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", - "zeroize", + "cfg-if", + "cpufeatures", + "rand_core 0.10.1", ] [[package]] @@ -619,17 +385,6 @@ dependencies = [ "half", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", - "zeroize", -] - [[package]] name = "clang-sys" version = "1.8.1" @@ -643,9 +398,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.26" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -653,9 +408,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.26" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -665,91 +420,114 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.24" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmake" -version = "0.1.52" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "crossbeam-utils", + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] -name = "const-oid" -version = "0.9.6" +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-models" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "657f625ff361906f779745d08375ae3cc9fef87a35fba5f22874cf773010daf4" +dependencies = [ + "hax-lib", + "pastey", + "rand 0.9.4", +] [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" dependencies = [ "libc", ] [[package]] name = "crabgrind" -version = "0.1.9" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbd43e4f32a9681a504577db2d4ea7d3f7b1bf2e97955561af98501ab600508" +checksum = "370855733cbddd025d0cb602df6f4cf898310921a3dcc4244cc3d1d8b3f2a3e4" dependencies = [ + "bindgen", "cc", + "pkg-config", ] [[package]] name = "criterion" -version = "0.5.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" dependencies = [ + "alloca", "anes", "cast", "ciborium", "clap", "criterion-plot", - "is-terminal", - "itertools 0.10.5", + "itertools 0.13.0", "num-traits", - "once_cell", "oorandom", + "page_size", "plotters", "rayon", "regex", "serde", - "serde_derive", "serde_json", "tinytemplate", "walkdir", @@ -757,12 +535,27 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.5.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", - "itertools 0.10.5", + "itertools 0.13.0", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", ] [[package]] @@ -792,192 +585,67 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-bigint" -version = "0.5.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", -] +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] -name = "crypto-common" -version = "0.1.6" +name = "data-encoding" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] -name = "ctr" -version = "0.9.2" +name = "der-parser" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" dependencies = [ - "cipher", + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", ] [[package]] -name = "curve25519-dalek" -version = "4.1.3" +name = "deranged" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", + "powerfmt", ] [[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] -name = "data-encoding" -version = "2.6.0" +name = "dunce" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] -name = "der" -version = "0.7.9" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid", - "zeroize", -] - -[[package]] -name = "der-parser" -version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.95", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "hkdf", - "pkcs8", - "rand_core", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.95", -] +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "env_filter" -version = "0.1.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", "regex", @@ -985,81 +653,28 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] [[package]] name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" -dependencies = [ - "event-listener 5.4.0", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "ff" -version = "0.13.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core", - "subtle", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "fiat-crypto" -version = "0.2.9" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fnv" @@ -1069,9 +684,15 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "foreign-types" @@ -1090,9 +711,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -1105,144 +726,119 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] -name = "futures-lite" -version = "2.5.0" +name = "futures-macro" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", + "futures-macro", "futures-task", "pin-project-lite", - "pin-utils", "slab", ] [[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.14.7" +name = "getrandom" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ - "typenum", - "version_check", - "zeroize", + "cfg-if", + "libc", + "wasi", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "wasi", + "r-efi 5.3.0", + "wasip2", ] [[package]] -name = "ghash" -version = "0.5.1" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ - "opaque-debug", - "polyval", + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" - -[[package]] -name = "gloo-timers" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] -name = "group" -version = "0.13.0" +name = "graviola" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +checksum = "4387e0458389da24c6fe732531e65595c7c4a32b027f98f4789e512e28224465" dependencies = [ - "ff", - "rand_core", - "subtle", + "cfg-if", + "getrandom 0.3.4", ] [[package]] name = "h2" -version = "0.4.7" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -1259,34 +855,75 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] -name = "heck" -version = "0.5.0" +name = "hashbrown" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +dependencies = [ + "foldhash 0.2.0", +] [[package]] -name = "hermit-abi" -version = "0.4.0" +name = "hax-lib" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "543f93241d32b3f00569201bfce9d7a93c92c6421b23c77864ac929dc947b9fc" +dependencies = [ + "hax-lib-macros", + "num-bigint", + "num-traits", +] + +[[package]] +name = "hax-lib-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8755751e760b11021765bb04cb4a6c4e24742688d9f3aa14c2079638f537b0f" +dependencies = [ + "hax-lib-macros-types", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hax-lib-macros-types" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f177c9ae8ea456e2f71ff3c1ea47bf4464f772a05133fcbba56cd5ba169035a2" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -1295,28 +932,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "hickory-proto" -version = "0.25.0-alpha.4" +name = "hickory-net" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d063c0692ee669aa6d261988aa19ca5510f1cc40e4f211024f50c888499a35d7" +checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" dependencies = [ - "async-recursion", "async-trait", "bytes", "cfg-if", "data-encoding", - "enum-as-inner", "futures-channel", "futures-io", "futures-util", "h2", + "hickory-proto", "http", "idna", "ipnet", - "once_cell", - "rand", - "rustls 0.23.20", - "thiserror 2.0.10", + "jni", + "rand 0.10.1", + "rustls 0.23.40", + "thiserror", "tinyvec", "tokio", "tokio-rustls", @@ -1325,140 +961,84 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hickory-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" +dependencies = [ + "data-encoding", + "idna", + "ipnet", + "jni", + "once_cell", + "prefix-trie", + "rand 0.10.1", + "ring", + "thiserror", + "tinyvec", + "tracing", + "url", +] + [[package]] name = "hickory-resolver" -version = "0.25.0-alpha.3" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eec9ac701a9b34a77c8ccbe39d589db876290f6bdc6e05ac118fd114cb1ad26" +checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" dependencies = [ "cfg-if", "futures-util", + "hickory-net", "hickory-proto", "ipconfig", - "lru-cache", + "ipnet", + "jni", + "moka", + "ndk-context", "once_cell", "parking_lot", - "rand", + "rand 0.10.1", "resolv-conf", - "rustls 0.23.20", + "rustls 0.23.40", "smallvec", - "thiserror 1.0.69", + "system-configuration", + "thiserror", "tokio", "tokio-rustls", "tracing", "webpki-roots", ] -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - -[[package]] -name = "hpke-rs" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11bd4ee27b79fa1820e72ef8489cc729c87299ec3f7f52b8fc8dcb87cb2d485" -dependencies = [ - "hpke-rs-crypto", - "log", - "zeroize", -] - -[[package]] -name = "hpke-rs-crypto" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c3f1ae0a26c18d6469a70db1217136056261c4a244b09a755bc60bd4e055b67" -dependencies = [ - "rand_core", -] - -[[package]] -name = "hpke-rs-rust-crypto" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08d4500baf0aced746723d3515d08212bdb9d941df6d1aca3d46d1619b2a1cf" -dependencies = [ - "aes-gcm", - "chacha20poly1305", - "hkdf", - "hpke-rs-crypto", - "p256", - "p384", - "rand_chacha", - "rand_core", - "sha2", - "x25519-dalek", -] - [[package]] name = "http" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", + "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1467,104 +1047,72 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ - "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", + "icu_locale_core", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] [[package]] -name = "icu_provider_macros" -version = "1.5.0" +name = "id-arena" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.95", -] +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -1573,9 +1121,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1583,117 +1131,161 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", + "hashbrown 0.17.0", + "serde", + "serde_core", ] [[package]] name = "ipconfig" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" dependencies = [ "socket2", "widestring", - "windows-sys 0.48.0", - "winreg", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", ] [[package]] name = "ipnet" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" - -[[package]] -name = "is-terminal" -version = "0.4.13" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", + "serde", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" -version = "0.10.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.12.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] -name = "itertools" -version = "0.14.0" +name = "itoa" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" dependencies = [ - "either", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", ] [[package]] -name = "itoa" -version = "1.0.14" +name = "jiff-static" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "jobserver" -version = "0.1.32" +name = "jni" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "libc", + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", ] [[package]] -name = "js-sys" -version = "0.3.76" +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" dependencies = [ - "once_cell", - "wasm-bindgen", + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", ] [[package]] -name = "kv-log-macro" -version = "1.0.7" +name = "jni-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" dependencies = [ - "log", + "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", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", ] [[package]] @@ -1701,89 +1293,119 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] -name = "lazycell" -version = "1.3.0" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] -name = "libloading" -version = "0.8.6" +name = "libcrux-intrinsics" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "b1b5db005ff8001e026b73a6842ee81bbef8ec5ff0e1915a67ae65fd2a9fafa5" dependencies = [ - "cfg-if", - "windows-targets 0.52.6", + "core-models", + "hax-lib", ] [[package]] -name = "libm" -version = "0.2.11" +name = "libcrux-ml-kem" +version = "0.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "a14ab3e477de9df6ee1273a114018ff62c4996ca9220070c4e5cb1743f94a67d" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-sha3", + "libcrux-traits", +] [[package]] -name = "linked-hash-map" -version = "0.5.6" +name = "libcrux-platform" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "1d9e21d7ed31a92ac539bd69a8c970b183ee883872d2d19ce27036e24cb8ecc4" +dependencies = [ + "libc", +] [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "libcrux-secrets" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "1ce650f3041b44ba40d4263852347d007cd2cd9d1cc856a6f6c8b2e10c3fd40b" +dependencies = [ + "hax-lib", +] [[package]] -name = "litemap" -version = "0.7.4" +name = "libcrux-sha3" +version = "0.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "b1ae0b7d0e1cc4793a609fd0ff2ca3b3a3fabae523770c619a3d4bc86417b0d7" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-traits", +] [[package]] -name = "lock_api" -version = "0.4.12" +name = "libcrux-traits" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "812e4fa89f3f5e34b47f928b22b1b78395a0d4ec23b1f583db635f128159d65f" dependencies = [ - "autocfg", - "scopeguard", + "libcrux-secrets", + "rand 0.9.4", ] [[package]] -name = "log" -version = "0.4.22" +name = "libloading" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ - "value-bag", + "cfg-if", + "windows-link", ] [[package]] -name = "lru-cache" -version = "0.1.2" +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "linked-hash-map", + "scopeguard", ] +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "macro_rules_attribute" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a82271f7bc033d84bbca59a3ce3e4159938cb08a9c3aebbe54d215131518a13" +checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520" dependencies = [ "macro_rules_attribute-proc_macro", "paste", @@ -1791,21 +1413,15 @@ dependencies = [ [[package]] name = "macro_rules_attribute-proc_macro" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dd856d451cc0da70e2ef2ce95a18e39a93b7558bedf10201ad28503f918568" - -[[package]] -name = "match_cfg" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30" [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "minimal-lexical" @@ -1814,24 +1430,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] -name = "miniz_oxide" -version = "0.8.2" +name = "mio" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ - "adler2", + "libc", + "log", + "wasi", + "windows-sys 0.61.2", ] [[package]] -name = "mio" -version = "1.0.3" +name = "moka" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "nix" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", "libc", - "log", - "wasi", - "windows-sys 0.52.0", ] [[package]] @@ -1846,36 +1488,19 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "byteorder", - "lazy_static", - "libm", "num-integer", - "num-iter", "num-traits", - "rand", - "smallvec", - "zeroize", ] [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -1886,17 +1511,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1904,56 +1518,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", ] [[package]] name = "oid-registry" -version = "0.7.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" dependencies = [ "asn1-rs", ] [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] -name = "oorandom" -version = "11.1.4" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] -name = "opaque-debug" -version = "0.3.1" +name = "oorandom" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -1966,14 +1573,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" dependencies = [ "cc", "libc", @@ -1982,38 +1589,20 @@ dependencies = [ ] [[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "p384" -version = "0.13.0" +name = "page_size" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" dependencies = [ - "elliptic-curve", - "primeorder", + "libc", + "winapi", ] -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -2021,15 +1610,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -2038,71 +1627,39 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a" + [[package]] name = "pem" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ "base64", - "serde", + "serde_core", ] [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plotters" @@ -2133,41 +1690,27 @@ dependencies = [ ] [[package]] -name = "polling" -version = "3.7.4" +name = "portable-atomic" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "tracing", - "windows-sys 0.59.0", -] +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] -name = "poly1305" -version = "0.8.0" +name = "portable-atomic-util" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ - "cpufeatures", - "opaque-debug", - "universal-hash", + "portable-atomic", ] [[package]] -name = "polyval" -version = "0.6.2" +name = "potential_utf" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", + "zerovec", ] [[package]] @@ -2178,91 +1721,137 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] +[[package]] +name = "prefix-trie" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23370be78b7e5bcbb0cab4a02047eb040279a693c78daad04c2c5f1c24a83503" +dependencies = [ + "either", + "ipnet", + "num-traits", +] + [[package]] name = "prettyplease" -version = "0.2.27" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483f8c21f64f3ea09fe0f30f5d48c3e8eefe5dac9129f0075f76593b4c1da705" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.95", + "syn", ] [[package]] -name = "primeorder" -version = "0.13.6" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "elliptic-curve", + "proc-macro2", + "quote", ] [[package]] -name = "proc-macro2" -version = "1.0.92" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ - "unicode-ident", + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "quick-error" -version = "1.2.3" +name = "proc-macro2" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[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.8.5" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "libc", "rand_chacha", - "rand_core", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", ] [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom", + "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rayon" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -2270,9 +1859,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -2280,31 +1869,32 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.13.2" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" dependencies = [ "aws-lc-rs", "pem", "rustls-pki-types", "time", + "x509-parser", "yasna", ] [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -2314,9 +1904,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -2325,77 +1915,35 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.17", "libc", - "spin", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] -[[package]] -name = "rsa" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core", - "sha2", - "signature", - "spki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -2415,72 +1963,70 @@ dependencies = [ "nom", ] -[[package]] -name = "rustix" -version = "0.38.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ + "aws-lc-rs", "log", "once_cell", - "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.13", "subtle", "zeroize", ] [[package]] name = "rustls" -version = "0.23.21" +version = "0.24.0-dev.0" dependencies = [ - "aws-lc-rs", - "base64", "bencher", "brotli", "brotli-decompressor", "env_logger", - "hashbrown", - "hex", + "graviola", + "hashbrown 0.17.0", "log", - "macro_rules_attribute", - "num-bigint", "once_cell", "rcgen", - "ring", "rustls-pki-types", - "rustls-webpki", - "rustversion", - "serde", - "serde_json", + "rustls-test", + "rustls-webpki 0.104.0-alpha.7", "subtle", "time", "webpki-roots", - "x509-parser", "zeroize", "zlib-rs", ] +[[package]] +name = "rustls-aws-lc-rs" +version = "0.1.0-dev.0" +dependencies = [ + "aws-lc-rs", + "bencher", + "hex", + "rustls 0.24.0-dev.0", + "rustls-pki-types", + "rustls-test", + "serde", + "serde_json", + "subtle", + "zeroize", +] + [[package]] name = "rustls-bench" version = "0.1.0" dependencies = [ "clap", - "rustls 0.23.21", - "rustls-post-quantum", + "rustls 0.24.0-dev.0", + "rustls-aws-lc-rs", + "rustls-graviola", + "rustls-ring", + "rustls-test", "tikv-jemallocator", ] @@ -2493,21 +2039,23 @@ dependencies = [ "byteorder", "clap", "crabgrind", - "fxhash", "itertools 0.14.0", "rayon", - "rustls 0.23.21", - "tikv-jemallocator", + "rustc-hash", + "rustls 0.24.0-dev.0", + "rustls-aws-lc-rs", + "rustls-fuzzing-provider", + "rustls-ring", + "rustls-test", ] [[package]] name = "rustls-connect-tests" version = "0.0.1" dependencies = [ - "hickory-resolver", "regex", "ring", - "rustls 0.23.21", + "rustls 0.24.0-dev.0", "tokio", ] @@ -2515,14 +2063,15 @@ dependencies = [ name = "rustls-examples" version = "0.0.1" dependencies = [ - "async-std", "clap", "env_logger", "hickory-resolver", "log", "mio", "rcgen", - "rustls 0.23.21", + "rustls 0.24.0-dev.0", + "rustls-aws-lc-rs", + "rustls-util", "serde", "tokio", "webpki-roots", @@ -2533,8 +2082,18 @@ name = "rustls-fuzzing-provider" version = "0.1.0" dependencies = [ "env_logger", - "rustls 0.23.21", - "rustls-webpki", + "rustls 0.24.0-dev.0", +] + +[[package]] +name = "rustls-graviola" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323c712e50c59ceb2ba9ad4d79dcfd3e0046a082d61efa87fcdf8f59af04473c" +dependencies = [ + "graviola", + "libcrux-ml-kem", + "rustls 0.23.40", ] [[package]] @@ -2546,85 +2105,113 @@ dependencies = [ "num-bigint", "once_cell", "openssl", - "rustls 0.23.21", + "rustls 0.24.0-dev.0", + "rustls-aws-lc-rs", + "rustls-util", ] [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-post-quantum" -version = "0.2.1" +version = "0.3.0-alpha.0" dependencies = [ "aws-lc-rs", "criterion", "env_logger", - "rustls 0.23.21", + "rcgen", + "rustls 0.24.0-dev.0", + "rustls-aws-lc-rs", + "rustls-test", + "rustls-util", + "rustls-webpki 0.104.0-alpha.7", "webpki-roots", ] [[package]] -name = "rustls-provider-example" -version = "0.0.1" +name = "rustls-provider-test" +version = "0.1.0" +dependencies = [ + "hex", + "rustls 0.24.0-dev.0", + "rustls-aws-lc-rs", + "serde", + "serde_json", +] + +[[package]] +name = "rustls-ring" +version = "0.1.0-dev.0" +dependencies = [ + "bencher", + "ring", + "rustls 0.24.0-dev.0", + "rustls-pki-types", + "rustls-test", + "subtle", +] + +[[package]] +name = "rustls-test" +version = "0.1.0" dependencies = [ - "chacha20poly1305", - "der", - "ecdsa", "env_logger", - "hmac", - "hpke-rs", - "hpke-rs-crypto", - "hpke-rs-rust-crypto", - "p256", - "pkcs8", - "rand_core", - "rcgen", - "rsa", - "rustls 0.23.21", - "rustls-webpki", - "sha2", - "signature", + "log", + "macro_rules_attribute", + "num-bigint", + "rustls 0.24.0-dev.0", + "rustls-aws-lc-rs", + "rustls-pki-types", + "rustls-ring", + "rustls-util", + "rustls-webpki 0.104.0-alpha.7", "webpki-roots", - "x25519-dalek", + "x509-parser", ] [[package]] -name = "rustls-provider-test" +name = "rustls-util" version = "0.1.0" dependencies = [ - "hex", - "rustls 0.23.21", - "rustls-provider-example", - "serde", - "serde_json", + "env_logger", + "log", + "rustls 0.24.0-dev.0", ] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] -name = "rustversion" -version = "1.0.19" +name = "rustls-webpki" +version = "0.104.0-alpha.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "bea702cca24d344fc70973022bf7eb920c224e318466eb49784272337dd24b1a" +dependencies = [ + "rustls-pki-types", + "untrusted 0.9.0", +] [[package]] -name = "ryu" -version = "1.0.18" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" @@ -2641,67 +2228,53 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - [[package]] name = "semver" -version = "1.0.24" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] [[package]] -name = "serde_derive" -version = "1.0.217" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.95", + "serde_derive", ] [[package]] -name = "serde_json" -version = "1.0.135" +name = "serde_derive" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "sha2" -version = "0.10.8" +name = "serde_json" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "cfg-if", - "cpufeatures", - "digest", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", ] [[package]] @@ -2711,61 +2284,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "signature" -version = "2.2.0" +name = "simd_cesu8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" dependencies = [ - "digest", - "rand_core", + "rustc_version", + "simdutf8", ] +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.8" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", + "windows-sys 0.61.2", ] [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "strsim" @@ -2781,9 +2341,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -2791,72 +2351,68 @@ dependencies = [ ] [[package]] -name = "syn" -version = "2.0.95" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn", ] [[package]] -name = "synstructure" -version = "0.13.1" +name = "system-configuration" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.95", + "bitflags", + "core-foundation", + "system-configuration-sys", ] [[package]] -name = "thiserror" -version = "1.0.69" +name = "system-configuration-sys" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ - "thiserror-impl 1.0.69", + "core-foundation-sys", + "libc", ] [[package]] -name = "thiserror" -version = "2.0.10" +name = "tagptr" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ac7f54ca534db81081ef1c1e7f6ea8a3ef428d2fc069097c079443d24124d3" -dependencies = [ - "thiserror-impl 2.0.10", -] +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] -name = "thiserror-impl" -version = "1.0.69" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.95", + "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.10" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e9465d30713b56a37ede7185763c3492a91be2f5fa68d958c44e41ab9248beb" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] name = "tikv-jemalloc-sys" -version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d" +checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" dependencies = [ "cc", "libc", @@ -2864,9 +2420,9 @@ dependencies = [ [[package]] name = "tikv-jemallocator" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865" +checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" dependencies = [ "libc", "tikv-jemalloc-sys", @@ -2874,30 +2430,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -2905,9 +2461,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -2925,9 +2481,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -2940,46 +2496,45 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.43.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ - "backtrace", "bytes", "libc", "mio", "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] name = "tokio-rustls" -version = "0.26.1" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.20", + "rustls 0.23.40", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -2990,56 +2545,40 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.95", -] - [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] [[package]] -name = "typenum" -version = "1.17.0" +name = "unicode-ident" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] -name = "unicode-ident" -version = "1.0.14" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] -name = "universal-hash" -version = "0.5.1" +name = "untrusted" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "untrusted" @@ -3049,21 +2588,16 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -3077,10 +2611,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "value-bag" -version = "1.10.0" +name = "uuid" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] [[package]] name = "vcpkg" @@ -3088,12 +2627,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "walkdir" version = "2.5.0" @@ -3106,53 +2639,46 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasm-bindgen" -version = "0.2.99" +name = "wasip2" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", + "wit-bindgen 0.57.1", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.99" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.95", - "wasm-bindgen-shared", + "wit-bindgen 0.51.0", ] [[package]] -name = "wasm-bindgen-futures" -version = "0.4.49" +name = "wasm-bindgen" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", - "js-sys", "once_cell", - "wasm-bindgen", - "web-sys", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3160,59 +2686,84 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.95", - "wasm-bindgen-backend", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +dependencies = [ + "unicode-ident", +] [[package]] -name = "web-sys" -version = "0.3.76" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ - "js-sys", - "wasm-bindgen", + "leb128fmt", + "wasmparser", ] [[package]] -name = "webpki-roots" -version = "0.26.7" +name = "wasm-metadata" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ - "rustls-pki-types", + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", ] [[package]] -name = "which" -version = "4.4.2" +name = "wasmparser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "either", - "home", - "once_cell", - "rustix", + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", ] [[package]] name = "widestring" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" [[package]] name = "winapi" @@ -3232,11 +2783,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3246,45 +2797,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-targets 0.48.5", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "windows-sys" -version = "0.52.0" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-targets 0.52.6", + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-link", ] [[package]] @@ -3293,46 +2855,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3345,24 +2889,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3371,76 +2903,131 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_gnullvm" +name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "wit-bindgen" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] -name = "winreg" -version = "0.50.0" +name = "wit-bindgen-core" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "anyhow", + "heck", + "wit-parser", ] [[package]] -name = "write16" -version = "1.0.0" +name = "wit-bindgen-rust" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] [[package]] -name = "writeable" -version = "0.5.5" +name = "wit-bindgen-rust-macro" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] [[package]] -name = "x25519-dalek" -version = "2.0.1" +name = "wit-component" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ - "curve25519-dalek", - "rand_core", + "anyhow", + "bitflags", + "indexmap", + "log", "serde", - "zeroize", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + [[package]] name = "x509-parser" -version = "0.16.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" dependencies = [ "asn1-rs", + "aws-lc-rs", "data-encoding", "der-parser", "lazy_static", "nom", "oid-registry", "rusticata-macros", - "thiserror 1.0.69", + "thiserror", "time", ] @@ -3455,11 +3042,10 @@ dependencies = [ [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -3467,83 +3053,79 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", "synstructure", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] -name = "zeroize_derive" -version = "1.4.2" +name = "zerotrie" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.95", + "displaydoc", + "yoke", + "zerofrom", ] [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -3552,17 +3134,23 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] name = "zlib-rs" -version = "0.4.1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + +[[package]] +name = "zmij" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aada01553a9312bad4b9569035a1f12b05e5ec9770a1a4b323757356928944f8" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 88fdcb74976..07aa8561013 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,10 +10,14 @@ members = [ "examples", # Tests that require OpenSSL "openssl-tests", - # example of custom provider - "provider-example", # the main library and tests "rustls", + # AWS-LC based crypto provider + "rustls-aws-lc-rs", + # the ring crypto provider + "rustls-ring", + # common code for testing the core crate + "rustls-test", # benchmarking tool "rustls-bench", # experimental post-quantum algorithm support @@ -22,6 +26,8 @@ members = [ "rustls-provider-test", # rustls cryptography provider for fuzzing "rustls-fuzzing-provider", + # utility code + "rustls-util", ] ## Deliberately not included in `members`: @@ -31,36 +37,36 @@ exclude = [ ] default-members = [ + # --- "examples", "rustls", + "rustls-aws-lc-rs", + "rustls-ring", + "rustls-test", ] resolver = "2" [workspace.dependencies] anyhow = "1.0.73" -asn1 = "0.20" -async-std = { version = "1.12.0", features = ["attributes"] } +asn1 = "0.24" async-trait = "0.1.74" -aws-lc-rs = { version = "1.12", default-features = false, features = ["aws-lc-sys", "prebuilt-nasm"] } +aws-lc-rs = { version = "1.14", default-features = false } base64 = "0.22" bencher = "0.1.5" -brotli = { version = "7", default-features = false, features = ["std"] } -brotli-decompressor = "4.0.1" # 4.0.1 required for panic fix +brotli = { version = "8", default-features = false, features = ["std"] } +brotli-decompressor = "5.0.0" byteorder = "1.4.3" -chacha20poly1305 = { version = "0.10", default-features = false, features = ["alloc"] } clap = { version = "4.3.21", features = ["derive", "env"] } -crabgrind = "=0.1.9" # compatible with valgrind package on GHA ubuntu-latest -der = "0.7" -ecdsa = "0.16.8" +crabgrind = "0.2" +criterion = "0.8" env_logger = "0.11" -fxhash = "0.2.1" -hashbrown = { version = "0.15", default-features = false, features = ["default-hasher", "inline-more"] } +graviola = "0.3" +hashbrown = { version = "0.17", default-features = false, features = ["default-hasher", "inline-more"] } hex = "0.4" -hickory-resolver = { version = "=0.25.0-alpha.3", features = ["dns-over-https-rustls", "webpki-roots"] } -hmac = "0.12" -hpke-rs = "0.2" -hpke-rs-crypto = "0.2" -hpke-rs-rust-crypto = "0.2" +hickory-resolver = { version = "0.26", features = ["https-aws-lc-rs", "webpki-roots"] } +hpke-rs = "0.6" +hpke-rs-crypto = "0.6" +hpke-rs-rust-crypto = "0.6" itertools = "0.14" log = { version = "0.4.8" } macro_rules_attribute = "0.2" @@ -68,30 +74,70 @@ mio = { version = "1", features = ["net", "os-poll"] } num-bigint = "0.4.4" once_cell = { version = "1.16", default-features = false, features = ["alloc", "race"] } openssl = "0.10" -p256 = { version = "0.13.2", default-features = false, features = ["alloc", "ecdsa", "pkcs8"] } -pkcs8 = "0.10.2" -pki-types = { package = "rustls-pki-types", version = "1.10", features = ["alloc"] } -rand_core = { version = "0.6", features = ["getrandom"] } +pki-types = { package = "rustls-pki-types", version = "1.14", features = ["alloc"] } rayon = "1.7" -rcgen = { version = "0.13", features = ["pem", "aws_lc_rs"], default-features = false } +rcgen = { version = "0.14.4", features = ["pem", "aws_lc_rs"], default-features = false } regex = "1" ring = "0.17" -rsa = { version = "0.9", features = ["sha2"], default-features = false } +rustc-hash = "2" +rustls-fuzzing-provider = { path = "rustls-fuzzing-provider/" } +rustls-graviola = { version = "0.3" } +rustls-test = { path = "rustls-test/", default-features = false } +rustls-util = { path = "rustls-util/" } serde = { version = "1", features = ["derive"] } serde_json = "1" -sha2 = { version = "0.10", default-features = false } -signature = "2" subtle = { version = "2.5.0", default-features = false } time = { version = "0.3.6", default-features = false } tikv-jemallocator = "0.6" -tokio = { version = "1.34", features = ["io-util", "macros", "net", "rt"]} -webpki = { package = "rustls-webpki", version = "0.102.8", features = ["alloc"], default-features = false } -webpki-roots = "0.26" -x25519-dalek = "2" -x509-parser = "0.16" -zeroize = "1.7" -zlib-rs = "0.4" +tokio = { version = "1.34", features = ["io-util", "macros", "net", "rt"] } +webpki = { package = "rustls-webpki", version = "=0.104.0-alpha.7", features = [ + "alloc", +], default-features = false } +webpki-roots = "1" +x509-parser = "0.18" +zeroize = "1.8" +zlib-rs = "0.6" [profile.bench] codegen-units = 1 lto = true + +[workspace.lints.clippy] +alloc_instead_of_core = "warn" +cloned_instead_of_copied = "warn" +manual_let_else = "warn" +needless_collect = "warn" +needless_pass_by_ref_mut = "warn" +or_fun_call = "warn" +redundant_clone = "warn" +std_instead_of_core = "warn" +upper_case_acronyms = "warn" +use_self = "warn" + +# Relax these clippy lints: +# - too_many_arguments: some things just need a lot of state, wrapping it +# doesn't necessarily make it easier to follow what's going on +too_many_arguments = "allow" + +# - new_without_default: for internal constructors, the indirection is not +# helpful +new_without_default = "allow" + +[workspace.lints.rust] +elided_lifetimes_in_paths = "warn" +trivial_numeric_casts = "warn" +unexpected_cfgs = { level = "warn", check-cfg = [ + "cfg(bench)", + "cfg(coverage_nightly)", + "cfg(rustls_docsrs)", +] } +unnameable_types = "warn" +unreachable_pub = "warn" +unused_extern_crates = "warn" +unused_import_braces = "warn" +unused_qualifications = "warn" + +# ensure all our tests are against the local copy, never +# against the latest _published_ copy. +[patch.crates-io] +rustls = { path = "rustls" } diff --git a/README.md b/README.md index 2488264fce6..0c6362b63c9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

@@ -23,6 +23,9 @@ If you'd like to help out, please see [CONTRIBUTING.md](CONTRIBUTING.md). [![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/MCSB76RU96) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9034/badge)](https://www.bestpractices.dev/projects/9034) +The maintainers pronounce "rustls" as rustles (rather than rust-TLS), but we don't feel strongly +about it. + ## Changelog The detailed list of changes in each release can be found at @@ -43,64 +46,72 @@ list of protocol features](https://docs.rs/rustls/latest/rustls/manual/_04_featu ### Platform support -While Rustls itself is platform independent, by default it uses [`aws-lc-rs`] for implementing -the cryptography in TLS. See [the aws-lc-rs FAQ][aws-lc-rs-platforms-faq] for more details of the -platform/architecture support constraints in aws-lc-rs. - -[`ring`] is also available via the `ring` crate feature: see -[the supported `ring` target platforms][ring-target-platforms]. +While Rustls itself is platform independent, it requires the use of cryptography primitives +for implementing the cryptography algorithms used in TLS. In Rustls, a +[`crypto::CryptoProvider`] represents a collection of crypto primitive implementations. By providing a custom instance of the [`crypto::CryptoProvider`] struct, you can replace all cryptography dependencies of rustls. This is a route to being portable to a wider set of architectures and environments, or compliance requirements. See the [`crypto::CryptoProvider`] documentation for more details. -Specifying `default-features = false` when depending on rustls will remove the -dependency on aws-lc-rs. - -Rustls requires Rust 1.63 or later. It has an optional dependency on zlib-rs which requires 1.75 or later. - -[ring-target-platforms]: https://github.com/briansmith/ring/blob/2e8363b433fa3b3962c877d9ed2e9145612f3160/include/ring-core/target.h#L18-L64 [`crypto::CryptoProvider`]: https://docs.rs/rustls/latest/rustls/crypto/struct.CryptoProvider.html -[`ring`]: https://crates.io/crates/ring -[aws-lc-rs-platforms-faq]: https://aws.github.io/aws-lc-rs/faq.html#can-i-run-aws-lc-rs-on-x-platform-or-architecture -[`aws-lc-rs`]: https://crates.io/crates/aws-lc-rs ### Cryptography providers Since Rustls 0.22 it has been possible to choose the provider of the cryptographic primitives that Rustls uses. This may be appealing if you have specific platform, compliance or feature -requirements that aren't met by the default provider, [`aws-lc-rs`]. +requirements. -Users that wish to customize the provider in use can do so when constructing `ClientConfig` -and `ServerConfig` instances using the `with_crypto_provider` method on the respective config -builder types. See the [`crypto::CryptoProvider`] documentation for more details. +From 0.24, users must explicitly provide a crypto provider when constructing `ClientConfig` or +`ServerConfig` instances. See the [`crypto::CryptoProvider`] documentation for more details. -#### Built-in providers +#### First-party providers -Rustls ships with two built-in providers controlled with associated feature flags: +The Rustls project currently maintains two cryptography providers: -* [`aws-lc-rs`] - enabled by default, available with the `aws_lc_rs` feature flag enabled. -* [`ring`] - available with the `ring` feature flag enabled. +* [`rustls-aws-lc-rs`] - a provider that uses the [`aws-lc-rs`] crate for cryptography. +While this provider can be harder to build on [some platforms][aws-lc-rs-platforms-faq], it provides excellent +performance and a complete feature set (including post-quantum algorithms). +* [`rustls-ring`] - a provider that uses the [`ring`] crate for cryptography. This +provider is easier to build on a variety of platforms, but has a more limited feature set +(for example, it does not support post-quantum algorithms). + +The Rustls team recommends using the [`rustls-aws-lc-rs`] crate for its complete feature set +and performance. See [the aws-lc-rs FAQ][aws-lc-rs-platforms-faq] for more details of the +platform/architecture support constraints in aws-lc-rs. See the documentation for [`crypto::CryptoProvider`] for details on how providers are selected. +(For rustls versions prior to 0.24, both of these providers were shipped as part of the rustls +crate, and Cargo features were used to select the preferred provider. The `aws-lc-rs` feature +was enabled by default.) + +[`rustls-aws-lc-rs`]: https://crates.io/crates/rustls-aws-lc-rs +[`aws-lc-rs`]: https://crates.io/crates/aws-lc-rs +[aws-lc-rs-platforms-faq]: https://aws.github.io/aws-lc-rs/faq.html#can-i-run-aws-lc-rs-on-x-platform-or-architecture +[`rustls-ring`]: https://crates.io/crates/rustls-ring +[`ring`]: https://crates.io/crates/ring + #### Third-party providers The community has also started developing third-party providers for Rustls: -* [`rustls-mbedtls-provider`] - a provider that uses [`mbedtls`] for cryptography. -* [`rustls-openssl`] - a provider that uses [OpenSSL] for cryptography. -* [`rustls-post-quantum`]: an experimental provider that adds support for post-quantum -key exchange to the default aws-lc-rs provider. * [`boring-rustls-provider`] - a work-in-progress provider that uses [`boringssl`] for cryptography. +* [`rustls-ccm`] - adds AES-CCM cipher suites (TLS 1.2 and 1.3) using [`RustCrypto`], for IoT/constrained-device protocols (IEEE 2030.5, Matter, RFC 7925). +* [`rustls-graviola`] - a provider that uses [`graviola`] for cryptography. +* [`rustls-mbedtls-provider`] - a provider that uses [`mbedtls`] for cryptography. +* [`rustls-openssl`] - a provider that uses [OpenSSL] for cryptography. * [`rustls-rustcrypto`] - an experimental provider that uses the crypto primitives from [`RustCrypto`] for cryptography. * [`rustls-symcrypt`] - a provider that uses Microsoft's [SymCrypt] library. * [`rustls-wolfcrypt-provider`] - a work-in-progress provider that uses [`wolfCrypt`] for cryptography. +[`rustls-ccm`]: https://github.com/jsulmont/rustls-ccm +[`rustls-graviola`]: https://crates.io/crates/rustls-graviola +[`graviola`]: https://github.com/ctz/graviola [`rustls-mbedtls-provider`]: https://github.com/fortanix/rustls-mbedtls-provider [`mbedtls`]: https://github.com/Mbed-TLS/mbedtls [`rustls-openssl`]: https://github.com/tofay/rustls-openssl @@ -111,21 +122,12 @@ from [`RustCrypto`] for cryptography. [`boringssl`]: https://github.com/google/boringssl [`rustls-rustcrypto`]: https://github.com/RustCrypto/rustls-rustcrypto [`RustCrypto`]: https://github.com/RustCrypto -[`rustls-post-quantum`]: https://crates.io/crates/rustls-post-quantum [`rustls-wolfcrypt-provider`]: https://github.com/wolfSSL/rustls-wolfcrypt-provider [`wolfCrypt`]: https://www.wolfssl.com/products/wolfcrypt -#### Custom provider - -We also provide a simple example of writing your own provider in the [`custom-provider`] -example. This example implements a minimal provider using parts of the [`RustCrypto`] -ecosystem. - See the [Making a custom CryptoProvider] section of the documentation for more information on this topic. -[`custom-provider`]: https://github.com/rustls/rustls/tree/main/provider-example/ -[`RustCrypto`]: https://github.com/RustCrypto [Making a custom CryptoProvider]: https://docs.rs/rustls/latest/rustls/crypto/struct.CryptoProvider.html#making-a-custom-cryptoprovider # Example code @@ -184,7 +186,7 @@ depth=2 CN = ponytown RSA CA verify error:num=19:self signed certificate in certificate chain hello world ^C -$ echo hello world | cargo run --bin tlsclient-mio -- --cafile test-ca/rsa-2048/ca.cert -p 8443 localhost +$ echo hello world | cargo run --bin tlsclient-mio -- --cafile test-ca/rsa-2048/ca.cert --port 8443 localhost hello world ^C ``` diff --git a/SECURITY.md b/SECURITY.md index 595d560d05a..6e88fdb160f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -5,7 +5,7 @@ Security fixes will be backported only to the rustls versions for which the original semver-compatible release was published less than 2 years ago. -For example, as of 2024-04-18 the latest release is 0.23.4. +For example, as of 2025-05-09 the latest release is 0.23.27. * 0.23.0 was released in February of 2024 * 0.22.0 was released in December of 2023 @@ -13,7 +13,7 @@ For example, as of 2024-04-18 the latest release is 0.23.4. * 0.20.0 was released in September of 2021 * 0.19.0 was released in November of 2020 -Therefore 0.23.x, 0.22.x and 0.21.x will be updated, while 0.20.x and 0.19.x +Therefore 0.23.x and 0.22.x will be updated, while 0.21.x, 0.20.x and 0.19.x will not be. > [!NOTE] @@ -29,8 +29,8 @@ in the course of normal development, subject to these constraints: - Our MSRV will be no more recent than 9 versions old, or approximately 12 months. > [!TIP] -> At the time of writing, the most recent Rust release is 1.82.0. That means -> our MSRV could be as recent as 1.73.0. As it happens, it is 1.71.0. +> At the time of writing, the most recent Rust release is 1.94. That means +> our MSRV could be as recent as 1.85. As it happens, it is 1.85. - Our MSRV policy only covers the core library crate: it does not cover tests or example code, and is not binding on our dependencies. @@ -55,11 +55,20 @@ MSRV than this policy. > [!NOTE] > This is currently the case for our optional dependency on `zlib-rs`, which -> has a current MSRV of 1.75.0. +> has a current MSRV of 1.75. ## Reporting a Vulnerability +Before reporting a security bug, make sure to: + +- Consider the threat model below. Misconfiguration that is unlikely to happen accidentally is + unlikely to be a security bug. +- If applicable, compare the behavior to other TLS implementations. If the behavior is consistent + with other implementations, it is less likely to be a security bug. + Please report security bugs [via github](https://github.com/rustls/rustls/security/advisories/new). +Make sure to disclose any use of AI assistance upfront. + We'll then: - Prepare a fix and regression tests. @@ -69,3 +78,112 @@ We'll then: If you're *looking* for security bugs, this crate is set up for `cargo fuzz` but would benefit from more runtime, targets and corpora. + +## Threat model + +### Scope and assumptions + +This library typically sits between raw network I/O and application code. The network side +is fully attacker-controlled; the application-side is relatively trusted but still aims to be +misuse-resistant. + +By "fully attacker-controlled", we specifically include: + +- on-path attackers (manipulating traffic to a real, honest peer) +- malicious peers (whether pre- or post-authentication) +- honest but broken peers (again, pre- or post-authentication) + +The core `rustls` library is a TLS protocol implementation, and requires additional items to +become useful. These items are therefore in scope: + +- the default certificate verifiers based on `rustls-webpki`. +- the cryptography providers published from the rustls repository (currently `rustls-ring` and `rustls-aws-lc-rs`; + but not the underlying libraries). + +These items are out of scope (security reports for them will be treated as normal bug reports): + +- examples, benchmarking and test code, +- code in the `rustls-util` crate +- our public website https://rustls.dev/ + +### Boundary: network-originated input + +Everything arriving from the wire is treated as adversarially crafted. This is the primary attack surface. + +Specific threats (non-exhaustive): + +- Integer overflow or underflow in length fields, +- Buffer over-read during fragment reassembly, +- Infinite loops, +- Reachable loops with inappropriate and attacker-controlled complexity, with significant amplification, +- Reachable panics, +- Authentication bypass, +- Protocol downgrade, +- Memory exhaustion or excessive memory consumption, with a significant amplification compared to attacker-controlled input. + +Mitigations: + +- The entire crate which processes items on this trust boundary is `forbid(unsafe_code)`. This means all + code within is the memory safe-subset of Rust. This ameliorates impact of items like integer overflows (generally + reducing their impact to denial-of-service), but has little impact on other threats. +- We fuzz this interface, looking for reachable panics. The project is registered with OSS-Fuzz which provides + compute for this effort. Fuzzing is performed with a mock provider of cryptography, which is intended to make + both pre-auth and post-auth code paths reachable to the fuzzer (at the cost of fuzzing not covering the actual + cryptography implementations). +- We have [studied and explained](https://rustls.dev/docs/manual/_01_impl_vulnerabilities/index.html#a-review-of-tls-implementation-vulnerabilities) + issues encountered in other TLS implementations and discuss further mitigations there. +- We implement the TLS 1.3 downgrade sentinel (a [standard and required protocol feature](https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.3) + which limits downgrade from TLS 1.3). + +### Boundary: cryptography provider + +All items under the `CryptoProvider` and associated interfaces are external to the core `rustls` +library, but overall secure operation is contingent on their correct implementation. Specific +responsibilities are delegated through this interface (non-exhaustive): + +- random material generation +- cryptography operations (encryption, decryption, hashing, key agreement, key derivation, signing, verification) +- zeroization of long-term, ephemeral and intermediate secret key material +- side-channel safety of cryptographic operations +- correct error reporting (especially for cases such as signature verification and input validation) + +The core `rustls` crate cannot perform securely if these items are faulty, and it is not +a security finding that (for example) rustls does not detect random material generation which produces +the number 4 repeatedly. + +In addition, the core `rustls` crate itself: + +- performs zeroization of key material values it holds, +- compares secret and public values in constant time, +- correctly propagates and handles errors, +- avoids `Debug` impls on any type that contains secret key material, +- eschews support for problematic protocol features such as RSA encryption, and CBC-mode ciphersuites. + +### Boundary: public API + +The public API is the interface between the core `rustls` library and application code. +This is a semi-trusted boundary: callers are not treated as adversaries, but the API +aims to be misuse-resistant so that common mistakes do not lead to security failures. + +However, application code is not treated as adversarial. Callers who deliberately work +to undermine their own security (for example, by implementing a custom certificate verifier +that accepts all certificates) are outside the threat model. Security reports that +require the caller to actively opt in to insecure behavior — through custom configuration +that is unlikely to arise by accident — will be treated as normal bug reports. + +Specific threats (non-exhaustive): + +- Accidental or inadvertent disabling of essential security controls such as + hostname verification or certificate chain validation. +- Accidental or inadvertent exposure of secret key material outside the library. +- Reachable panics from normal sequences of API calls. + +Mitigations: + +- We make it specifically unfriendly to configure a custom certificate verifier, + to guide people away from this route of problem solving deployment issues. +- Support for [`SSLKEYLOGFILE`](https://datatracker.ietf.org/doc/draft-ietf-tls-keylogfile/) + requires explicit action on the part of the application. +- Avoiding `Debug` impls on any type that contains secret key material. +- In the public API error type, we have items for unreachable conditions, and + conditions that indicate misuse of the public API. These are returned instead of panics. diff --git a/admin/all-features-except b/admin/all-features-except index 395ee9d1611..d94ce63815f 100755 --- a/admin/all-features-except +++ b/admin/all-features-except @@ -9,7 +9,7 @@ a, b and c. The output is decorated with `--no-default-features --features` meaning it can be used directly with cargo, for example: -$ cargo build $(admin/all-features-except std,logging rustls) +$ cargo build $(admin/all-features-except std,log rustls) It is assumed but not verified that the remaining features do not have dependencies on the disabled features (otherwise, cargo will diff --git a/admin/bench-measure.mk b/admin/bench-measure.mk index a02510fed47..d982db3d060 100644 --- a/admin/bench-measure.mk +++ b/admin/bench-measure.mk @@ -48,14 +48,14 @@ memory: $(BENCH) $(MEMUSAGE) $^ memory TLS13_AES_256_GCM_SHA384 5000 threads: $(BENCH) - for thr in $(shell admin/threads-seq.rs) ; do \ - $^ --threads $$thr handshake TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ; \ - $^ --threads $$thr handshake-resume TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ; \ - $^ --threads $$thr handshake-ticket TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ; \ - $^ --key-type rsa2048 --threads $$thr handshake TLS13_AES_256_GCM_SHA384 ; \ - $^ --threads $$thr handshake-ticket TLS13_AES_256_GCM_SHA384 ; \ - $^ --threads $$thr bulk TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ; \ - $^ --key-type rsa2048 --threads $$thr bulk TLS13_AES_256_GCM_SHA384 ; \ + for t in $(shell admin/threads-seq.rs) ; do \ + $^ --threads $$t handshake TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ; \ + $^ --threads $$t handshake-resume TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ; \ + $^ --threads $$t handshake-ticket TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ; \ + $^ --key-type rsa2048 --threads $$t handshake TLS13_AES_256_GCM_SHA384 ; \ + $^ --threads $$t handshake-ticket TLS13_AES_256_GCM_SHA384 ; \ + $^ --threads $$t bulk TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ; \ + $^ --key-type rsa2048 --threads $$t bulk TLS13_AES_256_GCM_SHA384 ; \ done thread-latency: $(BENCH) diff --git a/admin/capture-certdata b/admin/capture-certdata index b02cf24dc6a..2d3ad4b9961 100755 --- a/admin/capture-certdata +++ b/admin/capture-certdata @@ -49,7 +49,7 @@ def collect(hostname): return certs if __name__ == '__main__': - certfile = lambda name, i: 'rustls/src/testdata/cert-%s.%d.der' % (name, i) + certfile = lambda name, i: 'rustls-test/benches/data/cert-%s.%d.der' % (name, i) for name, hostname in SITES.items(): if path.exists(certfile(name, 0)): @@ -59,4 +59,3 @@ if __name__ == '__main__': for i, cert in enumerate(certchain): open(certfile(name, i), 'wb').write(cert) print('wrote', certfile(name, i)) - diff --git a/admin/clippy b/admin/clippy index 32250254c43..1a02146a5ce 100755 --- a/admin/clippy +++ b/admin/clippy @@ -23,6 +23,9 @@ run_clippy --package rustls --no-default-features --all-targets # run all workspace members (individually, because we don't want feature unification) for p in $(admin/all-workspace-members) ; do case "$p" in + rustls-bench) + ALL_FEATURES=$(admin/all-features-except graviola rustls-bench) + ;; *) ALL_FEATURES="--all-features" ;; diff --git a/admin/coverage b/admin/coverage index 61360e42284..aeea3db2b0d 100755 --- a/admin/coverage +++ b/admin/coverage @@ -2,13 +2,18 @@ set -e -source <(cargo llvm-cov show-env --export-prefix "$@") +# for branch coverage support, which is unstable +export RUSTC_BOOTSTRAP=1 +OPTIONS=--branch + +source <(cargo llvm-cov show-env --export-prefix $OPTIONS "$@") cargo llvm-cov clean --workspace cargo build --locked --all-targets --all-features -cargo test --locked --all-features -cargo test -p rustls --locked --no-default-features --features tls12,logging,aws_lc_rs,fips,std -cargo test -p rustls --locked --no-default-features --features tls12,logging,ring,std +cargo test --locked --all-features # this presumably enables `fips` +cargo test -p rustls-ring --locked +cargo test -p rustls-aws-lc-rs --locked +cargo test -p rustls-test --locked --features aws-lc-rs # aws-lc-rs, but no fips # ensure both zlib and brotli are tested, irrespective of their order cargo test --locked $(admin/all-features-except zlib rustls) @@ -17,10 +22,4 @@ cargo test --locked $(admin/all-features-except brotli rustls) ## bogo cargo test --locked --test bogo -- --ignored --test-threads 1 -# provider example unit tests -cargo test --locked --package rustls-provider-example - -## provider example tests -cargo test --locked --package rustls-provider-test - -cargo llvm-cov report "$@" +cargo llvm-cov report --ignore-filename-regex "(bogo|rustls-test)/" $OPTIONS "$@" diff --git a/admin/logo/LICENSE b/admin/logo/LICENSE new file mode 100644 index 00000000000..fe463e0f788 --- /dev/null +++ b/admin/logo/LICENSE @@ -0,0 +1,408 @@ +Attribution-NonCommercial 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-NonCommercial 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + j. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + k. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + l. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce, reproduce, and Share Adapted Material for + NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database for NonCommercial purposes + only; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.†The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/admin/logo/rustls.svg b/admin/logo/rustls.svg new file mode 100644 index 00000000000..256cf8fff7a --- /dev/null +++ b/admin/logo/rustls.svg @@ -0,0 +1,628 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/admin/rustls-logo-web.png b/admin/rustls-logo-web.png old mode 100755 new mode 100644 index 705b1307837..5df223bf6d6 Binary files a/admin/rustls-logo-web.png and b/admin/rustls-logo-web.png differ diff --git a/bogo/Cargo.toml b/bogo/Cargo.toml index cc3f2d3d64d..a654f0bb79a 100644 --- a/bogo/Cargo.toml +++ b/bogo/Cargo.toml @@ -2,14 +2,20 @@ name = "bogo" version = "0.1.0" edition = "2021" +publish = false [dependencies] base64 = { workspace = true } env_logger = { workspace = true } -rustls = { path = "../rustls", features = ["aws_lc_rs", "ring", "tls12"] } -rustls-post-quantum = { path = "../rustls-post-quantum", optional = true } +nix = { version = "0.31", default-features = false, features = ["signal"] } +rustls = { path = "../rustls" } +rustls-aws-lc-rs = { path = "../rustls-aws-lc-rs" } +rustls-ring = { path = "../rustls-ring" } +webpki = { workspace = true } [features] default = [] -post-quantum = ["dep:rustls-post-quantum"] -fips = ["rustls/fips"] +fips = ["rustls-aws-lc-rs/fips"] + +[lints] +workspace = true diff --git a/bogo/check.py b/bogo/check.py index c6fb9cb33ba..062d8ed8030 100644 --- a/bogo/check.py +++ b/bogo/check.py @@ -12,10 +12,9 @@ import fnmatch import sys -config = json.load(open('config.json')) -test_error_set = set(config['TestErrorMap'].keys()) -test_local_error_set = set(config['TestLocalErrorMap'].keys()) -obsolete_disabled_tests = set() +config = json.load(open("config.json")) +test_error_set = set(config["TestErrorMap"].keys()) +test_local_error_set = set(config["TestLocalErrorMap"].keys()) all_tests = set() failing_tests = set() @@ -24,7 +23,7 @@ passed_tests = set() for line in sys.stdin: - m = re.match(r'^(PASSED|UNIMPLEMENTED|FAILED|DISABLED) \((.*)\)$', line.strip()) + m = re.match(r"^(PASSED|UNIMPLEMENTED|FAILED|DISABLED) \((.*)\)$", line.strip()) if m: status, name = m.groups() if name in test_error_set: @@ -32,33 +31,33 @@ if name in test_local_error_set: test_local_error_set.remove(name) all_tests.add(name) - if status == 'FAILED': + if status == "FAILED": failing_tests.add(name) - elif status == 'UNIMPLEMENTED': + elif status == "UNIMPLEMENTED": unimpl_tests.add(name) - elif status == 'DISABLED': + elif status == "DISABLED": disabled_tests.add(name) - elif status == 'PASSED': + elif status == "PASSED": passed_tests.add(name) if disabled_tests: - for disabled_glob in sorted(config['DisabledTests'].keys()): + for disabled_glob in sorted(config["DisabledTests"].keys()): tests_matching_glob = fnmatch.filter(disabled_tests, disabled_glob) if not tests_matching_glob: - print('DisabledTests glob', disabled_glob, 'matches no tests') + print("DisabledTests glob", disabled_glob, "matches no tests") else: # to check DisabledTests, apply patch below to bogo - print('(DisabledTests unchecked)') + print("(DisabledTests unchecked)") -print(len(all_tests), 'total tests') -print(len(passed_tests), 'passed') -print(len(failing_tests), 'tests failing') -print(len(unimpl_tests), 'tests not supported') +print(len(all_tests), "total tests") +print(len(passed_tests), "passed") +print(len(failing_tests), "tests failing") +print(len(unimpl_tests), "tests not supported") if test_error_set: - print('unknown TestErrorMap keys', list(sorted(test_error_set))) + print("unknown TestErrorMap keys", list(sorted(test_error_set))) if test_local_error_set: - print('unknown TestLocalErrorMap keys', list(sorted(test_local_error_set))) + print("unknown TestLocalErrorMap keys", list(sorted(test_local_error_set))) MENTION_DISABLED_TESTS_PATCH = """ diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go diff --git a/bogo/config.json.in b/bogo/config.json.in index 2586a6f303c..a12200ea28c 100644 --- a/bogo/config.json.in +++ b/bogo/config.json.in @@ -1,234 +1,183 @@ { "DisabledTests": { - "SendV2ClientHello-*": "only support TLS1.2", - "*SSL3*": "", - "*TLS1-*": "", - "*-TLS1": "", - "*TLS11-*": "", - "*-TLS11": "", - "ConflictingVersionNegotiation": "", - "CertificateSelection-*": "TODO", - "SendFallbackSCSV": "fallback scsv not implemented", - "ECDSAKeyUsage-*": "TODO: we don't do anything with key usages", - "CheckRecordVersion-*": "we don't look at record version", + "*-ECDSA_SHA1-*": "no ecdsa-sha1", + "*-HintMismatch-*": "hints are a boringssl-specific feature", + "*-P-224-*": "no p224", + "*-QUIC": "quic mode does not work over TLS1.3 framing -- do this in the shim?", + "*-QUIC-*" :"quic mode does not work over TLS1.3 framing -- do this in the shim?", + "*-RSA_PKCS1_SHA256_LEGACY-*": "NYI on our side https://datatracker.ietf.org/doc/draft-ietf-tls-tls13-pkcs1/01/", + "*-RSA_WITH_3DES_EDE_CBC_SHA-*": "no cbc suites", + "*-RSA_WITH_AES_128_CBC_SHA-*": "no rsa kem", + "*-RSA_WITH_AES_128_GCM_SHA256-*": "no rsa kem", + "*-RSA_WITH_AES_256_CBC_SHA-*": "no rsa kem", + "*-RSA_WITH_AES_256_GCM_SHA384-*": "no rsa kem", + "*-Sign-RSA_PKCS1_SHA1-*": "no sha1", + "*-SignDefault-RSA_PKCS1_SHA1-*": "no sha1", + "*-TLS1": "we only support >=TLS1.2", + "*-TLS11": "we only support >=TLS1.2", + "*-VerifyDefault-RSA_PKCS1_SHA1-*": "no sha1", + "*Auth-SHA1-Fallback*": "no sha1 support, so no meaningful need to assume client supports it", + "*CBCPadding*": "no cbc suites", "*DTLS*": "not supported", - "MTU*": "dtls only", + "*EarlyKeyingMaterial-Client-*": "early exporter NYI", + "*Kyber*": "old draft, not implemented", + "*SSL3*": "we only support >=TLS1.2", + "*TLS1-*": "we only support >=TLS1.2", + "*TLS11-*": "we only support >=TLS1.2", + "*_P224_*": "no p224", + "*_WITH_AES_128_CBC_*": "no cbc suites", + "*_WITH_AES_256_CBC_*": "no cbc suites", + "ALPN*SelectEmpty-*": "our API design does not allow for this", + "ALPS-*": "no ALPS support yet", + "BadRSAClientKeyExchange-*": "no rsa kem", + "Basic-Server-RSA-*": "no rsa kem", + "CBCRecordSplitting*": "no cbc suites", + "CertificateCipherMismatch-PSS": "no rsa kem", + "CertificateSelection-*ClientCertificateTypes-*": "no support for CertificateRequest::certificate_types", + "CertificateSelection-*TrustAnchorIDs-*": "no support for trust anchor IDs draft", + "CertificateSelection-Client-SignatureAlgorithmECDSACurve-TLS-TLS12": "ClientCredentialResolver does not receive protocol version", + "CertificateSelection-Server-*": "TODO certificate selection for servers", + "CheckClientCertificateTypes": "we don't check TLS1.2 CertificateRequest.certificate_types", + "CheckClientCertificateTypes": "we don't check TLS1.2 CertificateRequest.certificate_types", + "CheckECDSACurve-TLS12": "we don't have this check", + "CheckLeafCurve": "we don't check this in TLS1.2 (note CheckLeafCurve-TLS13 works)", + "CheckRecordVersion-*": "we don't look at record version", + "Client-RejectJDK11DowngradeRandom": "workarounds for oracle engineering quality", + "Client-VerifyDefault-ECDSA_P521_SHA512-TLS12": "p521-sha512 accepted by default (where supported)", + "Client-VerifyDefault-ECDSA_P521_SHA512-TLS13": "p521-sha512 accepted by default (where supported)", + "Client-VerifyDefault-Ed25519-TLS12": "ed25519 accepted by default", + "Client-VerifyDefault-Ed25519-TLS13": "ed25519 accepted by default", + "ClientHelloPadding": "hello padding extension not implemented", + "ConflictingVersionNegotiation": "expects to negotiate TLS1.1", + "CurveTest-*-P-521-*": "no p521 key exchange", + "DelegatedCredentials-*": "not implemented", "DisableEverything": "not useful", - "CheckLeafCurve": "", - "SendWarningAlerts-*": "", - "Peek-*": "", - "EchoTLS13CompatibilitySessionID": "", - "ClientOCSPCallback*": "ocsp not supported yet", - "ServerOCSPCallback*": "", - "SendUnsolicitedOCSPOnCertificate-TLS13": "we unconditionally request a stapled OCSP response", "DuplicateCertCompressionExt*-TLS12": "RFC8879: if TLS 1.2 or earlier is negotiated, the peers MUST ignore this extension", + "ECDSAKeyUsage-*": "TODO: we don't do anything with key usages", + "EarlyData-*ALPN*-*": "no alpn change in resumed sessions", + "Ed25519DefaultDisable-NoAccept": "not implemented (ed25519 accepted by default)", + "Ed25519DefaultDisable-NoAdvertise": "not implemented (ed25519 advertised by default)", + "EmptyExtensions-ClientHello-TLS12": "we require signature algorithms ext", + "ExtendedMasterSecret-Renego-*": "no renegotiation", + "ExtraClientEncryptedExtension-*": "we don't implement ALPS", + "FallbackSCSV*": "fallback countermeasure not yet implemented", + "GREASE-*": "not implemented", + "IgnoreExtensionsOnIntermediates-TLS13": "assumes SCT support", + "LargeMessage-Reject": "not supported as a runtime option (cert size limit is compile-time constant)", + "MLKEMKeyShareIncludedSecond-X25519MLKEM768": "we only include a share for the first configured group", + "MLKEMKeyShareIncludedThird-X25519MLKEM768": "we only include a share for the first configured group", + "MTU*": "dtls only", + "NPN-*": "no NPN support, never", + "NoCommonCurves": "nothing to fall back to", + "NoCommonSignatureAlgorithms-TLS12-Fallback": "requires TLS_RSA_WITH_AES_128_GCM_SHA256", + "OmitExtensions-ClientHello-TLS12": "we require signature algorithms ext", + "PAKE-*": "no PAKE draft support", + "Peek-*": "no SSL_peek or equivalent", + "QUIC-*": "quic mode does not work over TLS1.3 framing -- do this in the shim?", + "QUICCompatibilityMode": "quic mode does not work over TLS1.3 framing -- do this in the shim?", + "QUICTransportParams-*": "quic mode does not work over TLS1.3 framing -- do this in the shim?", + "RSA-PSS-Large": "no support for 1024-bit RSA keys", + "RSAEphemeralKey": "no rsa kem", + "RSAKeyUsage-*": "no rsa kem", + "Renegotiate-Client-*": "no renegotiation", + "Renegotiate-ForbidAfterHandshake": "no renegotiation", + "Renegotiate-Server-*": "no renegotiation", + "RequireAnyClientCertificate-TLS12": "we don't send an alert in this case", + "Resume-Client-CipherMismatch": "tries to vary to unimplemented CBC-mode cs", + "Resume-Server-OmitPSKsOnSecondClientHello": "not required by RFC", + "RetainOnlySHA256-*": "test for SSL_CTX_set_retain_only_sha256_of_client_certs api", + "SendClientVersion-RSA": "no rsa kem", + "SendFallbackSCSV": "fallback scsv not implemented", + "SendSCTListOnResume-TLS-TLS12": "no sct stapling", + "SendUnsolicitedOCSPOnCertificate-TLS13": "we unconditionally request a stapled OCSP response", + "SendUnsolicitedSCTOnCertificate-TLS13": "SCT stapling not supported", + "SendV2ClientHello-*": "we only support >=TLS1.2", + "Server-JDK11*": "workarounds for oracle engineering quality", + "Server-VerifyDefault-ECDSA_P521_SHA512-TLS12": "p521-sha512 accepted by default (where supported)", + "Server-VerifyDefault-ECDSA_P521_SHA512-TLS13": "p521-sha512 accepted by default (where supported)", + "Server-VerifyDefault-Ed25519-TLS12": "ed25519 accepted by default", + "Server-VerifyDefault-Ed25519-TLS13": "ed25519 accepted by default", + "ServerBogusVersion": "we ignore legacy_version if there's an extension", + "ServerOCSPCallback*": "ocsp not supported for servers", + "Shutdown-Shim-ApplicationData*": "tests boringssl/openssl-specific behaviour, we don't let application data overtake connection shutdown", + "Shutdown-Shim-HelloRequest-*": "no renegotiation", + "Shutdown-Shim-Renegotiate-*": "no renegotiation", + "SignedCertificateTimestampListEmpty-Client-*": "no sct stapling", + "SignedCertificateTimestampListEmptySCT-Client-*": "no sct stapling", + "TLS13-Client-*TicketFlags*": "no flags extension support (on tickets or elsewhere)", + "TrustAnchors-*": "no TrustAnchor draft support", + "TwoMLKEMs": "we only include a share for the first configured group", + "VerifyPreferences-Enforced": "we validate but don't actually implement -verify-prefs", + "VerifyPreferences-NoCommonAlgorithms": "we validate but don't actually implement -verify-prefs", #if defined(RING) "TLS-ECH-*": "*ring* has no HPKE provider for ECH", + "AllExtensions-Client-Permute-ECH-HelloRetryRequest-TLS-TLS13": "requires ECH", #elif defined(AWS_LC_RS) && defined(FIPS) "TLS-ECH-*": "ECH test suites use non-FIPS approved algos", #else - "TLS-ECH-Server*": "ECH server support NYI", "TLS-ECH-Client-NoSupportedConfigs": "we don't fallback to TLS w/o ECH for no supported configs", - "TLS-ECH-Client-SkipInvalidPublicName*": "we allow underscore names, don't fallback on no ECH configs", "TLS-ECH-Client-NoSupportedConfigs-GREASE": "we don't fallback to GREASE for no ECH configs", - "TLS-ECH-Client-TLS12-RejectRetryConfigs": "we disable TLS1.2 w/ ECH", - "TLS-ECH-Client-Reject-NoClientCertificate-TLS12": "we disable TLS1.2 w/ ECH", - "TLS-ECH-Client-Reject-TLS12": "we disable TLS1.2 w/ ECH", - "TLS-ECH-Client-Reject-ResumeInnerSession-TLS12": "we disable TLS1.2 w/ ECH", - "TLS-ECH-GREASE-Client-TLS12-RejectRetryConfigs": "we disable TLS1.2 w/ ECH", "TLS-ECH-Client-Reject-EarlyDataRejected-TLS12": "we disable TLS1.2 w/ ECH", + "TLS-ECH-Client-Reject-NoClientCertificate-TLS12": "we disable TLS1.2 w/ ECH", "TLS-ECH-Client-Reject-NoClientCertificate-TLS12-Async": "we disable TLS1.2 w/ ECH", + "TLS-ECH-Client-Reject-ResumeInnerSession-TLS12": "we disable TLS1.2 w/ ECH", "TLS-ECH-Client-Reject-ResumeInnerSession-TLS13": "assumes no outer GREASE PSK, we send GREASE PSK", + "TLS-ECH-Client-Reject-TLS12": "we disable TLS1.2 w/ ECH", + "TLS-ECH-Client-SkipInvalidPublicName*": "we allow underscore names, don't fallback on no ECH configs", + "TLS-ECH-Client-TLS12-RejectRetryConfigs": "we disable TLS1.2 w/ ECH", + "TLS-ECH-GREASE-Client-TLS12-RejectRetryConfigs": "we disable TLS1.2 w/ ECH", + "TLS-ECH-Server*": "ECH server support NYI", #endif - "ALPS-*": "", - "NPN-*": "", - "NoCheckClientCertificateTypes": "we don't have this check", - "CheckClientCertificateTypes": "^", - "CheckECDSACurve-TLS12": "we don't have this check", - "NoCheckECDSACurve-TLS12": "^", - "CheckClientCertificateTypes": "^", - "*-RSA_PKCS1_SHA256_LEGACY-*": "NYI on our side https://datatracker.ietf.org/doc/draft-ietf-tls-tls13-pkcs1/01/", - "NoCommonSignatureAlgorithms-TLS12-Fallback": "requires TLS_RSA_WITH_AES_128_GCM_SHA256", -#if defined(POST_QUANTUM) - "MLKEMKeyShareIncludedSecond": "we only include a share for the first configured group", - "MLKEMKeyShareIncludedThird": "^", +#if defined(AWS_LC_RS) + "MLKEMKeyShareIncludedSecond-MLKEM1024": "we only include a share for the first configured group", + "MLKEMKeyShareIncludedThird-MLKEM1024": "we only include a share for the first configured group", + "NotJustMLKEMKeyShare-MLKEM1024": "we only include a share for the first configured group", #else "*MLKEM*": "not implemented", #endif - "*Kyber*": "old draft, not implemented", - "ExtraClientEncryptedExtension-*": "we don't implement ALPS", - "Server-JDK11*": "workarounds for oracle engineering quality", - "Client-RejectJDK11DowngradeRandom": "", - "SendUnsolicitedSCTOnCertificate-TLS13": "SCT stapling not supported", - "SignedCertificateTimestampListEmpty-Client-*": "", - "SignedCertificateTimestampListEmptySCT-Client-*": "", - "SendSCTListOnResume-TLS-TLS12": "", - "IgnoreExtensionsOnIntermediates-TLS13": "assumes SCT support", - "CBCRecordSplitting*": "insane ciphersuites", - "*CBCPadding*": "", - "RSAEphemeralKey": "", - "BadRSAClientKeyExchange-*": "", - "SendClientVersion-RSA": "", - "Basic-Server-RSA-*": "", - "RSAKeyUsage-*": "", - "*-RSA_WITH_3DES_EDE_CBC_SHA-*": "", - "*_WITH_AES_128_CBC_*": "", - "*_WITH_AES_256_CBC_*": "", - "*-ECDSA_SHA1-*": "no ecdsa-sha1", - "*-Sign-RSA_PKCS1_SHA1-*": "no sha1", - "*-SignDefault-RSA_PKCS1_SHA1-*": "no sha1", - "*-VerifyDefault-RSA_PKCS1_SHA1-*": "no sha1", - "VerifyPreferences-NoCommonAlgorithms": "we validate but don't actually implement -verify-prefs", - "VerifyPreferences-Enforced": "", - "*_P224_*": "no p224", - "*-P-224-*": "", +#if defined(AWS_LC_RS) + "PostQuantumNotEnabledByDefaultForAServer": "it is", + "PostQuantumNotEnabledByDefaultInClients": "it is", +#endif + #ifdef RING "*-ECDSA_P521_SHA512-*": "no p521 signatures/verification", #endif - "CurveTest-*-P-521-*": "no p521 key exchange", - "GREASE-*": "not implemented", - "LargeMessage-Reject": "", - "DelegatedCredentials-*": "not implemented", - "ExportTrafficSecrets-*": "", - "NoCommonCurves": "nothing to fall back to", - "ClientHelloPadding": "hello padding extension not implemented", - "Resume-Client-CipherMismatch": "tries to vary to unimplemented CBC-mode cs", - "*Auth-SHA1-Fallback*": "", - "RSA-PSS-Large": "", - "*-RSA_WITH_AES_128_GCM_SHA256-*": "", - "*-RSA_WITH_AES_256_GCM_SHA384-*": "", - "*-RSA_WITH_AES_128_CBC_SHA-*": "", - "*-RSA_WITH_AES_256_CBC_SHA-*": "", - "OmitExtensions-ClientHello-TLS12": "", - "EmptyExtensions-ClientHello-TLS12": "", - "Resume-Server-OmitPSKsOnSecondClientHello": "not required by RFC", - "FallbackSCSV*": "fallback countermeasure not yet implemented", - "RequireAnyClientCertificate-TLS12": "we don't send an alert in this case", - "ServerBogusVersion": "we ignore legacy_version if there's an extension", - "Renegotiate-Client-*": "no reneg", - "Shutdown-Shim-Renegotiate-*": "", - "Shutdown-Shim-HelloRequest-*": "", - "Shutdown-Shim-ApplicationData*": "tests boringssl/openssl-specific behaviour, we don't let application data overtake connection shutdown", - "Renegotiate-Server-*": "", - "Renegotiate-ForbidAfterHandshake": "", - "SendHalfHelloRequest-*": "", - "RetainOnlySHA256-*": "", - "ExtendedMasterSecret-Renego-*": "", - "ALPN*SelectEmpty-*": "", - "EarlyData-*ALPN*-*": "no alpn change in resumed sessions", - "*EarlyKeyingMaterial-Client-*": "early exporter NYI", - "QUICTransportParams-*": "Bogo assumes this can be tested over TLS1.3 framing -- could make this work with some effort", - "QUICCompatibilityMode": "", - "Ed25519DefaultDisable-NoAccept": "not implemented (ed25519 accepted by default)", - "Ed25519DefaultDisable-NoAdvertise": "not implemented (ed25519 advertised by default)", - "Server-VerifyDefault-Ed25519-TLS13": "ed25519 accepted by default", - "Server-VerifyDefault-Ed25519-TLS12": "", - "Client-VerifyDefault-Ed25519-TLS13": "", - "Client-VerifyDefault-Ed25519-TLS12": "", - "Server-VerifyDefault-ECDSA_P521_SHA512-TLS13": "p521-sha512 accepted by default (where supported)", - "Server-VerifyDefault-ECDSA_P521_SHA512-TLS12": "", - "Client-VerifyDefault-ECDSA_P521_SHA512-TLS13": "", - "Client-VerifyDefault-ECDSA_P521_SHA512-TLS12": "", - "*-HintMismatch-*": "hints are a boringssl-specific feature", + #if defined(AWS_LC_RS) && defined(FIPS) - "Compliance-fips-202205-TLS-Client-ECDSA_P521_SHA512": "these algorithms are fips approved in aws-lc-rs", - "Compliance-fips-202205-TLS-Server-ECDSA_P521_SHA512": "", - "Compliance-fips-202205-TLS-Client-Ed25519": "", - "Compliance-fips-202205-TLS-Server-Ed25519": "", + "Compliance-fips-202205-TLS-Client-ECDSA_P521_SHA512": "fips approved in aws-lc-rs", + "Compliance-fips-202205-TLS-Client-Ed25519": "fips approved in aws-lc-rs", + "Compliance-fips-202205-TLS-Client-MLKEM": "fips pending in aws-lc-rs", + "Compliance-fips-202205-TLS-Client-X25519MLKEM768": "ml-kem fips pending in aws-lc-rs", + "Compliance-fips-202205-TLS-Server-ECDSA_P521_SHA512": "fips approved in aws-lc-rs", + "Compliance-fips-202205-TLS-Server-Ed25519": "fips approved in aws-lc-rs", + "Compliance-fips-202205-TLS-Server-MLKEM": "fips pending in aws-lc-rs", + "Compliance-fips-202205-TLS-Server-X25519MLKEM768": "ml-kem fips pending in aws-lc-rs", #endif - "*-QUIC-*" :"", - "QUIC-*": "", - "*-QUIC": "" + + "__LAST__": "" }, "ErrorMap": { - ":HTTP_REQUEST:": ":GARBAGE:", - ":HTTPS_PROXY_REQUEST:": ":GARBAGE:", - ":WRONG_VERSION_NUMBER:": ":GARBAGE:", - ":PEER_DID_NOT_RETURN_A_CERTIFICATE:": ":NO_CERTS:", - ":UNEXPECTED_RECORD:": ":UNEXPECTED_MESSAGE:", - ":NO_RENEGOTIATION:": ":UNEXPECTED_MESSAGE:", - ":DIGEST_CHECK_FAILED:": ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:", - ":APPLICATION_DATA_INSTEAD_OF_HANDSHAKE:": ":UNEXPECTED_MESSAGE:", - ":ENCRYPTED_LENGTH_TOO_LONG:": ":GARBAGE:" + ":APPLICATION_DATA_INSTEAD_OF_HANDSHAKE:": [":UNEXPECTED_MESSAGE:"], + ":ENCRYPTED_LENGTH_TOO_LONG:": [":GARBAGE:"], + ":HTTPS_PROXY_REQUEST:": [":GARBAGE:"], + ":HTTP_REQUEST:": [":GARBAGE:"], + ":NO_RENEGOTIATION:": [":UNEXPECTED_MESSAGE:"], + ":PEER_DID_NOT_RETURN_A_CERTIFICATE:": [":NO_CERTS:"], + ":UNEXPECTED_RECORD:": [":UNEXPECTED_MESSAGE:"], + ":WRONG_VERSION_NUMBER:": [":GARBAGE:"] }, "TestErrorMap": { - "EmptyCertificateList": ":NO_CERTS:", - "SendInvalidRecordType": ":GARBAGE:", - "NoSharedCipher": ":HANDSHAKE_FAILURE:", - "NoSharedCipher-TLS13": ":HANDSHAKE_FAILURE:", - "InvalidECDHPoint-Client": ":PEER_MISBEHAVIOUR:", - "InvalidECDHPoint-Server": ":PEER_MISBEHAVIOUR:", - "TrailingMessageData-ClientHello-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-ServerHello-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-ServerCertificate-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-CertificateRequest-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-ClientCertificate-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-CertificateVerify-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-NewSessionTicket-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-ServerHelloDone-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-ServerKeyExchange-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-ClientKeyExchange-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-CertificateStatus-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-TLS13-ClientHello-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-TLS13-ServerHello-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-TLS13-EncryptedExtensions-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-TLS13-CertificateRequest-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-TLS13-ServerCertificate-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-TLS13-ServerCertificateVerify-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-TLS13-ServerFinished-TLS": ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:", - "TrailingMessageData-TLS13-ClientCertificate-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-TLS13-ClientCertificateVerify-TLS": ":BAD_HANDSHAKE_MSG:", - "TrailingMessageData-TLS13-EndOfEarlyData-TLS": ":BAD_HANDSHAKE_MSG:", - "Server-NonEmptyEndOfEarlyData-TLS13": ":BAD_HANDSHAKE_MSG:", - "MissingKeyShare-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "MissingKeyShare-Server-TLS13": ":INCOMPATIBLE:", - "EmptyEncryptedExtensions-TLS13": ":BAD_HANDSHAKE_MSG:", - "NoSupportedCurves": ":INCOMPATIBLE:", - "VersionTooLow": ":INCOMPATIBLE:", - "ALPNClient-RejectUnknown-TLS-TLS12": ":PEER_MISBEHAVIOUR:", - "ALPNClient-RejectUnknown-TLS-TLS13": ":PEER_MISBEHAVIOUR:", - "ALPNClient-EmptyProtocolName-TLS-TLS12": ":PEER_MISBEHAVIOUR:", - "ALPNClient-EmptyProtocolName-TLS-TLS13": ":PEER_MISBEHAVIOUR:", - "ALPNServer-EmptyProtocolName-TLS-TLS12": ":PEER_MISBEHAVIOUR:", - "ALPNServer-EmptyProtocolName-TLS-TLS13": ":PEER_MISBEHAVIOUR:", - "Verify-ClientAuth-SignatureType": ":BAD_SIGNATURE:", - "Verify-ServerAuth-SignatureType-TLS13": ":BAD_SIGNATURE:", - "Verify-ClientAuth-SignatureType-TLS13": ":BAD_SIGNATURE:", - "UnofferedExtension-Client": ":PEER_MISBEHAVIOUR:", - "UnknownExtension-Client": ":PEER_MISBEHAVIOUR:", - "KeyUpdate-InvalidRequestMode": ":BAD_HANDSHAKE_MSG:", - "ExtraCompressionMethods-TLS13": ":PEER_MISBEHAVIOUR:", - "NoNullCompression-TLS12": ":INCOMPATIBLE:", - "NoNullCompression-TLS13": ":INCOMPATIBLE:", - "InvalidCompressionMethod": ":PEER_MISBEHAVIOUR:", - "TLS13-InvalidCompressionMethod": ":PEER_MISBEHAVIOUR:", - "TLS13-WrongOuterRecord": ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:", - "TLS-TLS13-ECDHE_ECDSA_WITH_AES_128_GCM_SHA256-server": ":INCOMPATIBLE:", - "TLS-TLS13-ECDHE_ECDSA_WITH_AES_128_GCM_SHA256-client": ":PEER_MISBEHAVIOUR:", - "TLS-TLS13-ECDHE_ECDSA_WITH_AES_256_GCM_SHA384-server": ":INCOMPATIBLE:", - "TLS-TLS13-ECDHE_ECDSA_WITH_AES_256_GCM_SHA384-client": ":PEER_MISBEHAVIOUR:", - "TLS-TLS13-ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256-server": ":INCOMPATIBLE:", - "TLS-TLS13-ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256-client": ":PEER_MISBEHAVIOUR:", - "TLS-TLS13-ECDHE_RSA_WITH_AES_128_GCM_SHA256-server": ":INCOMPATIBLE:", - "TLS-TLS13-ECDHE_RSA_WITH_AES_128_GCM_SHA256-client": ":PEER_MISBEHAVIOUR:", - "TLS-TLS13-ECDHE_RSA_WITH_AES_256_GCM_SHA384-server": ":INCOMPATIBLE:", - "TLS-TLS13-ECDHE_RSA_WITH_AES_256_GCM_SHA384-client": ":PEER_MISBEHAVIOUR:", - "TLS-TLS13-ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256-server": ":INCOMPATIBLE:", - "TLS-TLS13-ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256-client": ":PEER_MISBEHAVIOUR:", - "TLS-TLS12-CHACHA20_POLY1305_SHA256-server": ":INCOMPATIBLE:", - "TLS-TLS12-CHACHA20_POLY1305_SHA256-client": ":PEER_MISBEHAVIOUR:", - "TLS-TLS12-AES_128_GCM_SHA256-server": ":INCOMPATIBLE:", - "TLS-TLS12-AES_128_GCM_SHA256-client": ":PEER_MISBEHAVIOUR:", - "TLS-TLS12-AES_256_GCM_SHA384-server": ":INCOMPATIBLE:", - "TLS-TLS12-AES_256_GCM_SHA384-client": ":PEER_MISBEHAVIOUR:", - "SkipHelloRetryRequest-TLS13": ":PEER_MISBEHAVIOUR:", - "NoSupportedVersions": ":INCOMPATIBLE:", - "Server-Sign-RSA_PKCS1_SHA256-TLS13": ":INCOMPATIBLE:", - "Server-Sign-RSA_PKCS1_SHA384-TLS13": ":INCOMPATIBLE:", - "Server-Sign-RSA_PKCS1_SHA512-TLS13": ":INCOMPATIBLE:", - "Server-SignDefault-RSA_PKCS1_SHA256-TLS13": ":INCOMPATIBLE:", - "Server-SignDefault-RSA_PKCS1_SHA384-TLS13": ":INCOMPATIBLE:", - "Server-SignDefault-RSA_PKCS1_SHA512-TLS13": ":INCOMPATIBLE:", + "ALPNClient-EmptyProtocolName-TLS-TLS12": ":DECODE_ERROR:", + "ALPNClient-EmptyProtocolName-TLS-TLS13": ":DECODE_ERROR:", + "ALPNServer-EmptyProtocolName-TLS-TLS12": ":DECODE_ERROR:", + "ALPNServer-EmptyProtocolName-TLS-TLS13": ":DECODE_ERROR:", + "AppDataBeforeTLS13KeyChange": ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:", + "AppDataBeforeTLS13KeyChange-Empty": ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:", + "CertCompressionTooLargeClient-TLS13": ":GARBAGE:", + "CertificateCipherMismatch-ECDSA": ":WRONG_SIGNATURE_TYPE:", + "CertificateCipherMismatch-Ed25519": ":WRONG_SIGNATURE_TYPE:", + "CertificateCipherMismatch-RSA": ":WRONG_SIGNATURE_TYPE:", "Client-Sign-RSA_PKCS1_SHA256-TLS13": ":INCOMPATIBLE:", "Client-Sign-RSA_PKCS1_SHA384-TLS13": ":INCOMPATIBLE:", "Client-Sign-RSA_PKCS1_SHA512-TLS13": ":INCOMPATIBLE:", @@ -236,154 +185,122 @@ "Client-SignDefault-RSA_PKCS1_SHA384-TLS13": ":INCOMPATIBLE:", "Client-SignDefault-RSA_PKCS1_SHA512-TLS13": ":INCOMPATIBLE:", "Client-TLS13-NoSign-RSA_PKCS1_MD5_SHA1": ":INCOMPATIBLE:", - "Server-TLS12-NoSign-RSA_PKCS1_MD5_SHA1": ":INCOMPATIBLE:", - "Server-TLS13-NoSign-RSA_PKCS1_MD5_SHA1": ":INCOMPATIBLE:", - "ClientAuth-NoFallback-RSA": ":BAD_HANDSHAKE_MSG:", + "Client-TooLongSessionID": ":BAD_HANDSHAKE_MSG:", "ClientAuth-NoFallback-ECDSA": ":BAD_HANDSHAKE_MSG:", + "ClientAuth-NoFallback-RSA": ":BAD_HANDSHAKE_MSG:", "ClientAuth-NoFallback-TLS13": ":BAD_HANDSHAKE_MSG:", - "ServerAuth-NoFallback-TLS13": ":INCOMPATIBLE:", - "SecondClientHelloWrongCurve-TLS13": ":PEER_MISBEHAVIOUR:", - "SecondClientHelloMissingKeyShare-TLS13": ":INCOMPATIBLE:", - "Resume-Server-BinderWrongLength-SecondBinder": ":PEER_MISBEHAVIOUR:", - "Resume-Server-NoPSKBinder-SecondBinder": ":PEER_MISBEHAVIOUR:", - "Resume-Server-ExtraPSKBinder-SecondBinder": ":PEER_MISBEHAVIOUR:", - "Resume-Server-ExtraIdentityNoBinder-SecondBinder": ":PEER_MISBEHAVIOUR:", - "Resume-Server-InvalidPSKBinder-SecondBinder": ":PEER_MISBEHAVIOUR:", - "Resume-Server-PSKBinderFirstExtension-SecondBinder": ":PEER_MISBEHAVIOUR:", - "Resume-Server-BinderWrongLength": ":PEER_MISBEHAVIOUR:", - "Resume-Server-NoPSKBinder": ":PEER_MISBEHAVIOUR:", - "Resume-Server-ExtraPSKBinder": ":PEER_MISBEHAVIOUR:", - "Resume-Server-ExtraIdentityNoBinder": ":PEER_MISBEHAVIOUR:", - "Resume-Server-InvalidPSKBinder": ":PEER_MISBEHAVIOUR:", - "Resume-Server-PSKBinderFirstExtension": ":PEER_MISBEHAVIOUR:", - "Resume-Client-PRFMismatch-TLS13": ":PEER_MISBEHAVIOUR:", - "Resume-Client-Mismatch-TLS12-TLS13-TLS": ":PEER_MISBEHAVIOUR:", - "Resume-Client-Mismatch-TLS13-TLS12-TLS": ":WRONG_CIPHER_RETURNED:", - "NoSupportedCurves-TLS13": ":INCOMPATIBLE:", - "BadECDHECurve-TLS13": ":PEER_MISBEHAVIOUR:", - "InvalidECDHPoint-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "InvalidECDHPoint-Server-TLS13": ":PEER_MISBEHAVIOUR:", - "InvalidPSKIdentity-TLS13": ":PEER_MISBEHAVIOUR:", - "AlwaysSelectPSKIdentity-TLS13": ":PEER_MISBEHAVIOUR:", - "TrailingKeyShareData-TLS13": ":BAD_HANDSHAKE_MSG:", - "HelloRetryRequestCurveMismatch-TLS13": ":PEER_MISBEHAVIOUR:", + "DisabledCurve-HelloRetryRequest-TLS13": ":ILLEGAL_HELLO_RETRY_REQUEST_WITH_UNOFFERED_GROUP:", + "EarlyData-SkipEndOfEarlyData-TLS13": ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:", + "EmptyCertificateList": ":NO_CERTS:", + "EmptyEncryptedExtensions-TLS13": ":BAD_HANDSHAKE_MSG:", + "ExtendedMasterSecret-NoToYes-Client": ":RESUMED_SESSION_WITH_VARIED_EMS:", + "ExtendedMasterSecret-YesToNo-Client": ":RESUMED_SESSION_WITH_VARIED_EMS:", + "ExtendedMasterSecret-YesToNo-Server": ":RESUMED_SESSION_WITH_VARIED_EMS:", + "HelloRetryRequest-CipherChange-TLS13": ":SELECTED_DIFFERENT_CIPHERSUITE_AFTER_RETRY:", + "HelloRetryRequest-EmptyCookie-TLS13": ":DECODE_ERROR:", "HelloRetryRequestVersionMismatch-TLS13": ":INCOMPATIBLE:", - "HelloRetryRequest-DuplicateCookie-TLS13": ":PEER_MISBEHAVIOUR:", - "HelloRetryRequest-DuplicateCurve-TLS13": ":PEER_MISBEHAVIOUR:", - "UnknownUnencryptedExtension-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "UnexpectedUnencryptedExtension-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "UnofferedExtension-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "RenegotiationInfo-Forbidden-TLS13": ":PEER_MISBEHAVIOUR:", - "UnknownExtension-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "RequestContextInHandshake-TLS13": ":BAD_HANDSHAKE_MSG:", - "UnnecessaryHelloRetryRequest-TLS13": ":PEER_MISBEHAVIOUR:", - "UnknownCurve-HelloRetryRequest-TLS13": ":PEER_MISBEHAVIOUR:", - "DisabledCurve-HelloRetryRequest-TLS13": ":PEER_MISBEHAVIOUR:", - "HelloRetryRequest-Empty-TLS13": ":PEER_MISBEHAVIOUR:", - "HelloRetryRequest-EmptyCookie-TLS13": ":PEER_MISBEHAVIOUR:", - "TrailingDataWithFinished-Client-TLS12": ":PEER_MISBEHAVIOUR:", - "TrailingDataWithFinished-Resume-Client-TLS12": ":PEER_MISBEHAVIOUR:", - "TrailingDataWithFinished-Server-TLS12": ":PEER_MISBEHAVIOUR:", - "TrailingDataWithFinished-Resume-Server-TLS12": ":PEER_MISBEHAVIOUR:", - "TrailingDataWithFinished-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "TrailingDataWithFinished-Server-TLS13": ":PEER_MISBEHAVIOUR:", - "TrailingDataWithFinished-Resume-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "TrailingDataWithFinished-Resume-Server-TLS13": ":PEER_MISBEHAVIOUR:", - "PartialSecondClientHelloAfterFirst": ":PEER_MISBEHAVIOUR:", - "PartialClientFinishedWithSecondClientHello": ":PEER_MISBEHAVIOUR:", - "PartialClientFinishedWithClientHello-TLS12-Resume": ":PEER_MISBEHAVIOUR:", - "PartialServerHelloWithHelloRetryRequest": ":PEER_MISBEHAVIOUR:", - "PartialNewSessionTicketWithServerHelloDone": ":PEER_MISBEHAVIOUR:", - "PartialEndOfEarlyDataWithClientHello": ":PEER_MISBEHAVIOUR:", - "FragmentAcrossChangeCipherSpec-Client": ":PEER_MISBEHAVIOUR:", - "FragmentAcrossChangeCipherSpec-Server-Packed": ":PEER_MISBEHAVIOUR:", - "FragmentAcrossChangeCipherSpec-Client-Resume-Packed": ":PEER_MISBEHAVIOUR:", - "FragmentAcrossChangeCipherSpec-Client-Resume": ":PEER_MISBEHAVIOUR:", - "FragmentAcrossChangeCipherSpec-Server-Resume": ":PEER_MISBEHAVIOUR:", - "FragmentAcrossChangeCipherSpec-Server": ":PEER_MISBEHAVIOUR:", - "FragmentAcrossChangeCipherSpec-Client-Packed": ":PEER_MISBEHAVIOUR:", - "FragmentAcrossChangeCipherSpec-Server-Resume-Packed": ":PEER_MISBEHAVIOUR:", - "PartialFinishedWithServerHelloDone": ":PEER_MISBEHAVIOUR:", - "UnsupportedCurve-ServerHello-TLS13": ":PEER_MISBEHAVIOUR:", - "PartialClientKeyExchangeWithClientHello": ":PEER_MISBEHAVIOUR:", + "KeyUpdate-InvalidRequestMode": ":BAD_HANDSHAKE_MSG:", "MinimumVersion-Client-TLS13-TLS12-TLS": ":INCOMPATIBLE:", "MinimumVersion-Client2-TLS13-TLS12-TLS": ":INCOMPATIBLE:", "MinimumVersion-Server-TLS13-TLS12-TLS": ":INCOMPATIBLE:", "MinimumVersion-Server2-TLS13-TLS12-TLS": ":INCOMPATIBLE:", - "DuplicateKeyShares-TLS13": ":PEER_MISBEHAVIOUR:", - "PartialEncryptedExtensionsWithServerHello": ":PEER_MISBEHAVIOUR:", - "PartialClientFinishedWithClientHello": ":PEER_MISBEHAVIOUR:", - "PointFormat-EncryptedExtensions-TLS13": ":PEER_MISBEHAVIOUR:", - "Ticket-Forbidden-TLS13": ":PEER_MISBEHAVIOUR:", - "PointFormat-Server-MissingUncompressed": ":INCOMPATIBLE:", + "MissingKeyShare-Server-TLS13": ":INCOMPATIBLE:", "MissingSignatureAlgorithmsInCertificateRequest-TLS13": ":INCOMPATIBLE:", - "NegotiatePSKResumption-TLS13": ":PEER_MISBEHAVIOUR:", - "PointFormat-Client-MissingUncompressed": ":PEER_MISBEHAVIOUR:", - "UnsolicitedServerNameAck-TLS-TLS12": ":PEER_MISBEHAVIOUR:", - "UnsolicitedServerNameAck-TLS-TLS13": ":PEER_MISBEHAVIOUR:", - "TicketSessionIDLength-33-TLS-TLS12": ":BAD_HANDSHAKE_MSG:", - "Server-TooLongSessionID-TLS13": ":BAD_HANDSHAKE_MSG:", - "Server-TooLongSessionID-TLS12": ":BAD_HANDSHAKE_MSG:", - "Client-TooLongSessionID": ":BAD_HANDSHAKE_MSG:", - "SendUnknownExtensionOnCertificate-TLS13": ":PEER_MISBEHAVIOUR:", - "SendDuplicateExtensionsOnCerts-TLS13": ":PEER_MISBEHAVIOUR:", - "EMS-Forbidden-TLS13": ":PEER_MISBEHAVIOUR:", - "Unclean-Shutdown": ":CLOSE_WITHOUT_CLOSE_NOTIFY:", - "SendExtensionOnClientCertificate-TLS13": ":PEER_MISBEHAVIOUR:", + "NoNullCompression-TLS12": ":INCOMPATIBLE:", + "NoNullCompression-TLS13": ":INCOMPATIBLE:", + "NoSharedCipher": ":HANDSHAKE_FAILURE:", + "NoSharedCipher-TLS13": ":HANDSHAKE_FAILURE:", + "NoSupportedCurves": ":INCOMPATIBLE:", + "NoSupportedCurves-TLS13": ":INCOMPATIBLE:", + "NoSupportedVersions": ":INCOMPATIBLE:", + "PointFormat-Client-MissingUncompressed": ":SERVER_HELLO_MUST_OFFER_UNCOMPRESSED_EC_POINTS:", + "PointFormat-Server-MissingUncompressed": ":INCOMPATIBLE:", + "RejectPSSKeyType-Client-TLS12": ":WRONG_SIGNATURE_TYPE:", + "RejectPSSKeyType-Client-TLS13": ":WRONG_SIGNATURE_TYPE:", + "RejectPSSKeyType-Server-TLS12": ":WRONG_SIGNATURE_TYPE:", + "RejectPSSKeyType-Server-TLS13": ":WRONG_SIGNATURE_TYPE:", + "RenegotiationInfo-Forbidden-TLS13": ":UNEXPECTED_EXTENSION:", + "RequestContextInHandshake-TLS13": ":BAD_HANDSHAKE_MSG:", + "Resume-Client-Mismatch-TLS13-TLS12-TLS": ":WRONG_CIPHER_RETURNED:", + "SecondClientHelloMissingKeyShare-TLS13": ":INCOMPATIBLE:", + "SecondServerHelloNoVersion-TLS13": ":WRONG_CIPHER_RETURNED:", + "SecondServerHelloWrongVersion-TLS13": ":INCOMPATIBLE:", "SendBogusAlertType": ":BAD_ALERT:", - "TLS13-HRR-InvalidCompressionMethod": ":BAD_HANDSHAKE_MSG:", - "CertificateCipherMismatch-RSA": ":WRONG_SIGNATURE_TYPE:", - "CertificateCipherMismatch-ECDSA": ":WRONG_SIGNATURE_TYPE:", - "CertificateCipherMismatch-Ed25519": ":WRONG_SIGNATURE_TYPE:", - "ServerCipherFilter-RSA": ":INCOMPATIBLE:", + "SendInvalidRecordType": ":GARBAGE:", + "Server-NonEmptyEndOfEarlyData-TLS13": ":BAD_HANDSHAKE_MSG:", + "Server-Sign-RSA_PKCS1_SHA256-TLS13": ":INCOMPATIBLE:", + "Server-Sign-RSA_PKCS1_SHA384-TLS13": ":INCOMPATIBLE:", + "Server-Sign-RSA_PKCS1_SHA512-TLS13": ":INCOMPATIBLE:", + "Server-SignDefault-RSA_PKCS1_SHA256-TLS13": ":INCOMPATIBLE:", + "Server-SignDefault-RSA_PKCS1_SHA384-TLS13": ":INCOMPATIBLE:", + "Server-SignDefault-RSA_PKCS1_SHA512-TLS13": ":INCOMPATIBLE:", + "Server-TLS12-NoSign-RSA_PKCS1_MD5_SHA1": ":INCOMPATIBLE:", + "Server-TLS13-NoSign-RSA_PKCS1_MD5_SHA1": ":INCOMPATIBLE:", + "Server-TooLongSessionID-TLS12": ":BAD_HANDSHAKE_MSG:", + "Server-TooLongSessionID-TLS13": ":BAD_HANDSHAKE_MSG:", + "ServerAuth-NoFallback-TLS13": ":INCOMPATIBLE:", "ServerCipherFilter-ECDSA": ":INCOMPATIBLE:", "ServerCipherFilter-Ed25519": ":INCOMPATIBLE:", - "TLS13-OnlyPadding": ":PEER_MISBEHAVIOUR:", - "TLS13-EmptyRecords": ":PEER_MISBEHAVIOUR:", - "TLS13-DuplicateTicketEarlyDataSupport": ":PEER_MISBEHAVIOUR:", - "SupportedVersionSelection-TLS12": ":PEER_MISBEHAVIOUR:", - "HelloRetryRequest-CipherChange-TLS13": ":PEER_MISBEHAVIOUR:", - "CurveTest-Client-Compressed-P-256-TLS12": ":PEER_MISBEHAVIOUR:", - "CurveTest-Server-Compressed-P-256-TLS12": ":PEER_MISBEHAVIOUR:", - "CurveTest-Client-Compressed-P-256-TLS13": ":PEER_MISBEHAVIOUR:", - "CurveTest-Server-Compressed-P-256-TLS13": ":PEER_MISBEHAVIOUR:", - "CurveTest-Client-Compressed-P-384-TLS12": ":PEER_MISBEHAVIOUR:", - "CurveTest-Server-Compressed-P-384-TLS12": ":PEER_MISBEHAVIOUR:", - "CurveTest-Client-Compressed-P-384-TLS13": ":PEER_MISBEHAVIOUR:", - "CurveTest-Server-Compressed-P-384-TLS13": ":PEER_MISBEHAVIOUR:", - "ExtendedMasterSecret-NoToYes-Client": ":PEER_MISBEHAVIOUR:", - "ExtendedMasterSecret-YesToNo-Server": ":PEER_MISBEHAVIOUR:", - "ExtendedMasterSecret-YesToNo-Client": ":PEER_MISBEHAVIOUR:", - "ServerAcceptsEarlyDataOnHRR-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "Downgrade-TLS12-Client": ":PEER_MISBEHAVIOUR:", - "Downgrade-TLS10-Client": ":HANDSHAKE_FAILURE:", - "Downgrade-TLS10-Server": ":INCOMPATIBLE:", - "ECDSACurveMismatch-Verify-TLS13": ":BAD_SIGNATURE:", - "SecondServerHelloNoVersion-TLS13": ":PEER_MISBEHAVIOUR:", - "SecondServerHelloWrongVersion-TLS13": ":INCOMPATIBLE:", - "TooManyChangeCipherSpec-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "TooManyChangeCipherSpec-Server-TLS13": ":PEER_MISBEHAVIOUR:", - "EarlyData-CipherMismatch-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "EarlyDataWithoutResume-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "EarlyDataVersionDowngrade-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "EarlyData-SkipEndOfEarlyData-TLS13": ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:", - "CertCompressionTooLargeClient-TLS13": ":GARBAGE:", - "SkipEarlyData-Interleaved-TLS13": ":PEER_MISBEHAVIOUR:", - "SkipEarlyData-TooMuchData-TLS13": ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:", + "ServerCipherFilter-RSA": ":INCOMPATIBLE:", "SkipEarlyData-HRR-FatalAlert-TLS13": ":HANDSHAKE_FAILURE:", - "SkipEarlyData-HRR-Interleaved-TLS13": ":PEER_MISBEHAVIOUR:", "SkipEarlyData-HRR-TooMuchData-TLS13": ":UNEXPECTED_MESSAGE:", - "SkipEarlyData-SecondClientHelloEarlyData-TLS13": ":PEER_MISBEHAVIOUR:" + "SkipEarlyData-TooMuchData-TLS13": ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:", + "SupportedVersionSelection-TLS12": ":SELECTED_TLS12_USING_TLS13_VERSION_EXTENSION:", + "TicketSessionIDLength-33-TLS-TLS12": ":BAD_HANDSHAKE_MSG:", + "TLS-TLS12-AES_128_GCM_SHA256-server": ":INCOMPATIBLE:", + "TLS-TLS12-AES_256_GCM_SHA384-server": ":INCOMPATIBLE:", + "TLS-TLS12-CHACHA20_POLY1305_SHA256-server": ":INCOMPATIBLE:", + "TLS13-HRR-InvalidCompressionMethod": ":BAD_HANDSHAKE_MSG:", + "TLS13-InvalidCompressionMethod": ":UNSUPPORTED_COMPRESSION_ALGORITHM:", + "TLS13-WrongOuterRecord-TLS": ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:", + "TooManyChangeCipherSpec-Client-TLS13": ":ILLEGAL_MIDDLEBOX_CHANGE_CIPHER_SPEC:", + "TooManyChangeCipherSpec-Server-TLS13": ":ILLEGAL_MIDDLEBOX_CHANGE_CIPHER_SPEC:", + "TrailingKeyShareData-TLS13": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-CertificateRequest-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-CertificateStatus-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-CertificateVerify-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-ClientCertificate-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-ClientHello-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-ClientKeyExchange-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-NewSessionTicket-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-ServerCertificate-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-ServerHello-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-ServerHelloDone-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-ServerKeyExchange-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-TLS13-CertificateRequest-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-TLS13-ClientCertificate-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-TLS13-ClientCertificateVerify-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-TLS13-ClientHello-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-TLS13-EncryptedExtensions-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-TLS13-EndOfEarlyData-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-TLS13-ServerCertificate-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-TLS13-ServerCertificateVerify-TLS": ":BAD_HANDSHAKE_MSG:", + "TrailingMessageData-TLS13-ServerHello-TLS": ":BAD_HANDSHAKE_MSG:", + "Unclean-Shutdown": ":CLOSE_WITHOUT_CLOSE_NOTIFY:", + "UnknownCurve-HelloRetryRequest-TLS13": ":ILLEGAL_HELLO_RETRY_REQUEST_WITH_UNOFFERED_GROUP:", + "UnnecessaryHelloRetryRequest-TLS13": ":ILLEGAL_HELLO_RETRY_REQUEST_WITH_OFFERED_GROUP:", + "VersionTooLow": ":INCOMPATIBLE:" }, "TestLocalErrorMap": { - "GarbageCertificate-Server-TLS12": "remote error: bad certificate", - "GarbageCertificate-Server-TLS13": "remote error: bad certificate", + "C__": "no alerts sent for the following tests... (artificial error created in the shim)", + "CertificateSelection-Client-CheckIssuer-MatchNone-TLS-TLS12": "", + "CertificateSelection-Client-CheckIssuer-MatchNone-TLS-TLS13": "", + "CertificateSelection-Client-CheckIssuerFallback-MatchNone-TLS-TLS12": "", + "CertificateSelection-Client-CheckIssuerFallback-MatchNone-TLS-TLS13": "", + "CertificateSelection-Client-SignatureAlgorithm-MatchNone-TLS-TLS12": "", + "CertificateSelection-Client-SignatureAlgorithm-MatchNone-TLS-TLS13": "", + "CertificateSelection-Client-SignatureAlgorithmECDSACurve-MatchNone-TLS-TLS13": "", + "CertificateSelection-Client-SignatureAlgorithmKeyPrefs-MatchNone-TLS-TLS12": "", + "CertificateSelection-Client-SignatureAlgorithmKeyPrefs-MatchNone-TLS-TLS13": "", + "D__": "", + "DuplicateExtensionClient-TLS-TLS12": "remote error: illegal parameter", + "DuplicateExtensionClient-TLS-TLS13": "remote error: illegal parameter", + "DuplicateExtensionServer-TLS-TLS12": "remote error: illegal parameter", + "DuplicateExtensionServer-TLS-TLS13": "remote error: illegal parameter", "GarbageCertificate-Client-TLS12": "remote error: bad certificate", "GarbageCertificate-Client-TLS13": "remote error: bad certificate", - "Downgrade-TLS10-Client": "tls: no cipher suite supported by both client and server", - "Downgrade-TLS10-Server": "remote error: protocol version not supported", - "TrailingDataWithFinished-Client-TLS13": "local error: bad record MAC", - "TrailingDataWithFinished-Resume-Client-TLS13": "local error: bad record MAC", + "GarbageCertificate-Server-TLS12": "remote error: bad certificate", + "GarbageCertificate-Server-TLS13": "remote error: bad certificate", "SkipEarlyData-SecondClientHelloEarlyData-TLS13": "remote error: illegal parameter" }, "HalfRTTTickets": 0 diff --git a/bogo/fetch-and-build b/bogo/fetch-and-build index 67e2214abb8..f852e34e96e 100755 --- a/bogo/fetch-and-build +++ b/bogo/fetch-and-build @@ -15,7 +15,7 @@ util/testresult EOF # fix on a tested point of rustls-testing branch -COMMIT=018edfaaaeea43bf35a16e9f7ba24510c0c003bb +COMMIT=b6a09c71d983cf1ad7b729a7b1b287064bc6fae0 git fetch --depth=1 https://github.com/rustls/boringssl.git $COMMIT git checkout $COMMIT (cd ssl/test/runner && go test -c) diff --git a/bogo/runme b/bogo/runme index 1a79f77a5d4..f0bba108bf9 100755 --- a/bogo/runme +++ b/bogo/runme @@ -12,16 +12,13 @@ case ${BOGO_SHIM_PROVIDER:-aws-lc-rs} in ;; aws-lc-rs) cpp -P -DAWS_LC_RS config.json.in > config.json + export AWS_LC_SYS_NO_JITTER_ENTROPY=1 cargo run -- -print-rustls-provider ;; aws-lc-rs-fips) cpp -P -DAWS_LC_RS -DFIPS config.json.in > config.json cargo run --features fips -- -print-rustls-provider ;; - post-quantum) - cpp -P -DAWS_LC_RS -DPOST_QUANTUM config.json.in > config.json - cargo run --features post-quantum -- -print-rustls-provider - ;; existing) ;; *) @@ -38,10 +35,14 @@ fi # Best effort on OS-X case $OSTYPE in darwin*) set +e ;; esac +# Set default timeout unless -wait-for-debugger flag is present. +# When this flag is set the shim will pause itself, and the runner will +# print the PID to attach to in order to continue. +runner_args="-pipe -allow-unimplemented" +[[ ! "$*" =~ "-wait-for-debugger" ]] && runner_args+=" -test.timeout 60s" + ( cd bogo/ssl/test/runner && ./runner.test -shim-path ../../../../../target/debug/bogo \ -shim-config ../../../../config.json \ - -pipe \ - -allow-unimplemented \ - -test.timeout 60s \ + $runner_args \ "$@") # you can pass in `-test "Foo;Bar"` to run specific tests true diff --git a/bogo/src/main.rs b/bogo/src/main.rs index 8b8656a3eda..a97cfb9f27d 100644 --- a/bogo/src/main.rs +++ b/bogo/src/main.rs @@ -4,1359 +4,730 @@ // https://boringssl.googlesource.com/boringssl/+/master/ssl/test // -use std::fmt::{Debug, Formatter}; +use core::any::Any; +use core::fmt::{Debug, Formatter}; +use core::hash::Hasher; +use std::borrow::Cow; use std::io::{self, Read, Write}; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::{env, net, process, thread, time}; -use base64::prelude::{Engine, BASE64_STANDARD}; -use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; +use base64::prelude::{BASE64_STANDARD, Engine}; +#[cfg(unix)] +use nix::sys::signal::{self, Signal}; +#[cfg(unix)] +use nix::unistd::Pid; +use rustls::client::danger::{ + HandshakeSignatureValid, PeerVerified, ServerIdentity, ServerVerifier, +}; use rustls::client::{ - ClientConfig, ClientConnection, EchConfig, EchGreaseConfig, EchMode, EchStatus, Resumption, + self, ClientConfig, ClientConnection, ClientSessionKey, CredentialRequest, EchConfig, + EchGreaseConfig, EchMode, EchStatus, Resumption, Tls12Resumption, Tls13Session, WebPkiServerVerifier, }; -use rustls::crypto::aws_lc_rs::hpke; use rustls::crypto::hpke::{Hpke, HpkePublicKey}; -use rustls::crypto::{aws_lc_rs, ring, CryptoProvider}; -use rustls::internal::msgs::codec::{Codec, Reader}; -use rustls::internal::msgs::handshake::EchConfigPayload; -use rustls::internal::msgs::persist::ServerSessionValue; +use rustls::crypto::kx::NamedGroup; +use rustls::crypto::{ + Credentials, CryptoProvider, Identity, SelectedCredential, SignatureScheme, Signer, SigningKey, + SingleCredential, +}; +use rustls::enums::{ + ApplicationProtocol, CertificateCompressionAlgorithm, CertificateType, ProtocolVersion, +}; +use rustls::error::{ + AlertDescription, CertificateError, Error, InvalidMessage, PeerIncompatible, PeerMisbehaved, +}; use rustls::pki_types::pem::PemObject; -use rustls::pki_types::{CertificateDer, EchConfigListBytes, PrivateKeyDer, ServerName, UnixTime}; -use rustls::server::danger::{ClientCertVerified, ClientCertVerifier}; -use rustls::server::{ - ClientHello, ProducesTickets, ServerConfig, ServerConnection, WebPkiClientVerifier, +use rustls::pki_types::{ + CertificateDer, EchConfigListBytes, PrivateKeyDer, ServerName, SubjectPublicKeyInfoDer, }; -use rustls::{ - client, compress, server, sign, version, AlertDescription, CertificateCompressionAlgorithm, - CertificateError, Connection, DigitallySignedStruct, DistinguishedName, Error, HandshakeKind, - InvalidMessage, NamedGroup, PeerIncompatible, PeerMisbehaved, ProtocolVersion, RootCertStore, - Side, SignatureAlgorithm, SignatureScheme, SupportedProtocolVersion, +use rustls::server::danger::{ClientIdentity, ClientVerifier, SignatureVerificationInput}; +use rustls::server::{ + self, ClientHello, PreferClientOrder, PreferServerOrder, ServerConfig, ServerConnection, + ServerSessionKey, WebPkiClientVerifier, }; +use rustls::{Connection, DistinguishedName, HandshakeKind, RootCertStore, compress}; +use rustls_aws_lc_rs::hpke; -static BOGO_NACK: i32 = 89; - -macro_rules! println_err( - ($($arg:tt)*) => { { - writeln!(&mut ::std::io::stderr(), $($arg)*).unwrap(); - } } -); +pub fn main() { + let mut args: Vec<_> = env::args().collect(); + env_logger::init(); -#[derive(Debug)] -struct Options { - port: u16, - shim_id: u64, - side: Side, - max_fragment: Option, - resumes: usize, - verify_peer: bool, - require_any_client_cert: bool, - server_preference: bool, - root_hint_subjects: Vec, - offer_no_client_cas: bool, - tickets: bool, - resume_with_tickets_disabled: bool, - queue_data: bool, - queue_data_on_resume: bool, - only_write_one_byte_after_handshake: bool, - only_write_one_byte_after_handshake_on_resume: bool, - shut_down_after_handshake: bool, - check_close_notify: bool, - host_name: String, - use_sni: bool, - trusted_cert_file: String, - key_file: String, - cert_file: String, - protocols: Vec, - reject_alpn: bool, - support_tls13: bool, - support_tls12: bool, - min_version: Option, - max_version: Option, - server_ocsp_response: Vec, - use_signing_scheme: u16, - groups: Option>, - export_keying_material: usize, - export_keying_material_label: String, - export_keying_material_context: String, - export_keying_material_context_used: bool, - read_size: usize, - quic_transport_params: Vec, - expect_quic_transport_params: Vec, - enable_early_data: bool, - expect_ticket_supports_early_data: bool, - expect_accept_early_data: bool, - expect_reject_early_data: bool, - expect_version: u16, - resumption_delay: u32, - queue_early_data_after_received_messages: Vec, - require_ems: bool, - expect_handshake_kind: Option>, - expect_handshake_kind_resumed: Option>, - install_cert_compression_algs: CompressionAlgs, - selected_provider: SelectedProvider, - provider: CryptoProvider, - ech_config_list: Option>, - expect_ech_accept: bool, - expect_ech_retry_configs: Option>, - on_resume_ech_config_list: Option>, - on_resume_expect_ech_accept: bool, - on_initial_expect_ech_accept: bool, - enable_ech_grease: bool, - send_key_update: bool, - expect_curve_id: Option, - on_initial_expect_curve_id: Option, - on_resume_expect_curve_id: Option, -} + args.remove(0); -impl Options { - fn new() -> Self { - let selected_provider = SelectedProvider::from_env(); - Options { - port: 0, - shim_id: 0, - side: Side::Client, - max_fragment: None, - resumes: 0, - verify_peer: false, - tickets: true, - resume_with_tickets_disabled: false, - host_name: "example.com".to_string(), - use_sni: false, - queue_data: false, - queue_data_on_resume: false, - only_write_one_byte_after_handshake: false, - only_write_one_byte_after_handshake_on_resume: false, - shut_down_after_handshake: false, - check_close_notify: false, - require_any_client_cert: false, - server_preference: false, - root_hint_subjects: vec![], - offer_no_client_cas: false, - trusted_cert_file: "".to_string(), - key_file: "".to_string(), - cert_file: "".to_string(), - protocols: vec![], - reject_alpn: false, - support_tls13: true, - support_tls12: true, - min_version: None, - max_version: None, - server_ocsp_response: vec![], - use_signing_scheme: 0, - groups: None, - export_keying_material: 0, - export_keying_material_label: "".to_string(), - export_keying_material_context: "".to_string(), - export_keying_material_context_used: false, - read_size: 512, - quic_transport_params: vec![], - expect_quic_transport_params: vec![], - enable_early_data: false, - expect_ticket_supports_early_data: false, - expect_accept_early_data: false, - expect_reject_early_data: false, - expect_version: 0, - resumption_delay: 0, - queue_early_data_after_received_messages: vec![], - require_ems: false, - expect_handshake_kind: None, - expect_handshake_kind_resumed: Some(vec![HandshakeKind::Resumed]), - install_cert_compression_algs: CompressionAlgs::None, - selected_provider, - provider: selected_provider.provider(), - ech_config_list: None, - expect_ech_accept: false, - expect_ech_retry_configs: None, - on_resume_ech_config_list: None, - on_resume_expect_ech_accept: false, - on_initial_expect_ech_accept: false, - enable_ech_grease: false, - send_key_update: false, - expect_curve_id: None, - on_initial_expect_curve_id: None, - on_resume_expect_curve_id: None, - } + if !args.is_empty() && args[0] == "-is-handshaker-supported" { + println!("No"); + process::exit(0); } + println!("options: {args:?}"); - fn version_allowed(&self, vers: ProtocolVersion) -> bool { - (self.min_version.is_none() || u16::from(vers) >= u16::from(self.min_version.unwrap())) - && (self.max_version.is_none() - || u16::from(vers) <= u16::from(self.max_version.unwrap())) - } + let mut opts = Options::new(); - fn tls13_supported(&self) -> bool { - self.support_tls13 && self.version_allowed(ProtocolVersion::TLSv1_3) + while !args.is_empty() { + opts.parse_one(&mut args); } - fn tls12_supported(&self) -> bool { - self.support_tls12 && self.version_allowed(ProtocolVersion::TLSv1_2) + if opts.side == Side::Client + && opts.on_initial_expect_curve_id != opts.on_resume_expect_curve_id + { + // expecting server to HRR us to its desired curve + opts.expect_handshake_kind_resumed = + Some(vec![HandshakeKind::ResumedWithHelloRetryRequest]); } - fn supported_versions(&self) -> Vec<&'static SupportedProtocolVersion> { - let mut versions = vec![]; - - if self.tls12_supported() { - versions.push(&version::TLS12); - } + println!("opts {opts:?}"); - if self.tls13_supported() { - versions.push(&version::TLS13); - } - versions + #[cfg(unix)] + if opts.wait_for_debugger { + // On Unix systems when -wait-for-debugger is passed from the BoGo runner + // we should SIGSTOP ourselves to allow a debugger to attach to the shim to + // continue the testing process. + signal::kill(Pid::from_raw(process::id() as i32), Signal::SIGSTOP).unwrap(); } -} -#[derive(Clone, Copy, Debug, PartialEq)] -enum SelectedProvider { - AwsLcRs, - #[cfg_attr(not(feature = "fips"), allow(dead_code))] - AwsLcRsFips, - #[cfg_attr(not(feature = "post-quantum"), allow(dead_code))] - PostQuantum, - Ring, -} + let key_log = Arc::new(KeyLogMemo::default()); + let mut config = match opts.side { + Side::Client => SideConfig::Client(make_client_cfg(&opts, &key_log)), + Side::Server => SideConfig::Server(make_server_cfg(&opts, &key_log)), + }; -impl SelectedProvider { - fn from_env() -> Self { - match env::var("BOGO_SHIM_PROVIDER") - .ok() - .as_deref() - { - None | Some("aws-lc-rs") => Self::AwsLcRs, - #[cfg(feature = "fips")] - Some("aws-lc-rs-fips") => Self::AwsLcRsFips, - #[cfg(feature = "post-quantum")] - Some("post-quantum") => Self::PostQuantum, - Some("ring") => Self::Ring, - Some(other) => panic!("unrecognised value for BOGO_SHIM_PROVIDER: {other:?}"), + for i in 0..opts.resumes + 1 { + assert!(opts.quic_transport_params.is_empty()); + assert!( + opts.expect_quic_transport_params + .is_empty() + ); + + match &config { + SideConfig::Client(config) => { + let server_name = ServerName::try_from(opts.host_name.as_str()) + .unwrap() + .to_owned(); + let sess = config + .connect(server_name) + .build() + .unwrap(); + exec(&opts, sess, &key_log, i); + } + SideConfig::Server(config) => { + let sess = ServerConnection::new(config.clone()).unwrap(); + exec(&opts, sess, &key_log, i); + } } - } - fn provider(&self) -> CryptoProvider { - match self { - Self::AwsLcRs | Self::AwsLcRsFips | Self::PostQuantum => { - // ensure all suites and kx groups are included (even in fips builds) - // as non-fips test cases require them. runner activates fips mode via -fips-202205 option - // this includes rustls-post-quantum, which just returns an altered - // version of `aws_lc_rs::default_provider()` - CryptoProvider { - kx_groups: aws_lc_rs::ALL_KX_GROUPS.to_vec(), - cipher_suites: aws_lc_rs::ALL_CIPHER_SUITES.to_vec(), - ..aws_lc_rs::default_provider() - } - } + if opts.resume_with_tickets_disabled { + opts.tickets = false; - Self::Ring => ring::default_provider(), + match &mut config { + SideConfig::Server(server) => *server = make_server_cfg(&opts, &key_log), + SideConfig::Client(client) => *client = make_client_cfg(&opts, &key_log), + }; } - } - fn ticketer(&self) -> Arc { - match self { - Self::AwsLcRs | Self::AwsLcRsFips | Self::PostQuantum => { - aws_lc_rs::Ticketer::new().unwrap() + if opts.on_resume_ech_config_list.is_some() { + opts.ech_config_list + .clone_from(&opts.on_resume_ech_config_list); + opts.expect_ech_accept = opts.on_resume_expect_ech_accept; + if let SideConfig::Client(client_cfg) = &mut config { + *client_cfg = make_client_cfg(&opts, &key_log); } - Self::Ring => ring::Ticketer::new().unwrap(), } - } - fn supports_ech(&self) -> bool { - match *self { - Self::AwsLcRs | Self::AwsLcRsFips | Self::PostQuantum => true, - Self::Ring => false, - } + opts.expect_handshake_kind + .clone_from(&opts.expect_handshake_kind_resumed); } } -fn load_root_certs(filename: &str) -> Arc { - let mut roots = RootCertStore::empty(); +fn exec(opts: &Options, mut sess: impl Connection + 'static, key_log: &KeyLogMemo, count: usize) { + let mut sent_message = false; - // -verify-peer can be used without specifying a root cert, - // to test (eg) client auth without actually looking at the certs. - // - // but WebPkiClientVerifier requires a non-empty set of roots. - // - // use an unrelated cert we have lying around. - let filename = match filename { - "" => "../../../../../test-ca/rsa-2048/ca.cert", - filename => filename, - }; + let addrs = [ + net::SocketAddr::from((net::Ipv6Addr::LOCALHOST, opts.port)), + net::SocketAddr::from((net::Ipv4Addr::LOCALHOST, opts.port)), + ]; + let mut conn = net::TcpStream::connect(&addrs[..]).expect("cannot connect"); + let mut sent_shutdown = false; + let mut sent_exporter = false; + let mut sent_key_update = false; + let mut quench_writes = false; - roots.add_parsable_certificates( - CertificateDer::pem_file_iter(filename) - .unwrap() - .map(|item| item.unwrap()), - ); - Arc::new(roots) -} + conn.write_all(&opts.shim_id.to_le_bytes()) + .unwrap(); -fn split_protocols(protos: &str) -> Vec { - let mut ret = Vec::new(); + loop { + if !sent_message && (opts.queue_data || (opts.queue_data_on_resume && count > 0)) { + if !opts + .queue_early_data_after_received_messages + .is_empty() + { + flush(&mut sess, &mut conn); + for message_size_estimate in &opts.queue_early_data_after_received_messages { + read_n_bytes(opts, &mut sess, &mut conn, *message_size_estimate); + } + println!("now ready for early data"); + } - let mut offs = 0; - while offs < protos.len() { - let len = protos.as_bytes()[offs] as usize; - let item = protos[offs + 1..offs + 1 + len].to_string(); - ret.push(item); - offs += 1 + len; - } - - ret -} - -fn decode_hex(hex: &str) -> Vec { - (0..hex.len()) - .step_by(2) - .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap()) - .inspect(|x| println!("item {:?}", x)) - .collect() -} - -#[derive(Debug)] -struct DummyClientAuth { - mandatory: bool, - root_hint_subjects: Vec, - parent: Arc, -} - -impl DummyClientAuth { - fn new( - trusted_cert_file: &str, - mandatory: bool, - root_hint_subjects: Vec, - ) -> Self { - Self { - mandatory, - root_hint_subjects, - parent: WebPkiClientVerifier::builder_with_provider( - load_root_certs(trusted_cert_file), - SelectedProvider::from_env() - .provider() - .into(), - ) - .build() - .unwrap(), + if count > 0 && opts.enable_early_data { + let len = client(&mut sess) + .early_data() + .expect("0rtt not available") + .write(b"hello") + .expect("0rtt write failed"); + sess.writer() + .write_all(&b"hello"[len..]) + .unwrap(); + sent_message = true; + } else if !opts.only_write_one_byte_after_handshake { + let _ = sess.writer().write_all(b"hello"); + sent_message = true; + } } - } -} - -impl ClientCertVerifier for DummyClientAuth { - fn offer_client_auth(&self) -> bool { - true - } - - fn client_auth_mandatory(&self) -> bool { - self.mandatory - } - fn root_hint_subjects(&self) -> &[DistinguishedName] { - &self.root_hint_subjects - } + if !quench_writes { + flush(&mut sess, &mut conn); + } - fn verify_client_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _now: UnixTime, - ) -> Result { - Ok(ClientCertVerified::assertion()) - } + if sess.wants_read() { + read_all_bytes(opts, &mut sess, &mut conn); + } - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - self.parent - .verify_tls12_signature(message, cert, dss) - } + if opts.side == Side::Server && opts.enable_early_data { + if let Some(ed) = &mut server(&mut sess).early_data() { + let mut data = Vec::new(); + let data_len = ed + .read_to_end(&mut data) + .expect("cannot read early_data"); - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - self.parent - .verify_tls13_signature(message, cert, dss) - } + for b in data.iter_mut() { + *b ^= 0xff; + } - fn supported_verify_schemes(&self) -> Vec { - self.parent.supported_verify_schemes() - } -} + sess.writer() + .write_all(&data[..data_len]) + .expect("cannot echo early_data in 1rtt data"); + } + } -#[derive(Debug)] -struct DummyServerAuth { - parent: Arc, -} + if !sess.is_handshaking() && opts.export_keying_material > 0 && !sent_exporter { + let mut export = vec![0; opts.export_keying_material]; + sess.exporter() + .unwrap() + .derive( + opts.export_keying_material_label + .as_bytes(), + if opts.export_keying_material_context_used { + Some( + opts.export_keying_material_context + .as_bytes(), + ) + } else { + None + }, + &mut export, + ) + .unwrap(); + sess.writer() + .write_all(&export) + .unwrap(); + sent_exporter = true; + } -impl DummyServerAuth { - fn new(trusted_cert_file: &str) -> Self { - DummyServerAuth { - parent: WebPkiServerVerifier::builder_with_provider( - load_root_certs(trusted_cert_file), - SelectedProvider::from_env() - .provider() - .into(), - ) - .build() - .unwrap(), + if !sess.is_handshaking() && opts.export_traffic_secrets && !sent_exporter { + let secrets = key_log.clone_inner(); + assert_eq!( + secrets.client_traffic_secret.len(), + secrets.server_traffic_secret.len() + ); + sess.writer() + .write_all(&(secrets.client_traffic_secret.len() as u16).to_le_bytes()) + .unwrap(); + sess.writer() + .write_all(&secrets.server_traffic_secret) + .unwrap(); + sess.writer() + .write_all(&secrets.client_traffic_secret) + .unwrap(); + sent_exporter = true; } - } -} -impl ServerCertVerifier for DummyServerAuth { - fn verify_server_cert( - &self, - _end_entity: &CertificateDer<'_>, - _certs: &[CertificateDer<'_>], - _hostname: &ServerName<'_>, - _ocsp: &[u8], - _now: UnixTime, - ) -> Result { - Ok(ServerCertVerified::assertion()) - } + if opts.send_key_update && !sent_key_update && !sess.is_handshaking() { + sess.refresh_traffic_keys().unwrap(); + sent_key_update = true; + } - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - self.parent - .verify_tls12_signature(message, cert, dss) - } + if !sess.is_handshaking() && opts.only_write_one_byte_after_handshake && !sent_message { + println!("writing message and then only one byte of its tls frame"); + flush(&mut sess, &mut conn); - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - self.parent - .verify_tls13_signature(message, cert, dss) - } + sess.writer() + .write_all(b"hello") + .unwrap(); + sent_message = true; - fn supported_verify_schemes(&self) -> Vec { - self.parent.supported_verify_schemes() - } -} + let mut one_byte = [0u8]; + let mut cursor = io::Cursor::new(&mut one_byte[..]); + sess.write_tls(&mut cursor).unwrap(); + conn.write_all(&one_byte) + .expect("IO error"); -#[derive(Debug)] -struct FixedSignatureSchemeSigningKey { - key: Arc, - scheme: SignatureScheme, -} + quench_writes = true; + } -impl sign::SigningKey for FixedSignatureSchemeSigningKey { - fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { - if offered.contains(&self.scheme) { - self.key.choose_scheme(&[self.scheme]) - } else { - self.key.choose_scheme(&[]) + if opts.enable_early_data + && opts.side == Side::Client + && !sess.is_handshaking() + && count > 0 + { + if opts.expect_accept_early_data && !client(&mut sess).is_early_data_accepted() { + quit_err("Early data was not accepted, but we expect the opposite"); + } else if opts.expect_reject_early_data && client(&mut sess).is_early_data_accepted() { + quit_err("Early data was accepted, but we expect the opposite"); + } + if opts.expect_version == 0x0304 { + match sess.protocol_version() { + Some(ProtocolVersion::TLSv1_3) | Some(ProtocolVersion(0x7f17)) => {} + _ => quit_err("wrong protocol version"), + } + } } - } - fn algorithm(&self) -> SignatureAlgorithm { - self.key.algorithm() - } -} -#[derive(Debug)] -struct FixedSignatureSchemeServerCertResolver { - resolver: Arc, - scheme: SignatureScheme, -} + if let (Some(expected_options), false) = + (opts.expect_handshake_kind.as_ref(), sess.is_handshaking()) + { + let actual = sess.handshake_kind().unwrap(); + assert!( + expected_options.contains(&actual), + "wanted to see {expected_options:?} but got {actual:?}" + ); + } -impl server::ResolvesServerCert for FixedSignatureSchemeServerCertResolver { - fn resolve(&self, client_hello: ClientHello) -> Option> { - let mut certkey = self.resolver.resolve(client_hello)?; - Arc::make_mut(&mut certkey).key = Arc::new(FixedSignatureSchemeSigningKey { - key: certkey.key.clone(), - scheme: self.scheme, - }); - Some(certkey) - } -} + if let Some(curve_id) = &opts.expect_curve_id { + // unlike openssl/boringssl's API, `negotiated_key_exchange_group` + // works for the connection, not session. this means TLS1.2 + // resumptions never have a value for `negotiated_key_exchange_group` + let tls12_resumed = sess.protocol_version() == Some(ProtocolVersion::TLSv1_2) + && sess.handshake_kind() == Some(HandshakeKind::Resumed); + let negotiated_key_exchange_group_ready = !(sess.is_handshaking() || tls12_resumed); -#[derive(Debug)] -struct FixedSignatureSchemeClientCertResolver { - resolver: Arc, - scheme: SignatureScheme, -} + if negotiated_key_exchange_group_ready { + let actual = sess + .negotiated_key_exchange_group() + .expect("no kx with -expect-curve-id"); + assert_eq!(curve_id, &actual.name()); + } + } -impl client::ResolvesClientCert for FixedSignatureSchemeClientCertResolver { - fn resolve( - &self, - root_hint_subjects: &[&[u8]], - sigschemes: &[SignatureScheme], - ) -> Option> { - if !sigschemes.contains(&self.scheme) { - quit(":NO_COMMON_SIGNATURE_ALGORITHMS:"); + if let Some(curve_id) = &opts.on_initial_expect_curve_id { + if !sess.is_handshaking() && count == 0 { + assert_eq!(sess.handshake_kind().unwrap(), HandshakeKind::Full); + assert_eq!( + sess.negotiated_key_exchange_group() + .expect("no kx with -on-initial-expect-curve-id") + .name(), + *curve_id + ); + } } - let mut certkey = self - .resolver - .resolve(root_hint_subjects, sigschemes)?; - Arc::make_mut(&mut certkey).key = Arc::new(FixedSignatureSchemeSigningKey { - key: certkey.key.clone(), - scheme: self.scheme, - }); - Some(certkey) - } - fn has_certs(&self) -> bool { - self.resolver.has_certs() - } -} + if let Some(curve_id) = &opts.on_resume_expect_curve_id { + if !sess.is_handshaking() && count > 0 { + assert!(matches!( + sess.handshake_kind().unwrap(), + HandshakeKind::Resumed | HandshakeKind::ResumedWithHelloRetryRequest + )); + assert_eq!( + sess.negotiated_key_exchange_group() + .expect("no kx with -on-resume-expect-curve-id") + .name(), + *curve_id + ); + } + } -fn lookup_scheme(scheme: u16) -> SignatureScheme { - match scheme { - 0x0401 => SignatureScheme::RSA_PKCS1_SHA256, - 0x0501 => SignatureScheme::RSA_PKCS1_SHA384, - 0x0601 => SignatureScheme::RSA_PKCS1_SHA512, - 0x0403 => SignatureScheme::ECDSA_NISTP256_SHA256, - 0x0503 => SignatureScheme::ECDSA_NISTP384_SHA384, - 0x0603 => SignatureScheme::ECDSA_NISTP521_SHA512, - 0x0804 => SignatureScheme::RSA_PSS_SHA256, - 0x0805 => SignatureScheme::RSA_PSS_SHA384, - 0x0806 => SignatureScheme::RSA_PSS_SHA512, - 0x0807 => SignatureScheme::ED25519, - // TODO: add support for Ed448 - // 0x0808 => SignatureScheme::ED448, - _ => { - println_err!("Unsupported signature scheme {:04x}", scheme); - process::exit(BOGO_NACK); + { + let ech_accept_required = + (count == 0 && opts.on_initial_expect_ech_accept) || opts.expect_ech_accept; + if ech_accept_required + && !sess.is_handshaking() + && client(&mut sess).ech_status() != EchStatus::Accepted + { + quit_err("ECH was not accepted, but we expect the opposite"); + } } - } -} -#[derive(Debug)] -struct ServerCacheWithResumptionDelay { - delay: u32, - storage: Arc, -} - -impl ServerCacheWithResumptionDelay { - fn new(delay: u32) -> Arc { - Arc::new(Self { - delay, - storage: server::ServerSessionMemoryCache::new(32), - }) - } -} - -fn align_time() { - /* we don't have an injectable clock source in rustls' public api, and - * resumption timing is in seconds resolution, so tests that use - * resumption_delay tend to be flickery if the seconds time ticks - * during this. - * - * this function delays until a fresh second ticks, which alleviates - * this. gross! - */ - fn sample() -> u64 { - time::SystemTime::now() - .duration_since(time::SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs() - } - - let start_secs = sample(); - while start_secs == sample() { - thread::sleep(time::Duration::from_millis(20)); - } -} - -impl server::StoresServerSessions for ServerCacheWithResumptionDelay { - fn put(&self, key: Vec, value: Vec) -> bool { - let mut ssv = ServerSessionValue::read_bytes(&value).unwrap(); - ssv.creation_time_sec -= self.delay as u64; - - self.storage - .put(key, ssv.get_encoding()) - } - - fn get(&self, key: &[u8]) -> Option> { - self.storage.get(key) - } - - fn take(&self, key: &[u8]) -> Option> { - self.storage.take(key) - } - - fn can_cache(&self) -> bool { - self.storage.can_cache() - } -} - -fn make_server_cfg(opts: &Options) -> Arc { - let client_auth = - if opts.verify_peer || opts.offer_no_client_cas || opts.require_any_client_cert { - Arc::new(DummyClientAuth::new( - &opts.trusted_cert_file, - opts.require_any_client_cert, - opts.root_hint_subjects.clone(), - )) - } else { - server::WebPkiClientVerifier::no_client_auth() + let mut buf = [0u8; 1024]; + let len = match sess + .reader() + .read(&mut buf[..opts.read_size]) + { + Ok(0) => { + if opts.check_close_notify { + println!("close notify ok"); + } + println!("EOF (tls)"); + return; + } + Ok(len) => len, + Err(err) if err.kind() == io::ErrorKind::WouldBlock => 0, + Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { + if opts.check_close_notify { + quit_err(":CLOSE_WITHOUT_CLOSE_NOTIFY:"); + } + println!("EOF (tcp)"); + return; + } + Err(err) => panic!("unhandled read error {err:?}"), }; - let cert = CertificateDer::pem_file_iter(&opts.cert_file) - .unwrap() - .map(|cert| cert.unwrap()) - .collect::>(); - let key = PrivateKeyDer::from_pem_file(&opts.key_file).unwrap(); - - let mut provider = opts.provider.clone(); - - if let Some(groups) = &opts.groups { - provider - .kx_groups - .retain(|kxg| groups.contains(&kxg.name())); - } - - let mut cfg = ServerConfig::builder_with_provider(provider.into()) - .with_protocol_versions(&opts.supported_versions()) - .unwrap() - .with_client_cert_verifier(client_auth) - .with_single_cert_with_ocsp(cert.clone(), key, opts.server_ocsp_response.clone()) - .unwrap(); - - cfg.session_storage = ServerCacheWithResumptionDelay::new(opts.resumption_delay); - cfg.max_fragment_size = opts.max_fragment; - cfg.send_tls13_tickets = 1; - cfg.require_ems = opts.require_ems; - cfg.ignore_client_order = opts.server_preference; - - if opts.use_signing_scheme > 0 { - let scheme = lookup_scheme(opts.use_signing_scheme); - cfg.cert_resolver = Arc::new(FixedSignatureSchemeServerCertResolver { - resolver: cfg.cert_resolver.clone(), - scheme, - }); - } - - if opts.tickets { - cfg.ticketer = opts.selected_provider.ticketer(); - } else if opts.resumes == 0 { - cfg.session_storage = Arc::new(server::NoServerSessionStorage {}); - } - - if !opts.protocols.is_empty() { - cfg.alpn_protocols = opts - .protocols - .iter() - .map(|proto| proto.as_bytes().to_vec()) - .collect::>(); - } - - if opts.reject_alpn { - cfg.alpn_protocols = vec![b"invalid".to_vec()]; - } - - if opts.enable_early_data { - // see kMaxEarlyDataAccepted in boringssl, which bogo validates - cfg.max_early_data_size = 14336; - cfg.send_half_rtt_data = true; - } - - match opts.install_cert_compression_algs { - CompressionAlgs::All => { - cfg.cert_compressors = vec![&ExpandingAlgorithm, &ShrinkingAlgorithm, &RandomAlgorithm]; - cfg.cert_decompressors = - vec![&ExpandingAlgorithm, &ShrinkingAlgorithm, &RandomAlgorithm]; - } - CompressionAlgs::One(ShrinkingAlgorithm::ALGORITHM) => { - cfg.cert_compressors = vec![&ShrinkingAlgorithm]; - cfg.cert_decompressors = vec![&ShrinkingAlgorithm]; + if opts.shut_down_after_handshake && !sent_shutdown && !sess.is_handshaking() { + sess.send_close_notify(); + sent_shutdown = true; } - CompressionAlgs::None => {} - _ => unimplemented!(), - } - Arc::new(cfg) -} + if quench_writes && len > 0 { + println!("unquenching writes after {len:?}"); + quench_writes = false; + } -struct ClientCacheWithoutKxHints { - delay: u32, - storage: Arc, -} + for b in buf.iter_mut() { + *b ^= 0xff; + } -impl ClientCacheWithoutKxHints { - fn new(delay: u32) -> Arc { - Arc::new(ClientCacheWithoutKxHints { - delay, - storage: Arc::new(client::ClientSessionMemoryCache::new(32)), - }) + sess.writer() + .write_all(&buf[..len]) + .unwrap(); } } -impl client::ClientSessionStore for ClientCacheWithoutKxHints { - fn set_kx_hint(&self, _: ServerName<'static>, _: NamedGroup) {} - fn kx_hint(&self, _: &ServerName<'_>) -> Option { - None - } - - fn set_tls12_session( - &self, - server_name: ServerName<'static>, - mut value: client::Tls12ClientSessionValue, - ) { - value.rewind_epoch(self.delay); - self.storage - .set_tls12_session(server_name, value); - } - - fn tls12_session( - &self, - server_name: &ServerName<'_>, - ) -> Option { - self.storage.tls12_session(server_name) - } - - fn remove_tls12_session(&self, server_name: &ServerName<'static>) { - self.storage - .remove_tls12_session(server_name); - } - - fn insert_tls13_ticket( - &self, - server_name: ServerName<'static>, - mut value: client::Tls13ClientSessionValue, - ) { - value.rewind_epoch(self.delay); - self.storage - .insert_tls13_ticket(server_name, value) - } - - fn take_tls13_ticket( - &self, - server_name: &ServerName<'static>, - ) -> Option { - self.storage - .take_tls13_ticket(server_name) - } +enum SideConfig { + Client(Arc), + Server(Arc), } -impl Debug for ClientCacheWithoutKxHints { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - // Note: we omit self.storage here as it may contain sensitive data. - f.debug_struct("ClientCacheWithoutKxHints") - .field("delay", &self.delay) - .finish() - } +fn client(conn: &mut dyn Any) -> &mut ClientConnection { + conn.downcast_mut::() + .unwrap() } -fn make_client_cfg(opts: &Options) -> Arc { - let mut provider = opts.provider.clone(); - - if let Some(groups) = &opts.groups { - provider - .kx_groups - .retain(|kxg| groups.contains(&kxg.name())); - } - - let cfg = ClientConfig::builder_with_provider(provider.into()); - - let cfg = if opts.selected_provider.supports_ech() { - if let Some(ech_config_list) = &opts.ech_config_list { - let ech_mode: EchMode = EchConfig::new(ech_config_list.clone(), ALL_HPKE_SUITES) - .unwrap_or_else(|_| quit(":INVALID_ECH_CONFIG_LIST:")) - .into(); - - cfg.with_ech(ech_mode) - .expect("invalid ECH config") - } else if opts.enable_ech_grease { - let ech_mode = EchMode::Grease(EchGreaseConfig::new( - GREASE_HPKE_SUITE, - HpkePublicKey(GREASE_25519_PUBKEY.to_vec()), - )); +fn server(conn: &mut dyn Any) -> &mut ServerConnection { + conn.downcast_mut::() + .unwrap() +} - cfg.with_ech(ech_mode) - .expect("invalid GREASE ECH config") - } else { - cfg.with_protocol_versions(&opts.supported_versions()) - .expect("inconsistent settings") +fn read_n_bytes(opts: &Options, sess: &mut impl Connection, conn: &mut net::TcpStream, n: usize) { + let mut bytes = [0u8; MAX_MESSAGE_SIZE]; + match conn.read(&mut bytes[..n]) { + Ok(count) => { + println!("read {count:?} bytes"); + sess.read_tls(&mut io::Cursor::new(&mut bytes[..count])) + .expect("read_tls not expected to fail reading from buffer"); } - } else { - cfg.with_protocol_versions(&opts.supported_versions()) - .expect("inconsistent settings") + Err(err) if err.kind() == io::ErrorKind::ConnectionReset => {} + Err(err) => panic!("invalid read: {err}"), }; - let cfg = cfg - .dangerous() - .with_custom_certificate_verifier(Arc::new(DummyServerAuth::new(&opts.trusted_cert_file))); + after_read(opts, sess, conn); +} - let mut cfg = if !opts.cert_file.is_empty() && !opts.key_file.is_empty() { - let cert = CertificateDer::pem_file_iter(&opts.cert_file) - .unwrap() - .map(|item| item.unwrap()) - .collect(); - let key = PrivateKeyDer::from_pem_file(&opts.key_file).unwrap(); - cfg.with_client_auth_cert(cert, key) - .unwrap() - } else { - cfg.with_no_client_auth() +fn read_all_bytes(opts: &Options, sess: &mut impl Connection, conn: &mut net::TcpStream) { + match sess.read_tls(conn) { + Ok(_) => {} + Err(err) if err.kind() == io::ErrorKind::ConnectionReset => {} + Err(err) => panic!("invalid read: {err}"), }; - if !opts.cert_file.is_empty() && opts.use_signing_scheme > 0 { - let scheme = lookup_scheme(opts.use_signing_scheme); - cfg.client_auth_cert_resolver = Arc::new(FixedSignatureSchemeClientCertResolver { - resolver: cfg.client_auth_cert_resolver.clone(), - scheme, - }); - } - - cfg.resumption = Resumption::store(ClientCacheWithoutKxHints::new(opts.resumption_delay)); - cfg.enable_sni = opts.use_sni; - cfg.max_fragment_size = opts.max_fragment; - cfg.require_ems = opts.require_ems; - - if !opts.protocols.is_empty() { - cfg.alpn_protocols = opts - .protocols - .iter() - .map(|proto| proto.as_bytes().to_vec()) - .collect(); - } + after_read(opts, sess, conn); +} - if opts.enable_early_data { - cfg.enable_early_data = true; +fn after_read(opts: &Options, sess: &mut impl Connection, conn: &mut net::TcpStream) { + if let Err(err) = sess.process_new_packets() { + flush(sess, conn); /* send any alerts before exiting */ + orderly_close(conn); + handle_err(opts, err); } +} - match opts.install_cert_compression_algs { - CompressionAlgs::All => { - cfg.cert_decompressors = - vec![&ExpandingAlgorithm, &ShrinkingAlgorithm, &RandomAlgorithm]; - cfg.cert_compressors = vec![&ExpandingAlgorithm, &ShrinkingAlgorithm, &RandomAlgorithm]; - } - CompressionAlgs::One(ShrinkingAlgorithm::ALGORITHM) => { - cfg.cert_decompressors = vec![&ShrinkingAlgorithm]; - cfg.cert_compressors = vec![&ShrinkingAlgorithm]; +fn flush(sess: &mut impl Connection, conn: &mut net::TcpStream) { + while sess.wants_write() { + if let Err(err) = sess.write_tls(conn) { + println!("IO error: {err:?}"); + process::exit(0); } - CompressionAlgs::None => {} - _ => unimplemented!(), } - - Arc::new(cfg) + conn.flush().unwrap(); } -fn quit(why: &str) -> ! { - println_err!("{}", why); - process::exit(0) -} +fn orderly_close(conn: &mut net::TcpStream) { + // assuming we just flush()'d, we will write no more. + let _ = conn.shutdown(net::Shutdown::Write); -fn quit_err(why: &str) -> ! { - println_err!("{}", why); - process::exit(1) -} + // wait for EOF + let mut buf = [0u8; 32]; + while let Ok(p @ 1..) = conn.peek(&mut buf) { + let _ = conn.read(&mut buf[..p]).unwrap(); + } -fn handle_err(opts: &Options, err: Error) -> ! { - println!("TLS error: {:?}", err); - thread::sleep(time::Duration::from_millis(100)); + let _ = conn.shutdown(net::Shutdown::Read); +} - match err { - Error::InappropriateHandshakeMessage { .. } | Error::InappropriateMessage { .. } => { - quit(":UNEXPECTED_MESSAGE:") - } - Error::AlertReceived(AlertDescription::RecordOverflow) => { - quit(":TLSV1_ALERT_RECORD_OVERFLOW:") - } - Error::AlertReceived(AlertDescription::HandshakeFailure) => quit(":HANDSHAKE_FAILURE:"), - Error::AlertReceived(AlertDescription::ProtocolVersion) => quit(":WRONG_VERSION:"), - Error::AlertReceived(AlertDescription::InternalError) => { - quit(":PEER_ALERT_INTERNAL_ERROR:") - } - Error::InvalidMessage( - InvalidMessage::MissingData("AlertDescription") - | InvalidMessage::TrailingData("AlertMessagePayload"), - ) => quit(":BAD_ALERT:"), - Error::InvalidMessage( - InvalidMessage::TrailingData("ChangeCipherSpecPayload") | InvalidMessage::InvalidCcs, - ) => quit(":BAD_CHANGE_CIPHER_SPEC:"), - Error::InvalidMessage( - InvalidMessage::InvalidKeyUpdate - | InvalidMessage::MissingData(_) - | InvalidMessage::TrailingData(_) - | InvalidMessage::UnexpectedMessage("HelloRetryRequest") - | InvalidMessage::NoSignatureSchemes - | InvalidMessage::UnsupportedCompression, - ) => quit(":BAD_HANDSHAKE_MSG:"), - Error::InvalidMessage(InvalidMessage::InvalidCertRequest) - | Error::InvalidMessage(InvalidMessage::InvalidDhParams) - | Error::InvalidMessage(InvalidMessage::MissingKeyExchange) => quit(":BAD_HANDSHAKE_MSG:"), - Error::InvalidMessage(InvalidMessage::InvalidContentType) - | Error::InvalidMessage(InvalidMessage::InvalidEmptyPayload) - | Error::InvalidMessage(InvalidMessage::UnknownProtocolVersion) - | Error::InvalidMessage(InvalidMessage::MessageTooLarge) => quit(":GARBAGE:"), - Error::InvalidMessage(InvalidMessage::MessageTooShort) - if opts.enable_ech_grease || opts.ech_config_list.is_some() => - { - quit(":ERROR_PARSING_EXTENSION:") - } - Error::InvalidMessage(InvalidMessage::UnexpectedMessage(_)) => quit(":GARBAGE:"), - Error::DecryptError if opts.ech_config_list.is_some() => { - quit(":INCONSISTENT_ECH_NEGOTIATION:") - } - Error::DecryptError => quit(":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:"), - Error::NoApplicationProtocol => quit(":NO_APPLICATION_PROTOCOL:"), - Error::PeerIncompatible( - PeerIncompatible::ServerSentHelloRetryRequestWithUnknownExtension, - ) => quit(":UNEXPECTED_EXTENSION:"), - Error::PeerIncompatible(PeerIncompatible::ServerRejectedEncryptedClientHello( - _retry_configs, - )) => { - if let Some(expected_configs) = &opts.expect_ech_retry_configs { - let expected_configs = - Vec::::read(&mut Reader::init(expected_configs)).unwrap(); - assert_eq!(_retry_configs, Some(expected_configs)); - } - quit(":ECH_REJECTED:") - } - Error::PeerIncompatible(_) => quit(":INCOMPATIBLE:"), - Error::PeerMisbehaved(PeerMisbehaved::MissingPskModesExtension) => { - quit(":MISSING_EXTENSION:") - } - Error::PeerMisbehaved(PeerMisbehaved::TooMuchEarlyDataReceived) => { - quit(":TOO_MUCH_READ_EARLY_DATA:") - } - Error::PeerMisbehaved(PeerMisbehaved::SignedHandshakeWithUnadvertisedSigScheme) - | Error::PeerMisbehaved(PeerMisbehaved::SignedKxWithWrongAlgorithm) => { - quit(":WRONG_SIGNATURE_TYPE:") - } - Error::PeerMisbehaved(PeerMisbehaved::SelectedUnofferedCertCompression) => { - quit(":UNKNOWN_CERT_COMPRESSION_ALG:") - } - Error::PeerMisbehaved(PeerMisbehaved::InvalidCertCompression) => { - quit(":CERT_DECOMPRESSION_FAILED:") - } - Error::PeerMisbehaved(PeerMisbehaved::OfferedDuplicateCertificateCompressions) => { - quit(":ERROR_PARSING_EXTENSION:") - } - Error::PeerMisbehaved(PeerMisbehaved::SelectedUnofferedCipherSuite) => { - quit(":WRONG_CIPHER_RETURNED:") - } - Error::PeerMisbehaved(PeerMisbehaved::TooManyWarningAlertsReceived) => { - quit(":TOO_MANY_WARNING_ALERTS:") - } - Error::PeerMisbehaved(PeerMisbehaved::TooManyKeyUpdateRequests) => { - quit(":TOO_MANY_KEY_UPDATES:") - } - Error::PeerMisbehaved(PeerMisbehaved::TooManyEmptyFragments) => { - quit(":TOO_MANY_EMPTY_FRAGMENTS:") - } - Error::PeerMisbehaved(PeerMisbehaved::IllegalHelloRetryRequestWithInvalidEch) - | Error::PeerMisbehaved(PeerMisbehaved::UnsolicitedEchExtension) => { - quit(":UNEXPECTED_EXTENSION:") - } - // The TLS-ECH-Client-UnsolicitedInnerServerNameAck test is expected to fail with - // :UNEXPECTED_EXTENSION: when we receive an unsolicited inner hello SNI extension. - // We treat this the same as any unexpected enc'd ext and return :PEER_MISBEHAVIOUR:. - // Convert to the expected if this error occurs when we're configured w/ ECH. - Error::PeerMisbehaved(PeerMisbehaved::UnsolicitedEncryptedExtension) - if opts.ech_config_list.is_some() => - { - quit(":UNEXPECTED_EXTENSION:") - } - Error::PeerMisbehaved(PeerMisbehaved::SelectedUnofferedKxGroup) => quit(":WRONG_CURVE:"), - Error::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare) => quit(":BAD_ECPOINT:"), - Error::PeerMisbehaved(_) => quit(":PEER_MISBEHAVIOUR:"), - Error::NoCertificatesPresented => quit(":NO_CERTS:"), - Error::AlertReceived(AlertDescription::UnexpectedMessage) => quit(":BAD_ALERT:"), - Error::AlertReceived(AlertDescription::DecompressionFailure) => { - quit_err(":SSLV3_ALERT_DECOMPRESSION_FAILURE:") - } - Error::InvalidCertificate(CertificateError::BadEncoding) => { - quit(":CANNOT_PARSE_LEAF_CERT:") - } - Error::InvalidCertificate(CertificateError::BadSignature) => quit(":BAD_SIGNATURE:"), - Error::InvalidCertificate(e) => quit(&format!(":BAD_CERT: ({:?})", e)), - Error::PeerSentOversizedRecord => quit(":DATA_LENGTH_TOO_LONG:"), - _ => { - println_err!("unhandled error: {:?}", err); - quit(":FIXME:") - } - } +#[derive(Debug)] +struct Options { + port: u16, + shim_id: u64, + side: Side, + max_fragment: Option, + resumes: usize, + verify_peer: bool, + require_any_client_cert: bool, + server_preference: bool, + root_hint_subjects: Vec, + offer_no_client_cas: bool, + tickets: bool, + resume_with_tickets_disabled: bool, + queue_data: bool, + queue_data_on_resume: bool, + only_write_one_byte_after_handshake: bool, + only_write_one_byte_after_handshake_on_resume: bool, + shut_down_after_handshake: bool, + check_close_notify: bool, + host_name: String, + use_sni: bool, + trusted_cert_file: String, + credentials: CredentialSet, + protocols: Vec, + reject_alpn: bool, + support_tls13: bool, + support_tls12: bool, + min_version: Option, + max_version: Option, + server_ocsp_response: Arc<[u8]>, + groups: Option>, + server_supported_group_hint: Option, + export_keying_material: usize, + export_keying_material_label: String, + export_keying_material_context: String, + export_keying_material_context_used: bool, + export_traffic_secrets: bool, + read_size: usize, + quic_transport_params: Vec, + expect_quic_transport_params: Vec, + enable_early_data: bool, + expect_ticket_supports_early_data: bool, + expect_accept_early_data: bool, + expect_reject_early_data: bool, + expect_version: u16, + resumption_delay: u32, + queue_early_data_after_received_messages: Vec, + require_ems: bool, + expect_handshake_kind: Option>, + expect_handshake_kind_resumed: Option>, + install_cert_compression_algs: CompressionAlgs, + selected_provider: SelectedProvider, + provider: CryptoProvider, + ech_config_list: Option>, + expect_ech_accept: bool, + expect_ech_retry_configs: Option>, + on_resume_ech_config_list: Option>, + on_resume_expect_ech_accept: bool, + on_initial_expect_ech_accept: bool, + enable_ech_grease: bool, + send_key_update: bool, + expect_curve_id: Option, + on_initial_expect_curve_id: Option, + on_resume_expect_curve_id: Option, + wait_for_debugger: bool, + ocsp: OcspValidation, } -fn flush(sess: &mut Connection, conn: &mut net::TcpStream) { - while sess.wants_write() { - if let Err(err) = sess.write_tls(conn) { - println!("IO error: {:?}", err); - process::exit(0); +impl Options { + fn new() -> Self { + let selected_provider = SelectedProvider::from_env(); + Self { + port: 0, + shim_id: 0, + side: Side::Client, + max_fragment: None, + resumes: 0, + verify_peer: false, + tickets: true, + resume_with_tickets_disabled: false, + host_name: "example.com".to_string(), + use_sni: false, + queue_data: false, + queue_data_on_resume: false, + only_write_one_byte_after_handshake: false, + only_write_one_byte_after_handshake_on_resume: false, + shut_down_after_handshake: false, + check_close_notify: false, + require_any_client_cert: false, + server_preference: false, + root_hint_subjects: vec![], + offer_no_client_cas: false, + trusted_cert_file: "".to_string(), + credentials: CredentialSet::default(), + protocols: vec![], + reject_alpn: false, + support_tls13: true, + support_tls12: true, + min_version: None, + max_version: None, + server_ocsp_response: Arc::from([]), + groups: None, + server_supported_group_hint: None, + export_keying_material: 0, + export_keying_material_label: "".to_string(), + export_keying_material_context: "".to_string(), + export_keying_material_context_used: false, + export_traffic_secrets: false, + read_size: 512, + quic_transport_params: vec![], + expect_quic_transport_params: vec![], + enable_early_data: false, + expect_ticket_supports_early_data: false, + expect_accept_early_data: false, + expect_reject_early_data: false, + expect_version: 0, + resumption_delay: 0, + queue_early_data_after_received_messages: vec![], + require_ems: false, + expect_handshake_kind: None, + expect_handshake_kind_resumed: Some(vec![HandshakeKind::Resumed]), + install_cert_compression_algs: CompressionAlgs::None, + selected_provider, + provider: selected_provider.provider(), + ech_config_list: None, + expect_ech_accept: false, + expect_ech_retry_configs: None, + on_resume_ech_config_list: None, + on_resume_expect_ech_accept: false, + on_initial_expect_ech_accept: false, + enable_ech_grease: false, + send_key_update: false, + expect_curve_id: None, + on_initial_expect_curve_id: None, + on_resume_expect_curve_id: None, + wait_for_debugger: false, + ocsp: OcspValidation::default(), } } - conn.flush().unwrap(); -} - -fn client(conn: &mut Connection) -> &mut ClientConnection { - conn.try_into().unwrap() -} -fn server(conn: &mut Connection) -> &mut ServerConnection { - match conn { - Connection::Server(s) => s, - _ => panic!("Connection is not a ServerConnection"), + fn version_allowed(&self, vers: ProtocolVersion) -> bool { + (self.min_version.is_none() || u16::from(vers) >= u16::from(self.min_version.unwrap())) + && (self.max_version.is_none() + || u16::from(vers) <= u16::from(self.max_version.unwrap())) } -} -const MAX_MESSAGE_SIZE: usize = 0xffff + 5; + fn tls13_supported(&self) -> bool { + self.support_tls13 && self.version_allowed(ProtocolVersion::TLSv1_3) + } -fn after_read(opts: &Options, sess: &mut Connection, conn: &mut net::TcpStream) { - if let Err(err) = sess.process_new_packets() { - flush(sess, conn); /* send any alerts before exiting */ - handle_err(opts, err); + fn tls12_supported(&self) -> bool { + self.support_tls12 && self.version_allowed(ProtocolVersion::TLSv1_2) } -} -fn read_n_bytes(opts: &Options, sess: &mut Connection, conn: &mut net::TcpStream, n: usize) { - let mut bytes = [0u8; MAX_MESSAGE_SIZE]; - match conn.read(&mut bytes[..n]) { - Ok(count) => { - println!("read {:?} bytes", count); - sess.read_tls(&mut io::Cursor::new(&mut bytes[..count])) - .expect("read_tls not expected to fail reading from buffer"); + fn provider(&self) -> CryptoProvider { + let mut provider = self.provider.clone(); + + if let Some(groups) = &self.groups { + provider + .kx_groups + .to_mut() + .retain(|kxg| groups.contains(&kxg.name())); } - Err(ref err) if err.kind() == io::ErrorKind::ConnectionReset => {} - Err(err) => panic!("invalid read: {}", err), - }; - after_read(opts, sess, conn); -} - -fn read_all_bytes(opts: &Options, sess: &mut Connection, conn: &mut net::TcpStream) { - match sess.read_tls(conn) { - Ok(_) => {} - Err(ref err) if err.kind() == io::ErrorKind::ConnectionReset => {} - Err(err) => panic!("invalid read: {}", err), - }; - - after_read(opts, sess, conn); -} - -fn exec(opts: &Options, mut sess: Connection, count: usize) { - let mut sent_message = false; - - let addrs = [ - net::SocketAddr::from((net::Ipv6Addr::LOCALHOST, opts.port)), - net::SocketAddr::from((net::Ipv4Addr::LOCALHOST, opts.port)), - ]; - let mut conn = net::TcpStream::connect(&addrs[..]).expect("cannot connect"); - let mut sent_shutdown = false; - let mut sent_exporter = false; - let mut sent_key_update = false; - let mut quench_writes = false; - - conn.write_all(&opts.shim_id.to_le_bytes()) - .unwrap(); - - loop { - if !sent_message && (opts.queue_data || (opts.queue_data_on_resume && count > 0)) { - if !opts - .queue_early_data_after_received_messages - .is_empty() - { - flush(&mut sess, &mut conn); - for message_size_estimate in &opts.queue_early_data_after_received_messages { - read_n_bytes(opts, &mut sess, &mut conn, *message_size_estimate); - } - println!("now ready for early data"); - } - - if count > 0 && opts.enable_early_data { - let len = client(&mut sess) - .early_data() - .expect("0rtt not available") - .write(b"hello") - .expect("0rtt write failed"); - sess.writer() - .write_all(&b"hello"[len..]) - .unwrap(); - sent_message = true; - } else if !opts.only_write_one_byte_after_handshake { - let _ = sess.writer().write_all(b"hello"); - sent_message = true; - } - } - - if !quench_writes { - flush(&mut sess, &mut conn); - } - - if sess.wants_read() { - read_all_bytes(opts, &mut sess, &mut conn); - } - - if opts.side == Side::Server && opts.enable_early_data { - if let Some(ref mut ed) = server(&mut sess).early_data() { - let mut data = Vec::new(); - let data_len = ed - .read_to_end(&mut data) - .expect("cannot read early_data"); - - for b in data.iter_mut() { - *b ^= 0xff; - } - - sess.writer() - .write_all(&data[..data_len]) - .expect("cannot echo early_data in 1rtt data"); - } - } - - if !sess.is_handshaking() && opts.export_keying_material > 0 && !sent_exporter { - let mut export = vec![0; opts.export_keying_material]; - sess.export_keying_material( - &mut export, - opts.export_keying_material_label - .as_bytes(), - if opts.export_keying_material_context_used { - Some( - opts.export_keying_material_context - .as_bytes(), - ) - } else { - None - }, - ) - .unwrap(); - sess.writer() - .write_all(&export) - .unwrap(); - sent_exporter = true; - } - - if opts.send_key_update && !sent_key_update && !sess.is_handshaking() { - sess.refresh_traffic_keys().unwrap(); - sent_key_update = true; - } - - if !sess.is_handshaking() && opts.only_write_one_byte_after_handshake && !sent_message { - println!("writing message and then only one byte of its tls frame"); - flush(&mut sess, &mut conn); - - sess.writer() - .write_all(b"hello") - .unwrap(); - sent_message = true; - - let mut one_byte = [0u8]; - let mut cursor = io::Cursor::new(&mut one_byte[..]); - sess.write_tls(&mut cursor).unwrap(); - conn.write_all(&one_byte) - .expect("IO error"); - - quench_writes = true; - } - - if opts.enable_early_data - && opts.side == Side::Client - && !sess.is_handshaking() - && count > 0 - { - if opts.expect_accept_early_data && !client(&mut sess).is_early_data_accepted() { - quit_err("Early data was not accepted, but we expect the opposite"); - } else if opts.expect_reject_early_data && client(&mut sess).is_early_data_accepted() { - quit_err("Early data was accepted, but we expect the opposite"); - } - if opts.expect_version == 0x0304 { - match sess.protocol_version() { - Some(ProtocolVersion::TLSv1_3) | Some(ProtocolVersion::Unknown(0x7f17)) => {} - _ => quit_err("wrong protocol version"), - } - } - } - - if let (Some(expected_options), false) = - (opts.expect_handshake_kind.as_ref(), sess.is_handshaking()) - { - let actual = sess.handshake_kind().unwrap(); - assert!( - expected_options.contains(&actual), - "wanted to see {expected_options:?} but got {actual:?}" - ); - } - - if let Some(curve_id) = &opts.expect_curve_id { - // unlike openssl/boringssl's API, `negotiated_key_exchange_group` - // works for the connection, not session. this means TLS1.2 - // resumptions never have a value for `negotiated_key_exchange_group` - let tls12_resumed = sess.protocol_version() == Some(ProtocolVersion::TLSv1_2) - && sess.handshake_kind() == Some(HandshakeKind::Resumed); - let negotiated_key_exchange_group_ready = !(sess.is_handshaking() || tls12_resumed); - - if negotiated_key_exchange_group_ready { - let actual = sess - .negotiated_key_exchange_group() - .expect("no kx with -expect-curve-id"); - assert_eq!(curve_id, &actual.name()); - } - } - - if let Some(curve_id) = &opts.on_initial_expect_curve_id { - if !sess.is_handshaking() && count == 0 { - assert_eq!(sess.handshake_kind().unwrap(), HandshakeKind::Full); - assert_eq!( - sess.negotiated_key_exchange_group() - .expect("no kx with -on-initial-expect-curve-id") - .name(), - *curve_id - ); - } - } - - if let Some(curve_id) = &opts.on_resume_expect_curve_id { - if !sess.is_handshaking() && count > 0 { - assert_eq!(sess.handshake_kind().unwrap(), HandshakeKind::Resumed); - assert_eq!( - sess.negotiated_key_exchange_group() - .expect("no kx with -on-resume-expect-curve-id") - .name(), - *curve_id - ); - } - } - - { - let ech_accept_required = - (count == 0 && opts.on_initial_expect_ech_accept) || opts.expect_ech_accept; - if ech_accept_required - && !sess.is_handshaking() - && client(&mut sess).ech_status() != EchStatus::Accepted - { - quit_err("ECH was not accepted, but we expect the opposite"); - } - } - - let mut buf = [0u8; 1024]; - let len = match sess - .reader() - .read(&mut buf[..opts.read_size]) - { - Ok(0) => { - if opts.check_close_notify { - println!("close notify ok"); - } - println!("EOF (tls)"); - return; - } - Ok(len) => len, - Err(err) if err.kind() == io::ErrorKind::WouldBlock => 0, - Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { - if opts.check_close_notify { - quit_err(":CLOSE_WITHOUT_CLOSE_NOTIFY:"); - } - println!("EOF (tcp)"); - return; - } - Err(err) => panic!("unhandled read error {:?}", err), - }; - - if opts.shut_down_after_handshake && !sent_shutdown && !sess.is_handshaking() { - sess.send_close_notify(); - sent_shutdown = true; - } - - if quench_writes && len > 0 { - println!("unquenching writes after {:?}", len); - quench_writes = false; - } - - for b in buf.iter_mut() { - *b ^= 0xff; + match (self.tls12_supported(), self.tls13_supported()) { + (true, true) => provider, + (true, false) => CryptoProvider { + tls13_cipher_suites: Default::default(), + ..provider + }, + (false, true) => CryptoProvider { + tls12_cipher_suites: Default::default(), + ..provider + }, + _ => panic!("nonsense version constraint"), } - - sess.writer() - .write_all(&buf[..len]) - .unwrap(); - } -} - -pub fn main() { - let mut args: Vec<_> = env::args().collect(); - env_logger::init(); - - args.remove(0); - - if !args.is_empty() && args[0] == "-is-handshaker-supported" { - println!("No"); - process::exit(0); } - println!("options: {:?}", args); - - let mut opts = Options::new(); - while !args.is_empty() { + fn parse_one(&mut self, args: &mut Vec) { let arg = args.remove(0); match arg.as_ref() { "-port" => { - opts.port = args.remove(0).parse::().unwrap(); + self.port = args.remove(0).parse::().unwrap(); } "-shim-id" => { - opts.shim_id = args.remove(0).parse::().unwrap(); + self.shim_id = args.remove(0).parse::().unwrap(); } "-server" => { - opts.side = Side::Server; + self.side = Side::Server; } "-key-file" => { - opts.key_file = args.remove(0); + self.credentials.last_mut().key_file = args.remove(0); + } + "-new-x509-credential" => { + self.credentials.additional.push(Credential::default()); + } + "-expect-selected-credential" => { + self.credentials.expect_selected = args.remove(0).parse::().ok(); } "-cert-file" => { - opts.cert_file = args.remove(0); + self.credentials.last_mut().cert_file = args.remove(0); } "-trust-cert" => { - opts.trusted_cert_file = args.remove(0); + self.trusted_cert_file = args.remove(0); } "-resume-count" => { - opts.resumes = args.remove(0).parse::().unwrap(); + self.resumes = args.remove(0).parse::().unwrap(); } "-no-tls13" => { - opts.support_tls13 = false; + self.support_tls13 = false; } "-no-tls12" => { - opts.support_tls12 = false; + self.support_tls12 = false; } "-min-version" => { let min = args.remove(0).parse::().unwrap(); - opts.min_version = Some(ProtocolVersion::Unknown(min)); + self.min_version = Some(ProtocolVersion(min)); } "-max-version" => { let max = args.remove(0).parse::().unwrap(); - opts.max_version = Some(ProtocolVersion::Unknown(max)); + self.max_version = Some(ProtocolVersion(max)); } "-max-send-fragment" => { let max_fragment = args.remove(0).parse::().unwrap(); - opts.max_fragment = Some(max_fragment + 5); // ours includes header + self.max_fragment = Some(max_fragment + 5); // ours includes header } "-read-size" => { let rdsz = args.remove(0).parse::().unwrap(); - opts.read_size = rdsz; + self.read_size = rdsz; } "-tls13-variant" => { let variant = args.remove(0).parse::().unwrap(); if variant != 1 { - println!("NYI TLS1.3 variant selection: {:?} {:?}", arg, variant); + println!("NYI TLS1.3 variant selection: {arg:?} {variant:?}"); process::exit(BOGO_NACK); } } "-no-ticket" => { - opts.tickets = false; + self.tickets = false; } "-on-resume-no-ticket" => { - opts.resume_with_tickets_disabled = true; + self.resume_with_tickets_disabled = true; } "-signing-prefs" => { let alg = args.remove(0).parse::().unwrap(); - opts.use_signing_scheme = alg; + self.credentials.last_mut().use_signing_scheme = Some(alg); + } + "-must-match-issuer" => { + self.credentials.last_mut().must_match_issuer = true; } "-use-client-ca-list" => { match args.remove(0).as_ref() { "" | "" => { - opts.root_hint_subjects = vec![]; + self.root_hint_subjects = vec![]; } list => { - opts.root_hint_subjects = list.split(',') + self.root_hint_subjects = list.split(',') .map(|entry| DistinguishedName::from(decode_hex(entry))) .collect(); } @@ -1366,13 +737,13 @@ pub fn main() { lookup_scheme(args.remove(0).parse::().unwrap()); } "-expect-curve-id" => { - opts.expect_curve_id = Some(NamedGroup::from(args.remove(0).parse::().unwrap())); + self.expect_curve_id = Some(NamedGroup::from(args.remove(0).parse::().unwrap())); } "-on-initial-expect-curve-id" => { - opts.on_initial_expect_curve_id = Some(NamedGroup::from(args.remove(0).parse::().unwrap())); + self.on_initial_expect_curve_id = Some(NamedGroup::from(args.remove(0).parse::().unwrap())); } "-on-resume-expect-curve-id" => { - opts.on_resume_expect_curve_id = Some(NamedGroup::from(args.remove(0).parse::().unwrap())); + self.on_resume_expect_curve_id = Some(NamedGroup::from(args.remove(0).parse::().unwrap())); } "-max-cert-list" | "-expect-peer-signature-algorithm" | @@ -1387,8 +758,6 @@ pub fn main() { "-expect-signed-cert-timestamps" | "-expect-certificate-types" | "-expect-client-ca-list" | - "-on-retry-expect-early-data-reason" | - "-on-resume-expect-early-data-reason" | "-on-initial-expect-early-data-reason" | "-on-initial-expect-cipher" | "-on-resume-expect-cipher" | @@ -1407,213 +776,235 @@ pub fn main() { "-expect-tls13-downgrade" | "-enable-signed-cert-timestamps" | "-expect-session-id" => { - println!("not checking {}; NYI", arg); + println!("not checking {arg}; NYI"); } "-key-update" => { - opts.send_key_update = true; + self.send_key_update = true; } "-expect-hrr" => { - opts.expect_handshake_kind = Some(vec![HandshakeKind::FullWithHelloRetryRequest]); + self.expect_handshake_kind = Some(vec![HandshakeKind::FullWithHelloRetryRequest]); + self.expect_handshake_kind_resumed = Some(vec![HandshakeKind::ResumedWithHelloRetryRequest]); } "-expect-no-hrr" => { - opts.expect_handshake_kind = Some(vec![HandshakeKind::Full]); + self.expect_handshake_kind = Some(vec![HandshakeKind::Full]); + } + "-on-retry-expect-early-data-reason" | "-on-resume-expect-early-data-reason" => { + if args.remove(0) == "hello_retry_request" { + self.expect_handshake_kind_resumed = Some(vec![HandshakeKind::ResumedWithHelloRetryRequest]); + } } "-expect-session-miss" => { - opts.expect_handshake_kind_resumed = Some(vec![ + self.expect_handshake_kind_resumed = Some(vec![ HandshakeKind::Full, HandshakeKind::FullWithHelloRetryRequest ]); } "-export-keying-material" => { - opts.export_keying_material = args.remove(0).parse::().unwrap(); + self.export_keying_material = args.remove(0).parse::().unwrap(); } "-export-label" => { - opts.export_keying_material_label = args.remove(0); + self.export_keying_material_label = args.remove(0); } "-export-context" => { - opts.export_keying_material_context = args.remove(0); + self.export_keying_material_context = args.remove(0); } "-use-export-context" => { - opts.export_keying_material_context_used = true; + self.export_keying_material_context_used = true; + } + "-export-traffic-secrets" => { + self.export_traffic_secrets = true; } "-quic-transport-params" => { - opts.quic_transport_params = BASE64_STANDARD.decode(args.remove(0).as_bytes()) + self.quic_transport_params = BASE64_STANDARD.decode(args.remove(0).as_bytes()) .expect("invalid base64"); } "-expect-quic-transport-params" => { - opts.expect_quic_transport_params = BASE64_STANDARD.decode(args.remove(0).as_bytes()) + self.expect_quic_transport_params = BASE64_STANDARD.decode(args.remove(0).as_bytes()) .expect("invalid base64"); } "-ocsp-response" => { - opts.server_ocsp_response = BASE64_STANDARD.decode(args.remove(0).as_bytes()) - .expect("invalid base64"); + self.server_ocsp_response = Arc::from(BASE64_STANDARD.decode(args.remove(0).as_bytes()) + .expect("invalid base64")); } "-select-alpn" => { - opts.protocols.push(args.remove(0)); + self.protocols.push(args.remove(0)); } "-require-any-client-certificate" => { - opts.require_any_client_cert = true; + self.require_any_client_cert = true; } "-verify-peer" => { - opts.verify_peer = true; + self.verify_peer = true; } "-shim-writes-first" => { - opts.queue_data = true; + self.queue_data = true; } "-read-with-unfinished-write" => { - opts.queue_data = true; - opts.only_write_one_byte_after_handshake = true; + self.queue_data = true; + self.only_write_one_byte_after_handshake = true; } "-shim-shuts-down" => { - opts.shut_down_after_handshake = true; + self.shut_down_after_handshake = true; } "-check-close-notify" => { - opts.check_close_notify = true; + self.check_close_notify = true; } "-host-name" => { - opts.host_name = args.remove(0); - opts.use_sni = true; + self.host_name = args.remove(0); + self.use_sni = true; } "-advertise-alpn" => { - opts.protocols = split_protocols(&args.remove(0)); + self.protocols = split_protocols(&args.remove(0)); } "-reject-alpn" => { - opts.reject_alpn = true; + self.reject_alpn = true; } "-use-null-client-ca-list" => { - opts.offer_no_client_cas = true; + self.offer_no_client_cas = true; } "-enable-early-data" => { - opts.tickets = false; - opts.enable_early_data = true; + self.tickets = false; + self.enable_early_data = true; } "-on-resume-shim-writes-first" => { - opts.queue_data_on_resume = true; + self.queue_data_on_resume = true; } "-on-resume-read-with-unfinished-write" => { - opts.queue_data_on_resume = true; - opts.only_write_one_byte_after_handshake_on_resume = true; + self.queue_data_on_resume = true; + self.only_write_one_byte_after_handshake_on_resume = true; } "-on-resume-early-write-after-message" => { - opts.queue_early_data_after_received_messages= match args.remove(0).parse::().unwrap() { + self.queue_early_data_after_received_messages = match args.remove(0).parse::().unwrap() { // estimate where these messages appear in the server's first flight. - 2 => vec![5 + 128 + 5 + 32], - 8 => vec![5 + 128 + 5 + 32, 5 + 64], + 2 => vec![5 + 112 + 5 + 32], + 8 => vec![5 + 112 + 5 + 32, 5 + 64], _ => { panic!("unhandled -on-resume-early-write-after-message"); } }; - opts.queue_data_on_resume = true; + self.queue_data_on_resume = true; } "-expect-ticket-supports-early-data" => { - opts.expect_ticket_supports_early_data = true; + self.expect_ticket_supports_early_data = true; } "-expect-accept-early-data" | "-on-resume-expect-accept-early-data" => { - opts.expect_accept_early_data = true; + self.expect_accept_early_data = true; } "-expect-early-data-reason" | "-on-resume-expect-reject-early-data-reason" => { let reason = args.remove(0); match reason.as_str() { "disabled" | "protocol_version" => { - opts.expect_reject_early_data = true; + self.expect_reject_early_data = true; } _ => { - println!("NYI early data reason: {}", reason); + println!("NYI early data reason: {reason}"); process::exit(1); } } } "-expect-reject-early-data" | "-on-resume-expect-reject-early-data" => { - opts.expect_reject_early_data = true; + self.expect_reject_early_data = true; } "-expect-version" => { - opts.expect_version = args.remove(0).parse::().unwrap(); + self.expect_version = args.remove(0).parse::().unwrap(); } "-curves" => { let group = NamedGroup::from(args.remove(0).parse::().unwrap()); - opts.groups.get_or_insert(Vec::new()).push(group); - - // if X25519MLKEM768 is requested, insert it from rustls_post_quantum - #[cfg(feature = "post-quantum")] - if group == rustls_post_quantum::X25519MLKEM768.name() && opts.selected_provider == SelectedProvider::PostQuantum { - opts.provider.kx_groups.insert(0, rustls_post_quantum::X25519MLKEM768); - } + self.groups.get_or_insert(Vec::new()).push(group); + } + "-server-supported-groups-hint" => { + let group = NamedGroup::from(args.remove(0).parse::().unwrap()); + self.server_supported_group_hint = Some(group); } "-resumption-delay" => { - opts.resumption_delay = args.remove(0).parse::().unwrap(); + self.resumption_delay = args.remove(0).parse::().unwrap(); align_time(); } "-expect-extended-master-secret" => { - opts.require_ems = true; + self.require_ems = true; } "-install-cert-compression-algs" => { - opts.install_cert_compression_algs = CompressionAlgs::All; + self.install_cert_compression_algs = CompressionAlgs::All; } "-install-one-cert-compression-alg" => { - opts.install_cert_compression_algs = CompressionAlgs::One(args.remove(0).parse::().unwrap()); + self.install_cert_compression_algs = CompressionAlgs::One(args.remove(0).parse::().unwrap()); } #[cfg(feature = "fips")] - "-fips-202205" if opts.selected_provider == SelectedProvider::AwsLcRsFips => { - opts.provider = rustls::crypto::default_fips_provider(); + "-fips-202205" if self.selected_provider == SelectedProvider::AwsLcRsFips => { + self.provider = rustls_aws_lc_rs::DEFAULT_FIPS_PROVIDER.clone(); } "-fips-202205" => { println!("Not a FIPS build"); process::exit(BOGO_NACK); } "-ech-config-list" => { - opts.ech_config_list = Some(BASE64_STANDARD.decode(args.remove(0).as_bytes()) + self.ech_config_list = Some(BASE64_STANDARD.decode(args.remove(0).as_bytes()) .expect("invalid ECH config base64").into()); } "-expect-ech-accept" => { - opts.expect_ech_accept = true; + self.expect_ech_accept = true; } "-expect-ech-retry-configs" => { - opts.expect_ech_retry_configs = Some(BASE64_STANDARD.decode(args.remove(0).as_bytes()) + self.expect_ech_retry_configs = Some(BASE64_STANDARD.decode(args.remove(0).as_bytes()) .expect("invalid ECH config base64").into()); } "-on-resume-ech-config-list" => { - opts.on_resume_ech_config_list = Some(BASE64_STANDARD.decode(args.remove(0).as_bytes()) + self.on_resume_ech_config_list = Some(BASE64_STANDARD.decode(args.remove(0).as_bytes()) .expect("invalid on resume ECH config base64").into()); } "-on-resume-expect-ech-accept" => { - opts.on_resume_expect_ech_accept = true; + self.on_resume_expect_ech_accept = true; } "-expect-no-ech-retry-configs" => { - opts.expect_ech_retry_configs = None; + self.expect_ech_retry_configs = None; } "-on-initial-expect-ech-accept" => { - opts.on_initial_expect_ech_accept = true; + self.on_initial_expect_ech_accept = true; } "-on-retry-expect-ech-retry-configs" => { // Note: we treat this the same as -expect-ech-retry-configs - opts.expect_ech_retry_configs = Some(BASE64_STANDARD.decode(args.remove(0).as_bytes()) + self.expect_ech_retry_configs = Some(BASE64_STANDARD.decode(args.remove(0).as_bytes()) .expect("invalid retry ECH config base64").into()); } "-enable-ech-grease" => { - opts.enable_ech_grease = true; + self.enable_ech_grease = true; } "-server-preference" => { - opts.server_preference = true; + self.server_preference = true; + } + "-fail-ocsp-callback" => { + self.ocsp = OcspValidation::Reject; + } + "-wait-for-debugger" => { + #[cfg(windows)] + { + panic!("-wait-for-debugger not supported on Windows"); + } + #[cfg(unix)] + { + self.wait_for_debugger = true; + } } // defaults: + "-decline-alpn" | "-enable-all-curves" | - "-renegotiate-ignore" | - "-no-tls11" | - "-no-tls1" | - "-no-ssl3" | - "-handoff" | - "-ipv6" | - "-decline-alpn" | + "-enable-ocsp-stapling" | "-expect-no-session" | "-expect-ticket-renewal" | - "-enable-ocsp-stapling" | "-forbid-renegotiation-after-handshake" | + "-handoff" | + "-ipv6" | + "-no-ssl3" | + "-no-tls1" | + "-no-tls11" | + "-permute-extensions" | + "-renegotiate-ignore" | + "-use-ocsp-callback" | // internal openssl details: "-async" | "-implicit-handshake" | @@ -1621,126 +1012,1154 @@ pub fn main() { "-use-early-callback" => {} // Not implemented things - "-dtls" | - "-cipher" | - "-psk" | - "-renegotiate-freely" | - "-false-start" | - "-fallback-scsv" | - "-fail-early-callback" | - "-fail-cert-callback" | - "-install-ddos-callback" | - "-advertise-npn" | "-advertise-empty-npn" | - "-verify-fail" | - "-expect-channel-id" | - "-send-channel-id" | - "-select-next-proto" | - "-select-empty-next-proto" | - "-expect-verify-result" | - "-send-alert" | + "-advertise-npn" | + "-allow-hint-mismatch" | + "-allow-unknown-alpn-protos" | + "-cipher" | + "-cnsa-202407" | "-digest-prefs" | - "-use-exporter-between-reads" | - "-ticket-key" | - "-tls-unique" | - "-enable-server-custom-extension" | + "-dtls" | + "-enable-channel-id" | "-enable-client-custom-extension" | - "-expect-dhe-group-size" | - "-use-ticket-callback" | "-enable-grease" | - "-enable-channel-id" | - "-expect-early-data-info" | + "-enable-server-custom-extension" | + "-expect-channel-id" | "-expect-cipher-aes" | - "-retain-only-sha256-client-cert-initial" | + "-expect-dhe-group-size" | "-expect-draft-downgrade" | - "-allow-unknown-alpn-protos" | - "-on-initial-tls13-variant" | - "-on-resume-export-early-keying-material" | - "-on-resume-enable-early-data" | + "-expect-early-data-info" | + "-expect-not-resumable-across-names" | + "-expect-peer-cert-file" | + "-expect-resumable-across-names" | + "-expect-verify-result" | "-export-early-keying-material" | + "-fail-cert-callback" | + "-fail-early-callback" | + "-fallback-scsv" | + "-false-start" | "-handshake-twice" | - "-on-resume-verify-fail" | - "-reverify-on-resume" | + "-ignore-tls13-downgrade" | + "-install-ddos-callback" | + "-key-shares" | "-no-op-extra-handshake" | - "-expect-peer-cert-file" | + "-no-key-shares" | + "-no-server-name-ack" | "-no-rsa-pss-rsae-certs" | - "-ignore-tls13-downgrade" | - "-allow-hint-mismatch" | - "-wpa-202304" | - "-cnsa-202407" | - "-srtp-profiles" | - "-permute-extensions" | - "-signed-cert-timestamps" | "-on-initial-expect-peer-cert-file" | - "-use-custom-verify-callback" => { - println!("NYI option {:?}", arg); + "-on-initial-tls13-variant" | + "-on-resume-enable-early-data" | + "-on-resume-export-early-keying-material" | + "-on-resume-verify-fail" | + "-psk" | + "-renegotiate-freely" | + "-resumption-across-names-enabled" | + "-retain-only-sha256-client-cert-initial" | + "-reverify-on-resume" | + "-select-empty-next-proto" | + "-select-next-proto" | + "-send-alert" | + "-send-channel-id" | + "-signed-cert-timestamps" | + "-srtp-profiles" | + "-ticket-key" | + "-tls-unique" | + "-use-custom-verify-callback" | + "-use-exporter-between-reads" | + "-use-ticket-aead-callback" | + "-use-ticket-callback" | + "-verify-fail" | + "-wpa-202304" => { + println!("NYI option {arg:?}"); process::exit(BOGO_NACK); } - "-print-rustls-provider" => { - println!("{}", "*".repeat(66)); - println!("rustls provider is {:?}", opts.selected_provider); - println!("{}", "*".repeat(66)); - process::exit(0); + "-print-rustls-provider" => { + println!("{}", "*".repeat(66)); + println!("rustls provider is {:?}", self.selected_provider); + println!("{}", "*".repeat(66)); + process::exit(0); + } + + _ => { + println!("unhandled option {arg:?}"); + process::exit(1); + } + } + } +} + +#[derive(Debug, Default)] +struct CredentialSet { + default: Credential, + additional: Vec, + /// Some(-1) means `default`, otherwise index into `additional` + expect_selected: Option, +} + +impl CredentialSet { + fn last_mut(&mut self) -> &mut Credential { + self.additional + .last_mut() + .unwrap_or(&mut self.default) + } + + fn configured(&self) -> bool { + self.default.configured() + || self + .additional + .iter() + .any(|cred| cred.configured()) + } +} + +#[derive(Clone, Debug, Default)] +struct Credential { + key_file: String, + cert_file: String, + use_signing_scheme: Option, + must_match_issuer: bool, +} + +impl Credential { + fn load_from_file(&self, provider: &CryptoProvider) -> Credentials { + let certs = CertificateDer::pem_file_iter(&self.cert_file) + .unwrap() + .map(|cert| cert.unwrap()) + .collect::>(); + let key = PrivateKeyDer::from_pem_file(&self.key_file).unwrap(); + Credentials::from_der( + Arc::from(Identity::from_cert_chain(certs).unwrap()), + key, + provider, + ) + .unwrap() + } + + fn configured(&self) -> bool { + !self.cert_file.is_empty() && !self.key_file.is_empty() + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +enum SelectedProvider { + AwsLcRs, + #[cfg_attr(not(feature = "fips"), allow(dead_code))] + AwsLcRsFips, + Ring, +} + +impl SelectedProvider { + fn from_env() -> Self { + match env::var("BOGO_SHIM_PROVIDER") + .ok() + .as_deref() + { + None | Some("aws-lc-rs") => Self::AwsLcRs, + #[cfg(feature = "fips")] + Some("aws-lc-rs-fips") => Self::AwsLcRsFips, + Some("ring") => Self::Ring, + Some(other) => panic!("unrecognized value for BOGO_SHIM_PROVIDER: {other:?}"), + } + } + + fn provider(&self) -> CryptoProvider { + match self { + Self::AwsLcRs | Self::AwsLcRsFips => { + // ensure all suites and kx groups are included (even in fips builds) + // as non-fips test cases require them. runner activates fips mode via -fips-202205 option + // this includes rustls-post-quantum, which just returns an altered + // version of `aws_lc_rs::default_provider()` + CryptoProvider { + kx_groups: Cow::Borrowed(rustls_aws_lc_rs::ALL_KX_GROUPS), + tls12_cipher_suites: Cow::Borrowed(rustls_aws_lc_rs::ALL_TLS12_CIPHER_SUITES), + tls13_cipher_suites: Cow::Borrowed(rustls_aws_lc_rs::ALL_TLS13_CIPHER_SUITES), + ..rustls_aws_lc_rs::DEFAULT_PROVIDER + } + } + + Self::Ring => rustls_ring::DEFAULT_PROVIDER, + } + } + + fn supports_ech(&self) -> bool { + match *self { + Self::AwsLcRs | Self::AwsLcRsFips => true, + Self::Ring => false, + } + } +} + +fn load_root_certs(filename: &str) -> Arc { + let mut roots = RootCertStore::empty(); + + // -verify-peer can be used without specifying a root cert, + // to test (eg) client auth without actually looking at the certs. + // + // but WebPkiClientVerifier requires a non-empty set of roots. + // + // use an unrelated cert we have lying around. + let filename = match filename { + "" => "../../../../../test-ca/rsa-2048/ca.cert", + filename => filename, + }; + + roots.add_parsable_certificates( + CertificateDer::pem_file_iter(filename) + .unwrap() + .map(|item| item.unwrap()), + ); + Arc::new(roots) +} + +fn split_protocols(protos: &str) -> Vec { + let mut ret = Vec::new(); + + let mut offs = 0; + while offs < protos.len() { + let len = protos.as_bytes()[offs] as usize; + let item = protos[offs + 1..offs + 1 + len].to_string(); + ret.push(item); + offs += 1 + len; + } + + ret +} + +fn decode_hex(hex: &str) -> Vec { + (0..hex.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap()) + .inspect(|x| println!("item {x:?}")) + .collect() +} + +#[derive(Debug)] +struct DummyClientAuth { + mandatory: bool, + root_hint_subjects: Arc<[DistinguishedName]>, + parent: Arc, +} + +impl DummyClientAuth { + fn new( + trusted_cert_file: &str, + mandatory: bool, + root_hint_subjects: Arc<[DistinguishedName]>, + ) -> Self { + Self { + mandatory, + root_hint_subjects, + parent: Arc::new( + WebPkiClientVerifier::builder( + load_root_certs(trusted_cert_file), + &SelectedProvider::from_env().provider(), + ) + .build() + .unwrap(), + ), + } + } +} + +impl ClientVerifier for DummyClientAuth { + fn verify_identity(&self, _identity: &ClientIdentity<'_>) -> Result { + Ok(PeerVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + input: &SignatureVerificationInput<'_>, + ) -> Result { + self.parent + .verify_tls12_signature(input) + } + + fn verify_tls13_signature( + &self, + input: &SignatureVerificationInput<'_>, + ) -> Result { + self.parent + .verify_tls13_signature(input) + } + + fn root_hint_subjects(&self) -> Arc<[DistinguishedName]> { + self.root_hint_subjects.clone() + } + + fn client_auth_mandatory(&self) -> bool { + self.mandatory + } + + fn offer_client_auth(&self) -> bool { + true + } + + fn supported_verify_schemes(&self) -> Vec { + self.parent.supported_verify_schemes() + } +} + +#[derive(Debug)] +struct DummyServerAuth { + parent: Arc, + ocsp: OcspValidation, +} + +impl DummyServerAuth { + fn new(trusted_cert_file: &str, ocsp: OcspValidation) -> Self { + Self { + parent: Arc::new( + WebPkiServerVerifier::builder( + load_root_certs(trusted_cert_file), + &SelectedProvider::from_env().provider(), + ) + .build() + .unwrap(), + ), + ocsp, + } + } +} + +impl ServerVerifier for DummyServerAuth { + fn verify_identity(&self, _identity: &ServerIdentity<'_>) -> Result { + if let OcspValidation::Reject = self.ocsp { + return Err(CertificateError::InvalidOcspResponse.into()); + } + Ok(PeerVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + input: &SignatureVerificationInput<'_>, + ) -> Result { + self.parent + .verify_tls12_signature(input) + } + + fn verify_tls13_signature( + &self, + input: &SignatureVerificationInput<'_>, + ) -> Result { + self.parent + .verify_tls13_signature(input) + } + + fn supported_verify_schemes(&self) -> Vec { + self.parent.supported_verify_schemes() + } + + fn request_ocsp_response(&self) -> bool { + true + } + + fn hash_config(&self, h: &mut dyn Hasher) { + self.parent.hash_config(h) + } +} + +#[derive(Clone, Copy, Debug, Default)] +enum OcspValidation { + /// Totally ignore `ocsp_response` value + #[default] + None, + + /// Return an error (irrespective of `ocsp_response` value) + Reject, +} + +#[derive(Debug)] +struct FixedSignatureSchemeSigningKey { + key: Box, + scheme: SignatureScheme, +} + +impl SigningKey for FixedSignatureSchemeSigningKey { + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { + if offered.contains(&self.scheme) { + self.key.choose_scheme(&[self.scheme]) + } else { + self.key.choose_scheme(&[]) + } + } + + fn public_key(&self) -> Option> { + self.key.public_key() + } +} + +#[derive(Debug)] +struct FixedSignatureSchemeServerCertResolver { + credentials: Credentials, + scheme: SignatureScheme, +} + +impl server::ServerCredentialResolver for FixedSignatureSchemeServerCertResolver { + fn resolve(&self, client_hello: &ClientHello<'_>) -> Result { + if !client_hello + .signature_schemes() + .contains(&self.scheme) + { + return Err(Error::PeerIncompatible( + PeerIncompatible::NoSignatureSchemesInCommon, + )); + } + + self.credentials + .signer(&[self.scheme]) + .ok_or(Error::PeerIncompatible( + PeerIncompatible::NoSignatureSchemesInCommon, + )) + } +} + +#[derive(Debug, Default)] +struct MultipleClientCredentialResolver { + additional: Vec, + default: Option, + expect_selected: Option, +} + +impl MultipleClientCredentialResolver { + fn add(&mut self, key: Credentials, meta: &Credential) { + self.additional + .push(ClientCert::new(key, meta)); + } + + fn set_default(&mut self, key: Credentials, meta: &Credential) { + self.default = Some(ClientCert::new(key, meta)); + } +} + +impl client::ClientCredentialResolver for MultipleClientCredentialResolver { + fn resolve(&self, request: &CredentialRequest<'_>) -> Option { + // `sig_schemes` is in server preference order, so respect that. + let sig_schemes = request.signature_schemes(); + let root_hint_subjects = request.root_hint_subjects(); + for sig_scheme in sig_schemes.iter().copied() { + for (i, cert) in self.additional.iter().enumerate() { + // if the server sends any issuer hints, respect them + if cert.must_match_issuer && !cert.any_issuer_matches_hints(root_hint_subjects) { + continue; + } + + if let Some(signer) = cert.certkey.signer(&[sig_scheme]) { + assert!( + Some(i as isize) == self.expect_selected || self.expect_selected.is_none() + ); + return Some(signer); + } + } + } + + if let Some(cert) = &self.default { + if let Some(signer) = cert.certkey.signer(sig_schemes) { + assert!(matches!(self.expect_selected, Some(-1) | None)); + return Some(signer); + } + } + + assert_eq!(self.expect_selected, None); + + let all_must_match_issuer = self + .additional + .iter() + .chain(self.default.iter()) + .all(|item| item.must_match_issuer); + + quit(match all_must_match_issuer { + true => ":NO_MATCHING_ISSUER:", + false => ":NO_COMMON_SIGNATURE_ALGORITHMS:", + }) + } + + fn supported_certificate_types(&self) -> &'static [CertificateType] { + match self.default.is_some() || !self.additional.is_empty() { + true => &[CertificateType::X509], + false => &[], + } + } + + fn hash_config(&self, _: &mut dyn Hasher) {} +} + +#[derive(Debug)] +struct ClientCert { + certkey: Credentials, + issuer_names: Vec, + must_match_issuer: bool, +} + +impl ClientCert { + fn new(mut certkey: Credentials, meta: &Credential) -> Self { + let Identity::X509(id) = &*certkey.identity else { + panic!("only X.509 client certs supported"); + }; + + let mut issuer_names = Vec::new(); + for cert in [&id.end_entity] + .into_iter() + .chain(id.intermediates.iter()) + { + let parsed_cert = webpki::EndEntityCert::try_from(cert).unwrap(); + issuer_names.push(DistinguishedName::in_sequence(parsed_cert.issuer())); + } + + if let Some(scheme) = meta.use_signing_scheme { + certkey.key = Box::new(FixedSignatureSchemeSigningKey { + key: certkey.key, + scheme: lookup_scheme(scheme), + }); + } + + Self { + certkey, + issuer_names, + must_match_issuer: meta.must_match_issuer, + } + } + + fn any_issuer_matches_hints(&self, hints: &[DistinguishedName]) -> bool { + hints.iter().any(|dn| { + self.issuer_names + .iter() + .any(|issuer| dn.as_ref() == issuer.as_ref()) + }) + } +} + +fn lookup_scheme(scheme: u16) -> SignatureScheme { + match scheme { + 0x0401 => SignatureScheme::RSA_PKCS1_SHA256, + 0x0501 => SignatureScheme::RSA_PKCS1_SHA384, + 0x0601 => SignatureScheme::RSA_PKCS1_SHA512, + 0x0403 => SignatureScheme::ECDSA_NISTP256_SHA256, + 0x0503 => SignatureScheme::ECDSA_NISTP384_SHA384, + 0x0603 => SignatureScheme::ECDSA_NISTP521_SHA512, + 0x0804 => SignatureScheme::RSA_PSS_SHA256, + 0x0805 => SignatureScheme::RSA_PSS_SHA384, + 0x0806 => SignatureScheme::RSA_PSS_SHA512, + 0x0807 => SignatureScheme::ED25519, + // TODO: add support for Ed448 + // 0x0808 => SignatureScheme::ED448, + _ => { + eprintln!("Unsupported signature scheme {:04x}", scheme); + process::exit(BOGO_NACK); + } + } +} + +#[derive(Debug)] +struct ServerCacheWithResumptionDelay { + delay: u32, + storage: Arc, +} + +impl ServerCacheWithResumptionDelay { + fn new(delay: u32) -> Arc { + Arc::new(Self { + delay, + storage: server::ServerSessionMemoryCache::new(32), + }) + } +} + +fn align_time() { + /* we don't have an injectable clock source in rustls' public api, and + * resumption timing is in seconds resolution, so tests that use + * resumption_delay tend to be flickery if the seconds time ticks + * during this. + * + * this function delays until a fresh second ticks, which alleviates + * this. gross! + */ + fn sample() -> u64 { + time::SystemTime::now() + .duration_since(time::SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() + } + + let start_secs = sample(); + while start_secs == sample() { + thread::sleep(time::Duration::from_millis(20)); + } +} + +impl server::StoresServerSessions for ServerCacheWithResumptionDelay { + fn put(&self, key: ServerSessionKey<'_>, mut value: Vec) -> bool { + // The creation time should be stored directly after the 2-byte version discriminant. + let creation_time_sec = &mut value[2..10]; + let original = u64::from_be_bytes(creation_time_sec.try_into().unwrap()); + let delayed = original - self.delay as u64; + creation_time_sec.copy_from_slice(&delayed.to_be_bytes()); + self.storage.put(key, value) + } + + fn get(&self, key: ServerSessionKey<'_>) -> Option> { + self.storage.get(key) + } + + fn take(&self, key: ServerSessionKey<'_>) -> Option> { + self.storage.take(key) + } + + fn can_cache(&self) -> bool { + self.storage.can_cache() + } +} + +fn make_server_cfg(opts: &Options, key_log: &Arc) -> Arc { + let client_auth = + if opts.verify_peer || opts.offer_no_client_cas || opts.require_any_client_cert { + Arc::new(DummyClientAuth::new( + &opts.trusted_cert_file, + opts.require_any_client_cert, + Arc::from(opts.root_hint_subjects.clone()), + )) + } else { + WebPkiClientVerifier::no_client_auth() + }; + + assert!( + opts.credentials.additional.is_empty(), + "TODO: server certificate switching not implemented yet" + ); + let cred = &opts.credentials.default; + let provider = opts.provider(); + let mut credentials = cred.load_from_file(&provider); + credentials.ocsp = Some(opts.server_ocsp_response.clone()); + + let cert_resolver = match cred.use_signing_scheme { + Some(scheme) => Arc::new(FixedSignatureSchemeServerCertResolver { + credentials, + scheme: lookup_scheme(scheme), + }) as Arc, + None => Arc::new(SingleCredential::from(credentials)), + }; + + let mut cfg = ServerConfig::builder(Arc::new(provider)) + .with_client_cert_verifier(client_auth) + .with_server_credential_resolver(cert_resolver) + .unwrap(); + + cfg.session_storage = ServerCacheWithResumptionDelay::new(opts.resumption_delay); + cfg.max_fragment_size = opts.max_fragment; + cfg.send_tls13_tickets = 1; + cfg.require_ems = opts.require_ems; + cfg.cipher_suite_selector = match opts.server_preference { + true => &PreferServerOrder, + false => &PreferClientOrder, + }; + + if opts.export_traffic_secrets { + cfg.key_log = key_log.clone(); + } + + if opts.tickets { + cfg.ticketer = Some( + cfg.crypto_provider() + .ticketer_factory + .ticketer() + .unwrap(), + ); + } else if opts.resumes == 0 { + cfg.session_storage = Arc::new(server::NoServerSessionStorage {}); + } + + if !opts.protocols.is_empty() { + cfg.alpn_protocols = opts + .protocols + .iter() + .map(|proto| ApplicationProtocol::from(proto.as_bytes()).to_owned()) + .collect::>(); + } + + if opts.reject_alpn { + cfg.alpn_protocols = vec![ApplicationProtocol::from(b"invalid")]; + } + + if opts.enable_early_data { + // see kMaxEarlyDataAccepted in boringssl, which bogo validates + cfg.max_early_data_size = 14336; + cfg.send_half_rtt_data = true; + } + + match opts.install_cert_compression_algs { + CompressionAlgs::All => { + cfg.cert_compressors = vec![&ExpandingAlgorithm, &ShrinkingAlgorithm, &RandomAlgorithm]; + cfg.cert_decompressors = + vec![&ExpandingAlgorithm, &ShrinkingAlgorithm, &RandomAlgorithm]; + } + CompressionAlgs::One(ShrinkingAlgorithm::ALGORITHM) => { + cfg.cert_compressors = vec![&ShrinkingAlgorithm]; + cfg.cert_decompressors = vec![&ShrinkingAlgorithm]; + } + CompressionAlgs::None => {} + _ => unimplemented!(), + } + + Arc::new(cfg) +} + +struct ClientCacheWithSpecificKxHints { + delay: u32, + kx_hint: Option, + storage: Arc, +} + +impl ClientCacheWithSpecificKxHints { + fn new(delay: u32, kx_hint: Option) -> Arc { + Arc::new(Self { + delay, + kx_hint, + storage: Arc::new(client::ClientSessionMemoryCache::new(32)), + }) + } +} + +impl client::ClientSessionStore for ClientCacheWithSpecificKxHints { + fn set_kx_hint(&self, _: ClientSessionKey<'static>, _: NamedGroup) {} + fn kx_hint(&self, _: &ClientSessionKey<'_>) -> Option { + self.kx_hint + } + + fn set_tls12_session(&self, key: ClientSessionKey<'static>, mut value: client::Tls12Session) { + value.rewind_epoch(self.delay); + self.storage + .set_tls12_session(key, value); + } + + fn tls12_session(&self, key: &ClientSessionKey<'_>) -> Option { + self.storage.tls12_session(key) + } + + fn remove_tls12_session(&self, key: &ClientSessionKey<'static>) { + self.storage.remove_tls12_session(key); + } + + fn insert_tls13_ticket(&self, key: ClientSessionKey<'static>, mut value: Tls13Session) { + value.rewind_epoch(self.delay); + self.storage + .insert_tls13_ticket(key, value) + } + + fn take_tls13_ticket(&self, key: &ClientSessionKey<'static>) -> Option { + self.storage.take_tls13_ticket(key) + } +} + +impl Debug for ClientCacheWithSpecificKxHints { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + // Note: we omit self.storage here as it may contain sensitive data. + f.debug_struct("ClientCacheWithoutKxHints") + .field("delay", &self.delay) + .finish_non_exhaustive() + } +} + +fn make_client_cfg(opts: &Options, key_log: &Arc) -> Arc { + let provider = Arc::new(opts.provider()); + let cfg = ClientConfig::builder(provider.clone()); + + let cfg = if opts.selected_provider.supports_ech() { + let ech_cfg = ClientConfig::builder( + CryptoProvider { + tls12_cipher_suites: Default::default(), + ..opts.provider() + } + .into(), + ); + + if let Some(ech_config_list) = &opts.ech_config_list { + let ech_mode: EchMode = EchConfig::new(ech_config_list.clone(), ALL_HPKE_SUITES) + .unwrap_or_else(|_| quit(":INVALID_ECH_CONFIG_LIST:")) + .into(); + + ech_cfg.with_ech(ech_mode) + } else if opts.enable_ech_grease { + let ech_mode = EchMode::Grease(EchGreaseConfig::new( + GREASE_HPKE_SUITE, + HpkePublicKey(GREASE_25519_PUBKEY.to_vec()), + )); + + ech_cfg.with_ech(ech_mode) + } else { + cfg + } + } else { + cfg + }; + + let cfg = cfg + .dangerous() + .with_custom_certificate_verifier(Arc::new(DummyServerAuth::new( + &opts.trusted_cert_file, + opts.ocsp, + ))); + + let mut cfg = match opts.credentials.configured() { + true => { + let mut resolver = MultipleClientCredentialResolver { + expect_selected: opts.credentials.expect_selected, + ..Default::default() + }; + + if opts.credentials.default.configured() { + let cred = &opts.credentials.default; + resolver.set_default(cred.load_from_file(&provider), cred) } - _ => { - println!("unhandled option {:?}", arg); - process::exit(1); + for cred in opts.credentials.additional.iter() { + resolver.add(cred.load_from_file(&provider), cred); } + + cfg.with_client_credential_resolver(Arc::new(resolver)) + .unwrap() } + false => cfg.with_no_client_auth().unwrap(), + }; + + cfg.resumption = Resumption::store(ClientCacheWithSpecificKxHints::new( + opts.resumption_delay, + opts.server_supported_group_hint, + )) + .tls12_resumption(match opts.tickets { + true => Tls12Resumption::SessionIdOrTickets, + false => Tls12Resumption::SessionIdOnly, + }); + cfg.enable_sni = opts.use_sni; + cfg.max_fragment_size = opts.max_fragment; + cfg.require_ems = opts.require_ems; + if opts.export_traffic_secrets { + cfg.key_log = key_log.clone(); } - println!("opts {:?}", opts); + if !opts.protocols.is_empty() { + cfg.alpn_protocols = opts + .protocols + .iter() + .map(|proto| ApplicationProtocol::from(proto.as_bytes()).to_owned()) + .collect(); + } - let (mut client_cfg, mut server_cfg) = match opts.side { - Side::Client => (Some(make_client_cfg(&opts)), None), - Side::Server => (None, Some(make_server_cfg(&opts))), - }; + if opts.enable_early_data { + cfg.enable_early_data = true; + } - fn make_session( - opts: &Options, - scfg: &Option>, - ccfg: &Option>, - ) -> Connection { - assert!(opts.quic_transport_params.is_empty()); - assert!(opts - .expect_quic_transport_params - .is_empty()); + match opts.install_cert_compression_algs { + CompressionAlgs::All => { + cfg.cert_decompressors = + vec![&ExpandingAlgorithm, &ShrinkingAlgorithm, &RandomAlgorithm]; + cfg.cert_compressors = vec![&ExpandingAlgorithm, &ShrinkingAlgorithm, &RandomAlgorithm]; + } + CompressionAlgs::One(ShrinkingAlgorithm::ALGORITHM) => { + cfg.cert_decompressors = vec![&ShrinkingAlgorithm]; + cfg.cert_compressors = vec![&ShrinkingAlgorithm]; + } + CompressionAlgs::None => {} + _ => unimplemented!(), + } - if opts.side == Side::Server { - let scfg = Arc::clone(scfg.as_ref().unwrap()); - ServerConnection::new(scfg) - .unwrap() - .into() - } else { - let server_name = ServerName::try_from(opts.host_name.as_str()) - .unwrap() - .to_owned(); - let ccfg = Arc::clone(ccfg.as_ref().unwrap()); + Arc::new(cfg) +} - ClientConnection::new(ccfg, server_name) - .unwrap() - .into() +fn quit(why: &str) -> ! { + eprintln!("{}", why); + process::exit(0) +} + +fn quit_err(why: &str) -> ! { + eprintln!("{}", why); + process::exit(1) +} + +fn handle_err(opts: &Options, err: Error) -> ! { + println!("TLS error: {err:?}"); + + match err { + Error::InappropriateHandshakeMessage { .. } | Error::InappropriateMessage { .. } => { + quit(":UNEXPECTED_MESSAGE:") + } + Error::AlertReceived(AlertDescription::RecordOverflow) => { + quit(":TLSV1_ALERT_RECORD_OVERFLOW:") + } + Error::AlertReceived(AlertDescription::HandshakeFailure) => quit(":HANDSHAKE_FAILURE:"), + Error::AlertReceived(AlertDescription::ProtocolVersion) => quit(":WRONG_VERSION:"), + Error::AlertReceived(AlertDescription::InternalError) => { + quit(":PEER_ALERT_INTERNAL_ERROR:") + } + Error::InvalidMessage( + InvalidMessage::MissingData("AlertDescription") + | InvalidMessage::TrailingData("AlertMessagePayload"), + ) => quit(":BAD_ALERT:"), + Error::InvalidMessage( + InvalidMessage::TrailingData("ChangeCipherSpecPayload") | InvalidMessage::InvalidCcs, + ) => quit(":BAD_CHANGE_CIPHER_SPEC:"), + Error::InvalidMessage( + InvalidMessage::EmptyTicketValue | InvalidMessage::IllegalEmptyList(_), + ) => quit(":DECODE_ERROR:"), + Error::InvalidMessage( + InvalidMessage::InvalidKeyUpdate + | InvalidMessage::MissingData(_) + | InvalidMessage::TrailingData(_) + | InvalidMessage::UnexpectedMessage("HelloRetryRequest") + | InvalidMessage::NoSignatureSchemes + | InvalidMessage::UnsupportedCompression, + ) => quit(":BAD_HANDSHAKE_MSG:"), + Error::InvalidMessage(InvalidMessage::InvalidCertRequest) + | Error::InvalidMessage(InvalidMessage::InvalidDhParams) + | Error::InvalidMessage(InvalidMessage::MissingKeyExchange) => quit(":BAD_HANDSHAKE_MSG:"), + Error::InvalidMessage(InvalidMessage::InvalidContentType) + | Error::InvalidMessage(InvalidMessage::InvalidEmptyPayload) + | Error::InvalidMessage(InvalidMessage::UnknownProtocolVersion) + | Error::InvalidMessage( + InvalidMessage::MessageTooLarge | InvalidMessage::CertificatePayloadTooLarge, + ) => quit(":GARBAGE:"), + Error::InvalidMessage(InvalidMessage::MessageTooShort) + if opts.enable_ech_grease || opts.ech_config_list.is_some() => + { + quit(":ERROR_PARSING_EXTENSION:") + } + Error::InvalidMessage(InvalidMessage::DuplicateExtension(_)) => { + quit(":DUPLICATE_EXTENSION:") + } + Error::InvalidMessage(InvalidMessage::UnknownHelloRetryRequestExtension) + | Error::InvalidMessage(InvalidMessage::UnknownCertificateExtension) => { + quit(":UNEXPECTED_EXTENSION:") + } + Error::InvalidMessage(InvalidMessage::UnexpectedMessage(_)) => quit(":GARBAGE:"), + Error::InvalidMessage(InvalidMessage::PreSharedKeyIsNotFinalExtension) => { + quit(":PRE_SHARED_KEY_MUST_BE_LAST:") + } + Error::DecryptError if opts.ech_config_list.is_some() => { + quit(":INCONSISTENT_ECH_NEGOTIATION:") + } + Error::DecryptError => quit(":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:"), + Error::NoApplicationProtocol => quit(":NO_APPLICATION_PROTOCOL:"), + Error::PeerIncompatible( + PeerIncompatible::ServerSentHelloRetryRequestWithUnknownExtension, + ) => quit(":UNEXPECTED_EXTENSION:"), + Error::RejectedEch(rejected_err) => { + if let Some(expected_configs) = &opts.expect_ech_retry_configs { + assert_eq!( + rejected_err.retry_configs().as_ref(), + Some(expected_configs) + ); + } + quit(":ECH_REJECTED:") + } + Error::PeerIncompatible(PeerIncompatible::NoCipherSuitesInCommon) => { + quit(":NO_SHARED_CIPHER:") + } + Error::PeerIncompatible(_) => quit(":INCOMPATIBLE:"), + Error::PeerMisbehaved(PeerMisbehaved::MissingPskModesExtension) => { + quit(":MISSING_EXTENSION:") + } + Error::PeerMisbehaved(PeerMisbehaved::TooMuchEarlyDataReceived) => { + quit(":TOO_MUCH_READ_EARLY_DATA:") + } + Error::PeerMisbehaved(PeerMisbehaved::SignedHandshakeWithUnadvertisedSigScheme) + | Error::PeerMisbehaved(PeerMisbehaved::SignedKxWithWrongAlgorithm) => { + quit(":WRONG_SIGNATURE_TYPE:") + } + Error::PeerMisbehaved(PeerMisbehaved::SelectedUnofferedCertCompression) => { + quit(":UNKNOWN_CERT_COMPRESSION_ALG:") + } + Error::PeerMisbehaved(PeerMisbehaved::InvalidCertCompression) => { + quit(":CERT_DECOMPRESSION_FAILED:") + } + Error::PeerMisbehaved(PeerMisbehaved::OfferedDuplicateCertificateCompressions) => { + quit(":ERROR_PARSING_EXTENSION:") + } + Error::PeerMisbehaved(PeerMisbehaved::SelectedUnofferedCipherSuite) => { + quit(":WRONG_CIPHER_RETURNED:") + } + Error::PeerMisbehaved(PeerMisbehaved::TooManyWarningAlertsReceived) => { + quit(":TOO_MANY_WARNING_ALERTS:") + } + Error::PeerMisbehaved( + PeerMisbehaved::TooManyConsecutiveHandshakeMessagesAfterHandshake, + ) => quit(":TOO_MANY_KEY_UPDATES:"), + Error::PeerMisbehaved(PeerMisbehaved::MissingKeyShare) => quit(":MISSING_KEY_SHARE:"), + Error::PeerMisbehaved(PeerMisbehaved::OfferedDuplicateKeyShares) => { + quit(":DUPLICATE_KEY_SHARE:") + } + Error::PeerMisbehaved(PeerMisbehaved::IllegalMiddleboxChangeCipherSpec) => { + quit(":ILLEGAL_MIDDLEBOX_CHANGE_CIPHER_SPEC:") + } + Error::PeerMisbehaved(PeerMisbehaved::EarlyDataExtensionWithoutResumption) => { + quit(":UNEXPECTED_EXTENSION:") + } + Error::PeerMisbehaved(PeerMisbehaved::EarlyDataOfferedWithVariedCipherSuite) => { + quit(":CIPHER_MISMATCH_ON_EARLY_DATA:") + } + Error::PeerMisbehaved(PeerMisbehaved::ServerEchoedCompatibilitySessionId) => { + quit(":SERVER_ECHOED_INVALID_SESSION_ID:") + } + Error::PeerMisbehaved(PeerMisbehaved::TooManyEmptyFragments) => { + quit(":TOO_MANY_EMPTY_FRAGMENTS:") + } + Error::PeerMisbehaved(PeerMisbehaved::IllegalHelloRetryRequestWithInvalidEch) + | Error::PeerMisbehaved(PeerMisbehaved::UnsolicitedEchExtension) => { + quit(":UNEXPECTED_EXTENSION:") + } + Error::PeerMisbehaved( + PeerMisbehaved::UnsolicitedEncryptedExtension + | PeerMisbehaved::UnsolicitedServerHelloExtension + | PeerMisbehaved::UnexpectedCleartextExtension + | PeerMisbehaved::UnsolicitedCertExtension, + ) => quit(":UNEXPECTED_EXTENSION:"), + Error::PeerMisbehaved(PeerMisbehaved::DisallowedEncryptedExtension) => { + quit(":ERROR_PARSING_EXTENSION:") + } + Error::PeerMisbehaved(PeerMisbehaved::IllegalHelloRetryRequestWithOfferedGroup) => { + quit(":ILLEGAL_HELLO_RETRY_REQUEST_WITH_OFFERED_GROUP:") + } + Error::PeerMisbehaved(PeerMisbehaved::IllegalHelloRetryRequestWithUnofferedNamedGroup) => { + quit(":ILLEGAL_HELLO_RETRY_REQUEST_WITH_UNOFFERED_GROUP:") + } + Error::PeerMisbehaved(PeerMisbehaved::IllegalHelloRetryRequestWithNoChanges) => { + quit(":EMPTY_HELLO_RETRY_REQUEST:") + } + Error::PeerMisbehaved(PeerMisbehaved::DuplicateHelloRetryRequestExtensions) => { + quit(":DUPLICATE_HELLO_RETRY_REQUEST_EXTENSIONS:") + } + Error::PeerMisbehaved(PeerMisbehaved::SelectedTls12UsingTls13VersionExtension) => { + quit(":SELECTED_TLS12_USING_TLS13_VERSION_EXTENSION:") + } + Error::PeerMisbehaved(PeerMisbehaved::OfferedIncorrectCompressions) => { + quit(":INVALID_COMPRESSION_LIST:") + } + Error::PeerMisbehaved(PeerMisbehaved::SelectedUnofferedCompression) => { + quit(":UNSUPPORTED_COMPRESSION_ALGORITHM:") + } + Error::PeerMisbehaved(PeerMisbehaved::WrongGroupForKeyShare) => quit(":WRONG_CURVE:"), + Error::PeerMisbehaved(PeerMisbehaved::SelectedUnofferedKxGroup) => quit(":WRONG_CURVE:"), + Error::PeerMisbehaved(PeerMisbehaved::RefusedToFollowHelloRetryRequest) => { + quit(":WRONG_CURVE:") + } + Error::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare) => quit(":BAD_ECPOINT:"), + Error::PeerMisbehaved(PeerMisbehaved::MessageInterleavedWithHandshakeMessage) => { + quit(":UNEXPECTED_MESSAGE:") + } + Error::PeerMisbehaved(PeerMisbehaved::KeyEpochWithPendingFragment) => { + quit(":EXCESS_HANDSHAKE_DATA:") + } + Error::PeerMisbehaved(PeerMisbehaved::NoCertificatesPresented) => quit(":NO_CERTS:"), + Error::PeerMisbehaved(PeerMisbehaved::OfferedEarlyDataWithOldProtocolVersion) => { + quit(":WRONG_VERSION_ON_EARLY_DATA:") + } + Error::PeerMisbehaved(PeerMisbehaved::SelectedUnofferedApplicationProtocol) => { + quit(":INVALID_ALPN_PROTOCOL:") + } + Error::PeerMisbehaved(PeerMisbehaved::SelectedDifferentCipherSuiteAfterRetry) => { + quit(":SELECTED_DIFFERENT_CIPHERSUITE_AFTER_RETRY:") + } + Error::PeerMisbehaved( + PeerMisbehaved::ResumptionAttemptedWithVariedEms + | PeerMisbehaved::ResumptionOfferedWithVariedEms, + ) => quit(":RESUMED_SESSION_WITH_VARIED_EMS:"), + Error::PeerMisbehaved(PeerMisbehaved::IllegalTlsInnerPlaintext) => { + quit(":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:") + } + Error::PeerMisbehaved(PeerMisbehaved::SelectedInvalidPsk) => { + quit(":PSK_IDENTITY_NOT_FOUND:") + } + Error::PeerMisbehaved(PeerMisbehaved::PskExtensionWithMismatchedIdsAndBinders) => { + quit(":PSK_IDENTITY_BINDER_COUNT_MISMATCH:") + } + Error::PeerMisbehaved(PeerMisbehaved::ResumptionOfferedWithIncompatibleCipherSuite) => { + quit(":OLD_SESSION_PRF_HASH_MISMATCH:") + } + Error::PeerMisbehaved(PeerMisbehaved::PskExtensionMustBeLast) => { + quit(":PRE_SHARED_KEY_MUST_BE_LAST:") + } + Error::PeerMisbehaved( + PeerMisbehaved::IncorrectBinder | PeerMisbehaved::IncorrectFinished, + ) => quit(":DIGEST_CHECK_FAILED:"), + Error::PeerMisbehaved(PeerMisbehaved::ServerHelloMustOfferUncompressedEcPoints) => { + quit(":SERVER_HELLO_MUST_OFFER_UNCOMPRESSED_EC_POINTS:") + } + Error::PeerMisbehaved(PeerMisbehaved::AttemptedDowngradeToTls12WhenTls13IsSupported) => { + quit(":TLS13_DOWNGRADE:") + } + Error::PeerMisbehaved(PeerMisbehaved::RejectedEarlyDataInterleavedWithHandshakeMessage) => { + quit(":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:") + } + Error::PeerMisbehaved( + PeerMisbehaved::IllegalAlertLevel(_, _) | PeerMisbehaved::IllegalWarningAlert(_), + ) => quit(":BAD_ALERT:"), + Error::PeerMisbehaved(_) => panic!("!!! please add error mapping for {err:?}"), + Error::AlertReceived(AlertDescription::UnexpectedMessage) => quit(":BAD_ALERT:"), + Error::AlertReceived(AlertDescription::DecompressionFailure) => { + quit_err(":SSLV3_ALERT_DECOMPRESSION_FAILURE:") + } + Error::InvalidCertificate(CertificateError::BadEncoding) => { + quit(":CANNOT_PARSE_LEAF_CERT:") + } + Error::InvalidCertificate(CertificateError::BadSignature) => quit(":BAD_SIGNATURE:"), + Error::InvalidCertificate( + CertificateError::UnsupportedSignatureAlgorithm { .. } + | CertificateError::UnsupportedSignatureAlgorithmForPublicKey { .. }, + ) => quit(":WRONG_SIGNATURE_TYPE:"), + Error::InvalidCertificate(CertificateError::InvalidOcspResponse) => { + // note: only use is in this file. + quit(":OCSP_CB_ERROR:") + } + Error::InvalidCertificate(e) => quit(&format!(":BAD_CERT: ({e:?})")), + Error::PeerSentOversizedRecord => quit(":DATA_LENGTH_TOO_LONG:"), + _ => { + eprintln!("unhandled error: {:?}", err); + quit(":FIXME:") } } +} - for i in 0..opts.resumes + 1 { - let sess = make_session(&opts, &server_cfg, &client_cfg); - exec(&opts, sess, i); - if opts.resume_with_tickets_disabled { - opts.tickets = false; - server_cfg = Some(make_server_cfg(&opts)); - } - if opts.on_resume_ech_config_list.is_some() { - opts.ech_config_list - .clone_from(&opts.on_resume_ech_config_list); - opts.expect_ech_accept = opts.on_resume_expect_ech_accept; - client_cfg = Some(make_client_cfg(&opts)); +#[derive(Debug, Default)] +struct KeyLogMemo(Mutex); + +impl KeyLogMemo { + fn clone_inner(&self) -> KeyLogMemoInner { + self.0.lock().unwrap().clone() + } +} + +impl rustls::KeyLog for KeyLogMemo { + fn log(&self, label: &str, _client_random: &[u8], secret: &[u8]) { + match label { + "CLIENT_TRAFFIC_SECRET_0" => { + self.0 + .lock() + .unwrap() + .client_traffic_secret = secret.to_vec() + } + "SERVER_TRAFFIC_SECRET_0" => { + self.0 + .lock() + .unwrap() + .server_traffic_secret = secret.to_vec() + } + _ => {} } - opts.expect_handshake_kind - .clone_from(&opts.expect_handshake_kind_resumed); } + + fn will_log(&self, _label: &str) -> bool { + true + } +} + +#[derive(Clone, Debug, Default)] +struct KeyLogMemoInner { + client_traffic_secret: Vec, + server_traffic_secret: Vec, +} + +#[derive(Debug, PartialEq)] +enum Side { + Client, + Server, } #[derive(Debug, PartialEq)] @@ -1759,7 +2178,7 @@ impl ShrinkingAlgorithm { impl compress::CertDecompressor for ShrinkingAlgorithm { fn algorithm(&self) -> CertificateCompressionAlgorithm { - CertificateCompressionAlgorithm::Unknown(Self::ALGORITHM) + CertificateCompressionAlgorithm(Self::ALGORITHM) } fn decompress( @@ -1778,7 +2197,7 @@ impl compress::CertDecompressor for ShrinkingAlgorithm { impl compress::CertCompressor for ShrinkingAlgorithm { fn algorithm(&self) -> CertificateCompressionAlgorithm { - CertificateCompressionAlgorithm::Unknown(Self::ALGORITHM) + CertificateCompressionAlgorithm(Self::ALGORITHM) } fn compress( @@ -1797,7 +2216,7 @@ struct ExpandingAlgorithm; impl compress::CertDecompressor for ExpandingAlgorithm { fn algorithm(&self) -> CertificateCompressionAlgorithm { - CertificateCompressionAlgorithm::Unknown(0xff02) + CertificateCompressionAlgorithm(0xff02) } fn decompress( @@ -1818,7 +2237,7 @@ impl compress::CertDecompressor for ExpandingAlgorithm { impl compress::CertCompressor for ExpandingAlgorithm { fn algorithm(&self) -> CertificateCompressionAlgorithm { - CertificateCompressionAlgorithm::Unknown(0xff02) + CertificateCompressionAlgorithm(0xff02) } fn compress( @@ -1839,7 +2258,7 @@ struct RandomAlgorithm; impl compress::CertDecompressor for RandomAlgorithm { fn algorithm(&self) -> CertificateCompressionAlgorithm { - CertificateCompressionAlgorithm::Unknown(0xff03) + CertificateCompressionAlgorithm(0xff03) } fn decompress( @@ -1857,7 +2276,7 @@ impl compress::CertDecompressor for RandomAlgorithm { impl compress::CertCompressor for RandomAlgorithm { fn algorithm(&self) -> CertificateCompressionAlgorithm { - CertificateCompressionAlgorithm::Unknown(0xff03) + CertificateCompressionAlgorithm(0xff03) } fn compress( @@ -1868,7 +2287,7 @@ impl compress::CertCompressor for RandomAlgorithm { let random_byte = { let mut bytes = [0]; // nb. provider is irrelevant for this use - ring::default_provider() + rustls_ring::DEFAULT_PROVIDER .secure_random .fill(&mut bytes) .unwrap(); @@ -1903,3 +2322,7 @@ static ALL_HPKE_SUITES: &[&dyn Hpke] = &[ hpke::DH_KEM_X25519_HKDF_SHA256_AES_256, hpke::DH_KEM_X25519_HKDF_SHA256_CHACHA20_POLY1305, ]; + +static BOGO_NACK: i32 = 89; + +const MAX_MESSAGE_SIZE: usize = 0xffff + 5; diff --git a/ci-bench/Cargo.toml b/ci-bench/Cargo.toml index 6fde65b814e..f9abca8ee7b 100644 --- a/ci-bench/Cargo.toml +++ b/ci-bench/Cargo.toml @@ -11,13 +11,17 @@ anyhow = { workspace = true } async-trait = { workspace = true } byteorder = { workspace = true } clap = { workspace = true } -fxhash = { workspace = true } itertools = { workspace = true } rayon = { workspace = true } -rustls = { path = "../rustls", features = ["ring", "aws_lc_rs"] } - -[target.'cfg(not(target_env = "msvc"))'.dependencies] -tikv-jemallocator = { workspace = true } +rustc-hash = { workspace = true } +rustls = { path = "../rustls" } +rustls-aws-lc-rs = { path = "../rustls-aws-lc-rs" } +rustls-ring = { path = "../rustls-ring" } +rustls-test = { workspace = true, default-features = false } +rustls-fuzzing-provider = { workspace = true } [target.'cfg(target_os = "linux")'.dependencies] crabgrind = { workspace = true } + +[lints] +workspace = true diff --git a/ci-bench/README.md b/ci-bench/README.md index 8cab18e0231..924c71d8571 100644 --- a/ci-bench/README.md +++ b/ci-bench/README.md @@ -132,7 +132,7 @@ The solution was to: that complete after a single `poll`. This way we avoid using an async runtime, which could introduce non-determinism. 3. Use non-blocking operations under the hood in wall-time mode, which simulate IO through shared - in-memory buffers. The server and client `Future`s are polled in turns, so again we we avoid + in-memory buffers. The server and client `Future`s are polled in turns, so again we avoid pulling in an async runtime and keep things as deterministic as possible. ### Why measure CPU instructions diff --git a/ci-bench/src/benchmark.rs b/ci-bench/src/benchmark.rs index 6434df68a54..94ed21d7c3a 100644 --- a/ci-bench/src/benchmark.rs +++ b/ci-bench/src/benchmark.rs @@ -1,36 +1,16 @@ +use core::borrow::Borrow; +use core::cmp; use std::sync::Arc; -use fxhash::FxHashMap; -use itertools::Itertools; +use rustc_hash::FxHashMap; +use rustls::crypto::{CryptoProvider, TicketProducer}; +use rustls_test::KeyType; -use crate::callgrind::InstructionCounts; -use crate::util::KeyType; use crate::Side; - -/// Validates a benchmark collection, returning an error if the provided benchmarks are invalid -/// -/// Benchmarks can be invalid because of the following reasons: -/// -/// - Re-using an already defined benchmark name. -pub fn validate_benchmarks(benchmarks: &[Benchmark]) -> anyhow::Result<()> { - // Detect duplicate definitions - let duplicate_names: Vec<_> = benchmarks - .iter() - .map(|b| b.name.as_str()) - .duplicates() - .collect(); - if !duplicate_names.is_empty() { - anyhow::bail!( - "The following benchmarks are defined multiple times: {}", - duplicate_names.join(", ") - ); - } - - Ok(()) -} +use crate::valgrind::InstructionCounts; /// Get the reported instruction counts for the provided benchmark -pub fn get_reported_instr_count( +pub(crate) fn get_reported_instr_count( bench: &Benchmark, results: &FxHashMap<&str, InstructionCounts>, ) -> InstructionCounts { @@ -39,7 +19,7 @@ pub fn get_reported_instr_count( /// Specifies which functionality is being benchmarked #[derive(Copy, Clone)] -pub enum BenchmarkKind { +pub(crate) enum BenchmarkKind { /// Perform the handshake and exit Handshake(ResumptionKind), /// Perform the handshake and transfer 1MB of data @@ -48,17 +28,17 @@ pub enum BenchmarkKind { impl BenchmarkKind { /// Returns the [`ResumptionKind`] used in the handshake part of the benchmark - pub fn resumption_kind(self) -> ResumptionKind { + pub(crate) fn resumption_kind(self) -> ResumptionKind { match self { - BenchmarkKind::Handshake(kind) => kind, - BenchmarkKind::Transfer => ResumptionKind::No, + Self::Handshake(kind) => kind, + Self::Transfer => ResumptionKind::No, } } } -#[derive(PartialEq, Clone, Copy)] /// The kind of resumption used during the handshake -pub enum ResumptionKind { +#[derive(PartialEq, Clone, Copy)] +pub(crate) enum ResumptionKind { /// No resumption No, /// Session ID @@ -68,10 +48,10 @@ pub enum ResumptionKind { } impl ResumptionKind { - pub const ALL: &'static [ResumptionKind] = &[Self::No, Self::SessionId, Self::Tickets]; + pub(crate) const ALL: &'static [Self] = &[Self::No, Self::SessionId, Self::Tickets]; /// Returns a user-facing label that identifies the resumption kind - pub fn label(&self) -> &'static str { + pub(crate) fn label(&self) -> &'static str { match *self { Self::No => "no_resume", Self::SessionId => "session_id", @@ -82,44 +62,48 @@ impl ResumptionKind { /// Parameters associated to a benchmark #[derive(Clone, Debug)] -pub struct BenchmarkParams { - /// Which `CryptoProvider` to test - pub provider: rustls::crypto::CryptoProvider, - /// How to make a suitable [`rustls::server::ProducesTickets`]. - pub ticketer: &'static fn() -> Arc, - /// The type of key used to sign the TLS certificate - pub key_type: KeyType, - /// Cipher suite - pub ciphersuite: rustls::SupportedCipherSuite, - /// TLS version - pub version: &'static rustls::SupportedProtocolVersion, +pub(crate) struct BenchmarkParams { + /// Which `CryptoProvider` to test. + /// + /// The choice of cipher suite is baked into this. + pub provider: Arc, + /// How to make a suitable [`rustls::crypto::TicketProducer`]. + pub ticketer: &'static fn() -> Arc, + /// Where to get keys for server auth + pub auth_key: AuthKeySource, /// A user-facing label that identifies these params pub label: String, + /// Call this once this BenchmarkParams is sure to be used + pub warm_up: Option, } impl BenchmarkParams { /// Create a new set of benchmark params - pub const fn new( - provider: rustls::crypto::CryptoProvider, - ticketer: &'static fn() -> Arc, - key_type: KeyType, - ciphersuite: rustls::SupportedCipherSuite, - version: &'static rustls::SupportedProtocolVersion, + pub(crate) const fn new( + provider: Arc, + ticketer: &'static fn() -> Arc, + auth_key: AuthKeySource, label: String, + warm_up: Option, ) -> Self { Self { provider, ticketer, - key_type, - ciphersuite, - version, + auth_key, label, + warm_up, } } } +#[derive(Clone, Debug)] +pub(crate) enum AuthKeySource { + KeyType(KeyType), + FuzzingProvider, +} + /// A benchmark specification -pub struct Benchmark { +pub(crate) struct Benchmark { /// The name of the benchmark, as shown in the benchmark results name: String, /// The benchmark kind @@ -130,17 +114,43 @@ pub struct Benchmark { impl Benchmark { /// Create a new benchmark - pub fn new(name: String, kind: BenchmarkKind, params: BenchmarkParams) -> Self { + pub(crate) fn new(name: String, kind: BenchmarkKind, params: BenchmarkParams) -> Self { Self { name, kind, params } } /// Returns the benchmark's unique name - pub fn name(&self) -> &str { + pub(crate) fn name(&self) -> &str { &self.name } /// Returns the benchmark's unique name with the side appended to it - pub fn name_with_side(&self, side: Side) -> String { + pub(crate) fn name_with_side(&self, side: Side) -> String { format!("{}_{}", self.name, side.as_str()) } } + +impl Borrow for Benchmark { + fn borrow(&self) -> &str { + &self.name + } +} + +impl PartialEq for Benchmark { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl Eq for Benchmark {} + +impl PartialOrd for Benchmark { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Benchmark { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.name.cmp(&other.name) + } +} diff --git a/ci-bench/src/callgrind.rs b/ci-bench/src/callgrind.rs deleted file mode 100644 index 2dba776762d..00000000000 --- a/ci-bench/src/callgrind.rs +++ /dev/null @@ -1,288 +0,0 @@ -use std::fs::File; -use std::io::{BufRead, BufReader}; -use std::ops::Sub; -use std::path::{Path, PathBuf}; -use std::process::{Child, Command, Stdio}; - -use anyhow::Context; - -use crate::benchmark::Benchmark; -use crate::Side; - -/// The subdirectory in which the callgrind output should be stored -const CALLGRIND_OUTPUT_SUBDIR: &str = "callgrind"; - -/// A callgrind-based benchmark runner -pub struct CallgrindRunner { - /// The path to the ci-bench executable - /// - /// This is necessary because the callgrind runner works by spawning child processes - executable: String, - /// The directory where the callgrind output will be stored - output_dir: PathBuf, -} - -impl CallgrindRunner { - /// Returns a new callgrind-based benchmark runner - pub fn new(executable: String, output_dir: PathBuf) -> anyhow::Result { - Self::ensure_callgrind_available()?; - - let callgrind_output_dir = output_dir.join(CALLGRIND_OUTPUT_SUBDIR); - std::fs::create_dir_all(&callgrind_output_dir) - .context("Failed to create callgrind output directory")?; - - Ok(CallgrindRunner { - executable, - output_dir: callgrind_output_dir, - }) - } - - /// Runs the benchmark at the specified index and returns the instruction counts for each side - pub fn run_bench( - &self, - benchmark_index: u32, - bench: &Benchmark, - ) -> anyhow::Result { - // The server and client are started as child processes, and communicate with each other - // through stdio. - - let mut server = Self::run_bench_side( - &self.executable, - benchmark_index, - Side::Server, - &bench.name_with_side(Side::Server), - Stdio::piped(), - Stdio::piped(), - &self.output_dir, - ) - .context("server side bench crashed")?; - - let client = Self::run_bench_side( - &self.executable, - benchmark_index, - Side::Client, - &bench.name_with_side(Side::Client), - Stdio::from(server.process.stdout.take().unwrap()), - Stdio::from(server.process.stdin.take().unwrap()), - &self.output_dir, - ) - .context("client side bench crashed")?; - - Ok(InstructionCounts { - server: server.wait_and_get_instr_count()?, - client: client.wait_and_get_instr_count()?, - }) - } - - /// Returns an error if callgrind is not available - fn ensure_callgrind_available() -> anyhow::Result<()> { - let result = Command::new("valgrind") - .arg("--tool=callgrind") - .arg("--version") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - - match result { - Err(e) => anyhow::bail!("Unexpected error while launching callgrind. Error: {}", e), - Ok(status) => { - if status.success() { - Ok(()) - } else { - anyhow::bail!("Failed to launch callgrind. Error: {}. Please ensure that valgrind is installed and on the $PATH.", status) - } - } - } - } - - /// See docs for [`Self::run_bench`] - fn run_bench_side( - executable: &str, - benchmark_index: u32, - side: Side, - name: &str, - stdin: Stdio, - stdout: Stdio, - output_dir: &Path, - ) -> anyhow::Result { - let callgrind_output_file = output_dir.join(name); - let callgrind_log_file = output_dir.join(format!("{name}.log")); - - // Run under setarch to disable ASLR, to reduce noise - let mut cmd = Command::new("setarch"); - let child = cmd - .arg("-R") - .arg("valgrind") - .arg("--tool=callgrind") - // Do not count instructions from the start, instead this is controlled by `CountInstructions` - .arg("--collect-atstart=no") - // Disable the cache simulation, since we are only interested in instruction counts - .arg("--cache-sim=no") - // Save callgrind's logs, which would otherwise be printed to stderr (we want to - // keep stderr free of noise, to see any errors from the child process) - .arg(format!("--log-file={}", callgrind_log_file.display())) - // The file where the instruction counts will be stored - .arg(format!( - "--callgrind-out-file={}", - callgrind_output_file.display() - )) - .arg(executable) - .arg("run-single") - .arg(benchmark_index.to_string()) - .arg(side.as_str()) - .stdin(stdin) - .stdout(stdout) - .stderr(Stdio::inherit()) - .spawn() - .context("Failed to run benchmark in callgrind")?; - - Ok(BenchSubprocess { - process: child, - callgrind_output_file, - }) - } -} - -/// A running subprocess for one of the sides of the benchmark (client or server) -struct BenchSubprocess { - /// The benchmark's child process, running under callgrind - process: Child, - /// Callgrind's output file for this benchmark - callgrind_output_file: PathBuf, -} - -impl BenchSubprocess { - /// Waits for the process to finish and returns the measured instruction count - fn wait_and_get_instr_count(mut self) -> anyhow::Result { - let status = self - .process - .wait() - .context("Failed to run benchmark in callgrind")?; - if !status.success() { - anyhow::bail!( - "Failed to run benchmark in callgrind. Exit code: {:?}", - status.code() - ); - } - - let instruction_count = parse_callgrind_output(&self.callgrind_output_file)?; - Ok(instruction_count) - } -} - -/// Returns the instruction count, extracted from the callgrind output file at the provided path -fn parse_callgrind_output(file: &Path) -> anyhow::Result { - let file_in = File::open(file).context("Unable to open callgrind output file")?; - - for line in BufReader::new(file_in).lines() { - let line = line.context("Error reading callgrind output file")?; - if let Some(line) = line.strip_prefix("summary: ") { - let instr_count = line - .trim() - .parse() - .context("Unable to parse instruction counts from callgrind output file")?; - - return Ok(instr_count); - } - } - - anyhow::bail!("`summary` section not found in callgrind output file") -} - -/// The instruction counts, for each side, after running a benchmark -#[derive(Copy, Clone)] -pub struct InstructionCounts { - pub client: u64, - pub server: u64, -} - -impl Sub for InstructionCounts { - type Output = InstructionCounts; - - fn sub(self, rhs: Self) -> Self::Output { - InstructionCounts { - client: self.client - rhs.client, - server: self.server - rhs.server, - } - } -} - -/// Returns the detailed instruction diff between the baseline and the candidate -pub fn diff(baseline: &Path, candidate: &Path, scenario: &str) -> anyhow::Result { - // callgrind_annotate formats the callgrind output file, suitable for comparison with - // callgrind_differ - let callgrind_annotate_base = Command::new("callgrind_annotate") - .arg( - baseline - .join(CALLGRIND_OUTPUT_SUBDIR) - .join(scenario), - ) - // do not annotate source, to keep output compact - .arg("--auto=no") - .output() - .context("error waiting for callgrind_annotate to finish")?; - - let callgrind_annotate_candidate = Command::new("callgrind_annotate") - .arg( - candidate - .join(CALLGRIND_OUTPUT_SUBDIR) - .join(scenario), - ) - // do not annotate source, to keep output compact - .arg("--auto=no") - .output() - .context("error waiting for callgrind_annotate to finish")?; - - if !callgrind_annotate_base.status.success() { - anyhow::bail!( - "callgrind_annotate for base finished with an error (code = {:?})", - callgrind_annotate_base.status.code() - ) - } - - if !callgrind_annotate_candidate - .status - .success() - { - anyhow::bail!( - "callgrind_annotate for candidate finished with an error (code = {:?})", - callgrind_annotate_candidate - .status - .code() - ) - } - - let string_base = String::from_utf8(callgrind_annotate_base.stdout) - .context("callgrind_annotate produced invalid UTF8")?; - let string_candidate = String::from_utf8(callgrind_annotate_candidate.stdout) - .context("callgrind_annotate produced invalid UTF8")?; - - // TODO: reinstate actual diffing, using `callgrind_differ` crate - Ok(format!( - "Base output:\n{string_base}\n\ - =====\n\n\ - Candidate output:\n{string_candidate}\n" - )) -} - -/// A RAII-like object for enabling callgrind instruction counting. -/// -/// Warning: must not be nested. -/// -/// Instructions outside the scope of these objects are not counted. -pub(crate) struct CountInstructions; - -impl CountInstructions { - pub(crate) fn start() -> Self { - #[cfg(target_os = "linux")] - crabgrind::callgrind::toggle_collect(); - CountInstructions - } -} - -impl Drop for CountInstructions { - fn drop(&mut self) { - #[cfg(target_os = "linux")] - crabgrind::callgrind::toggle_collect(); - } -} diff --git a/ci-bench/src/main.rs b/ci-bench/src/main.rs index fd27a77cdbe..7f21f231a44 100644 --- a/ci-bench/src/main.rs +++ b/ci-bench/src/main.rs @@ -1,8 +1,8 @@ -use std::collections::HashMap; +use core::hint::black_box; +use core::mem; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fs::File; -use std::hint::black_box; use std::io::{self, BufRead, BufReader, Write}; -use std::mem; use std::os::fd::{AsRawFd, FromRawFd}; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -11,35 +11,34 @@ use std::time::Instant; use anyhow::Context; use async_trait::async_trait; use clap::{Parser, Subcommand, ValueEnum}; -use fxhash::FxHashMap; use itertools::Itertools; use rayon::iter::Either; use rayon::prelude::*; +use rustc_hash::FxHashMap; use rustls::client::Resumption; -use rustls::crypto::{aws_lc_rs, ring, CryptoProvider, GetRandomFailed, SecureRandom}; -use rustls::pki_types::pem::PemObject; -use rustls::pki_types::CertificateDer; +use rustls::crypto::{CipherSuite, CryptoProvider, GetRandomFailed, SecureRandom, TicketProducer}; +use rustls::enums::ProtocolVersion; use rustls::server::{NoServerSessionStorage, ServerSessionMemoryCache, WebPkiClientVerifier}; use rustls::{ - CipherSuite, ClientConfig, ClientConnection, HandshakeKind, ProtocolVersion, RootCertStore, - ServerConfig, ServerConnection, + ClientConfig, ClientConnection, Connection, HandshakeKind, RootCertStore, ServerConfig, + ServerConnection, }; +use rustls_test::KeyType; use crate::benchmark::{ - get_reported_instr_count, validate_benchmarks, Benchmark, BenchmarkKind, BenchmarkParams, - ResumptionKind, + AuthKeySource, Benchmark, BenchmarkKind, BenchmarkParams, ResumptionKind, + get_reported_instr_count, }; -use crate::callgrind::{CallgrindRunner, CountInstructions}; use crate::util::async_io::{self, AsyncRead, AsyncWrite}; use crate::util::transport::{ read_handshake_message, read_plaintext_to_end_bounded, send_handshake_message, write_all_plaintext_bounded, }; -use crate::util::KeyType; +use crate::valgrind::{CallgrindRunner, CountInstructions, DhatRunner, MemoryDetails}; mod benchmark; -mod callgrind; mod util; +mod valgrind; /// The size in bytes of the plaintext sent in the transfer benchmark const TRANSFER_PLAINTEXT_SIZE: usize = 1024 * 1024 * 10; // 10 MB @@ -61,6 +60,9 @@ const RESUMED_HANDSHAKE_RUNS: usize = 30; /// The name of the file where the instruction counts are stored after a `run-all` run const ICOUNTS_FILENAME: &str = "icounts.csv"; +/// The name of the file where the memory data are stored after a `run-all` run +const MEMORY_FILENAME: &str = "memory.csv"; + /// Default size in bytes for internal buffers (256 KB) const DEFAULT_BUFFER_SIZE: usize = 262144; @@ -78,20 +80,59 @@ pub enum Command { #[arg(short, long, default_value = "target/ci-bench")] output_dir: PathBuf, }, - /// Run a single benchmark at the provided index (used by the bench runner to start each benchmark in its own process) - RunSingle { index: u32, side: Side }, + /// Run a named benchmark and print the measured CPU instruction counts in CSV format + RunSingle { + /// The name of the benchmark. + bench: String, + #[arg(short, long, default_value = "target/ci-bench")] + output_dir: PathBuf, + }, + /// Run a single benchmark at the provided name (used by the bench runner to start each benchmark in its own process) + RunPipe { + name: String, + side: Side, + measurement_mode: Mode, + }, /// Run all benchmarks in walltime mode and print the measured timings in CSV format Walltime { #[arg(short, long)] iterations_per_scenario: usize, }, - /// Compare the results from two previous benchmark runs and print a user-friendly markdown overview + /// Compare the icount results from two previous benchmark runs and print a user-friendly markdown overview Compare { /// Path to the directory with the results of a previous `run-all` execution baseline_dir: PathBuf, /// Path to the directory with the results of a previous `run-all` execution candidate_dir: PathBuf, }, + /// Compare the memory results from two previous benchmark runs and print a user-friendly markdown overview + CompareMemory { + comparator: CompareMemoryOperand, + /// Path to the directory with the results of a previous `run-all` execution + baseline_dir: PathBuf, + /// Path to the directory with the results of a previous `run-all` execution + candidate_dir: PathBuf, + }, +} + +#[derive(Copy, Clone, Debug, Default, ValueEnum)] +pub enum CompareMemoryOperand { + #[default] + TotalBytes, + TotalBlocks, + PeakBytes, + PeakBlocks, +} + +impl CompareMemoryOperand { + fn choose(&self, memory: MemoryDetails) -> u64 { + match self { + Self::TotalBytes => memory.heap_total_bytes, + Self::TotalBlocks => memory.heap_total_blocks, + Self::PeakBytes => memory.heap_peak_bytes, + Self::PeakBlocks => memory.heap_peak_blocks, + } + } } #[derive(Copy, Clone, ValueEnum)] @@ -100,12 +141,18 @@ pub enum Side { Client, } +#[derive(Copy, Clone, ValueEnum)] +pub enum Mode { + Instruction, + Memory, +} + impl Side { /// Returns the string representation of the side pub fn as_str(self) -> &'static str { match self { - Side::Client => "client", - Side::Server => "server", + Self::Client => "client", + Self::Server => "server", } } } @@ -117,25 +164,40 @@ fn main() -> anyhow::Result<()> { match cli.command { Command::RunAll { output_dir } => { let executable = std::env::args().next().unwrap(); - let results = run_all(executable, output_dir.clone(), &benchmarks)?; - - // Output results in CSV (note: not using a library here to avoid extra dependencies) - let mut csv_file = File::create(output_dir.join(ICOUNTS_FILENAME)) - .context("cannot create output csv file")?; - for (name, instr_count) in results { - writeln!(csv_file, "{name},{instr_count}")?; - } + let results = run_all( + executable, + output_dir.clone(), + &benchmarks.iter().collect::>(), + )?; + output_csv(results, output_dir)?; } - Command::RunSingle { index, side } => { - // `u32::MAX` is used as a signal to do nothing and return. By "running" an empty - // benchmark we can measure the startup overhead. - if index == u32::MAX { - return Ok(()); - } + Command::RunSingle { bench, output_dir } => { + let executable = std::env::args().next().unwrap(); + let Some(benchmark) = benchmarks.get(bench.as_str()) else { + let mut output = String::new(); + for bench in all_benchmarks()? { + output.push_str(&format!(" - {:?}\n", bench.name())); + } + return Err(anyhow::anyhow!( + "Benchmark {bench:?} not found\n\nAvailable are:\n{output}" + )); + }; + let results = run_all(executable, output_dir.clone(), &[benchmark])?; + output_csv(results, output_dir)?; + } + Command::RunPipe { + name, + side, + measurement_mode, + } => { let bench = benchmarks - .get(index as usize) - .ok_or(anyhow::anyhow!("Benchmark not found: {index}"))?; + .get(name.as_str()) + .ok_or_else(|| anyhow::anyhow!("Benchmark not found: {name}"))?; + + if let Some(warm_up) = bench.params.warm_up { + warm_up(); + } let stdin_lock = io::stdin().lock(); let stdout_lock = io::stdout().lock(); @@ -151,6 +213,13 @@ fn main() -> anyhow::Result<()> { let mut stdin = unsafe { File::from_raw_fd(stdin_lock.as_raw_fd()) }; let mut stdout = unsafe { File::from_raw_fd(stdout_lock.as_raw_fd()) }; + // When measuring instructions, we do multiple resumed handshakes, for + // reasons explained in the comments to `RESUMED_HANDSHAKE_RUNS`. + let resumed_reps = match measurement_mode { + Mode::Instruction => RESUMED_HANDSHAKE_RUNS, + _ => 1, + }; + let handshake_buf = &mut [0u8; DEFAULT_BUFFER_SIZE]; let resumption_kind = bench.kind.resumption_kind(); let io = StepperIo { @@ -170,6 +239,7 @@ fn main() -> anyhow::Result<()> { ), }, bench.kind, + resumed_reps, ) .await } @@ -184,6 +254,7 @@ fn main() -> anyhow::Result<()> { ), }, bench.kind, + resumed_reps, ) .await } @@ -198,9 +269,9 @@ fn main() -> anyhow::Result<()> { Command::Walltime { iterations_per_scenario, } => { - let mut timings = vec![Vec::with_capacity(iterations_per_scenario); benchmarks.len()]; + let mut timings = BTreeMap::new(); for _ in 0..iterations_per_scenario { - for (i, bench) in benchmarks.iter().enumerate() { + for bench in &benchmarks { let start = Instant::now(); // The variables below are used to initialize the client and server configs. We @@ -227,6 +298,7 @@ fn main() -> anyhow::Result<()> { config: ServerSideStepper::make_config(params, resumption_kind), }, bench.kind, + RESUMED_HANDSHAKE_RUNS, ) .await }; @@ -244,6 +316,7 @@ fn main() -> anyhow::Result<()> { config: ClientSideStepper::make_config(params, resumption_kind), }, bench.kind, + RESUMED_HANDSHAKE_RUNS, ) .await }; @@ -255,13 +328,16 @@ fn main() -> anyhow::Result<()> { server_result .with_context(|| format!("server side of {} crashed", bench.name()))?; - timings[i].push(start.elapsed()); + timings + .entry(bench.name().to_string()) + .or_insert_with(|| Vec::with_capacity(iterations_per_scenario)) + .push(start.elapsed()); } } // Output the results - for (i, bench_timings) in timings.into_iter().enumerate() { - print!("{}", benchmarks[i].name()); + for (name, bench_timings) in timings.into_iter() { + print!("{}", name); for timing in bench_timings { print!(",{}", timing.as_nanos()) } @@ -272,24 +348,61 @@ fn main() -> anyhow::Result<()> { baseline_dir, candidate_dir, } => { - let baseline = read_results(&baseline_dir.join(ICOUNTS_FILENAME))?; - let candidate = read_results(&candidate_dir.join(ICOUNTS_FILENAME))?; - let result = compare_results(&baseline_dir, &candidate_dir, &baseline, &candidate)?; - print_report(&result); + let baseline = read_icount_results(&baseline_dir.join(ICOUNTS_FILENAME))?; + let candidate = read_icount_results(&candidate_dir.join(ICOUNTS_FILENAME))?; + let result = + compare_icount_results(&baseline_dir, &candidate_dir, &baseline, &candidate)?; + print_icount_report(&result); + } + Command::CompareMemory { + comparator, + baseline_dir, + candidate_dir, + } => { + let baseline = read_memory_results(&baseline_dir.join(MEMORY_FILENAME))?; + let candidate = read_memory_results(&candidate_dir.join(MEMORY_FILENAME))?; + + print_memory_report(&compare_memory_results(&baseline, &candidate, comparator)?); } } Ok(()) } +fn output_csv( + results: Vec<(String, CombinedMeasurement)>, + output_dir: PathBuf, +) -> anyhow::Result<()> { + // Output results in CSV (note: not using a library here to avoid extra dependencies) + let mut csv_file = + File::create(output_dir.join(ICOUNTS_FILENAME)).context("cannot create output csv file")?; + for (name, combined) in &results { + writeln!(csv_file, "{name},{}", combined.instructions)?; + } + + let mut csv_file = + File::create(output_dir.join(MEMORY_FILENAME)).context("cannot create output csv file")?; + for (name, combined) in results { + writeln!( + csv_file, + "{name},{},{},{},{}", + combined.memory.heap_total_bytes, + combined.memory.heap_total_blocks, + combined.memory.heap_peak_bytes, + combined.memory.heap_peak_blocks, + )?; + } + + Ok(()) +} + /// Returns all benchmarks -fn all_benchmarks() -> anyhow::Result> { - let mut benchmarks = Vec::new(); +fn all_benchmarks() -> anyhow::Result> { + let mut benchmarks = BTreeSet::new(); for param in all_benchmarks_params() { add_benchmark_group(&mut benchmarks, param); } - validate_benchmarks(&benchmarks)?; Ok(benchmarks) } @@ -297,93 +410,113 @@ fn all_benchmarks() -> anyhow::Result> { fn all_benchmarks_params() -> Vec { let mut all = Vec::new(); - for (provider, suites, ticketer, provider_name) in [ + for (provider, ticketer, provider_name, warm_up) in [ ( - derandomize(ring::default_provider()), - ring::ALL_CIPHER_SUITES, - &(ring_ticketer as fn() -> Arc), + derandomize(rustls_ring::DEFAULT_PROVIDER), + &(ring_ticketer as fn() -> Arc), "ring", + None, ), ( - derandomize(aws_lc_rs::default_provider()), - aws_lc_rs::ALL_CIPHER_SUITES, - &(aws_lc_rs_ticketer as fn() -> Arc), + derandomize(rustls_aws_lc_rs::DEFAULT_PROVIDER), + &(aws_lc_rs_ticketer as fn() -> Arc), "aws_lc_rs", + Some(warm_up_aws_lc_rs as fn()), ), ] { - for (key_type, suite_name, version, name) in [ + for (key_type, suite_name, name) in [ ( KeyType::Rsa2048, CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - &rustls::version::TLS12, "1.2_rsa_aes", ), ( KeyType::Rsa2048, CipherSuite::TLS13_AES_128_GCM_SHA256, - &rustls::version::TLS13, "1.3_rsa_aes", ), ( KeyType::EcdsaP256, CipherSuite::TLS13_AES_128_GCM_SHA256, - &rustls::version::TLS13, "1.3_ecdsap256_aes", ), ( KeyType::EcdsaP384, CipherSuite::TLS13_AES_128_GCM_SHA256, - &rustls::version::TLS13, "1.3_ecdsap384_aes", ), ( KeyType::Rsa2048, CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, - &rustls::version::TLS13, "1.3_rsa_chacha", ), ( KeyType::EcdsaP256, CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, - &rustls::version::TLS13, "1.3_ecdsap256_chacha", ), ( KeyType::EcdsaP384, CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, - &rustls::version::TLS13, "1.3_ecdsap384_chacha", ), ] { all.push(BenchmarkParams::new( - provider.clone(), + select_suite(provider.clone(), suite_name), ticketer, - key_type, - find_suite(suites, suite_name), - version, + AuthKeySource::KeyType(key_type), format!("{provider_name}_{name}"), + warm_up, )); } } + let make_ticketer = + &((|| Arc::new(rustls_fuzzing_provider::Ticketer)) as fn() -> Arc); + + all.push(BenchmarkParams::new( + rustls_fuzzing_provider::PROVIDER_TLS13.into(), + make_ticketer, + AuthKeySource::FuzzingProvider, + "1.3_no_crypto".to_string(), + None, + )); + + all.push(BenchmarkParams::new( + rustls_fuzzing_provider::PROVIDER_TLS12.into(), + make_ticketer, + AuthKeySource::FuzzingProvider, + "1.2_no_crypto".to_string(), + None, + )); + all } -fn find_suite( - all: &[rustls::SupportedCipherSuite], - name: CipherSuite, -) -> rustls::SupportedCipherSuite { - *all.iter() - .find(|suite| suite.suite() == name) - .unwrap_or_else(|| panic!("cannot find cipher suite {name:?}")) +fn ring_ticketer() -> Arc { + rustls_ring::DEFAULT_PROVIDER + .ticketer_factory + .ticketer() + .unwrap() } -fn ring_ticketer() -> Arc { - ring::Ticketer::new().unwrap() +fn aws_lc_rs_ticketer() -> Arc { + rustls_aws_lc_rs::DEFAULT_PROVIDER + .ticketer_factory + .ticketer() + .unwrap() } -fn aws_lc_rs_ticketer() -> Arc { - aws_lc_rs::Ticketer::new().unwrap() +fn select_suite(mut provider: CryptoProvider, name: CipherSuite) -> Arc { + provider + .tls12_cipher_suites + .to_mut() + .retain(|suite| suite.common.suite == name); + provider + .tls13_cipher_suites + .to_mut() + .retain(|suite| suite.common.suite == name); + provider.into() } fn derandomize(base: CryptoProvider) -> CryptoProvider { @@ -393,6 +526,16 @@ fn derandomize(base: CryptoProvider) -> CryptoProvider { } } +fn warm_up_aws_lc_rs() { + // "Warm up" provider's actual entropy source. aws-lc-rs particularly + // has an expensive process here, which is one-time (per calling thread) + // so not useful to include in benchmark measurements. + rustls_aws_lc_rs::DEFAULT_PROVIDER + .secure_random + .fill(&mut [0u8]) + .unwrap(); +} + #[derive(Debug)] struct NotRandom; @@ -411,7 +554,7 @@ impl SecureRandom for NotRandom { /// - Handshake with session id resumption /// - Handshake with ticket resumption /// - Transfer a 1MB data stream from the server to the client -fn add_benchmark_group(benchmarks: &mut Vec, params: BenchmarkParams) { +fn add_benchmark_group(benchmarks: &mut BTreeSet, params: BenchmarkParams) { let params_label = params.label.clone(); // Create handshake benchmarks for all resumption kinds @@ -422,34 +565,48 @@ fn add_benchmark_group(benchmarks: &mut Vec, params: BenchmarkParams) params.clone(), ); - benchmarks.push(handshake_bench); + assert!(benchmarks.insert(handshake_bench), "duplicate benchmark"); } // Benchmark data transfer - benchmarks.push(Benchmark::new( - format!("transfer_no_resume_{params_label}"), - BenchmarkKind::Transfer, - params.clone(), - )); + assert!( + benchmarks.insert(Benchmark::new( + format!("transfer_no_resume_{params_label}"), + BenchmarkKind::Transfer, + params + )), + "duplicate benchmark" + ); } /// Run all the provided benches under callgrind to retrieve their instruction count -pub fn run_all( +fn run_all( executable: String, output_dir: PathBuf, - benches: &[Benchmark], -) -> anyhow::Result> { + benches: &[&Benchmark], +) -> anyhow::Result> { + for bench in benches { + if let Some(warm_up) = bench.params.warm_up { + warm_up(); + } + } + // Run the benchmarks in parallel - let runner = CallgrindRunner::new(executable, output_dir)?; - let results: Vec<_> = benches + let cg_runner = CallgrindRunner::new(executable.clone(), output_dir.clone())?; + let cg_results: Vec<_> = benches + .par_iter() + .map(|bench| (bench, cg_runner.run_bench(bench))) + .collect(); + + let dh_runner = DhatRunner::new(executable, output_dir)?; + let dh_results: Vec<_> = benches .par_iter() - .enumerate() - .map(|(i, bench)| (bench, runner.run_bench(i as u32, bench))) + .map(|bench| (bench, dh_runner.run_bench(bench))) .collect(); // Report possible errors - let (errors, results): (Vec<_>, FxHashMap<_, _>) = - results + let (errors, cg_results): (Vec<_>, FxHashMap<_, _>) = + cg_results .into_iter() .partition_map(|(bench, result)| match result { Err(_) => Either::Left(()), @@ -461,18 +618,49 @@ pub fn run_all( // crashing anyhow::bail!("One or more benchmarks crashed"); } + let (errors, dh_results): (Vec<_>, FxHashMap<_, _>) = + dh_results + .into_iter() + .partition_map(|(bench, result)| match result { + Err(_) => Either::Left(()), + Ok(heap_profile) => Either::Right((bench.name(), heap_profile)), + }); + if !errors.is_empty() { + // Note: there is no need to explicitly report the names of each crashed benchmark, because + // names and other details are automatically printed to stderr by the child process upon + // crashing + anyhow::bail!("One or more benchmarks crashed"); + } // Gather results keeping the original order of the benchmarks let mut measurements = Vec::new(); for bench in benches { - let instr_counts = get_reported_instr_count(bench, &results); - measurements.push((bench.name_with_side(Side::Server), instr_counts.server)); - measurements.push((bench.name_with_side(Side::Client), instr_counts.client)); + let instr_counts = get_reported_instr_count(bench, &cg_results); + let memory = &dh_results[bench.name()]; + measurements.push(( + bench.name_with_side(Side::Server), + CombinedMeasurement { + instructions: instr_counts.server, + memory: memory.server, + }, + )); + measurements.push(( + bench.name_with_side(Side::Client), + CombinedMeasurement { + instructions: instr_counts.client, + memory: memory.client, + }, + )); } Ok(measurements) } +pub struct CombinedMeasurement { + instructions: u64, + memory: MemoryDetails, +} + /// Drives the different steps in a benchmark. /// /// See [`run_bench`] for specific details on how it is used. @@ -502,25 +690,26 @@ struct ClientSideStepper<'a> { impl ClientSideStepper<'_> { fn make_config(params: &BenchmarkParams, resume: ResumptionKind) -> Arc { - assert_eq!(params.ciphersuite.version(), params.version); - let mut root_store = RootCertStore::empty(); - root_store.add_parsable_certificates( - CertificateDer::pem_file_iter(params.key_type.path_for("ca.cert")) - .unwrap() - .map(|result| result.unwrap()), - ); - - let mut cfg = ClientConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![params.ciphersuite], - ..params.provider.clone() + let cfg = ClientConfig::builder(params.provider.clone()); + + let mut cfg = match params.auth_key { + AuthKeySource::KeyType(key_type) => { + let mut root_store = RootCertStore::empty(); + root_store + .add(key_type.ca_cert()) + .unwrap(); + + cfg.with_root_certificates(root_store) + .with_no_client_auth() + .unwrap() } - .into(), - ) - .with_protocol_versions(&[params.version]) - .unwrap() - .with_root_certificates(root_store) - .with_no_client_auth(); + + AuthKeySource::FuzzingProvider => cfg + .dangerous() + .with_custom_certificate_verifier(rustls_fuzzing_provider::server_verifier()) + .with_no_client_auth() + .unwrap(), + }; if resume != ResumptionKind::No { cfg.resumption = Resumption::in_memory_sessions(128); @@ -538,7 +727,11 @@ impl BenchStepper for ClientSideStepper<'_> { async fn handshake(&mut self) -> anyhow::Result { let server_name = "localhost".try_into().unwrap(); - let mut client = ClientConnection::new(self.config.clone(), server_name).unwrap(); + let mut client = self + .config + .connect(server_name) + .build() + .unwrap(); client.set_buffer_limit(None); loop { @@ -588,19 +781,24 @@ struct ServerSideStepper<'a> { impl ServerSideStepper<'_> { fn make_config(params: &BenchmarkParams, resume: ResumptionKind) -> Arc { - assert_eq!(params.ciphersuite.version(), params.version); - - let mut cfg = ServerConfig::builder_with_provider(params.provider.clone().into()) - .with_protocol_versions(&[params.version]) - .unwrap() - .with_client_cert_verifier(WebPkiClientVerifier::no_client_auth()) - .with_single_cert(params.key_type.get_chain(), params.key_type.get_key()) - .expect("bad certs/private key?"); + let cfg = ServerConfig::builder(params.provider.clone()); + + let mut cfg = match params.auth_key { + AuthKeySource::KeyType(key_type) => cfg + .with_client_cert_verifier(WebPkiClientVerifier::no_client_auth()) + .with_single_cert(key_type.identity(), key_type.key()) + .expect("bad certs/private key?"), + + AuthKeySource::FuzzingProvider => cfg + .with_client_cert_verifier(WebPkiClientVerifier::no_client_auth()) + .with_server_credential_resolver(rustls_fuzzing_provider::server_cert_resolver()) + .unwrap(), + }; if resume == ResumptionKind::SessionId { cfg.session_storage = ServerSessionMemoryCache::new(128); } else if resume == ResumptionKind::Tickets { - cfg.ticketer = (params.ticketer)(); + cfg.ticketer = Some((params.ticketer)()); } else { cfg.session_storage = Arc::new(NoServerSessionStorage {}); } @@ -643,7 +841,11 @@ impl BenchStepper for ServerSideStepper<'_> { } /// Runs the benchmark using the provided stepper -async fn run_bench(mut stepper: T, kind: BenchmarkKind) -> anyhow::Result<()> { +async fn run_bench( + mut stepper: T, + kind: BenchmarkKind, + resumed_reps: usize, +) -> anyhow::Result<()> { match kind { BenchmarkKind::Handshake(ResumptionKind::No) => { // Just count instructions for one handshake. @@ -655,10 +857,8 @@ async fn run_bench(mut stepper: T, kind: BenchmarkKind) -> anyh // session ID / ticket. This is not measured. stepper.handshake().await?; - // From now on we can perform resumed handshakes. We do it multiple - // times, for reasons explained in the comments to `RESUMED_HANDSHAKE_RUNS`. let _count = CountInstructions::start(); - for _ in 0..RESUMED_HANDSHAKE_RUNS { + for _ in 0..resumed_reps { // Wait for the endpoints to sync (i.e. the server must have discarded the previous // connection and be ready for a new handshake, otherwise the client will start a // handshake before the server is ready and the bytes will be fed to the old @@ -693,6 +893,13 @@ struct CompareResult { missing_in_baseline: Vec, } +/// The results of a comparison between two `run-all` executions +struct MemoryCompareResult { + diffs: Vec, + /// Benchmark scenarios present in the candidate but missing in the baseline + missing_in_baseline: Vec, +} + /// Contains information about instruction counts and their difference for a specific scenario #[derive(Clone)] struct Diff { @@ -703,8 +910,19 @@ struct Diff { diff_ratio: f64, } +/// Contains information about memory usage and a difference for a specific scenario & comparator +#[derive(Clone)] +struct MemoryDiff { + scenario: String, + baseline: MemoryDetails, + candidate: MemoryDetails, + comparator: CompareMemoryOperand, + diff: i64, + diff_ratio: f64, +} + /// Reads the (benchmark, instruction count) pairs from previous CSV output -fn read_results(path: &Path) -> anyhow::Result> { +fn read_icount_results(path: &Path) -> anyhow::Result> { let file = File::open(path).context(format!( "CSV file for comparison not found: {}", path.display() @@ -718,11 +936,11 @@ fn read_results(path: &Path) -> anyhow::Result> { measurements.insert( parts .next() - .ok_or(anyhow::anyhow!("CSV is wrongly formatted"))? + .ok_or_else(|| anyhow::anyhow!("CSV is wrongly formatted"))? .to_string(), parts .next() - .ok_or(anyhow::anyhow!("CSV is wrongly formatted"))? + .ok_or_else(|| anyhow::anyhow!("CSV is wrongly formatted"))? .parse() .context("Unable to parse instruction count from CSV")?, ); @@ -731,9 +949,54 @@ fn read_results(path: &Path) -> anyhow::Result> { Ok(measurements) } +/// Reads the (benchmark, instruction count) pairs from previous CSV output +fn read_memory_results(path: &Path) -> anyhow::Result> { + let file = File::open(path).context(format!( + "CSV file for comparison not found: {}", + path.display() + ))?; + + let mut measurements = HashMap::new(); + for line in BufReader::new(file).lines() { + let line = line.context("Unable to read results from CSV file")?; + let line = line.trim(); + let mut parts = line.split(','); + measurements.insert( + parts + .next() + .ok_or_else(|| anyhow::anyhow!("CSV is wrongly formatted"))? + .to_string(), + MemoryDetails { + heap_total_bytes: parts + .next() + .ok_or_else(|| anyhow::anyhow!("CSV is wrongly formatted"))? + .parse() + .context("Unable to parse heap total bytes from CSV")?, + heap_total_blocks: parts + .next() + .ok_or_else(|| anyhow::anyhow!("CSV is wrongly formatted"))? + .parse() + .context("Unable to parse heap total blocks from CSV")?, + heap_peak_bytes: parts + .next() + .ok_or_else(|| anyhow::anyhow!("CSV is wrongly formatted"))? + .parse() + .context("Unable to parse heap peak bytes from CSV")?, + heap_peak_blocks: parts + .next() + .ok_or_else(|| anyhow::anyhow!("CSV is wrongly formatted"))? + .parse() + .context("Unable to parse heap peak blocks from CSV")?, + }, + ); + } + + Ok(measurements) +} + /// Returns an internal representation of the comparison between the baseline and the candidate /// measurements -fn compare_results( +fn compare_icount_results( baseline_dir: &Path, candidate_dir: &Path, baseline: &HashMap, @@ -770,7 +1033,7 @@ fn compare_results( let mut diffs_with_callgrind_diff = Vec::new(); for diff in diffs { - let detailed_diff = callgrind::diff(baseline_dir, candidate_dir, &diff.scenario)?; + let detailed_diff = valgrind::callgrind_diff(baseline_dir, candidate_dir, &diff.scenario)?; diffs_with_callgrind_diff.push((diff, detailed_diff)); } @@ -780,14 +1043,62 @@ fn compare_results( }) } +/// Returns an internal representation of the comparison between the baseline and the candidate +/// measurements +fn compare_memory_results( + baseline: &HashMap, + candidate: &HashMap, + comparator: CompareMemoryOperand, +) -> anyhow::Result { + let mut diffs = Vec::new(); + let mut missing = Vec::new(); + + for (scenario, &candidate_memory) in candidate { + let Some(&baseline_memory) = baseline.get(scenario) else { + missing.push(scenario.clone()); + continue; + }; + + let candidate_count = comparator.choose(candidate_memory); + let baseline_count = comparator.choose(baseline_memory); + + let diff = candidate_count as i64 - baseline_count as i64; + let diff_ratio = diff as f64 / baseline_count as f64; + let diff = MemoryDiff { + scenario: scenario.clone(), + baseline: baseline_memory, + candidate: candidate_memory, + comparator, + diff, + diff_ratio, + }; + + diffs.push(diff); + } + + diffs.sort_by(|diff1, diff2| { + diff2 + .diff_ratio + .abs() + .total_cmp(&diff1.diff_ratio.abs()) + }); + + Ok(MemoryCompareResult { + diffs, + missing_in_baseline: missing, + }) +} + /// Prints a report of the comparison to stdout, using GitHub-flavored markdown -fn print_report(result: &CompareResult) { +fn print_icount_report(result: &CompareResult) { println!("# Benchmark results"); if !result.missing_in_baseline.is_empty() { println!("### âš ï¸ Warning: missing benchmarks"); println!(); - println!("The following benchmark scenarios are present in the candidate but not in the baseline:"); + println!( + "The following benchmark scenarios are present in the candidate but not in the baseline:" + ); println!(); for scenario in &result.missing_in_baseline { println!("* {scenario}"); @@ -817,6 +1128,29 @@ fn print_report(result: &CompareResult) { } } +fn print_memory_report(result: &MemoryCompareResult) { + println!("# Memory measurement results"); + + if !result.missing_in_baseline.is_empty() { + println!("### âš ï¸ Warning: missing benchmarks"); + println!(); + println!( + "The following benchmark scenarios are present in the candidate but not in the baseline:" + ); + println!(); + for scenario in &result.missing_in_baseline { + println!("* {scenario}"); + } + } + + println!("## Memory measurement differences"); + if result.diffs.is_empty() { + println!("_There are no memory measurement differences_"); + } else { + memory_table(&result.diffs, true); + } +} + /// Renders the diffs as a markdown table fn table<'a>(diffs: impl Iterator, emoji_feedback: bool) { println!("| Scenario | Baseline | Candidate | Diff |"); @@ -840,6 +1174,32 @@ fn table<'a>(diffs: impl Iterator, emoji_feedback: bool) { } } -#[cfg(not(target_env = "msvc"))] -#[global_allocator] -static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; +/// Renders the diffs as a markdown table +fn memory_table(diffs: &[MemoryDiff], emoji_feedback: bool) { + println!("| Scenario | Baseline | Candidate | Diff |"); + println!("| --- | ---: | ---: | ---: |"); + for diff in diffs { + let emoji = match emoji_feedback { + true if diff.diff_ratio > 0.01 => "âš ï¸ ", + true if diff.diff_ratio < -0.01 => "✅ ", + _ => "", + }; + + println!( + "| {} | Total {}B / {}#
Peak {}B / {}# | Total {}B / {}#
Peak {}B / {}# | {:?} {}{} ({:.2}%) |", + diff.scenario, + diff.baseline.heap_total_bytes, + diff.baseline.heap_total_blocks, + diff.baseline.heap_peak_bytes, + diff.baseline.heap_peak_blocks, + diff.candidate.heap_total_bytes, + diff.candidate.heap_total_blocks, + diff.candidate.heap_peak_bytes, + diff.candidate.heap_peak_blocks, + diff.comparator, + emoji, + diff.diff, + diff.diff_ratio * 100.0 + ) + } +} diff --git a/ci-bench/src/util.rs b/ci-bench/src/util.rs index b32da09cd7b..9b9bf291adb 100644 --- a/ci-bench/src/util.rs +++ b/ci-bench/src/util.rs @@ -1,46 +1,16 @@ -use rustls::pki_types::pem::PemObject; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; - -#[derive(PartialEq, Clone, Copy, Debug)] -pub enum KeyType { - Rsa2048, - EcdsaP256, - EcdsaP384, -} - -impl KeyType { - pub(crate) fn path_for(&self, part: &str) -> String { - match self { - Self::Rsa2048 => format!("../test-ca/rsa-2048/{}", part), - Self::EcdsaP256 => format!("../test-ca/ecdsa-p256/{}", part), - Self::EcdsaP384 => format!("../test-ca/ecdsa-p384/{}", part), - } - } - - pub(crate) fn get_chain(&self) -> Vec> { - CertificateDer::pem_file_iter(self.path_for("end.fullchain")) - .unwrap() - .map(|result| result.unwrap()) - .collect() - } - - pub(crate) fn get_key(&self) -> PrivateKeyDer<'static> { - PrivateKeyDer::from_pem_file(self.path_for("end.key")).unwrap() - } -} - -pub mod async_io { +pub(crate) mod async_io { //! Async IO building blocks required for sharing code between the instruction count and //! wall-time benchmarks - use std::cell::{Cell, RefCell}; + use core::cell::{Cell, RefCell}; + use core::future::Future; + use core::pin::{Pin, pin}; + use core::task::{Poll, RawWaker, RawWakerVTable, Waker}; + use core::{ptr, task}; use std::collections::VecDeque; use std::fs::File; - use std::future::Future; - use std::pin::{pin, Pin}; + use std::io; use std::rc::Rc; - use std::task::{Poll, RawWaker, RawWakerVTable, Waker}; - use std::{io, ptr, task}; use async_trait::async_trait; @@ -52,7 +22,7 @@ pub mod async_io { /// Useful when counting CPU instructions, because the server and the client side of the /// connection run in two separate processes and communicate through stdio using blocking /// operations. - pub fn block_on_single_poll( + pub(crate) fn block_on_single_poll( future: impl Future>, ) -> anyhow::Result<()> { // We don't need a waker, because the future will complete in one go @@ -75,7 +45,7 @@ pub mod async_io { /// /// Using this together with blocking futures can lead to deadlocks (i.e. when one of the /// futures is blocked while it waits on a message from the other). - pub fn block_on_concurrent( + pub(crate) fn block_on_concurrent( x: impl Future>, y: impl Future>, ) -> (anyhow::Result<()>, anyhow::Result<()>) { @@ -131,14 +101,14 @@ pub mod async_io { /// Read bytes asynchronously #[async_trait(?Send)] - pub trait AsyncRead { + pub(crate) trait AsyncRead { async fn read(&mut self, buf: &mut [u8]) -> io::Result; async fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()>; } /// Write bytes asynchronously #[async_trait(?Send)] - pub trait AsyncWrite { + pub(crate) trait AsyncWrite { async fn write_all(&mut self, buf: &[u8]) -> io::Result<()>; async fn flush(&mut self) -> io::Result<()>; } @@ -169,14 +139,14 @@ pub mod async_io { /// Creates an unidirectional byte pipe of the given capacity, suitable for async reading and /// writing - pub fn async_pipe(capacity: usize) -> (AsyncSender, AsyncReceiver) { + pub(crate) fn async_pipe(capacity: usize) -> (AsyncSender, AsyncReceiver) { let open = Rc::new(Cell::new(true)); let buf = Rc::new(RefCell::new(VecDeque::with_capacity(capacity))); ( AsyncSender { inner: AsyncPipeSide { - open: open.clone(), - buf: buf.clone(), + open: Rc::clone(&open), + buf: Rc::clone(&buf), }, }, AsyncReceiver { @@ -186,12 +156,12 @@ pub mod async_io { } /// The sender end of an asynchronous byte pipe - pub struct AsyncSender { + pub(crate) struct AsyncSender { inner: AsyncPipeSide, } /// The receiver end of an asynchronous byte pipe - pub struct AsyncReceiver { + pub(crate) struct AsyncReceiver { inner: AsyncPipeSide, } @@ -290,10 +260,7 @@ pub mod async_io { fn poll(mut self: Pin<&mut Self>, _: &mut task::Context<'_>) -> Poll { if !self.writer.inner.open.get() { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::Other, - "channel was closed", - ))); + return Poll::Ready(Err(io::Error::other("channel was closed"))); } let mut pipe_buf = self.writer.inner.buf.borrow_mut(); @@ -381,7 +348,7 @@ pub mod async_io { } } -pub mod transport { +pub(crate) mod transport { //! Custom functions to interact between rustls clients and a servers. //! //! The goal of these functions is to ensure messages are exchanged in chunks of a fixed size, to make @@ -398,7 +365,7 @@ pub mod transport { use std::io::{Cursor, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; - use rustls::{ClientConnection, ConnectionCommon, ServerConnection, SideData}; + use rustls::{ClientConnection, Connection, ServerConnection}; use super::async_io::{AsyncRead, AsyncWrite}; @@ -409,8 +376,8 @@ pub mod transport { /// length, followed by the message itself. /// /// The receiving end should use [`read_handshake_message`] to process the transmission. - pub async fn send_handshake_message( - conn: &mut ConnectionCommon, + pub(crate) async fn send_handshake_message( + conn: &mut impl Connection, writer: &mut dyn AsyncWrite, buf: &mut [u8], ) -> anyhow::Result<()> { @@ -447,8 +414,8 @@ pub mod transport { /// /// Used in combination with [`send_handshake_message`] (see that function's documentation for /// more details). - pub async fn read_handshake_message( - conn: &mut ConnectionCommon, + pub(crate) async fn read_handshake_message( + conn: &mut impl Connection, reader: &mut dyn AsyncRead, buf: &mut [u8], ) -> anyhow::Result { @@ -483,7 +450,7 @@ pub mod transport { /// Reads plaintext until the reader reaches EOF, using a bounded amount of memory. /// /// Returns the amount of plaintext bytes received. - pub async fn read_plaintext_to_end_bounded( + pub(crate) async fn read_plaintext_to_end_bounded( client: &mut ClientConnection, reader: &mut dyn AsyncRead, ) -> anyhow::Result { @@ -535,7 +502,7 @@ pub mod transport { } /// Writes a plaintext of size `plaintext_size`, using a bounded amount of memory - pub async fn write_all_plaintext_bounded( + pub(crate) async fn write_all_plaintext_bounded( server: &mut ServerConnection, writer: &mut dyn AsyncWrite, plaintext_size: usize, diff --git a/ci-bench/src/valgrind.rs b/ci-bench/src/valgrind.rs new file mode 100644 index 00000000000..11cc84f31ba --- /dev/null +++ b/ci-bench/src/valgrind.rs @@ -0,0 +1,477 @@ +use core::ops::Sub; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::{Path, PathBuf}; +use std::process::{Child, Command, Stdio}; + +use anyhow::Context; + +use crate::Side; +use crate::benchmark::Benchmark; + +/// A callgrind-based benchmark runner +pub(crate) struct CallgrindRunner { + /// The path to the ci-bench executable + /// + /// This is necessary because the callgrind runner works by spawning child processes + executable: String, + /// The directory where the callgrind output will be stored + output_dir: PathBuf, +} + +impl CallgrindRunner { + /// Returns a new callgrind-based benchmark runner + pub(crate) fn new(executable: String, output_dir: PathBuf) -> anyhow::Result { + ensure_valgrind_tool_available("--tool=callgrind")?; + + let callgrind_output_dir = output_dir.join(CALLGRIND_OUTPUT_SUBDIR); + std::fs::create_dir_all(&callgrind_output_dir) + .context("Failed to create callgrind output directory")?; + + Ok(Self { + executable, + output_dir: callgrind_output_dir, + }) + } + + /// Runs the benchmark at the specified index and returns the instruction counts for each side + pub(crate) fn run_bench(&self, bench: &Benchmark) -> anyhow::Result { + // The server and client are started as child processes, and communicate with each other + // through stdio. + + let mut server = Self::run_bench_side( + &self.executable, + bench.name(), + Side::Server, + &bench.name_with_side(Side::Server), + Stdio::piped(), + Stdio::piped(), + &self.output_dir, + ) + .context("server side bench crashed")?; + + let client = Self::run_bench_side( + &self.executable, + bench.name(), + Side::Client, + &bench.name_with_side(Side::Client), + Stdio::from(server.process.stdout.take().unwrap()), + Stdio::from(server.process.stdin.take().unwrap()), + &self.output_dir, + ) + .context("client side bench crashed")?; + + Ok(InstructionCounts { + server: server.wait_and_get_instr_count()?, + client: client.wait_and_get_instr_count()?, + }) + } + + /// See docs for [`Self::run_bench`] + fn run_bench_side( + executable: &str, + bench_name: &str, + side: Side, + output_name: &str, + stdin: Stdio, + stdout: Stdio, + output_dir: &Path, + ) -> anyhow::Result { + let output_file = output_dir.join(output_name); + let log_file = output_dir.join(format!("{output_name}.log")); + + // Run under setarch to disable ASLR, to reduce noise + let mut cmd = Command::new("setarch"); + let child = cmd + .arg("-R") + .arg("valgrind") + .arg("--tool=callgrind") + // Do not count instructions from the start, instead this is controlled by `CountInstructions` + .arg("--collect-atstart=no") + // Disable the cache simulation, since we are only interested in instruction counts + .arg("--cache-sim=no") + // Save callgrind's logs, which would otherwise be printed to stderr (we want to + // keep stderr free of noise, to see any errors from the child process) + .arg(format!("--log-file={}", log_file.display())) + // The file where the instruction counts will be stored + .arg(format!("--callgrind-out-file={}", output_file.display())) + .arg(executable) + .arg("run-pipe") + .arg(bench_name) + .arg(side.as_str()) + .arg("instruction") + .stdin(stdin) + .stdout(stdout) + .stderr(Stdio::inherit()) + .spawn() + .context("Failed to run benchmark in callgrind")?; + + Ok(BenchSubprocess { + process: child, + output: ValgrindOutput::Callgrind { output_file }, + }) + } +} + +/// A DHAT-based benchmark runner that measures runtime memory use. +pub(crate) struct DhatRunner { + /// The path to the ci-bench executable + /// + /// This is necessary because the runner works by spawning child processes + executable: String, + /// The directory where the output will be stored + output_dir: PathBuf, +} + +impl DhatRunner { + /// Returns a new callgrind-based benchmark runner + pub(crate) fn new(executable: String, output_dir: PathBuf) -> anyhow::Result { + ensure_valgrind_tool_available("--tool=dhat")?; + + let output_dir = output_dir.join("dhat"); + std::fs::create_dir_all(&output_dir).context("Failed to create DHAT output directory")?; + + Ok(Self { + executable, + output_dir, + }) + } + + /// Runs the benchmark at the specified index and returns the memory usage for each side + pub(crate) fn run_bench(&self, bench: &Benchmark) -> anyhow::Result { + // The server and client are started as child processes, and communicate with each other + // through stdio. + + let mut server = Self::run_bench_side( + &self.executable, + bench.name(), + Side::Server, + &bench.name_with_side(Side::Server), + Stdio::piped(), + Stdio::piped(), + &self.output_dir, + ) + .context("server side bench crashed")?; + + let client = Self::run_bench_side( + &self.executable, + bench.name(), + Side::Client, + &bench.name_with_side(Side::Client), + Stdio::from(server.process.stdout.take().unwrap()), + Stdio::from(server.process.stdin.take().unwrap()), + &self.output_dir, + ) + .context("client side bench crashed")?; + + Ok(MemoryProfile { + server: server.wait_and_get_memory_details()?, + client: client.wait_and_get_memory_details()?, + }) + } + + /// See docs for [`Self::run_bench`] + fn run_bench_side( + executable: &str, + bench_name: &str, + side: Side, + output_name: &str, + stdin: Stdio, + stdout: Stdio, + output_dir: &Path, + ) -> anyhow::Result { + let output_file = output_dir.join(output_name); + let log_file = output_dir.join(format!("{output_name}.log")); + + // Run under setarch to disable ASLR, to reduce noise + let mut cmd = Command::new("setarch"); + let child = cmd + .arg("-R") + .arg("valgrind") + .arg("--tool=dhat") + // We extract output from DHAT's logs, which contain a summary. + .arg(format!("--log-file={}", log_file.display())) + // Also save the detailed JSON + .arg(format!("--dhat-out-file={}", output_file.display())) + .arg(executable) + .arg("run-pipe") + .arg(bench_name) + .arg(side.as_str()) + .arg("memory") + .stdin(stdin) + .stdout(stdout) + .stderr(Stdio::inherit()) + .spawn() + .context("Failed to run benchmark in DHAT")?; + + Ok(BenchSubprocess { + process: child, + output: ValgrindOutput::Dhat { log_file }, + }) + } +} + +/// The subdirectory in which the callgrind output should be stored +const CALLGRIND_OUTPUT_SUBDIR: &str = "callgrind"; + +/// Returns an error if valgrind is not available +fn ensure_valgrind_tool_available(tool: &str) -> anyhow::Result<()> { + let result = Command::new("valgrind") + .arg(tool) + .arg("--version") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + + match result { + Err(e) => anyhow::bail!( + "Unexpected error while launching valgrind {tool}. Error: {}", + e + ), + Ok(status) => { + if status.success() { + Ok(()) + } else { + anyhow::bail!( + "Failed to launch valgrind {tool}. Error: {}. Please ensure that valgrind is installed and on the $PATH.", + status + ) + } + } + } +} + +/// A running subprocess for one of the sides of the benchmark (client or server) +struct BenchSubprocess { + /// The benchmark's child process, running under valgrind + process: Child, + /// Valgrind's output file for this benchmark + output: ValgrindOutput, +} + +enum ValgrindOutput { + Callgrind { output_file: PathBuf }, + Dhat { log_file: PathBuf }, +} + +impl BenchSubprocess { + /// Waits for the process to finish and returns the measured instruction count + fn wait_and_get_instr_count(mut self) -> anyhow::Result { + let status = self + .process + .wait() + .context("Failed to run benchmark in callgrind")?; + if !status.success() { + anyhow::bail!( + "Failed to run benchmark in callgrind. Exit code: {:?}", + status.code() + ); + } + + let ValgrindOutput::Callgrind { output_file } = self.output else { + panic!("wait_and_get_instr_count() is for Callgrind users"); + }; + + parse_callgrind_output(&output_file) + } + + /// Waits for the process to finish and returns the measured peak heap usage + fn wait_and_get_memory_details(mut self) -> anyhow::Result { + let status = self + .process + .wait() + .context("Failed to run benchmark in DHAT")?; + if !status.success() { + anyhow::bail!( + "Failed to run benchmark in DHAT. Exit code: {:?}", + status.code() + ); + } + + let ValgrindOutput::Dhat { log_file } = self.output else { + panic!("wait_and_get_memory_details() is for DHAT users"); + }; + + MemoryDetails::from_file(&log_file) + } +} + +/// Returns the instruction count, extracted from the callgrind output file at the provided path +fn parse_callgrind_output(file: &Path) -> anyhow::Result { + let file_in = File::open(file).context("Unable to open callgrind output file")?; + + for line in BufReader::new(file_in).lines() { + let line = line.context("Error reading callgrind output file")?; + if let Some(line) = line.strip_prefix("summary: ") { + let instr_count = line + .trim() + .parse() + .context("Unable to parse instruction counts from callgrind output file")?; + + return Ok(instr_count); + } + } + + anyhow::bail!("`summary` section not found in callgrind output file") +} + +/// The instruction counts, for each side, after running a benchmark +#[derive(Copy, Clone)] +pub(crate) struct InstructionCounts { + pub client: u64, + pub server: u64, +} + +impl Sub for InstructionCounts { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self { + client: self.client - rhs.client, + server: self.server - rhs.server, + } + } +} + +/// Peak heap usage in bytes, for each side +#[derive(Copy, Clone)] +pub(crate) struct MemoryProfile { + pub client: MemoryDetails, + pub server: MemoryDetails, +} + +#[derive(Copy, Clone, Default)] +pub(crate) struct MemoryDetails { + pub heap_total_bytes: u64, + pub heap_total_blocks: u64, + pub heap_peak_bytes: u64, + pub heap_peak_blocks: u64, +} + +impl MemoryDetails { + /// Returns the heap usage, extracted from the DHAT log file at the provided path + fn from_file(file: &Path) -> anyhow::Result { + let file_in = File::open(file).context("Unable to open DHAT log file")?; + let mut out = Self::default(); + + /* + * Sample: + * + * ==1018358== Total: 690,380 bytes in 4,158 blocks + * ==1018358== At t-gmax: 70,539 bytes in 220 blocks + * ==1018358== At t-end: 8,648 bytes in 2 blocks + * ==1018358== Reads: 861,492 bytes + * ==1018358== Writes: 782,958 bytes + */ + + for line in BufReader::new(file_in).lines() { + let line = line.context("Error reading DHAT log file")?; + + match line + .split_whitespace() + .collect::>() + .as_slice() + { + [_, "Total:", bytes, "bytes", "in", blocks, "blocks"] => { + out.heap_total_bytes = parse_u64(bytes); + out.heap_total_blocks = parse_u64(blocks); + } + [_, "At", "t-gmax:", bytes, "bytes", "in", blocks, "blocks"] => { + out.heap_peak_bytes = parse_u64(bytes); + out.heap_peak_blocks = parse_u64(blocks); + } + _ => {} + } + } + + fn parse_u64(s: &str) -> u64 { + s.replace(",", "").parse().unwrap() + } + + Ok(out) + } +} + +/// Returns the detailed instruction diff between the baseline and the candidate +pub(crate) fn callgrind_diff( + baseline: &Path, + candidate: &Path, + scenario: &str, +) -> anyhow::Result { + // callgrind_annotate formats the callgrind output file, suitable for comparison with + // callgrind_differ + let callgrind_annotate_base = Command::new("callgrind_annotate") + .arg( + baseline + .join(CALLGRIND_OUTPUT_SUBDIR) + .join(scenario), + ) + // do not annotate source, to keep output compact + .arg("--auto=no") + .output() + .context("error waiting for callgrind_annotate to finish")?; + + let callgrind_annotate_candidate = Command::new("callgrind_annotate") + .arg( + candidate + .join(CALLGRIND_OUTPUT_SUBDIR) + .join(scenario), + ) + // do not annotate source, to keep output compact + .arg("--auto=no") + .output() + .context("error waiting for callgrind_annotate to finish")?; + + if !callgrind_annotate_base.status.success() { + anyhow::bail!( + "callgrind_annotate for base finished with an error (code = {:?})", + callgrind_annotate_base.status.code() + ) + } + + if !callgrind_annotate_candidate + .status + .success() + { + anyhow::bail!( + "callgrind_annotate for candidate finished with an error (code = {:?})", + callgrind_annotate_candidate + .status + .code() + ) + } + + let string_base = String::from_utf8(callgrind_annotate_base.stdout) + .context("callgrind_annotate produced invalid UTF8")?; + let string_candidate = String::from_utf8(callgrind_annotate_candidate.stdout) + .context("callgrind_annotate produced invalid UTF8")?; + + // TODO: reinstate actual diffing, using `callgrind_differ` crate + Ok(format!( + "Base output:\n{string_base}\n\ + =====\n\n\ + Candidate output:\n{string_candidate}\n" + )) +} + +/// A RAII-like object for enabling callgrind instruction counting. +/// +/// Warning: must not be nested. +/// +/// Instructions outside the scope of these objects are not counted. +pub(crate) struct CountInstructions; + +impl CountInstructions { + pub(crate) fn start() -> Self { + #[cfg(target_os = "linux")] + crabgrind::callgrind::toggle_collect(); + Self + } +} + +impl Drop for CountInstructions { + fn drop(&mut self) { + #[cfg(target_os = "linux")] + crabgrind::callgrind::toggle_collect(); + } +} diff --git a/connect-tests/Cargo.toml b/connect-tests/Cargo.toml index 5b61033caab..483712d7bce 100644 --- a/connect-tests/Cargo.toml +++ b/connect-tests/Cargo.toml @@ -7,10 +7,12 @@ description = "Rustls connectivity based integration tests." publish = false [dependencies] -rustls = { path = "../rustls", features = [ "logging" ]} +rustls = { path = "../rustls", features = ["log"] } [dev-dependencies] -hickory-resolver = { workspace = true } regex = { workspace = true } ring = { workspace = true } tokio = { workspace = true } + +[lints] +workspace = true diff --git a/connect-tests/tests/badssl.rs b/connect-tests/tests/badssl.rs index a3671e532e3..c8fe22e34ad 100644 --- a/connect-tests/tests/badssl.rs +++ b/connect-tests/tests/badssl.rs @@ -15,7 +15,9 @@ mod online { fn no_cbc() { connect("cbc.badssl.com") .fails() - .expect(r"TLS error: AlertReceived\(HandshakeFailure\)") + .expect( + r"TLS error: received fatal alert: the peer failed to negotiate an acceptable set of security parameters", + ) .go() .unwrap(); } @@ -24,7 +26,9 @@ mod online { fn no_rc4() { connect("rc4.badssl.com") .fails() - .expect(r"TLS error: AlertReceived\(HandshakeFailure\)") + .expect( + r"TLS error: received fatal alert: the peer failed to negotiate an acceptable set of security parameters", + ) .go() .unwrap(); } @@ -33,7 +37,7 @@ mod online { fn expired() { connect("expired.badssl.com") .fails() - .expect(r"TLS error: InvalidCertificate\(Expired\)") + .expect(r"TLS error: invalid peer certificate: certificate expired: verification time [0-9]+ \(UNIX\), but certificate is not valid after [0-9]+ \([0-9]+ seconds ago\)") .go() .unwrap(); } @@ -42,7 +46,7 @@ mod online { fn wrong_host() { connect("wrong.host.badssl.com") .fails() - .expect(r"TLS error: InvalidCertificate\(NotValidForName\)") + .expect(r#"TLS error: invalid peer certificate: certificate not valid for name \"wrong.host.badssl.com\"; certificate is only valid for DnsName\(\"\*.badssl.com\"\) or DnsName\(\"badssl.com\"\)"#) .go() .unwrap(); } @@ -51,7 +55,7 @@ mod online { fn self_signed() { connect("self-signed.badssl.com") .fails() - .expect(r"TLS error: InvalidCertificate\(UnknownIssuer\)") + .expect(r"TLS error: invalid peer certificate: UnknownIssuer") .go() .unwrap(); } @@ -60,7 +64,9 @@ mod online { fn no_dh() { connect("dh2048.badssl.com") .fails() - .expect(r"TLS error: AlertReceived\(HandshakeFailure\)") + .expect( + r"TLS error: received fatal alert: the peer failed to negotiate an acceptable set of security parameters", + ) .go() .unwrap(); } @@ -101,7 +107,7 @@ mod online { fn too_many_sans() { connect("10000-sans.badssl.com") .fails() - .expect(r"TLS error: InvalidMessage\(HandshakePayloadTooLarge\)") + .expect(r"TLS error: received corrupt message of type HandshakePayloadTooLarge") .go() .unwrap(); } @@ -119,7 +125,7 @@ mod online { fn sha1_2016() { connect("sha1-2016.badssl.com") .fails() - .expect(r"TLS error: InvalidCertificate\(Expired\)") + .expect(r"TLS error: invalid peer certificate: certificate expired: verification time [0-9]+ \(UNIX\), but certificate is not valid after [0-9]+ \([0-9]+ seconds ago\)") .go() .unwrap(); } diff --git a/connect-tests/tests/common/mod.rs b/connect-tests/tests/common/mod.rs index 145e7e9219f..cad5f0964aa 100644 --- a/connect-tests/tests/common/mod.rs +++ b/connect-tests/tests/common/mod.rs @@ -3,15 +3,15 @@ use std::process; use regex::Regex; -pub fn tlsserver_find() -> &'static str { +pub(super) fn tlsserver_find() -> &'static str { "../target/debug/tlsserver-mio" } -pub fn tlsclient_find() -> &'static str { +pub(super) fn tlsclient_find() -> &'static str { "../target/debug/tlsclient-mio" } -pub struct TlsClient { +pub(super) struct TlsClient { pub hostname: String, pub port: u16, pub http: bool, @@ -28,7 +28,7 @@ pub struct TlsClient { } impl TlsClient { - pub fn new(hostname: &str) -> Self { + pub(super) fn new(hostname: &str) -> Self { Self { hostname: hostname.to_string(), port: 443, @@ -46,64 +46,64 @@ impl TlsClient { } } - pub fn cafile(&mut self, cafile: &Path) -> &mut Self { + pub(super) fn cafile(&mut self, cafile: &Path) -> &mut Self { self.cafile = Some(cafile.to_path_buf()); self } - pub fn cache(&mut self, cache: &str) -> &mut Self { + pub(super) fn cache(&mut self, cache: &str) -> &mut Self { self.cache = Some(cache.to_string()); self } - pub fn no_sni(&mut self) -> &mut Self { + pub(super) fn no_sni(&mut self) -> &mut Self { self.no_sni = true; self } - pub fn insecure(&mut self) -> &mut Self { + pub(super) fn insecure(&mut self) -> &mut Self { self.insecure = true; self } - pub fn verbose(&mut self) -> &mut Self { + pub(super) fn verbose(&mut self) -> &mut Self { self.verbose = true; self } - pub fn max_fragment_size(&mut self, max_fragment_size: usize) -> &mut Self { + pub(super) fn max_fragment_size(&mut self, max_fragment_size: usize) -> &mut Self { self.max_fragment_size = Some(max_fragment_size); self } - pub fn port(&mut self, port: u16) -> &mut Self { + pub(super) fn port(&mut self, port: u16) -> &mut Self { self.port = port; self } - pub fn expect(&mut self, expect: &str) -> &mut Self { + pub(super) fn expect(&mut self, expect: &str) -> &mut Self { self.expect_output .push(expect.to_string()); self } - pub fn expect_log(&mut self, expect: &str) -> &mut Self { + pub(super) fn expect_log(&mut self, expect: &str) -> &mut Self { self.verbose = true; self.expect_log.push(expect.to_string()); self } - pub fn suite(&mut self, suite: &str) -> &mut Self { + pub(super) fn suite(&mut self, suite: &str) -> &mut Self { self.suites.push(suite.to_string()); self } - pub fn fails(&mut self) -> &mut Self { + pub(super) fn fails(&mut self) -> &mut Self { self.expect_fails = true; self } - pub fn go(&mut self) -> Option<()> { + pub(super) fn go(&self) -> Option<()> { let fragstring; let portstring = self.port.to_string(); let mut args = Vec::<&str>::new(); @@ -153,7 +153,7 @@ impl TlsClient { .args(&args) .env("SSLKEYLOGFILE", "./sslkeylogfile.txt") .output() - .unwrap_or_else(|e| panic!("failed to execute: {}", e)); + .unwrap_or_else(|e| panic!("failed to execute: {e}")); let stdout_str = String::from_utf8_lossy(&output.stdout); let stderr_str = String::from_utf8_lossy(&output.stderr); @@ -161,8 +161,8 @@ impl TlsClient { for expect in &self.expect_output { let re = Regex::new(expect).unwrap(); if re.find(&stdout_str).is_none() { - println!("We expected to find '{}' in the following output:", expect); - println!("{:?}", output); + println!("We expected to find '{expect}' in the following output:"); + println!("{output:?}"); panic!("Test failed"); } } @@ -170,8 +170,8 @@ impl TlsClient { for expect in &self.expect_log { let re = Regex::new(expect).unwrap(); if re.find(&stderr_str).is_none() { - println!("We expected to find '{}' in the following output:", expect); - println!("{:?}", output); + println!("We expected to find '{expect}' in the following output:"); + println!("{output:?}"); panic!("Test failed"); } } diff --git a/connect-tests/tests/ech.rs b/connect-tests/tests/ech.rs deleted file mode 100644 index d0a3f466b51..00000000000 --- a/connect-tests/tests/ech.rs +++ /dev/null @@ -1,60 +0,0 @@ -mod ech_config { - use hickory_resolver::config::{ResolverConfig, ResolverOpts}; - use hickory_resolver::proto::rr::rdata::svcb::{SvcParamKey, SvcParamValue}; - use hickory_resolver::proto::rr::{RData, RecordType}; - use hickory_resolver::{Resolver, TokioResolver}; - use rustls::internal::msgs::codec::{Codec, Reader}; - use rustls::internal::msgs::handshake::EchConfigPayload; - use rustls::pki_types::EchConfigListBytes; - - #[tokio::test] - async fn cloudflare() { - test_deserialize_ech_config_list("research.cloudflare.com").await; - } - - #[tokio::test] - async fn defo_ie() { - test_deserialize_ech_config_list("defo.ie").await; - } - - #[tokio::test] - async fn tls_ech_dev() { - test_deserialize_ech_config_list("tls-ech.dev").await; - } - - /// Lookup the ECH config list for a domain and deserialize it. - async fn test_deserialize_ech_config_list(domain: &str) { - let resolver = Resolver::tokio(ResolverConfig::google_https(), ResolverOpts::default()); - let tls_encoded_list = lookup_ech(&resolver, domain).await; - let parsed_configs = Vec::::read(&mut Reader::init(&tls_encoded_list)) - .expect("failed to deserialize ECH config list"); - assert!(!parsed_configs.is_empty()); - assert!(parsed_configs - .iter() - .all(|config| matches!(config, EchConfigPayload::V18(_)))); - } - - /// Use `resolver` to make an HTTPS record type query for `domain`, returning the - /// first SvcParam EchConfig value found, panicking if none are returned. - async fn lookup_ech(resolver: &TokioResolver, domain: &str) -> EchConfigListBytes<'static> { - resolver - .lookup(domain, RecordType::HTTPS) - .await - .expect("failed to lookup HTTPS record type") - .record_iter() - .find_map(|r| match r.data() { - RData::HTTPS(svcb) => svcb - .svc_params() - .iter() - .find_map(|sp| match sp { - (SvcParamKey::EchConfigList, SvcParamValue::EchConfigList(e)) => { - Some(e.clone().0) - } - _ => None, - }), - _ => None, - }) - .expect("missing expected HTTPS SvcParam EchConfig record") - .into() - } -} diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000000..75c033096a0 --- /dev/null +++ b/deny.toml @@ -0,0 +1,18 @@ +[licenses] +version = 2 +allow = [ + "Apache-2.0", + "BSD-3-Clause", + "CDLA-Permissive-2.0", + "ISC", + "MIT", + "OpenSSL", + "Unicode-3.0", + "Zlib", +] +private = { ignore = true } + +[advisories] +ignore = [ + "RUSTSEC-2024-0436", # Unmaintained paste via macro_rules_attributes; dev-dependency only +] diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 24e3acd9fe4..054f66ed911 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -7,14 +7,18 @@ description = "Rustls example code and tests." publish = false [dependencies] -async-std = { workspace = true, optional = true } clap = { workspace = true } env_logger = { workspace = true } hickory-resolver = { workspace = true } log = { workspace = true } mio = { workspace = true } rcgen = { workspace = true } -rustls = { path = "../rustls", features = [ "logging" ]} +rustls = { path = "../rustls", features = ["log"] } +rustls-aws-lc-rs = { path = "../rustls-aws-lc-rs" } +rustls-util = { path = "../rustls-util" } serde = { workspace = true } tokio = { workspace = true } webpki-roots = { workspace = true } + +[lints] +workspace = true diff --git a/examples/README.md b/examples/README.md index aa97553503f..b8d45bd25e9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -10,8 +10,6 @@ We recommend new users start by looking at `simpleclient.rs` and `simpleserver.r * `tlsclient-mio.rs` - shows a more complete client example that handles command line flags for customizing TLS options, and uses MIO to handle asynchronous I/O. * `limitedclient.rs` - shows how to configure Rustls so that unused cryptography is discarded by the linker. This client only supports TLS 1.3 and a single cipher suite. * `simple_0rtt_client.rs` - shows how to make a TLS 1.3 client connection that sends early 0RTT data. -* `unbuffered-client.rs` - shows an advanced example of using Rustls lower-level APIs to implement a client that does not buffer any data inside Rustls. -* `unbuffered-async-client.rs` - shows an advanced example of using Rustls lower-level APIs to implement a client that does not buffer any data inside Rustls, and that processes TLS events asynchronously. * `ech-client.rs` - shows how to configure Rustls to use encrypted client hello (ECH), including fetching an ECH config list with DNS-over-HTTPS. ## Server examples @@ -20,7 +18,6 @@ We recommend new users start by looking at `simpleclient.rs` and `simpleserver.r * `tlsserver-mio.rs` - shows a more complete server example that handles command line flags for customizing TLS options, and uses MIO to handle asynchronous I/O. * `simple_0rtt_server.rs` - shows how to make a TLS1.3 that accepts multiple connections and prints early 0RTT data. * `server_acceptor.rs` - shows how to use the `Acceptor` API to create a server that generates a unique `ServerConfig` for each client. This example also shows how to use client authentication, CRL revocation checking, and uses `rcgen` to generate its own certificates. -* `unbuffered-server.rs` - shows an advanced example of using Rustls lower-level APIs to implement a server that does not buffer any data inside Rustls. ## Client-Server examples diff --git a/examples/src/bin/ech-client.rs b/examples/src/bin/ech-client.rs index 62e9869b489..adcac9f5335 100644 --- a/examples/src/bin/ech-client.rs +++ b/examples/src/bin/ech-client.rs @@ -28,25 +28,27 @@ //! "SSL_ECH_STATUS": "success" //! ``` -use std::error::Error; +use core::error::Error; use std::fs; -use std::io::{stdout, BufReader, Read, Write}; +use std::io::{BufReader, Read, Write, stdout}; use std::net::{TcpStream, ToSocketAddrs}; use std::sync::Arc; use clap::Parser; -use hickory_resolver::config::{ResolverConfig, ResolverOpts}; +use hickory_resolver::config::{CLOUDFLARE, GOOGLE, ResolverConfig}; +use hickory_resolver::net::NetError; +use hickory_resolver::net::runtime::TokioRuntimeProvider; use hickory_resolver::proto::rr::rdata::svcb::{SvcParamKey, SvcParamValue}; use hickory_resolver::proto::rr::{RData, RecordType}; -use hickory_resolver::{ResolveError, Resolver, TokioResolver}; +use hickory_resolver::{Resolver, TokioResolver}; use log::trace; use rustls::client::{EchConfig, EchGreaseConfig, EchMode, EchStatus}; -use rustls::crypto::aws_lc_rs; -use rustls::crypto::aws_lc_rs::hpke::ALL_SUPPORTED_SUITES; use rustls::crypto::hpke::Hpke; use rustls::pki_types::pem::PemObject; use rustls::pki_types::{CertificateDer, EchConfigListBytes, ServerName}; -use rustls::RootCertStore; +use rustls::{ClientConfig, Connection, RootCertStore}; +use rustls_aws_lc_rs::hpke::ALL_SUPPORTED_SUITES; +use rustls_util::{KeyLogFile, Stream}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -54,21 +56,19 @@ async fn main() -> Result<(), Box> { let server_ech_configs = match (args.grease, args.ech_config) { (true, Some(_)) => return Err("cannot specify both --grease and --ech-config".into()), - (true, None) => { - Vec::new() // Force the use of the GREASE ext by skipping ECH config lookup - } - (false, Some(path)) => { - vec![read_ech(&path)?] - } + // Force the use of the GREASE ext by skipping ECH config lookup + (true, None) => Vec::new(), + (false, Some(path)) => vec![read_ech(&path)?], + // Find raw ECH configs using DNS-over-HTTPS with Hickory DNS. (false, None) => { - // Find raw ECH configs using DNS-over-HTTPS with Hickory DNS. - let resolver_config = if args.use_cloudflare_dns { - ResolverConfig::cloudflare_https() - } else { - ResolverConfig::google_https() - }; + let resolver_config = ResolverConfig::https(match args.use_cloudflare_dns { + true => &CLOUDFLARE, + false => &GOOGLE, + }); + lookup_ech_configs( - &Resolver::tokio(resolver_config, ResolverOpts::default()), + &Resolver::builder_with_config(resolver_config, TokioRuntimeProvider::default()) + .build()?, &args.inner_hostname, args.port, ) @@ -111,15 +111,14 @@ async fn main() -> Result<(), Box> { }, }; - // Construct a rustls client config with a custom provider, and ECH enabled. - let mut config = - rustls::ClientConfig::builder_with_provider(aws_lc_rs::default_provider().into()) - .with_ech(ech_mode)? - .with_root_certificates(root_store) - .with_no_client_auth(); + // Construct a rustls client config with a TLS1.3-only provider, and ECH enabled. + let mut config = ClientConfig::builder(rustls_aws_lc_rs::DEFAULT_TLS13_PROVIDER.into()) + .with_ech(ech_mode) + .with_root_certificates(root_store) + .with_no_client_auth()?; // Allow using SSLKEYLOGFILE. - config.key_log = Arc::new(rustls::KeyLogFile::new()); + config.key_log = Arc::new(KeyLogFile::new()); let config = Arc::new(config); // The "inner" SNI that we're really trying to reach. @@ -127,21 +126,26 @@ async fn main() -> Result<(), Box> { for i in 0..args.num_reqs { trace!("\nRequest {} of {}", i + 1, args.num_reqs); - let mut conn = rustls::ClientConnection::new(config.clone(), server_name.clone())?; + let mut conn = config + .connect(server_name.clone()) + .build()?; // The "outer" server that we're connecting to. let sock_addr = (args.outer_hostname.as_str(), args.port) .to_socket_addrs()? .next() .ok_or("cannot resolve hostname")?; let mut sock = TcpStream::connect(sock_addr)?; - let mut tls = rustls::Stream::new(&mut conn, &mut sock); + let mut tls = Stream::new(&mut conn, &mut sock); - let request = - format!( - "GET /{} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\nAccept-Encoding: identity\r\n\r\n", - args.path, - args.host.as_ref().unwrap_or(&args.inner_hostname), - ); + // Trim a leading '/' from the user-supplied path so we never emit a request line + // like `GET //foo HTTP/1.1`. + let path = args.path.trim_start_matches('/'); + let request = format!( + "GET /{path} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\nAccept-Encoding: identity\r\n\r\n", + args.host + .as_ref() + .unwrap_or(&args.inner_hostname), + ); dbg!(&request); tls.write_all(request.as_bytes())?; assert!(!tls.conn.is_handshaking()); @@ -226,7 +230,7 @@ async fn lookup_ech_configs( resolver: &TokioResolver, domain: &str, port: u16, -) -> Result>, ResolveError> { +) -> Result>, NetError> { // For non-standard ports, lookup the ECHConfig using port-prefix naming // See: https://datatracker.ietf.org/doc/html/rfc9460#section-9.1 let qname_to_lookup = match port { @@ -239,13 +243,13 @@ async fn lookup_ech_configs( .await?; let mut ech_config_lists = Vec::new(); - for r in lookup.record_iter() { - let RData::HTTPS(svcb) = r.data() else { + for r in lookup.answers() { + let RData::HTTPS(svcb) = &r.data else { continue; }; ech_config_lists.extend( - svcb.svc_params() + svcb.svc_params .iter() .find_map(|sp| match sp { (SvcParamKey::EchConfigList, SvcParamValue::EchConfigList(e)) => { @@ -272,4 +276,4 @@ fn read_ech(path: &str) -> Result, Box> { /// A HPKE suite to use for GREASE ECH. /// /// A real implementation should vary this suite across all of the suites that are supported. -static GREASE_HPKE_SUITE: &dyn Hpke = aws_lc_rs::hpke::DH_KEM_X25519_HKDF_SHA256_AES_128; +static GREASE_HPKE_SUITE: &dyn Hpke = rustls_aws_lc_rs::hpke::DH_KEM_X25519_HKDF_SHA256_AES_128; diff --git a/examples/src/bin/limitedclient.rs b/examples/src/bin/limitedclient.rs index 62443caef7e..04229907ba9 100644 --- a/examples/src/bin/limitedclient.rs +++ b/examples/src/bin/limitedclient.rs @@ -2,36 +2,37 @@ //! so that unused cryptography in rustls can be discarded by the linker. You can //! observe using `nm` that the binary of this program does not contain any AES code. -use std::io::{stdout, Read, Write}; +use std::borrow::Cow; +use std::io::{Read, Write, stdout}; use std::net::TcpStream; use std::sync::Arc; -use rustls::crypto::{aws_lc_rs as provider, CryptoProvider}; +use rustls::crypto::CryptoProvider; +use rustls::{ClientConfig, RootCertStore}; +use rustls_aws_lc_rs as provider; +use rustls_util::Stream; fn main() { - let root_store = rustls::RootCertStore::from_iter( + let root_store = RootCertStore::from_iter( webpki_roots::TLS_SERVER_ROOTS .iter() .cloned(), ); - let config = rustls::ClientConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![provider::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256], - kx_groups: vec![provider::kx_group::X25519], - ..provider::default_provider() - } - .into(), - ) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() - .with_root_certificates(root_store) - .with_no_client_auth(); + let config = Arc::new( + ClientConfig::builder(PROVIDER.into()) + .with_root_certificates(root_store) + .with_no_client_auth() + .unwrap(), + ); let server_name = "www.rust-lang.org".try_into().unwrap(); - let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap(); + let mut conn = config + .connect(server_name) + .build() + .unwrap(); let mut sock = TcpStream::connect("www.rust-lang.org:443").unwrap(); - let mut tls = rustls::Stream::new(&mut conn, &mut sock); + let mut tls = Stream::new(&mut conn, &mut sock); tls.write_all( concat!( "GET / HTTP/1.1\r\n", @@ -57,3 +58,10 @@ fn main() { tls.read_to_end(&mut plaintext).unwrap(); stdout().write_all(&plaintext).unwrap(); } + +const PROVIDER: CryptoProvider = CryptoProvider { + tls12_cipher_suites: Cow::Borrowed(&[]), + tls13_cipher_suites: Cow::Borrowed(&[provider::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256]), + kx_groups: Cow::Borrowed(&[provider::kx_group::X25519]), + ..provider::DEFAULT_PROVIDER +}; diff --git a/examples/src/bin/server_acceptor.rs b/examples/src/bin/server_acceptor.rs index 46f4141b3e8..8b800b7b6f5 100644 --- a/examples/src/bin/server_acceptor.rs +++ b/examples/src/bin/server_acceptor.rs @@ -4,19 +4,22 @@ //! //! For a more complete server demonstration, see `tlsserver-mio.rs`. +use core::ops::Add; +use core::time::Duration; use std::fs::File; use std::io::{Read, Write}; -use std::ops::Add; use std::path::PathBuf; use std::sync::Arc; -use std::time::Duration; use std::{fs, thread}; use clap::Parser; -use rcgen::KeyPair; +use rcgen::{Issuer, KeyPair, SerialNumber}; +use rustls::RootCertStore; +use rustls::crypto::{CryptoProvider, Identity}; use rustls::pki_types::{CertificateRevocationListDer, PrivatePkcs8KeyDer}; use rustls::server::{Acceptor, ClientHello, ServerConfig, WebPkiClientVerifier}; -use rustls::RootCertStore; +use rustls_aws_lc_rs::DEFAULT_PROVIDER; +use rustls_util::{KeyLogFile, complete_io}; fn main() { let args = Args::parse(); @@ -41,13 +44,14 @@ fn main() { // Write out the parts of the test PKI a client will need to connect: // * The CA certificate for validating the server certificate. // * The client certificate and key for its presented mTLS identity. - write_pem(&args.ca_path, &test_pki.ca_cert.cert.pem()); - write_pem(&args.client_cert_path, &test_pki.client_cert.cert.pem()); + write_pem(&args.ca_path, &test_pki.ca_cert.1.pem()); + write_pem(&args.client_cert_path, &test_pki.client_cert.0.cert.pem()); write_pem( &args.client_key_path, &test_pki .client_cert - .key_pair + .0 + .signing_key .serialize_pem(), ); @@ -103,16 +107,17 @@ fn main() { // Proceed with handling the ServerConnection // Important: We do no error handling here, but you should! - _ = conn.complete_io(&mut stream); + _ = complete_io(&mut stream, &mut conn); } } /// A test PKI with a CA certificate, server certificate, and client certificate. struct TestPki { + provider: Arc, roots: Arc, - ca_cert: rcgen::CertifiedKey, - client_cert: rcgen::CertifiedKey, - server_cert: rcgen::CertifiedKey, + ca_cert: (Issuer<'static, KeyPair>, rcgen::Certificate), + client_cert: (rcgen::CertifiedKey, SerialNumber), + server_cert: rcgen::CertifiedKey, } impl TestPki { @@ -135,6 +140,7 @@ impl TestPki { ]; let ca_key = KeyPair::generate_for(alg).unwrap(); let ca_cert = ca_params.self_signed(&ca_key).unwrap(); + let ca = Issuer::new(ca_params, ca_key); // Create a server end entity cert issued by the CA. let mut server_ee_params = @@ -143,7 +149,7 @@ impl TestPki { server_ee_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth]; let ee_key = KeyPair::generate_for(alg).unwrap(); let server_cert = server_ee_params - .signed_by(&ee_key, &ca_cert, &ca_key) + .signed_by(&ee_key, &ca) .unwrap(); // Create a client end entity cert issued by the CA. @@ -153,10 +159,11 @@ impl TestPki { .push(rcgen::DnType::CommonName, "Example Client"); client_ee_params.is_ca = rcgen::IsCa::NoCa; client_ee_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ClientAuth]; - client_ee_params.serial_number = Some(rcgen::SerialNumber::from(vec![0xC0, 0xFF, 0xEE])); + let client_serial = SerialNumber::from(vec![0xC0, 0xFF, 0xEE]); + client_ee_params.serial_number = Some(client_serial); let client_key = KeyPair::generate_for(alg).unwrap(); let client_cert = client_ee_params - .signed_by(&client_key, &ca_cert, &ca_key) + .signed_by(&client_key, &ca) .unwrap(); // Create a root cert store that includes the CA certificate. @@ -165,18 +172,19 @@ impl TestPki { .add(ca_cert.der().clone()) .unwrap(); Self { + provider: Arc::new(DEFAULT_PROVIDER), roots: roots.into(), - ca_cert: rcgen::CertifiedKey { - cert: ca_cert, - key_pair: ca_key, - }, - client_cert: rcgen::CertifiedKey { - cert: client_cert, - key_pair: client_key, - }, + ca_cert: (ca, ca_cert), + client_cert: ( + rcgen::CertifiedKey { + cert: client_cert, + signing_key: client_key, + }, + client_ee_params.serial_number.unwrap(), + ), server_cert: rcgen::CertifiedKey { cert: server_cert, - key_pair: ee_key, + signing_key: ee_key, }, } } @@ -188,7 +196,7 @@ impl TestPki { /// /// Since the presented client certificate is not available in the `ClientHello` the server /// must know ahead of time which CRLs it cares about. - fn server_config(&self, crl_path: &str, _hello: ClientHello) -> Arc { + fn server_config(&self, crl_path: &str, _hello: ClientHello<'_>) -> Arc { // Read the latest CRL from disk. The CRL is being periodically updated by the crl_updater // thread. let mut crl_file = File::open(crl_path).unwrap(); @@ -196,21 +204,25 @@ impl TestPki { crl_file.read_to_end(&mut crl).unwrap(); // Construct a fresh verifier using the test PKI roots, and the updated CRL. - let verifier = WebPkiClientVerifier::builder(self.roots.clone()) - .with_crls([CertificateRevocationListDer::from(crl)]) - .build() - .unwrap(); + let verifier = Arc::new( + WebPkiClientVerifier::builder(self.roots.clone(), &self.provider) + .with_crls([CertificateRevocationListDer::from(crl)]) + .build() + .unwrap(), + ); // Build a server config using the fresh verifier. If necessary, this could be customized // based on the ClientHello (e.g. selecting a different certificate, or customizing // supported algorithms/protocol versions). - let mut server_config = ServerConfig::builder() + let mut server_config = ServerConfig::builder(self.provider.clone()) .with_client_cert_verifier(verifier) .with_single_cert( - vec![self.server_cert.cert.der().clone()], + Arc::from( + Identity::from_cert_chain(vec![self.server_cert.cert.der().clone()]).unwrap(), + ), PrivatePkcs8KeyDer::from( self.server_cert - .key_pair + .signing_key .serialize_der(), ) .into(), @@ -218,7 +230,7 @@ impl TestPki { .unwrap(); // Allow using SSLKEYLOGFILE. - server_config.key_log = Arc::new(rustls::KeyLogFile::new()); + server_config.key_log = Arc::new(KeyLogFile::new()); Arc::new(server_config) } @@ -227,9 +239,9 @@ impl TestPki { /// The CRL will be signed by the test PKI CA and returned in DER serialized form. fn crl( &self, - serials: Vec, + serials: Vec, next_update_seconds: u64, - ) -> CertificateRevocationListDer { + ) -> CertificateRevocationListDer<'static> { // In a real use-case you would want to set this to the current date/time. let now = rcgen::date_time_ymd(2023, 1, 1); @@ -248,13 +260,13 @@ impl TestPki { let crl_params = rcgen::CertificateRevocationListParams { this_update: now, next_update: now.add(Duration::from_secs(next_update_seconds)), - crl_number: rcgen::SerialNumber::from(1234), + crl_number: SerialNumber::from(1234), issuing_distribution_point: None, revoked_certs, key_identifier_method: rcgen::KeyIdMethod::Sha256, }; crl_params - .signed_by(&self.ca_cert.cert, &self.ca_cert.key_pair) + .signed_by(&self.ca_cert.0) .unwrap() .into() } @@ -280,14 +292,7 @@ impl CrlUpdater { thread::sleep(self.sleep_duration); let revoked_certs = if revoked { - vec![self - .pki - .client_cert - .cert - .params() - .serial_number - .clone() - .unwrap()] + vec![self.pki.client_cert.1.clone()] } else { Vec::default() }; diff --git a/examples/src/bin/simple_0rtt_client.rs b/examples/src/bin/simple_0rtt_client.rs index 6a27d44cb0e..576bbb50f95 100644 --- a/examples/src/bin/simple_0rtt_client.rs +++ b/examples/src/bin/simple_0rtt_client.rs @@ -13,30 +13,34 @@ //! Note that `unwrap()` is used to deal with networking errors; this is not something //! that is sensible outside of example code. +use core::str::FromStr; use std::env; use std::io::{BufRead, BufReader, Write}; use std::net::TcpStream; -use std::str::FromStr; use std::sync::Arc; use rustls::pki_types::pem::PemObject; use rustls::pki_types::{CertificateDer, ServerName}; -use rustls::RootCertStore; +use rustls::{ClientConfig, RootCertStore}; +use rustls_aws_lc_rs::DEFAULT_PROVIDER; +use rustls_util::{KeyLogFile, Stream}; -fn start_connection(config: &Arc, domain_name: &str, port: u16) { +fn start_connection(config: &Arc, domain_name: &str, port: u16) { let server_name = ServerName::try_from(domain_name) .expect("invalid DNS name") .to_owned(); - let mut conn = rustls::ClientConnection::new(Arc::clone(config), server_name).unwrap(); - let mut sock = TcpStream::connect(format!("{}:{}", domain_name, port)).unwrap(); + let mut conn = config + .connect(server_name) + .build() + .unwrap(); + let mut sock = TcpStream::connect(format!("{domain_name}:{port}")).unwrap(); sock.set_nodelay(true).unwrap(); let request = format!( "GET / HTTP/1.1\r\n\ - Host: {}\r\n\ + Host: {domain_name}\r\n\ Connection: close\r\n\ Accept-Encoding: identity\r\n\ - \r\n", - domain_name + \r\n" ); // If early data is available with this server, then early_data() @@ -49,7 +53,7 @@ fn start_connection(config: &Arc, domain_name: &str, port: println!(" * 0-RTT request sent"); } - let mut stream = rustls::Stream::new(&mut conn, &mut sock); + let mut stream = Stream::new(&mut conn, &mut sock); // Complete handshake. stream.flush().unwrap(); @@ -69,7 +73,7 @@ fn start_connection(config: &Arc, domain_name: &str, port: BufReader::new(stream) .read_line(&mut first_response_line) .unwrap(); - println!(" * Server response: {:?}", first_response_line); + println!(" * Server response: {first_response_line:?}"); } fn main() { @@ -79,7 +83,7 @@ fn main() { args.next(); let domain_name = args .next() - .unwrap_or("jbp.io".to_owned()); + .unwrap_or_else(|| "jbp.io".to_owned()); let port = args .next() .map(|port| u16::from_str(&port).expect("invalid port")) @@ -100,12 +104,13 @@ fn main() { ) } - let mut config = rustls::ClientConfig::builder() + let mut config = ClientConfig::builder(Arc::new(DEFAULT_PROVIDER)) .with_root_certificates(root_store) - .with_no_client_auth(); + .with_no_client_auth() + .unwrap(); // Allow using SSLKEYLOGFILE. - config.key_log = Arc::new(rustls::KeyLogFile::new()); + config.key_log = Arc::new(KeyLogFile::new()); // Enable early data. config.enable_early_data = true; diff --git a/examples/src/bin/simple_0rtt_server.rs b/examples/src/bin/simple_0rtt_server.rs index 1256c57fb0b..2fe424a78c0 100644 --- a/examples/src/bin/simple_0rtt_server.rs +++ b/examples/src/bin/simple_0rtt_server.rs @@ -12,14 +12,18 @@ //! Note that `unwrap()` is used to deal with networking errors; this is not something //! that is sensible outside of example code. -use std::error::Error as StdError; +use core::error::Error as StdError; use std::io::{Read, Write}; use std::net::TcpListener; use std::sync::Arc; use std::{env, io}; +use rustls::crypto::Identity; use rustls::pki_types::pem::PemObject; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; +use rustls::{Connection, ServerConfig, ServerConnection}; +use rustls_aws_lc_rs::DEFAULT_PROVIDER; +use rustls_util::complete_io; fn main() -> Result<(), Box> { let mut args = env::args(); @@ -34,13 +38,13 @@ fn main() -> Result<(), Box> { let certs = CertificateDer::pem_file_iter(cert_file) .expect("cannot open certificate file") .map(|cert| cert.unwrap()) - .collect::>(); + .collect(); let private_key = PrivateKeyDer::from_pem_file(private_key_file).expect("cannot open private key file"); - let mut config = rustls::ServerConfig::builder() + let mut config = ServerConfig::builder(Arc::new(DEFAULT_PROVIDER)) .with_no_client_auth() - .with_single_cert(certs, private_key)?; + .with_single_cert(Arc::new(Identity::from_cert_chain(certs)?), private_key)?; config.max_early_data_size = 1000; let listener = TcpListener::bind(format!("[::]:{}", 4443)).unwrap(); @@ -50,7 +54,7 @@ fn main() -> Result<(), Box> { println!("Accepting connection"); - let mut conn = rustls::ServerConnection::new(Arc::new(config.clone()))?; + let mut conn = ServerConnection::new(Arc::new(config.clone()))?; let mut buf = Vec::new(); let mut did_early_data = false; @@ -68,7 +72,7 @@ fn main() -> Result<(), Box> { match conn.read_tls(&mut stream) { Ok(0) => return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()), Ok(_) => break, - Err(ref err) if err.kind() == io::ErrorKind::Interrupted => {} + Err(err) if err.kind() == io::ErrorKind::Interrupted => {} Err(err) => return Err(err.into()), }; } @@ -91,7 +95,7 @@ fn main() -> Result<(), Box> { .unwrap(); if bytes_read != 0 { - println!("Early data from client: {:?}", buf); + println!("Early data from client: {buf:?}"); } } } @@ -105,6 +109,6 @@ fn main() -> Result<(), Box> { conn.writer() .write_all(b"Hello from the server")?; conn.send_close_notify(); - conn.complete_io(&mut stream)?; + complete_io(&mut stream, &mut conn)?; } } diff --git a/examples/src/bin/simpleclient.rs b/examples/src/bin/simpleclient.rs index 6b042ed0211..8fcd9c467d1 100644 --- a/examples/src/bin/simpleclient.rs +++ b/examples/src/bin/simpleclient.rs @@ -8,27 +8,33 @@ //! Note that `unwrap()` is used to deal with networking errors; this is not something //! that is sensible outside of example code. -use std::io::{stdout, Read, Write}; +use std::io::{Read, Write, stdout}; use std::net::TcpStream; use std::sync::Arc; -use rustls::RootCertStore; +use rustls::{ClientConfig, RootCertStore}; +use rustls_util::{KeyLogFile, Stream}; fn main() { let root_store = RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.into(), }; - let mut config = rustls::ClientConfig::builder() + + let mut config = ClientConfig::builder(rustls_aws_lc_rs::DEFAULT_PROVIDER.into()) .with_root_certificates(root_store) - .with_no_client_auth(); + .with_no_client_auth() + .unwrap(); // Allow using SSLKEYLOGFILE. - config.key_log = Arc::new(rustls::KeyLogFile::new()); + config.key_log = Arc::new(KeyLogFile::new()); let server_name = "www.rust-lang.org".try_into().unwrap(); - let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap(); + let mut conn = Arc::new(config) + .connect(server_name) + .build() + .unwrap(); let mut sock = TcpStream::connect("www.rust-lang.org:443").unwrap(); - let mut tls = rustls::Stream::new(&mut conn, &mut sock); + let mut tls = Stream::new(&mut conn, &mut sock); tls.write_all( concat!( "GET / HTTP/1.1\r\n", diff --git a/examples/src/bin/simpleserver.rs b/examples/src/bin/simpleserver.rs index f3fa54a144b..71ead55248c 100644 --- a/examples/src/bin/simpleserver.rs +++ b/examples/src/bin/simpleserver.rs @@ -7,14 +7,18 @@ //! Note that `unwrap()` is used to deal with networking errors; this is not something //! that is sensible outside of example code. +use core::error::Error as StdError; use std::env; -use std::error::Error as StdError; use std::io::{Read, Write}; use std::net::TcpListener; use std::sync::Arc; +use rustls::crypto::Identity; use rustls::pki_types::pem::PemObject; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; +use rustls::{ServerConfig, ServerConnection}; +use rustls_aws_lc_rs::DEFAULT_PROVIDER; +use rustls_util::Stream; fn main() -> Result<(), Box> { let mut args = env::args(); @@ -31,21 +35,19 @@ fn main() -> Result<(), Box> { .map(|cert| cert.unwrap()) .collect(); let private_key = PrivateKeyDer::from_pem_file(private_key_file).unwrap(); - let config = rustls::ServerConfig::builder() + let config = ServerConfig::builder(Arc::new(DEFAULT_PROVIDER)) .with_no_client_auth() - .with_single_cert(certs, private_key)?; + .with_single_cert(Arc::new(Identity::from_cert_chain(certs)?), private_key)?; let listener = TcpListener::bind(format!("[::]:{}", 4443)).unwrap(); - let (mut stream, _) = listener.accept()?; + let (mut tcp_stream, _) = listener.accept()?; + let mut conn = ServerConnection::new(Arc::new(config))?; + let mut tls_stream = Stream::new(&mut conn, &mut tcp_stream); - let mut conn = rustls::ServerConnection::new(Arc::new(config))?; - conn.complete_io(&mut stream)?; - - conn.writer() - .write_all(b"Hello from the server")?; - conn.complete_io(&mut stream)?; + tls_stream.write_all(b"Hello from the server")?; + tls_stream.flush()?; let mut buf = [0; 64]; - let len = conn.reader().read(&mut buf)?; + let len = tls_stream.read(&mut buf)?; println!("Received message from client: {:?}", &buf[..len]); Ok(()) diff --git a/examples/src/bin/tlsclient-mio.rs b/examples/src/bin/tlsclient-mio.rs index 02a84384322..0044cc50abf 100644 --- a/examples/src/bin/tlsclient-mio.rs +++ b/examples/src/bin/tlsclient-mio.rs @@ -19,6 +19,7 @@ //! //! [mio]: https://docs.rs/mio/latest/mio/ +use std::borrow::Cow; use std::io::{self, Read, Write}; use std::net::ToSocketAddrs; use std::sync::Arc; @@ -26,10 +27,15 @@ use std::{process, str}; use clap::Parser; use mio::net::TcpStream; -use rustls::crypto::{aws_lc_rs as provider, CryptoProvider}; +use rustls::client::Tls12Resumption; +use rustls::crypto::kx::SupportedKxGroup; +use rustls::crypto::{CryptoProvider, Identity}; +use rustls::enums::{ApplicationProtocol, ProtocolVersion}; use rustls::pki_types::pem::PemObject; use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName}; -use rustls::RootCertStore; +use rustls::{ClientConfig, ClientConnection, Connection, RootCertStore}; +use rustls_aws_lc_rs as provider; +use rustls_util::KeyLogFile; const CLIENT: mio::Token = mio::Token(0); @@ -39,20 +45,19 @@ struct TlsClient { socket: TcpStream, closing: bool, clean_closure: bool, - tls_conn: rustls::ClientConnection, + tls_conn: ClientConnection, } impl TlsClient { - fn new( - sock: TcpStream, - server_name: ServerName<'static>, - cfg: Arc, - ) -> Self { + fn new(sock: TcpStream, server_name: ServerName<'static>, cfg: Arc) -> Self { Self { socket: sock, closing: false, clean_closure: false, - tls_conn: rustls::ClientConnection::new(cfg, server_name).unwrap(), + tls_conn: cfg + .connect(server_name) + .build() + .unwrap(), } } @@ -74,7 +79,7 @@ impl TlsClient { } } - fn read_source_to_end(&mut self, rd: &mut dyn io::Read) -> io::Result { + fn read_source_to_end(&mut self, rd: &mut dyn Read) -> io::Result { let mut buf = Vec::new(); let len = rd.read_to_end(&mut buf)?; self.tls_conn @@ -93,7 +98,7 @@ impl TlsClient { if error.kind() == io::ErrorKind::WouldBlock { return; } - println!("TLS read error: {:?}", error); + println!("TLS read error: {error:?}"); self.closing = true; return; } @@ -115,7 +120,7 @@ impl TlsClient { let io_state = match self.tls_conn.process_new_packets() { Ok(io_state) => io_state, Err(err) => { - println!("TLS error: {:?}", err); + println!("TLS error: {err}"); self.closing = true; return; } @@ -185,7 +190,7 @@ impl TlsClient { self.closing } } -impl io::Write for TlsClient { +impl Write for TlsClient { fn write(&mut self, bytes: &[u8]) -> io::Result { self.tls_conn.writer().write(bytes) } @@ -195,7 +200,7 @@ impl io::Write for TlsClient { } } -impl io::Read for TlsClient { +impl Read for TlsClient { fn read(&mut self, bytes: &mut [u8]) -> io::Result { self.tls_conn.reader().read(bytes) } @@ -232,6 +237,10 @@ struct Args { #[clap(long)] suite: Vec, + /// Disable default key exchange list, and use KX instead. Maybe be used multiple times. + #[clap(long)] + key_exchange: Vec, + /// Send ALPN extension containing PROTOCOL. /// May be used multiple times to offer several protocols. #[clap(long)] @@ -270,48 +279,110 @@ struct Args { hostname: String, } -/// Find a ciphersuite with the given name -fn find_suite(name: &str) -> Option { - for suite in provider::ALL_CIPHER_SUITES { - let sname = format!("{:?}", suite.suite()).to_lowercase(); +impl Args { + fn provider(&self) -> CryptoProvider { + let kx_groups = match self.key_exchange.as_slice() { + [] => Cow::Borrowed(provider::DEFAULT_KX_GROUPS), + items => Cow::Owned( + items + .iter() + .map(|kx| find_key_exchange(kx)) + .collect::>(), + ), + }; + + let provider = match lookup_versions(&self.protover).as_slice() { + [ProtocolVersion::TLSv1_2] => provider::DEFAULT_TLS12_PROVIDER, + [ProtocolVersion::TLSv1_3] => provider::DEFAULT_TLS13_PROVIDER, + _ => provider::DEFAULT_PROVIDER, + }; - if sname == name.to_string().to_lowercase() { - return Some(*suite); + let provider = CryptoProvider { + kx_groups, + ..provider + }; + + match self.suite.as_slice() { + [] => provider, + _ => filter_suites(provider, &self.suite), } } - - None } -/// Make a vector of ciphersuites named in `suites` -fn lookup_suites(suites: &[String]) -> Vec { - let mut out = Vec::new(); +/// Find a key exchange with the given name +fn find_key_exchange(name: &str) -> &'static dyn SupportedKxGroup { + for kx_group in provider::ALL_KX_GROUPS { + let kx_name = format!("{:?}", kx_group.name()).to_lowercase(); - for csname in suites { - let scs = find_suite(csname); - match scs { - Some(s) => out.push(s), - None => panic!("cannot look up ciphersuite '{}'", csname), + if kx_name == name.to_string().to_lowercase() { + return *kx_group; } } - out + panic!("cannot find key exchange with name '{name}'"); +} + +/// Alter `provider` to reduce the set of ciphersuites to just `suites` +fn filter_suites(mut provider: CryptoProvider, suites: &[String]) -> CryptoProvider { + // first, check `suites` all name known suites, and will have some effect + let known_suites = provider + .tls12_cipher_suites + .iter() + .map(|cs| cs.common.suite) + .chain( + provider + .tls13_cipher_suites + .iter() + .map(|cs| cs.common.suite), + ) + .map(|cs| format!("{:?}", cs).to_lowercase()) + .collect::>(); + + for s in suites { + if !known_suites.contains(&s.to_lowercase()) { + panic!( + "unsupported ciphersuite '{s}'; should be one of {known_suites}", + known_suites = known_suites.join(", ") + ); + } + } + + // now discard non-named suites + provider + .tls12_cipher_suites + .to_mut() + .retain(|cs| { + let name = format!("{:?}", cs.common.suite).to_lowercase(); + suites + .iter() + .any(|s| s.to_lowercase() == name) + }); + provider + .tls13_cipher_suites + .to_mut() + .retain(|cs| { + let name = format!("{:?}", cs.common.suite).to_lowercase(); + suites + .iter() + .any(|s| s.to_lowercase() == name) + }); + + provider } /// Make a vector of protocol versions named in `versions` -fn lookup_versions(versions: &[String]) -> Vec<&'static rustls::SupportedProtocolVersion> { +fn lookup_versions(versions: &[String]) -> Vec { let mut out = Vec::new(); for vname in versions { let version = match vname.as_ref() { - "1.2" => &rustls::version::TLS12, - "1.3" => &rustls::version::TLS13, - _ => panic!( - "cannot look up version '{}', valid are '1.2' and '1.3'", - vname - ), + "1.2" => ProtocolVersion::TLSv1_2, + "1.3" => ProtocolVersion::TLSv1_3, + _ => panic!("cannot look up version '{vname}', valid are '1.2' and '1.3'"), }; - out.push(version); + if !out.contains(&version) { + out.push(version); + } } out @@ -329,70 +400,71 @@ fn load_private_key(filename: &str) -> PrivateKeyDer<'static> { } mod danger { - use rustls::client::danger::HandshakeSignatureValid; - use rustls::crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}; - use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; - use rustls::DigitallySignedStruct; + use core::hash::Hasher; + use std::sync::Arc; + + use rustls::client::danger::{ + HandshakeSignatureValid, PeerVerified, ServerIdentity, ServerVerifier, + SignatureVerificationInput, + }; + use rustls::crypto::{ + CryptoProvider, SignatureScheme, verify_tls12_signature, verify_tls13_signature, + }; + use rustls::enums::CertificateType; + use rustls::{DistinguishedName, Error}; #[derive(Debug)] - pub struct NoCertificateVerification(CryptoProvider); + pub(super) struct NoCertificateVerification(CryptoProvider); impl NoCertificateVerification { - pub fn new(provider: CryptoProvider) -> Self { + pub(super) fn new(provider: CryptoProvider) -> Self { Self(provider) } } - impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification { - fn verify_server_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp: &[u8], - _now: UnixTime, - ) -> Result { - Ok(rustls::client::danger::ServerCertVerified::assertion()) + impl ServerVerifier for NoCertificateVerification { + fn verify_identity(&self, _identity: &ServerIdentity<'_>) -> Result { + Ok(PeerVerified::assertion()) } fn verify_tls12_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls12_signature( - message, - cert, - dss, - &self.0.signature_verification_algorithms, - ) + input: &SignatureVerificationInput<'_>, + ) -> Result { + verify_tls12_signature(input, &self.0.signature_verification_algorithms) } fn verify_tls13_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls13_signature( - message, - cert, - dss, - &self.0.signature_verification_algorithms, - ) + input: &SignatureVerificationInput<'_>, + ) -> Result { + verify_tls13_signature(input, &self.0.signature_verification_algorithms) } - fn supported_verify_schemes(&self) -> Vec { + fn supported_verify_schemes(&self) -> Vec { self.0 .signature_verification_algorithms .supported_schemes() } + + fn request_ocsp_response(&self) -> bool { + false + } + + fn hash_config(&self, _: &mut dyn Hasher) {} + + fn supported_certificate_types(&self) -> &'static [CertificateType] { + &[CertificateType::X509] + } + + fn root_hint_subjects(&self) -> Option> { + None + } } } /// Build a `ClientConfig` from our arguments -fn make_config(args: &Args) -> Arc { +fn make_config(args: &Args) -> Arc { let mut root_store = RootCertStore::empty(); if let Some(cafile) = args.cafile.as_ref() { @@ -409,49 +481,28 @@ fn make_config(args: &Args) -> Arc { ); } - let suites = if !args.suite.is_empty() { - lookup_suites(&args.suite) - } else { - provider::DEFAULT_CIPHER_SUITES.to_vec() - }; - - let versions = if !args.protover.is_empty() { - lookup_versions(&args.protover) - } else { - rustls::DEFAULT_VERSIONS.to_vec() - }; - - let config = rustls::ClientConfig::builder_with_provider( - CryptoProvider { - cipher_suites: suites, - ..provider::default_provider() - } - .into(), - ) - .with_protocol_versions(&versions) - .expect("inconsistent cipher-suite/versions selected") - .with_root_certificates(root_store); + let config = ClientConfig::builder(args.provider().into()).with_root_certificates(root_store); let mut config = match (&args.auth_key, &args.auth_certs) { (Some(key_file), Some(certs_file)) => { let certs = load_certs(certs_file); let key = load_private_key(key_file); config - .with_client_auth_cert(certs, key) + .with_client_auth_cert(Arc::new(Identity::from_cert_chain(certs).unwrap()), key) .expect("invalid client auth certs/key") } - (None, None) => config.with_no_client_auth(), + (None, None) => config.with_no_client_auth().unwrap(), (_, _) => { panic!("must provide --auth-certs and --auth-key together"); } }; - config.key_log = Arc::new(rustls::KeyLogFile::new()); + config.key_log = Arc::new(KeyLogFile::new()); if args.no_tickets { config.resumption = config .resumption - .tls12_resumption(rustls::client::Tls12Resumption::SessionIdOnly); + .tls12_resumption(Tls12Resumption::SessionIdOnly); } if args.no_sni { @@ -461,7 +512,7 @@ fn make_config(args: &Args) -> Arc { config.alpn_protocols = args .proto .iter() - .map(|proto| proto.as_bytes().to_vec()) + .map(|proto| ApplicationProtocol::from(proto.as_bytes()).to_owned()) .collect(); config.max_fragment_size = args.max_frag_size; @@ -469,7 +520,7 @@ fn make_config(args: &Args) -> Arc { config .dangerous() .set_certificate_verifier(Arc::new(danger::NoCertificateVerification::new( - provider::default_provider(), + provider::DEFAULT_PROVIDER, ))); } @@ -526,7 +577,7 @@ fn main() { // Polling can be interrupted (e.g. by a debugger) - retry if so. Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, Err(e) => { - panic!("poll failed: {:?}", e) + panic!("poll failed: {e:?}") } } diff --git a/examples/src/bin/tlsserver-mio.rs b/examples/src/bin/tlsserver-mio.rs index 7dac1af6ff7..be0b56b680b 100644 --- a/examples/src/bin/tlsserver-mio.rs +++ b/examples/src/bin/tlsserver-mio.rs @@ -28,11 +28,14 @@ use std::{fs, net}; use clap::{Parser, Subcommand}; use log::{debug, error}; use mio::net::{TcpListener, TcpStream}; -use rustls::crypto::{aws_lc_rs as provider, CryptoProvider}; +use rustls::crypto::{CryptoProvider, Identity}; +use rustls::enums::{ApplicationProtocol, ProtocolVersion}; use rustls::pki_types::pem::PemObject; use rustls::pki_types::{CertificateDer, CertificateRevocationListDer, PrivateKeyDer}; -use rustls::server::WebPkiClientVerifier; -use rustls::RootCertStore; +use rustls::server::{NoServerSessionStorage, WebPkiClientVerifier}; +use rustls::{Connection, RootCertStore, ServerConfig, ServerConnection}; +use rustls_aws_lc_rs as provider; +use rustls_util::KeyLogFile; // Token for our listening socket. const LISTENER: mio::Token = mio::Token(0); @@ -57,12 +60,12 @@ struct TlsServer { server: TcpListener, connections: HashMap, next_id: usize, - tls_config: Arc, + tls_config: Arc, mode: ServerMode, } impl TlsServer { - fn new(server: TcpListener, mode: ServerMode, cfg: Arc) -> Self { + fn new(server: TcpListener, mode: ServerMode, cfg: Arc) -> Self { Self { server, connections: HashMap::new(), @@ -76,10 +79,9 @@ impl TlsServer { loop { match self.server.accept() { Ok((socket, addr)) => { - debug!("Accepting new connection from {:?}", addr); + debug!("Accepting new connection from {addr:?}"); - let tls_conn = - rustls::ServerConnection::new(Arc::clone(&self.tls_config)).unwrap(); + let tls_conn = ServerConnection::new(self.tls_config.clone()).unwrap(); let mode = self.mode.clone(); let token = mio::Token(self.next_id); @@ -90,12 +92,9 @@ impl TlsServer { self.connections .insert(token, connection); } - Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => return Ok(()), + Err(err) if err.kind() == io::ErrorKind::WouldBlock => return Ok(()), Err(err) => { - println!( - "encountered error while accepting connection; err={:?}", - err - ); + println!("encountered error while accepting connection; err={err:?}"); return Err(err); } } @@ -129,7 +128,7 @@ struct OpenConnection { closing: bool, closed: bool, mode: ServerMode, - tls_conn: rustls::ServerConnection, + tls_conn: ServerConnection, back: Option, sent_http_response: bool, } @@ -166,7 +165,7 @@ impl OpenConnection { socket: TcpStream, token: mio::Token, mode: ServerMode, - tls_conn: rustls::ServerConnection, + tls_conn: ServerConnection, ) -> Self { let back = open_back(&mode); Self { @@ -224,7 +223,7 @@ impl OpenConnection { return; } - error!("read error {:?}", err); + error!("read error {err:?}"); self.closing = true; return; } @@ -238,7 +237,7 @@ impl OpenConnection { // Process newly-received TLS messages. if let Err(err) = self.tls_conn.process_new_packets() { - error!("cannot process packet: {:?}", err); + error!("cannot process packet: {err:?}"); // last gasp write to send any alerts self.do_tls_write_and_handle_error(); @@ -288,7 +287,7 @@ impl OpenConnection { let rc = try_read(back.read(&mut buf)); if rc.is_err() { - error!("backend read failed: {:?}", rc); + error!("backend read failed: {rc:?}"); self.closing = true; return; } @@ -355,7 +354,7 @@ impl OpenConnection { fn do_tls_write_and_handle_error(&mut self) { let rc = self.tls_write(); if rc.is_err() { - error!("write failed {:?}", rc); + error!("write failed {rc:?}"); self.closing = true; } } @@ -366,13 +365,9 @@ impl OpenConnection { .register(&mut self.socket, self.token, event_set) .unwrap(); - if self.back.is_some() { + if let Some(back) = &mut self.back { registry - .register( - self.back.as_mut().unwrap(), - self.token, - mio::Interest::READABLE, - ) + .register(back, self.token, mio::Interest::READABLE) .unwrap(); } } @@ -475,46 +470,91 @@ struct Args { max_early_data: u32, } -fn find_suite(name: &str) -> Option { - for suite in provider::ALL_CIPHER_SUITES { - let sname = format!("{:?}", suite.suite()).to_lowercase(); +impl Args { + fn provider(&self) -> (Vec, CryptoProvider) { + let (versions, provider) = match lookup_versions(&self.protover).as_slice() { + versions @ [ProtocolVersion::TLSv1_2] => { + (versions.to_vec(), provider::DEFAULT_TLS12_PROVIDER) + } + versions @ [ProtocolVersion::TLSv1_3] => { + (versions.to_vec(), provider::DEFAULT_TLS13_PROVIDER) + } + _ => ( + vec![ProtocolVersion::TLSv1_2, ProtocolVersion::TLSv1_3], + provider::DEFAULT_PROVIDER, + ), + }; - if sname == name.to_string().to_lowercase() { - return Some(*suite); - } - } + let provider = match self.suite.as_slice() { + [] => provider, + _ => filter_suites(provider, &self.suite), + }; - None + (versions, provider) + } } -fn lookup_suites(suites: &[String]) -> Vec { - let mut out = Vec::new(); - - for csname in suites { - let scs = find_suite(csname); - match scs { - Some(s) => out.push(s), - None => panic!("cannot look up ciphersuite '{}'", csname), +/// Alter `provider` to reduce the set of ciphersuites to just `suites` +fn filter_suites(mut provider: CryptoProvider, suites: &[String]) -> CryptoProvider { + // first, check `suites` all name known suites, and will have some effect + let known_suites = provider + .tls12_cipher_suites + .iter() + .map(|cs| cs.common.suite) + .chain( + provider + .tls13_cipher_suites + .iter() + .map(|cs| cs.common.suite), + ) + .map(|cs| format!("{:?}", cs).to_lowercase()) + .collect::>(); + + for s in suites { + if !known_suites.contains(&s.to_lowercase()) { + panic!( + "unsupported ciphersuite '{s}'; should be one of {known_suites}", + known_suites = known_suites.join(", ") + ); } } - out + // now discard non-named suites + provider + .tls12_cipher_suites + .to_mut() + .retain(|cs| { + let name = format!("{:?}", cs.common.suite).to_lowercase(); + suites + .iter() + .any(|s| s.to_lowercase() == name) + }); + provider + .tls13_cipher_suites + .to_mut() + .retain(|cs| { + let name = format!("{:?}", cs.common.suite).to_lowercase(); + suites + .iter() + .any(|s| s.to_lowercase() == name) + }); + + provider } /// Make a vector of protocol versions named in `versions` -fn lookup_versions(versions: &[String]) -> Vec<&'static rustls::SupportedProtocolVersion> { +fn lookup_versions(versions: &[String]) -> Vec { let mut out = Vec::new(); for vname in versions { let version = match vname.as_ref() { - "1.2" => &rustls::version::TLS12, - "1.3" => &rustls::version::TLS13, - _ => panic!( - "cannot look up version '{}', valid are '1.2' and '1.3'", - vname - ), + "1.2" => ProtocolVersion::TLSv1_2, + "1.3" => ProtocolVersion::TLSv1_3, + _ => panic!("cannot look up version '{vname}', valid are '1.2' and '1.3'"), }; - out.push(version); + if !out.contains(&version) { + out.push(version); + } } out @@ -554,7 +594,8 @@ fn load_crls( .collect() } -fn make_config(args: &Args) -> Arc { +fn make_config(args: &Args) -> Arc { + let (versions, provider) = args.provider(); let client_auth = if let Some(auth) = &args.auth { let roots = load_certs(auth); let mut client_auth_roots = RootCertStore::empty(); @@ -563,62 +604,55 @@ fn make_config(args: &Args) -> Arc { } let crls = load_crls(args.crl.iter()); if args.require_auth { - WebPkiClientVerifier::builder(client_auth_roots.into()) - .with_crls(crls) - .build() - .unwrap() + Arc::new( + WebPkiClientVerifier::builder(client_auth_roots.into(), &provider) + .with_crls(crls) + .build() + .unwrap(), + ) } else { - WebPkiClientVerifier::builder(client_auth_roots.into()) - .with_crls(crls) - .allow_unauthenticated() - .build() - .unwrap() + Arc::new( + WebPkiClientVerifier::builder(client_auth_roots.into(), &provider) + .with_crls(crls) + .allow_unauthenticated() + .build() + .unwrap(), + ) } } else { WebPkiClientVerifier::no_client_auth() }; - let suites = if !args.suite.is_empty() { - lookup_suites(&args.suite) - } else { - provider::ALL_CIPHER_SUITES.to_vec() - }; - - let versions = if !args.protover.is_empty() { - lookup_versions(&args.protover) - } else { - rustls::ALL_VERSIONS.to_vec() - }; - let certs = load_certs(&args.certs); let privkey = load_private_key(&args.key); let ocsp = load_ocsp(args.ocsp.as_deref()); - let mut config = rustls::ServerConfig::builder_with_provider( - CryptoProvider { - cipher_suites: suites, - ..provider::default_provider() - } - .into(), - ) - .with_protocol_versions(&versions) - .expect("inconsistent cipher-suites/versions specified") - .with_client_cert_verifier(client_auth) - .with_single_cert_with_ocsp(certs, privkey, ocsp) - .expect("bad certificates/private key"); + let mut config = ServerConfig::builder(provider.into()) + .with_client_cert_verifier(client_auth) + .with_single_cert_with_ocsp( + Arc::new(Identity::from_cert_chain(certs).unwrap()), + privkey, + Arc::from(ocsp), + ) + .expect("bad certificates/private key"); - config.key_log = Arc::new(rustls::KeyLogFile::new()); + config.key_log = Arc::new(KeyLogFile::new()); if args.no_resumption { - config.session_storage = Arc::new(rustls::server::NoServerSessionStorage {}); + config.session_storage = Arc::new(NoServerSessionStorage {}); } if args.tickets { - config.ticketer = provider::Ticketer::new().unwrap(); + config.ticketer = Some( + provider::DEFAULT_PROVIDER + .ticketer_factory + .ticketer() + .unwrap(), + ); } if args.max_early_data > 0 { - if !versions.contains(&&rustls::version::TLS13) { + if !versions.contains(&ProtocolVersion::TLSv1_3) { panic!("Early data is only available for servers supporting TLS1.3"); } if args.no_resumption { @@ -630,7 +664,11 @@ fn make_config(args: &Args) -> Arc { config.max_early_data_size = args.max_early_data; } - config.alpn_protocols = args.proto.clone(); + config.alpn_protocols = args + .proto + .iter() + .map(|bytes| ApplicationProtocol::from(bytes.as_slice()).to_owned()) + .collect(); Arc::new(config) } @@ -669,7 +707,7 @@ fn main() { // Polling can be interrupted (e.g. by a debugger) - retry if so. Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, Err(e) => { - panic!("poll failed: {:?}", e) + panic!("poll failed: {e:?}") } } diff --git a/examples/src/bin/unbuffered-async-client.rs b/examples/src/bin/unbuffered-async-client.rs deleted file mode 100644 index 5cfb1390d9a..00000000000 --- a/examples/src/bin/unbuffered-async-client.rs +++ /dev/null @@ -1,269 +0,0 @@ -//! This is a simple client using rustls' unbuffered API. Meaning that the application layer must -//! handle the buffers required to receive, process and send TLS data. Additionally it demonstrates -//! using asynchronous I/O using either async-std or tokio. - -use std::error::Error; -use std::sync::Arc; - -#[cfg(feature = "async-std")] -use async_std::io::{ReadExt, WriteExt}; -#[cfg(feature = "async-std")] -use async_std::net::TcpStream; -use rustls::client::{ClientConnectionData, UnbufferedClientConnection}; -use rustls::unbuffered::{ - AppDataRecord, ConnectionState, EncodeError, EncryptError, InsufficientSizeError, - UnbufferedStatus, WriteTraffic, -}; -use rustls::version::TLS13; -use rustls::{ClientConfig, RootCertStore}; -#[cfg(not(feature = "async-std"))] -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -#[cfg(not(feature = "async-std"))] -use tokio::net::TcpStream; - -#[cfg_attr(not(feature = "async-std"), tokio::main(flavor = "current_thread"))] -#[cfg_attr(feature = "async-std", async_std::main)] -async fn main() -> Result<(), Box> { - let root_store = RootCertStore { - roots: webpki_roots::TLS_SERVER_ROOTS.into(), - }; - - let config = ClientConfig::builder_with_protocol_versions(&[&TLS13]) - .with_root_certificates(root_store) - .with_no_client_auth(); - - let config = Arc::new(config); - - let mut incoming_tls = vec![0; INCOMING_TLS_BUFSIZE]; - let mut outgoing_tls = vec![0; OUTGOING_TLS_INITIAL_BUFSIZE]; - - converse(&config, &mut incoming_tls, &mut outgoing_tls).await?; - - Ok(()) -} - -async fn converse( - config: &Arc, - incoming_tls: &mut [u8], - outgoing_tls: &mut Vec, -) -> Result<(), Box> { - let mut conn = UnbufferedClientConnection::new(Arc::clone(config), SERVER_NAME.try_into()?)?; - let mut sock = TcpStream::connect(format!("{SERVER_NAME}:{PORT}")).await?; - - let mut incoming_used = 0; - let mut outgoing_used = 0; - - let mut we_closed = false; - let mut peer_closed = false; - let mut sent_request = false; - let mut received_response = false; - - let mut iter_count = 0; - while !(peer_closed || (we_closed && incoming_used == 0)) { - let UnbufferedStatus { mut discard, state } = - conn.process_tls_records(&mut incoming_tls[..incoming_used]); - - match dbg!(state.unwrap()) { - ConnectionState::ReadTraffic(mut state) => { - while let Some(res) = state.next_record() { - let AppDataRecord { - discard: new_discard, - payload, - } = res?; - discard += new_discard; - - if payload.starts_with(b"HTTP") { - let response = core::str::from_utf8(payload)?; - let header = response - .lines() - .next() - .unwrap_or(response); - - println!("{header}"); - } else { - println!("(.. continued HTTP response ..)"); - } - - received_response = true; - } - } - - ConnectionState::EncodeTlsData(mut state) => { - try_or_resize_and_retry( - |out_buffer| state.encode(out_buffer), - |e| { - if let EncodeError::InsufficientSize(is) = &e { - Ok(*is) - } else { - Err(e.into()) - } - }, - outgoing_tls, - &mut outgoing_used, - )?; - } - - ConnectionState::TransmitTlsData(mut state) => { - if let Some(mut may_encrypt) = state.may_encrypt_app_data() { - encrypt_http_request( - &mut sent_request, - &mut may_encrypt, - outgoing_tls, - &mut outgoing_used, - ); - } - - send_tls(&mut sock, outgoing_tls, &mut outgoing_used).await?; - state.done(); - } - - ConnectionState::BlockedHandshake { .. } => { - recv_tls(&mut sock, incoming_tls, &mut incoming_used).await?; - } - - ConnectionState::WriteTraffic(mut may_encrypt) => { - if encrypt_http_request( - &mut sent_request, - &mut may_encrypt, - outgoing_tls, - &mut outgoing_used, - ) { - send_tls(&mut sock, outgoing_tls, &mut outgoing_used).await?; - recv_tls(&mut sock, incoming_tls, &mut incoming_used).await?; - } else if !received_response { - // this happens in the TLS 1.3 case. the app-data was sent in the preceding - // `TransmitTlsData` state. the server should have already written a - // response which we can read out from the socket - recv_tls(&mut sock, incoming_tls, &mut incoming_used).await?; - } else if !we_closed { - try_or_resize_and_retry( - |out_buffer| may_encrypt.queue_close_notify(out_buffer), - |e| { - if let EncryptError::InsufficientSize(is) = &e { - Ok(*is) - } else { - Err(e.into()) - } - }, - outgoing_tls, - &mut outgoing_used, - )?; - send_tls(&mut sock, outgoing_tls, &mut outgoing_used).await?; - we_closed = true; - } else { - recv_tls(&mut sock, incoming_tls, &mut incoming_used).await?; - } - } - - ConnectionState::Closed => { - peer_closed = true; - } - - // other states are not expected in this example - _ => unreachable!(), - } - - if discard != 0 { - assert!(discard <= incoming_used); - - incoming_tls.copy_within(discard..incoming_used, 0); - incoming_used -= discard; - - eprintln!("discarded {discard}B from `incoming_tls`"); - } - - iter_count += 1; - assert!( - iter_count < MAX_ITERATIONS, - "did not get a HTTP response within {MAX_ITERATIONS} iterations" - ); - } - - assert!(sent_request); - assert!(received_response); - assert_eq!(0, incoming_used); - assert_eq!(0, outgoing_used); - - Ok(()) -} - -fn try_or_resize_and_retry( - mut f: impl FnMut(&mut [u8]) -> Result, - map_err: impl FnOnce(E) -> Result>, - outgoing_tls: &mut Vec, - outgoing_used: &mut usize, -) -> Result> -where - E: Error + 'static, -{ - let written = match f(&mut outgoing_tls[*outgoing_used..]) { - Ok(written) => written, - - Err(e) => { - let InsufficientSizeError { required_size } = map_err(e)?; - let new_len = *outgoing_used + required_size; - outgoing_tls.resize(new_len, 0); - eprintln!("resized `outgoing_tls` buffer to {new_len}B"); - - f(&mut outgoing_tls[*outgoing_used..])? - } - }; - - *outgoing_used += written; - - Ok(written) -} - -async fn recv_tls( - sock: &mut TcpStream, - incoming_tls: &mut [u8], - incoming_used: &mut usize, -) -> Result<(), Box> { - let read = sock - .read(&mut incoming_tls[*incoming_used..]) - .await?; - eprintln!("received {read}B of data"); - *incoming_used += read; - Ok(()) -} - -async fn send_tls( - sock: &mut TcpStream, - outgoing_tls: &[u8], - outgoing_used: &mut usize, -) -> Result<(), Box> { - sock.write_all(&outgoing_tls[..*outgoing_used]) - .await?; - eprintln!("sent {outgoing_used}B of data"); - *outgoing_used = 0; - Ok(()) -} - -fn encrypt_http_request( - sent_request: &mut bool, - may_encrypt: &mut WriteTraffic<'_, ClientConnectionData>, - outgoing_tls: &mut [u8], - outgoing_used: &mut usize, -) -> bool { - if !*sent_request { - let request = format!("GET / HTTP/1.1\r\nHost: {SERVER_NAME}\r\nConnection: close\r\nAccept-Encoding: identity\r\n\r\n").into_bytes(); - let written = may_encrypt - .encrypt(&request, &mut outgoing_tls[*outgoing_used..]) - .expect("encrypted request does not fit in `outgoing_tls`"); - *outgoing_used += written; - *sent_request = true; - eprintln!("queued HTTP request"); - true - } else { - false - } -} - -const SERVER_NAME: &str = "example.com"; -const PORT: u16 = 443; - -const KB: usize = 1024; -const INCOMING_TLS_BUFSIZE: usize = 16 * KB; -const OUTGOING_TLS_INITIAL_BUFSIZE: usize = KB; - -const MAX_ITERATIONS: usize = 20; diff --git a/examples/src/bin/unbuffered-client.rs b/examples/src/bin/unbuffered-client.rs deleted file mode 100644 index ec0f4c1b193..00000000000 --- a/examples/src/bin/unbuffered-client.rs +++ /dev/null @@ -1,282 +0,0 @@ -//! This is a simple client using rustls' unbuffered API. Meaning that the application layer must -//! handle the buffers required to receive, process and send TLS data. - -use std::error::Error; -use std::io::{Read, Write}; -use std::net::TcpStream; -use std::sync::Arc; - -use rustls::client::{ClientConnectionData, EarlyDataError, UnbufferedClientConnection}; -use rustls::unbuffered::{ - AppDataRecord, ConnectionState, EncodeError, EncryptError, InsufficientSizeError, - UnbufferedStatus, WriteTraffic, -}; -use rustls::version::TLS13; -use rustls::{ClientConfig, RootCertStore}; - -fn main() -> Result<(), Box> { - let root_store = RootCertStore { - roots: webpki_roots::TLS_SERVER_ROOTS.into(), - }; - - let mut config = ClientConfig::builder_with_protocol_versions(&[&TLS13]) - .with_root_certificates(root_store) - .with_no_client_auth(); - config.enable_early_data = SEND_EARLY_DATA; - - let config = Arc::new(config); - - let mut incoming_tls = vec![0; INCOMING_TLS_BUFSIZE]; - let mut outgoing_tls = vec![0; OUTGOING_TLS_INITIAL_BUFSIZE]; - - converse(&config, false, &mut incoming_tls, &mut outgoing_tls)?; - if SEND_EARLY_DATA { - eprintln!("---- second connection ----"); - converse(&config, true, &mut incoming_tls, &mut outgoing_tls)?; - } - - Ok(()) -} - -fn converse( - config: &Arc, - send_early_data: bool, - incoming_tls: &mut [u8], - outgoing_tls: &mut Vec, -) -> Result<(), Box> { - let mut conn = UnbufferedClientConnection::new(Arc::clone(config), SERVER_NAME.try_into()?)?; - let mut sock = TcpStream::connect(format!("{SERVER_NAME}:{PORT}"))?; - - let mut incoming_used = 0; - let mut outgoing_used = 0; - - let mut we_closed = false; - let mut peer_closed = false; - let mut sent_request = false; - let mut received_response = false; - let mut sent_early_data = false; - - let mut iter_count = 0; - while !(peer_closed || (we_closed && incoming_used == 0)) { - let UnbufferedStatus { mut discard, state } = - conn.process_tls_records(&mut incoming_tls[..incoming_used]); - - match dbg!(state.unwrap()) { - ConnectionState::ReadTraffic(mut state) => { - while let Some(res) = state.next_record() { - let AppDataRecord { - discard: new_discard, - payload, - } = res?; - discard += new_discard; - - if payload.starts_with(b"HTTP") { - let response = core::str::from_utf8(payload)?; - let header = response - .lines() - .next() - .unwrap_or(response); - - println!("{header}"); - } else { - println!("(.. continued HTTP response ..)"); - } - - received_response = true; - } - } - - ConnectionState::EncodeTlsData(mut state) => { - try_or_resize_and_retry( - |out_buffer| state.encode(out_buffer), - |e| { - if let EncodeError::InsufficientSize(is) = &e { - Ok(*is) - } else { - Err(e.into()) - } - }, - outgoing_tls, - &mut outgoing_used, - )?; - } - - ConnectionState::TransmitTlsData(mut state) => { - if let Some(mut may_encrypt_early_data) = state.may_encrypt_early_data() { - let written = try_or_resize_and_retry( - |out_buffer| may_encrypt_early_data.encrypt(EARLY_DATA, out_buffer), - |e| match &e { - EarlyDataError::Encrypt(EncryptError::InsufficientSize(is)) => Ok(*is), - _ => Err(e.into()), - }, - outgoing_tls, - &mut outgoing_used, - )?; - - eprintln!("queued {written}B of early data"); - sent_early_data = true; - } - - if let Some(mut may_encrypt) = state.may_encrypt_app_data() { - encrypt_http_request( - &mut sent_request, - &mut may_encrypt, - outgoing_tls, - &mut outgoing_used, - ); - } - - send_tls(&mut sock, outgoing_tls, &mut outgoing_used)?; - state.done(); - } - - ConnectionState::BlockedHandshake { .. } => { - recv_tls(&mut sock, incoming_tls, &mut incoming_used)?; - } - - ConnectionState::WriteTraffic(mut may_encrypt) => { - if encrypt_http_request( - &mut sent_request, - &mut may_encrypt, - outgoing_tls, - &mut outgoing_used, - ) { - send_tls(&mut sock, outgoing_tls, &mut outgoing_used)?; - recv_tls(&mut sock, incoming_tls, &mut incoming_used)?; - } else if !received_response { - // this happens in the TLS 1.3 case. the app-data was sent in the preceding - // `TransmitTlsData` state. the server should have already written a - // response which we can read out from the socket - recv_tls(&mut sock, incoming_tls, &mut incoming_used)?; - } else if !we_closed { - try_or_resize_and_retry( - |out_buffer| may_encrypt.queue_close_notify(out_buffer), - |e| { - if let EncryptError::InsufficientSize(is) = &e { - Ok(*is) - } else { - Err(e.into()) - } - }, - outgoing_tls, - &mut outgoing_used, - )?; - send_tls(&mut sock, outgoing_tls, &mut outgoing_used)?; - we_closed = true; - } else { - recv_tls(&mut sock, incoming_tls, &mut incoming_used)?; - } - } - - ConnectionState::Closed => { - peer_closed = true; - } - - // other states are not expected in this example - _ => unreachable!(), - } - - if discard != 0 { - assert!(discard <= incoming_used); - - incoming_tls.copy_within(discard..incoming_used, 0); - incoming_used -= discard; - - eprintln!("discarded {discard}B from `incoming_tls`"); - } - - iter_count += 1; - assert!( - iter_count < MAX_ITERATIONS, - "did not get a HTTP response within {MAX_ITERATIONS} iterations" - ); - } - - assert!(sent_request); - assert!(received_response); - assert_eq!(send_early_data, sent_early_data); - assert_eq!(0, incoming_used); - assert_eq!(0, outgoing_used); - - Ok(()) -} - -fn try_or_resize_and_retry( - mut f: impl FnMut(&mut [u8]) -> Result, - map_err: impl FnOnce(E) -> Result>, - outgoing_tls: &mut Vec, - outgoing_used: &mut usize, -) -> Result> -where - E: Error + 'static, -{ - let written = match f(&mut outgoing_tls[*outgoing_used..]) { - Ok(written) => written, - - Err(e) => { - let InsufficientSizeError { required_size } = map_err(e)?; - let new_len = *outgoing_used + required_size; - outgoing_tls.resize(new_len, 0); - eprintln!("resized `outgoing_tls` buffer to {new_len}B"); - - f(&mut outgoing_tls[*outgoing_used..])? - } - }; - - *outgoing_used += written; - - Ok(written) -} - -fn recv_tls( - sock: &mut TcpStream, - incoming_tls: &mut [u8], - incoming_used: &mut usize, -) -> Result<(), Box> { - let read = sock.read(&mut incoming_tls[*incoming_used..])?; - eprintln!("received {read}B of data"); - *incoming_used += read; - Ok(()) -} - -fn send_tls( - sock: &mut TcpStream, - outgoing_tls: &[u8], - outgoing_used: &mut usize, -) -> Result<(), Box> { - sock.write_all(&outgoing_tls[..*outgoing_used])?; - eprintln!("sent {outgoing_used}B of data"); - *outgoing_used = 0; - Ok(()) -} - -fn encrypt_http_request( - sent_request: &mut bool, - may_encrypt: &mut WriteTraffic<'_, ClientConnectionData>, - outgoing_tls: &mut [u8], - outgoing_used: &mut usize, -) -> bool { - if !*sent_request { - let request = format!("GET / HTTP/1.1\r\nHost: {SERVER_NAME}\r\nConnection: close\r\nAccept-Encoding: identity\r\n\r\n").into_bytes(); - let written = may_encrypt - .encrypt(&request, &mut outgoing_tls[*outgoing_used..]) - .expect("encrypted request does not fit in `outgoing_tls`"); - *outgoing_used += written; - *sent_request = true; - eprintln!("queued HTTP request"); - true - } else { - false - } -} - -const SERVER_NAME: &str = "example.com"; -const PORT: u16 = 443; - -const KB: usize = 1024; -const INCOMING_TLS_BUFSIZE: usize = 16 * KB; -const OUTGOING_TLS_INITIAL_BUFSIZE: usize = KB; - -const MAX_ITERATIONS: usize = 20; -const SEND_EARLY_DATA: bool = false; -const EARLY_DATA: &[u8] = b"hello"; diff --git a/examples/src/bin/unbuffered-server.rs b/examples/src/bin/unbuffered-server.rs deleted file mode 100644 index 80e567b5bc9..00000000000 --- a/examples/src/bin/unbuffered-server.rs +++ /dev/null @@ -1,267 +0,0 @@ -//! This is a simple server using rustls' unbuffered API. Meaning that the application layer must -//! handle the buffers required to receive, process and send TLS data. - -use std::env; -use std::error::Error; -use std::io::{self, Read, Write}; -use std::net::{TcpListener, TcpStream}; -use std::path::Path; -use std::sync::Arc; - -use rustls::pki_types::pem::PemObject; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; -use rustls::server::UnbufferedServerConnection; -use rustls::unbuffered::{ - AppDataRecord, ConnectionState, EncodeError, EncryptError, InsufficientSizeError, - UnbufferedStatus, -}; -use rustls::ServerConfig; - -fn main() -> Result<(), Box> { - let mut args = env::args(); - args.next(); - let cert_file = args - .next() - .expect("missing certificate file argument"); - let private_key_file = args - .next() - .expect("missing private key file argument"); - - let mut config = ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(load_certs(cert_file)?, load_private_key(private_key_file)?)?; - - if let Some(max_early_data_size) = MAX_EARLY_DATA_SIZE { - config.max_early_data_size = max_early_data_size; - } - - config.max_fragment_size = MAX_FRAGMENT_SIZE; - - let config = Arc::new(config); - - let listener = TcpListener::bind(format!("[::]:{PORT}"))?; - - let mut incoming_tls = [0; INCOMING_TLS_BUFSIZE]; - let mut outgoing_tls = vec![0; OUTGOING_TLS_INITIAL_BUFSIZE]; - for stream in listener.incoming() { - handle(stream?, &config, &mut incoming_tls, &mut outgoing_tls)?; - } - - Ok(()) -} - -fn handle( - mut sock: TcpStream, - config: &Arc, - incoming_tls: &mut [u8], - outgoing_tls: &mut Vec, -) -> Result<(), Box> { - eprintln!("\n---- new client ----"); - - dbg!(sock.peer_addr()?); - - let mut conn = UnbufferedServerConnection::new(config.clone())?; - - let mut incoming_used = 0; - let mut outgoing_used = 0; - - let mut open_connection = true; - let mut received_request = false; - let mut sent_response = false; - - let mut iter_count = 0; - while open_connection { - let UnbufferedStatus { mut discard, state } = - conn.process_tls_records(&mut incoming_tls[..incoming_used]); - - match dbg!(state.unwrap()) { - ConnectionState::ReadTraffic(mut state) => { - while let Some(res) = state.next_record() { - let AppDataRecord { - discard: new_discard, - payload, - } = res?; - discard += new_discard; - - if payload.starts_with(b"GET") { - let response = core::str::from_utf8(payload)?; - let header = response - .lines() - .next() - .unwrap_or(response); - - println!("{header}"); - } else { - println!("(.. continued HTTP request ..)"); - } - - received_request = true; - } - } - - ConnectionState::ReadEarlyData(mut state) => { - while let Some(res) = state.next_record() { - let AppDataRecord { - discard: new_discard, - payload, - } = res?; - discard += new_discard; - - println!("early data: {:?}", core::str::from_utf8(payload)); - - received_request = true; - } - } - - ConnectionState::EncodeTlsData(mut state) => { - try_or_resize_and_retry( - |out_buffer| state.encode(out_buffer), - |e| { - if let EncodeError::InsufficientSize(is) = &e { - Ok(*is) - } else { - Err(e.into()) - } - }, - outgoing_tls, - &mut outgoing_used, - )?; - } - - ConnectionState::TransmitTlsData(state) => { - send_tls(&mut sock, outgoing_tls, &mut outgoing_used)?; - state.done(); - } - - ConnectionState::BlockedHandshake { .. } => { - recv_tls(&mut sock, incoming_tls, &mut incoming_used)?; - } - - ConnectionState::WriteTraffic(mut state) => { - if !received_request { - recv_tls(&mut sock, incoming_tls, &mut incoming_used)?; - } else { - let map_err = |e| { - if let EncryptError::InsufficientSize(is) = &e { - Ok(*is) - } else { - Err(e.into()) - } - }; - - let http_response = b"HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nHello world from rustls unbuffered server\r\n"; - try_or_resize_and_retry( - |out_buffer| state.encrypt(http_response, out_buffer), - map_err, - outgoing_tls, - &mut outgoing_used, - )?; - sent_response = true; - - try_or_resize_and_retry( - |out_buffer| state.queue_close_notify(out_buffer), - map_err, - outgoing_tls, - &mut outgoing_used, - )?; - open_connection = false; - - send_tls(&mut sock, outgoing_tls, &mut outgoing_used)?; - } - } - - _ => unreachable!(), - } - - if discard != 0 { - assert!(discard <= incoming_used); - - incoming_tls.copy_within(discard..incoming_used, 0); - incoming_used -= discard; - - eprintln!("discarded {discard}B from `incoming_tls`"); - } - - iter_count += 1; - assert!( - iter_count < MAX_ITERATIONS, - "did not get a HTTP response within {MAX_ITERATIONS} iterations" - ); - } - - assert!(received_request); - assert!(sent_response); - assert_eq!(0, incoming_used); - assert_eq!(0, outgoing_used); - - Ok(()) -} - -fn try_or_resize_and_retry( - mut f: impl FnMut(&mut [u8]) -> Result, - map_err: impl FnOnce(E) -> Result>, - outgoing_tls: &mut Vec, - outgoing_used: &mut usize, -) -> Result> -where - E: Error + 'static, -{ - let written = match f(&mut outgoing_tls[*outgoing_used..]) { - Ok(written) => written, - - Err(e) => { - let InsufficientSizeError { required_size } = map_err(e)?; - let new_len = *outgoing_used + required_size; - outgoing_tls.resize(new_len, 0); - eprintln!("resized `outgoing_tls` buffer to {new_len}B"); - - f(&mut outgoing_tls[*outgoing_used..])? - } - }; - - *outgoing_used += written; - - Ok(written) -} - -fn recv_tls( - sock: &mut TcpStream, - incoming_tls: &mut [u8], - incoming_used: &mut usize, -) -> Result<(), Box> { - let read = sock.read(&mut incoming_tls[*incoming_used..])?; - eprintln!("received {read}B of data"); - *incoming_used += read; - Ok(()) -} - -fn send_tls( - sock: &mut TcpStream, - outgoing_tls: &[u8], - outgoing_used: &mut usize, -) -> Result<(), Box> { - sock.write_all(&outgoing_tls[..*outgoing_used])?; - eprintln!("sent {outgoing_used}B of data"); - *outgoing_used = 0; - Ok(()) -} - -fn load_certs(path: impl AsRef) -> Result>, io::Error> { - Ok(CertificateDer::pem_file_iter(path) - .expect("cannot open certificate file") - .map(|cert| cert.unwrap()) - .collect()) -} - -fn load_private_key(path: impl AsRef) -> Result, io::Error> { - Ok(PrivateKeyDer::from_pem_file(path).expect("cannot open private key file")) -} - -const KB: usize = 1024; -const INCOMING_TLS_BUFSIZE: usize = 16 * KB; -const OUTGOING_TLS_INITIAL_BUFSIZE: usize = 0; -const MAX_EARLY_DATA_SIZE: Option = Some(128); -const MAX_FRAGMENT_SIZE: Option = None; - -const PORT: u16 = 1443; -const MAX_ITERATIONS: usize = 30; diff --git a/examples/tests/limitedclient.rs b/examples/tests/limitedclient.rs new file mode 100644 index 00000000000..911431516eb --- /dev/null +++ b/examples/tests/limitedclient.rs @@ -0,0 +1,75 @@ +//! Check that `limitedclient` is successful in its goal of not linking in any +//! AES code. +//! +//! We also check `simpleclient` (which includes everything) as a baseline to +//! detect errors in the test code. + +// Don't assume binutils are available everywhere, or that `nm` has a +// portable interface. +#![cfg(target_os = "linux")] + +use std::process::Command; + +#[test] +fn limited_no_aes_symbols() { + let aws_aes = + |sym: &str| sym.starts_with("aws_lc_") && sym.ends_with("_EVP_aead_aes_128_gcm_tls13"); + let expected = find_symbols_in_executable(aws_aes, env!("CARGO_BIN_EXE_simpleclient")); + assert!(!expected.is_empty()); + + let limited = env!("CARGO_BIN_EXE_limitedclient"); + let mut unexpected = find_symbols_in_executable(aws_aes, limited); + unexpected.retain(|sym| !sym.starts_with("aws_lc_fips_")); + assert!( + unexpected.is_empty(), + "found unexpected symbols in {limited}: {unexpected:#?}", + ); +} + +#[ignore] // XXX: pending runtime binding of state machine states +#[test] +fn limited_no_tls12_symbols() { + let expected = find_symbols_in_executable(tls12, env!("CARGO_BIN_EXE_simpleclient")); + assert!(!expected.is_empty()); + + let limited = env!("CARGO_BIN_EXE_limitedclient"); + let unexpected = find_symbols_in_executable(tls12, limited); + assert!( + unexpected.is_empty(), + "found unexpected symbols in {limited}: {unexpected:#?}", + ); +} + +fn tls12(sym: &str) -> bool { + sym.contains("rustls::client::tls12") + && !sym.contains("core::fmt::Debug") + // Exclude some trivial `State` default method implementations that + // appear to sometimes get inlined even if no TLS 1.2 code is used. + && !sym.ends_with("::send_key_update_request") + && !sym.ends_with("::handle_decrypt_error") + && !sym.ends_with("::into_external_state") + && !sym.ends_with("::set_resumption_data") +} + +fn find_symbols_in_executable(f: impl Fn(&str) -> bool, exe: &str) -> Vec { + let nm_output = dbg!( + Command::new("nm") + .arg("--defined-only") + .arg("--demangle") + .arg("--format=just-symbols") + .arg(exe) + ) + .output() + .expect("nm failed"); + + let mut matching = Vec::new(); + let symbols = String::from_utf8(nm_output.stdout).expect("nm output not valid utf8"); + for sym in symbols.lines() { + //println!("candidate symbol {sym:?}"); + if f(sym) { + matching.push(sym.trim().to_owned()); + } + } + + matching +} diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index dc437fcf8b8..bd076ed2c32 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -4,18 +4,18 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -28,70 +28,74 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "once_cell_polyfill", + "windows-sys", ] [[package]] name = "arbitrary" -version = "0.2.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cf76cb6e2222ed0ea86b2b0ee2f71c96ec6edd5af42e84d59160e91b836ec4" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "cc" -version = "1.2.7" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -99,50 +103,86 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "wasi", + "r-efi", + "wasip2", ] [[package]] -name = "humantime" -version = "2.1.0" +name = "is_terminal_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] -name = "is_terminal_polyfill" -version = "1.70.1" +name = "jiff" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] [[package]] name = "libc" -version = "0.2.169" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libfuzzer-sys" -version = "0.1.0" -source = "git+https://github.com/rust-fuzz/libfuzzer-sys.git#35ce7d7177c254b9c798aec171dfe76877d1a20f" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" dependencies = [ "arbitrary", "cc", @@ -150,27 +190,72 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "regex" +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -180,9 +265,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -191,28 +276,13 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rustls" -version = "0.23.21" +version = "0.24.0-dev.0" dependencies = [ "log", "once_cell", @@ -237,37 +307,52 @@ name = "rustls-fuzzing-provider" version = "0.1.0" dependencies = [ "rustls", - "rustls-webpki", ] [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.104.0-alpha.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "bea702cca24d344fc70973022bf7eb920c224e318466eb49784272337dd24b1a" dependencies = [ - "ring", "rustls-pki-types", "untrusted", ] [[package]] -name = "shlex" -version = "1.3.0" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "spin" -version = "0.9.8" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "subtle" @@ -275,6 +360,23 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + [[package]] name = "untrusted" version = "0.9.0" @@ -288,95 +390,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "windows-sys" -version = "0.52.0" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "windows-targets", + "wit-bindgen", ] [[package]] -name = "windows-sys" -version = "0.59.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-targets" -version = "0.52.6" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 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", + "windows-link", ] [[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" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 89e0b338698..dde2640c3f0 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,8 +10,8 @@ cargo-fuzz = true [dependencies] env_logger = "0.11" -libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" } -rustls = { path = "../rustls", default-features = false, features = ["std", "tls12", "custom-provider"]} +libfuzzer-sys = "0.4" +rustls = { path = "../rustls", default-features = false } rustls-fuzzing-provider = { path = "../rustls-fuzzing-provider" } # Prevent this from interfering with workspaces @@ -45,7 +45,3 @@ path = "fuzzers/server.rs" [[bin]] name = "server_name" path = "fuzzers/server_name.rs" - -[[bin]] -name = "unbuffered" -path = "fuzzers/unbuffered.rs" diff --git a/fuzz/fuzzers/client.rs b/fuzz/fuzzers/client.rs index fb1e935569f..439768b0190 100644 --- a/fuzz/fuzzers/client.rs +++ b/fuzz/fuzzers/client.rs @@ -6,20 +6,22 @@ extern crate rustls; use std::io; use std::sync::Arc; -use rustls::{ClientConfig, ClientConnection}; +use rustls::{ClientConfig, Connection}; fuzz_target!(|data: &[u8]| { let _ = env_logger::try_init(); let config = Arc::new( - ClientConfig::builder_with_provider(rustls_fuzzing_provider::provider().into()) - .with_safe_default_protocol_versions() - .unwrap() + ClientConfig::builder(rustls_fuzzing_provider::PROVIDER.into()) .dangerous() .with_custom_certificate_verifier(rustls_fuzzing_provider::server_verifier()) - .with_no_client_auth(), + .with_no_client_auth() + .unwrap(), ); let hostname = "localhost".try_into().unwrap(); - let mut client = ClientConnection::new(config, hostname).unwrap(); + let mut client = config + .connect(hostname) + .build() + .unwrap(); let mut stream = io::Cursor::new(data); loop { diff --git a/fuzz/fuzzers/deframer.rs b/fuzz/fuzzers/deframer.rs index 2bc230caee3..f4c9ea3ea5b 100644 --- a/fuzz/fuzzers/deframer.rs +++ b/fuzz/fuzzers/deframer.rs @@ -5,4 +5,4 @@ extern crate rustls; use rustls::internal::fuzzing::fuzz_deframer; -fuzz_target!(|bytes: &[u8]| { fuzz_deframer(bytes) }); +fuzz_target!(|bytes: &[u8]| fuzz_deframer(bytes)); diff --git a/fuzz/fuzzers/fragment.rs b/fuzz/fuzzers/fragment.rs index c08c90f46fc..096053177f6 100644 --- a/fuzz/fuzzers/fragment.rs +++ b/fuzz/fuzzers/fragment.rs @@ -3,30 +3,6 @@ extern crate libfuzzer_sys; extern crate rustls; -use rustls::internal::msgs::base::Payload; -use rustls::internal::msgs::codec::Reader; -use rustls::internal::msgs::fragmenter::MessageFragmenter; -use rustls::internal::msgs::message::{Message, OutboundOpaqueMessage, PlainMessage}; +use rustls::internal::fuzzing::fuzz_fragmenter; -fuzz_target!(|data: &[u8]| { - let mut rdr = Reader::init(data); - let Ok(msg) = OutboundOpaqueMessage::read(&mut rdr) else { - return; - }; - - let Ok(msg) = Message::try_from(msg.into_plain_message()) else { - return; - }; - - let mut frg = MessageFragmenter::default(); - frg.set_max_fragment_size(Some(32)) - .unwrap(); - for msg in frg.fragment_message(&PlainMessage::from(msg)) { - Message::try_from(PlainMessage { - typ: msg.typ, - version: msg.version, - payload: Payload::Owned(msg.payload.to_vec()), - }) - .ok(); - } -}); +fuzz_target!(|data: &[u8]| fuzz_fragmenter(data)); diff --git a/fuzz/fuzzers/message.rs b/fuzz/fuzzers/message.rs index 8ba535503c3..e16795a7621 100644 --- a/fuzz/fuzzers/message.rs +++ b/fuzz/fuzzers/message.rs @@ -3,20 +3,6 @@ extern crate libfuzzer_sys; extern crate rustls; -use rustls::internal::msgs::codec::Reader; -use rustls::internal::msgs::message::{Message, OutboundOpaqueMessage, PlainMessage}; +use rustls::internal::fuzzing::fuzz_message; -fuzz_target!(|data: &[u8]| { - let mut rdr = Reader::init(data); - if let Ok(m) = OutboundOpaqueMessage::read(&mut rdr) { - let Ok(msg) = Message::try_from(m.into_plain_message()) else { - return; - }; - //println!("msg = {:#?}", m); - let enc = PlainMessage::from(msg) - .into_unencrypted_opaque() - .encode(); - //println!("data = {:?}", &data[..rdr.used()]); - assert_eq!(enc, data[..rdr.used()]); - } -}); +fuzz_target!(|data: &[u8]| fuzz_message(data)); diff --git a/fuzz/fuzzers/persist.rs b/fuzz/fuzzers/persist.rs index 6d17727f9b6..cd5c7e17ac9 100644 --- a/fuzz/fuzzers/persist.rs +++ b/fuzz/fuzzers/persist.rs @@ -3,18 +3,6 @@ extern crate libfuzzer_sys; extern crate rustls; -use rustls::internal::msgs::codec::{Codec, Reader}; -use rustls::internal::msgs::persist; +use rustls::internal::fuzzing::fuzz_server_session_value; -fn try_type(data: &[u8]) -where - T: for<'a> Codec<'a>, -{ - let mut rdr = Reader::init(data); - - let _ = T::read(&mut rdr); -} - -fuzz_target!(|data: &[u8]| { - try_type::(data); -}); +fuzz_target!(|data: &[u8]| fuzz_server_session_value(data)); diff --git a/fuzz/fuzzers/server.rs b/fuzz/fuzzers/server.rs index 35184d876e4..7d38a4d5d8e 100644 --- a/fuzz/fuzzers/server.rs +++ b/fuzz/fuzzers/server.rs @@ -7,7 +7,7 @@ use std::io; use std::sync::Arc; use rustls::server::{Accepted, Acceptor}; -use rustls::{ServerConfig, ServerConnection}; +use rustls::{Connection, ServerConfig, ServerConnection}; fuzz_target!(|data: &[u8]| { let _ = env_logger::try_init(); @@ -20,11 +20,10 @@ fuzz_target!(|data: &[u8]| { fn fuzz_buffered_api(data: &[u8]) { let config = Arc::new( - ServerConfig::builder_with_provider(rustls_fuzzing_provider::provider().into()) - .with_safe_default_protocol_versions() - .unwrap() + ServerConfig::builder(rustls_fuzzing_provider::PROVIDER.into()) .with_no_client_auth() - .with_cert_resolver(rustls_fuzzing_provider::server_cert_resolver()), + .with_server_credential_resolver(rustls_fuzzing_provider::server_cert_resolver()) + .unwrap(), ); let mut stream = io::Cursor::new(data); let mut server = ServerConnection::new(config).unwrap(); @@ -59,11 +58,10 @@ fn fuzz_acceptor_api(data: &[u8]) { fn fuzz_accepted(stream: &mut dyn io::Read, accepted: Accepted) { let mut maybe_server = accepted.into_connection(Arc::new( - ServerConfig::builder_with_provider(rustls_fuzzing_provider::provider().into()) - .with_safe_default_protocol_versions() - .unwrap() + ServerConfig::builder(rustls_fuzzing_provider::PROVIDER.into()) .with_no_client_auth() - .with_cert_resolver(rustls_fuzzing_provider::server_cert_resolver()), + .with_server_credential_resolver(rustls_fuzzing_provider::server_cert_resolver()) + .unwrap(), )); if let Ok(conn) = &mut maybe_server { diff --git a/fuzz/fuzzers/unbuffered.rs b/fuzz/fuzzers/unbuffered.rs deleted file mode 100644 index 67a50d07823..00000000000 --- a/fuzz/fuzzers/unbuffered.rs +++ /dev/null @@ -1,85 +0,0 @@ -#![no_main] -#[macro_use] -extern crate libfuzzer_sys; -extern crate rustls; - -use rustls::client::UnbufferedClientConnection; -use rustls::server::UnbufferedServerConnection; -use rustls::unbuffered::{ConnectionState, UnbufferedStatus}; -use rustls::{ClientConfig, ServerConfig, SideData}; - -fuzz_target!(|data: &[u8]| { - let _ = env_logger::try_init(); - let mut data = data.to_vec(); - match data.split_first_mut() { - Some((0x00, rest)) => client(rest), - Some((0x01, rest)) => server(rest), - Some((_, _)) | None => {} - } -}); - -fn client(data: &mut [u8]) { - let config = ClientConfig::builder_with_provider(rustls_fuzzing_provider::provider().into()) - .with_safe_default_protocol_versions() - .unwrap() - .dangerous() - .with_custom_certificate_verifier(rustls_fuzzing_provider::server_verifier()) - .with_no_client_auth(); - let conn = - UnbufferedClientConnection::new(config.into(), "localhost".try_into().unwrap()).unwrap(); - fuzz_unbuffered(data, ClientServer::Client(conn)); -} - -fn server(data: &mut [u8]) { - let config = ServerConfig::builder_with_provider(rustls_fuzzing_provider::provider().into()) - .with_safe_default_protocol_versions() - .unwrap() - .with_no_client_auth() - .with_cert_resolver(rustls_fuzzing_provider::server_cert_resolver()); - let conn = UnbufferedServerConnection::new(config.into()).unwrap(); - fuzz_unbuffered(data, ClientServer::Server(conn)); -} - -enum ClientServer { - Client(UnbufferedClientConnection), - Server(UnbufferedServerConnection), -} - -fn fuzz_unbuffered(mut data: &mut [u8], mut conn: ClientServer) { - while !data.is_empty() { - let consumed = match &mut conn { - ClientServer::Server(server) => process(server.process_tls_records(data)), - ClientServer::Client(client) => process(client.process_tls_records(data)), - }; - - match consumed { - Some(consumed) => { - data = &mut data[consumed..]; - } - None => break, - }; - } -} - -fn process(status: UnbufferedStatus<'_, '_, S>) -> Option { - let UnbufferedStatus { discard, state } = status; - - match state { - Ok(ConnectionState::EncodeTlsData(mut enc)) => { - let mut buffer = [0u8; 16_384 + 5]; // big enough for largest TLS packet - enc.encode(&mut buffer).unwrap(); - } - Ok(ConnectionState::TransmitTlsData(xmit)) => xmit.done(), - Ok(ConnectionState::WriteTraffic(_)) => return None, - Ok(ConnectionState::BlockedHandshake) => return None, - Ok(ConnectionState::ReadTraffic(mut read)) => loop { - let Some(_app_data) = read.next_record() else { - break; - }; - }, - Ok(st) => panic!("unhandled state {st:?}"), - Err(_) => return None, - }; - - Some(discard) -} diff --git a/openssl-tests/Cargo.toml b/openssl-tests/Cargo.toml index ab2b50044ba..3cda80ce7be 100644 --- a/openssl-tests/Cargo.toml +++ b/openssl-tests/Cargo.toml @@ -12,4 +12,9 @@ base64 = { workspace = true } num-bigint = { workspace = true } once_cell = { workspace = true } rustls = { path = "../rustls" } +rustls-aws-lc-rs = { path = "../rustls-aws-lc-rs" } +rustls-util = { path = "../rustls-util" } openssl = { workspace = true } + +[lints] +workspace = true diff --git a/openssl-tests/src/early_exporter.rs b/openssl-tests/src/early_exporter.rs new file mode 100644 index 00000000000..eeaa03d0b46 --- /dev/null +++ b/openssl-tests/src/early_exporter.rs @@ -0,0 +1,151 @@ +use std::io::{Read, Write}; +use std::net::{TcpListener, TcpStream}; +use std::sync::Arc; +use std::{str, thread}; + +use openssl::ssl::{SslConnector, SslMethod, SslSession, SslStream}; +use rustls::crypto::Identity; +use rustls::pki_types::pem::PemObject; +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; +use rustls::{Connection, ServerConfig}; +use rustls_aws_lc_rs as provider; +use rustls_util::complete_io; + +use crate::utils::verify_openssl3_available; + +#[test] +fn test_early_exporter() { + verify_openssl3_available(); + + // full handshake + one resumption + const ITERS: usize = 2; + + let listener = TcpListener::bind(("localhost", 0)).unwrap(); + let port = listener.local_addr().unwrap().port(); + + let server_thread = thread::spawn(move || { + let mut config = ServerConfig::builder(provider::DEFAULT_PROVIDER.into()) + .with_no_client_auth() + .with_single_cert( + Arc::new(Identity::from_cert_chain(load_certs()).unwrap()), + load_private_key(), + ) + .unwrap(); + config.max_early_data_size = 8192; + let config = Arc::new(config); + + for _ in 0..ITERS { + let mut server = rustls::ServerConnection::new(config.clone()).unwrap(); + let (mut tcp_stream, _addr) = listener.accept().unwrap(); + + // read clienthello and then inspect early_data status + server + .read_tls(&mut tcp_stream) + .unwrap(); + server.process_new_packets().unwrap(); + + let message = if let Some(mut early) = server.early_data() { + let secret = early + .exporter() + .unwrap() + .derive(b"label", Some(b"context"), [0u8; 64]) + .unwrap(); + + let mut buf = b"early data: ".to_vec(); + early.read_to_end(&mut buf).unwrap(); + buf.push(b'\n'); + + buf.extend_from_slice(b"exported: "); + buf.extend_from_slice(format!("{:02x?}", secret).as_bytes()); + buf.push(b'\n'); + buf + } else { + b"no early data\n".to_vec() + }; + + server + .writer() + .write_all(&message) + .unwrap(); + + complete_io(&mut tcp_stream, &mut server).unwrap(); + + tcp_stream.flush().unwrap(); + } + }); + + let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); + connector + .set_ca_file(CA_PEM_FILE) + .unwrap(); + + let connector = connector.build(); + + let mut session: Option = None; + let mut saw_early_data_at_least_once = false; + + for _iter in 0..ITERS { + let stream = TcpStream::connect(("localhost", port)).unwrap(); + + let mut config = connector.configure().unwrap(); + config.set_connect_state(); + + if let Some(sess) = &session { + unsafe { config.set_session(sess).unwrap() }; + } + + let mut stream = SslStream::new( + config + .into_ssl("testserver.com") + .unwrap(), + stream, + ) + .unwrap(); + let _ = stream.write_early_data(b"early hello"); + + stream.connect().unwrap(); + + let mut buf = Vec::new(); + stream.read_to_end(&mut buf).unwrap(); + let message = String::from_utf8(buf).unwrap(); + + let mut secret = [0u8; 64]; + let expected_message = match stream + .ssl() + .export_keying_material_early(&mut secret, "label", b"context") + { + Ok(_) => { + saw_early_data_at_least_once = true; + format!("early data: early hello\nexported: {:02x?}\n", secret) + } + Err(_) => "no early data\n".to_string(), + }; + + assert_eq!(expected_message, message); + + let _ = stream.shutdown(); + + session = stream + .ssl() + .session() + .map(|sess| sess.to_owned()); + } + + assert!(saw_early_data_at_least_once); + server_thread.join().unwrap(); +} + +fn load_certs() -> Vec> { + CertificateDer::pem_file_iter(CERT_CHAIN_FILE) + .unwrap() + .map(|c| c.unwrap()) + .collect() +} + +fn load_private_key() -> PrivateKeyDer<'static> { + PrivateKeyDer::from_pem_file(PRIV_KEY_FILE).unwrap() +} + +const CERT_CHAIN_FILE: &str = "../test-ca/rsa-2048/end.fullchain"; +const PRIV_KEY_FILE: &str = "../test-ca/rsa-2048/end.key"; +const CA_PEM_FILE: &str = "../test-ca/rsa-2048/ca.cert"; diff --git a/openssl-tests/src/ffdhe.rs b/openssl-tests/src/ffdhe.rs index 941005b4b36..cc42b7a6195 100644 --- a/openssl-tests/src/ffdhe.rs +++ b/openssl-tests/src/ffdhe.rs @@ -1,22 +1,23 @@ use num_bigint::BigUint; -use rustls::crypto::{ - aws_lc_rs as provider, ActiveKeyExchange, CipherSuiteCommon, KeyExchangeAlgorithm, - SharedSecret, SupportedKxGroup, +use rustls::Tls12CipherSuite; +use rustls::crypto::kx::ffdhe::{FFDHE2048, FfdheGroup}; +use rustls::crypto::kx::{ + ActiveKeyExchange, KeyExchangeAlgorithm, NamedGroup, SharedSecret, StartedKeyExchange, + SupportedKxGroup, }; -use rustls::ffdhe_groups::FfdheGroup; -use rustls::{CipherSuite, NamedGroup, SupportedCipherSuite, Tls12CipherSuite}; +use rustls::crypto::{CipherSuite, CipherSuiteCommon}; +use rustls_aws_lc_rs as provider; -/// The (test-only) TLS1.2 ciphersuite TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 -pub static TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&TLS12_DHE_RSA_WITH_AES_128_GCM_SHA256); +pub(crate) const FFDHE2048_GROUP: &dyn SupportedKxGroup = + &FfdheKxGroup(NamedGroup::FFDHE2048, FFDHE2048); #[derive(Debug)] -pub struct FfdheKxGroup(pub NamedGroup, pub FfdheGroup<'static>); +pub(crate) struct FfdheKxGroup(pub NamedGroup, pub FfdheGroup<'static>); impl SupportedKxGroup for FfdheKxGroup { - fn start(&self) -> Result, rustls::Error> { + fn start(&self) -> Result { let mut x = vec![0; 64]; - provider::default_provider() + provider::DEFAULT_PROVIDER .secure_random .fill(&mut x)?; let x = BigUint::from_bytes_be(&x); @@ -28,13 +29,13 @@ impl SupportedKxGroup for FfdheKxGroup { let x_pub = g.modpow(&x, &p); let x_pub = to_bytes_be_with_len(x_pub, group.p.len()); - Ok(Box::new(ActiveFfdheKx { + Ok(StartedKeyExchange::Single(Box::new(ActiveFfdheKx { x_pub, x, p, group, named_group: self.0, - })) + }))) } fn ffdhe_group(&self) -> Option> { @@ -46,18 +47,14 @@ impl SupportedKxGroup for FfdheKxGroup { } } -static TLS12_DHE_RSA_WITH_AES_128_GCM_SHA256: Tls12CipherSuite = - match &provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 { - SupportedCipherSuite::Tls12(original) => Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - ..original.common - }, - kx: KeyExchangeAlgorithm::DHE, - ..**original - }, - _ => unreachable!(), - }; +pub(crate) static TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: Tls12CipherSuite = Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + ..provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256.common + }, + kx: KeyExchangeAlgorithm::DHE, + ..*provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 +}; struct ActiveFfdheKx { x_pub: Vec, diff --git a/openssl-tests/src/ffdhe_kx_with_openssl.rs b/openssl-tests/src/ffdhe_kx_with_openssl.rs index cc7cf3fa1d3..917e53ab5b4 100644 --- a/openssl-tests/src/ffdhe_kx_with_openssl.rs +++ b/openssl-tests/src/ffdhe_kx_with_openssl.rs @@ -1,56 +1,54 @@ +use std::borrow::Cow; use std::io::{Read, Write}; use std::net::{TcpListener, TcpStream}; use std::sync::Arc; use std::{fs, str, thread}; -use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; -use rustls::crypto::{aws_lc_rs as provider, CryptoProvider}; +use openssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod}; +use rustls::crypto::{CryptoProvider, Identity}; use rustls::pki_types::pem::PemObject; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; -use rustls::version::{TLS12, TLS13}; -use rustls::{ClientConfig, RootCertStore, ServerConfig, SupportedProtocolVersion}; +use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName}; +use rustls::{ClientConfig, Connection, RootCertStore, ServerConfig, ServerConnection}; +use rustls_aws_lc_rs as provider; +use rustls_util::complete_io; -use crate::ffdhe::{self, FfdheKxGroup}; +use crate::ffdhe::{self, FFDHE2048_GROUP}; use crate::utils::verify_openssl3_available; #[test] fn rustls_server_with_ffdhe_kx_tls13() { - test_rustls_server_with_ffdhe_kx(&TLS13, 1) + test_rustls_server_with_ffdhe_kx(FFDHE_TLS13_PROVIDER, 1) } #[test] fn rustls_server_with_ffdhe_kx_tls12() { - test_rustls_server_with_ffdhe_kx(&TLS12, 1) + test_rustls_server_with_ffdhe_kx(FFDHE_TLS12_PROVIDER, 1) } -fn test_rustls_server_with_ffdhe_kx( - protocol_version: &'static SupportedProtocolVersion, - iters: usize, -) { +fn test_rustls_server_with_ffdhe_kx(provider: CryptoProvider, iters: usize) { verify_openssl3_available(); let message = "Hello from rustls!\n"; - let listener = std::net::TcpListener::bind(("localhost", 0)).unwrap(); + let listener = TcpListener::bind(("localhost", 0)).unwrap(); let port = listener.local_addr().unwrap().port(); - let server_thread = std::thread::spawn(move || { - let config = Arc::new(server_config_with_ffdhe_kx(protocol_version)); + let server_thread = thread::spawn(move || { + let config = Arc::new(server_config_with_ffdhe_kx(provider)); for _ in 0..iters { - let mut server = rustls::ServerConnection::new(config.clone()).unwrap(); + let mut server = ServerConnection::new(config.clone()).unwrap(); let (mut tcp_stream, _addr) = listener.accept().unwrap(); server .writer() .write_all(message.as_bytes()) .unwrap(); - server - .complete_io(&mut tcp_stream) - .unwrap(); + + complete_io(&mut tcp_stream, &mut server).unwrap(); tcp_stream.flush().unwrap(); } }); - let mut connector = openssl::ssl::SslConnector::builder(SslMethod::tls()).unwrap(); + let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); connector .set_ca_file(CA_PEM_FILE) .unwrap(); @@ -102,7 +100,7 @@ fn test_rustls_client_with_ffdhe_kx(iters: usize) { let listener = TcpListener::bind(("localhost", 0)).unwrap(); let port = listener.local_addr().unwrap().port(); - let server_thread = std::thread::spawn(move || { + let server_thread = thread::spawn(move || { for stream in listener.incoming().take(iters) { match stream { Ok(stream) => { @@ -122,20 +120,28 @@ fn test_rustls_client_with_ffdhe_kx(iters: usize) { }); // client: - for _ in 0..iters { - let mut tcp_stream = std::net::TcpStream::connect(("localhost", port)).unwrap(); - let mut client = rustls::client::ClientConnection::new( - client_config_with_ffdhe_kx().into(), - "localhost".try_into().unwrap(), + let config = Arc::new( + ClientConfig::builder( + // OpenSSL 3 does not support RFC 7919 with TLS 1.2: https://github.com/openssl/openssl/issues/10971 + FFDHE_TLS13_PROVIDER.into(), ) - .unwrap(); + .with_root_certificates(root_ca()) + .with_no_client_auth() + .unwrap(), + ); + let server_name = ServerName::try_from("localhost").unwrap(); + for _ in 0..iters { + let mut tcp_stream = TcpStream::connect(("localhost", port)).unwrap(); + let mut client = config + .connect(server_name.clone()) + .build() + .unwrap(); client .writer() .write_all(message.as_bytes()) .unwrap(); - client - .complete_io(&mut tcp_stream) - .unwrap(); + + complete_io(&mut tcp_stream, &mut client).unwrap(); client.send_close_notify(); client .write_tls(&mut tcp_stream) @@ -146,15 +152,6 @@ fn test_rustls_client_with_ffdhe_kx(iters: usize) { server_thread.join().unwrap(); } -fn client_config_with_ffdhe_kx() -> ClientConfig { - ClientConfig::builder_with_provider(ffdhe_provider().into()) - // OpenSSL 3 does not support RFC 7919 with TLS 1.2: https://github.com/openssl/openssl/issues/10971 - .with_protocol_versions(&[&TLS13]) - .unwrap() - .with_root_certificates(root_ca()) - .with_no_client_auth() -} - // TLS 1.2 requires stripping leading zeros of the shared secret, // While TLS 1.3 requires the shared secret to be padded with zeros. // The chance of getting a shared secret with the first byte being zero is 1 in 256, @@ -168,13 +165,13 @@ fn rustls_client_with_ffdhe_kx_repeated() { #[test] #[ignore] fn rustls_server_with_ffdhe_tls13_repeated() { - test_rustls_server_with_ffdhe_kx(&TLS13, 512) + test_rustls_server_with_ffdhe_kx(FFDHE_TLS13_PROVIDER, 512) } #[test] #[ignore] fn rustls_server_with_ffdhe_tls12_repeated() { - test_rustls_server_with_ffdhe_kx(&TLS12, 512); + test_rustls_server_with_ffdhe_kx(FFDHE_TLS12_PROVIDER, 512); } fn root_ca() -> RootCertStore { @@ -194,26 +191,30 @@ fn load_private_key() -> PrivateKeyDer<'static> { PrivateKeyDer::from_pem_file(PRIV_KEY_FILE).unwrap() } -fn ffdhe_provider() -> CryptoProvider { - CryptoProvider { - cipher_suites: vec![ - ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - provider::cipher_suite::TLS13_AES_128_GCM_SHA256, - ], - kx_groups: vec![&FfdheKxGroup( - rustls::NamedGroup::FFDHE2048, - rustls::ffdhe_groups::FFDHE2048, - )], - ..provider::default_provider() - } -} - -fn server_config_with_ffdhe_kx(protocol: &'static SupportedProtocolVersion) -> ServerConfig { - ServerConfig::builder_with_provider(ffdhe_provider().into()) - .with_protocol_versions(&[protocol]) - .unwrap() +const FFDHE_PROVIDER: CryptoProvider = CryptoProvider { + tls12_cipher_suites: Cow::Borrowed(&[&ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256]), + tls13_cipher_suites: Cow::Borrowed(&[provider::cipher_suite::TLS13_AES_128_GCM_SHA256]), + kx_groups: Cow::Borrowed(&[FFDHE2048_GROUP]), + ..provider::DEFAULT_PROVIDER +}; + +const FFDHE_TLS12_PROVIDER: CryptoProvider = CryptoProvider { + tls13_cipher_suites: Cow::Borrowed(&[]), + ..FFDHE_PROVIDER +}; + +const FFDHE_TLS13_PROVIDER: CryptoProvider = CryptoProvider { + tls12_cipher_suites: Cow::Borrowed(&[]), + ..FFDHE_PROVIDER +}; + +fn server_config_with_ffdhe_kx(provider: CryptoProvider) -> ServerConfig { + ServerConfig::builder(provider.into()) .with_no_client_auth() - .with_single_cert(load_certs(), load_private_key()) + .with_single_cert( + Arc::new(Identity::from_cert_chain(load_certs()).unwrap()), + load_private_key(), + ) .unwrap() } diff --git a/openssl-tests/src/lib.rs b/openssl-tests/src/lib.rs index aeda47ff258..b68d6d3fd63 100644 --- a/openssl-tests/src/lib.rs +++ b/openssl-tests/src/lib.rs @@ -1,5 +1,6 @@ #![cfg(test)] +mod early_exporter; mod ffdhe; mod ffdhe_kx_with_openssl; mod raw_key_openssl_interop; diff --git a/openssl-tests/src/raw_key_openssl_interop.rs b/openssl-tests/src/raw_key_openssl_interop.rs index f8a9bfb8890..d1324d985ec 100644 --- a/openssl-tests/src/raw_key_openssl_interop.rs +++ b/openssl-tests/src/raw_key_openssl_interop.rs @@ -1,33 +1,35 @@ //! This module provides tests for the interoperability of raw public keys with OpenSSL, and also //! demonstrates how to set up a client-server architecture that utilizes raw public keys. //! -//! The module also includes example implementations of the `ServerCertVerifier` and `ClientCertVerifier` traits, using +//! The module also includes example implementations of the `ServerVerifier` and `ClientVerifier` traits, using //! pre-configured raw public keys for the verification of the peer. mod client { + use core::hash::Hasher; use std::io::{self, Read, Write}; use std::net::TcpStream; use std::sync::Arc; - use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; - use rustls::client::AlwaysResolvesClientRawPublicKeys; + use rustls::client::danger::{ + HandshakeSignatureValid, PeerVerified, ServerIdentity, ServerVerifier, + }; use rustls::crypto::{ - aws_lc_rs as provider, verify_tls13_signature_with_raw_key, WebPkiSupportedAlgorithms, + Credentials, Identity, InconsistentKeys, SignatureScheme, SingleCredential, + WebPkiSupportedAlgorithms, verify_tls13_signature, }; + use rustls::enums::CertificateType; + use rustls::error::{ApiMisuse, CertificateError, PeerIncompatible}; use rustls::pki_types::pem::PemObject; - use rustls::pki_types::{ - CertificateDer, PrivateKeyDer, ServerName, SubjectPublicKeyInfoDer, UnixTime, - }; - use rustls::sign::CertifiedKey; - use rustls::version::TLS13; - use rustls::{ - CertificateError, ClientConfig, ClientConnection, DigitallySignedStruct, Error, - InconsistentKeys, PeerIncompatible, SignatureScheme, Stream, - }; + use rustls::pki_types::{PrivateKeyDer, SubjectPublicKeyInfoDer}; + use rustls::server::danger::SignatureVerificationInput; + use rustls::{ClientConfig, Error}; + use rustls_aws_lc_rs as provider; + use rustls_util::Stream; /// Build a `ClientConfig` with the given client private key and a server public key to trust. pub(super) fn make_config(client_private_key: &str, server_pub_key: &str) -> ClientConfig { - let client_private_key = Arc::new(provider::default_provider()) + let provider = Arc::new(provider::DEFAULT_PROVIDER); + let client_private_key = provider .key_provider .load_private_key( PrivateKeyDer::from_pem_file(client_private_key) @@ -38,24 +40,22 @@ mod client { .public_key() .ok_or(Error::InconsistentKeys(InconsistentKeys::Unknown)) .expect("cannot load public key"); - let client_public_key_as_cert = CertificateDer::from(client_public_key.to_vec()); let server_raw_key = SubjectPublicKeyInfoDer::from_pem_file(server_pub_key) .expect("cannot open pub key file"); - let certified_key = Arc::new(CertifiedKey::new( - vec![client_public_key_as_cert], + let credentials = Credentials::new_unchecked( + Arc::new(Identity::RawPublicKey(client_public_key.into_owned())), client_private_key, - )); + ); - ClientConfig::builder_with_protocol_versions(&[&TLS13]) + ClientConfig::builder(provider) .dangerous() - .with_custom_certificate_verifier(Arc::new(SimpleRpkServerCertVerifier::new(vec![ + .with_custom_certificate_verifier(Arc::new(SimpleRpkServerVerifier::new(vec![ server_raw_key, ]))) - .with_client_cert_resolver(Arc::new(AlwaysResolvesClientRawPublicKeys::new( - certified_key, - ))) + .with_client_credential_resolver(Arc::new(SingleCredential::from(credentials))) + .unwrap() } /// Run the client and connect to the server at the specified port. @@ -63,8 +63,11 @@ mod client { /// This client reads a message and then writes 'Hello from the client' to the server. pub(super) fn run_client(config: ClientConfig, port: u16) -> Result { let server_name = "0.0.0.0".try_into().unwrap(); - let mut conn = ClientConnection::new(Arc::new(config), server_name).unwrap(); - let mut sock = TcpStream::connect(format!("[::]:{}", port)).unwrap(); + let mut conn = Arc::new(config) + .connect(server_name) + .build() + .unwrap(); + let mut sock = TcpStream::connect(format!("[::]:{port}")).unwrap(); let mut tls = Stream::new(&mut conn, &mut sock); let mut buf = vec![0; 128]; @@ -83,104 +86,93 @@ mod client { /// /// Note: when the verifier is used for Raw Public Keys the `CertificateDer` argument to the functions contains the SPKI instead of a X509 Certificate #[derive(Debug)] - struct SimpleRpkServerCertVerifier { + struct SimpleRpkServerVerifier { trusted_spki: Vec>, supported_algs: WebPkiSupportedAlgorithms, } - impl SimpleRpkServerCertVerifier { + impl SimpleRpkServerVerifier { fn new(trusted_spki: Vec>) -> Self { - SimpleRpkServerCertVerifier { + Self { trusted_spki, - supported_algs: Arc::new(provider::default_provider()) - .clone() - .signature_verification_algorithms, + supported_algs: provider::DEFAULT_PROVIDER.signature_verification_algorithms, } } } - impl ServerCertVerifier for SimpleRpkServerCertVerifier { - fn verify_server_cert( - &self, - end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp_response: &[u8], - _now: UnixTime, - ) -> Result { - let end_entity_as_spki = SubjectPublicKeyInfoDer::from(end_entity.as_ref()); - match self - .trusted_spki - .contains(&end_entity_as_spki) - { - false => Err(rustls::Error::InvalidCertificate( - CertificateError::UnknownIssuer, - )), - true => Ok(ServerCertVerified::assertion()), + impl ServerVerifier for SimpleRpkServerVerifier { + fn verify_identity(&self, identity: &ServerIdentity<'_>) -> Result { + let Identity::RawPublicKey(spki) = identity.identity else { + return Err(ApiMisuse::UnverifiableCertificateType.into()); + }; + + match self.trusted_spki.contains(spki) { + false => Err(Error::InvalidCertificate(CertificateError::UnknownIssuer)), + true => Ok(PeerVerified::assertion()), } } fn verify_tls12_signature( &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &DigitallySignedStruct, - ) -> Result { - Err(rustls::Error::PeerIncompatible( - PeerIncompatible::Tls12NotOffered, - )) + _input: &SignatureVerificationInput<'_>, + ) -> Result { + Err(Error::PeerIncompatible(PeerIncompatible::Tls12NotOffered)) } fn verify_tls13_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls13_signature_with_raw_key( - message, - &SubjectPublicKeyInfoDer::from(cert.as_ref()), - dss, - &self.supported_algs, - ) + input: &SignatureVerificationInput<'_>, + ) -> Result { + verify_tls13_signature(input, &self.supported_algs) } fn supported_verify_schemes(&self) -> Vec { self.supported_algs.supported_schemes() } - fn requires_raw_public_keys(&self) -> bool { - true + fn request_ocsp_response(&self) -> bool { + false + } + + fn supported_certificate_types(&self) -> &'static [CertificateType] { + &[CertificateType::RawPublicKey] + } + + fn hash_config(&self, _: &mut dyn Hasher) { + // XXX: a non-test implementation would hash its configuration here. } } } mod server { + #![allow(clippy::std_instead_of_core)] // awaits core::io::ErrorKind in stable (1.97) use std::io::{self, ErrorKind, Read, Write}; use std::net::TcpListener; use std::sync::Arc; use rustls::client::danger::HandshakeSignatureValid; use rustls::crypto::{ - aws_lc_rs as provider, verify_tls13_signature_with_raw_key, WebPkiSupportedAlgorithms, + Credentials, Identity, InconsistentKeys, SignatureScheme, SingleCredential, + WebPkiSupportedAlgorithms, verify_tls13_signature, }; + use rustls::enums::CertificateType; + use rustls::error::{ApiMisuse, CertificateError, Error, PeerIncompatible}; use rustls::pki_types::pem::PemObject; - use rustls::pki_types::{CertificateDer, PrivateKeyDer, SubjectPublicKeyInfoDer, UnixTime}; - use rustls::server::danger::{ClientCertVerified, ClientCertVerifier}; - use rustls::server::AlwaysResolvesServerRawPublicKeys; - use rustls::sign::CertifiedKey; - use rustls::version::TLS13; - use rustls::{ - CertificateError, DigitallySignedStruct, DistinguishedName, Error, InconsistentKeys, - PeerIncompatible, ServerConfig, ServerConnection, SignatureScheme, + use rustls::pki_types::{PrivateKeyDer, SubjectPublicKeyInfoDer}; + use rustls::server::danger::{ + ClientIdentity, ClientVerifier, PeerVerified, SignatureVerificationInput, }; + use rustls::{Connection, DistinguishedName, ServerConfig, ServerConnection}; + use rustls_aws_lc_rs as provider; + use rustls_util::complete_io; /// Build a `ServerConfig` with the given server private key and a client public key to trust. pub(super) fn make_config(server_private_key: &str, client_pub_key: &str) -> ServerConfig { let client_raw_key = SubjectPublicKeyInfoDer::from_pem_file(client_pub_key) .expect("cannot open pub key file"); - let server_private_key = provider::default_provider() + let provider = Arc::new(provider::DEFAULT_PROVIDER); + let server_private_key = provider .key_provider .load_private_key( PrivateKeyDer::from_pem_file(server_private_key) @@ -191,19 +183,19 @@ mod server { .public_key() .ok_or(Error::InconsistentKeys(InconsistentKeys::Unknown)) .expect("cannot load public key"); - let server_public_key_as_cert = CertificateDer::from(server_public_key.to_vec()); - let certified_key = Arc::new(CertifiedKey::new( - vec![server_public_key_as_cert], + let credentials = Credentials::new_unchecked( + Arc::new(Identity::RawPublicKey(server_public_key.into_owned())), server_private_key, - )); + ); - let client_cert_verifier = Arc::new(SimpleRpkClientCertVerifier::new(vec![client_raw_key])); - let server_cert_resolver = Arc::new(AlwaysResolvesServerRawPublicKeys::new(certified_key)); + let client_cert_verifier = Arc::new(SimpleRpkClientVerifier::new(vec![client_raw_key])); + let server_cert_resolver = Arc::new(SingleCredential::from(credentials)); - ServerConfig::builder_with_protocol_versions(&[&TLS13]) + ServerConfig::builder(provider) .with_client_cert_verifier(client_cert_verifier) - .with_cert_resolver(server_cert_resolver) + .with_server_credential_resolver(server_cert_resolver) + .unwrap() } /// Run the server at the specified port and accept a connection from the client. @@ -217,11 +209,11 @@ mod server { let (mut stream, _) = listener.accept()?; let mut conn = ServerConnection::new(Arc::new(config)).unwrap(); - conn.complete_io(&mut stream)?; + complete_io(&mut stream, &mut conn)?; conn.writer() .write_all(b"Hello from the server")?; - conn.complete_io(&mut stream)?; + complete_io(&mut stream, &mut conn)?; let mut buf = [0; 128]; @@ -229,7 +221,7 @@ mod server { match conn.reader().read(&mut buf) { Ok(len) => { conn.send_close_notify(); - conn.complete_io(&mut stream)?; + complete_io(&mut stream, &mut conn)?; return Ok(String::from_utf8_lossy(&buf[..len]).to_string()); } Err(err) if err.kind() == ErrorKind::WouldBlock => { @@ -248,76 +240,56 @@ mod server { /// /// Note: when the verifier is used for Raw Public Keys the `CertificateDer` argument to the functions contains the SPKI instead of a X509 Certificate #[derive(Debug)] - struct SimpleRpkClientCertVerifier { + struct SimpleRpkClientVerifier { trusted_spki: Vec>, supported_algs: WebPkiSupportedAlgorithms, } - impl SimpleRpkClientCertVerifier { - pub fn new(trusted_spki: Vec>) -> Self { + impl SimpleRpkClientVerifier { + pub(crate) fn new(trusted_spki: Vec>) -> Self { Self { trusted_spki, - supported_algs: Arc::new(provider::default_provider()) - .clone() - .signature_verification_algorithms, + supported_algs: provider::DEFAULT_PROVIDER.signature_verification_algorithms, } } } - impl ClientCertVerifier for SimpleRpkClientCertVerifier { - fn root_hint_subjects(&self) -> &[DistinguishedName] { - &[] - } + impl ClientVerifier for SimpleRpkClientVerifier { + fn verify_identity(&self, identity: &ClientIdentity<'_>) -> Result { + let Identity::RawPublicKey(spki) = identity.identity else { + return Err(ApiMisuse::UnverifiableCertificateType.into()); + }; - fn verify_client_cert( - &self, - end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _now: UnixTime, - ) -> Result { - let end_entity_as_spki = SubjectPublicKeyInfoDer::from(end_entity.as_ref()); - match self - .trusted_spki - .contains(&end_entity_as_spki) - { - false => Err(rustls::Error::InvalidCertificate( - CertificateError::UnknownIssuer, - )), - true => Ok(ClientCertVerified::assertion()), + match self.trusted_spki.contains(spki) { + false => Err(Error::InvalidCertificate(CertificateError::UnknownIssuer)), + true => Ok(PeerVerified::assertion()), } } fn verify_tls12_signature( &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &DigitallySignedStruct, - ) -> Result { - Err(rustls::Error::PeerIncompatible( - PeerIncompatible::Tls12NotOffered, - )) + _input: &SignatureVerificationInput<'_>, + ) -> Result { + Err(Error::PeerIncompatible(PeerIncompatible::Tls12NotOffered)) } fn verify_tls13_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls13_signature_with_raw_key( - message, - &SubjectPublicKeyInfoDer::from(cert.as_ref()), - dss, - &self.supported_algs, - ) + input: &SignatureVerificationInput<'_>, + ) -> Result { + verify_tls13_signature(input, &self.supported_algs) + } + + fn root_hint_subjects(&self) -> Arc<[DistinguishedName]> { + Arc::from(Vec::new()) } fn supported_verify_schemes(&self) -> Vec { self.supported_algs.supported_schemes() } - fn requires_raw_public_keys(&self) -> bool { - true + fn supported_certificate_types(&self) -> &'static [CertificateType] { + &[CertificateType::RawPublicKey] } } } @@ -326,9 +298,16 @@ mod tests { use std::io::{BufRead, BufReader, Read, Write}; use std::net::TcpListener; use std::process::{Command, Stdio}; + use std::sync::Arc; use std::sync::mpsc::channel; use std::thread; + use rustls::ServerConfig; + use rustls::crypto::Identity; + use rustls::pki_types::pem::PemObject; + use rustls::pki_types::{CertificateDer, PrivateKeyDer}; + use rustls_aws_lc_rs as provider; + use super::{client, server}; use crate::utils::verify_openssl3_available; @@ -365,7 +344,7 @@ mod tests { assert_eq!(server_message, "Hello from the server"); } Err(e) => { - panic!("Client failed to communicate with the server: {:?}", e); + panic!("Client failed to communicate with the server: {e:?}"); } } @@ -380,11 +359,64 @@ mod tests { assert_eq!(client_message, "Hello from the client"); } Err(e) => { - panic!("Server failed to communicate with the client: {:?}", e); + panic!("Server failed to communicate with the client: {e:?}"); } } } + #[test] + fn test_rust_x509_server_with_openssl_raw_key_and_x509_client() { + verify_openssl3_available(); + + let listener = tcp_listener(); + let port = listener.local_addr().unwrap().port(); + + let cert_file = SERVER_CERT_KEY_FILE; + let private_key_file = SERVER_PRIV_KEY_FILE; + + let certs = CertificateDer::pem_file_iter(cert_file) + .unwrap() + .map(|cert| cert.unwrap()) + .collect(); + let private_key = PrivateKeyDer::from_pem_file(private_key_file).unwrap(); + let config = ServerConfig::builder(Arc::new(provider::DEFAULT_PROVIDER)) + .with_no_client_auth() + .with_single_cert( + Arc::new(Identity::from_cert_chain(certs).unwrap()), + private_key, + ) + .unwrap(); + let server_thread = thread::spawn(move || { + server::run_server(config, listener).expect("failed to run server to completion") + }); + + // Start the OpenSSL client + let mut openssl_client = Command::new("openssl") + .arg("s_client") + .arg("-connect") + .arg(format!("[::]:{port:?}")) + .arg("-enable_client_rpk") + .arg("-key") + .arg(CLIENT_PRIV_KEY_FILE) + .arg("-cert") + .arg(CLIENT_CERT_KEY_FILE) + .arg("-tls1_3") + .arg("-debug") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("Failed to execute OpenSSL client"); + + let stdin = openssl_client.stdin.take().unwrap(); + let stdout = openssl_client.stdout.take().unwrap(); + let received_server_msg = + process_openssl_client_interaction(stdin, stdout, "Hello, from openssl client!"); + + assert!(received_server_msg); + assert_eq!(server_thread.join().unwrap(), "Hello, from openssl client!"); + openssl_client.wait().unwrap(); + } + #[test] fn test_rust_server_with_openssl_client() { verify_openssl3_available(); @@ -404,7 +436,7 @@ mod tests { let mut openssl_client = Command::new("openssl") .arg("s_client") .arg("-connect") - .arg(format!("[::]:{:?}", port)) + .arg(format!("[::]:{port:?}")) .arg("-enable_server_rpk") .arg("-enable_client_rpk") .arg("-key") @@ -418,36 +450,46 @@ mod tests { .spawn() .expect("Failed to execute OpenSSL client"); - let mut stdin = openssl_client.stdin.take().unwrap(); - let mut stdout = openssl_client.stdout.take().unwrap(); + let stdin = openssl_client.stdin.take().unwrap(); + let stdout = openssl_client.stdout.take().unwrap(); + let received_server_msg = + process_openssl_client_interaction(stdin, stdout, "Hello, from openssl client!"); + + assert!(received_server_msg); + assert_eq!(server_thread.join().unwrap(), "Hello, from openssl client!"); + openssl_client.wait().unwrap(); + } + + fn process_openssl_client_interaction( + mut stdin: std::process::ChildStdin, + mut stdout: std::process::ChildStdout, + message: &str, + ) -> bool { let mut stdout_buf = [0; 1024]; let mut openssl_stdout = String::new(); - let mut received_server_msg = false; + loop { - match stdout.read(&mut stdout_buf) { + let len = match stdout.read(&mut stdout_buf) { Ok(0) => break, - Ok(len) => { - let read = &stdout_buf[..len]; - - std::io::stdout() - .write_all(read) - .unwrap(); - openssl_stdout.push_str(&String::from_utf8_lossy(read)); - if openssl_stdout.contains("Hello from the server") { - received_server_msg = true; - stdin - .write_all(b"Hello, from openssl client!") - .expect("Failed to write to stdin"); - break; - } - } + Ok(len) => len, Err(e) => panic!("Error reading from OpenSSL stdin: {e:?}"), + }; + + let read = &stdout_buf[..len]; + std::io::stdout() + .write_all(read) + .unwrap(); + openssl_stdout.push_str(&String::from_utf8_lossy(read)); + + if openssl_stdout.contains("Hello from the server") { + stdin + .write_all(message.as_bytes()) + .expect("Failed to write to stdin"); + return true; } } - assert!(received_server_msg); - assert_eq!(server_thread.join().unwrap(), "Hello, from openssl client!"); - openssl_client.wait().unwrap(); + false } #[test] @@ -489,7 +531,7 @@ mod tests { } } Err(e) => { - panic!("Error reading from OpenSSL stdout: {:?}", e); + panic!("Error reading from OpenSSL stdout: {e:?}"); } } } diff --git a/openssl-tests/src/utils.rs b/openssl-tests/src/utils.rs index ae16a2bb36a..078613e9f85 100644 --- a/openssl-tests/src/utils.rs +++ b/openssl-tests/src/utils.rs @@ -1,6 +1,6 @@ use once_cell::sync::Lazy; -pub fn verify_openssl3_available() { +pub(crate) fn verify_openssl3_available() { static VERIFIED: Lazy<()> = Lazy::new(verify_openssl3_available_internal); *VERIFIED } @@ -15,11 +15,11 @@ fn verify_openssl3_available_internal() { panic!( "OpenSSL exited with an error status: {}\n{}", output.status, - std::str::from_utf8(&output.stderr).unwrap_or_default() + str::from_utf8(&output.stderr).unwrap_or_default() ); } Ok(output) => { - let version_str = std::str::from_utf8(&output.stdout).unwrap(); + let version_str = str::from_utf8(&output.stdout).unwrap(); let parts = version_str .split(' ') .collect::>(); diff --git a/openssl-tests/src/validate_ffdhe_params.rs b/openssl-tests/src/validate_ffdhe_params.rs index ae10871dc0e..4a1c2999890 100644 --- a/openssl-tests/src/validate_ffdhe_params.rs +++ b/openssl-tests/src/validate_ffdhe_params.rs @@ -1,21 +1,21 @@ use base64::prelude::*; -use rustls::ffdhe_groups::FfdheGroup; -use rustls::{ffdhe_groups, NamedGroup}; +use rustls::crypto::kx::NamedGroup; +use rustls::crypto::kx::ffdhe::{ + FFDHE2048, FFDHE3072, FFDHE4096, FFDHE6144, FFDHE8192, FfdheGroup, +}; use crate::utils::verify_openssl3_available; #[test] fn ffdhe_params_correct() { - use NamedGroup::*; - verify_openssl3_available(); for (name, group) in [ - (FFDHE2048, ffdhe_groups::FFDHE2048), - (FFDHE3072, ffdhe_groups::FFDHE3072), - (FFDHE4096, ffdhe_groups::FFDHE4096), - (FFDHE6144, ffdhe_groups::FFDHE6144), - (FFDHE8192, ffdhe_groups::FFDHE8192), + (NamedGroup::FFDHE2048, FFDHE2048), + (NamedGroup::FFDHE3072, FFDHE3072), + (NamedGroup::FFDHE4096, FFDHE4096), + (NamedGroup::FFDHE6144, FFDHE6144), + (NamedGroup::FFDHE8192, FFDHE8192), ] { println!("testing {name:?}"); test_ffdhe_params_correct(name, group); @@ -25,13 +25,7 @@ fn ffdhe_params_correct() { fn test_ffdhe_params_correct(name: NamedGroup, group: FfdheGroup<'static>) { let (p, g) = get_ffdhe_params_from_openssl(name); let openssl_params = FfdheGroup::from_params_trimming_leading_zeros(&p, &g); - #[allow(deprecated)] - let rustls_params_from_name = FfdheGroup::from_named_group(name).unwrap(); - #[allow(deprecated)] - let round_trip_name = rustls_params_from_name.named_group(); - assert_eq!(round_trip_name, Some(name)); - assert_eq!(rustls_params_from_name, openssl_params); assert_eq!(group, openssl_params); } @@ -63,8 +57,9 @@ fn get_ffdhe_params_from_openssl(ffdhe_group: NamedGroup) -> (Vec, Vec) } /// Parse PEM-encoded DH parameters, returning `(p, g)` +#[allow(clippy::result_large_err)] // For the closure passed to `asn1::parse()` fn parse_dh_params_pem(data: &[u8]) -> (Vec, Vec) { - let output_str = std::str::from_utf8(data).unwrap(); + let output_str = str::from_utf8(data).unwrap(); let output_str_lines = output_str.lines().collect::>(); assert_eq!(output_str_lines[0], "-----BEGIN DH PARAMETERS-----"); @@ -86,10 +81,10 @@ fn parse_dh_params_pem(data: &[u8]) -> (Vec, Vec) { .unwrap(); let res: asn1::ParseResult<_> = asn1::parse(&base64_decoded, |d| { - d.read_element::()? + d.read_element::>()? .parse(|d| { - let p = d.read_element::()?; - let g = d.read_element::()?; + let p = d.read_element::>()?; + let g = d.read_element::>()?; Ok((p, g)) }) }); diff --git a/provider-example/Cargo.toml b/provider-example/Cargo.toml deleted file mode 100644 index f116a6c0aa8..00000000000 --- a/provider-example/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "rustls-provider-example" -version = "0.0.1" -edition = "2021" -license = "Apache-2.0 OR ISC OR MIT" -description = "Example of rustls with custom crypto provider." -publish = false - -[dependencies] -chacha20poly1305 = { workspace = true } -der = { workspace = true } -ecdsa = { workspace = true } -hmac = { workspace = true } -hpke-rs = { workspace = true } -hpke-rs-crypto = { workspace = true } -hpke-rs-rust-crypto = { workspace = true } -p256 = { workspace = true } -pkcs8 = { workspace = true } -rand_core = { workspace = true } -rustls = { path = "../rustls", default-features = false, features = ["logging", "tls12"] } -rsa = { workspace = true } -sha2 = { workspace = true } -signature = { workspace = true } -webpki = { workspace = true } -x25519-dalek = { workspace = true } - -[dev-dependencies] -env_logger = { workspace = true } -rcgen = { workspace = true } -webpki-roots = { workspace = true } - -[features] -default = ["std"] -std = ["hpke-rs/std", "hpke-rs-crypto/std", "pkcs8/std", "rustls/std"] diff --git a/provider-example/examples/client.rs b/provider-example/examples/client.rs deleted file mode 100644 index 9fab7e097a4..00000000000 --- a/provider-example/examples/client.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::io::{stdout, Read, Write}; -use std::net::TcpStream; -use std::sync::Arc; - -fn main() { - env_logger::init(); - - let root_store = rustls::RootCertStore::from_iter( - webpki_roots::TLS_SERVER_ROOTS - .iter() - .cloned(), - ); - - let config = - rustls::ClientConfig::builder_with_provider(rustls_provider_example::provider().into()) - .with_safe_default_protocol_versions() - .unwrap() - .with_root_certificates(root_store) - .with_no_client_auth(); - - let server_name = "www.rust-lang.org".try_into().unwrap(); - let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap(); - let mut sock = TcpStream::connect("www.rust-lang.org:443").unwrap(); - let mut tls = rustls::Stream::new(&mut conn, &mut sock); - tls.write_all( - concat!( - "GET / HTTP/1.1\r\n", - "Host: www.rust-lang.org\r\n", - "Connection: close\r\n", - "Accept-Encoding: identity\r\n", - "\r\n" - ) - .as_bytes(), - ) - .unwrap(); - let ciphersuite = tls - .conn - .negotiated_cipher_suite() - .unwrap(); - writeln!( - &mut std::io::stderr(), - "Current ciphersuite: {:?}", - ciphersuite.suite() - ) - .unwrap(); - let mut plaintext = Vec::new(); - tls.read_to_end(&mut plaintext).unwrap(); - stdout().write_all(&plaintext).unwrap(); -} diff --git a/provider-example/examples/server.rs b/provider-example/examples/server.rs deleted file mode 100644 index 4f69fc237c6..00000000000 --- a/provider-example/examples/server.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::io::Write; -use std::sync::Arc; - -use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; -use rustls::server::Acceptor; -use rustls::ServerConfig; - -fn main() { - env_logger::init(); - - let pki = TestPki::new(); - let server_config = pki.server_config(); - - let listener = std::net::TcpListener::bind(format!("[::]:{}", 4443)).unwrap(); - for stream in listener.incoming() { - let mut stream = stream.unwrap(); - let mut acceptor = Acceptor::default(); - - let accepted = loop { - acceptor.read_tls(&mut stream).unwrap(); - if let Some(accepted) = acceptor.accept().unwrap() { - break accepted; - } - }; - - match accepted.into_connection(server_config.clone()) { - Ok(mut conn) => { - let msg = concat!( - "HTTP/1.1 200 OK\r\n", - "Connection: Closed\r\n", - "Content-Type: text/html\r\n", - "\r\n", - "

Hello World!

\r\n" - ) - .as_bytes(); - - // Note: do not use `unwrap()` on IO in real programs! - conn.writer().write_all(msg).unwrap(); - conn.write_tls(&mut stream).unwrap(); - conn.complete_io(&mut stream).unwrap(); - - conn.send_close_notify(); - conn.write_tls(&mut stream).unwrap(); - conn.complete_io(&mut stream).unwrap(); - } - Err((err, _)) => { - eprintln!("{err}"); - } - } - } -} - -struct TestPki { - server_cert_der: CertificateDer<'static>, - server_key_der: PrivateKeyDer<'static>, -} - -impl TestPki { - fn new() -> Self { - let alg = &rcgen::PKCS_ECDSA_P256_SHA256; - let mut ca_params = rcgen::CertificateParams::new(Vec::new()).unwrap(); - ca_params - .distinguished_name - .push(rcgen::DnType::OrganizationName, "Provider Server Example"); - ca_params - .distinguished_name - .push(rcgen::DnType::CommonName, "Example CA"); - ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained); - ca_params.key_usages = vec![ - rcgen::KeyUsagePurpose::KeyCertSign, - rcgen::KeyUsagePurpose::DigitalSignature, - ]; - let ca_key = rcgen::KeyPair::generate_for(alg).unwrap(); - let ca_cert = ca_params.self_signed(&ca_key).unwrap(); - - // Create a server end entity cert issued by the CA. - let mut server_ee_params = - rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap(); - server_ee_params.is_ca = rcgen::IsCa::NoCa; - server_ee_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth]; - let server_key = rcgen::KeyPair::generate_for(alg).unwrap(); - let server_cert = server_ee_params - .signed_by(&server_key, &ca_cert, &ca_key) - .unwrap(); - Self { - server_cert_der: server_cert.into(), - // TODO(XXX): update below once https://github.com/rustls/rcgen/issues/260 is resolved. - server_key_der: PrivatePkcs8KeyDer::from(server_key.serialize_der()).into(), - } - } - - fn server_config(self) -> Arc { - let mut server_config = - ServerConfig::builder_with_provider(rustls_provider_example::provider().into()) - .with_safe_default_protocol_versions() - .unwrap() - .with_no_client_auth() - .with_single_cert(vec![self.server_cert_der], self.server_key_der) - .unwrap(); - - server_config.key_log = Arc::new(rustls::KeyLogFile::new()); - - Arc::new(server_config) - } -} diff --git a/provider-example/src/aead.rs b/provider-example/src/aead.rs deleted file mode 100644 index cd7eb23a880..00000000000 --- a/provider-example/src/aead.rs +++ /dev/null @@ -1,231 +0,0 @@ -use alloc::boxed::Box; - -use chacha20poly1305::aead::Buffer; -use chacha20poly1305::{AeadInPlace, KeyInit, KeySizeUser}; -use rustls::crypto::cipher::{ - make_tls12_aad, make_tls13_aad, AeadKey, BorrowedPayload, InboundOpaqueMessage, - InboundPlainMessage, Iv, KeyBlockShape, MessageDecrypter, MessageEncrypter, Nonce, - OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, Tls12AeadAlgorithm, - Tls13AeadAlgorithm, UnsupportedOperationError, NONCE_LEN, -}; -use rustls::{ConnectionTrafficSecrets, ContentType, ProtocolVersion}; - -pub struct Chacha20Poly1305; - -impl Tls13AeadAlgorithm for Chacha20Poly1305 { - fn encrypter(&self, key: AeadKey, iv: Iv) -> Box { - Box::new(Tls13Cipher( - chacha20poly1305::ChaCha20Poly1305::new_from_slice(key.as_ref()).unwrap(), - iv, - )) - } - - fn decrypter(&self, key: AeadKey, iv: Iv) -> Box { - Box::new(Tls13Cipher( - chacha20poly1305::ChaCha20Poly1305::new_from_slice(key.as_ref()).unwrap(), - iv, - )) - } - - fn key_len(&self) -> usize { - chacha20poly1305::ChaCha20Poly1305::key_size() - } - - fn extract_keys( - &self, - key: AeadKey, - iv: Iv, - ) -> Result { - Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }) - } -} - -impl Tls12AeadAlgorithm for Chacha20Poly1305 { - fn encrypter(&self, key: AeadKey, iv: &[u8], _: &[u8]) -> Box { - Box::new(Tls12Cipher( - chacha20poly1305::ChaCha20Poly1305::new_from_slice(key.as_ref()).unwrap(), - Iv::copy(iv), - )) - } - - fn decrypter(&self, key: AeadKey, iv: &[u8]) -> Box { - Box::new(Tls12Cipher( - chacha20poly1305::ChaCha20Poly1305::new_from_slice(key.as_ref()).unwrap(), - Iv::copy(iv), - )) - } - - fn key_block_shape(&self) -> KeyBlockShape { - KeyBlockShape { - enc_key_len: 32, - fixed_iv_len: 12, - explicit_nonce_len: 0, - } - } - - fn extract_keys( - &self, - key: AeadKey, - iv: &[u8], - _explicit: &[u8], - ) -> Result { - // This should always be true because KeyBlockShape and the Iv nonce len are in agreement. - debug_assert_eq!(NONCE_LEN, iv.len()); - Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { - key, - iv: Iv::new(iv[..].try_into().unwrap()), - }) - } -} - -struct Tls13Cipher(chacha20poly1305::ChaCha20Poly1305, Iv); - -impl MessageEncrypter for Tls13Cipher { - fn encrypt( - &mut self, - m: OutboundPlainMessage, - seq: u64, - ) -> Result { - let total_len = self.encrypted_payload_len(m.payload.len()); - let mut payload = PrefixedPayload::with_capacity(total_len); - - payload.extend_from_chunks(&m.payload); - payload.extend_from_slice(&m.typ.to_array()); - let nonce = chacha20poly1305::Nonce::from(Nonce::new(&self.1, seq).0); - let aad = make_tls13_aad(total_len); - - self.0 - .encrypt_in_place(&nonce, &aad, &mut EncryptBufferAdapter(&mut payload)) - .map_err(|_| rustls::Error::EncryptError) - .map(|_| { - OutboundOpaqueMessage::new( - ContentType::ApplicationData, - ProtocolVersion::TLSv1_2, - payload, - ) - }) - } - - fn encrypted_payload_len(&self, payload_len: usize) -> usize { - payload_len + 1 + CHACHAPOLY1305_OVERHEAD - } -} - -impl MessageDecrypter for Tls13Cipher { - fn decrypt<'a>( - &mut self, - mut m: InboundOpaqueMessage<'a>, - seq: u64, - ) -> Result, rustls::Error> { - let payload = &mut m.payload; - let nonce = chacha20poly1305::Nonce::from(Nonce::new(&self.1, seq).0); - let aad = make_tls13_aad(payload.len()); - - self.0 - .decrypt_in_place(&nonce, &aad, &mut DecryptBufferAdapter(payload)) - .map_err(|_| rustls::Error::DecryptError)?; - - m.into_tls13_unpadded_message() - } -} - -struct Tls12Cipher(chacha20poly1305::ChaCha20Poly1305, Iv); - -impl MessageEncrypter for Tls12Cipher { - fn encrypt( - &mut self, - m: OutboundPlainMessage, - seq: u64, - ) -> Result { - let total_len = self.encrypted_payload_len(m.payload.len()); - let mut payload = PrefixedPayload::with_capacity(total_len); - - payload.extend_from_chunks(&m.payload); - let nonce = chacha20poly1305::Nonce::from(Nonce::new(&self.1, seq).0); - let aad = make_tls12_aad(seq, m.typ, m.version, m.payload.len()); - - self.0 - .encrypt_in_place(&nonce, &aad, &mut EncryptBufferAdapter(&mut payload)) - .map_err(|_| rustls::Error::EncryptError) - .map(|_| OutboundOpaqueMessage::new(m.typ, m.version, payload)) - } - - fn encrypted_payload_len(&self, payload_len: usize) -> usize { - payload_len + CHACHAPOLY1305_OVERHEAD - } -} - -impl MessageDecrypter for Tls12Cipher { - fn decrypt<'a>( - &mut self, - mut m: InboundOpaqueMessage<'a>, - seq: u64, - ) -> Result, rustls::Error> { - let payload = &m.payload; - let nonce = chacha20poly1305::Nonce::from(Nonce::new(&self.1, seq).0); - let aad = make_tls12_aad( - seq, - m.typ, - m.version, - payload.len() - CHACHAPOLY1305_OVERHEAD, - ); - - let payload = &mut m.payload; - self.0 - .decrypt_in_place(&nonce, &aad, &mut DecryptBufferAdapter(payload)) - .map_err(|_| rustls::Error::DecryptError)?; - - Ok(m.into_plain_message()) - } -} - -const CHACHAPOLY1305_OVERHEAD: usize = 16; - -struct EncryptBufferAdapter<'a>(&'a mut PrefixedPayload); - -impl AsRef<[u8]> for EncryptBufferAdapter<'_> { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl AsMut<[u8]> for EncryptBufferAdapter<'_> { - fn as_mut(&mut self) -> &mut [u8] { - self.0.as_mut() - } -} - -impl Buffer for EncryptBufferAdapter<'_> { - fn extend_from_slice(&mut self, other: &[u8]) -> chacha20poly1305::aead::Result<()> { - self.0.extend_from_slice(other); - Ok(()) - } - - fn truncate(&mut self, len: usize) { - self.0.truncate(len) - } -} - -struct DecryptBufferAdapter<'a, 'p>(&'a mut BorrowedPayload<'p>); - -impl AsRef<[u8]> for DecryptBufferAdapter<'_, '_> { - fn as_ref(&self) -> &[u8] { - self.0 - } -} - -impl AsMut<[u8]> for DecryptBufferAdapter<'_, '_> { - fn as_mut(&mut self) -> &mut [u8] { - self.0 - } -} - -impl Buffer for DecryptBufferAdapter<'_, '_> { - fn extend_from_slice(&mut self, _: &[u8]) -> chacha20poly1305::aead::Result<()> { - unreachable!("not used by `AeadInPlace::decrypt_in_place`") - } - - fn truncate(&mut self, len: usize) { - self.0.truncate(len) - } -} diff --git a/provider-example/src/hash.rs b/provider-example/src/hash.rs deleted file mode 100644 index 4f28b0f2e41..00000000000 --- a/provider-example/src/hash.rs +++ /dev/null @@ -1,44 +0,0 @@ -use alloc::boxed::Box; - -use rustls::crypto::hash; -use sha2::Digest; - -pub struct Sha256; - -impl hash::Hash for Sha256 { - fn start(&self) -> Box { - Box::new(Sha256Context(sha2::Sha256::new())) - } - - fn hash(&self, data: &[u8]) -> hash::Output { - hash::Output::new(&sha2::Sha256::digest(data)[..]) - } - - fn algorithm(&self) -> hash::HashAlgorithm { - hash::HashAlgorithm::SHA256 - } - - fn output_len(&self) -> usize { - 32 - } -} - -struct Sha256Context(sha2::Sha256); - -impl hash::Context for Sha256Context { - fn fork_finish(&self) -> hash::Output { - hash::Output::new(&self.0.clone().finalize()[..]) - } - - fn fork(&self) -> Box { - Box::new(Sha256Context(self.0.clone())) - } - - fn finish(self: Box) -> hash::Output { - hash::Output::new(&self.0.finalize()[..]) - } - - fn update(&mut self, data: &[u8]) { - self.0.update(data); - } -} diff --git a/provider-example/src/hmac.rs b/provider-example/src/hmac.rs deleted file mode 100644 index 763dd71e092..00000000000 --- a/provider-example/src/hmac.rs +++ /dev/null @@ -1,35 +0,0 @@ -use alloc::boxed::Box; - -use hmac::{Hmac, Mac}; -use rustls::crypto; -use sha2::{Digest, Sha256}; - -pub struct Sha256Hmac; - -impl crypto::hmac::Hmac for Sha256Hmac { - fn with_key(&self, key: &[u8]) -> Box { - Box::new(Sha256HmacKey(Hmac::::new_from_slice(key).unwrap())) - } - - fn hash_output_len(&self) -> usize { - Sha256::output_size() - } -} - -struct Sha256HmacKey(Hmac); - -impl crypto::hmac::Key for Sha256HmacKey { - fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> crypto::hmac::Tag { - let mut ctx = self.0.clone(); - ctx.update(first); - for m in middle { - ctx.update(m); - } - ctx.update(last); - crypto::hmac::Tag::new(&ctx.finalize().into_bytes()[..]) - } - - fn tag_len(&self) -> usize { - Sha256::output_size() - } -} diff --git a/provider-example/src/hpke.rs b/provider-example/src/hpke.rs deleted file mode 100644 index 890c2d57a22..00000000000 --- a/provider-example/src/hpke.rs +++ /dev/null @@ -1,294 +0,0 @@ -use alloc::boxed::Box; -use alloc::vec::Vec; -use core::fmt::Debug; - -use hpke_rs_crypto::types::{AeadAlgorithm, KdfAlgorithm, KemAlgorithm}; -use hpke_rs_crypto::HpkeCrypto; -use hpke_rs_rust_crypto::HpkeRustCrypto; -use rustls::crypto::hpke::{ - EncapsulatedSecret, Hpke, HpkeOpener, HpkePrivateKey, HpkePublicKey, HpkeSealer, HpkeSuite, -}; -use rustls::internal::msgs::enums::{ - HpkeAead as HpkeAeadId, HpkeKdf as HpkeKdfId, HpkeKem as HpkeKemId, HpkeKem, -}; -use rustls::internal::msgs::handshake::HpkeSymmetricCipherSuite; -use rustls::{Error, OtherError}; - -/// All supported HPKE suites. -/// -/// Note: hpke-rs w/ rust-crypto does not support P-384 and P-521 DH KEMs. -pub static ALL_SUPPORTED_SUITES: &[&dyn Hpke] = &[ - DHKEM_P256_HKDF_SHA256_AES_128, - DHKEM_P256_HKDF_SHA256_AES_256, - DHKEM_P256_HKDF_SHA256_CHACHA20_POLY1305, - DHKEM_X25519_HKDF_SHA256_AES_128, - DHKEM_X25519_HKDF_SHA256_AES_256, - DHKEM_X25519_HKDF_SHA256_CHACHA20_POLY1305, -]; - -pub static DHKEM_P256_HKDF_SHA256_AES_128: &HpkeRs = &HpkeRs(HpkeSuite { - kem: HpkeKemId::DHKEM_P256_HKDF_SHA256, - sym: HpkeSymmetricCipherSuite { - kdf_id: HpkeKdfId::HKDF_SHA256, - aead_id: HpkeAeadId::AES_128_GCM, - }, -}); - -pub static DHKEM_P256_HKDF_SHA256_AES_256: &HpkeRs = &HpkeRs(HpkeSuite { - kem: HpkeKemId::DHKEM_P256_HKDF_SHA256, - sym: HpkeSymmetricCipherSuite { - kdf_id: HpkeKdfId::HKDF_SHA256, - aead_id: HpkeAeadId::AES_256_GCM, - }, -}); - -pub static DHKEM_P256_HKDF_SHA256_CHACHA20_POLY1305: &HpkeRs = &HpkeRs(HpkeSuite { - kem: HpkeKemId::DHKEM_P256_HKDF_SHA256, - sym: HpkeSymmetricCipherSuite { - kdf_id: HpkeKdfId::HKDF_SHA256, - aead_id: HpkeAeadId::CHACHA20_POLY_1305, - }, -}); - -pub static DHKEM_X25519_HKDF_SHA256_AES_128: &HpkeRs = &HpkeRs(HpkeSuite { - kem: HpkeKemId::DHKEM_X25519_HKDF_SHA256, - sym: HpkeSymmetricCipherSuite { - kdf_id: HpkeKdfId::HKDF_SHA256, - aead_id: HpkeAeadId::AES_128_GCM, - }, -}); - -pub static DHKEM_X25519_HKDF_SHA256_AES_256: &HpkeRs = &HpkeRs(HpkeSuite { - kem: HpkeKemId::DHKEM_X25519_HKDF_SHA256, - sym: HpkeSymmetricCipherSuite { - kdf_id: HpkeKdfId::HKDF_SHA256, - aead_id: HpkeAeadId::AES_256_GCM, - }, -}); - -pub static DHKEM_X25519_HKDF_SHA256_CHACHA20_POLY1305: &HpkeRs = &HpkeRs(HpkeSuite { - kem: HpkeKemId::DHKEM_X25519_HKDF_SHA256, - sym: HpkeSymmetricCipherSuite { - kdf_id: HpkeKdfId::HKDF_SHA256, - aead_id: HpkeAeadId::CHACHA20_POLY_1305, - }, -}); - -/// A HPKE suite backed by the [hpke-rs] crate and its rust-crypto cryptography provider. -/// -/// [hpke-rs]: https://github.com/franziskuskiefer/hpke-rs -#[derive(Debug)] -pub struct HpkeRs(HpkeSuite); - -impl HpkeRs { - fn start(&self) -> Result, Error> { - Ok(hpke_rs::Hpke::new( - hpke_rs::Mode::Base, - KemAlgorithm::try_from(u16::from(self.0.kem)).map_err(other_err)?, - KdfAlgorithm::try_from(u16::from(self.0.sym.kdf_id)).map_err(other_err)?, - AeadAlgorithm::try_from(u16::from(self.0.sym.aead_id)).map_err(other_err)?, - )) - } -} - -impl Hpke for HpkeRs { - fn seal( - &self, - info: &[u8], - aad: &[u8], - plaintext: &[u8], - pub_key: &HpkePublicKey, - ) -> Result<(EncapsulatedSecret, Vec), Error> { - let pk_r = hpke_rs::HpkePublicKey::new(pub_key.0.clone()); - let (enc, ciphertext) = self - .start()? - .seal(&pk_r, info, aad, plaintext, None, None, None) - .map_err(other_err)?; - Ok((EncapsulatedSecret(enc.to_vec()), ciphertext)) - } - - fn setup_sealer( - &self, - info: &[u8], - pub_key: &HpkePublicKey, - ) -> Result<(EncapsulatedSecret, Box), Error> { - let pk_r = hpke_rs::HpkePublicKey::new(pub_key.0.clone()); - let (enc, context) = self - .start()? - .setup_sender(&pk_r, info, None, None, None) - .map_err(other_err)?; - Ok(( - EncapsulatedSecret(enc.to_vec()), - Box::new(HpkeRsSender { context }), - )) - } - - fn open( - &self, - enc: &EncapsulatedSecret, - info: &[u8], - aad: &[u8], - ciphertext: &[u8], - secret_key: &HpkePrivateKey, - ) -> Result, Error> { - let sk_r = hpke_rs::HpkePrivateKey::new(secret_key.secret_bytes().to_vec()); - self.start()? - .open( - enc.0.as_slice(), - &sk_r, - info, - aad, - ciphertext, - None, - None, - None, - ) - .map_err(other_err) - } - - fn setup_opener( - &self, - enc: &EncapsulatedSecret, - info: &[u8], - secret_key: &HpkePrivateKey, - ) -> Result, Error> { - let sk_r = hpke_rs::HpkePrivateKey::new(secret_key.secret_bytes().to_vec()); - Ok(Box::new(HpkeRsReceiver { - context: self - .start()? - .setup_receiver(enc.0.as_slice(), &sk_r, info, None, None, None) - .map_err(other_err)?, - })) - } - - fn generate_key_pair(&self) -> Result<(HpkePublicKey, HpkePrivateKey), Error> { - let kem_algorithm = match self.0.kem { - HpkeKem::DHKEM_P256_HKDF_SHA256 => KemAlgorithm::DhKemP256, - HpkeKem::DHKEM_X25519_HKDF_SHA256 => KemAlgorithm::DhKem25519, - _ => { - // Safety: we don't expose HpkeRs static instances for unsupported algorithms. - unimplemented!() - } - }; - - let secret_key = HpkeRustCrypto::kem_key_gen(kem_algorithm, &mut HpkeRustCrypto::prng()) - .map_err(other_err)?; - let public_key = HpkePublicKey( - HpkeRustCrypto::kem_derive_base(kem_algorithm, &secret_key).map_err(other_err)?, - ); - - Ok((public_key, HpkePrivateKey::from(secret_key))) - } - - fn suite(&self) -> HpkeSuite { - self.0 - } -} - -#[derive(Debug)] -struct HpkeRsSender { - context: hpke_rs::Context, -} - -impl HpkeSealer for HpkeRsSender { - fn seal(&mut self, aad: &[u8], plaintext: &[u8]) -> Result, Error> { - self.context - .seal(aad, plaintext) - .map_err(other_err) - } -} - -#[derive(Debug)] -struct HpkeRsReceiver { - context: hpke_rs::Context, -} - -impl HpkeOpener for HpkeRsReceiver { - fn open(&mut self, aad: &[u8], ciphertext: &[u8]) -> Result, Error> { - self.context - .open(aad, ciphertext) - .map_err(other_err) - } -} - -#[cfg(feature = "std")] -fn other_err(err: impl std::error::Error + Send + Sync + 'static) -> Error { - Error::Other(OtherError(alloc::sync::Arc::new(err))) -} - -#[cfg(not(feature = "std"))] -fn other_err(_err: impl core::any::Any) -> Error { - Error::Other(OtherError()) -} - -#[cfg(test)] -mod tests { - use alloc::{format, vec}; - - use super::*; - - #[test] - fn smoke_test() { - for suite in ALL_SUPPORTED_SUITES { - _ = format!("{suite:?}"); // HpkeRs suites should be Debug. - - // We should be able to generate a random keypair. - let (pk, sk) = suite.generate_key_pair().unwrap(); - - // Info value corresponds to the first RFC 9180 base mode test vector. - let info = &[ - 0x4f, 0x64, 0x65, 0x20, 0x6f, 0x6e, 0x20, 0x61, 0x20, 0x47, 0x72, 0x65, 0x63, 0x69, - 0x61, 0x6e, 0x20, 0x55, 0x72, 0x6e, - ][..]; - - // We should be able to set up a sealer. - let (enc, mut sealer) = suite.setup_sealer(info, &pk).unwrap(); - - _ = format!("{sealer:?}"); // Sealer should be Debug. - - // Setting up a sealer with an invalid public key should fail. - let bad_setup_res = suite.setup_sealer(info, &HpkePublicKey(vec![])); - assert!(matches!(bad_setup_res.unwrap_err(), Error::Other(_))); - - // We should be able to seal some plaintext. - let aad = &[0xC0, 0xFF, 0xEE]; - let pt = &[0xF0, 0x0D]; - let ct = sealer.seal(aad, pt).unwrap(); - - // We should be able to set up an opener. - let mut opener = suite - .setup_opener(&enc, info, &sk) - .unwrap(); - _ = format!("{opener:?}"); // Opener should be Debug. - - // Setting up an opener with an invalid private key should fail. - let bad_key_res = suite.setup_opener(&enc, info, &HpkePrivateKey::from(vec![])); - assert!(matches!(bad_key_res.unwrap_err(), Error::Other(_))); - - // Opening the plaintext should work with the correct opener and aad. - let pt_prime = opener.open(aad, &ct).unwrap(); - assert_eq!(pt_prime, pt); - - // Opening the plaintext with the correct opener and wrong aad should fail. - let open_res = opener.open(&[0x0], &ct); - assert!(matches!(open_res.unwrap_err(), Error::Other(_))); - - // Opening the plaintext with the wrong opener should fail. - let mut sk_rm_prime = sk.secret_bytes().to_vec(); - sk_rm_prime[10] ^= 0xFF; // Corrupt a byte of the private key. - let mut opener_two = suite - .setup_opener(&enc, info, &HpkePrivateKey::from(sk_rm_prime)) - .unwrap(); - let open_res = opener_two.open(aad, &ct); - assert!(matches!(open_res.unwrap_err(), Error::Other(_))); - } - } - - #[test] - fn test_fips() { - // None of the rust-crypto backed hpke-rs suites should be considered FIPS approved. - assert!(ALL_SUPPORTED_SUITES - .iter() - .all(|suite| !suite.fips())); - } -} diff --git a/provider-example/src/kx.rs b/provider-example/src/kx.rs deleted file mode 100644 index 80fb235bdcd..00000000000 --- a/provider-example/src/kx.rs +++ /dev/null @@ -1,59 +0,0 @@ -use alloc::boxed::Box; - -use crypto::SupportedKxGroup; -use rustls::crypto; -use rustls::ffdhe_groups::FfdheGroup; - -pub struct KeyExchange { - priv_key: x25519_dalek::EphemeralSecret, - pub_key: x25519_dalek::PublicKey, -} - -impl crypto::ActiveKeyExchange for KeyExchange { - fn complete( - self: Box, - peer: &[u8], - ) -> Result { - let peer_array: [u8; 32] = peer - .try_into() - .map_err(|_| rustls::Error::from(rustls::PeerMisbehaved::InvalidKeyShare))?; - let their_pub = x25519_dalek::PublicKey::from(peer_array); - let shared_secret = self.priv_key.diffie_hellman(&their_pub); - Ok(crypto::SharedSecret::from(&shared_secret.as_bytes()[..])) - } - - fn pub_key(&self) -> &[u8] { - self.pub_key.as_bytes() - } - - fn ffdhe_group(&self) -> Option> { - None - } - - fn group(&self) -> rustls::NamedGroup { - X25519.name() - } -} - -pub const ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[&X25519 as &dyn SupportedKxGroup]; - -#[derive(Debug)] -pub struct X25519; - -impl crypto::SupportedKxGroup for X25519 { - fn start(&self) -> Result, rustls::Error> { - let priv_key = x25519_dalek::EphemeralSecret::random_from_rng(rand_core::OsRng); - Ok(Box::new(KeyExchange { - pub_key: (&priv_key).into(), - priv_key, - })) - } - - fn ffdhe_group(&self) -> Option> { - None - } - - fn name(&self) -> rustls::NamedGroup { - rustls::NamedGroup::X25519 - } -} diff --git a/provider-example/src/lib.rs b/provider-example/src/lib.rs deleted file mode 100644 index e8daaa19a56..00000000000 --- a/provider-example/src/lib.rs +++ /dev/null @@ -1,90 +0,0 @@ -#![no_std] - -extern crate alloc; -#[cfg(feature = "std")] -extern crate std; - -use alloc::sync::Arc; - -use rustls::crypto::CryptoProvider; -use rustls::pki_types::PrivateKeyDer; - -mod aead; -mod hash; -mod hmac; -pub mod hpke; -mod kx; -mod sign; -mod verify; - -pub fn provider() -> CryptoProvider { - CryptoProvider { - cipher_suites: ALL_CIPHER_SUITES.to_vec(), - kx_groups: kx::ALL_KX_GROUPS.to_vec(), - signature_verification_algorithms: verify::ALGORITHMS, - secure_random: &Provider, - key_provider: &Provider, - } -} - -#[derive(Debug)] -struct Provider; - -impl rustls::crypto::SecureRandom for Provider { - fn fill(&self, bytes: &mut [u8]) -> Result<(), rustls::crypto::GetRandomFailed> { - use rand_core::RngCore; - rand_core::OsRng - .try_fill_bytes(bytes) - .map_err(|_| rustls::crypto::GetRandomFailed) - } -} - -impl rustls::crypto::KeyProvider for Provider { - fn load_private_key( - &self, - key_der: PrivateKeyDer<'static>, - ) -> Result, rustls::Error> { - Ok(Arc::new( - sign::EcdsaSigningKeyP256::try_from(key_der).map_err(|err| { - #[cfg(feature = "std")] - let err = rustls::OtherError(Arc::new(err)); - #[cfg(not(feature = "std"))] - let err = rustls::Error::General(alloc::format!("{}", err)); - err - })?, - )) - } -} - -static ALL_CIPHER_SUITES: &[rustls::SupportedCipherSuite] = &[ - TLS13_CHACHA20_POLY1305_SHA256, - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, -]; - -pub static TLS13_CHACHA20_POLY1305_SHA256: rustls::SupportedCipherSuite = - rustls::SupportedCipherSuite::Tls13(&rustls::Tls13CipherSuite { - common: rustls::crypto::CipherSuiteCommon { - suite: rustls::CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, - hash_provider: &hash::Sha256, - confidentiality_limit: u64::MAX, - }, - hkdf_provider: &rustls::crypto::tls13::HkdfUsingHmac(&hmac::Sha256Hmac), - aead_alg: &aead::Chacha20Poly1305, - quic: None, - }); - -pub static TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: rustls::SupportedCipherSuite = - rustls::SupportedCipherSuite::Tls12(&rustls::Tls12CipherSuite { - common: rustls::crypto::CipherSuiteCommon { - suite: rustls::CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - hash_provider: &hash::Sha256, - confidentiality_limit: u64::MAX, - }, - kx: rustls::crypto::KeyExchangeAlgorithm::ECDHE, - sign: &[ - rustls::SignatureScheme::RSA_PSS_SHA256, - rustls::SignatureScheme::RSA_PKCS1_SHA256, - ], - prf_provider: &rustls::crypto::tls12::PrfUsingHmac(&hmac::Sha256Hmac), - aead_alg: &aead::Chacha20Poly1305, - }); diff --git a/provider-example/src/sign.rs b/provider-example/src/sign.rs deleted file mode 100644 index 36f7beae815..00000000000 --- a/provider-example/src/sign.rs +++ /dev/null @@ -1,58 +0,0 @@ -use alloc::boxed::Box; -use alloc::sync::Arc; -use alloc::vec::Vec; - -use pkcs8::DecodePrivateKey; -use rustls::pki_types::PrivateKeyDer; -use rustls::sign::{Signer, SigningKey}; -use rustls::{SignatureAlgorithm, SignatureScheme}; -use signature::{RandomizedSigner, SignatureEncoding}; - -#[derive(Clone, Debug)] -pub struct EcdsaSigningKeyP256 { - key: Arc, - scheme: SignatureScheme, -} - -impl TryFrom> for EcdsaSigningKeyP256 { - type Error = pkcs8::Error; - - fn try_from(value: PrivateKeyDer<'_>) -> Result { - match value { - PrivateKeyDer::Pkcs8(der) => { - p256::ecdsa::SigningKey::from_pkcs8_der(der.secret_pkcs8_der()).map(|kp| Self { - key: Arc::new(kp), - scheme: SignatureScheme::ECDSA_NISTP256_SHA256, - }) - } - _ => panic!("unsupported private key format"), - } - } -} - -impl SigningKey for EcdsaSigningKeyP256 { - fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { - if offered.contains(&self.scheme) { - Some(Box::new(self.clone())) - } else { - None - } - } - - fn algorithm(&self) -> SignatureAlgorithm { - SignatureAlgorithm::ECDSA - } -} - -impl Signer for EcdsaSigningKeyP256 { - fn sign(&self, message: &[u8]) -> Result, rustls::Error> { - self.key - .try_sign_with_rng(&mut rand_core::OsRng, message) - .map_err(|_| rustls::Error::General("signing failed".into())) - .map(|sig: p256::ecdsa::DerSignature| sig.to_vec()) - } - - fn scheme(&self) -> SignatureScheme { - self.scheme - } -} diff --git a/provider-example/src/verify.rs b/provider-example/src/verify.rs deleted file mode 100644 index dbd6f73ddd8..00000000000 --- a/provider-example/src/verify.rs +++ /dev/null @@ -1,89 +0,0 @@ -use der::Reader; -use rsa::signature::Verifier; -use rsa::{pkcs1v15, pss, BigUint, RsaPublicKey}; -use rustls::crypto::WebPkiSupportedAlgorithms; -use rustls::pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm}; -use rustls::SignatureScheme; -use webpki::alg_id; - -pub static ALGORITHMS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms { - all: &[RSA_PSS_SHA256, RSA_PKCS1_SHA256], - mapping: &[ - (SignatureScheme::RSA_PSS_SHA256, &[RSA_PSS_SHA256]), - (SignatureScheme::RSA_PKCS1_SHA256, &[RSA_PKCS1_SHA256]), - ], -}; - -static RSA_PSS_SHA256: &dyn SignatureVerificationAlgorithm = &RsaPssSha256Verify; -static RSA_PKCS1_SHA256: &dyn SignatureVerificationAlgorithm = &RsaPkcs1Sha256Verify; - -#[derive(Debug)] -struct RsaPssSha256Verify; - -impl SignatureVerificationAlgorithm for RsaPssSha256Verify { - fn public_key_alg_id(&self) -> AlgorithmIdentifier { - alg_id::RSA_ENCRYPTION - } - - fn signature_alg_id(&self) -> AlgorithmIdentifier { - alg_id::RSA_PSS_SHA256 - } - - fn verify_signature( - &self, - public_key: &[u8], - message: &[u8], - signature: &[u8], - ) -> Result<(), InvalidSignature> { - let public_key = decode_spki_spk(public_key)?; - - let signature = pss::Signature::try_from(signature).map_err(|_| InvalidSignature)?; - - pss::VerifyingKey::::new(public_key) - .verify(message, &signature) - .map_err(|_| InvalidSignature) - } -} - -#[derive(Debug)] -struct RsaPkcs1Sha256Verify; - -impl SignatureVerificationAlgorithm for RsaPkcs1Sha256Verify { - fn public_key_alg_id(&self) -> AlgorithmIdentifier { - alg_id::RSA_ENCRYPTION - } - - fn signature_alg_id(&self) -> AlgorithmIdentifier { - alg_id::RSA_PKCS1_SHA256 - } - - fn verify_signature( - &self, - public_key: &[u8], - message: &[u8], - signature: &[u8], - ) -> Result<(), InvalidSignature> { - let public_key = decode_spki_spk(public_key)?; - - let signature = pkcs1v15::Signature::try_from(signature).map_err(|_| InvalidSignature)?; - - pkcs1v15::VerifyingKey::::new(public_key) - .verify(message, &signature) - .map_err(|_| InvalidSignature) - } -} - -fn decode_spki_spk(spki_spk: &[u8]) -> Result { - // public_key: unfortunately this is not a whole SPKI, but just the key material. - // decode the two integers manually. - let mut reader = der::SliceReader::new(spki_spk).map_err(|_| InvalidSignature)?; - let ne: [der::asn1::UintRef; 2] = reader - .decode() - .map_err(|_| InvalidSignature)?; - - RsaPublicKey::new( - BigUint::from_bytes_be(ne[0].as_bytes()), - BigUint::from_bytes_be(ne[1].as_bytes()), - ) - .map_err(|_| InvalidSignature) -} diff --git a/rustls-aws-lc-rs/Cargo.toml b/rustls-aws-lc-rs/Cargo.toml new file mode 100644 index 00000000000..3fa5582f84a --- /dev/null +++ b/rustls-aws-lc-rs/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "rustls-aws-lc-rs" +version = "0.1.0-dev.0" +edition = "2021" +rust-version = "1.85" +license = "Apache-2.0 OR ISC OR MIT" +description = "An aws-lc-based crypto provider for rustls." +homepage = "https://github.com/rustls/rustls" +repository = "https://github.com/rustls/rustls" +categories = ["network-programming", "cryptography"] +autobenches = false + +[features] +default = ["aws-lc-sys", "std"] +aws-lc-sys = ["aws-lc-rs/aws-lc-sys"] +fips = ["aws-lc-rs/fips"] +std = [] + +[dependencies] +aws-lc-rs = { workspace = true, features = ["prebuilt-nasm"] } +pki-types = { workspace = true } +rustls = { path = "../rustls", version = "0.24.0-dev.0", default-features = false } +subtle = { workspace = true } +zeroize = { workspace = true } + +[dev-dependencies] +bencher = { workspace = true } +hex = { workspace = true } +rustls-test = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } + +[lints] +workspace = true diff --git a/rustls-aws-lc-rs/src/data/alg-rsa-pkcs1-sha256-absent-params.der b/rustls-aws-lc-rs/src/data/alg-rsa-pkcs1-sha256-absent-params.der new file mode 100644 index 00000000000..a934df82f92 --- /dev/null +++ b/rustls-aws-lc-rs/src/data/alg-rsa-pkcs1-sha256-absent-params.der @@ -0,0 +1 @@ + *†H†÷  \ No newline at end of file diff --git a/rustls-aws-lc-rs/src/data/alg-rsa-pkcs1-sha384-absent-params.der b/rustls-aws-lc-rs/src/data/alg-rsa-pkcs1-sha384-absent-params.der new file mode 100644 index 00000000000..03aa775d69c --- /dev/null +++ b/rustls-aws-lc-rs/src/data/alg-rsa-pkcs1-sha384-absent-params.der @@ -0,0 +1 @@ + *†H†÷  \ No newline at end of file diff --git a/rustls-aws-lc-rs/src/data/alg-rsa-pkcs1-sha512-absent-params.der b/rustls-aws-lc-rs/src/data/alg-rsa-pkcs1-sha512-absent-params.der new file mode 100644 index 00000000000..e59473c5701 --- /dev/null +++ b/rustls-aws-lc-rs/src/data/alg-rsa-pkcs1-sha512-absent-params.der @@ -0,0 +1 @@ + *†H†÷  \ No newline at end of file diff --git a/rustls/src/crypto/ring/hash.rs b/rustls-aws-lc-rs/src/hash.rs similarity index 89% rename from rustls/src/crypto/ring/hash.rs rename to rustls-aws-lc-rs/src/hash.rs index 220dc536395..a8c262f34fa 100644 --- a/rustls/src/crypto/ring/hash.rs +++ b/rustls-aws-lc-rs/src/hash.rs @@ -1,10 +1,8 @@ -#![allow(clippy::duplicate_mod)] - use alloc::boxed::Box; -use super::ring_like::digest; -use crate::crypto; -use crate::msgs::enums::HashAlgorithm; +use aws_lc_rs::digest; +use pki_types::FipsStatus; +use rustls::crypto::{self, HashAlgorithm}; pub(crate) static SHA256: Hash = Hash(&digest::SHA256, HashAlgorithm::SHA256); pub(crate) static SHA384: Hash = Hash(&digest::SHA384, HashAlgorithm::SHA384); @@ -30,7 +28,7 @@ impl crypto::hash::Hash for Hash { self.1 } - fn fips(&self) -> bool { + fn fips(&self) -> FipsStatus { super::fips() } } diff --git a/rustls/src/crypto/ring/hmac.rs b/rustls-aws-lc-rs/src/hmac.rs similarity index 58% rename from rustls/src/crypto/ring/hmac.rs rename to rustls-aws-lc-rs/src/hmac.rs index 7f30aba481e..4da70ef7430 100644 --- a/rustls/src/crypto/ring/hmac.rs +++ b/rustls-aws-lc-rs/src/hmac.rs @@ -1,36 +1,35 @@ -#![allow(clippy::duplicate_mod)] - use alloc::boxed::Box; -use super::ring_like; -use crate::crypto; +use aws_lc_rs::hmac; +use pki_types::FipsStatus; +use rustls::crypto; -pub(crate) static HMAC_SHA256: Hmac = Hmac(&ring_like::hmac::HMAC_SHA256); -pub(crate) static HMAC_SHA384: Hmac = Hmac(&ring_like::hmac::HMAC_SHA384); +pub(crate) static HMAC_SHA256: Hmac = Hmac(&hmac::HMAC_SHA256); +pub(crate) static HMAC_SHA384: Hmac = Hmac(&hmac::HMAC_SHA384); #[allow(dead_code)] // Only used for TLS 1.2 prf test, and aws-lc-rs HPKE suites. -pub(crate) static HMAC_SHA512: Hmac = Hmac(&ring_like::hmac::HMAC_SHA512); +pub(crate) static HMAC_SHA512: Hmac = Hmac(&hmac::HMAC_SHA512); -pub(crate) struct Hmac(&'static ring_like::hmac::Algorithm); +pub(crate) struct Hmac(&'static hmac::Algorithm); impl crypto::hmac::Hmac for Hmac { fn with_key(&self, key: &[u8]) -> Box { - Box::new(Key(ring_like::hmac::Key::new(*self.0, key))) + Box::new(Key(hmac::Key::new(*self.0, key))) } fn hash_output_len(&self) -> usize { self.0.digest_algorithm().output_len() } - fn fips(&self) -> bool { + fn fips(&self) -> FipsStatus { super::fips() } } -struct Key(ring_like::hmac::Key); +struct Key(hmac::Key); impl crypto::hmac::Key for Key { fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> crypto::hmac::Tag { - let mut ctx = ring_like::hmac::Context::with_key(&self.0); + let mut ctx = hmac::Context::with_key(&self.0); ctx.update(first); for d in middle { ctx.update(d); diff --git a/rustls/src/crypto/aws_lc_rs/hpke.rs b/rustls-aws-lc-rs/src/hpke.rs similarity index 96% rename from rustls/src/crypto/aws_lc_rs/hpke.rs rename to rustls-aws-lc-rs/src/hpke.rs index 80514de19c3..5c6a43e8dce 100644 --- a/rustls/src/crypto/aws_lc_rs/hpke.rs +++ b/rustls-aws-lc-rs/src/hpke.rs @@ -1,27 +1,25 @@ use alloc::boxed::Box; -#[cfg(feature = "std")] -use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt::{self, Debug, Formatter}; use aws_lc_rs::aead::{ - self, Aad, BoundKey, Nonce, NonceSequence, OpeningKey, SealingKey, UnboundKey, NONCE_LEN, + self, Aad, BoundKey, NONCE_LEN, Nonce, NonceSequence, OpeningKey, SealingKey, UnboundKey, }; use aws_lc_rs::agreement; use aws_lc_rs::cipher::{AES_128_KEY_LEN, AES_256_KEY_LEN}; use aws_lc_rs::digest::{SHA256_OUTPUT_LEN, SHA384_OUTPUT_LEN, SHA512_OUTPUT_LEN}; use aws_lc_rs::encoding::{AsBigEndian, Curve25519SeedBin, EcPrivateKeyBin}; +use pki_types::FipsStatus; +use rustls::crypto::hpke::{ + EncapsulatedSecret, Hpke, HpkeAead, HpkeKdf, HpkeKem, HpkeOpener, HpkePrivateKey, + HpkePublicKey, HpkeSealer, HpkeSuite, HpkeSymmetricCipherSuite, +}; +use rustls::crypto::tls13::{HkdfExpander, HkdfPrkExtract, HkdfUsingHmac, expand}; +use rustls::error::{Error, OtherError}; use zeroize::Zeroize; -use crate::crypto::aws_lc_rs::hmac::{HMAC_SHA256, HMAC_SHA384, HMAC_SHA512}; -use crate::crypto::aws_lc_rs::unspecified_err; -use crate::crypto::hpke::{ - EncapsulatedSecret, Hpke, HpkeOpener, HpkePrivateKey, HpkePublicKey, HpkeSealer, HpkeSuite, -}; -use crate::crypto::tls13::{expand, HkdfExpander, HkdfPrkExtract, HkdfUsingHmac}; -use crate::msgs::enums::{HpkeAead, HpkeKdf, HpkeKem}; -use crate::msgs::handshake::HpkeSymmetricCipherSuite; -use crate::{Error, OtherError}; +use crate::hmac::{HMAC_SHA256, HMAC_SHA384, HMAC_SHA512}; +use crate::unspecified_err; /// Default [RFC 9180] Hybrid Public Key Encryption (HPKE) suites supported by aws-lc-rs cryptography. pub static ALL_SUPPORTED_SUITES: &[&dyn Hpke] = &[ @@ -359,8 +357,8 @@ impl Hpke for HpkeAwsLcRs bool { - matches!( + fn fips(&self) -> FipsStatus { + let allowed = matches!( // We make a FIPS determination based on the suite's DH KEM and AEAD choice. // We don't need to examine the KDF choice because all supported KDFs are FIPS // compatible. @@ -373,7 +371,12 @@ impl Hpke for HpkeAwsLcRs super::fips(), + false => FipsStatus::Unvalidated, + } } fn generate_key_pair(&self) -> Result<(HpkePublicKey, HpkePrivateKey), Error> { @@ -454,7 +457,8 @@ impl HpkeSealer for Sealer Debug for Sealer { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("Sealer").finish() + f.debug_struct("Sealer") + .finish_non_exhaustive() } } @@ -507,7 +511,8 @@ impl HpkeOpener for Opener Debug for Opener { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("Opener").finish() + f.debug_struct("Opener") + .finish_non_exhaustive() } } @@ -576,7 +581,7 @@ impl DhKem { let pk_r = agreement::UnparsedPublicKey::new(self.agreement_algorithm, &recipient.0); let kem_context = [enc.as_ref(), pk_r.bytes()].concat(); - let shared_secret = agreement::agree(&sk_e, &pk_r, aws_lc_rs::error::Unspecified, |dh| { + let shared_secret = agreement::agree(&sk_e, pk_r, aws_lc_rs::error::Unspecified, |dh| { Ok(self.extract_and_expand(dh, &kem_context)) }) .map_err(unspecified_err)?; @@ -616,7 +621,7 @@ impl DhKem { .map_err(unspecified_err)?; let kem_context = [&enc.0, pk_rm.as_ref()].concat(); - let shared_secret = agreement::agree(&sk_r, &pk_e, aws_lc_rs::error::Unspecified, |dh| { + let shared_secret = agreement::agree(&sk_r, pk_e, aws_lc_rs::error::Unspecified, |dh| { Ok(self.extract_and_expand(dh, &kem_context)) }) .map_err(unspecified_err)?; @@ -911,6 +916,7 @@ impl LabeledSuiteId { struct AeadKey([u8; KEY_LEN]); impl Drop for AeadKey { + #[inline(never)] fn drop(&mut self) { self.0.zeroize() } @@ -920,20 +926,14 @@ impl Drop for AeadKey { struct KemSharedSecret([u8; KDF_LEN]); impl Drop for KemSharedSecret { + #[inline(never)] fn drop(&mut self) { self.0.zeroize(); } } -fn key_rejected_err(_e: aws_lc_rs::error::KeyRejected) -> Error { - #[cfg(feature = "std")] - { - Error::Other(OtherError(Arc::new(_e))) - } - #[cfg(not(feature = "std"))] - { - Error::Other(OtherError()) - } +fn key_rejected_err(e: aws_lc_rs::error::KeyRejected) -> Error { + Error::Other(OtherError::new(e)) } // The `cipher::chacha::KEY_LEN` const is not exported, so we copy it here: @@ -1027,8 +1027,15 @@ mod tests { (DH_KEM_X25519_HKDF_SHA256_AES_256, false), (DH_KEM_X25519_HKDF_SHA256_CHACHA20_POLY1305, false), ]; - for (suite, expected) in testcases { - assert_eq!(suite.fips(), *expected); + for (suite, allowed) in testcases { + match allowed { + true => assert_eq!(suite.fips(), crate::fips()), + false => assert_eq!( + suite.fips(), + FipsStatus::Unvalidated, + "expected non-FIPS compatible suite" + ), + } } } } diff --git a/rustls-aws-lc-rs/src/kx.rs b/rustls-aws-lc-rs/src/kx.rs new file mode 100644 index 00000000000..ad6ffeb6021 --- /dev/null +++ b/rustls-aws-lc-rs/src/kx.rs @@ -0,0 +1,323 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::fmt; + +use aws_lc_rs::rand::SystemRandom; +use aws_lc_rs::{agreement, kem}; +use pki_types::FipsStatus; +use rustls::crypto::GetRandomFailed; +use rustls::crypto::kx::{ + ActiveKeyExchange, CompletedKeyExchange, Hybrid, HybridLayout, NamedGroup, SharedSecret, + StartedKeyExchange, SupportedKxGroup, +}; +use rustls::error::{Error, PeerMisbehaved}; + +/// This is the [X25519MLKEM768] key exchange. +/// +/// [X25519MLKEM768]: +pub static X25519MLKEM768: &dyn SupportedKxGroup = &Hybrid { + classical: X25519, + post_quantum: MLKEM768, + name: NamedGroup::X25519MLKEM768, + layout: HybridLayout { + classical_share_len: X25519_LEN, + post_quantum_client_share_len: MLKEM768_ENCAP_LEN, + post_quantum_server_share_len: MLKEM768_CIPHERTEXT_LEN, + post_quantum_first: true, + }, +}; + +/// This is the [SECP256R1MLKEM768] key exchange. +/// +/// [SECP256R1MLKEM768]: +pub static SECP256R1MLKEM768: &dyn SupportedKxGroup = &Hybrid { + classical: SECP256R1, + post_quantum: MLKEM768, + name: NamedGroup::secp256r1MLKEM768, + layout: HybridLayout { + classical_share_len: SECP256R1_LEN, + post_quantum_client_share_len: MLKEM768_ENCAP_LEN, + post_quantum_server_share_len: MLKEM768_CIPHERTEXT_LEN, + post_quantum_first: false, + }, +}; + +/// This is the [MLKEM] key encapsulation mechanism in NIST with security category 3. +/// +/// [MLKEM]: https://datatracker.ietf.org/doc/draft-ietf-tls-mlkem +pub static MLKEM768: &dyn SupportedKxGroup = &MlKem { + alg: &kem::ML_KEM_768, + group: NamedGroup::MLKEM768, +}; + +/// This is the [MLKEM] key encapsulation mechanism in NIST with security category 5. +/// +/// [MLKEM]: https://datatracker.ietf.org/doc/draft-ietf-tls-mlkem +pub static MLKEM1024: &dyn SupportedKxGroup = &MlKem { + alg: &kem::ML_KEM_1024, + group: NamedGroup::MLKEM1024, +}; + +#[derive(Debug)] +pub(crate) struct MlKem { + alg: &'static kem::Algorithm, + group: NamedGroup, +} + +impl SupportedKxGroup for MlKem { + fn start(&self) -> Result { + let decaps_key = kem::DecapsulationKey::generate(self.alg) + .map_err(|_| Error::General("key generation failed".into()))?; + + let pub_key_bytes = decaps_key + .encapsulation_key() + .and_then(|encaps_key| encaps_key.key_bytes()) + .map_err(|_| Error::General("encaps failed".into()))?; + + Ok(StartedKeyExchange::Single(Box::new(Active { + group: self.group, + decaps_key: Box::new(decaps_key), + encaps_key_bytes: Vec::from(pub_key_bytes.as_ref()), + }))) + } + + fn start_and_complete(&self, client_share: &[u8]) -> Result { + let encaps_key = kem::EncapsulationKey::new(self.alg, client_share) + .map_err(|_| PeerMisbehaved::InvalidKeyShare)?; + + let (ciphertext, shared_secret) = encaps_key + .encapsulate() + .map_err(|_| PeerMisbehaved::InvalidKeyShare)?; + + Ok(CompletedKeyExchange { + group: self.name(), + pub_key: Vec::from(ciphertext.as_ref()), + secret: SharedSecret::from(shared_secret.as_ref()), + }) + } + + fn name(&self) -> NamedGroup { + self.group + } + + fn fips(&self) -> FipsStatus { + // AUDITORS: + // At the time of writing, the ML-KEM implementation in AWS-LC-FIPS module 3.0 + // is FIPS-pending. Some regulatory regimes (eg, FedRAMP rev 5 SC-13) allow + // use of implementations in this state, as if they are already approved. + // + // We follow this liberal interpretation, and say MlKem768 is FIPS-compliant + // if the underlying library is in FIPS mode. + // + // TODO: adjust the `fips()` function return type to allow more policies to + // be expressed, perhaps following something like + // -- + // see + super::fips() + } +} + +struct Active { + group: NamedGroup, + decaps_key: Box>, + encaps_key_bytes: Vec, +} + +impl ActiveKeyExchange for Active { + // The received 'peer_pub_key' is actually the ML-KEM ciphertext, + // which when decapsulated with our `decaps_key` produces the shared + // secret. + fn complete(self: Box, peer_pub_key: &[u8]) -> Result { + let shared_secret = self + .decaps_key + .decapsulate(peer_pub_key.into()) + .map_err(|_| PeerMisbehaved::InvalidKeyShare)?; + + Ok(SharedSecret::from(shared_secret.as_ref())) + } + + fn pub_key(&self) -> &[u8] { + &self.encaps_key_bytes + } + + fn group(&self) -> NamedGroup { + self.group + } +} + +const X25519_LEN: usize = 32; +const SECP256R1_LEN: usize = 65; +const MLKEM768_CIPHERTEXT_LEN: usize = 1088; +const MLKEM768_ENCAP_LEN: usize = 1184; + +/// A key-exchange group supported by *ring*. +struct KxGroup { + /// The IANA "TLS Supported Groups" name of the group + name: NamedGroup, + + /// The corresponding ring agreement::Algorithm + agreement_algorithm: &'static agreement::Algorithm, + + /// Whether the algorithm is allowed by FIPS + /// + /// `SupportedKxGroup::fips()` is true if and only if the algorithm is allowed, + /// _and_ the implementation is FIPS-validated. + fips_allowed: bool, + + /// aws-lc-rs 1.9 and later accepts more formats of public keys than + /// just uncompressed. + /// + /// That is not compatible with TLS: + /// - TLS1.3 outlaws other encodings, + /// - TLS1.2 negotiates other encodings (we only offer uncompressed), and + /// defaults to uncompressed if negotiation is not done. + /// + /// This function should return `true` if the basic shape of its argument + /// is consistent with an uncompressed point encoding. It does not need + /// to verify that the point is on the curve (if the curve requires that + /// for security); aws-lc-rs/ring must do that. + pub_key_validator: fn(&[u8]) -> bool, +} + +impl SupportedKxGroup for KxGroup { + fn start(&self) -> Result { + let rng = SystemRandom::new(); + let priv_key = agreement::EphemeralPrivateKey::generate(self.agreement_algorithm, &rng) + .map_err(|_| GetRandomFailed)?; + + let pub_key = priv_key + .compute_public_key() + .map_err(|_| GetRandomFailed)?; + + Ok(StartedKeyExchange::Single(Box::new(KeyExchange { + name: self.name, + agreement_algorithm: self.agreement_algorithm, + priv_key, + pub_key, + pub_key_validator: self.pub_key_validator, + }))) + } + + fn name(&self) -> NamedGroup { + self.name + } + + fn fips(&self) -> FipsStatus { + match self.fips_allowed { + true => super::fips(), + false => FipsStatus::Unvalidated, + } + } +} + +impl fmt::Debug for KxGroup { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.name.fmt(f) + } +} + +/// Ephemeral ECDH on curve25519 (see RFC7748) +pub static X25519: &dyn SupportedKxGroup = &KxGroup { + name: NamedGroup::X25519, + agreement_algorithm: &agreement::X25519, + + // "Curves that are included in SP 800-186 but not included in SP 800-56Arev3 are + // not approved for key agreement. E.g., the ECDH X25519 and X448 key agreement + // schemes (defined in RFC 7748) that use Curve25519 and Curve448, respectively, + // are not compliant to SP 800-56Arev3." + // -- + fips_allowed: false, + + pub_key_validator: |point: &[u8]| point.len() == 32, +}; + +/// Ephemeral ECDH on secp256r1 (aka NIST-P256) +pub static SECP256R1: &dyn SupportedKxGroup = &KxGroup { + name: NamedGroup::secp256r1, + agreement_algorithm: &agreement::ECDH_P256, + fips_allowed: true, + pub_key_validator: uncompressed_point, +}; + +/// Ephemeral ECDH on secp384r1 (aka NIST-P384) +pub static SECP384R1: &dyn SupportedKxGroup = &KxGroup { + name: NamedGroup::secp384r1, + agreement_algorithm: &agreement::ECDH_P384, + fips_allowed: true, + pub_key_validator: uncompressed_point, +}; + +fn uncompressed_point(point: &[u8]) -> bool { + // See `UncompressedPointRepresentation`, which is a retelling of + // SEC1 section 2.3.3 "Elliptic-Curve-Point-to-Octet-String Conversion" + // + matches!(point.first(), Some(0x04)) +} + +/// An in-progress key exchange. This has the algorithm, +/// our private key, and our public key. +struct KeyExchange { + name: NamedGroup, + agreement_algorithm: &'static agreement::Algorithm, + priv_key: agreement::EphemeralPrivateKey, + pub_key: agreement::PublicKey, + pub_key_validator: fn(&[u8]) -> bool, +} + +impl ActiveKeyExchange for KeyExchange { + /// Completes the key exchange, given the peer's public key. + fn complete(self: Box, peer: &[u8]) -> Result { + if !(self.pub_key_validator)(peer) { + return Err(PeerMisbehaved::InvalidKeyShare.into()); + } + let peer_key = agreement::UnparsedPublicKey::new(self.agreement_algorithm, peer); + super::ring_shim::agree_ephemeral(self.priv_key, &peer_key) + .map_err(|_| PeerMisbehaved::InvalidKeyShare.into()) + } + + /// Return the group being used. + fn group(&self) -> NamedGroup { + self.name + } + + /// Return the public key being used. + fn pub_key(&self) -> &[u8] { + self.pub_key.as_ref() + } +} + +#[cfg(test)] +mod tests { + use std::format; + + #[test] + fn kxgroup_fmt_yields_name() { + assert_eq!("X25519", format!("{:?}", super::X25519)); + } +} + +#[cfg(all(test, bench))] +mod benchmarks { + #[bench] + fn bench_x25519(b: &mut test::Bencher) { + bench_any(b, super::X25519); + } + + #[bench] + fn bench_ecdh_p256(b: &mut test::Bencher) { + bench_any(b, super::SECP256R1); + } + + #[bench] + fn bench_ecdh_p384(b: &mut test::Bencher) { + bench_any(b, super::SECP384R1); + } + + fn bench_any(b: &mut test::Bencher, kxg: &dyn super::SupportedKxGroup) { + b.iter(|| { + let akx = kxg.start().unwrap().into_single(); + let pub_key = akx.pub_key().to_vec(); + test::black_box(akx.complete(&pub_key).unwrap()); + }); + } +} diff --git a/rustls-aws-lc-rs/src/lib.rs b/rustls-aws-lc-rs/src/lib.rs new file mode 100644 index 00000000000..8b3d0b33998 --- /dev/null +++ b/rustls-aws-lc-rs/src/lib.rs @@ -0,0 +1,421 @@ +//! A `CryptoProvider` implementation backed by *aws-lc-rs*. +//! +//! # aws-lc-rs FIPS approval status +//! +//! This is covered by [FIPS 140-3 certificate #4816][cert-4816]. +//! See [the security policy][policy-4816] for precisely which +//! environments and functions this certificate covers. +//! +//! Later releases of aws-lc-rs may be covered by later certificates, +//! or be pending certification. +//! +//! For the most up-to-date details see the latest documentation +//! for the [`aws-lc-fips-sys`] crate. +//! +//! [`aws-lc-fips-sys`]: https://crates.io/crates/aws-lc-fips-sys +//! [cert-4816]: https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/4816 +//! [policy-4816]: https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp4816.pdf + +#![no_std] +#![warn(clippy::exhaustive_enums, clippy::exhaustive_structs, missing_docs)] +#![cfg_attr(bench, feature(test))] + +extern crate alloc; +#[cfg(any(feature = "std", test))] +extern crate std; + +// Import `test` sysroot crate for `Bencher` definitions. +#[cfg(bench)] +#[allow(unused_extern_crates)] +extern crate test; + +use alloc::borrow::Cow; +use alloc::boxed::Box; +use alloc::sync::Arc; +#[cfg(feature = "std")] +use core::time::Duration; + +use pki_types::{FipsStatus, PrivateKeyDer}; +use rustls::crypto::kx::SupportedKxGroup; +use rustls::crypto::{ + CryptoProvider, GetRandomFailed, KeyProvider, SecureRandom, SignatureScheme, SigningKey, + TicketProducer, TicketerFactory, WebPkiSupportedAlgorithms, +}; +use rustls::error::{Error, OtherError}; +#[cfg(feature = "std")] +use rustls::ticketer::TicketRotator; +use rustls::{Tls12CipherSuite, Tls13CipherSuite}; + +/// Hybrid public key encryption (HPKE). +pub mod hpke; +/// Using software keys for authentication. +pub mod sign; +use sign::{EcdsaSigner, Ed25519Signer, RsaSigningKey}; + +pub(crate) mod hash; +pub(crate) mod hmac; +pub(crate) mod kx; +pub(crate) mod quic; +#[cfg(feature = "std")] +pub(crate) mod ticketer; +#[cfg(feature = "std")] +use ticketer::Rfc5077Ticketer; +pub(crate) mod tls12; +pub(crate) mod tls13; +pub(crate) mod verify; +pub use verify::{ + ALL_VERIFICATION_ALGS, AwsLcRsVerificationAlgorithm, ECDSA_P256_SHA256, ECDSA_P256_SHA384, + ECDSA_P256_SHA512, ECDSA_P384_SHA256, ECDSA_P384_SHA384, ECDSA_P384_SHA512, ECDSA_P521_SHA256, + ECDSA_P521_SHA384, ECDSA_P521_SHA512, ED25519, RSA_PKCS1_2048_8192_SHA256, + RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS, RSA_PKCS1_2048_8192_SHA384, + RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS, RSA_PKCS1_2048_8192_SHA512, + RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS, RSA_PKCS1_3072_8192_SHA384, + RSA_PSS_2048_8192_SHA256_LEGACY_KEY, RSA_PSS_2048_8192_SHA384_LEGACY_KEY, + RSA_PSS_2048_8192_SHA512_LEGACY_KEY, +}; + +/// A `CryptoProvider` backed by aws-lc-rs that uses FIPS140-3-approved cryptography. +/// +/// Using this constant expresses in your code that you require FIPS-approved cryptography, and +/// will not compile if you make a mistake with cargo features. +/// +/// See our [FIPS documentation][fips] for more detail. +/// +/// [fips]: https://docs.rs/rustls/latest/rustls/manual/_06_fips/index.html +#[cfg(feature = "fips")] +pub const DEFAULT_FIPS_PROVIDER: CryptoProvider = DEFAULT_PROVIDER; + +/// The default `CryptoProvider` backed by aws-lc-rs. +pub const DEFAULT_PROVIDER: CryptoProvider = CryptoProvider { + tls12_cipher_suites: Cow::Borrowed(DEFAULT_TLS12_CIPHER_SUITES), + tls13_cipher_suites: Cow::Borrowed(DEFAULT_TLS13_CIPHER_SUITES), + kx_groups: Cow::Borrowed(DEFAULT_KX_GROUPS), + signature_verification_algorithms: SUPPORTED_SIG_ALGS, + secure_random: &AwsLcRs, + key_provider: &AwsLcRs, + ticketer_factory: &AwsLcRs, +}; + +/// The default `CryptoProvider` backed by aws-lc-rs that only supports TLS1.3. +pub const DEFAULT_TLS13_PROVIDER: CryptoProvider = CryptoProvider { + tls12_cipher_suites: Cow::Borrowed(&[]), + ..DEFAULT_PROVIDER +}; + +/// The default `CryptoProvider` backed by aws-lc-rs that only supports TLS1.2. +/// +/// Use of TLS1.3 is **strongly** recommended. +pub const DEFAULT_TLS12_PROVIDER: CryptoProvider = CryptoProvider { + tls13_cipher_suites: Cow::Borrowed(&[]), + ..DEFAULT_PROVIDER +}; + +/// `KeyProvider` impl for aws-lc-rs +pub static DEFAULT_KEY_PROVIDER: &dyn KeyProvider = &AwsLcRs; + +/// `SecureRandom` impl for aws-lc-rs +pub static DEFAULT_SECURE_RANDOM: &dyn SecureRandom = &AwsLcRs; + +#[derive(Debug)] +struct AwsLcRs; + +impl SecureRandom for AwsLcRs { + fn fill(&self, buf: &mut [u8]) -> Result<(), GetRandomFailed> { + use aws_lc_rs::rand::SecureRandom; + + aws_lc_rs::rand::SystemRandom::new() + .fill(buf) + .map_err(|_| GetRandomFailed) + } + + fn fips(&self) -> FipsStatus { + fips() + } +} + +impl KeyProvider for AwsLcRs { + fn load_private_key( + &self, + key_der: PrivateKeyDer<'static>, + ) -> Result, Error> { + if let Ok(rsa) = RsaSigningKey::try_from(&key_der) { + return Ok(Box::new(rsa)); + } + + if let Ok(ecdsa) = EcdsaSigner::try_from(&key_der) { + return Ok(Box::new(ecdsa)); + } + + if let PrivateKeyDer::Pkcs8(pkcs8) = key_der { + if let Ok(eddsa) = Ed25519Signer::try_from(&pkcs8) { + return Ok(Box::new(eddsa)); + } + } + + Err(Error::General( + "failed to parse private key as RSA, ECDSA, or EdDSA".into(), + )) + } + + fn fips(&self) -> FipsStatus { + fips() + } +} + +impl TicketerFactory for AwsLcRs { + /// Make the recommended `Ticketer`. + /// + /// This produces tickets: + /// + /// - where each lasts for at least 6 hours, + /// - with randomly generated keys, and + /// - where keys are rotated every 6 hours. + /// + /// The `Ticketer` uses the [RFC 5077 §4] "Recommended Ticket Construction", + /// using AES 256 for encryption and HMAC-SHA256 for ciphertext authentication. + /// + /// [RFC 5077 §4]: https://www.rfc-editor.org/rfc/rfc5077#section-4 + fn ticketer(&self) -> Result, Error> { + #[cfg(feature = "std")] + { + Ok(Arc::new(TicketRotator::new( + SIX_HOURS, + Rfc5077Ticketer::new, + )?)) + } + #[cfg(not(feature = "std"))] + { + Err(Error::General( + "AwsLcRs::ticketer() relies on std-only RwLock via TicketRotator".into(), + )) + } + } + + fn fips(&self) -> FipsStatus { + fips() + } +} + +#[cfg(feature = "std")] +const SIX_HOURS: Duration = Duration::from_secs(6 * 60 * 60); + +/// The TLS1.2 cipher suite configuration that an application should use by default. +/// +/// This will be [`ALL_TLS12_CIPHER_SUITES`] sans any supported cipher suites that +/// shouldn't be enabled by most applications. +pub static DEFAULT_TLS12_CIPHER_SUITES: &[&Tls12CipherSuite] = &[ + tls12::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls12::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + #[cfg(not(feature = "fips"))] + tls12::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + tls12::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls12::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + #[cfg(not(feature = "fips"))] + tls12::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, +]; + +/// The TLS1.3 cipher suite configuration that an application should use by default. +/// +/// This will be [`ALL_TLS13_CIPHER_SUITES`] sans any supported cipher suites that +/// shouldn't be enabled by most applications. +pub static DEFAULT_TLS13_CIPHER_SUITES: &[&Tls13CipherSuite] = &[ + tls13::TLS13_AES_128_GCM_SHA256, + tls13::TLS13_AES_256_GCM_SHA384, + #[cfg(not(feature = "fips"))] + tls13::TLS13_CHACHA20_POLY1305_SHA256, +]; + +/// A list of all the TLS1.2 cipher suites supported by the rustls aws-lc-rs provider. +pub static ALL_TLS12_CIPHER_SUITES: &[&Tls12CipherSuite] = &[ + tls12::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls12::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls12::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + tls12::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls12::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls12::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, +]; + +/// A list of all the TLS1.3 cipher suites supported by the rustls aws-lc-rs provider. +pub static ALL_TLS13_CIPHER_SUITES: &[&Tls13CipherSuite] = &[ + tls13::TLS13_AES_128_GCM_SHA256, + tls13::TLS13_AES_256_GCM_SHA384, + tls13::TLS13_CHACHA20_POLY1305_SHA256, +]; + +/// All defined cipher suites supported by aws-lc-rs appear in this module. +pub mod cipher_suite { + pub use super::tls12::{ + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + }; + pub use super::tls13::{ + TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256, + }; +} + +/// A `WebPkiSupportedAlgorithms` value that reflects webpki's capabilities when +/// compiled against aws-lc-rs. +pub static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = match WebPkiSupportedAlgorithms::new( + &[ + ECDSA_P256_SHA256, + ECDSA_P256_SHA384, + ECDSA_P256_SHA512, + ECDSA_P384_SHA256, + ECDSA_P384_SHA384, + ECDSA_P384_SHA512, + ECDSA_P521_SHA256, + ECDSA_P521_SHA384, + ECDSA_P521_SHA512, + ED25519, + RSA_PSS_2048_8192_SHA256_LEGACY_KEY, + RSA_PSS_2048_8192_SHA384_LEGACY_KEY, + RSA_PSS_2048_8192_SHA512_LEGACY_KEY, + RSA_PKCS1_2048_8192_SHA256, + RSA_PKCS1_2048_8192_SHA384, + RSA_PKCS1_2048_8192_SHA512, + RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS, + RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS, + RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS, + ], + &[ + // Note: for TLS1.2 the curve is not fixed by SignatureScheme. For TLS1.3 it is. + ( + SignatureScheme::ECDSA_NISTP384_SHA384, + &[ECDSA_P384_SHA384, ECDSA_P256_SHA384, ECDSA_P521_SHA384], + ), + ( + SignatureScheme::ECDSA_NISTP256_SHA256, + &[ECDSA_P256_SHA256, ECDSA_P384_SHA256, ECDSA_P521_SHA256], + ), + ( + SignatureScheme::ECDSA_NISTP521_SHA512, + &[ECDSA_P521_SHA512, ECDSA_P384_SHA512, ECDSA_P256_SHA512], + ), + (SignatureScheme::ED25519, &[ED25519]), + ( + SignatureScheme::RSA_PSS_SHA512, + &[RSA_PSS_2048_8192_SHA512_LEGACY_KEY], + ), + ( + SignatureScheme::RSA_PSS_SHA384, + &[RSA_PSS_2048_8192_SHA384_LEGACY_KEY], + ), + ( + SignatureScheme::RSA_PSS_SHA256, + &[RSA_PSS_2048_8192_SHA256_LEGACY_KEY], + ), + ( + SignatureScheme::RSA_PKCS1_SHA512, + &[RSA_PKCS1_2048_8192_SHA512], + ), + ( + SignatureScheme::RSA_PKCS1_SHA384, + &[RSA_PKCS1_2048_8192_SHA384], + ), + ( + SignatureScheme::RSA_PKCS1_SHA256, + &[RSA_PKCS1_2048_8192_SHA256], + ), + ], +) { + Ok(algs) => algs, + Err(_) => panic!("bad WebPkiSupportedAlgorithms"), +}; + +/// All defined key exchange groups supported by aws-lc-rs appear in this module. +/// +/// [`ALL_KX_GROUPS`] is provided as an array of all of these values. +/// [`DEFAULT_KX_GROUPS`] is provided as an array of this provider's defaults. +pub mod kx_group { + pub use super::kx::{ + MLKEM768, MLKEM1024, SECP256R1, SECP256R1MLKEM768, SECP384R1, X25519, X25519MLKEM768, + }; +} + +/// A list of the default key exchange groups supported by this provider. +/// +/// This does not contain MLKEM768; by default MLKEM768 is only offered +/// in hybrid with X25519. +pub static DEFAULT_KX_GROUPS: &[&dyn SupportedKxGroup] = &[ + kx_group::X25519MLKEM768, + #[cfg(not(feature = "fips"))] + kx_group::X25519, + kx_group::SECP256R1, + kx_group::SECP384R1, +]; + +/// A list of all the key exchange groups supported by this provider. +pub static ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[ + kx_group::X25519MLKEM768, + kx_group::SECP256R1MLKEM768, + kx_group::X25519, + kx_group::SECP256R1, + kx_group::SECP384R1, + kx_group::MLKEM768, + kx_group::MLKEM1024, +]; + +/// Compatibility shims between ring 0.16.x and 0.17.x API +mod ring_shim { + use aws_lc_rs::agreement::{self, EphemeralPrivateKey, UnparsedPublicKey}; + use rustls::crypto::kx::SharedSecret; + + pub(super) fn agree_ephemeral( + priv_key: EphemeralPrivateKey, + peer_key: &UnparsedPublicKey<&[u8]>, + ) -> Result { + agreement::agree_ephemeral(priv_key, peer_key, (), |secret| { + Ok(SharedSecret::from(secret)) + }) + } +} + +/// Are we in FIPS mode? +fn fips() -> FipsStatus { + match aws_lc_rs::try_fips_mode().is_ok() { + true => FipsStatus::Pending, + false => FipsStatus::Unvalidated, + } +} + +fn unspecified_err(e: aws_lc_rs::error::Unspecified) -> Error { + Error::Other(OtherError::new(e)) +} + +const MAX_FRAGMENT_LEN: usize = 16384; + +#[cfg(test)] +mod tests { + + #[cfg(feature = "fips")] + use pki_types::FipsStatus; + + #[cfg(feature = "fips")] + #[test] + fn default_suites_are_fips() { + assert!( + super::DEFAULT_TLS12_CIPHER_SUITES + .iter() + .all(|scs| !matches!(scs.fips(), FipsStatus::Unvalidated)) + ); + assert!( + super::DEFAULT_TLS13_CIPHER_SUITES + .iter() + .all(|scs| !matches!(scs.fips(), FipsStatus::Unvalidated)) + ); + } + + #[cfg(not(feature = "fips"))] + #[test] + fn default_suites() { + assert_eq!( + super::DEFAULT_TLS12_CIPHER_SUITES, + super::ALL_TLS12_CIPHER_SUITES + ); + assert_eq!( + super::DEFAULT_TLS13_CIPHER_SUITES, + super::ALL_TLS13_CIPHER_SUITES + ); + } +} diff --git a/rustls/src/crypto/ring/quic.rs b/rustls-aws-lc-rs/src/quic.rs similarity index 69% rename from rustls/src/crypto/ring/quic.rs rename to rustls-aws-lc-rs/src/quic.rs index 3d0dc928916..ad5964eb1cd 100644 --- a/rustls/src/crypto/ring/quic.rs +++ b/rustls-aws-lc-rs/src/quic.rs @@ -1,11 +1,10 @@ -#![allow(clippy::duplicate_mod)] - use alloc::boxed::Box; -use super::ring_like::aead; -use crate::crypto::cipher::{AeadKey, Iv, Nonce}; -use crate::error::Error; -use crate::quic; +use aws_lc_rs::aead; +use pki_types::FipsStatus; +use rustls::crypto::cipher::{AeadKey, Iv, Nonce}; +use rustls::error::{ApiMisuse, Error}; +use rustls::quic; pub(crate) struct HeaderProtectionKey(aead::quic::HeaderProtectionKey); @@ -27,7 +26,7 @@ impl HeaderProtectionKey { let mask = self .0 .new_mask(sample) - .map_err(|_| Error::General("sample of invalid length".into()))?; + .map_err(|_| Error::ApiMisuse(ApiMisuse::InvalidQuicHeaderProtectionSampleLength))?; // The `unwrap()` will not panic because `new_mask` returns a // non-empty result. @@ -36,7 +35,7 @@ impl HeaderProtectionKey { // It is OK for the `mask` to be longer than `packet_number`, // but a valid `packet_number` will never be longer than `mask`. if packet_number.len() > pn_mask.len() { - return Err(Error::General("packet number too long".into())); + return Err(ApiMisuse::InvalidQuicHeaderProtectionPacketNumberLength.into()); } // Infallible from this point on. Before this point, `first` and @@ -130,9 +129,11 @@ impl quic::PacketKey for PacketKey { packet_number: u64, header: &[u8], payload: &mut [u8], + path_id: Option, ) -> Result { let aad = aead::Aad::from(header); - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, packet_number).0); + let nonce_value = Nonce::quic(path_id, &self.iv, packet_number); + let nonce = aead::Nonce::assume_unique_for_key(nonce_value.to_array()?); let tag = self .key .seal_in_place_separate_tag(nonce, aad, payload) @@ -142,20 +143,25 @@ impl quic::PacketKey for PacketKey { /// Decrypt a QUIC packet /// - /// Takes the packet `header`, which is used as the additional authenticated data, and the - /// `payload`, which includes the authentication tag. + /// Takes a `packet_number` and optional `path_id`, used to derive the nonce; the packet + /// `header`, which is used as the additional authenticated data, and the `payload`, which + /// includes the authentication tag. + /// + /// On success, returns the slice of `payload` containing the decrypted data. /// - /// If the return value is `Ok`, the decrypted payload can be found in `payload`, up to the - /// length found in the return value. + /// When provided, the `path_id` is used for multipath encryption as described in + /// . fn decrypt_in_place<'a>( &self, packet_number: u64, header: &[u8], payload: &'a mut [u8], + path_id: Option, ) -> Result<&'a [u8], Error> { let payload_len = payload.len(); let aad = aead::Aad::from(header); - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, packet_number).0); + let nonce_value = Nonce::quic(path_id, &self.iv, packet_number); + let nonce = aead::Nonce::assume_unique_for_key(nonce_value.to_array()?); self.key .open_in_place(nonce, aad, payload) .map_err(|_| Error::DecryptError)?; @@ -207,22 +213,19 @@ impl quic::Algorithm for KeyBuilder { self.packet_alg.key_len() } - fn fips(&self) -> bool { + fn fips(&self) -> FipsStatus { super::fips() } } #[cfg(test)] -#[macro_rules_attribute::apply(test_for_each_provider)] mod tests { use std::dbg; - use super::provider::tls13::{ - TLS13_AES_128_GCM_SHA256_INTERNAL, TLS13_CHACHA20_POLY1305_SHA256_INTERNAL, - }; - use crate::common_state::Side; - use crate::crypto::tls13::OkmBlock; - use crate::quic::*; + use rustls::crypto::tls13::OkmBlock; + use rustls::quic::*; + + use crate::tls13::{TLS13_AES_128_GCM_SHA256, TLS13_CHACHA20_POLY1305_SHA256}; fn test_short_packet(version: Version, expected: &[u8]) { const PN: u64 = 654360564; @@ -236,10 +239,10 @@ mod tests { let builder = KeyBuilder::new( &secret, version, - TLS13_CHACHA20_POLY1305_SHA256_INTERNAL + TLS13_CHACHA20_POLY1305_SHA256 .quic .unwrap(), - TLS13_CHACHA20_POLY1305_SHA256_INTERNAL.hkdf_provider, + TLS13_CHACHA20_POLY1305_SHA256.hkdf_provider, ); let packet = builder.packet_key(); let hpk = builder.header_protection_key(); @@ -249,7 +252,7 @@ mod tests { let mut buf = PLAIN.to_vec(); let (header, payload) = buf.split_at_mut(4); let tag = packet - .encrypt_in_place(PN, header, payload) + .encrypt_in_place(PN, header, payload, None) .unwrap(); buf.extend(tag.as_ref()); @@ -270,7 +273,7 @@ mod tests { let (header, payload_tag) = buf.split_at_mut(4); let plain = packet - .decrypt_in_place(PN, header, payload_tag) + .decrypt_in_place(PN, header, payload_tag, None) .unwrap(); assert_eq!(plain, &PLAIN[4..]); @@ -288,62 +291,9 @@ mod tests { ); } - #[test] - fn key_update_test_vector() { - fn equal_okm(x: &OkmBlock, y: &OkmBlock) -> bool { - x.as_ref() == y.as_ref() - } - - let mut secrets = Secrets::new( - // Constant dummy values for reproducibility - OkmBlock::new( - &[ - 0xb8, 0x76, 0x77, 0x08, 0xf8, 0x77, 0x23, 0x58, 0xa6, 0xea, 0x9f, 0xc4, 0x3e, - 0x4a, 0xdd, 0x2c, 0x96, 0x1b, 0x3f, 0x52, 0x87, 0xa6, 0xd1, 0x46, 0x7e, 0xe0, - 0xae, 0xab, 0x33, 0x72, 0x4d, 0xbf, - ][..], - ), - OkmBlock::new( - &[ - 0x42, 0xdc, 0x97, 0x21, 0x40, 0xe0, 0xf2, 0xe3, 0x98, 0x45, 0xb7, 0x67, 0x61, - 0x34, 0x39, 0xdc, 0x67, 0x58, 0xca, 0x43, 0x25, 0x9b, 0x87, 0x85, 0x06, 0x82, - 0x4e, 0xb1, 0xe4, 0x38, 0xd8, 0x55, - ][..], - ), - TLS13_AES_128_GCM_SHA256_INTERNAL, - TLS13_AES_128_GCM_SHA256_INTERNAL - .quic - .unwrap(), - Side::Client, - Version::V1, - ); - secrets.update(); - - assert!(equal_okm( - &secrets.client, - &OkmBlock::new( - &[ - 0x42, 0xca, 0xc8, 0xc9, 0x1c, 0xd5, 0xeb, 0x40, 0x68, 0x2e, 0x43, 0x2e, 0xdf, - 0x2d, 0x2b, 0xe9, 0xf4, 0x1a, 0x52, 0xca, 0x6b, 0x22, 0xd8, 0xe6, 0xcd, 0xb1, - 0xe8, 0xac, 0xa9, 0x6, 0x1f, 0xce - ][..] - ) - )); - assert!(equal_okm( - &secrets.server, - &OkmBlock::new( - &[ - 0xeb, 0x7f, 0x5e, 0x2a, 0x12, 0x3f, 0x40, 0x7d, 0xb4, 0x99, 0xe3, 0x61, 0xca, - 0xe5, 0x90, 0xd4, 0xd9, 0x92, 0xe1, 0x4b, 0x7a, 0xce, 0x3, 0xc2, 0x44, 0xe0, - 0x42, 0x21, 0x15, 0xb6, 0xd3, 0x8a - ][..] - ) - )); - } - #[test] fn short_packet_header_protection_v2() { - // https://www.ietf.org/archive/id/draft-ietf-quic-v2-10.html#name-chacha20-poly1305-short-head + // https://tools.ietf.org/html/rfc9369.html#name-chacha20-poly1305-short-hea test_short_packet( Version::V2, &[ @@ -355,14 +305,12 @@ mod tests { #[test] fn initial_test_vector_v2() { - // https://www.ietf.org/archive/id/draft-ietf-quic-v2-10.html#name-sample-packet-protection-2 + // https://tools.ietf.org/html/rfc9369.html#name-sample-packet-protection let icid = [0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08]; let server = Keys::initial( Version::V2, - TLS13_AES_128_GCM_SHA256_INTERNAL, - TLS13_AES_128_GCM_SHA256_INTERNAL - .quic - .unwrap(), + TLS13_AES_128_GCM_SHA256, + TLS13_AES_128_GCM_SHA256.quic.unwrap(), &icid, Side::Server, ); @@ -383,7 +331,7 @@ mod tests { let tag = server .local .packet - .encrypt_in_place(1, &server_header, &mut server_payload) + .encrypt_in_place(1, &server_header, &mut server_payload, None) .unwrap(); let (first, rest) = server_header.split_at_mut(1); let rest_len = rest.len(); @@ -413,4 +361,81 @@ mod tests { ]; assert_eq!(server_packet[..], expected_server_packet[..]); } + + // This test is based on picoquic's output for `multipath_aead_test` in + // `picoquictest/multipath_test.c`. + // + // See + #[test] + fn test_multipath_aead_basic() { + const SECRET: &[u8; 32] = &[ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 35, 26, 27, 28, 29, 30, 31, + ]; + const PN: u64 = 12345; + const PATH_ID: u32 = 2; + const PAYLOAD: &[u8] = b"The quick brown fox jumps over the lazy dog"; + const HEADER: &[u8] = b"This is a test"; + + const EXPECTED: &[u8] = &[ + 123, 139, 232, 52, 136, 25, 201, 143, 250, 89, 87, 39, 37, 63, 0, 210, 220, 227, 186, + 140, 183, 251, 13, 203, 6, 116, 204, 100, 166, 64, 43, 185, 174, 85, 212, 163, 242, + 141, 24, 166, 62, 228, 187, 137, 248, 31, 152, 126, 240, 151, 79, 51, 253, 130, 43, + 114, 173, 234, 254, + ]; + + let secret = OkmBlock::new(SECRET); + let builder = KeyBuilder::new( + &secret, + Version::V1, + TLS13_AES_128_GCM_SHA256.quic.unwrap(), + TLS13_AES_128_GCM_SHA256.hkdf_provider, + ); + + let packet = builder.packet_key(); + let mut buf = PAYLOAD.to_vec(); + let tag = packet + .encrypt_in_place(PN, HEADER, &mut buf, Some(PATH_ID)) + .unwrap(); + buf.extend_from_slice(tag.as_ref()); + + assert_eq!(buf.as_slice(), EXPECTED); + } + + // This test is based on `multipath_aead_test` in `picoquictest/multipath_test.c` + // + // See + #[test] + fn test_multipath_aead_roundtrip() { + const SECRET: &[u8; 32] = &[ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 35, 26, 27, 28, 29, 30, 31, + ]; + const PAYLOAD: &[u8] = b"The quick brown fox jumps over the lazy dog"; + const HEADER: &[u8] = b"This is a test"; + const PN: u64 = 12345; + + const TEST_PATH_IDS: &[u32] = &[0, 1, 2, 0xaead]; + + let secret = OkmBlock::new(SECRET); + let builder = KeyBuilder::new( + &secret, + Version::V1, + TLS13_AES_128_GCM_SHA256.quic.unwrap(), + TLS13_AES_128_GCM_SHA256.hkdf_provider, + ); + let packet = builder.packet_key(); + + for &path_id in TEST_PATH_IDS { + let mut buf = PAYLOAD.to_vec(); + let tag = packet + .encrypt_in_place(PN, HEADER, &mut buf, Some(path_id)) + .unwrap(); + buf.extend_from_slice(tag.as_ref()); + let decrypted = packet + .decrypt_in_place(PN, HEADER, &mut buf, Some(path_id)) + .unwrap(); + assert_eq!(decrypted, PAYLOAD); + } + } } diff --git a/rustls/src/crypto/aws_lc_rs/sign.rs b/rustls-aws-lc-rs/src/sign.rs similarity index 50% rename from rustls/src/crypto/aws_lc_rs/sign.rs rename to rustls-aws-lc-rs/src/sign.rs index 91d9110e982..5aecdc2b720 100644 --- a/rustls/src/crypto/aws_lc_rs/sign.rs +++ b/rustls-aws-lc-rs/src/sign.rs @@ -1,5 +1,3 @@ -#![allow(clippy::duplicate_mod)] - use alloc::boxed::Box; use alloc::string::ToString; use alloc::sync::Arc; @@ -7,107 +5,70 @@ use alloc::vec::Vec; use alloc::{format, vec}; use core::fmt::{self, Debug, Formatter}; -use pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer, SubjectPublicKeyInfoDer}; -use webpki::alg_id; - -use super::ring_like::rand::SystemRandom; -use super::ring_like::signature::{self, EcdsaKeyPair, Ed25519KeyPair, KeyPair, RsaKeyPair}; -use crate::crypto::signer::{public_key_to_spki, Signer, SigningKey}; -use crate::enums::{SignatureAlgorithm, SignatureScheme}; -use crate::error::Error; +use aws_lc_rs::rand::SystemRandom; +use aws_lc_rs::signature::{self, EcdsaKeyPair, Ed25519KeyPair, KeyPair, RsaKeyPair}; +use pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer, SubjectPublicKeyInfoDer, alg_id}; +#[cfg(test)] +use rustls::crypto::CryptoProvider; +use rustls::crypto::{SignatureScheme, Signer, SigningKey, public_key_to_spki}; +use rustls::error::Error; -/// Parse `der` as any supported key encoding/type, returning -/// the first which works. -pub fn any_supported_type(der: &PrivateKeyDer<'_>) -> Result, Error> { - if let Ok(rsa) = RsaSigningKey::new(der) { - return Ok(Arc::new(rsa)); - } +/// A `SigningKey` for RSA-PKCS1 or RSA-PSS. +pub(super) struct RsaSigningKey { + key: Arc, +} - if let Ok(ecdsa) = any_ecdsa_type(der) { - return Ok(ecdsa); - } +impl RsaSigningKey { + fn to_signer(&self, scheme: SignatureScheme) -> RsaSigner { + let encoding: &dyn signature::RsaEncoding = match scheme { + SignatureScheme::RSA_PKCS1_SHA256 => &signature::RSA_PKCS1_SHA256, + SignatureScheme::RSA_PKCS1_SHA384 => &signature::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA512 => &signature::RSA_PKCS1_SHA512, + SignatureScheme::RSA_PSS_SHA256 => &signature::RSA_PSS_SHA256, + SignatureScheme::RSA_PSS_SHA384 => &signature::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA512 => &signature::RSA_PSS_SHA512, + _ => unreachable!(), + }; - if let PrivateKeyDer::Pkcs8(pkcs8) = der { - if let Ok(eddsa) = any_eddsa_type(pkcs8) { - return Ok(eddsa); + RsaSigner { + key: self.key.clone(), + scheme, + encoding, } } - Err(Error::General( - "failed to parse private key as RSA, ECDSA, or EdDSA".into(), - )) -} - -/// Parse `der` as any ECDSA key type, returning the first which works. -/// -/// Both SEC1 (PEM section starting with 'BEGIN EC PRIVATE KEY') and PKCS8 -/// (PEM section starting with 'BEGIN PRIVATE KEY') encodings are supported. -pub fn any_ecdsa_type(der: &PrivateKeyDer<'_>) -> Result, Error> { - if let Ok(ecdsa_p256) = EcdsaSigningKey::new( - der, - SignatureScheme::ECDSA_NISTP256_SHA256, - &signature::ECDSA_P256_SHA256_ASN1_SIGNING, - ) { - return Ok(Arc::new(ecdsa_p256)); - } - - if let Ok(ecdsa_p384) = EcdsaSigningKey::new( - der, - SignatureScheme::ECDSA_NISTP384_SHA384, - &signature::ECDSA_P384_SHA384_ASN1_SIGNING, - ) { - return Ok(Arc::new(ecdsa_p384)); - } - - if let Ok(ecdsa_p521) = EcdsaSigningKey::new( - der, - SignatureScheme::ECDSA_NISTP521_SHA512, - &signature::ECDSA_P521_SHA512_ASN1_SIGNING, - ) { - return Ok(Arc::new(ecdsa_p521)); - } - - Err(Error::General( - "failed to parse ECDSA private key as PKCS#8 or SEC1".into(), - )) + const SCHEMES: &[SignatureScheme] = &[ + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA256, + ]; } -/// Parse `der` as any EdDSA key type, returning the first which works. -/// -/// Note that, at the time of writing, Ed25519 does not have wide support -/// in browsers. It is also not supported by the WebPKI, because the -/// CA/Browser Forum Baseline Requirements do not support it for publicly -/// trusted certificates. -pub fn any_eddsa_type(der: &PrivatePkcs8KeyDer<'_>) -> Result, Error> { - // TODO: Add support for Ed448 - Ok(Arc::new(Ed25519SigningKey::new( - der, - SignatureScheme::ED25519, - )?)) -} +impl SigningKey for RsaSigningKey { + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { + Self::SCHEMES + .iter() + .find(|scheme| offered.contains(scheme)) + .map(|&scheme| Box::new(self.to_signer(scheme)) as Box) + } -/// A `SigningKey` for RSA-PKCS1 or RSA-PSS. -/// -/// This is used by the test suite, so it must be `pub`, but it isn't part of -/// the public, stable, API. -#[doc(hidden)] -pub struct RsaSigningKey { - key: Arc, + fn public_key(&self) -> Option> { + Some(public_key_to_spki( + &alg_id::RSA_ENCRYPTION, + self.key.public_key(), + )) + } } -static ALL_RSA_SCHEMES: &[SignatureScheme] = &[ - SignatureScheme::RSA_PSS_SHA512, - SignatureScheme::RSA_PSS_SHA384, - SignatureScheme::RSA_PSS_SHA256, - SignatureScheme::RSA_PKCS1_SHA512, - SignatureScheme::RSA_PKCS1_SHA384, - SignatureScheme::RSA_PKCS1_SHA256, -]; +impl TryFrom<&PrivateKeyDer<'_>> for RsaSigningKey { + type Error = Error; -impl RsaSigningKey { /// Make a new `RsaSigningKey` from a DER encoding, in either /// PKCS#1 or PKCS#8 format. - pub fn new(der: &PrivateKeyDer<'_>) -> Result { + fn try_from(der: &PrivateKeyDer<'_>) -> Result { let key_pair = match der { PrivateKeyDer::Pkcs1(pkcs1) => RsaKeyPair::from_der(pkcs1.secret_pkcs1_der()), PrivateKeyDer::Pkcs8(pkcs8) => RsaKeyPair::from_pkcs8(pkcs8.secret_pkcs8_der()), @@ -118,7 +79,7 @@ impl RsaSigningKey { } } .map_err(|key_rejected| { - Error::General(format!("failed to parse RSA private key: {}", key_rejected)) + Error::General(format!("failed to parse RSA private key: {key_rejected}")) })?; Ok(Self { @@ -127,31 +88,10 @@ impl RsaSigningKey { } } -impl SigningKey for RsaSigningKey { - fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { - ALL_RSA_SCHEMES - .iter() - .find(|scheme| offered.contains(scheme)) - .map(|scheme| RsaSigner::new(Arc::clone(&self.key), *scheme)) - } - - fn public_key(&self) -> Option> { - Some(public_key_to_spki( - &alg_id::RSA_ENCRYPTION, - self.key.public_key(), - )) - } - - fn algorithm(&self) -> SignatureAlgorithm { - SignatureAlgorithm::RSA - } -} - impl Debug for RsaSigningKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("RsaSigningKey") - .field("algorithm", &self.algorithm()) - .finish() + .finish_non_exhaustive() } } @@ -162,26 +102,6 @@ struct RsaSigner { } impl RsaSigner { - fn new(key: Arc, scheme: SignatureScheme) -> Box { - let encoding: &dyn signature::RsaEncoding = match scheme { - SignatureScheme::RSA_PKCS1_SHA256 => &signature::RSA_PKCS1_SHA256, - SignatureScheme::RSA_PKCS1_SHA384 => &signature::RSA_PKCS1_SHA384, - SignatureScheme::RSA_PKCS1_SHA512 => &signature::RSA_PKCS1_SHA512, - SignatureScheme::RSA_PSS_SHA256 => &signature::RSA_PSS_SHA256, - SignatureScheme::RSA_PSS_SHA384 => &signature::RSA_PSS_SHA384, - SignatureScheme::RSA_PSS_SHA512 => &signature::RSA_PSS_SHA512, - _ => unreachable!(), - }; - - Box::new(Self { - key, - scheme, - encoding, - }) - } -} - -impl Signer for RsaSigner { fn sign(&self, message: &[u8]) -> Result, Error> { let mut sig = vec![0; self.key.public_modulus_len()]; @@ -191,6 +111,12 @@ impl Signer for RsaSigner { .map(|_| sig) .map_err(|_| Error::General("signing failed".to_string())) } +} + +impl Signer for RsaSigner { + fn sign(self: Box, message: &[u8]) -> Result, Error> { + (*self).sign(message) + } fn scheme(&self) -> SignatureScheme { self.scheme @@ -201,30 +127,23 @@ impl Debug for RsaSigner { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("RsaSigner") .field("scheme", &self.scheme) - .finish() + .finish_non_exhaustive() } } -/// A SigningKey that uses exactly one TLS-level SignatureScheme -/// and one ring-level signature::SigningAlgorithm. -/// -/// Compare this to RsaSigningKey, which for a particular key is -/// willing to sign with several algorithms. This is quite poor -/// cryptography practice, but is necessary because a given RSA key -/// is expected to work in TLS1.2 (PKCS#1 signatures) and TLS1.3 -/// (PSS signatures) -- nobody is willing to obtain certificates for -/// different protocol versions. +/// A [`SigningKey`] and [`Signer`] implementation for ECDSA. /// -/// Currently this is only implemented for ECDSA keys. -struct EcdsaSigningKey { +/// Unlike [`RsaSigningKey`]/[`RsaSigner`], where we have one key that supports +/// multiple signature schemes, we can use the same type for both traits here. +#[derive(Clone)] +pub(super) struct EcdsaSigner { key: Arc, scheme: SignatureScheme, } -impl EcdsaSigningKey { - /// Make a new `ECDSASigningKey` from a DER encoding in PKCS#8 or SEC1 - /// format, expecting a key usable with precisely the given signature - /// scheme. +impl EcdsaSigner { + /// Make a new [`EcdsaSigner`] from a DER encoding in PKCS#8 or SEC1 + /// format, expecting a key usable with precisely the given signature scheme. fn new( der: &PrivateKeyDer<'_>, scheme: SignatureScheme, @@ -246,15 +165,20 @@ impl EcdsaSigningKey { scheme, }) } + + fn sign(&self, message: &[u8]) -> Result, Error> { + let rng = SystemRandom::new(); + self.key + .sign(&rng, message) + .map_err(|_| Error::General("signing failed".into())) + .map(|sig| sig.as_ref().into()) + } } -impl SigningKey for EcdsaSigningKey { +impl SigningKey for EcdsaSigner { fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { if offered.contains(&self.scheme) { - Some(Box::new(EcdsaSigner { - key: Arc::clone(&self.key), - scheme: self.scheme, - })) + Some(Box::new(self.clone())) } else { None } @@ -270,36 +194,53 @@ impl SigningKey for EcdsaSigningKey { Some(public_key_to_spki(&id, self.key.public_key())) } +} - fn algorithm(&self) -> SignatureAlgorithm { - self.scheme.algorithm() +impl Signer for EcdsaSigner { + fn sign(self: Box, message: &[u8]) -> Result, Error> { + (*self).sign(message) } -} -impl Debug for EcdsaSigningKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("EcdsaSigningKey") - .field("algorithm", &self.algorithm()) - .finish() + fn scheme(&self) -> SignatureScheme { + self.scheme } } -struct EcdsaSigner { - key: Arc, - scheme: SignatureScheme, -} +impl TryFrom<&PrivateKeyDer<'_>> for EcdsaSigner { + type Error = Error; + + /// Parse `der` as any ECDSA key type, returning the first which works. + /// + /// Both SEC1 (PEM section starting with 'BEGIN EC PRIVATE KEY') and PKCS8 + /// (PEM section starting with 'BEGIN PRIVATE KEY') encodings are supported. + fn try_from(der: &PrivateKeyDer<'_>) -> Result { + if let Ok(ecdsa_p256) = Self::new( + der, + SignatureScheme::ECDSA_NISTP256_SHA256, + &signature::ECDSA_P256_SHA256_ASN1_SIGNING, + ) { + return Ok(ecdsa_p256); + } -impl Signer for EcdsaSigner { - fn sign(&self, message: &[u8]) -> Result, Error> { - let rng = SystemRandom::new(); - self.key - .sign(&rng, message) - .map_err(|_| Error::General("signing failed".into())) - .map(|sig| sig.as_ref().into()) - } + if let Ok(ecdsa_p384) = Self::new( + der, + SignatureScheme::ECDSA_NISTP384_SHA384, + &signature::ECDSA_P384_SHA384_ASN1_SIGNING, + ) { + return Ok(ecdsa_p384); + } - fn scheme(&self) -> SignatureScheme { - self.scheme + if let Ok(ecdsa_p521) = Self::new( + der, + SignatureScheme::ECDSA_NISTP521_SHA512, + &signature::ECDSA_P521_SHA512_ASN1_SIGNING, + ) { + return Ok(ecdsa_p521); + } + + Err(Error::General( + "failed to parse ECDSA private key as PKCS#8 or SEC1".into(), + )) } } @@ -307,49 +248,30 @@ impl Debug for EcdsaSigner { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("EcdsaSigner") .field("scheme", &self.scheme) - .finish() + .finish_non_exhaustive() } } -/// A SigningKey that uses exactly one TLS-level SignatureScheme -/// and one ring-level signature::SigningAlgorithm. -/// -/// Compare this to RsaSigningKey, which for a particular key is -/// willing to sign with several algorithms. This is quite poor -/// cryptography practice, but is necessary because a given RSA key -/// is expected to work in TLS1.2 (PKCS#1 signatures) and TLS1.3 -/// (PSS signatures) -- nobody is willing to obtain certificates for -/// different protocol versions. +/// A [`SigningKey`] and [`Signer`] implementation for ED25519. /// -/// Currently this is only implemented for Ed25519 keys. -struct Ed25519SigningKey { +/// Unlike [`RsaSigningKey`]/[`RsaSigner`], where we have one key that supports +/// multiple signature schemes, we can use the same type for both traits here. +#[derive(Clone)] +pub(super) struct Ed25519Signer { key: Arc, scheme: SignatureScheme, } -impl Ed25519SigningKey { - /// Make a new `Ed25519SigningKey` from a DER encoding in PKCS#8 format, - /// expecting a key usable with precisely the given signature scheme. - fn new(der: &PrivatePkcs8KeyDer<'_>, scheme: SignatureScheme) -> Result { - match Ed25519KeyPair::from_pkcs8_maybe_unchecked(der.secret_pkcs8_der()) { - Ok(key_pair) => Ok(Self { - key: Arc::new(key_pair), - scheme, - }), - Err(e) => Err(Error::General(format!( - "failed to parse Ed25519 private key: {e}" - ))), - } +impl Ed25519Signer { + fn sign(&self, message: &[u8]) -> Result, Error> { + Ok(self.key.sign(message).as_ref().into()) } } -impl SigningKey for Ed25519SigningKey { +impl SigningKey for Ed25519Signer { fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { if offered.contains(&self.scheme) { - Some(Box::new(Ed25519Signer { - key: Arc::clone(&self.key), - scheme: self.scheme, - })) + Some(Box::new(self.clone())) } else { None } @@ -358,28 +280,11 @@ impl SigningKey for Ed25519SigningKey { fn public_key(&self) -> Option> { Some(public_key_to_spki(&alg_id::ED25519, self.key.public_key())) } - - fn algorithm(&self) -> SignatureAlgorithm { - self.scheme.algorithm() - } -} - -impl Debug for Ed25519SigningKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("Ed25519SigningKey") - .field("algorithm", &self.algorithm()) - .finish() - } -} - -struct Ed25519Signer { - key: Arc, - scheme: SignatureScheme, } impl Signer for Ed25519Signer { - fn sign(&self, message: &[u8]) -> Result, Error> { - Ok(self.key.sign(message).as_ref().into()) + fn sign(self: Box, message: &[u8]) -> Result, Error> { + (*self).sign(message) } fn scheme(&self) -> SignatureScheme { @@ -387,14 +292,46 @@ impl Signer for Ed25519Signer { } } +impl TryFrom<&PrivatePkcs8KeyDer<'_>> for Ed25519Signer { + type Error = Error; + + /// Parse `der` as an Ed25519 key. + /// + /// Note that, at the time of writing, Ed25519 does not have wide support + /// in browsers. It is also not supported by the WebPKI, because the + /// CA/Browser Forum Baseline Requirements do not support it for publicly + /// trusted certificates. + fn try_from(der: &PrivatePkcs8KeyDer<'_>) -> Result { + match Ed25519KeyPair::from_pkcs8_maybe_unchecked(der.secret_pkcs8_der()) { + Ok(key_pair) => Ok(Self { + key: Arc::new(key_pair), + scheme: SignatureScheme::ED25519, + }), + Err(e) => Err(Error::General(format!( + "failed to parse Ed25519 private key: {e}" + ))), + } + } +} + impl Debug for Ed25519Signer { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("Ed25519Signer") .field("scheme", &self.scheme) - .finish() + .finish_non_exhaustive() } } +#[cfg(test)] // Also available for benchmarks +fn load_key( + provider: &CryptoProvider, + der: PrivateKeyDer<'static>, +) -> Result, Error> { + provider + .key_provider + .load_private_key(der) +} + #[cfg(test)] mod tests { use alloc::format; @@ -402,234 +339,256 @@ mod tests { use pki_types::{PrivatePkcs1KeyDer, PrivateSec1KeyDer}; use super::*; + use crate::DEFAULT_PROVIDER; #[test] fn can_load_ecdsa_nistp256_pkcs8() { - let key = - PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/nistp256key.pkcs8.der")[..]); - assert!(any_eddsa_type(&key).is_err()); + let key = PrivatePkcs8KeyDer::from( + &include_bytes!("../../rustls/src/testdata/nistp256key.pkcs8.der")[..], + ); + assert!(Ed25519Signer::try_from(&key).is_err()); let key = PrivateKeyDer::Pkcs8(key); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_ok()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_ok()); } #[test] fn can_load_ecdsa_nistp256_sec1() { let key = PrivateKeyDer::Sec1(PrivateSec1KeyDer::from( - &include_bytes!("../../testdata/nistp256key.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp256key.der")[..], )); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_ok()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_ok()); } #[test] fn can_sign_ecdsa_nistp256() { let key = PrivateKeyDer::Sec1(PrivateSec1KeyDer::from( - &include_bytes!("../../testdata/nistp256key.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp256key.der")[..], )); - let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "EcdsaSigningKey { algorithm: ECDSA }"); - assert_eq!(k.algorithm(), SignatureAlgorithm::ECDSA); + let k = load_key(&DEFAULT_PROVIDER, key.clone_key()).unwrap(); + assert_eq!( + format!("{k:?}"), + "EcdsaSigner { scheme: ECDSA_NISTP256_SHA256, .. }" + ); - assert!(k - .choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) - .is_none()); - assert!(k - .choose_scheme(&[SignatureScheme::ECDSA_NISTP384_SHA384]) - .is_none()); + assert!( + k.choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) + .is_none() + ); + assert!( + k.choose_scheme(&[SignatureScheme::ECDSA_NISTP384_SHA384]) + .is_none() + ); let s = k .choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) .unwrap(); assert_eq!( - format!("{:?}", s), - "EcdsaSigner { scheme: ECDSA_NISTP256_SHA256 }" + format!("{s:?}"), + "EcdsaSigner { scheme: ECDSA_NISTP256_SHA256, .. }" ); assert_eq!(s.scheme(), SignatureScheme::ECDSA_NISTP256_SHA256); // nb. signature is variable length and asn.1-encoded - assert!(s - .sign(b"hello") - .unwrap() - .starts_with(&[0x30])); + assert!( + s.sign(b"hello") + .unwrap() + .starts_with(&[0x30]) + ); } #[test] fn can_load_ecdsa_nistp384_pkcs8() { - let key = - PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/nistp384key.pkcs8.der")[..]); - assert!(any_eddsa_type(&key).is_err()); + let key = PrivatePkcs8KeyDer::from( + &include_bytes!("../../rustls/src/testdata/nistp384key.pkcs8.der")[..], + ); + assert!(Ed25519Signer::try_from(&key).is_err()); let key = PrivateKeyDer::Pkcs8(key); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_ok()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_ok()); } #[test] fn can_load_ecdsa_nistp384_sec1() { let key = PrivateKeyDer::Sec1(PrivateSec1KeyDer::from( - &include_bytes!("../../testdata/nistp384key.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp384key.der")[..], )); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_ok()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_ok()); } #[test] fn can_sign_ecdsa_nistp384() { let key = PrivateKeyDer::Sec1(PrivateSec1KeyDer::from( - &include_bytes!("../../testdata/nistp384key.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp384key.der")[..], )); - let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "EcdsaSigningKey { algorithm: ECDSA }"); - assert_eq!(k.algorithm(), SignatureAlgorithm::ECDSA); + let k = load_key(&DEFAULT_PROVIDER, key.clone_key()).unwrap(); + assert_eq!( + format!("{k:?}"), + "EcdsaSigner { scheme: ECDSA_NISTP384_SHA384, .. }" + ); - assert!(k - .choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) - .is_none()); - assert!(k - .choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) - .is_none()); + assert!( + k.choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) + .is_none() + ); + assert!( + k.choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) + .is_none() + ); let s = k .choose_scheme(&[SignatureScheme::ECDSA_NISTP384_SHA384]) .unwrap(); assert_eq!( - format!("{:?}", s), - "EcdsaSigner { scheme: ECDSA_NISTP384_SHA384 }" + format!("{s:?}"), + "EcdsaSigner { scheme: ECDSA_NISTP384_SHA384, .. }" ); assert_eq!(s.scheme(), SignatureScheme::ECDSA_NISTP384_SHA384); // nb. signature is variable length and asn.1-encoded - assert!(s - .sign(b"hello") - .unwrap() - .starts_with(&[0x30])); + assert!( + s.sign(b"hello") + .unwrap() + .starts_with(&[0x30]) + ); } #[test] fn can_load_ecdsa_nistp521_pkcs8() { - let key = - PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/nistp521key.pkcs8.der")[..]); - assert!(any_eddsa_type(&key).is_err()); + let key = PrivatePkcs8KeyDer::from( + &include_bytes!("../../rustls/src/testdata/nistp521key.pkcs8.der")[..], + ); + assert!(Ed25519Signer::try_from(&key).is_err()); let key = PrivateKeyDer::Pkcs8(key); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_ok()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_ok()); } #[test] fn can_load_ecdsa_nistp521_sec1() { let key = PrivateKeyDer::Sec1(PrivateSec1KeyDer::from( - &include_bytes!("../../testdata/nistp521key.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp521key.der")[..], )); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_ok()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_ok()); } #[test] fn can_sign_ecdsa_nistp521() { let key = PrivateKeyDer::Sec1(PrivateSec1KeyDer::from( - &include_bytes!("../../testdata/nistp521key.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp521key.der")[..], )); - let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "EcdsaSigningKey { algorithm: ECDSA }"); - assert_eq!(k.algorithm(), SignatureAlgorithm::ECDSA); + let k = load_key(&DEFAULT_PROVIDER, key.clone_key()).unwrap(); + assert_eq!( + format!("{k:?}"), + "EcdsaSigner { scheme: ECDSA_NISTP521_SHA512, .. }" + ); - assert!(k - .choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) - .is_none()); - assert!(k - .choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) - .is_none()); - assert!(k - .choose_scheme(&[SignatureScheme::ECDSA_NISTP384_SHA384]) - .is_none()); + assert!( + k.choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) + .is_none() + ); + assert!( + k.choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) + .is_none() + ); + assert!( + k.choose_scheme(&[SignatureScheme::ECDSA_NISTP384_SHA384]) + .is_none() + ); let s = k .choose_scheme(&[SignatureScheme::ECDSA_NISTP521_SHA512]) .unwrap(); assert_eq!( - format!("{:?}", s), - "EcdsaSigner { scheme: ECDSA_NISTP521_SHA512 }" + format!("{s:?}"), + "EcdsaSigner { scheme: ECDSA_NISTP521_SHA512, .. }" ); assert_eq!(s.scheme(), SignatureScheme::ECDSA_NISTP521_SHA512); // nb. signature is variable length and asn.1-encoded - assert!(s - .sign(b"hello") - .unwrap() - .starts_with(&[0x30])); + assert!( + s.sign(b"hello") + .unwrap() + .starts_with(&[0x30]) + ); } #[test] fn can_load_eddsa_pkcs8() { - let key = PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/eddsakey.der")[..]); - assert!(any_eddsa_type(&key).is_ok()); + let key = + PrivatePkcs8KeyDer::from(&include_bytes!("../../rustls/src/testdata/eddsakey.der")[..]); + assert!(Ed25519Signer::try_from(&key).is_ok()); let key = PrivateKeyDer::Pkcs8(key); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_err()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_err()); } #[test] fn can_sign_eddsa() { - let key = PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/eddsakey.der")[..]); + let key = + PrivatePkcs8KeyDer::from(&include_bytes!("../../rustls/src/testdata/eddsakey.der")[..]); - let k = any_eddsa_type(&key).unwrap(); - assert_eq!( - format!("{:?}", k), - "Ed25519SigningKey { algorithm: ED25519 }" - ); - assert_eq!(k.algorithm(), SignatureAlgorithm::ED25519); + let k = Ed25519Signer::try_from(&key).unwrap(); + assert_eq!(format!("{k:?}"), "Ed25519Signer { scheme: ED25519, .. }"); - assert!(k - .choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) - .is_none()); - assert!(k - .choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) - .is_none()); + assert!( + k.choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) + .is_none() + ); + assert!( + k.choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) + .is_none() + ); let s = k .choose_scheme(&[SignatureScheme::ED25519]) .unwrap(); - assert_eq!(format!("{:?}", s), "Ed25519Signer { scheme: ED25519 }"); + assert_eq!(format!("{s:?}"), "Ed25519Signer { scheme: ED25519, .. }"); assert_eq!(s.scheme(), SignatureScheme::ED25519); assert_eq!(s.sign(b"hello").unwrap().len(), 64); } #[test] fn can_load_rsa2048_pkcs8() { - let key = - PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/rsa2048key.pkcs8.der")[..]); - assert!(any_eddsa_type(&key).is_err()); + let key = PrivatePkcs8KeyDer::from( + &include_bytes!("../../rustls/src/testdata/rsa2048key.pkcs8.der")[..], + ); + assert!(Ed25519Signer::try_from(&key).is_err()); let key = PrivateKeyDer::Pkcs8(key); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_err()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_err()); } #[test] fn can_load_rsa2048_pkcs1() { let key = PrivateKeyDer::Pkcs1(PrivatePkcs1KeyDer::from( - &include_bytes!("../../testdata/rsa2048key.pkcs1.der")[..], + &include_bytes!("../../rustls/src/testdata/rsa2048key.pkcs1.der")[..], )); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_err()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_err()); } #[test] fn can_sign_rsa2048() { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/rsa2048key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/rsa2048key.pkcs8.der")[..], )); - let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "RsaSigningKey { algorithm: RSA }"); - assert_eq!(k.algorithm(), SignatureAlgorithm::RSA); + let k = load_key(&DEFAULT_PROVIDER, key.clone_key()).unwrap(); + assert_eq!(format!("{k:?}"), "RsaSigningKey { .. }"); - assert!(k - .choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) - .is_none()); - assert!(k - .choose_scheme(&[SignatureScheme::ED25519]) - .is_none()); + assert!( + k.choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) + .is_none() + ); + assert!( + k.choose_scheme(&[SignatureScheme::ED25519]) + .is_none() + ); let s = k .choose_scheme(&[SignatureScheme::RSA_PSS_SHA256]) .unwrap(); - assert_eq!(format!("{:?}", s), "RsaSigner { scheme: RSA_PSS_SHA256 }"); + assert_eq!(format!("{s:?}"), "RsaSigner { scheme: RSA_PSS_SHA256, .. }"); assert_eq!(s.scheme(), SignatureScheme::RSA_PSS_SHA256); assert_eq!(s.sign(b"hello").unwrap().len(), 256); @@ -649,19 +608,19 @@ mod tests { fn cannot_load_invalid_pkcs8_encoding() { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(&b"invalid"[..])); assert_eq!( - any_supported_type(&key).err(), + load_key(&DEFAULT_PROVIDER, key.clone_key()).err(), Some(Error::General( "failed to parse private key as RSA, ECDSA, or EdDSA".into() )) ); assert_eq!( - any_ecdsa_type(&key).err(), + EcdsaSigner::try_from(&key).err(), Some(Error::General( "failed to parse ECDSA private key as PKCS#8 or SEC1".into() )) ); assert_eq!( - RsaSigningKey::new(&key).err(), + RsaSigningKey::try_from(&key).err(), Some(Error::General( "failed to parse RSA private key: InvalidEncoding".into() )) @@ -669,19 +628,20 @@ mod tests { } } -#[cfg(bench)] +#[cfg(all(test, bench))] mod benchmarks { - use super::{PrivateKeyDer, PrivatePkcs8KeyDer, SignatureScheme}; + use super::*; + use crate::DEFAULT_PROVIDER; #[bench] fn bench_rsa2048_pkcs1_sha256(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/rsa2048key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/rsa2048key.pkcs8.der")[..], )); - let sk = super::any_supported_type(&key).unwrap(); - let signer = sk - .choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) - .unwrap(); + + let signer = RsaSigningKey::try_from(&key) + .unwrap() + .to_signer(SignatureScheme::RSA_PKCS1_SHA256); b.iter(|| { test::black_box( @@ -695,12 +655,12 @@ mod benchmarks { #[bench] fn bench_rsa2048_pss_sha256(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/rsa2048key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/rsa2048key.pkcs8.der")[..], )); - let sk = super::any_supported_type(&key).unwrap(); - let signer = sk - .choose_scheme(&[SignatureScheme::RSA_PSS_SHA256]) - .unwrap(); + + let signer = RsaSigningKey::try_from(&key) + .unwrap() + .to_signer(SignatureScheme::RSA_PSS_SHA256); b.iter(|| { test::black_box( @@ -713,13 +673,9 @@ mod benchmarks { #[bench] fn bench_eddsa(b: &mut test::Bencher) { - let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/eddsakey.der")[..], - )); - let sk = super::any_supported_type(&key).unwrap(); - let signer = sk - .choose_scheme(&[SignatureScheme::ED25519]) - .unwrap(); + let key = + PrivatePkcs8KeyDer::from(&include_bytes!("../../rustls/src/testdata/eddsakey.der")[..]); + let signer = Ed25519Signer::try_from(&key).unwrap(); b.iter(|| { test::black_box( @@ -733,13 +689,10 @@ mod benchmarks { #[bench] fn bench_ecdsa_p256_sha256(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/nistp256key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp256key.pkcs8.der")[..], )); - let sk = super::any_supported_type(&key).unwrap(); - let signer = sk - .choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) - .unwrap(); + let signer = EcdsaSigner::try_from(&key).unwrap(); b.iter(|| { test::black_box( signer @@ -752,13 +705,10 @@ mod benchmarks { #[bench] fn bench_ecdsa_p384_sha384(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/nistp384key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp384key.pkcs8.der")[..], )); - let sk = super::any_supported_type(&key).unwrap(); - let signer = sk - .choose_scheme(&[SignatureScheme::ECDSA_NISTP384_SHA384]) - .unwrap(); + let signer = EcdsaSigner::try_from(&key).unwrap(); b.iter(|| { test::black_box( signer @@ -771,13 +721,10 @@ mod benchmarks { #[bench] fn bench_ecdsa_p521_sha512(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/nistp521key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp521key.pkcs8.der")[..], )); - let sk = super::any_supported_type(&key).unwrap(); - let signer = sk - .choose_scheme(&[SignatureScheme::ECDSA_NISTP521_SHA512]) - .unwrap(); + let signer = EcdsaSigner::try_from(&key).unwrap(); b.iter(|| { test::black_box( signer @@ -790,64 +737,65 @@ mod benchmarks { #[bench] fn bench_load_and_validate_rsa2048(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/rsa2048key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/rsa2048key.pkcs8.der")[..], )); b.iter(|| { - test::black_box(super::any_supported_type(&key).unwrap()); + test::black_box(load_key(&DEFAULT_PROVIDER, key.clone_key()).unwrap()); }); } #[bench] fn bench_load_and_validate_rsa4096(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/rsa4096key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/rsa4096key.pkcs8.der")[..], )); b.iter(|| { - test::black_box(super::any_supported_type(&key).unwrap()); + test::black_box(load_key(&DEFAULT_PROVIDER, key.clone_key()).unwrap()); }); } #[bench] fn bench_load_and_validate_p256(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/nistp256key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp256key.pkcs8.der")[..], )); b.iter(|| { - test::black_box(super::any_ecdsa_type(&key).unwrap()); + test::black_box(EcdsaSigner::try_from(&key).unwrap()); }); } #[bench] fn bench_load_and_validate_p384(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/nistp384key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp384key.pkcs8.der")[..], )); b.iter(|| { - test::black_box(super::any_ecdsa_type(&key).unwrap()); + test::black_box(EcdsaSigner::try_from(&key).unwrap()); }); } #[bench] fn bench_load_and_validate_p521(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/nistp521key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp521key.pkcs8.der")[..], )); b.iter(|| { - test::black_box(super::any_ecdsa_type(&key).unwrap()); + test::black_box(EcdsaSigner::try_from(&key).unwrap()); }); } #[bench] fn bench_load_and_validate_eddsa(b: &mut test::Bencher) { - let key = PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/eddsakey.der")[..]); + let key = + PrivatePkcs8KeyDer::from(&include_bytes!("../../rustls/src/testdata/eddsakey.der")[..]); b.iter(|| { - test::black_box(super::any_eddsa_type(&key).unwrap()); + test::black_box(Ed25519Signer::try_from(&key).unwrap()); }); } diff --git a/rustls-aws-lc-rs/src/ticketer.rs b/rustls-aws-lc-rs/src/ticketer.rs new file mode 100644 index 00000000000..505d944024a --- /dev/null +++ b/rustls-aws-lc-rs/src/ticketer.rs @@ -0,0 +1,230 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::fmt; +use core::fmt::{Debug, Formatter}; +use core::sync::atomic::{AtomicUsize, Ordering}; +use core::time::Duration; + +use aws_lc_rs::cipher::{ + AES_256, AES_256_KEY_LEN, AES_CBC_IV_LEN, DecryptionContext, PaddedBlockDecryptingKey, + PaddedBlockEncryptingKey, UnboundCipherKey, +}; +use aws_lc_rs::rand::{SecureRandom, SystemRandom}; +use aws_lc_rs::{hmac, iv}; +use rustls::crypto::{GetRandomFailed, TicketProducer}; +use rustls::error::Error; + +use super::unspecified_err; + +/// An RFC 5077 "Recommended Ticket Construction" implementation of a [`TicketProducer`]. +pub(super) struct Rfc5077Ticketer { + aes_encrypt_key: PaddedBlockEncryptingKey, + aes_decrypt_key: PaddedBlockDecryptingKey, + hmac_key: hmac::Key, + key_name: [u8; 16], + maximum_ciphertext_len: AtomicUsize, +} + +impl Rfc5077Ticketer { + #[expect(clippy::new_ret_no_self)] + pub(super) fn new() -> Result, Error> { + let rand = SystemRandom::new(); + + // Generate a random AES 256 key to use for AES CBC encryption. + let mut aes_key = [0u8; AES_256_KEY_LEN]; + rand.fill(&mut aes_key) + .map_err(|_| GetRandomFailed)?; + + // Convert the raw AES 256 key bytes into encrypting and decrypting keys using CBC mode and + // PKCS#7 padding. We don't want to store just the raw key bytes as constructing the + // cipher keys has some setup overhead. We can't store just the `UnboundCipherKey` since + // constructing the padded encrypt/decrypt specific types consume the `UnboundCipherKey`. + let aes_encrypt_key = + UnboundCipherKey::new(&AES_256, &aes_key[..]).map_err(unspecified_err)?; + let aes_encrypt_key = + PaddedBlockEncryptingKey::cbc_pkcs7(aes_encrypt_key).map_err(unspecified_err)?; + + // Convert the raw AES 256 key bytes into a decrypting key using CBC PKCS#7 padding. + let aes_decrypt_key = + UnboundCipherKey::new(&AES_256, &aes_key[..]).map_err(unspecified_err)?; + let aes_decrypt_key = + PaddedBlockDecryptingKey::cbc_pkcs7(aes_decrypt_key).map_err(unspecified_err)?; + + // Generate a random HMAC SHA256 key to use for HMAC authentication. + let hmac_key = hmac::Key::generate(hmac::HMAC_SHA256, &rand).map_err(unspecified_err)?; + + // Generate a random key name. + let mut key_name = [0u8; 16]; + rand.fill(&mut key_name) + .map_err(|_| GetRandomFailed)?; + + Ok(Box::new(Self { + aes_encrypt_key, + aes_decrypt_key, + hmac_key, + key_name, + maximum_ciphertext_len: AtomicUsize::new(0), + })) + } +} + +impl TicketProducer for Rfc5077Ticketer { + /// Encrypt `message` and return the ciphertext. + fn encrypt(&self, message: &[u8]) -> Option> { + // Encrypt the ticket state - the cipher module handles generating a random IV of + // appropriate size, returning it in the `DecryptionContext`. + let mut encrypted_state = Vec::from(message); + let dec_ctx = self + .aes_encrypt_key + .encrypt(&mut encrypted_state) + .ok()?; + let iv: &[u8] = (&dec_ctx).try_into().ok()?; + + // Produce the MAC tag over the relevant context & encrypted state. + // Quoting RFC 5077: + // "The Message Authentication Code (MAC) is calculated using HMAC-SHA-256 over + // key_name (16 octets) and IV (16 octets), followed by the length of + // the encrypted_state field (2 octets) and its contents (variable + // length)." + let mut hmac_data = + Vec::with_capacity(self.key_name.len() + iv.len() + 2 + encrypted_state.len()); + hmac_data.extend(&self.key_name); + hmac_data.extend(iv); + hmac_data.extend( + u16::try_from(encrypted_state.len()) + .ok()? + .to_be_bytes(), + ); + hmac_data.extend(&encrypted_state); + let tag = hmac::sign(&self.hmac_key, &hmac_data); + let tag = tag.as_ref(); + + // Combine the context, the encrypted state, and the tag to produce the final ciphertext. + // Ciphertext structure is: + // key_name: [u8; 16] + // iv: [u8; 16] + // encrypted_state: [u8, _] + // mac tag: [u8; 32] + let mut ciphertext = + Vec::with_capacity(self.key_name.len() + iv.len() + encrypted_state.len() + tag.len()); + ciphertext.extend(self.key_name); + ciphertext.extend(iv); + ciphertext.extend(encrypted_state); + ciphertext.extend(tag); + + self.maximum_ciphertext_len + .fetch_max(ciphertext.len(), Ordering::SeqCst); + + Some(ciphertext) + } + + fn decrypt(&self, ciphertext: &[u8]) -> Option> { + if ciphertext.len() + > self + .maximum_ciphertext_len + .load(Ordering::SeqCst) + { + return None; + } + + // Split off the key name from the remaining ciphertext. + let (alleged_key_name, ciphertext) = ciphertext.split_at_checked(self.key_name.len())?; + + // Split off the IV from the remaining ciphertext. + let (iv, ciphertext) = ciphertext.split_at_checked(AES_CBC_IV_LEN)?; + + // And finally, split the encrypted state from the tag. + let tag_len = self + .hmac_key + .algorithm() + .digest_algorithm() + .output_len(); + let (enc_state, mac) = ciphertext.split_at_checked(ciphertext.len() - tag_len)?; + + // Reconstitute the HMAC data to verify the tag. + let mut hmac_data = + Vec::with_capacity(alleged_key_name.len() + iv.len() + 2 + enc_state.len()); + hmac_data.extend(alleged_key_name); + hmac_data.extend(iv); + hmac_data.extend( + u16::try_from(enc_state.len()) + .ok()? + .to_be_bytes(), + ); + hmac_data.extend(enc_state); + hmac::verify(&self.hmac_key, &hmac_data, mac).ok()?; + + // Convert the raw IV back into an appropriate decryption context. + let iv = iv::FixedLength::try_from(iv).ok()?; + let dec_context = DecryptionContext::Iv128(iv); + + // And finally, decrypt the encrypted state. + let mut out = Vec::from(enc_state); + let plaintext = self + .aes_decrypt_key + .decrypt(&mut out, dec_context) + .ok()?; + + Some(plaintext.into()) + } + + fn lifetime(&self) -> Duration { + // this is not used, as this ticketer is only used via a `TicketRotator` + // that is responsible for defining and managing the lifetime of tickets. + Duration::ZERO + } +} + +impl Debug for Rfc5077Ticketer { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // Note: we deliberately omit keys from the debug output. + f.debug_struct("Rfc5077Ticketer") + .finish_non_exhaustive() + } +} + +#[cfg(test)] +mod tests { + use rustls::crypto::TicketerFactory; + + use crate::AwsLcRs; + + #[test] + fn basic_pairwise_test() { + let t = AwsLcRs.ticketer().unwrap(); + let cipher = t.encrypt(b"hello world").unwrap(); + let plain = t.decrypt(&cipher).unwrap(); + assert_eq!(plain, b"hello world"); + } + + #[test] + fn refuses_decrypt_before_encrypt() { + let t = AwsLcRs.ticketer().unwrap(); + assert_eq!(t.decrypt(b"hello"), None); + } + + #[test] + fn refuses_decrypt_larger_than_largest_encryption() { + let t = AwsLcRs.ticketer().unwrap(); + let mut cipher = t.encrypt(b"hello world").unwrap(); + assert_eq!(t.decrypt(&cipher), Some(b"hello world".to_vec())); + + // obviously this would never work anyway, but this + // and `cannot_decrypt_before_encrypt` exercise the + // first branch in `decrypt()` + cipher.push(0); + assert_eq!(t.decrypt(&cipher), None); + } + + #[test] + fn rfc5077ticketer_is_debug_and_producestickets() { + use alloc::format; + + use super::*; + + let t = Rfc5077Ticketer::new().unwrap(); + + assert_eq!(format!("{t:?}"), "Rfc5077Ticketer { .. }"); + assert_eq!(t.lifetime(), Duration::ZERO); + } +} diff --git a/rustls/src/crypto/aws_lc_rs/tls12.rs b/rustls-aws-lc-rs/src/tls12.rs similarity index 63% rename from rustls/src/crypto/aws_lc_rs/tls12.rs rename to rustls-aws-lc-rs/src/tls12.rs index 38b368353e6..757ad90f817 100644 --- a/rustls/src/crypto/aws_lc_rs/tls12.rs +++ b/rustls-aws-lc-rs/src/tls12.rs @@ -1,106 +1,106 @@ use alloc::boxed::Box; use aws_lc_rs::{aead, tls_prf}; - -use crate::crypto::cipher::{ - make_tls12_aad, AeadKey, InboundOpaqueMessage, Iv, KeyBlockShape, MessageDecrypter, - MessageEncrypter, Nonce, Tls12AeadAlgorithm, UnsupportedOperationError, NONCE_LEN, -}; -use crate::crypto::tls12::Prf; -use crate::crypto::{ActiveKeyExchange, KeyExchangeAlgorithm}; -use crate::enums::{CipherSuite, SignatureScheme}; -use crate::error::Error; -use crate::msgs::fragmenter::MAX_FRAGMENT_LEN; -use crate::msgs::message::{ - InboundPlainMessage, OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, +use pki_types::FipsStatus; +use rustls::crypto::cipher::{ + AeadKey, EncodedMessage, InboundOpaque, Iv, KeyBlockShape, MessageDecrypter, MessageEncrypter, + NONCE_LEN, Nonce, OutboundOpaque, OutboundPlain, Tls12AeadAlgorithm, UnsupportedOperationError, + make_tls12_aad, }; -use crate::suites::{CipherSuiteCommon, ConnectionTrafficSecrets, SupportedCipherSuite}; -use crate::tls12::Tls12CipherSuite; -use crate::version::TLS12; +use rustls::crypto::kx::{ActiveKeyExchange, KeyExchangeAlgorithm, SharedSecret}; +use rustls::crypto::tls12::{Prf, PrfSecret}; +use rustls::crypto::{CipherSuite, SignatureScheme}; +use rustls::enums::ProtocolVersion; +use rustls::error::Error; +use rustls::version::TLS12_VERSION; +use rustls::{CipherSuiteCommon, ConnectionTrafficSecrets, Tls12CipherSuite}; +use zeroize::Zeroizing; + +use crate::MAX_FRAGMENT_LEN; /// The TLS1.2 ciphersuite TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256. -pub static TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - hash_provider: &super::hash::SHA256, - confidentiality_limit: u64::MAX, - }, - kx: KeyExchangeAlgorithm::ECDHE, - sign: TLS12_ECDSA_SCHEMES, - aead_alg: &ChaCha20Poly1305, - prf_provider: &Tls12Prf(&tls_prf::P_SHA256), - }); +pub static TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: &Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + hash_provider: &super::hash::SHA256, + confidentiality_limit: u64::MAX, + }, + protocol_version: TLS12_VERSION, + prf_provider: &Tls12Prf(&tls_prf::P_SHA256), + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_ECDSA_SCHEMES, + aead_alg: &ChaCha20Poly1305, +}; /// The TLS1.2 ciphersuite TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 -pub static TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - hash_provider: &super::hash::SHA256, - confidentiality_limit: u64::MAX, - }, - kx: KeyExchangeAlgorithm::ECDHE, - sign: TLS12_RSA_SCHEMES, - aead_alg: &ChaCha20Poly1305, - prf_provider: &Tls12Prf(&tls_prf::P_SHA256), - }); +pub static TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: &Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + hash_provider: &super::hash::SHA256, + confidentiality_limit: u64::MAX, + }, + protocol_version: TLS12_VERSION, + prf_provider: &Tls12Prf(&tls_prf::P_SHA256), + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_RSA_SCHEMES, + aead_alg: &ChaCha20Poly1305, +}; /// The TLS1.2 ciphersuite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 -pub static TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - hash_provider: &super::hash::SHA256, - confidentiality_limit: 1 << 24, - }, - kx: KeyExchangeAlgorithm::ECDHE, - sign: TLS12_RSA_SCHEMES, - aead_alg: &AES128_GCM, - prf_provider: &Tls12Prf(&tls_prf::P_SHA256), - }); +pub static TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: &Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + hash_provider: &super::hash::SHA256, + confidentiality_limit: 1 << 24, + }, + protocol_version: TLS12_VERSION, + prf_provider: &Tls12Prf(&tls_prf::P_SHA256), + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_RSA_SCHEMES, + aead_alg: &AES128_GCM, +}; /// The TLS1.2 ciphersuite TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 -pub static TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - hash_provider: &super::hash::SHA384, - confidentiality_limit: 1 << 24, - }, - kx: KeyExchangeAlgorithm::ECDHE, - sign: TLS12_RSA_SCHEMES, - aead_alg: &AES256_GCM, - prf_provider: &Tls12Prf(&tls_prf::P_SHA384), - }); +pub static TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: &Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + hash_provider: &super::hash::SHA384, + confidentiality_limit: 1 << 24, + }, + protocol_version: TLS12_VERSION, + prf_provider: &Tls12Prf(&tls_prf::P_SHA384), + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_RSA_SCHEMES, + aead_alg: &AES256_GCM, +}; /// The TLS1.2 ciphersuite TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 -pub static TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - hash_provider: &super::hash::SHA256, - confidentiality_limit: 1 << 24, - }, - kx: KeyExchangeAlgorithm::ECDHE, - sign: TLS12_ECDSA_SCHEMES, - aead_alg: &AES128_GCM, - prf_provider: &Tls12Prf(&tls_prf::P_SHA256), - }); +pub static TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: &Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + hash_provider: &super::hash::SHA256, + confidentiality_limit: 1 << 24, + }, + protocol_version: TLS12_VERSION, + prf_provider: &Tls12Prf(&tls_prf::P_SHA256), + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_ECDSA_SCHEMES, + aead_alg: &AES128_GCM, +}; /// The TLS1.2 ciphersuite TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 -pub static TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - hash_provider: &super::hash::SHA384, - confidentiality_limit: 1 << 24, - }, - kx: KeyExchangeAlgorithm::ECDHE, - sign: TLS12_ECDSA_SCHEMES, - aead_alg: &AES256_GCM, - prf_provider: &Tls12Prf(&tls_prf::P_SHA384), - }); +pub static TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: &Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + hash_provider: &super::hash::SHA384, + confidentiality_limit: 1 << 24, + }, + protocol_version: TLS12_VERSION, + prf_provider: &Tls12Prf(&tls_prf::P_SHA384), + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_ECDSA_SCHEMES, + aead_alg: &AES256_GCM, +}; static TLS12_ECDSA_SCHEMES: &[SignatureScheme] = &[ SignatureScheme::ED25519, @@ -187,7 +187,7 @@ impl Tls12AeadAlgorithm for GcmAlgorithm { }) } - fn fips(&self) -> bool { + fn fips(&self) -> FipsStatus { super::fips() } } @@ -201,7 +201,7 @@ impl Tls12AeadAlgorithm for ChaCha20Poly1305 { ); Box::new(ChaCha20Poly1305MessageDecrypter { dec_key, - dec_offset: Iv::copy(iv), + dec_offset: Iv::new(iv).expect("IV length validated by key_block_shape"), }) } @@ -211,7 +211,7 @@ impl Tls12AeadAlgorithm for ChaCha20Poly1305 { ); Box::new(ChaCha20Poly1305MessageEncrypter { enc_key, - enc_offset: Iv::copy(enc_iv), + enc_offset: Iv::new(enc_iv).expect("IV length validated by key_block_shape"), }) } @@ -233,12 +233,12 @@ impl Tls12AeadAlgorithm for ChaCha20Poly1305 { debug_assert_eq!(aead::NONCE_LEN, iv.len()); Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { key, - iv: Iv::new(iv[..].try_into().unwrap()), + iv: Iv::new(iv).expect("IV length validated by key_block_shape"), }) } - fn fips(&self) -> bool { - false // not FIPS approved + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated // not FIPS approved } } @@ -260,9 +260,9 @@ const GCM_OVERHEAD: usize = GCM_EXPLICIT_NONCE_LEN + 16; impl MessageDecrypter for GcmMessageDecrypter { fn decrypt<'a>( &mut self, - mut msg: InboundOpaqueMessage<'a>, + mut msg: EncodedMessage>, seq: u64, - ) -> Result, Error> { + ) -> Result, Error> { let payload = &msg.payload; if payload.len() < GCM_OVERHEAD { return Err(Error::DecryptError); @@ -304,13 +304,13 @@ impl MessageDecrypter for GcmMessageDecrypter { impl MessageEncrypter for GcmMessageEncrypter { fn encrypt( &mut self, - msg: OutboundPlainMessage<'_>, + msg: EncodedMessage>, seq: u64, - ) -> Result { + ) -> Result, Error> { let total_len = self.encrypted_payload_len(msg.payload.len()); - let mut payload = PrefixedPayload::with_capacity(total_len); + let mut payload = OutboundOpaque::with_capacity(total_len); - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).0); + let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).to_array()?); let aad = aead::Aad::from(make_tls12_aad(seq, msg.typ, msg.version, msg.payload.len())); payload.extend_from_slice(&nonce.as_ref()[4..]); payload.extend_from_chunks(&msg.payload); @@ -320,7 +320,11 @@ impl MessageEncrypter for GcmMessageEncrypter { .map(|tag| payload.extend_from_slice(tag.as_ref())) .map_err(|_| Error::EncryptError)?; - Ok(OutboundOpaqueMessage::new(msg.typ, msg.version, payload)) + Ok(EncodedMessage { + typ: msg.typ, + version: msg.version, + payload, + }) } fn encrypted_payload_len(&self, payload_len: usize) -> usize { @@ -349,16 +353,17 @@ const CHACHAPOLY1305_OVERHEAD: usize = 16; impl MessageDecrypter for ChaCha20Poly1305MessageDecrypter { fn decrypt<'a>( &mut self, - mut msg: InboundOpaqueMessage<'a>, + mut msg: EncodedMessage>, seq: u64, - ) -> Result, Error> { + ) -> Result, Error> { let payload = &msg.payload; if payload.len() < CHACHAPOLY1305_OVERHEAD { return Err(Error::DecryptError); } - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.dec_offset, seq).0); + let nonce = + aead::Nonce::assume_unique_for_key(Nonce::new(&self.dec_offset, seq).to_array()?); let aad = aead::Aad::from(make_tls12_aad( seq, msg.typ, @@ -385,13 +390,14 @@ impl MessageDecrypter for ChaCha20Poly1305MessageDecrypter { impl MessageEncrypter for ChaCha20Poly1305MessageEncrypter { fn encrypt( &mut self, - msg: OutboundPlainMessage<'_>, + msg: EncodedMessage>, seq: u64, - ) -> Result { + ) -> Result, Error> { let total_len = self.encrypted_payload_len(msg.payload.len()); - let mut payload = PrefixedPayload::with_capacity(total_len); + let mut payload = OutboundOpaque::with_capacity(total_len); - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.enc_offset, seq).0); + let nonce = + aead::Nonce::assume_unique_for_key(Nonce::new(&self.enc_offset, seq).to_array()?); let aad = aead::Aad::from(make_tls12_aad(seq, msg.typ, msg.version, msg.payload.len())); payload.extend_from_chunks(&msg.payload); @@ -399,7 +405,11 @@ impl MessageEncrypter for ChaCha20Poly1305MessageEncrypter { .seal_in_place_append_tag(nonce, aad, &mut payload) .map_err(|_| Error::EncryptError)?; - Ok(OutboundOpaqueMessage::new(msg.typ, msg.version, payload)) + Ok(EncodedMessage { + typ: msg.typ, + version: msg.version, + payload, + }) } fn encrypted_payload_len(&self, payload_len: usize) -> usize { @@ -422,25 +432,12 @@ fn gcm_iv(write_iv: &[u8], explicit: &[u8]) -> Iv { iv[..4].copy_from_slice(write_iv); iv[4..].copy_from_slice(explicit); - Iv::new(iv) + Iv::new(&iv).expect("IV length is NONCE_LEN, which is within MAX_LEN") } struct Tls12Prf(&'static tls_prf::Algorithm); impl Prf for Tls12Prf { - fn for_secret(&self, output: &mut [u8], secret: &[u8], label: &[u8], seed: &[u8]) { - // safety: - // - [1] is safe because our caller guarantees `secret` is non-empty; this is - // the only documented error case. - // - [2] is safe in practice because the only failure from `derive()` is due - // to zero `output.len()`; this is outlawed at higher levels - let derived = tls_prf::Secret::new(self.0, secret) - .unwrap() // [1] - .derive(label, seed, output.len()) - .unwrap(); // [2] - output.copy_from_slice(derived.as_ref()); - } - fn for_key_exchange( &self, output: &mut [u8; 48], @@ -449,17 +446,60 @@ impl Prf for Tls12Prf { label: &[u8], seed: &[u8], ) -> Result<(), Error> { - self.for_secret( - output, - kx.complete_for_tls_version(peer_pub_key, &TLS12)? - .secret_bytes(), - label, - seed, - ); + Tls12PrfSecret { + alg: self.0, + secret: Secret::KeyExchange( + kx.complete_for_tls_version(peer_pub_key, ProtocolVersion::TLSv1_2)?, + ), + } + .prf(output, label, seed); Ok(()) } - fn fips(&self) -> bool { + fn new_secret(&self, secret: &[u8; 48]) -> Box { + Box::new(Tls12PrfSecret { + alg: self.0, + secret: Secret::Master(Zeroizing::new(*secret)), + }) + } + + fn fips(&self) -> FipsStatus { super::fips() } } + +// nb: we can't put a `tls_prf::Secret` in here because it is +// consumed by `tls_prf::Secret::derive()` +struct Tls12PrfSecret { + alg: &'static tls_prf::Algorithm, + secret: Secret, +} + +impl PrfSecret for Tls12PrfSecret { + fn prf(&self, output: &mut [u8], label: &[u8], seed: &[u8]) { + // safety: + // - [1] is safe because our caller guarantees `secret` is non-empty; this is + // the only documented error case. + // - [2] is safe in practice because the only failure from `derive()` is due + // to zero `output.len()`; this is outlawed at higher levels + let derived = tls_prf::Secret::new(self.alg, self.secret.as_ref()) + .unwrap() // [1] + .derive(label, seed, output.len()) + .unwrap(); // [2] + output.copy_from_slice(derived.as_ref()); + } +} + +enum Secret { + Master(Zeroizing<[u8; 48]>), + KeyExchange(SharedSecret), +} + +impl AsRef<[u8]> for Secret { + fn as_ref(&self) -> &[u8] { + match self { + Self::Master(ms) => ms.as_ref(), + Self::KeyExchange(kx) => kx.secret_bytes(), + } + } +} diff --git a/rustls/src/crypto/aws_lc_rs/tls13.rs b/rustls-aws-lc-rs/src/tls13.rs similarity index 77% rename from rustls/src/crypto/aws_lc_rs/tls13.rs rename to rustls-aws-lc-rs/src/tls13.rs index 9d234c82e44..0d813a1e5e4 100644 --- a/rustls/src/crypto/aws_lc_rs/tls13.rs +++ b/rustls-aws-lc-rs/src/tls13.rs @@ -2,32 +2,27 @@ use alloc::boxed::Box; use aws_lc_rs::hkdf::KeyType; use aws_lc_rs::{aead, hkdf, hmac}; - -use crate::crypto; -use crate::crypto::cipher::{ - make_tls13_aad, AeadKey, InboundOpaqueMessage, Iv, MessageDecrypter, MessageEncrypter, Nonce, - Tls13AeadAlgorithm, UnsupportedOperationError, -}; -use crate::crypto::tls13::{Hkdf, HkdfExpander, OkmBlock, OutputLengthError}; -use crate::enums::{CipherSuite, ContentType, ProtocolVersion}; -use crate::error::Error; -use crate::msgs::message::{ - InboundPlainMessage, OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, +use pki_types::FipsStatus; +use rustls::crypto::cipher::{ + AeadKey, EncodedMessage, InboundOpaque, Iv, MessageDecrypter, MessageEncrypter, Nonce, + OutboundOpaque, OutboundPlain, Tls13AeadAlgorithm, UnsupportedOperationError, make_tls13_aad, }; -use crate::suites::{CipherSuiteCommon, ConnectionTrafficSecrets, SupportedCipherSuite}; -use crate::tls13::Tls13CipherSuite; +use rustls::crypto::tls13::{Hkdf, HkdfExpander, OkmBlock, OutputLengthError}; +use rustls::crypto::{self, CipherSuite}; +use rustls::enums::{ContentType, ProtocolVersion}; +use rustls::error::Error; +use rustls::version::TLS13_VERSION; +use rustls::{CipherSuiteCommon, ConnectionTrafficSecrets, Tls13CipherSuite}; /// The TLS1.3 ciphersuite TLS_CHACHA20_POLY1305_SHA256 -pub static TLS13_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls13(TLS13_CHACHA20_POLY1305_SHA256_INTERNAL); - -pub(crate) static TLS13_CHACHA20_POLY1305_SHA256_INTERNAL: &Tls13CipherSuite = &Tls13CipherSuite { +pub static TLS13_CHACHA20_POLY1305_SHA256: &Tls13CipherSuite = &Tls13CipherSuite { common: CipherSuiteCommon { suite: CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, hash_provider: &super::hash::SHA256, // ref: confidentiality_limit: u64::MAX, }, + protocol_version: TLS13_VERSION, hkdf_provider: &AwsLcHkdf(hkdf::HKDF_SHA256, hmac::HMAC_SHA256), aead_alg: &Chacha20Poly1305Aead(AeadAlgorithm(&aead::CHACHA20_POLY1305)), quic: Some(&super::quic::KeyBuilder { @@ -41,35 +36,33 @@ pub(crate) static TLS13_CHACHA20_POLY1305_SHA256_INTERNAL: &Tls13CipherSuite = & }; /// The TLS1.3 ciphersuite TLS_AES_256_GCM_SHA384 -pub static TLS13_AES_256_GCM_SHA384: SupportedCipherSuite = - SupportedCipherSuite::Tls13(&Tls13CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS13_AES_256_GCM_SHA384, - hash_provider: &super::hash::SHA384, - confidentiality_limit: 1 << 24, - }, - hkdf_provider: &AwsLcHkdf(hkdf::HKDF_SHA384, hmac::HMAC_SHA384), - aead_alg: &Aes256GcmAead(AeadAlgorithm(&aead::AES_256_GCM)), - quic: Some(&super::quic::KeyBuilder { - packet_alg: &aead::AES_256_GCM, - header_alg: &aead::quic::AES_256, - // ref: - confidentiality_limit: 1 << 23, - // ref: - integrity_limit: 1 << 52, - }), - }); +pub static TLS13_AES_256_GCM_SHA384: &Tls13CipherSuite = &Tls13CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS13_AES_256_GCM_SHA384, + hash_provider: &super::hash::SHA384, + confidentiality_limit: 1 << 24, + }, + protocol_version: TLS13_VERSION, + hkdf_provider: &AwsLcHkdf(hkdf::HKDF_SHA384, hmac::HMAC_SHA384), + aead_alg: &Aes256GcmAead(AeadAlgorithm(&aead::AES_256_GCM)), + quic: Some(&super::quic::KeyBuilder { + packet_alg: &aead::AES_256_GCM, + header_alg: &aead::quic::AES_256, + // ref: + confidentiality_limit: 1 << 23, + // ref: + integrity_limit: 1 << 52, + }), +}; /// The TLS1.3 ciphersuite TLS_AES_128_GCM_SHA256 -pub static TLS13_AES_128_GCM_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls13(TLS13_AES_128_GCM_SHA256_INTERNAL); - -pub(crate) static TLS13_AES_128_GCM_SHA256_INTERNAL: &Tls13CipherSuite = &Tls13CipherSuite { +pub static TLS13_AES_128_GCM_SHA256: &Tls13CipherSuite = &Tls13CipherSuite { common: CipherSuiteCommon { suite: CipherSuite::TLS13_AES_128_GCM_SHA256, hash_provider: &super::hash::SHA256, confidentiality_limit: 1 << 24, }, + protocol_version: TLS13_VERSION, hkdf_provider: &AwsLcHkdf(hkdf::HKDF_SHA256, hmac::HMAC_SHA256), aead_alg: &Aes128GcmAead(AeadAlgorithm(&aead::AES_128_GCM)), quic: Some(&super::quic::KeyBuilder { @@ -88,9 +81,7 @@ impl Tls13AeadAlgorithm for Chacha20Poly1305Aead { fn encrypter(&self, key: AeadKey, iv: Iv) -> Box { // safety: the caller arranges that `key` is `key_len()` in bytes, so this unwrap is safe. Box::new(AeadMessageEncrypter { - enc_key: aead::LessSafeKey::new( - aead::UnboundKey::new(self.0 .0, key.as_ref()).unwrap(), - ), + enc_key: aead::LessSafeKey::new(aead::UnboundKey::new(self.0.0, key.as_ref()).unwrap()), iv, }) } @@ -98,9 +89,7 @@ impl Tls13AeadAlgorithm for Chacha20Poly1305Aead { fn decrypter(&self, key: AeadKey, iv: Iv) -> Box { // safety: the caller arranges that `key` is `key_len()` in bytes, so this unwrap is safe. Box::new(AeadMessageDecrypter { - dec_key: aead::LessSafeKey::new( - aead::UnboundKey::new(self.0 .0, key.as_ref()).unwrap(), - ), + dec_key: aead::LessSafeKey::new(aead::UnboundKey::new(self.0.0, key.as_ref()).unwrap()), iv, }) } @@ -117,8 +106,8 @@ impl Tls13AeadAlgorithm for Chacha20Poly1305Aead { Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }) } - fn fips(&self) -> bool { - false // not FIPS approved + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated // not FIPS approved } } @@ -145,7 +134,7 @@ impl Tls13AeadAlgorithm for Aes256GcmAead { Ok(ConnectionTrafficSecrets::Aes256Gcm { key, iv }) } - fn fips(&self) -> bool { + fn fips(&self) -> FipsStatus { super::fips() } } @@ -173,7 +162,7 @@ impl Tls13AeadAlgorithm for Aes128GcmAead { Ok(ConnectionTrafficSecrets::Aes128Gcm { key, iv }) } - fn fips(&self) -> bool { + fn fips(&self) -> FipsStatus { super::fips() } } @@ -232,13 +221,13 @@ struct AeadMessageDecrypter { impl MessageEncrypter for AeadMessageEncrypter { fn encrypt( &mut self, - msg: OutboundPlainMessage<'_>, + msg: EncodedMessage>, seq: u64, - ) -> Result { + ) -> Result, Error> { let total_len = self.encrypted_payload_len(msg.payload.len()); - let mut payload = PrefixedPayload::with_capacity(total_len); + let mut payload = OutboundOpaque::with_capacity(total_len); - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).0); + let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).to_array()?); let aad = aead::Aad::from(make_tls13_aad(total_len)); payload.extend_from_chunks(&msg.payload); payload.extend_from_slice(&msg.typ.to_array()); @@ -247,13 +236,13 @@ impl MessageEncrypter for AeadMessageEncrypter { .seal_in_place_append_tag(nonce, aad, &mut payload) .map_err(|_| Error::EncryptError)?; - Ok(OutboundOpaqueMessage::new( - ContentType::ApplicationData, + Ok(EncodedMessage { + typ: ContentType::ApplicationData, // Note: all TLS 1.3 application data records use TLSv1_2 (0x0303) as the legacy record // protocol version, see https://www.rfc-editor.org/rfc/rfc8446#section-5.1 - ProtocolVersion::TLSv1_2, + version: ProtocolVersion::TLSv1_2, payload, - )) + }) } fn encrypted_payload_len(&self, payload_len: usize) -> usize { @@ -264,15 +253,15 @@ impl MessageEncrypter for AeadMessageEncrypter { impl MessageDecrypter for AeadMessageDecrypter { fn decrypt<'a>( &mut self, - mut msg: InboundOpaqueMessage<'a>, + mut msg: EncodedMessage>, seq: u64, - ) -> Result, Error> { + ) -> Result, Error> { let payload = &mut msg.payload; if payload.len() < self.dec_key.algorithm().tag_len() { return Err(Error::DecryptError); } - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).0); + let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).to_array()?); let aad = aead::Aad::from(make_tls13_aad(payload.len())); let plain_len = self .dec_key @@ -293,13 +282,13 @@ struct GcmMessageEncrypter { impl MessageEncrypter for GcmMessageEncrypter { fn encrypt( &mut self, - msg: OutboundPlainMessage<'_>, + msg: EncodedMessage>, seq: u64, - ) -> Result { + ) -> Result, Error> { let total_len = self.encrypted_payload_len(msg.payload.len()); - let mut payload = PrefixedPayload::with_capacity(total_len); + let mut payload = OutboundOpaque::with_capacity(total_len); - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).0); + let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).to_array()?); let aad = aead::Aad::from(make_tls13_aad(total_len)); payload.extend_from_chunks(&msg.payload); payload.extend_from_slice(&msg.typ.to_array()); @@ -308,11 +297,11 @@ impl MessageEncrypter for GcmMessageEncrypter { .seal_in_place_append_tag(nonce, aad, &mut payload) .map_err(|_| Error::EncryptError)?; - Ok(OutboundOpaqueMessage::new( - ContentType::ApplicationData, - ProtocolVersion::TLSv1_2, + Ok(EncodedMessage { + typ: ContentType::ApplicationData, + version: ProtocolVersion::TLSv1_2, payload, - )) + }) } fn encrypted_payload_len(&self, payload_len: usize) -> usize { @@ -328,15 +317,15 @@ struct GcmMessageDecrypter { impl MessageDecrypter for GcmMessageDecrypter { fn decrypt<'a>( &mut self, - mut msg: InboundOpaqueMessage<'a>, + mut msg: EncodedMessage>, seq: u64, - ) -> Result, Error> { + ) -> Result, Error> { let payload = &mut msg.payload; if payload.len() < self.dec_key.algorithm().tag_len() { return Err(Error::DecryptError); } - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).0); + let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).to_array()?); let aad = aead::Aad::from(make_tls13_aad(payload.len())); let plain_len = self .dec_key @@ -387,7 +376,7 @@ impl Hkdf for AwsLcHkdf { crypto::hmac::Tag::new(hmac::sign(&hmac::Key::new(self.1, key.as_ref()), message).as_ref()) } - fn fips(&self) -> bool { + fn fips(&self) -> FipsStatus { super::fips() } } diff --git a/rustls-aws-lc-rs/src/verify.rs b/rustls-aws-lc-rs/src/verify.rs new file mode 100644 index 00000000000..e9f42fe8d05 --- /dev/null +++ b/rustls-aws-lc-rs/src/verify.rs @@ -0,0 +1,307 @@ +use aws_lc_rs::signature; +use pki_types::{ + AlgorithmIdentifier, FipsStatus, InvalidSignature, SignatureVerificationAlgorithm, alg_id, +}; + +// nb. aws-lc-rs has an API that is broadly compatible with *ring*, +// so this is very similar to ring_algs.rs. + +/// An array of all the verification algorithms exported by this crate. +/// +/// This will be empty if the crate is built without the `ring` and `aws-lc-rs` features. +pub static ALL_VERIFICATION_ALGS: &[&dyn SignatureVerificationAlgorithm] = &[ + ECDSA_P256_SHA256, + ECDSA_P256_SHA384, + ECDSA_P256_SHA512, + ECDSA_P384_SHA256, + ECDSA_P384_SHA384, + ECDSA_P384_SHA512, + ECDSA_P521_SHA256, + ECDSA_P521_SHA384, + ECDSA_P521_SHA512, + ED25519, + RSA_PKCS1_2048_8192_SHA256, + RSA_PKCS1_2048_8192_SHA384, + RSA_PKCS1_2048_8192_SHA512, + RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS, + RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS, + RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS, + RSA_PKCS1_3072_8192_SHA384, + RSA_PSS_2048_8192_SHA256_LEGACY_KEY, + RSA_PSS_2048_8192_SHA384_LEGACY_KEY, + RSA_PSS_2048_8192_SHA512_LEGACY_KEY, +]; + +/// A `SignatureVerificationAlgorithm` implemented using aws-lc-rs. +#[expect(clippy::exhaustive_structs)] +#[derive(Debug)] +pub struct AwsLcRsVerificationAlgorithm { + /// The public key algorithm identifier (for example, `id-ecPublicKey`). + pub public_key_alg_id: AlgorithmIdentifier, + /// The signature algorithm identifier (for example, `ecdsa-with-SHA256`). + pub signature_alg_id: AlgorithmIdentifier, + /// The aws-lc-rs verification algorithm to use for this signature algorithm. + pub verification_alg: &'static dyn signature::VerificationAlgorithm, + /// Whether this algorithm is included in the FIPS submission for aws-lc-rs. + pub in_fips_submission: bool, +} + +impl SignatureVerificationAlgorithm for AwsLcRsVerificationAlgorithm { + fn public_key_alg_id(&self) -> AlgorithmIdentifier { + self.public_key_alg_id + } + + fn signature_alg_id(&self) -> AlgorithmIdentifier { + self.signature_alg_id + } + + fn verify_signature( + &self, + public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), InvalidSignature> { + if matches!( + self.public_key_alg_id, + alg_id::ECDSA_P256 | alg_id::ECDSA_P384 | alg_id::ECDSA_P521 + ) { + // Restrict the allowed encodings of EC public keys. + // + // "The first octet of the OCTET STRING indicates whether the key is + // compressed or uncompressed. The uncompressed form is indicated + // by 0x04 and the compressed form is indicated by either 0x02 or + // 0x03 (see 2.3.3 in [SEC1]). The public key MUST be rejected if + // any other value is included in the first octet." + // -- + match public_key.first() { + Some(0x04) | Some(0x02) | Some(0x03) => {} + _ => return Err(InvalidSignature), + }; + } + signature::UnparsedPublicKey::new(self.verification_alg, public_key) + .verify(message, signature) + .map_err(|_| InvalidSignature) + } + + fn fips_status(&self) -> FipsStatus { + match self.in_fips_submission { + true => super::fips(), + false => FipsStatus::Unvalidated, + } + } +} + +/// ECDSA signatures using the P-256 curve and SHA-256. +pub static ECDSA_P256_SHA256: &dyn SignatureVerificationAlgorithm = &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::ECDSA_P256, + signature_alg_id: alg_id::ECDSA_SHA256, + verification_alg: &signature::ECDSA_P256_SHA256_ASN1, + in_fips_submission: true, +}; + +/// ECDSA signatures using the P-256 curve and SHA-384. Deprecated. +pub static ECDSA_P256_SHA384: &dyn SignatureVerificationAlgorithm = &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::ECDSA_P256, + signature_alg_id: alg_id::ECDSA_SHA384, + verification_alg: &signature::ECDSA_P256_SHA384_ASN1, + in_fips_submission: true, +}; + +/// ECDSA signatures using the P-256 curve and SHA-512. Deprecated. +pub static ECDSA_P256_SHA512: &dyn SignatureVerificationAlgorithm = &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::ECDSA_P256, + signature_alg_id: alg_id::ECDSA_SHA512, + verification_alg: &signature::ECDSA_P256_SHA512_ASN1, + in_fips_submission: true, +}; + +/// ECDSA signatures using the P-384 curve and SHA-256. Deprecated. +pub static ECDSA_P384_SHA256: &dyn SignatureVerificationAlgorithm = &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::ECDSA_P384, + signature_alg_id: alg_id::ECDSA_SHA256, + verification_alg: &signature::ECDSA_P384_SHA256_ASN1, + in_fips_submission: true, +}; + +/// ECDSA signatures using the P-384 curve and SHA-384. +pub static ECDSA_P384_SHA384: &dyn SignatureVerificationAlgorithm = &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::ECDSA_P384, + signature_alg_id: alg_id::ECDSA_SHA384, + verification_alg: &signature::ECDSA_P384_SHA384_ASN1, + in_fips_submission: true, +}; + +/// ECDSA signatures using the P-384 curve and SHA-512. Deprecated. +pub static ECDSA_P384_SHA512: &dyn SignatureVerificationAlgorithm = &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::ECDSA_P384, + signature_alg_id: alg_id::ECDSA_SHA512, + verification_alg: &signature::ECDSA_P384_SHA512_ASN1, + in_fips_submission: true, +}; + +/// ECDSA signatures using the P-521 curve and SHA-256. +pub static ECDSA_P521_SHA256: &dyn SignatureVerificationAlgorithm = &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::ECDSA_P521, + signature_alg_id: alg_id::ECDSA_SHA256, + verification_alg: &signature::ECDSA_P521_SHA256_ASN1, + in_fips_submission: true, +}; + +/// ECDSA signatures using the P-521 curve and SHA-384. +pub static ECDSA_P521_SHA384: &dyn SignatureVerificationAlgorithm = &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::ECDSA_P521, + signature_alg_id: alg_id::ECDSA_SHA384, + verification_alg: &signature::ECDSA_P521_SHA384_ASN1, + in_fips_submission: true, +}; + +/// ECDSA signatures using the P-521 curve and SHA-512. +pub static ECDSA_P521_SHA512: &dyn SignatureVerificationAlgorithm = &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::ECDSA_P521, + signature_alg_id: alg_id::ECDSA_SHA512, + verification_alg: &signature::ECDSA_P521_SHA512_ASN1, + in_fips_submission: true, +}; + +/// RSA PKCS#1 1.5 signatures using SHA-256 for keys of 2048-8192 bits. +pub static RSA_PKCS1_2048_8192_SHA256: &dyn SignatureVerificationAlgorithm = + &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PKCS1_SHA256, + verification_alg: &signature::RSA_PKCS1_2048_8192_SHA256, + in_fips_submission: true, + }; + +/// RSA PKCS#1 1.5 signatures using SHA-384 for keys of 2048-8192 bits. +pub static RSA_PKCS1_2048_8192_SHA384: &dyn SignatureVerificationAlgorithm = + &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PKCS1_SHA384, + verification_alg: &signature::RSA_PKCS1_2048_8192_SHA384, + in_fips_submission: true, + }; + +/// RSA PKCS#1 1.5 signatures using SHA-512 for keys of 2048-8192 bits. +pub static RSA_PKCS1_2048_8192_SHA512: &dyn SignatureVerificationAlgorithm = + &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PKCS1_SHA512, + verification_alg: &signature::RSA_PKCS1_2048_8192_SHA512, + in_fips_submission: true, + }; + +/// RSA PKCS#1 1.5 signatures using SHA-256 for keys of 2048-8192 bits, +/// with illegally absent AlgorithmIdentifier parameters. +/// +/// RFC4055 says on sha256WithRSAEncryption and company: +/// +/// > When any of these four object identifiers appears within an +/// > AlgorithmIdentifier, the parameters MUST be NULL. Implementations +/// > MUST accept the parameters being absent as well as present. +/// +/// This algorithm covers the absent case, [`RSA_PKCS1_2048_8192_SHA256`] covers +/// the present case. +pub static RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm = + &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: AlgorithmIdentifier::from_slice(include_bytes!( + "data/alg-rsa-pkcs1-sha256-absent-params.der" + )), + verification_alg: &signature::RSA_PKCS1_2048_8192_SHA256, + in_fips_submission: true, + }; + +/// RSA PKCS#1 1.5 signatures using SHA-384 for keys of 2048-8192 bits, +/// with illegally absent AlgorithmIdentifier parameters. +/// +/// RFC4055 says on sha256WithRSAEncryption and company: +/// +/// > When any of these four object identifiers appears within an +/// > AlgorithmIdentifier, the parameters MUST be NULL. Implementations +/// > MUST accept the parameters being absent as well as present. +/// +/// This algorithm covers the absent case, [`RSA_PKCS1_2048_8192_SHA384`] covers +/// the present case. +pub static RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm = + &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: AlgorithmIdentifier::from_slice(include_bytes!( + "data/alg-rsa-pkcs1-sha384-absent-params.der" + )), + verification_alg: &signature::RSA_PKCS1_2048_8192_SHA384, + in_fips_submission: true, + }; + +/// RSA PKCS#1 1.5 signatures using SHA-512 for keys of 2048-8192 bits, +/// with illegally absent AlgorithmIdentifier parameters. +/// +/// RFC4055 says on sha256WithRSAEncryption and company: +/// +/// > When any of these four object identifiers appears within an +/// > AlgorithmIdentifier, the parameters MUST be NULL. Implementations +/// > MUST accept the parameters being absent as well as present. +/// +/// This algorithm covers the absent case, [`RSA_PKCS1_2048_8192_SHA512`] covers +/// the present case. +pub static RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm = + &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: AlgorithmIdentifier::from_slice(include_bytes!( + "data/alg-rsa-pkcs1-sha512-absent-params.der" + )), + verification_alg: &signature::RSA_PKCS1_2048_8192_SHA512, + in_fips_submission: true, + }; + +/// RSA PKCS#1 1.5 signatures using SHA-384 for keys of 3072-8192 bits. +pub static RSA_PKCS1_3072_8192_SHA384: &dyn SignatureVerificationAlgorithm = + &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PKCS1_SHA384, + verification_alg: &signature::RSA_PKCS1_3072_8192_SHA384, + in_fips_submission: true, + }; + +/// RSA PSS signatures using SHA-256 for keys of 2048-8192 bits and of +/// type rsaEncryption; see [RFC 4055 Section 1.2]. +/// +/// [RFC 4055 Section 1.2]: https://tools.ietf.org/html/rfc4055#section-1.2 +pub static RSA_PSS_2048_8192_SHA256_LEGACY_KEY: &dyn SignatureVerificationAlgorithm = + &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PSS_SHA256, + verification_alg: &signature::RSA_PSS_2048_8192_SHA256, + in_fips_submission: true, + }; + +/// RSA PSS signatures using SHA-384 for keys of 2048-8192 bits and of +/// type rsaEncryption; see [RFC 4055 Section 1.2]. +/// +/// [RFC 4055 Section 1.2]: https://tools.ietf.org/html/rfc4055#section-1.2 +pub static RSA_PSS_2048_8192_SHA384_LEGACY_KEY: &dyn SignatureVerificationAlgorithm = + &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PSS_SHA384, + verification_alg: &signature::RSA_PSS_2048_8192_SHA384, + in_fips_submission: true, + }; + +/// RSA PSS signatures using SHA-512 for keys of 2048-8192 bits and of +/// type rsaEncryption; see [RFC 4055 Section 1.2]. +/// +/// [RFC 4055 Section 1.2]: https://tools.ietf.org/html/rfc4055#section-1.2 +pub static RSA_PSS_2048_8192_SHA512_LEGACY_KEY: &dyn SignatureVerificationAlgorithm = + &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PSS_SHA512, + verification_alg: &signature::RSA_PSS_2048_8192_SHA512, + in_fips_submission: true, + }; + +/// ED25519 signatures according to RFC 8410 +pub static ED25519: &dyn SignatureVerificationAlgorithm = &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::ED25519, + signature_alg_id: alg_id::ED25519, + verification_alg: &signature::ED25519, + in_fips_submission: true, +}; diff --git a/rustls-bench/Cargo.toml b/rustls-bench/Cargo.toml index 989c75ba807..f67226b739c 100644 --- a/rustls-bench/Cargo.toml +++ b/rustls-bench/Cargo.toml @@ -2,18 +2,25 @@ name = "rustls-bench" version = "0.1.0" edition = "2021" +publish = false [dependencies] clap = { workspace = true } rustls = { path = "../rustls" } -rustls-post-quantum = { path = "../rustls-post-quantum", optional = true } +rustls-aws-lc-rs = { path = "../rustls-aws-lc-rs", optional = true } +rustls-ring = { path = "../rustls-ring", optional = true } +rustls-graviola = { workspace = true, optional = true } +rustls-test = { workspace = true, default-features = false } [features] default = [] -aws-lc-rs = ["rustls/aws-lc-rs"] -fips = ["rustls/fips", "aws-lc-rs"] -post-quantum = ["dep:rustls-post-quantum"] -ring = ["rustls/ring"] +aws-lc-rs = ["rustls-aws-lc-rs"] +fips = ["rustls-aws-lc-rs/fips", "aws-lc-rs"] +graviola = ["dep:rustls-graviola"] +ring = ["dep:rustls-ring"] [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemallocator = { workspace = true } + +[lints] +workspace = true diff --git a/rustls-bench/src/main.rs b/rustls-bench/src/main.rs index 8bbc155c0e2..7b3b23b9767 100644 --- a/rustls-bench/src/main.rs +++ b/rustls-bench/src/main.rs @@ -3,28 +3,24 @@ // Note: we don't use any of the standard 'cargo bench', 'test::Bencher', // etc. because it's unstable at the time of writing. +use core::num::NonZeroUsize; +use core::time::Duration; use std::fs::File; use std::io::{self, Read, Write}; -use std::num::NonZeroUsize; -use std::ops::{Deref, DerefMut}; use std::sync::Arc; -use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -use std::{mem, thread}; +use std::thread; +use std::time::{Instant, SystemTime, UNIX_EPOCH}; use clap::{Parser, ValueEnum}; -use rustls::client::{Resumption, UnbufferedClientConnection}; -use rustls::crypto::CryptoProvider; -use rustls::pki_types::pem::PemObject; -use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; -use rustls::server::{ - NoServerSessionStorage, ProducesTickets, ServerSessionMemoryCache, UnbufferedServerConnection, - WebPkiClientVerifier, -}; -use rustls::unbuffered::{ConnectionState, EncryptError, InsufficientSizeError, UnbufferedStatus}; +use rustls::client::Resumption; +use rustls::crypto::{CipherSuite, CryptoProvider, Identity}; +use rustls::enums::ProtocolVersion; +use rustls::server::{NoServerSessionStorage, ServerSessionMemoryCache, WebPkiClientVerifier}; use rustls::{ - CipherSuite, ClientConfig, ClientConnection, ConnectionCommon, Error, HandshakeKind, - RootCertStore, ServerConfig, ServerConnection, SideData, + ClientConfig, ClientConnection, Connection, HandshakeKind, RootCertStore, ServerConfig, + ServerConnection, }; +use rustls_test::KeyType; pub fn main() { let args = Args::parse(); @@ -35,7 +31,10 @@ pub fn main() { plaintext_size, max_fragment_size, } => { - for bench in lookup_matching_benches(cipher_suite, args.key_type).iter() { + let provider = args + .provider + .unwrap_or_else(Provider::choose_default); + for bench in lookup_matching_benches(cipher_suite, args.key_type, &provider).iter() { bench_bulk( &Parameters::new(bench, &args) .with_plaintext_size(*plaintext_size) @@ -48,8 +47,10 @@ pub fn main() { | Command::HandshakeResume { cipher_suite } | Command::HandshakeTicket { cipher_suite } => { let resume = ResumptionParam::from_subcommand(args.command()); - - for bench in lookup_matching_benches(cipher_suite, args.key_type).iter() { + let provider = args + .provider + .unwrap_or_else(Provider::choose_default); + for bench in lookup_matching_benches(cipher_suite, args.key_type, &provider).iter() { bench_handshake( &Parameters::new(bench, &args) .with_client_auth(ClientAuth::No) @@ -61,7 +62,10 @@ pub fn main() { cipher_suite, count, } => { - for bench in lookup_matching_benches(cipher_suite, args.key_type).iter() { + let provider = args + .provider + .unwrap_or_else(Provider::choose_default); + for bench in lookup_matching_benches(cipher_suite, args.key_type, &provider).iter() { let params = Parameters::new(bench, &args); let client_config = params.client_config(); let server_config = params.server_config(); @@ -111,7 +115,7 @@ struct Args { long, help = "Which key type to use for server and client authentication. The default is to run tests once for each key type." )] - key_type: Option, + key_type: Option, #[arg(long, help = "Which provider to test")] provider: Option, @@ -199,11 +203,7 @@ enum Api { impl Api { fn use_buffered(&self) -> bool { - matches!(*self, Api::Both | Api::Buffered) - } - - fn use_unbuffered(&self) -> bool { - matches!(*self, Api::Both | Api::Unbuffered) + matches!(*self, Self::Both | Self::Buffered) } } @@ -273,19 +273,6 @@ fn bench_handshake(params: &Parameters) { report_handshake_result("handshakes", params, rounds, results); } - - if params.api.use_unbuffered() { - let results = multithreaded( - params.threads, - &client_config, - &server_config, - move |client_config, server_config| { - bench_handshake_unbuffered(rounds, params.resume, client_config, server_config) - }, - ); - - report_handshake_result("handshakes-unbuffered", params, rounds, results); - } } fn bench_handshake_buffered( @@ -306,10 +293,13 @@ fn bench_handshake_buffered( let mut client = time(&mut client_time, || { let server_name = "localhost".try_into().unwrap(); - ClientConnection::new(Arc::clone(&client_config), server_name).unwrap() + client_config + .connect(server_name) + .build() + .unwrap() }); let mut server = time(&mut server_time, || { - ServerConnection::new(Arc::clone(&server_config)).unwrap() + ServerConnection::new(server_config.clone()).unwrap() }); time(&mut server_time, || { @@ -349,72 +339,6 @@ fn bench_handshake_buffered( timings } -fn bench_handshake_unbuffered( - mut rounds: u64, - resume: ResumptionParam, - client_config: Arc, - server_config: Arc, -) -> Timings { - let mut timings = Timings::default(); - - while rounds > 0 { - let mut client_time = 0f64; - let mut server_time = 0f64; - - let client = time(&mut client_time, || { - let server_name = "localhost".try_into().unwrap(); - UnbufferedClientConnection::new(Arc::clone(&client_config), server_name).unwrap() - }); - let server = time(&mut server_time, || { - UnbufferedServerConnection::new(Arc::clone(&server_config)).unwrap() - }); - - // nb. buffer allocation is outside the library, so is outside the benchmark scope - let mut client = Unbuffered::new_client(client); - let mut server = Unbuffered::new_server(server); - - let client_wrote = time(&mut client_time, || client.communicate()); - if client_wrote { - client.swap_buffers(&mut server); - } - - let server_wrote = time(&mut server_time, || server.communicate()); - if server_wrote { - server.swap_buffers(&mut client); - } - - let client_wrote = time(&mut client_time, || client.communicate()); - if client_wrote { - client.swap_buffers(&mut server); - } - - let server_wrote = time(&mut server_time, || server.communicate()); - if server_wrote { - server.swap_buffers(&mut client); - } - - // check we reached idle - assert!(!server.communicate()); - assert!(!client.communicate()); - - // if we achieved the desired handshake shape, count this handshake. - if client.conn.handshake_kind() == Some(resume.as_handshake_kind()) - && server.conn.handshake_kind() == Some(resume.as_handshake_kind()) - { - timings.client += client_time; - timings.server += server_time; - rounds -= 1; - } else { - // otherwise, this handshake is ignored against the quota for this thread, - // and serves just to refresh the session cache. that is mainly - // necessary for TLS1.3, where tickets are single-use and limited to - // 8 per server. - } - } - - timings -} - /// Run `f` on `count` threads, and then return the timings produced /// by each thread. /// @@ -436,7 +360,7 @@ fn multithreaded( threads .into_iter() - .map(|thr| thr.join().unwrap()) + .map(|thread| thread.join().unwrap()) .collect::>() }) } @@ -483,7 +407,7 @@ fn report_timings( for t in thread_timings.iter() { let rate = work_per_thread / which(t); total_rate += rate; - print!("{:.2}\t", rate); + print!("{rate:.2}\t"); } println!( @@ -527,19 +451,6 @@ fn bench_bulk(params: &Parameters) { report_bulk_result("bulk", params, results, rounds); } - - if params.api.use_unbuffered() { - let results = multithreaded( - params.threads, - &client_config, - &server_config, - move |client_config, server_config| { - bench_bulk_unbuffered(client_config, server_config, params.plaintext_size, rounds) - }, - ); - - report_bulk_result("bulk-unbuffered", params, results, rounds); - } } fn bench_bulk_buffered( @@ -549,7 +460,10 @@ fn bench_bulk_buffered( rounds: u64, ) -> Timings { let server_name = "localhost".try_into().unwrap(); - let mut client = ClientConnection::new(client_config, server_name).unwrap(); + let mut client = client_config + .connect(server_name) + .build() + .unwrap(); client.set_buffer_limit(None); let mut server = ServerConnection::new(server_config).unwrap(); server.set_buffer_limit(None); @@ -570,39 +484,6 @@ fn bench_bulk_buffered( timings } -fn bench_bulk_unbuffered( - client_config: Arc, - server_config: Arc, - plaintext_size: u64, - rounds: u64, -) -> Timings { - let server_name = "localhost".try_into().unwrap(); - let mut client = Unbuffered::new_client( - UnbufferedClientConnection::new(client_config, server_name).unwrap(), - ); - let mut server = - Unbuffered::new_server(UnbufferedServerConnection::new(server_config).unwrap()); - - client.handshake(&mut server); - - let mut timings = Timings::default(); - - let buf = vec![0; plaintext_size as usize]; - for _ in 0..rounds { - time(&mut timings.server, || { - server.write(&buf); - }); - - server.swap_buffers(&mut client); - - time(&mut timings.client, || { - client.read_and_discard(buf.len()); - }); - } - - timings -} - fn report_bulk_result(variant: &str, params: &Parameters, timings: Vec, rounds: u64) { let mfs_str = format!( "max_fragment_size:{}", @@ -638,9 +519,14 @@ fn bench_memory( let mut buffers = TempBuffers::new(); for _i in 0..conn_count { - servers.push(ServerConnection::new(Arc::clone(&server_config)).unwrap()); + servers.push(ServerConnection::new(server_config.clone()).unwrap()); let server_name = "localhost".try_into().unwrap(); - clients.push(ClientConnection::new(Arc::clone(&client_config), server_name).unwrap()); + clients.push( + client_config + .connect(server_name) + .build() + .unwrap(), + ); } for _step in 0..5 { @@ -669,19 +555,21 @@ fn bench_memory( fn lookup_matching_benches( ciphersuite_name: &str, - key_type: Option, + key_type: Option, + provider: &Provider, ) -> Vec { let r: Vec = ALL_BENCHMARKS .iter() .filter(|params| { format!("{:?}", params.ciphersuite).to_lowercase() == ciphersuite_name.to_lowercase() - && (key_type.is_none() || Some(params.key_type) == key_type) + && (key_type.is_none() || Some(params.key_type) == key_type.map(KeyType::from)) + && provider.supports_benchmark(params) }) .cloned() .collect(); if r.is_empty() { - panic!("unknown suite {:?}", ciphersuite_name); + panic!("unknown suite {ciphersuite_name:?}"); } r @@ -765,29 +653,29 @@ impl Parameters { let provider = Arc::new(self.provider.build()); let client_auth = match self.client_auth { ClientAuth::Yes => { - let roots = self.proto.key_type.get_chain(); + let Identity::X509(id) = &*self.proto.key_type.identity() else { + panic!("client auth requested but no X.509 identity available"); + }; + let mut client_auth_roots = RootCertStore::empty(); - for root in roots { - client_auth_roots.add(root).unwrap(); + for root in &id.intermediates { + client_auth_roots + .add(root.clone()) + .unwrap(); } - WebPkiClientVerifier::builder_with_provider( - client_auth_roots.into(), - provider.clone(), + + Arc::new( + WebPkiClientVerifier::builder(client_auth_roots.into(), &provider) + .build() + .unwrap(), ) - .build() - .unwrap() } ClientAuth::No => WebPkiClientVerifier::no_client_auth(), }; - let mut cfg = ServerConfig::builder_with_provider(provider) - .with_protocol_versions(&[self.proto.version]) - .unwrap() + let mut cfg = ServerConfig::builder(provider) .with_client_cert_verifier(client_auth) - .with_single_cert( - self.proto.key_type.get_chain(), - self.proto.key_type.get_key(), - ) + .with_single_cert(self.proto.key_type.identity(), self.proto.key_type.key()) .expect("bad certs/private key?"); match self.resume { @@ -795,7 +683,12 @@ impl Parameters { cfg.session_storage = ServerSessionMemoryCache::new(128); } ResumptionParam::Tickets => { - cfg.ticketer = self.provider.ticketer().unwrap(); + cfg.ticketer = Some( + cfg.crypto_provider() + .ticketer_factory + .ticketer() + .unwrap(), + ); } ResumptionParam::No => { cfg.session_storage = Arc::new(NoServerSessionStorage {}); @@ -808,33 +701,25 @@ impl Parameters { fn client_config(&self) -> Arc { let mut root_store = RootCertStore::empty(); - root_store.add_parsable_certificates( - CertificateDer::pem_file_iter(self.proto.key_type.path_for("ca.cert")) - .unwrap() - .map(|result| result.unwrap()), - ); + root_store + .add(self.proto.key_type.ca_cert()) + .unwrap(); - let cfg = ClientConfig::builder_with_provider( - CryptoProvider { - cipher_suites: self - .provider - .find_suite(self.proto.ciphersuite), - ..self.provider.build() - } - .into(), + let cfg = ClientConfig::builder( + self.provider + .build_with_cipher_suite(self.proto.ciphersuite) + .into(), ) - .with_protocol_versions(&[self.proto.version]) - .unwrap() .with_root_certificates(root_store); let mut cfg = match self.client_auth { ClientAuth::Yes => cfg .with_client_auth_cert( - self.proto.key_type.get_client_chain(), - self.proto.key_type.get_client_key(), + self.proto.key_type.client_identity(), + self.proto.key_type.client_key(), ) .unwrap(), - ClientAuth::No => cfg.with_no_client_auth(), + ClientAuth::No => cfg.with_no_client_auth().unwrap(), }; cfg.resumption = match self.resume { @@ -850,7 +735,7 @@ impl Parameters { } fn open_latency_file(&self, role: &str) -> LatencyOutput { - LatencyOutput::new(&self.latency_prefix, role) + LatencyOutput::new(self.latency_prefix.as_deref(), role) } } @@ -859,9 +744,9 @@ struct LatencyOutput { } impl LatencyOutput { - fn new(prefix: &Option, role: &str) -> Self { + fn new(prefix: Option<&str>, role: &str) -> Self { let thread_id = thread::current().id(); - let output = prefix.as_ref().map(|prefix| { + let output = prefix.map(|prefix| { let file_name = format!("{prefix}-{role}-{thread_id:?}-latency.tsv"); File::create(&file_name).expect("cannot open latency output file") }); @@ -930,8 +815,8 @@ enum Provider { AwsLcRs, #[cfg(all(feature = "aws-lc-rs", feature = "fips"))] AwsLcRsFips, - #[cfg(feature = "post-quantum")] - PostQuantum, + #[cfg(feature = "graviola")] + Graviola, #[cfg(feature = "ring")] Ring, #[value(skip)] @@ -942,49 +827,43 @@ impl Provider { fn build(self) -> CryptoProvider { match self { #[cfg(feature = "aws-lc-rs")] - Self::AwsLcRs => rustls::crypto::aws_lc_rs::default_provider(), - #[cfg(all(feature = "aws-lc-rs", feature = "fips"))] - Self::AwsLcRsFips => rustls::crypto::default_fips_provider(), - #[cfg(feature = "post-quantum")] - Self::PostQuantum => rustls_post_quantum::provider(), - #[cfg(feature = "ring")] - Self::Ring => rustls::crypto::ring::default_provider(), - Self::_None => unreachable!(), - } - } - - fn ticketer(self) -> Result, Error> { - match self { - #[cfg(feature = "aws-lc-rs")] - Self::AwsLcRs => rustls::crypto::aws_lc_rs::Ticketer::new(), + Self::AwsLcRs => rustls_aws_lc_rs::DEFAULT_PROVIDER, #[cfg(all(feature = "aws-lc-rs", feature = "fips"))] - Self::AwsLcRsFips => rustls::crypto::aws_lc_rs::Ticketer::new(), - #[cfg(feature = "post-quantum")] - Self::PostQuantum => rustls::crypto::aws_lc_rs::Ticketer::new(), + Self::AwsLcRsFips => rustls_aws_lc_rs::DEFAULT_FIPS_PROVIDER, + #[cfg(feature = "graviola")] + Self::Graviola => rustls_graviola::default_provider(), #[cfg(feature = "ring")] - Self::Ring => rustls::crypto::ring::Ticketer::new(), + Self::Ring => rustls_ring::DEFAULT_PROVIDER, Self::_None => unreachable!(), } } - fn find_suite(&self, name: CipherSuite) -> Vec { + fn build_with_cipher_suite(&self, name: CipherSuite) -> CryptoProvider { let mut provider = self.build(); provider - .cipher_suites - .retain(|cs| cs.suite() == name); - provider.cipher_suites + .tls12_cipher_suites + .to_mut() + .retain(|cs| cs.common.suite == name); + provider + .tls13_cipher_suites + .to_mut() + .retain(|cs| cs.common.suite == name); + provider } fn supports_benchmark(&self, param: &BenchmarkParam) -> bool { - !self - .find_suite(param.ciphersuite) - .is_empty() + let prov = self.build_with_cipher_suite(param.ciphersuite); + (prov.tls12_cipher_suites.len() + prov.tls13_cipher_suites.len()) > 0 && self.supports_key_type(param.key_type) } fn supports_key_type(&self, _key_type: KeyType) -> bool { - // currently all providers support all key types - true + match self { + #[cfg(feature = "graviola")] + Self::Graviola => !matches!(_key_type, KeyType::Ed25519), + // all other providers support all key types + _ => true, + } } fn choose_default() -> Self { @@ -997,8 +876,8 @@ impl Provider { #[cfg(all(feature = "aws-lc-rs", feature = "fips"))] available.push(Self::AwsLcRsFips); - #[cfg(feature = "post-quantum")] - available.push(Self::PostQuantum); + #[cfg(feature = "graviola")] + available.push(Self::Graviola); #[cfg(feature = "ring")] available.push(Self::Ring); @@ -1017,16 +896,12 @@ impl Provider { #[derive(Clone)] struct BenchmarkParam { key_type: KeyType, - ciphersuite: rustls::CipherSuite, - version: &'static rustls::SupportedProtocolVersion, + ciphersuite: CipherSuite, + version: ProtocolVersion, } impl BenchmarkParam { - const fn new( - key_type: KeyType, - ciphersuite: rustls::CipherSuite, - version: &'static rustls::SupportedProtocolVersion, - ) -> Self { + const fn new(key_type: KeyType, ciphersuite: CipherSuite, version: ProtocolVersion) -> Self { Self { key_type, ciphersuite, @@ -1035,281 +910,21 @@ impl BenchmarkParam { } } -// copied from tests/api.rs #[derive(PartialEq, Clone, Copy, Debug, ValueEnum)] -enum KeyType { +enum RequestedKeyType { Rsa2048, EcdsaP256, EcdsaP384, Ed25519, } -impl KeyType { - fn path_for(&self, part: &str) -> String { - match self { - Self::Rsa2048 => format!("test-ca/rsa-2048/{}", part), - Self::EcdsaP256 => format!("test-ca/ecdsa-p256/{}", part), - Self::EcdsaP384 => format!("test-ca/ecdsa-p384/{}", part), - Self::Ed25519 => format!("test-ca/eddsa/{}", part), - } - } - - fn get_chain(&self) -> Vec> { - CertificateDer::pem_file_iter(self.path_for("end.fullchain")) - .unwrap() - .map(|result| result.unwrap()) - .collect() - } - - fn get_key(&self) -> PrivateKeyDer<'static> { - PrivatePkcs8KeyDer::from_pem_file(self.path_for("end.key")) - .unwrap() - .into() - } - - fn get_client_chain(&self) -> Vec> { - CertificateDer::pem_file_iter(self.path_for("client.fullchain")) - .unwrap() - .map(|result| result.unwrap()) - .collect() - } - - fn get_client_key(&self) -> PrivateKeyDer<'static> { - PrivatePkcs8KeyDer::from_pem_file(self.path_for("client.key")) - .unwrap() - .into() - } -} - -struct Unbuffered { - conn: UnbufferedConnection, - input: Vec, - input_used: usize, - output: Vec, - output_used: usize, -} - -impl Unbuffered { - fn new_client(client: UnbufferedClientConnection) -> Self { - Self { - conn: UnbufferedConnection::Client(client), - input: vec![0u8; Self::BUFFER_LEN], - input_used: 0, - output: vec![0u8; Self::BUFFER_LEN], - output_used: 0, - } - } - - fn new_server(server: UnbufferedServerConnection) -> Self { - Self { - conn: UnbufferedConnection::Server(server), - input: vec![0u8; Self::BUFFER_LEN], - input_used: 0, - output: vec![0u8; Self::BUFFER_LEN], - output_used: 0, - } - } - - fn handshake(&mut self, peer: &mut Unbuffered) { - loop { - let mut progress = false; - - if self.communicate() { - self.swap_buffers(peer); - progress = true; - } - - if peer.communicate() { - peer.swap_buffers(self); - progress = true; - } - - if !progress { - return; - } - } - } - - fn swap_buffers(&mut self, peer: &mut Unbuffered) { - // our output becomes peer's input, and peer's input - // becomes our output. - mem::swap(&mut self.input, &mut peer.output); - mem::swap(&mut self.input_used, &mut peer.output_used); - mem::swap(&mut self.output, &mut peer.input); - mem::swap(&mut self.output_used, &mut peer.input_used); - } - - fn communicate(&mut self) -> bool { - let (input_used, output_added) = self.conn.communicate( - &mut self.input[..self.input_used], - &mut self.output[self.output_used..], - ); - assert_eq!(input_used, self.input_used); - self.input_used = 0; - self.output_used += output_added; - self.output_used > 0 - } - - fn write(&mut self, data: &[u8]) { - assert_eq!(self.input_used, 0); - let output_added = match self - .conn - .write(data, &mut self.output[self.output_used..]) - { - Ok(output_added) => output_added, - Err(EncryptError::InsufficientSize(InsufficientSizeError { required_size })) => { - self.output - .resize(self.output_used + required_size, 0); - self.conn - .write(data, &mut self.output[self.output_used..]) - .unwrap() - } - Err(other) => panic!("unexpected write error {other:?}"), - }; - self.output_used += output_added; - } - - fn read_and_discard(&mut self, len: usize) { - assert!(self.input_used > 0); - let input_used = self - .conn - .read_and_discard(len, &mut self.input[..self.input_used]); - assert_eq!(input_used, self.input_used); - self.input_used = 0; - } - - const BUFFER_LEN: usize = 16_384; -} - -enum UnbufferedConnection { - Client(UnbufferedClientConnection), - Server(UnbufferedServerConnection), -} - -impl UnbufferedConnection { - fn communicate(&mut self, input: &mut [u8], output: &mut [u8]) -> (usize, usize) { - let mut input_used = 0; - let mut output_added = 0; - - loop { - match self { - Self::Client(client) => { - match client.process_tls_records(&mut input[input_used..]) { - UnbufferedStatus { - state: Ok(ConnectionState::EncodeTlsData(mut etd)), - discard, - } => { - input_used += discard; - output_added += etd - .encode(&mut output[output_added..]) - .unwrap(); - } - UnbufferedStatus { - state: Ok(ConnectionState::TransmitTlsData(ttd)), - discard, - } => { - input_used += discard; - ttd.done(); - return (input_used, output_added); - } - UnbufferedStatus { - state: Ok(ConnectionState::WriteTraffic(_)), - discard, - } => { - input_used += discard; - return (input_used, output_added); - } - st => { - println!("unexpected client {st:?}"); - return (input_used, output_added); - } - } - } - Self::Server(server) => { - match server.process_tls_records(&mut input[input_used..]) { - UnbufferedStatus { - state: Ok(ConnectionState::EncodeTlsData(mut etd)), - discard, - } => { - input_used += discard; - output_added += etd - .encode(&mut output[output_added..]) - .unwrap(); - } - UnbufferedStatus { - state: Ok(ConnectionState::TransmitTlsData(ttd)), - discard, - } => { - input_used += discard; - ttd.done(); - return (input_used, output_added); - } - UnbufferedStatus { - state: Ok(ConnectionState::WriteTraffic(_)), - discard, - } => { - input_used += discard; - return (input_used, output_added); - } - st => { - println!("unexpected server {st:?}"); - return (input_used, output_added); - } - } - } - } - } - } - - fn write(&mut self, data: &[u8], output: &mut [u8]) -> Result { - match self { - Self::Client(client) => match client.process_tls_records(&mut []) { - UnbufferedStatus { - state: Ok(ConnectionState::WriteTraffic(mut wt)), - .. - } => wt.encrypt(data, output), - st => panic!("unexpected write state: {st:?}"), - }, - Self::Server(server) => match server.process_tls_records(&mut []) { - UnbufferedStatus { - state: Ok(ConnectionState::WriteTraffic(mut wt)), - .. - } => wt.encrypt(data, output), - st => panic!("unexpected write state: {st:?}"), - }, - } - } - - fn read_and_discard(&mut self, mut expected: usize, input: &mut [u8]) -> usize { - let mut input_used = 0; - - let client = match self { - Self::Client(client) => client, - Self::Server(_) => todo!("server read"), - }; - - while expected > 0 { - match client.process_tls_records(&mut input[input_used..]) { - UnbufferedStatus { - state: Ok(ConnectionState::ReadTraffic(mut rt)), - discard, - } => { - input_used += discard; - let record = rt.next_record().unwrap().unwrap(); - input_used += record.discard; - expected -= record.payload.len(); - } - st => panic!("unexpected read state: {st:?}"), - } - } - - input_used - } - - fn handshake_kind(&self) -> Option { - match self { - Self::Client(client) => client.handshake_kind(), - Self::Server(server) => server.handshake_kind(), +impl From for KeyType { + fn from(val: RequestedKeyType) -> Self { + match val { + RequestedKeyType::Rsa2048 => Self::Rsa2048, + RequestedKeyType::EcdsaP256 => Self::EcdsaP256, + RequestedKeyType::EcdsaP384 => Self::EcdsaP384, + RequestedKeyType::Ed25519 => Self::Ed25519, } } } @@ -1347,18 +962,12 @@ where r } -fn transfer( +fn transfer( buffers: &mut TempBuffers, - left: &mut L, - right: &mut R, + left: &mut impl Connection, + right: &mut impl Connection, expect_data: Option, -) -> f64 -where - L: DerefMut + Deref>, - R: DerefMut + Deref>, - LS: SideData, - RS: SideData, -{ +) -> f64 { let mut read_time = 0f64; let mut data_left = expect_data; @@ -1389,7 +998,7 @@ where offs += read; } Err(err) => { - panic!("error on transfer {}..{}: {}", offs, sz, err); + panic!("error on transfer {offs}..{sz}: {err}"); } } @@ -1398,7 +1007,7 @@ where let sz = match right.reader().read(&mut [0u8; 16_384]) { Ok(sz) => sz, Err(err) if err.kind() == io::ErrorKind::WouldBlock => break, - Err(err) => panic!("failed to read data: {}", err), + Err(err) => panic!("failed to read data: {err}"), }; *left -= sz; @@ -1446,72 +1055,72 @@ static ALL_BENCHMARKS: &[BenchmarkParam] = &[ BenchmarkParam::new( KeyType::Rsa2048, CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - &rustls::version::TLS12, + ProtocolVersion::TLSv1_2, ), BenchmarkParam::new( KeyType::EcdsaP256, CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - &rustls::version::TLS12, + ProtocolVersion::TLSv1_2, ), BenchmarkParam::new( KeyType::Rsa2048, CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - &rustls::version::TLS12, + ProtocolVersion::TLSv1_2, ), BenchmarkParam::new( KeyType::Rsa2048, CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - &rustls::version::TLS12, + ProtocolVersion::TLSv1_2, ), BenchmarkParam::new( KeyType::EcdsaP256, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - &rustls::version::TLS12, + ProtocolVersion::TLSv1_2, ), BenchmarkParam::new( KeyType::EcdsaP384, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - &rustls::version::TLS12, + ProtocolVersion::TLSv1_2, ), BenchmarkParam::new( KeyType::Ed25519, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - &rustls::version::TLS12, + ProtocolVersion::TLSv1_2, ), BenchmarkParam::new( KeyType::Rsa2048, CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, - &rustls::version::TLS13, + ProtocolVersion::TLSv1_3, ), BenchmarkParam::new( KeyType::Rsa2048, CipherSuite::TLS13_AES_256_GCM_SHA384, - &rustls::version::TLS13, + ProtocolVersion::TLSv1_3, ), BenchmarkParam::new( KeyType::EcdsaP256, CipherSuite::TLS13_AES_256_GCM_SHA384, - &rustls::version::TLS13, + ProtocolVersion::TLSv1_3, ), BenchmarkParam::new( KeyType::Ed25519, CipherSuite::TLS13_AES_256_GCM_SHA384, - &rustls::version::TLS13, + ProtocolVersion::TLSv1_3, ), BenchmarkParam::new( KeyType::Rsa2048, CipherSuite::TLS13_AES_128_GCM_SHA256, - &rustls::version::TLS13, + ProtocolVersion::TLSv1_3, ), BenchmarkParam::new( KeyType::EcdsaP256, CipherSuite::TLS13_AES_128_GCM_SHA256, - &rustls::version::TLS13, + ProtocolVersion::TLSv1_3, ), BenchmarkParam::new( KeyType::Ed25519, CipherSuite::TLS13_AES_128_GCM_SHA256, - &rustls::version::TLS13, + ProtocolVersion::TLSv1_3, ), ]; diff --git a/rustls-fuzzing-provider/Cargo.toml b/rustls-fuzzing-provider/Cargo.toml index 0307c40bfcf..e869071a602 100644 --- a/rustls-fuzzing-provider/Cargo.toml +++ b/rustls-fuzzing-provider/Cargo.toml @@ -5,8 +5,10 @@ edition = "2021" publish = false [dependencies] -rustls = { path = "../rustls", default-features = false, features = ["logging", "std", "tls12"] } -webpki = { workspace = true } +rustls = { path = "../rustls", default-features = false, features = ["log", "webpki"] } [dev-dependencies] env_logger = { workspace = true } + +[lints] +workspace = true diff --git a/rustls-fuzzing-provider/src/lib.rs b/rustls-fuzzing-provider/src/lib.rs index eb9d7bcf1ae..660cf3eba9f 100644 --- a/rustls-fuzzing-provider/src/lib.rs +++ b/rustls-fuzzing-provider/src/lib.rs @@ -1,63 +1,85 @@ +use core::time::Duration; +use std::borrow::Cow; use std::sync::Arc; -use rustls::client::danger::ServerCertVerifier; use rustls::client::WebPkiServerVerifier; +use rustls::client::danger::ServerVerifier; use rustls::crypto::cipher::{ - AeadKey, InboundOpaqueMessage, InboundPlainMessage, Iv, KeyBlockShape, MessageDecrypter, - MessageEncrypter, OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, - Tls12AeadAlgorithm, Tls13AeadAlgorithm, UnsupportedOperationError, + AeadKey, EncodedMessage, InboundOpaque, Iv, KeyBlockShape, MessageDecrypter, MessageEncrypter, + OutboundOpaque, OutboundPlain, Tls12AeadAlgorithm, Tls13AeadAlgorithm, + UnsupportedOperationError, +}; +use rustls::crypto::kx::{ + KeyExchangeAlgorithm, NamedGroup, SharedSecret, StartedKeyExchange, SupportedKxGroup, }; use rustls::crypto::{ - hash, tls12, tls13, CipherSuiteCommon, GetRandomFailed, KeyExchangeAlgorithm, - WebPkiSupportedAlgorithms, + self, CipherSuite, CipherSuiteCommon, Credentials, GetRandomFailed, HashAlgorithm, Identity, + SelectedCredential, SignatureScheme, TicketProducer, WebPkiSupportedAlgorithms, hash, tls12, + tls13, }; -use rustls::ffdhe_groups::FfdheGroup; +use rustls::enums::{ContentType, ProtocolVersion}; +use rustls::error::{PeerIncompatible, PeerMisbehaved}; use rustls::pki_types::{ - AlgorithmIdentifier, CertificateDer, InvalidSignature, PrivateKeyDer, - SignatureVerificationAlgorithm, -}; -use rustls::{ - crypto, server, sign, CipherSuite, ConnectionTrafficSecrets, ContentType, Error, NamedGroup, - PeerMisbehaved, ProtocolVersion, RootCertStore, SignatureAlgorithm, SignatureScheme, - SupportedCipherSuite, Tls12CipherSuite, Tls13CipherSuite, + AlgorithmIdentifier, CertificateDer, FipsStatus, InvalidSignature, PrivateKeyDer, + SignatureVerificationAlgorithm, SubjectPublicKeyInfoDer, alg_id, }; -use webpki::alg_id; +use rustls::server::{ClientHello, ServerCredentialResolver}; +use rustls::{ConnectionTrafficSecrets, Error, RootCertStore, Tls12CipherSuite, Tls13CipherSuite}; /// This is a `CryptoProvider` that provides NO SECURITY and is for fuzzing only. -pub fn provider() -> crypto::CryptoProvider { - crypto::CryptoProvider { - cipher_suites: vec![TLS13_FUZZING_SUITE, TLS_FUZZING_SUITE], - kx_groups: vec![&KeyExchangeGroup as &dyn crypto::SupportedKxGroup], - signature_verification_algorithms: VERIFY_ALGORITHMS, - secure_random: &Provider, - key_provider: &Provider, - } -} +pub const PROVIDER: crypto::CryptoProvider = crypto::CryptoProvider { + tls12_cipher_suites: Cow::Borrowed(&[TLS_FUZZING_SUITE]), + tls13_cipher_suites: Cow::Borrowed(&[TLS13_FUZZING_SUITE]), + kx_groups: Cow::Borrowed(&[KEY_EXCHANGE_GROUP]), + signature_verification_algorithms: VERIFY_ALGORITHMS, + secure_random: &Provider, + key_provider: &Provider, + ticketer_factory: &Provider, +}; + +pub const PROVIDER_TLS12: crypto::CryptoProvider = crypto::CryptoProvider { + tls13_cipher_suites: Cow::Borrowed(&[]), + ..PROVIDER +}; + +pub const PROVIDER_TLS13: crypto::CryptoProvider = crypto::CryptoProvider { + tls12_cipher_suites: Cow::Borrowed(&[]), + ..PROVIDER +}; -pub fn server_verifier() -> Arc { +pub fn server_verifier() -> Arc { // we need one of these, but it doesn't matter what it is let mut root_store = RootCertStore::empty(); root_store.add_parsable_certificates([CertificateDer::from( &include_bytes!("../../test-ca/ecdsa-p256/inter.der")[..], )]); - WebPkiServerVerifier::builder_with_provider(root_store.into(), provider().into()) - .build() - .unwrap() + Arc::new( + WebPkiServerVerifier::builder(root_store.into(), &PROVIDER) + .build() + .unwrap(), + ) } -pub fn server_cert_resolver() -> Arc { +pub fn server_cert_resolver() -> Arc { let cert = CertificateDer::from(&include_bytes!("../../test-ca/ecdsa-p256/end.der")[..]); - let certified_key = sign::CertifiedKey::new(vec![cert], Arc::new(SigningKey)); - Arc::new(DummyCert(certified_key.into())) + let credentials = Credentials::new_unchecked( + Arc::new(Identity::from_cert_chain(vec![cert]).unwrap()), + Box::new(SigningKey), + ); + Arc::new(DummyCert(credentials.into())) } #[derive(Debug)] -struct DummyCert(Arc); +struct DummyCert(Arc); -impl server::ResolvesServerCert for DummyCert { - fn resolve(&self, _client_hello: server::ClientHello) -> Option> { - Some(self.0.clone()) +impl ServerCredentialResolver for DummyCert { + fn resolve(&self, client_hello: &ClientHello<'_>) -> Result { + self.0 + .signer(client_hello.signature_schemes()) + .ok_or(Error::PeerIncompatible( + PeerIncompatible::NoSignatureSchemesInCommon, + )) } } @@ -82,33 +104,62 @@ impl crypto::KeyProvider for Provider { fn load_private_key( &self, _key_der: PrivateKeyDer<'static>, - ) -> Result, Error> { - Ok(Arc::new(SigningKey)) + ) -> Result, Error> { + Ok(Box::new(SigningKey)) } } -static TLS13_FUZZING_SUITE: SupportedCipherSuite = SupportedCipherSuite::Tls13(&Tls13CipherSuite { +impl crypto::TicketerFactory for Provider { + fn ticketer(&self) -> Result, Error> { + Ok(Arc::new(Ticketer)) + } + + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated + } +} + +pub const TLS13_FUZZING_SUITE: &Tls13CipherSuite = &Tls13CipherSuite { common: CipherSuiteCommon { - suite: CipherSuite::Unknown(0xff13), + suite: CipherSuite(0xff13), hash_provider: &Hash, confidentiality_limit: u64::MAX, }, + protocol_version: rustls::version::TLS13_VERSION, hkdf_provider: &tls13::HkdfUsingHmac(&Hmac), aead_alg: &Aead, quic: None, -}); +}; -static TLS_FUZZING_SUITE: SupportedCipherSuite = SupportedCipherSuite::Tls12(&Tls12CipherSuite { +pub const TLS_FUZZING_SUITE: &Tls12CipherSuite = &Tls12CipherSuite { common: CipherSuiteCommon { - suite: CipherSuite::Unknown(0xff12), + suite: CipherSuite(0xff12), hash_provider: &Hash, confidentiality_limit: u64::MAX, }, + protocol_version: rustls::version::TLS12_VERSION, kx: KeyExchangeAlgorithm::ECDHE, sign: &[SIGNATURE_SCHEME], prf_provider: &tls12::PrfUsingHmac(&Hmac), aead_alg: &Aead, -}); +}; + +#[derive(Debug, Default)] +pub struct Ticketer; + +impl TicketProducer for Ticketer { + fn encrypt(&self, plain: &[u8]) -> Option> { + Some(plain.to_vec()) + } + + fn decrypt(&self, cipher: &[u8]) -> Option> { + Some(cipher.to_vec()) + } + + fn lifetime(&self) -> Duration { + Duration::from_secs(60 * 60 * 6) + } +} struct Hash; @@ -121,8 +172,8 @@ impl hash::Hash for Hash { hash::Output::new(HASH_OUTPUT) } - fn algorithm(&self) -> hash::HashAlgorithm { - hash::HashAlgorithm::from(0xff) + fn algorithm(&self) -> HashAlgorithm { + HashAlgorithm::from(0xff) } fn output_len(&self) -> usize { @@ -138,7 +189,7 @@ impl hash::Context for HashContext { } fn fork(&self) -> Box { - Box::new(HashContext) + Box::new(Self) } fn finish(self: Box) -> hash::Output { @@ -178,10 +229,10 @@ const HMAC_OUTPUT: &[u8] = b"HmacHmacHmacHmacHmacHmacHmacHmac"; struct ActiveKeyExchange; -impl crypto::ActiveKeyExchange for ActiveKeyExchange { - fn complete(self: Box, peer: &[u8]) -> Result { +impl crypto::kx::ActiveKeyExchange for ActiveKeyExchange { + fn complete(self: Box, peer: &[u8]) -> Result { match peer { - KX_PEER_SHARE => Ok(crypto::SharedSecret::from(KX_SHARED_SECRET)), + KX_PEER_SHARE => Ok(SharedSecret::from(KX_SHARED_SECRET)), _ => Err(Error::from(PeerMisbehaved::InvalidKeyShare)), } } @@ -190,25 +241,19 @@ impl crypto::ActiveKeyExchange for ActiveKeyExchange { KX_PEER_SHARE } - fn ffdhe_group(&self) -> Option> { - None - } - fn group(&self) -> NamedGroup { NamedGroup::from(0xfe00) } } +const KEY_EXCHANGE_GROUP: &dyn SupportedKxGroup = &KeyExchangeGroup; + #[derive(Debug)] struct KeyExchangeGroup; -impl crypto::SupportedKxGroup for KeyExchangeGroup { - fn start(&self) -> Result, Error> { - Ok(Box::new(ActiveKeyExchange)) - } - - fn ffdhe_group(&self) -> Option> { - None +impl SupportedKxGroup for KeyExchangeGroup { + fn start(&self) -> Result { + Ok(StartedKeyExchange::Single(Box::new(ActiveKeyExchange))) } fn name(&self) -> NamedGroup { @@ -275,11 +320,11 @@ struct Tls13Cipher; impl MessageEncrypter for Tls13Cipher { fn encrypt( &mut self, - m: OutboundPlainMessage, + m: EncodedMessage>, seq: u64, - ) -> Result { + ) -> Result, Error> { let total_len = self.encrypted_payload_len(m.payload.len()); - let mut payload = PrefixedPayload::with_capacity(total_len); + let mut payload = OutboundOpaque::with_capacity(total_len); payload.extend_from_chunks(&m.payload); payload.extend_from_slice(&m.typ.to_array()); @@ -295,11 +340,11 @@ impl MessageEncrypter for Tls13Cipher { payload.extend_from_slice(&seq.to_be_bytes()); payload.extend_from_slice(AEAD_TAG); - Ok(OutboundOpaqueMessage::new( - ContentType::ApplicationData, - ProtocolVersion::TLSv1_2, + Ok(EncodedMessage { + typ: ContentType::ApplicationData, + version: ProtocolVersion::TLSv1_2, payload, - )) + }) } fn encrypted_payload_len(&self, payload_len: usize) -> usize { @@ -310,9 +355,9 @@ impl MessageEncrypter for Tls13Cipher { impl MessageDecrypter for Tls13Cipher { fn decrypt<'a>( &mut self, - mut m: InboundOpaqueMessage<'a>, + mut m: EncodedMessage>, seq: u64, - ) -> Result, Error> { + ) -> Result, Error> { let payload = &mut m.payload; let mut expected_tag = vec![]; @@ -344,11 +389,11 @@ struct Tls12Cipher; impl MessageEncrypter for Tls12Cipher { fn encrypt( &mut self, - m: OutboundPlainMessage, + m: EncodedMessage>, seq: u64, - ) -> Result { + ) -> Result, Error> { let total_len = self.encrypted_payload_len(m.payload.len()); - let mut payload = PrefixedPayload::with_capacity(total_len); + let mut payload = OutboundOpaque::with_capacity(total_len); payload.extend_from_chunks(&m.payload); for (p, mask) in payload @@ -362,7 +407,11 @@ impl MessageEncrypter for Tls12Cipher { payload.extend_from_slice(&seq.to_be_bytes()); payload.extend_from_slice(AEAD_TAG); - Ok(OutboundOpaqueMessage::new(m.typ, m.version, payload)) + Ok(EncodedMessage { + typ: m.typ, + version: m.version, + payload, + }) } fn encrypted_payload_len(&self, payload_len: usize) -> usize { @@ -373,9 +422,9 @@ impl MessageEncrypter for Tls12Cipher { impl MessageDecrypter for Tls12Cipher { fn decrypt<'a>( &mut self, - mut m: InboundOpaqueMessage<'a>, + mut m: EncodedMessage>, seq: u64, - ) -> Result, Error> { + ) -> Result, Error> { let payload = &mut m.payload; let mut expected_tag = vec![]; @@ -406,9 +455,12 @@ const AEAD_MASK: &[u8] = b"AeadMaskPattern"; const AEAD_TAG: &[u8] = b"AeadTagA"; const AEAD_OVERHEAD: usize = 16; -pub static VERIFY_ALGORITHMS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms { - all: &[VERIFY_ALGORITHM], - mapping: &[(SIGNATURE_SCHEME, &[VERIFY_ALGORITHM])], +pub static VERIFY_ALGORITHMS: WebPkiSupportedAlgorithms = match WebPkiSupportedAlgorithms::new( + &[VERIFY_ALGORITHM], + &[(SIGNATURE_SCHEME, &[VERIFY_ALGORITHM])], +) { + Ok(algs) => algs, + Err(_) => panic!("bad WebPkiSupportedAlgorithms"), }; static VERIFY_ALGORITHM: &dyn SignatureVerificationAlgorithm = &VerifyAlgorithm; @@ -441,21 +493,21 @@ impl SignatureVerificationAlgorithm for VerifyAlgorithm { #[derive(Debug)] pub struct SigningKey; -impl sign::SigningKey for SigningKey { - fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { +impl crypto::SigningKey for SigningKey { + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { match offered.contains(&SIGNATURE_SCHEME) { - true => Some(Box::new(SigningKey)), + true => Some(Box::new(Self)), false => None, } } - fn algorithm(&self) -> SignatureAlgorithm { - SignatureAlgorithm::ECDSA + fn public_key(&self) -> Option> { + None } } -impl sign::Signer for SigningKey { - fn sign(&self, _message: &[u8]) -> Result, Error> { +impl crypto::Signer for SigningKey { + fn sign(self: Box, _message: &[u8]) -> Result, Error> { Ok(SIGNATURE.to_vec()) } diff --git a/rustls-fuzzing-provider/tests/smoke.rs b/rustls-fuzzing-provider/tests/smoke.rs index e10acee51de..ac36799fecd 100644 --- a/rustls-fuzzing-provider/tests/smoke.rs +++ b/rustls-fuzzing-provider/tests/smoke.rs @@ -1,10 +1,9 @@ use std::fs; use std::io::Write; +use std::sync::Arc; -use rustls::version::{TLS12, TLS13}; -use rustls::{ - ClientConfig, ClientConnection, ServerConfig, ServerConnection, SupportedProtocolVersion, -}; +use rustls::crypto::CryptoProvider; +use rustls::{ClientConfig, Connection, ServerConfig, ServerConnection}; // These tests exercise rustls_fuzzing_provider and makes sure it can // handshake with itself without errors. @@ -13,7 +12,7 @@ use rustls::{ #[test] fn pairwise_tls12() { - let transcript = test_version(&TLS12); + let transcript = test_version(rustls_fuzzing_provider::PROVIDER_TLS12); fs::write( "../fuzz/corpus/unbuffered/tls12-server.bin", @@ -44,7 +43,7 @@ fn pairwise_tls12() { #[test] fn pairwise_tls13() { - let transcript = test_version(&TLS13); + let transcript = test_version(rustls_fuzzing_provider::PROVIDER_TLS13); fs::write( "../fuzz/corpus/unbuffered/tls13-server.bin", @@ -73,26 +72,27 @@ fn pairwise_tls13() { .unwrap(); } -fn test_version(version: &'static SupportedProtocolVersion) -> Transcript { +fn test_version(provider: CryptoProvider) -> Transcript { let _ = env_logger::try_init(); - let server_config = - ServerConfig::builder_with_provider(rustls_fuzzing_provider::provider().into()) - .with_protocol_versions(&[version]) - .unwrap() - .with_no_client_auth() - .with_cert_resolver(rustls_fuzzing_provider::server_cert_resolver()); + let server_config = ServerConfig::builder(provider.clone().into()) + .with_no_client_auth() + .with_server_credential_resolver(rustls_fuzzing_provider::server_cert_resolver()) + .unwrap(); let mut server = ServerConnection::new(server_config.into()).unwrap(); - let client_config = - ClientConfig::builder_with_provider(rustls_fuzzing_provider::provider().into()) - .with_protocol_versions(&[version]) - .unwrap() + let client_config = Arc::new( + ClientConfig::builder(provider.into()) .dangerous() .with_custom_certificate_verifier(rustls_fuzzing_provider::server_verifier()) - .with_no_client_auth(); + .with_no_client_auth() + .unwrap(), + ); let hostname = "localhost".try_into().unwrap(); - let mut client = ClientConnection::new(client_config.into(), hostname).unwrap(); + let mut client = client_config + .connect(hostname) + .build() + .unwrap(); server .writer() .write_all(b"hello from server") diff --git a/rustls-post-quantum/Cargo.toml b/rustls-post-quantum/Cargo.toml index cb18f244858..b99be7f3be0 100644 --- a/rustls-post-quantum/Cargo.toml +++ b/rustls-post-quantum/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "rustls-post-quantum" -version = "0.2.1" +version = "0.3.0-alpha.0" edition = "2021" -rust-version = "1.71" +rust-version = "1.85" license = "Apache-2.0 OR ISC OR MIT" readme = "README.md" description = "Experimental support for post-quantum key exchange in rustls" @@ -12,15 +12,23 @@ categories = ["network-programming", "cryptography"] autobenches = false [dependencies] -rustls = { version = "0.23.20", features = ["aws_lc_rs"], path = "../rustls" } -aws-lc-rs = { workspace = true } +aws-lc-rs = { workspace = true, features = ["unstable"] } +rustls-aws-lc-rs = { path = "../rustls-aws-lc-rs" } +rustls = { path = "../rustls", version = "0.24.0-dev.0", default-features = false } +webpki = { workspace = true } [dev-dependencies] -criterion = "0.5" -env_logger = "0.11" -webpki-roots = "0.26" +criterion = { workspace = true } +env_logger = { workspace = true } +rcgen = { workspace = true, features = ["aws_lc_rs_unstable"] } +rustls-test = { workspace = true, default-features = false } +rustls-util = { workspace = true } +webpki-roots = { workspace = true } [[bench]] name = "benchmarks" path = "benches/benchmarks.rs" harness = false + +[lints] +workspace = true diff --git a/rustls-post-quantum/README.md b/rustls-post-quantum/README.md index ba4681472c9..438bb22278c 100644 --- a/rustls-post-quantum/README.md +++ b/rustls-post-quantum/README.md @@ -1,5 +1,5 @@

- +

@@ -8,66 +8,18 @@ Rustls is a modern TLS library written in Rust. # rustls-post-quantum -This crate provides a [`rustls::crypto::CryptoProvider`] that includes -a hybrid[^1], post-quantum-secure[^2] key exchange algorithm -- -specifically [X25519MLKEM768], as well as a non-hybrid -post-quantum-secure key exchange algorithm. +This crate provide a `CryptoProvider` built on the default aws-lc-rs default provider. -X25519MLKEM768 is pre-standardization, so you should treat -this as experimental. You may see unexpected connection failures (such as [tldr.fail]) --- [please report these to us][interop-bug]. X25519MLKEM768 is becoming widely -deployed, eg, by [Chrome] and [Cloudflare]. +Features: -The two components of this key exchange are well regarded: -X25519 alone is already used by default by rustls, and tends to have -higher quality implementations than other elliptic curves. -ML-KEM-768 was standardized by NIST in [FIPS203]. - -[^1]: meaning: a construction that runs a classical and post-quantum - key exchange, and uses the output of both together. This is a hedge - against the post-quantum half being broken. - -[^2]: a "post-quantum-secure" algorithm is one posited to be invulnerable - to attack using a cryptographically-relevant quantum computer. In contrast, - classical algorithms would be broken by such a computer. Note that such computers - do not currently exist, and may never exist, but current traffic could be captured - now and attacked later. - -[X25519MLKEM768]: -[FIPS203]: -[Chrome]: -[Cloudflare]: -[interop-bug]: -[tldr.fail]: - - -## How to use this crate - -There are a few options: - -**To use this as the rustls default provider**: include this code early in your program: - -```rust -rustls_post_quantum::provider().install_default().unwrap(); -``` - -**To incorporate just the key exchange algorithm(s) in a custom [`rustls::crypto::CryptoProvider`]**: - -```rust -use rustls::crypto::{aws_lc_rs, CryptoProvider}; -let parent = aws_lc_rs::default_provider(); -let my_provider = CryptoProvider { - kx_groups: vec![ - rustls_post_quantum::X25519MLKEM768, - aws_lc_rs::kx_group::X25519, - rustls_post_quantum::MLKEM768, - ], - ..parent -}; -``` +- `aws-lc-rs-unstable`: adds support for three variants of the experimental ML-DSA signature + algorithm. +Before rustls 0.23.22, this crate additionally provided support for the ML-KEM key exchange +(both "pure" and hybrid variants), but these have been moved to the rustls crate itself. +In rustls 0.23.22 and later, you can use rustls' `prefer-post-quantum` feature to determine +whether the ML-KEM key exchange is preferred over non-post-quantum key exchanges. This crate is release under the same licenses as the [main rustls crate][rustls]. [rustls]: https://crates.io/crates/rustls -[`rustls::crypto::CryptoProvider`]: https://docs.rs/rustls/latest/rustls/crypto/struct.CryptoProvider.html diff --git a/rustls-post-quantum/README.tpl b/rustls-post-quantum/README.tpl deleted file mode 100644 index 3054c2ce066..00000000000 --- a/rustls-post-quantum/README.tpl +++ /dev/null @@ -1,16 +0,0 @@ -

- -

- -

-Rustls is a modern TLS library written in Rust. -

- -# {{crate}} - -{{readme}} - -This crate is release under the same licenses as the [main rustls crate][rustls]. - -[rustls]: https://crates.io/crates/rustls -[`rustls::crypto::CryptoProvider`]: https://docs.rs/rustls/latest/rustls/crypto/struct.CryptoProvider.html diff --git a/rustls-post-quantum/benches/benchmarks.rs b/rustls-post-quantum/benches/benchmarks.rs index cc85f1a0883..c6cafc96bc9 100644 --- a/rustls-post-quantum/benches/benchmarks.rs +++ b/rustls-post-quantum/benches/benchmarks.rs @@ -1,13 +1,15 @@ +use core::hint::black_box; +use std::borrow::Cow; use std::sync::Arc; -use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; -use rustls::crypto::aws_lc_rs::kx_group::X25519; -use rustls::crypto::{ - aws_lc_rs, ActiveKeyExchange, CryptoProvider, SharedSecret, SupportedKxGroup, +use criterion::{Criterion, Throughput, criterion_group, criterion_main}; +use rustls::crypto::CryptoProvider; +use rustls::crypto::kx::{ + ActiveKeyExchange, HybridKeyExchange, NamedGroup, SharedSecret, StartedKeyExchange, + SupportedKxGroup, }; -use rustls::ffdhe_groups::FfdheGroup; -use rustls::{ClientConfig, ClientConnection, Error, NamedGroup, RootCertStore}; -use rustls_post_quantum::{MLKEM768, X25519MLKEM768}; +use rustls::{ClientConfig, Connection, Error, RootCertStore}; +use rustls_aws_lc_rs::kx_group::{MLKEM768, X25519, X25519MLKEM768}; fn bench_client(c: &mut Criterion) { let mut group = c.benchmark_group("client"); @@ -73,28 +75,26 @@ fn bench_clienthello(c: &mut Criterion) { roots: webpki_roots::TLS_SERVER_ROOTS.into(), }); - let config_x25519 = ClientConfig::builder_with_provider(aws_lc_rs::default_provider().into()) - .with_safe_default_protocol_versions() - .unwrap() - .with_root_certificates(anchors.clone()) - .with_no_client_auth() - .into(); - - let config_x25519mlkem768 = - ClientConfig::builder_with_provider(rustls_post_quantum::provider().into()) - .with_safe_default_protocol_versions() - .unwrap() + let config_x25519 = Arc::new( + ClientConfig::builder(rustls_aws_lc_rs::DEFAULT_PROVIDER.into()) .with_root_certificates(anchors.clone()) .with_no_client_auth() - .into(); + .unwrap(), + ); - let config_x25519mlkem768_x25519 = - ClientConfig::builder_with_provider(separate_provider().into()) - .with_safe_default_protocol_versions() - .unwrap() + let config_x25519mlkem768 = Arc::new( + ClientConfig::builder(rustls_post_quantum::DEFAULT_PROVIDER.into()) .with_root_certificates(anchors.clone()) .with_no_client_auth() - .into(); + .unwrap(), + ); + + let config_x25519mlkem768_x25519 = Arc::new( + ClientConfig::builder(separate_provider().into()) + .with_root_certificates(anchors) + .with_no_client_auth() + .unwrap(), + ); println!("Clienthello lengths:"); println!(" X25519 alone = {:?}", do_client_hello(&config_x25519)); @@ -119,7 +119,10 @@ fn bench_clienthello(c: &mut Criterion) { } fn do_client_hello(config: &Arc) -> usize { - let mut conn = ClientConnection::new(config.clone(), "localhost".try_into().unwrap()).unwrap(); + let mut conn = config + .connect("localhost".try_into().unwrap()) + .build() + .unwrap(); let mut buf = vec![]; let len = conn.write_tls(&mut &mut buf).unwrap(); black_box(buf); @@ -127,15 +130,16 @@ fn do_client_hello(config: &Arc) -> usize { } fn separate_provider() -> CryptoProvider { + const KX_GROUPS: &[&'static dyn SupportedKxGroup] = &[ + &SeparateX25519Mlkem768, + MLKEM768, + X25519, + rustls_aws_lc_rs::kx_group::SECP256R1, + rustls_aws_lc_rs::kx_group::SECP384R1, + ]; CryptoProvider { - kx_groups: vec![ - &SeparateX25519Mlkem768, - MLKEM768, - X25519, - aws_lc_rs::kx_group::SECP256R1, - aws_lc_rs::kx_group::SECP384R1, - ], - ..aws_lc_rs::default_provider() + kx_groups: Cow::Borrowed(KX_GROUPS), + ..rustls_aws_lc_rs::DEFAULT_PROVIDER } } @@ -144,24 +148,27 @@ fn separate_provider() -> CryptoProvider { struct SeparateX25519Mlkem768; impl SupportedKxGroup for SeparateX25519Mlkem768 { - fn start(&self) -> Result, Error> { - Ok(Box::new(Active { - hybrid: X25519MLKEM768.start()?, - separate: X25519.start()?, - })) + fn start(&self) -> Result { + let StartedKeyExchange::Hybrid(hybrid) = X25519MLKEM768.start()? else { + unreachable!(); + }; + let StartedKeyExchange::Single(separate) = X25519.start()? else { + unreachable!(); + }; + + Ok(StartedKeyExchange::Hybrid(Box::new(Active { + hybrid, + separate, + }))) } fn name(&self) -> NamedGroup { X25519MLKEM768.name() } - - fn ffdhe_group(&self) -> Option> { - X25519MLKEM768.ffdhe_group() - } } struct Active { - hybrid: Box, + hybrid: Box, separate: Box, } @@ -170,10 +177,6 @@ impl ActiveKeyExchange for Active { todo!() } - fn hybrid_component(&self) -> Option<(NamedGroup, &[u8])> { - Some((self.separate.group(), self.separate.pub_key())) - } - fn group(&self) -> NamedGroup { self.hybrid.group() } @@ -183,5 +186,23 @@ impl ActiveKeyExchange for Active { } } +impl HybridKeyExchange for Active { + fn component(&self) -> (NamedGroup, &[u8]) { + (self.separate.group(), self.separate.pub_key()) + } + + fn complete_component(self: Box, peer_pub_key: &[u8]) -> Result { + self.separate.complete(peer_pub_key) + } + + fn as_key_exchange(&self) -> &(dyn ActiveKeyExchange + 'static) { + self + } + + fn into_key_exchange(self: Box) -> Box { + self.separate + } +} + criterion_group!(benches, bench_client, bench_server, bench_clienthello); criterion_main!(benches); diff --git a/rustls-post-quantum/examples/client.rs b/rustls-post-quantum/examples/client.rs index b11f0578d3a..512b3431817 100644 --- a/rustls-post-quantum/examples/client.rs +++ b/rustls-post-quantum/examples/client.rs @@ -7,30 +7,32 @@ //! Note that `unwrap()` is used to deal with networking errors; this is not something //! that is sensible outside of example code. -use std::io::{stdout, Read, Write}; +use std::io::{Read, Write, stdout}; use std::net::TcpStream; use std::sync::Arc; fn main() { env_logger::init(); - rustls_post_quantum::provider() - .install_default() - .unwrap(); let root_store = rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.into(), }; - let config = rustls::ClientConfig::builder() + let config = rustls::ClientConfig::builder(Arc::new(rustls_post_quantum::DEFAULT_PROVIDER)) .with_root_certificates(root_store) - .with_no_client_auth(); + .with_no_client_auth() + .unwrap(); let server_name = "pq.cloudflareresearch.com" .try_into() .unwrap(); - let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap(); + + let mut conn = Arc::new(config) + .connect(server_name) + .build() + .unwrap(); let mut sock = TcpStream::connect("pq.cloudflareresearch.com:443").unwrap(); - let mut tls = rustls::Stream::new(&mut conn, &mut sock); + let mut tls = rustls_util::Stream::new(&mut conn, &mut sock); tls.write_all( concat!( "GET /cdn-cgi/trace HTTP/1.0\r\n", diff --git a/rustls-post-quantum/src/hybrid.rs b/rustls-post-quantum/src/hybrid.rs deleted file mode 100644 index 2be2da77c21..00000000000 --- a/rustls-post-quantum/src/hybrid.rs +++ /dev/null @@ -1,175 +0,0 @@ -use rustls::crypto::{ActiveKeyExchange, CompletedKeyExchange, SharedSecret, SupportedKxGroup}; -use rustls::ffdhe_groups::FfdheGroup; -use rustls::{Error, NamedGroup, ProtocolVersion}; - -use crate::INVALID_KEY_SHARE; - -/// A generalization of hybrid key exchange. -#[derive(Debug)] -pub(crate) struct Hybrid { - pub(crate) classical: &'static dyn SupportedKxGroup, - pub(crate) post_quantum: &'static dyn SupportedKxGroup, - pub(crate) name: NamedGroup, - pub(crate) layout: Layout, -} - -impl SupportedKxGroup for Hybrid { - fn start(&self) -> Result, Error> { - let classical = self.classical.start()?; - let post_quantum = self.post_quantum.start()?; - - let combined_pub_key = self - .layout - .concat(post_quantum.pub_key(), classical.pub_key()); - - Ok(Box::new(ActiveHybrid { - classical, - post_quantum, - name: self.name, - layout: self.layout, - combined_pub_key, - })) - } - - fn start_and_complete(&self, client_share: &[u8]) -> Result { - let (post_quantum_share, classical_share) = self - .layout - .split_received_client_share(client_share) - .ok_or(INVALID_KEY_SHARE)?; - - let cl = self - .classical - .start_and_complete(classical_share)?; - let pq = self - .post_quantum - .start_and_complete(post_quantum_share)?; - - let combined_pub_key = self - .layout - .concat(&pq.pub_key, &cl.pub_key); - let secret = self - .layout - .concat(pq.secret.secret_bytes(), cl.secret.secret_bytes()); - - Ok(CompletedKeyExchange { - group: self.name, - pub_key: combined_pub_key, - secret: SharedSecret::from(secret), - }) - } - - fn ffdhe_group(&self) -> Option> { - None - } - - fn name(&self) -> NamedGroup { - self.name - } - - fn usable_for_version(&self, version: ProtocolVersion) -> bool { - version == ProtocolVersion::TLSv1_3 - } -} - -struct ActiveHybrid { - classical: Box, - post_quantum: Box, - name: NamedGroup, - layout: Layout, - combined_pub_key: Vec, -} - -impl ActiveKeyExchange for ActiveHybrid { - fn complete(self: Box, peer_pub_key: &[u8]) -> Result { - let (post_quantum_share, classical_share) = self - .layout - .split_received_server_share(peer_pub_key) - .ok_or(INVALID_KEY_SHARE)?; - - let cl = self - .classical - .complete(classical_share)?; - let pq = self - .post_quantum - .complete(post_quantum_share)?; - - let secret = self - .layout - .concat(pq.secret_bytes(), cl.secret_bytes()); - Ok(SharedSecret::from(secret)) - } - - /// Allow the classical computation to be offered and selected separately. - fn hybrid_component(&self) -> Option<(NamedGroup, &[u8])> { - Some((self.classical.group(), self.classical.pub_key())) - } - - fn complete_hybrid_component( - self: Box, - peer_pub_key: &[u8], - ) -> Result { - self.classical.complete(peer_pub_key) - } - - fn pub_key(&self) -> &[u8] { - &self.combined_pub_key - } - - fn ffdhe_group(&self) -> Option> { - None - } - - fn group(&self) -> NamedGroup { - self.name - } -} - -#[derive(Clone, Copy, Debug)] -pub(crate) struct Layout { - /// Length of classical key share. - pub(crate) classical_share_len: usize, - - /// Length of post-quantum key share sent by client - pub(crate) post_quantum_client_share_len: usize, - - /// Length of post-quantum key share sent by server - pub(crate) post_quantum_server_share_len: usize, - - /// Whether the post-quantum element comes first in shares and secrets. - /// - /// For dismal and unprincipled reasons, SECP256R1MLKEM768 has the - /// classical element first, while X25519MLKEM768 has it second. - pub(crate) post_quantum_first: bool, -} - -impl Layout { - fn split_received_client_share<'a>(&self, share: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> { - self.split(share, self.post_quantum_client_share_len) - } - - fn split_received_server_share<'a>(&self, share: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> { - self.split(share, self.post_quantum_server_share_len) - } - - fn split<'a>( - &self, - share: &'a [u8], - post_quantum_share_len: usize, - ) -> Option<(&'a [u8], &'a [u8])> { - if share.len() != self.classical_share_len + post_quantum_share_len { - return None; - } - - Some(match self.post_quantum_first { - true => share.split_at(post_quantum_share_len), - false => share.split_at(self.classical_share_len), - }) - } - - fn concat(&self, post_quantum: &[u8], classical: &[u8]) -> Vec { - match self.post_quantum_first { - true => [post_quantum, classical].concat(), - false => [classical, post_quantum].concat(), - } - } -} diff --git a/rustls-post-quantum/src/lib.rs b/rustls-post-quantum/src/lib.rs index 89aae035b92..201ed41d8a3 100644 --- a/rustls-post-quantum/src/lib.rs +++ b/rustls-post-quantum/src/lib.rs @@ -1,103 +1,342 @@ -//! This crate provides a [`rustls::crypto::CryptoProvider`] that includes -//! a hybrid[^1], post-quantum-secure[^2] key exchange algorithm -- -//! specifically [X25519MLKEM768], as well as a non-hybrid -//! post-quantum-secure key exchange algorithm. +//! This crate provide a [`CryptoProvider`] built on the default aws-lc-rs default provider. //! -//! X25519MLKEM768 is pre-standardization, so you should treat -//! this as experimental. You may see unexpected connection failures (such as [tldr.fail]) -//! -- [please report these to us][interop-bug]. X25519MLKEM768 is becoming widely -//! deployed, eg, by [Chrome] and [Cloudflare]. +//! Features: //! -//! The two components of this key exchange are well regarded: -//! X25519 alone is already used by default by rustls, and tends to have -//! higher quality implementations than other elliptic curves. -//! ML-KEM-768 was standardized by NIST in [FIPS203]. -//! -//! [^1]: meaning: a construction that runs a classical and post-quantum -//! key exchange, and uses the output of both together. This is a hedge -//! against the post-quantum half being broken. -//! -//! [^2]: a "post-quantum-secure" algorithm is one posited to be invulnerable -//! to attack using a cryptographically-relevant quantum computer. In contrast, -//! classical algorithms would be broken by such a computer. Note that such computers -//! do not currently exist, and may never exist, but current traffic could be captured -//! now and attacked later. -//! -//! [X25519MLKEM768]: -//! [FIPS203]: -//! [Chrome]: -//! [Cloudflare]: -//! [interop-bug]: -//! [tldr.fail]: -//! -//! -//! # How to use this crate -//! -//! There are a few options: -//! -//! **To use this as the rustls default provider**: include this code early in your program: -//! -//! ```rust -//! rustls_post_quantum::provider().install_default().unwrap(); -//! ``` -//! -//! **To incorporate just the key exchange algorithm(s) in a custom [`rustls::crypto::CryptoProvider`]**: -//! -//! ```rust -//! use rustls::crypto::{aws_lc_rs, CryptoProvider}; -//! let parent = aws_lc_rs::default_provider(); -//! let my_provider = CryptoProvider { -//! kx_groups: vec![ -//! rustls_post_quantum::X25519MLKEM768, -//! aws_lc_rs::kx_group::X25519, -//! rustls_post_quantum::MLKEM768, -//! ], -//! ..parent -//! }; -//! ``` +//! - `aws-lc-rs-unstable`: adds support for three variants of the experimental ML-DSA signature +//! algorithm. //! +//! Before rustls 0.23.22, this crate additionally provided support for the ML-KEM key exchange +//! (both "pure" and hybrid variants), but these have been moved to the rustls crate itself. +//! In rustls 0.23.22 and later, you can use rustls' `prefer-post-quantum` feature to determine +//! whether the ML-KEM key exchange is preferred over non-post-quantum key exchanges. + +use core::fmt::{self, Debug, Formatter}; +use std::sync::Arc; + +use aws_lc_rs::signature::KeyPair; +use aws_lc_rs::unstable::signature::{ + ML_DSA_44_SIGNING, ML_DSA_65_SIGNING, ML_DSA_87_SIGNING, PqdsaKeyPair, PqdsaSigningAlgorithm, +}; +use rustls::Error; +use rustls::crypto::{ + CryptoProvider, KeyProvider, SignatureScheme, Signer, SigningKey, WebPkiSupportedAlgorithms, + public_key_to_spki, +}; +use rustls::pki_types::{ + AlgorithmIdentifier, FipsStatus, PrivateKeyDer, SignatureVerificationAlgorithm, + SubjectPublicKeyInfoDer, alg_id, +}; +use rustls_aws_lc_rs::AwsLcRsVerificationAlgorithm; + +/// The default `CryptoProvider` backed by aws-lc-rs. +pub const DEFAULT_PROVIDER: CryptoProvider = CryptoProvider { + signature_verification_algorithms: SUPPORTED_SIG_ALGS, + key_provider: &PqAwsLcRs, + ..rustls_aws_lc_rs::DEFAULT_PROVIDER +}; -use rustls::crypto::aws_lc_rs::{default_provider, kx_group}; -use rustls::crypto::{CryptoProvider, SupportedKxGroup}; -use rustls::{Error, NamedGroup, PeerMisbehaved}; +#[derive(Debug)] +pub struct PqAwsLcRs; -mod hybrid; -mod mlkem; +impl KeyProvider for PqAwsLcRs { + fn load_private_key( + &self, + key_der: PrivateKeyDer<'static>, + ) -> Result, Error> { + // TODO: support `PqdsaKeyPair::from_raw_private_key()`? + if let PrivateKeyDer::Pkcs8(pkcs8) = &key_der { + for kind in PqdsaKeyKind::iter() { + match PqdsaKeyPair::from_pkcs8(kind.to_alg(), pkcs8.secret_pkcs8_der()) { + Ok(key_pair) => { + return Ok(Box::new(PqdsaSigningKey { + kind, + inner: Arc::new(key_pair), + })); + } + Err(_) => continue, + } + } + } -/// A `CryptoProvider` which includes `X25519MLKEM768` and `MLKEM768` -/// key exchanges. -pub fn provider() -> CryptoProvider { - let mut parent = default_provider(); + match rustls_aws_lc_rs::DEFAULT_KEY_PROVIDER.load_private_key(key_der) { + Ok(key) => Ok(key), + Err(_) => Err(Error::General( + "failed to parse private key as ML-DSA, RSA, ECDSA, or EdDSA".into(), + )), + } + } - parent - .kx_groups - .splice(0..0, [X25519MLKEM768, MLKEM768]); + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated + } +} + +struct PqdsaSigningKey { + kind: PqdsaKeyKind, + inner: Arc, +} + +impl SigningKey for PqdsaSigningKey { + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { + if !offered.contains(&self.kind.scheme()) { + return None; + } + + Some(Box::new(PqdsaSigner { + key: self.inner.clone(), + kind: self.kind, + })) + } + + fn public_key(&self) -> Option> { + Some(public_key_to_spki( + &self.kind.alg_id(), + self.inner.public_key(), + )) + } +} + +impl Debug for PqdsaSigningKey { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("PqdsaSigningKey") + .field("scheme", &self.kind.scheme()) + .finish_non_exhaustive() + } +} - parent +struct PqdsaSigner { + key: Arc, + kind: PqdsaKeyKind, } -/// This is the [X25519MLKEM768] key exchange. -/// -/// [X25519MLKEM768]: -pub static X25519MLKEM768: &dyn SupportedKxGroup = &hybrid::Hybrid { - classical: kx_group::X25519, - post_quantum: MLKEM768, - name: NamedGroup::X25519MLKEM768, - layout: hybrid::Layout { - classical_share_len: X25519_LEN, - post_quantum_client_share_len: MLKEM768_ENCAP_LEN, - post_quantum_server_share_len: MLKEM768_CIPHERTEXT_LEN, - post_quantum_first: true, - }, +impl Signer for PqdsaSigner { + fn sign(self: Box, message: &[u8]) -> Result, Error> { + let expected_sig_len = self.key.algorithm().signature_len(); + let mut sig = vec![0; expected_sig_len]; + let actual_sig_len = self + .key + .sign(message, &mut sig) + .map_err(|_| Error::General("signing failed".into()))?; + + if actual_sig_len != expected_sig_len { + return Err(Error::General("unexpected signature length".into())); + } + + Ok(sig) + } + + fn scheme(&self) -> SignatureScheme { + self.kind.scheme() + } +} + +impl Debug for PqdsaSigner { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("PqdsaSigner") + .field("scheme", &self.kind.scheme()) + .finish_non_exhaustive() + } +} + +#[derive(Clone, Copy)] +enum PqdsaKeyKind { + MlDsa44, + MlDsa65, + MlDsa87, +} + +impl PqdsaKeyKind { + fn iter() -> impl Iterator { + [Self::MlDsa44, Self::MlDsa65, Self::MlDsa87].into_iter() + } + + fn to_alg(self) -> &'static PqdsaSigningAlgorithm { + match self { + Self::MlDsa44 => &ML_DSA_44_SIGNING, + Self::MlDsa65 => &ML_DSA_65_SIGNING, + Self::MlDsa87 => &ML_DSA_87_SIGNING, + } + } + + fn scheme(&self) -> SignatureScheme { + match self { + Self::MlDsa44 => SignatureScheme::ML_DSA_44, + Self::MlDsa65 => SignatureScheme::ML_DSA_65, + Self::MlDsa87 => SignatureScheme::ML_DSA_87, + } + } + + fn alg_id(&self) -> AlgorithmIdentifier { + match self { + Self::MlDsa44 => alg_id::ML_DSA_44, + Self::MlDsa65 => alg_id::ML_DSA_65, + Self::MlDsa87 => alg_id::ML_DSA_87, + } + } +} + +/// Keep in sync with the `SUPPORTED_SIG_ALGS` in `rustls_aws_lc_rs`. +static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = match WebPkiSupportedAlgorithms::new( + &[ + rustls_aws_lc_rs::ECDSA_P256_SHA256, + rustls_aws_lc_rs::ECDSA_P256_SHA384, + rustls_aws_lc_rs::ECDSA_P384_SHA256, + rustls_aws_lc_rs::ECDSA_P384_SHA384, + rustls_aws_lc_rs::ECDSA_P521_SHA256, + rustls_aws_lc_rs::ECDSA_P521_SHA384, + rustls_aws_lc_rs::ECDSA_P521_SHA512, + rustls_aws_lc_rs::ED25519, + rustls_aws_lc_rs::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, + rustls_aws_lc_rs::RSA_PSS_2048_8192_SHA384_LEGACY_KEY, + rustls_aws_lc_rs::RSA_PSS_2048_8192_SHA512_LEGACY_KEY, + rustls_aws_lc_rs::RSA_PKCS1_2048_8192_SHA256, + rustls_aws_lc_rs::RSA_PKCS1_2048_8192_SHA384, + rustls_aws_lc_rs::RSA_PKCS1_2048_8192_SHA512, + rustls_aws_lc_rs::RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS, + rustls_aws_lc_rs::RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS, + rustls_aws_lc_rs::RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS, + ML_DSA_44, + ML_DSA_65, + ML_DSA_87, + ], + &[ + // Note: for TLS1.2 the curve is not fixed by SignatureScheme. For TLS1.3 it is. + ( + SignatureScheme::ECDSA_NISTP384_SHA384, + &[ + rustls_aws_lc_rs::ECDSA_P384_SHA384, + rustls_aws_lc_rs::ECDSA_P256_SHA384, + rustls_aws_lc_rs::ECDSA_P521_SHA384, + ], + ), + ( + SignatureScheme::ECDSA_NISTP256_SHA256, + &[ + rustls_aws_lc_rs::ECDSA_P256_SHA256, + rustls_aws_lc_rs::ECDSA_P384_SHA256, + rustls_aws_lc_rs::ECDSA_P521_SHA256, + ], + ), + ( + SignatureScheme::ECDSA_NISTP521_SHA512, + &[ + rustls_aws_lc_rs::ECDSA_P521_SHA512, + rustls_aws_lc_rs::ECDSA_P384_SHA512, + rustls_aws_lc_rs::ECDSA_P256_SHA512, + ], + ), + (SignatureScheme::ED25519, &[rustls_aws_lc_rs::ED25519]), + ( + SignatureScheme::RSA_PSS_SHA512, + &[rustls_aws_lc_rs::RSA_PSS_2048_8192_SHA512_LEGACY_KEY], + ), + ( + SignatureScheme::RSA_PSS_SHA384, + &[rustls_aws_lc_rs::RSA_PSS_2048_8192_SHA384_LEGACY_KEY], + ), + ( + SignatureScheme::RSA_PSS_SHA256, + &[rustls_aws_lc_rs::RSA_PSS_2048_8192_SHA256_LEGACY_KEY], + ), + ( + SignatureScheme::RSA_PKCS1_SHA512, + &[rustls_aws_lc_rs::RSA_PKCS1_2048_8192_SHA512], + ), + ( + SignatureScheme::RSA_PKCS1_SHA384, + &[rustls_aws_lc_rs::RSA_PKCS1_2048_8192_SHA384], + ), + ( + SignatureScheme::RSA_PKCS1_SHA256, + &[rustls_aws_lc_rs::RSA_PKCS1_2048_8192_SHA256], + ), + (SignatureScheme::ML_DSA_44, &[ML_DSA_44]), + (SignatureScheme::ML_DSA_65, &[ML_DSA_65]), + (SignatureScheme::ML_DSA_87, &[ML_DSA_87]), + ], +) { + Ok(algs) => algs, + Err(_) => panic!("bad WebPkiSupportedAlgorithms"), +}; + +/// ML-DSA signatures using the [4, 4] matrix (security strength category 2). +pub static ML_DSA_44: &dyn SignatureVerificationAlgorithm = &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::ML_DSA_44, + signature_alg_id: alg_id::ML_DSA_44, + verification_alg: &aws_lc_rs::unstable::signature::ML_DSA_44, + // Not included in AWS-LC-FIPS 3.0 FIPS scope + in_fips_submission: false, +}; + +/// ML-DSA signatures using the [6, 5] matrix (security strength category 3). +pub static ML_DSA_65: &dyn SignatureVerificationAlgorithm = &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::ML_DSA_65, + signature_alg_id: alg_id::ML_DSA_65, + verification_alg: &aws_lc_rs::unstable::signature::ML_DSA_65, + // Not included in AWS-LC-FIPS 3.0 FIPS scope + in_fips_submission: false, +}; + +/// ML-DSA signatures using the [8. 7] matrix (security strength category 5). +pub static ML_DSA_87: &dyn SignatureVerificationAlgorithm = &AwsLcRsVerificationAlgorithm { + public_key_alg_id: alg_id::ML_DSA_87, + signature_alg_id: alg_id::ML_DSA_87, + verification_alg: &aws_lc_rs::unstable::signature::ML_DSA_87, + // Not included in AWS-LC-FIPS 3.0 FIPS scope + in_fips_submission: false, }; -/// This is the [MLKEM] key exchange. -/// -/// [MLKEM]: https://datatracker.ietf.org/doc/draft-connolly-tls-mlkem-key-agreement -pub static MLKEM768: &dyn SupportedKxGroup = &mlkem::MlKem768; +#[cfg(test)] +mod tests { + use rcgen::{ + CertificateParams, CertifiedIssuer, ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose, + }; + use rustls::crypto::Identity; + use rustls::{ClientConfig, RootCertStore, ServerConfig, ServerConnection}; + use rustls_test::do_handshake; + + use super::*; + + #[test] + fn ml_dsa() { + let ca_key = KeyPair::generate_for(&rcgen::PKCS_ML_DSA_44).unwrap(); + let mut ca_params = CertificateParams::new(vec!["Test CA".into()]).unwrap(); + ca_params.is_ca = IsCa::Ca(rcgen::BasicConstraints::Unconstrained); + ca_params.key_usages = vec![ + KeyUsagePurpose::DigitalSignature, + KeyUsagePurpose::KeyCertSign, + ]; + ca_params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth]; + let issuer = CertifiedIssuer::self_signed(ca_params, ca_key).unwrap(); -const INVALID_KEY_SHARE: Error = Error::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare); + let ee_key = KeyPair::generate_for(&rcgen::PKCS_ML_DSA_87).unwrap(); + let ee_params = CertificateParams::new(vec!["localhost".into()]).unwrap(); + let ee_cert = ee_params + .signed_by(&ee_key, &issuer) + .unwrap(); -const X25519_LEN: usize = 32; -const MLKEM768_CIPHERTEXT_LEN: usize = 1088; -const MLKEM768_ENCAP_LEN: usize = 1184; + let provider = Arc::new(DEFAULT_PROVIDER); + let server_config = ServerConfig::builder(provider.clone()) + .with_no_client_auth() + .with_single_cert( + Arc::new(Identity::from_cert_chain(vec![ee_cert.der().clone()]).unwrap()), + PrivateKeyDer::try_from(ee_key.serialize_der()).unwrap(), + ) + .unwrap(); + + let mut roots = RootCertStore::empty(); + roots.add(issuer.der().clone()).unwrap(); + let mut client = Arc::new( + ClientConfig::builder(provider) + .with_root_certificates(roots) + .with_no_client_auth() + .unwrap(), + ) + .connect("localhost".try_into().unwrap()) + .build() + .unwrap(); + + let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); + do_handshake(&mut client, &mut server); + } +} diff --git a/rustls-post-quantum/src/mlkem.rs b/rustls-post-quantum/src/mlkem.rs deleted file mode 100644 index 9756b6ed474..00000000000 --- a/rustls-post-quantum/src/mlkem.rs +++ /dev/null @@ -1,84 +0,0 @@ -use aws_lc_rs::kem; -use rustls::crypto::{ActiveKeyExchange, CompletedKeyExchange, SharedSecret, SupportedKxGroup}; -use rustls::ffdhe_groups::FfdheGroup; -use rustls::{Error, NamedGroup, ProtocolVersion}; - -use crate::INVALID_KEY_SHARE; - -#[derive(Debug)] -pub(crate) struct MlKem768; - -impl SupportedKxGroup for MlKem768 { - fn start(&self) -> Result, Error> { - let decaps_key = kem::DecapsulationKey::generate(&kem::ML_KEM_768) - .map_err(|_| Error::General("key generation failed".into()))?; - - let pub_key_bytes = decaps_key - .encapsulation_key() - .and_then(|encaps_key| encaps_key.key_bytes()) - .map_err(|_| Error::General("encaps failed".into()))?; - - Ok(Box::new(Active { - decaps_key: Box::new(decaps_key), - encaps_key_bytes: Vec::from(pub_key_bytes.as_ref()), - })) - } - - fn start_and_complete(&self, client_share: &[u8]) -> Result { - let encaps_key = kem::EncapsulationKey::new(&kem::ML_KEM_768, client_share) - .map_err(|_| INVALID_KEY_SHARE)?; - - let (ciphertext, shared_secret) = encaps_key - .encapsulate() - .map_err(|_| INVALID_KEY_SHARE)?; - - Ok(CompletedKeyExchange { - group: self.name(), - pub_key: Vec::from(ciphertext.as_ref()), - secret: SharedSecret::from(shared_secret.as_ref()), - }) - } - - fn ffdhe_group(&self) -> Option> { - None - } - - fn name(&self) -> NamedGroup { - NamedGroup::MLKEM768 - } - - fn usable_for_version(&self, version: ProtocolVersion) -> bool { - version == ProtocolVersion::TLSv1_3 - } -} - -struct Active { - decaps_key: Box>, - encaps_key_bytes: Vec, -} - -impl ActiveKeyExchange for Active { - // The received 'peer_pub_key' is actually the ML-KEM ciphertext, - // which when decapsulated with our `decaps_key` produces the shared - // secret. - fn complete(self: Box, peer_pub_key: &[u8]) -> Result { - let shared_secret = self - .decaps_key - .decapsulate(peer_pub_key.into()) - .map_err(|_| INVALID_KEY_SHARE)?; - - Ok(SharedSecret::from(shared_secret.as_ref())) - } - - fn pub_key(&self) -> &[u8] { - &self.encaps_key_bytes - } - - fn ffdhe_group(&self) -> Option> { - None - } - - fn group(&self) -> NamedGroup { - NamedGroup::MLKEM768 - } -} diff --git a/rustls-provider-test/Cargo.toml b/rustls-provider-test/Cargo.toml index 205e391df97..0314d5ea21a 100644 --- a/rustls-provider-test/Cargo.toml +++ b/rustls-provider-test/Cargo.toml @@ -8,7 +8,10 @@ publish = false [dependencies] hex = "0.4" -provider-example = { package = "rustls-provider-example", version = "0.0.1", path = "../provider-example" } -rustls = { version = "0.23.8", features = ["aws_lc_rs", "logging"], path = "../rustls" } +rustls = { version = "0.24.0-dev.0", features = ["log"], path = "../rustls" } +rustls-aws-lc-rs = { path = "../rustls-aws-lc-rs" } serde = { version = "1", features = ["derive"] } serde_json = "1" + +[lints] +workspace = true diff --git a/rustls-provider-test/tests/hpke.rs b/rustls-provider-test/tests/hpke.rs index 1d6f910c9de..3cbd47e5dc3 100644 --- a/rustls-provider-test/tests/hpke.rs +++ b/rustls-provider-test/tests/hpke.rs @@ -1,9 +1,9 @@ use std::fs::File; -use rustls::crypto::aws_lc_rs; -use rustls::crypto::hpke::{Hpke, HpkePrivateKey, HpkePublicKey, HpkeSuite}; -use rustls::internal::msgs::enums::{HpkeAead, HpkeKdf, HpkeKem}; -use rustls::internal::msgs::handshake::HpkeSymmetricCipherSuite; +use rustls::crypto::hpke::{ + Hpke, HpkeAead, HpkeKdf, HpkeKem, HpkePrivateKey, HpkePublicKey, HpkeSuite, + HpkeSymmetricCipherSuite, +}; use serde::Deserialize; /// Confirm open/seal operations work using the test vectors from [RFC 9180 Appendix A]. @@ -77,29 +77,11 @@ impl TestVector { return None; } - match ( - Self::lookup_suite(self.suite(), aws_lc_rs::hpke::ALL_SUPPORTED_SUITES), - Self::lookup_suite(self.suite(), provider_example::hpke::ALL_SUPPORTED_SUITES), - ) { - // Both providers support the suite. Test against themselves, and each other. - (Some(aws_suite), Some(hpke_rs_suite)) => Some(vec![ - (aws_suite, aws_suite), - (hpke_rs_suite, hpke_rs_suite), - (aws_suite, hpke_rs_suite), - (hpke_rs_suite, aws_suite), - ]), - - // aws-lc-rs supported the suite, not hpke-rs, test against itself - (Some(aws_suite), None) => Some(vec![(aws_suite, aws_suite)]), - - // hpke-rs supported the suite, not AWS-LC-RS, test against itself - // - // Note: presently there are no suites hpke-rs supports that aws-lc-rs doesn't. This - // is future-proofing. - (None, Some(hpke_rs_suite)) => Some(vec![(hpke_rs_suite, hpke_rs_suite)]), - + match Self::lookup_suite(self.suite(), rustls_aws_lc_rs::hpke::ALL_SUPPORTED_SUITES) { + // aws-lc-rs supported the suite, test against itself + Some(aws_suite) => Some(vec![(aws_suite, aws_suite)]), // Neither provider supported the suite - nothing to do. - (None, None) => None, + None => None, } } diff --git a/rustls-ring/Cargo.toml b/rustls-ring/Cargo.toml new file mode 100644 index 00000000000..eec673610a7 --- /dev/null +++ b/rustls-ring/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "rustls-ring" +version = "0.1.0-dev.0" +edition = "2021" +rust-version = "1.85" +license = "Apache-2.0 OR ISC OR MIT" +description = "A *ring*-based crypto provider for rustls." +homepage = "https://github.com/rustls/rustls" +repository = "https://github.com/rustls/rustls" +categories = ["network-programming", "cryptography"] +autobenches = false + +[features] +default = ["std"] +std = [] + +[dependencies] +pki-types = { workspace = true } +rustls = { path = "../rustls", version = "0.24.0-dev.0", default-features = false } +ring = { workspace = true } +subtle = { workspace = true } + +[dev-dependencies] +bencher = { workspace = true } +rustls-test = { workspace = true, default-features = false } + +[[bench]] +name = "benchmarks" +path = "benches/benchmarks.rs" +harness = false + +[lints] +workspace = true diff --git a/rustls-ring/benches/benchmarks.rs b/rustls-ring/benches/benchmarks.rs new file mode 100644 index 00000000000..685b2061bd6 --- /dev/null +++ b/rustls-ring/benches/benchmarks.rs @@ -0,0 +1,14 @@ +use std::sync::Arc; + +use bencher::{Bencher, benchmark_group, benchmark_main}; +use rustls::{Connection, ServerConnection}; +use rustls_test::{KeyType, TestNonBlockIo, make_server_config}; + +fn bench_ewouldblock(c: &mut Bencher) { + let server_config = make_server_config(KeyType::Rsa2048, &rustls_ring::DEFAULT_PROVIDER); + let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); + c.iter(|| server.read_tls(&mut TestNonBlockIo::default())); +} + +benchmark_group!(benches, bench_ewouldblock); +benchmark_main!(benches); diff --git a/rustls-ring/src/data/alg-rsa-pkcs1-sha256-absent-params.der b/rustls-ring/src/data/alg-rsa-pkcs1-sha256-absent-params.der new file mode 100644 index 00000000000..a934df82f92 --- /dev/null +++ b/rustls-ring/src/data/alg-rsa-pkcs1-sha256-absent-params.der @@ -0,0 +1 @@ + *†H†÷  \ No newline at end of file diff --git a/rustls-ring/src/data/alg-rsa-pkcs1-sha384-absent-params.der b/rustls-ring/src/data/alg-rsa-pkcs1-sha384-absent-params.der new file mode 100644 index 00000000000..03aa775d69c --- /dev/null +++ b/rustls-ring/src/data/alg-rsa-pkcs1-sha384-absent-params.der @@ -0,0 +1 @@ + *†H†÷  \ No newline at end of file diff --git a/rustls-ring/src/data/alg-rsa-pkcs1-sha512-absent-params.der b/rustls-ring/src/data/alg-rsa-pkcs1-sha512-absent-params.der new file mode 100644 index 00000000000..e59473c5701 --- /dev/null +++ b/rustls-ring/src/data/alg-rsa-pkcs1-sha512-absent-params.der @@ -0,0 +1 @@ + *†H†÷  \ No newline at end of file diff --git a/rustls-ring/src/hash.rs b/rustls-ring/src/hash.rs new file mode 100644 index 00000000000..bfb1ac61c36 --- /dev/null +++ b/rustls-ring/src/hash.rs @@ -0,0 +1,58 @@ +use alloc::boxed::Box; + +use pki_types::FipsStatus; +use ring::digest; +use rustls::crypto::{self, HashAlgorithm}; + +pub(crate) static SHA256: Hash = Hash(&digest::SHA256, HashAlgorithm::SHA256); +pub(crate) static SHA384: Hash = Hash(&digest::SHA384, HashAlgorithm::SHA384); + +pub(crate) struct Hash(&'static digest::Algorithm, HashAlgorithm); + +impl crypto::hash::Hash for Hash { + fn start(&self) -> Box { + Box::new(Context(digest::Context::new(self.0))) + } + + fn hash(&self, bytes: &[u8]) -> crypto::hash::Output { + let mut ctx = digest::Context::new(self.0); + ctx.update(bytes); + convert(ctx.finish()) + } + + fn output_len(&self) -> usize { + self.0.output_len() + } + + fn algorithm(&self) -> HashAlgorithm { + self.1 + } + + fn fips(&self) -> FipsStatus { + super::fips() + } +} + +struct Context(digest::Context); + +impl crypto::hash::Context for Context { + fn fork_finish(&self) -> crypto::hash::Output { + convert(self.0.clone().finish()) + } + + fn fork(&self) -> Box { + Box::new(Self(self.0.clone())) + } + + fn finish(self: Box) -> crypto::hash::Output { + convert(self.0.finish()) + } + + fn update(&mut self, data: &[u8]) { + self.0.update(data); + } +} + +fn convert(val: digest::Digest) -> crypto::hash::Output { + crypto::hash::Output::new(val.as_ref()) +} diff --git a/rustls-ring/src/hmac.rs b/rustls-ring/src/hmac.rs new file mode 100644 index 00000000000..54cc28fb525 --- /dev/null +++ b/rustls-ring/src/hmac.rs @@ -0,0 +1,47 @@ +use alloc::boxed::Box; + +use pki_types::FipsStatus; +use ring::hmac; +use rustls::crypto; + +pub(crate) static HMAC_SHA256: Hmac = Hmac(&hmac::HMAC_SHA256); +pub(crate) static HMAC_SHA384: Hmac = Hmac(&hmac::HMAC_SHA384); +#[allow(dead_code)] // Only used for TLS 1.2 prf test, and aws-lc-rs HPKE suites. +pub(crate) static HMAC_SHA512: Hmac = Hmac(&hmac::HMAC_SHA512); + +pub(crate) struct Hmac(&'static hmac::Algorithm); + +impl crypto::hmac::Hmac for Hmac { + fn with_key(&self, key: &[u8]) -> Box { + Box::new(Key(hmac::Key::new(*self.0, key))) + } + + fn hash_output_len(&self) -> usize { + self.0.digest_algorithm().output_len() + } + + fn fips(&self) -> FipsStatus { + super::fips() + } +} + +struct Key(hmac::Key); + +impl crypto::hmac::Key for Key { + fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> crypto::hmac::Tag { + let mut ctx = hmac::Context::with_key(&self.0); + ctx.update(first); + for d in middle { + ctx.update(d); + } + ctx.update(last); + crypto::hmac::Tag::new(ctx.sign().as_ref()) + } + + fn tag_len(&self) -> usize { + self.0 + .algorithm() + .digest_algorithm() + .output_len() + } +} diff --git a/rustls/src/crypto/ring/kx.rs b/rustls-ring/src/kx.rs similarity index 84% rename from rustls/src/crypto/ring/kx.rs rename to rustls-ring/src/kx.rs index 8c019d2ef25..107ac851a69 100644 --- a/rustls/src/crypto/ring/kx.rs +++ b/rustls-ring/src/kx.rs @@ -1,19 +1,16 @@ -#![allow(clippy::duplicate_mod)] - use alloc::boxed::Box; use core::fmt; -use super::ring_like::agreement; -use super::ring_like::rand::SystemRandom; -use crate::crypto::{ActiveKeyExchange, FfdheGroup, SharedSecret, SupportedKxGroup}; -use crate::error::{Error, PeerMisbehaved}; -use crate::msgs::enums::NamedGroup; -use crate::rand::GetRandomFailed; +use pki_types::FipsStatus; +use ring::agreement; +use ring::rand::SystemRandom; +use rustls::crypto::GetRandomFailed; +use rustls::crypto::kx::{ + ActiveKeyExchange, NamedGroup, SharedSecret, StartedKeyExchange, SupportedKxGroup, +}; +use rustls::error::{Error, PeerMisbehaved}; /// A key-exchange group supported by *ring*. -/// -/// All possible instances of this class are provided by the library in -/// the [`ALL_KX_GROUPS`] array. struct KxGroup { /// The IANA "TLS Supported Groups" name of the group name: NamedGroup, @@ -43,7 +40,7 @@ struct KxGroup { } impl SupportedKxGroup for KxGroup { - fn start(&self) -> Result, Error> { + fn start(&self) -> Result { let rng = SystemRandom::new(); let priv_key = agreement::EphemeralPrivateKey::generate(self.agreement_algorithm, &rng) .map_err(|_| GetRandomFailed)?; @@ -52,25 +49,24 @@ impl SupportedKxGroup for KxGroup { .compute_public_key() .map_err(|_| GetRandomFailed)?; - Ok(Box::new(KeyExchange { + Ok(StartedKeyExchange::Single(Box::new(KeyExchange { name: self.name, agreement_algorithm: self.agreement_algorithm, priv_key, pub_key, pub_key_validator: self.pub_key_validator, - })) - } - - fn ffdhe_group(&self) -> Option> { - None + }))) } fn name(&self) -> NamedGroup { self.name } - fn fips(&self) -> bool { - self.fips_allowed && super::fips() + fn fips(&self) -> FipsStatus { + match self.fips_allowed { + true => super::fips(), + false => FipsStatus::Unvalidated, + } } } @@ -118,9 +114,6 @@ fn uncompressed_point(point: &[u8]) -> bool { matches!(point.first(), Some(0x04)) } -/// A list of all the key exchange groups supported by rustls. -pub static ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[X25519, SECP256R1, SECP384R1]; - /// An in-progress key exchange. This has the algorithm, /// our private key, and our public key. struct KeyExchange { @@ -142,10 +135,6 @@ impl ActiveKeyExchange for KeyExchange { .map_err(|_| PeerMisbehaved::InvalidKeyShare.into()) } - fn ffdhe_group(&self) -> Option> { - None - } - /// Return the group being used. fn group(&self) -> NamedGroup { self.name @@ -159,7 +148,7 @@ impl ActiveKeyExchange for KeyExchange { #[cfg(test)] mod tests { - use std::format; + use alloc::format; #[test] fn kxgroup_fmt_yields_name() { @@ -167,7 +156,7 @@ mod tests { } } -#[cfg(bench)] +#[cfg(all(test, bench))] mod benchmarks { #[bench] fn bench_x25519(b: &mut test::Bencher) { @@ -186,7 +175,7 @@ mod benchmarks { fn bench_any(b: &mut test::Bencher, kxg: &dyn super::SupportedKxGroup) { b.iter(|| { - let akx = kxg.start().unwrap(); + let akx = kxg.start().unwrap().into_single(); let pub_key = akx.pub_key().to_vec(); test::black_box(akx.complete(&pub_key).unwrap()); }); diff --git a/rustls-ring/src/lib.rs b/rustls-ring/src/lib.rs new file mode 100644 index 00000000000..1c7eb7367d7 --- /dev/null +++ b/rustls-ring/src/lib.rs @@ -0,0 +1,287 @@ +//! A `CryptoProvider` implementation backed by *ring*. + +#![no_std] +#![warn(clippy::exhaustive_enums, clippy::exhaustive_structs, missing_docs)] +#![cfg_attr(bench, feature(test))] + +extern crate alloc; +#[cfg(any(feature = "std", test))] +extern crate std; + +// Import `test` sysroot crate for `Bencher` definitions. +#[cfg(all(test, bench))] +#[allow(unused_extern_crates)] +extern crate test; + +use alloc::borrow::Cow; +use alloc::boxed::Box; +use alloc::sync::Arc; +#[cfg(feature = "std")] +use core::time::Duration; + +use pki_types::{FipsStatus, PrivateKeyDer}; +use rustls::crypto::kx::SupportedKxGroup; +use rustls::crypto::{ + CryptoProvider, GetRandomFailed, KeyProvider, SecureRandom, SignatureScheme, SigningKey, + TicketProducer, TicketerFactory, WebPkiSupportedAlgorithms, +}; +use rustls::error::Error; +#[cfg(feature = "std")] +use rustls::ticketer::TicketRotator; +use rustls::{Tls12CipherSuite, Tls13CipherSuite}; + +/// Using software keys for authentication. +pub mod sign; +use sign::{EcdsaSigner, Ed25519Signer, RsaSigningKey}; + +pub(crate) mod hash; +pub(crate) mod hmac; +pub(crate) mod kx; +pub(crate) mod quic; +#[cfg(feature = "std")] +pub(crate) mod ticketer; +#[cfg(feature = "std")] +use ticketer::AeadTicketer; +pub(crate) mod tls12; +pub(crate) mod tls13; +mod verify; +pub use verify::{ + ALL_VERIFICATION_ALGS, ECDSA_P256_SHA256, ECDSA_P256_SHA384, ECDSA_P384_SHA256, + ECDSA_P384_SHA384, ED25519, RSA_PKCS1_2048_8192_SHA256, + RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS, RSA_PKCS1_2048_8192_SHA384, + RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS, RSA_PKCS1_2048_8192_SHA512, + RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS, RSA_PKCS1_3072_8192_SHA384, + RSA_PSS_2048_8192_SHA256_LEGACY_KEY, RSA_PSS_2048_8192_SHA384_LEGACY_KEY, + RSA_PSS_2048_8192_SHA512_LEGACY_KEY, +}; + +/// The default `CryptoProvider` backed by [*ring*]. +/// +/// [*ring*]: https://github.com/briansmith/ring +pub const DEFAULT_PROVIDER: CryptoProvider = CryptoProvider { + tls12_cipher_suites: Cow::Borrowed(DEFAULT_TLS12_CIPHER_SUITES), + tls13_cipher_suites: Cow::Borrowed(DEFAULT_TLS13_CIPHER_SUITES), + kx_groups: Cow::Borrowed(DEFAULT_KX_GROUPS), + signature_verification_algorithms: SUPPORTED_SIG_ALGS, + secure_random: &Ring, + key_provider: &Ring, + ticketer_factory: &Ring, +}; + +/// The default `CryptoProvider` backed by *ring* that only supports TLS1.3. +pub const DEFAULT_TLS13_PROVIDER: CryptoProvider = CryptoProvider { + tls12_cipher_suites: Cow::Borrowed(&[]), + ..DEFAULT_PROVIDER +}; + +/// The default `CryptoProvider` backed by *ring* that only supports TLS1.2. +/// +/// Use of TLS1.3 is **strongly** recommended. +pub const DEFAULT_TLS12_PROVIDER: CryptoProvider = CryptoProvider { + tls13_cipher_suites: Cow::Borrowed(&[]), + ..DEFAULT_PROVIDER +}; + +/// Default crypto provider. +#[derive(Debug)] +struct Ring; + +impl SecureRandom for Ring { + fn fill(&self, buf: &mut [u8]) -> Result<(), GetRandomFailed> { + use ring::rand::SecureRandom; + ring::rand::SystemRandom::new() + .fill(buf) + .map_err(|_| GetRandomFailed) + } +} + +impl KeyProvider for Ring { + fn load_private_key( + &self, + key_der: PrivateKeyDer<'static>, + ) -> Result, Error> { + if let Ok(rsa) = RsaSigningKey::try_from(&key_der) { + return Ok(Box::new(rsa)); + } + + if let Ok(ecdsa) = EcdsaSigner::try_from(&key_der) { + return Ok(Box::new(ecdsa)); + } + + if let PrivateKeyDer::Pkcs8(pkcs8) = key_der { + if let Ok(eddsa) = Ed25519Signer::try_from(&pkcs8) { + return Ok(Box::new(eddsa)); + } + } + + Err(Error::General( + "failed to parse private key as RSA, ECDSA, or EdDSA".into(), + )) + } +} + +impl TicketerFactory for Ring { + /// Make the recommended `Ticketer`. + /// + /// This produces tickets: + /// + /// - where each lasts for at least 6 hours, + /// - with randomly generated keys, and + /// - where keys are rotated every 6 hours. + /// + /// The encryption mechanism used is Chacha20Poly1305. + fn ticketer(&self) -> Result, Error> { + #[cfg(feature = "std")] + { + Ok(Arc::new(TicketRotator::new(SIX_HOURS, AeadTicketer::new)?)) + } + #[cfg(not(feature = "std"))] + { + Err(Error::General( + "Ring::ticketer() relies on std-only RwLock via TicketRotator".into(), + )) + } + } + + fn fips(&self) -> FipsStatus { + fips() + } +} + +/// The TLS1.2 cipher suite configuration that an application should use by default. +/// +/// This will be [`ALL_TLS12_CIPHER_SUITES`] sans any supported cipher suites that +/// shouldn't be enabled by most applications. +pub static DEFAULT_TLS12_CIPHER_SUITES: &[&Tls12CipherSuite] = ALL_TLS12_CIPHER_SUITES; + +/// A list of all the TLS1.2 cipher suites supported by the rustls *ring* provider. +pub static ALL_TLS12_CIPHER_SUITES: &[&Tls12CipherSuite] = &[ + tls12::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls12::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls12::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + tls12::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls12::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls12::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, +]; + +/// The TLS1.3 cipher suite configuration that an application should use by default. +/// +/// This will be [`ALL_TLS13_CIPHER_SUITES`] sans any supported cipher suites that +/// shouldn't be enabled by most applications. +pub static DEFAULT_TLS13_CIPHER_SUITES: &[&Tls13CipherSuite] = ALL_TLS13_CIPHER_SUITES; + +/// A list of all the TLS1.3 cipher suites supported by the rustls *ring* provider. +pub static ALL_TLS13_CIPHER_SUITES: &[&Tls13CipherSuite] = &[ + tls13::TLS13_AES_128_GCM_SHA256, + tls13::TLS13_AES_256_GCM_SHA384, + tls13::TLS13_CHACHA20_POLY1305_SHA256, +]; + +/// All defined cipher suites supported by *ring* appear in this module. +pub mod cipher_suite { + pub use super::tls12::{ + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + }; + pub use super::tls13::{ + TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256, + }; +} + +/// A `WebPkiSupportedAlgorithms` value that reflects webpki's capabilities when +/// compiled against *ring*. +static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = match WebPkiSupportedAlgorithms::new( + &[ + ECDSA_P256_SHA256, + ECDSA_P256_SHA384, + ECDSA_P384_SHA256, + ECDSA_P384_SHA384, + ED25519, + RSA_PSS_2048_8192_SHA256_LEGACY_KEY, + RSA_PSS_2048_8192_SHA384_LEGACY_KEY, + RSA_PSS_2048_8192_SHA512_LEGACY_KEY, + RSA_PKCS1_2048_8192_SHA256, + RSA_PKCS1_2048_8192_SHA384, + RSA_PKCS1_2048_8192_SHA512, + RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS, + RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS, + RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS, + ], + &[ + // Note: for TLS1.2 the curve is not fixed by SignatureScheme. For TLS1.3 it is. + ( + SignatureScheme::ECDSA_NISTP384_SHA384, + &[ECDSA_P384_SHA384, ECDSA_P256_SHA384], + ), + ( + SignatureScheme::ECDSA_NISTP256_SHA256, + &[ECDSA_P256_SHA256, ECDSA_P384_SHA256], + ), + (SignatureScheme::ED25519, &[ED25519]), + ( + SignatureScheme::RSA_PSS_SHA512, + &[RSA_PSS_2048_8192_SHA512_LEGACY_KEY], + ), + ( + SignatureScheme::RSA_PSS_SHA384, + &[RSA_PSS_2048_8192_SHA384_LEGACY_KEY], + ), + ( + SignatureScheme::RSA_PSS_SHA256, + &[RSA_PSS_2048_8192_SHA256_LEGACY_KEY], + ), + ( + SignatureScheme::RSA_PKCS1_SHA512, + &[RSA_PKCS1_2048_8192_SHA512], + ), + ( + SignatureScheme::RSA_PKCS1_SHA384, + &[RSA_PKCS1_2048_8192_SHA384], + ), + ( + SignatureScheme::RSA_PKCS1_SHA256, + &[RSA_PKCS1_2048_8192_SHA256], + ), + ], +) { + Ok(algs) => algs, + Err(_) => panic!("bad WebPkiSupportedAlgorithms"), +}; + +/// All defined key exchange groups supported by *ring* appear in this module. +/// +/// [`ALL_KX_GROUPS`] is provided as an array of all of these values. +/// [`DEFAULT_KX_GROUPS`] is provided as an array of this provider's defaults. +pub mod kx_group { + pub use super::kx::{SECP256R1, SECP384R1, X25519}; +} + +/// A list of the default key exchange groups supported by this provider. +pub static DEFAULT_KX_GROUPS: &[&dyn SupportedKxGroup] = ALL_KX_GROUPS; + +/// A list of all the key exchange groups supported by this provider. +pub static ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = + &[kx_group::X25519, kx_group::SECP256R1, kx_group::SECP384R1]; + +/// Compatibility shims between ring 0.16.x and 0.17.x API +mod ring_shim { + use ring::agreement::{self, EphemeralPrivateKey, UnparsedPublicKey}; + use rustls::crypto::kx::SharedSecret; + + pub(super) fn agree_ephemeral( + priv_key: EphemeralPrivateKey, + peer_key: &UnparsedPublicKey<&[u8]>, + ) -> Result { + agreement::agree_ephemeral(priv_key, peer_key, |secret| SharedSecret::from(secret)) + .map_err(|_| ()) + } +} + +/// Return the FIPS validation status of this implementation. +pub fn fips() -> FipsStatus { + FipsStatus::Unvalidated +} + +#[cfg(feature = "std")] +const SIX_HOURS: Duration = Duration::from_secs(6 * 60 * 60); diff --git a/rustls-ring/src/quic.rs b/rustls-ring/src/quic.rs new file mode 100644 index 00000000000..fa7c1fe153c --- /dev/null +++ b/rustls-ring/src/quic.rs @@ -0,0 +1,441 @@ +use alloc::boxed::Box; + +use pki_types::FipsStatus; +use ring::aead; +use rustls::crypto::cipher::{AeadKey, Iv, Nonce}; +use rustls::error::{ApiMisuse, Error}; +use rustls::quic; + +pub(crate) struct HeaderProtectionKey(aead::quic::HeaderProtectionKey); + +impl HeaderProtectionKey { + pub(crate) fn new(key: AeadKey, alg: &'static aead::quic::Algorithm) -> Self { + Self(aead::quic::HeaderProtectionKey::new(alg, key.as_ref()).unwrap()) + } + + fn xor_in_place( + &self, + sample: &[u8], + first: &mut u8, + packet_number: &mut [u8], + masked: bool, + ) -> Result<(), Error> { + // This implements "Header Protection Application" almost verbatim. + // + + let mask = self + .0 + .new_mask(sample) + .map_err(|_| Error::ApiMisuse(ApiMisuse::InvalidQuicHeaderProtectionSampleLength))?; + + // The `unwrap()` will not panic because `new_mask` returns a + // non-empty result. + let (first_mask, pn_mask) = mask.split_first().unwrap(); + + // It is OK for the `mask` to be longer than `packet_number`, + // but a valid `packet_number` will never be longer than `mask`. + if packet_number.len() > pn_mask.len() { + return Err(ApiMisuse::InvalidQuicHeaderProtectionPacketNumberLength.into()); + } + + // Infallible from this point on. Before this point, `first` and + // `packet_number` are unchanged. + + const LONG_HEADER_FORM: u8 = 0x80; + let bits = match *first & LONG_HEADER_FORM == LONG_HEADER_FORM { + true => 0x0f, // Long header: 4 bits masked + false => 0x1f, // Short header: 5 bits masked + }; + + let first_plain = match masked { + // When unmasking, use the packet length bits after unmasking + true => *first ^ (first_mask & bits), + // When masking, use the packet length bits before masking + false => *first, + }; + let pn_len = (first_plain & 0x03) as usize + 1; + + *first ^= first_mask & bits; + for (dst, m) in packet_number + .iter_mut() + .zip(pn_mask) + .take(pn_len) + { + *dst ^= m; + } + + Ok(()) + } +} + +impl quic::HeaderProtectionKey for HeaderProtectionKey { + fn encrypt_in_place( + &self, + sample: &[u8], + first: &mut u8, + packet_number: &mut [u8], + ) -> Result<(), Error> { + self.xor_in_place(sample, first, packet_number, false) + } + + fn decrypt_in_place( + &self, + sample: &[u8], + first: &mut u8, + packet_number: &mut [u8], + ) -> Result<(), Error> { + self.xor_in_place(sample, first, packet_number, true) + } + + #[inline] + fn sample_len(&self) -> usize { + self.0.algorithm().sample_len() + } +} + +pub(crate) struct PacketKey { + /// Encrypts or decrypts a packet's payload + key: aead::LessSafeKey, + /// Computes unique nonces for each packet + iv: Iv, + /// Confidentiality limit (see [`quic::PacketKey::confidentiality_limit`]) + confidentiality_limit: u64, + /// Integrity limit (see [`quic::PacketKey::integrity_limit`]) + integrity_limit: u64, +} + +impl PacketKey { + pub(crate) fn new( + key: AeadKey, + iv: Iv, + confidentiality_limit: u64, + integrity_limit: u64, + aead_algorithm: &'static aead::Algorithm, + ) -> Self { + Self { + key: aead::LessSafeKey::new( + aead::UnboundKey::new(aead_algorithm, key.as_ref()).unwrap(), + ), + iv, + confidentiality_limit, + integrity_limit, + } + } +} + +impl quic::PacketKey for PacketKey { + fn encrypt_in_place( + &self, + packet_number: u64, + header: &[u8], + payload: &mut [u8], + path_id: Option, + ) -> Result { + let aad = aead::Aad::from(header); + let nonce_value = Nonce::quic(path_id, &self.iv, packet_number); + let nonce = aead::Nonce::assume_unique_for_key(nonce_value.to_array()?); + let tag = self + .key + .seal_in_place_separate_tag(nonce, aad, payload) + .map_err(|_| Error::EncryptError)?; + Ok(quic::Tag::from(tag.as_ref())) + } + + /// Decrypt a QUIC packet + /// + /// Takes a `packet_number` and optional `path_id`, used to derive the nonce; the packet + /// `header`, which is used as the additional authenticated data, and the `payload`, which + /// includes the authentication tag. + /// + /// On success, returns the slice of `payload` containing the decrypted data. + /// + /// When provided, the `path_id` is used for multipath encryption as described in + /// . + fn decrypt_in_place<'a>( + &self, + packet_number: u64, + header: &[u8], + payload: &'a mut [u8], + path_id: Option, + ) -> Result<&'a [u8], Error> { + let payload_len = payload.len(); + let aad = aead::Aad::from(header); + let nonce_value = Nonce::quic(path_id, &self.iv, packet_number); + let nonce = aead::Nonce::assume_unique_for_key(nonce_value.to_array()?); + self.key + .open_in_place(nonce, aad, payload) + .map_err(|_| Error::DecryptError)?; + + let plain_len = payload_len - self.key.algorithm().tag_len(); + Ok(&payload[..plain_len]) + } + + /// Tag length for the underlying AEAD algorithm + #[inline] + fn tag_len(&self) -> usize { + self.key.algorithm().tag_len() + } + + /// Confidentiality limit (see [`quic::PacketKey::confidentiality_limit`]) + fn confidentiality_limit(&self) -> u64 { + self.confidentiality_limit + } + + /// Integrity limit (see [`quic::PacketKey::integrity_limit`]) + fn integrity_limit(&self) -> u64 { + self.integrity_limit + } +} + +pub(crate) struct KeyBuilder { + pub(crate) packet_alg: &'static aead::Algorithm, + pub(crate) header_alg: &'static aead::quic::Algorithm, + pub(crate) confidentiality_limit: u64, + pub(crate) integrity_limit: u64, +} + +impl quic::Algorithm for KeyBuilder { + fn packet_key(&self, key: AeadKey, iv: Iv) -> Box { + Box::new(PacketKey::new( + key, + iv, + self.confidentiality_limit, + self.integrity_limit, + self.packet_alg, + )) + } + + fn header_protection_key(&self, key: AeadKey) -> Box { + Box::new(HeaderProtectionKey::new(key, self.header_alg)) + } + + fn aead_key_len(&self) -> usize { + self.packet_alg.key_len() + } + + fn fips(&self) -> FipsStatus { + super::fips() + } +} + +#[cfg(test)] +mod tests { + use std::dbg; + + use rustls::crypto::tls13::OkmBlock; + use rustls::quic::{KeyBuilder, Keys, Side, Version}; + + use crate::tls13::{TLS13_AES_128_GCM_SHA256, TLS13_CHACHA20_POLY1305_SHA256}; + + fn test_short_packet(version: Version, expected: &[u8]) { + const PN: u64 = 654360564; + const SECRET: &[u8] = &[ + 0x9a, 0xc3, 0x12, 0xa7, 0xf8, 0x77, 0x46, 0x8e, 0xbe, 0x69, 0x42, 0x27, 0x48, 0xad, + 0x00, 0xa1, 0x54, 0x43, 0xf1, 0x82, 0x03, 0xa0, 0x7d, 0x60, 0x60, 0xf6, 0x88, 0xf3, + 0x0f, 0x21, 0x63, 0x2b, + ]; + + let secret = OkmBlock::new(SECRET); + let builder = KeyBuilder::new( + &secret, + version, + TLS13_CHACHA20_POLY1305_SHA256 + .quic + .unwrap(), + TLS13_CHACHA20_POLY1305_SHA256.hkdf_provider, + ); + let packet = builder.packet_key(); + let hpk = builder.header_protection_key(); + + const PLAIN: &[u8] = &[0x42, 0x00, 0xbf, 0xf4, 0x01]; + + let mut buf = PLAIN.to_vec(); + let (header, payload) = buf.split_at_mut(4); + let tag = packet + .encrypt_in_place(PN, header, payload, None) + .unwrap(); + buf.extend(tag.as_ref()); + + let pn_offset = 1; + let (header, sample) = buf.split_at_mut(pn_offset + 4); + let (first, rest) = header.split_at_mut(1); + let sample = &sample[..hpk.sample_len()]; + hpk.encrypt_in_place(sample, &mut first[0], dbg!(rest)) + .unwrap(); + + assert_eq!(&buf, expected); + + let (header, sample) = buf.split_at_mut(pn_offset + 4); + let (first, rest) = header.split_at_mut(1); + let sample = &sample[..hpk.sample_len()]; + hpk.decrypt_in_place(sample, &mut first[0], rest) + .unwrap(); + + let (header, payload_tag) = buf.split_at_mut(4); + let plain = packet + .decrypt_in_place(PN, header, payload_tag, None) + .unwrap(); + + assert_eq!(plain, &PLAIN[4..]); + } + + #[test] + fn short_packet_header_protection() { + // https://www.rfc-editor.org/rfc/rfc9001.html#name-chacha20-poly1305-short-hea + test_short_packet( + Version::V1, + &[ + 0x4c, 0xfe, 0x41, 0x89, 0x65, 0x5e, 0x5c, 0xd5, 0x5c, 0x41, 0xf6, 0x90, 0x80, 0x57, + 0x5d, 0x79, 0x99, 0xc2, 0x5a, 0x5b, 0xfb, + ], + ); + } + + #[test] + fn short_packet_header_protection_v2() { + // https://tools.ietf.org/html/rfc9369.html#name-chacha20-poly1305-short-hea + test_short_packet( + Version::V2, + &[ + 0x55, 0x58, 0xb1, 0xc6, 0x0a, 0xe7, 0xb6, 0xb9, 0x32, 0xbc, 0x27, 0xd7, 0x86, 0xf4, + 0xbc, 0x2b, 0xb2, 0x0f, 0x21, 0x62, 0xba, + ], + ); + } + + #[test] + fn initial_test_vector_v2() { + // https://tools.ietf.org/html/rfc9369.html#name-sample-packet-protection + let icid = [0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08]; + let server = Keys::initial( + Version::V2, + TLS13_AES_128_GCM_SHA256, + TLS13_AES_128_GCM_SHA256.quic.unwrap(), + &icid, + Side::Server, + ); + let mut server_payload = [ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x40, 0x5a, 0x02, 0x00, 0x00, 0x56, 0x03, + 0x03, 0xee, 0xfc, 0xe7, 0xf7, 0xb3, 0x7b, 0xa1, 0xd1, 0x63, 0x2e, 0x96, 0x67, 0x78, + 0x25, 0xdd, 0xf7, 0x39, 0x88, 0xcf, 0xc7, 0x98, 0x25, 0xdf, 0x56, 0x6d, 0xc5, 0x43, + 0x0b, 0x9a, 0x04, 0x5a, 0x12, 0x00, 0x13, 0x01, 0x00, 0x00, 0x2e, 0x00, 0x33, 0x00, + 0x24, 0x00, 0x1d, 0x00, 0x20, 0x9d, 0x3c, 0x94, 0x0d, 0x89, 0x69, 0x0b, 0x84, 0xd0, + 0x8a, 0x60, 0x99, 0x3c, 0x14, 0x4e, 0xca, 0x68, 0x4d, 0x10, 0x81, 0x28, 0x7c, 0x83, + 0x4d, 0x53, 0x11, 0xbc, 0xf3, 0x2b, 0xb9, 0xda, 0x1a, 0x00, 0x2b, 0x00, 0x02, 0x03, + 0x04, + ]; + let mut server_header = [ + 0xd1, 0x6b, 0x33, 0x43, 0xcf, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, + 0xb5, 0x00, 0x40, 0x75, 0x00, 0x01, + ]; + let tag = server + .local + .packet + .encrypt_in_place(1, &server_header, &mut server_payload, None) + .unwrap(); + let (first, rest) = server_header.split_at_mut(1); + let rest_len = rest.len(); + server + .local + .header + .encrypt_in_place( + &server_payload[2..18], + &mut first[0], + &mut rest[rest_len - 2..], + ) + .unwrap(); + let mut server_packet = server_header.to_vec(); + server_packet.extend(server_payload); + server_packet.extend(tag.as_ref()); + let expected_server_packet = [ + 0xdc, 0x6b, 0x33, 0x43, 0xcf, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, + 0xb5, 0x00, 0x40, 0x75, 0xd9, 0x2f, 0xaa, 0xf1, 0x6f, 0x05, 0xd8, 0xa4, 0x39, 0x8c, + 0x47, 0x08, 0x96, 0x98, 0xba, 0xee, 0xa2, 0x6b, 0x91, 0xeb, 0x76, 0x1d, 0x9b, 0x89, + 0x23, 0x7b, 0xbf, 0x87, 0x26, 0x30, 0x17, 0x91, 0x53, 0x58, 0x23, 0x00, 0x35, 0xf7, + 0xfd, 0x39, 0x45, 0xd8, 0x89, 0x65, 0xcf, 0x17, 0xf9, 0xaf, 0x6e, 0x16, 0x88, 0x6c, + 0x61, 0xbf, 0xc7, 0x03, 0x10, 0x6f, 0xba, 0xf3, 0xcb, 0x4c, 0xfa, 0x52, 0x38, 0x2d, + 0xd1, 0x6a, 0x39, 0x3e, 0x42, 0x75, 0x75, 0x07, 0x69, 0x80, 0x75, 0xb2, 0xc9, 0x84, + 0xc7, 0x07, 0xf0, 0xa0, 0x81, 0x2d, 0x8c, 0xd5, 0xa6, 0x88, 0x1e, 0xaf, 0x21, 0xce, + 0xda, 0x98, 0xf4, 0xbd, 0x23, 0xf6, 0xfe, 0x1a, 0x3e, 0x2c, 0x43, 0xed, 0xd9, 0xce, + 0x7c, 0xa8, 0x4b, 0xed, 0x85, 0x21, 0xe2, 0xe1, 0x40, + ]; + assert_eq!(server_packet[..], expected_server_packet[..]); + } + + // This test is based on picoquic's output for `multipath_aead_test` in + // `picoquictest/multipath_test.c`. + // + // See + #[test] + fn test_multipath_aead_basic() { + const SECRET: &[u8; 32] = &[ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 35, 26, 27, 28, 29, 30, 31, + ]; + const PN: u64 = 12345; + const PATH_ID: u32 = 2; + const PAYLOAD: &[u8] = b"The quick brown fox jumps over the lazy dog"; + const HEADER: &[u8] = b"This is a test"; + + const EXPECTED: &[u8] = &[ + 123, 139, 232, 52, 136, 25, 201, 143, 250, 89, 87, 39, 37, 63, 0, 210, 220, 227, 186, + 140, 183, 251, 13, 203, 6, 116, 204, 100, 166, 64, 43, 185, 174, 85, 212, 163, 242, + 141, 24, 166, 62, 228, 187, 137, 248, 31, 152, 126, 240, 151, 79, 51, 253, 130, 43, + 114, 173, 234, 254, + ]; + + let secret = OkmBlock::new(SECRET); + let builder = KeyBuilder::new( + &secret, + Version::V1, + TLS13_AES_128_GCM_SHA256.quic.unwrap(), + TLS13_AES_128_GCM_SHA256.hkdf_provider, + ); + + let packet = builder.packet_key(); + let mut buf = PAYLOAD.to_vec(); + let tag = packet + .encrypt_in_place(PN, HEADER, &mut buf, Some(PATH_ID)) + .unwrap(); + buf.extend_from_slice(tag.as_ref()); + + assert_eq!(buf.as_slice(), EXPECTED); + } + + // This test is based on `multipath_aead_test` in `picoquictest/multipath_test.c` + // + // See + #[test] + fn test_multipath_aead_roundtrip() { + const SECRET: &[u8; 32] = &[ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 35, 26, 27, 28, 29, 30, 31, + ]; + const PAYLOAD: &[u8] = b"The quick brown fox jumps over the lazy dog"; + const HEADER: &[u8] = b"This is a test"; + const PN: u64 = 12345; + + const TEST_PATH_IDS: &[u32] = &[0, 1, 2, 0xaead]; + + let secret = OkmBlock::new(SECRET); + let builder = KeyBuilder::new( + &secret, + Version::V1, + TLS13_AES_128_GCM_SHA256.quic.unwrap(), + TLS13_AES_128_GCM_SHA256.hkdf_provider, + ); + let packet = builder.packet_key(); + + for &path_id in TEST_PATH_IDS { + let mut buf = PAYLOAD.to_vec(); + let tag = packet + .encrypt_in_place(PN, HEADER, &mut buf, Some(path_id)) + .unwrap(); + buf.extend_from_slice(tag.as_ref()); + let decrypted = packet + .decrypt_in_place(PN, HEADER, &mut buf, Some(path_id)) + .unwrap(); + assert_eq!(decrypted, PAYLOAD); + } + } +} diff --git a/rustls/src/crypto/ring/sign.rs b/rustls-ring/src/sign.rs similarity index 52% rename from rustls/src/crypto/ring/sign.rs rename to rustls-ring/src/sign.rs index 0ddfe2d41c2..6e8403e59b1 100644 --- a/rustls/src/crypto/ring/sign.rs +++ b/rustls-ring/src/sign.rs @@ -1,5 +1,3 @@ -#![allow(clippy::duplicate_mod)] - use alloc::boxed::Box; use alloc::string::ToString; use alloc::sync::Arc; @@ -7,100 +5,70 @@ use alloc::vec::Vec; use alloc::{format, vec}; use core::fmt::{self, Debug, Formatter}; -use pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer, SubjectPublicKeyInfoDer}; -use webpki::alg_id; - -use super::ring_like::rand::{SecureRandom, SystemRandom}; -use super::ring_like::signature::{self, EcdsaKeyPair, Ed25519KeyPair, KeyPair, RsaKeyPair}; -use crate::crypto::signer::{public_key_to_spki, Signer, SigningKey}; -use crate::enums::{SignatureAlgorithm, SignatureScheme}; -use crate::error::Error; -use crate::x509::{wrap_concat_in_sequence, wrap_in_octet_string}; +use pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer, SubjectPublicKeyInfoDer, alg_id}; +use ring::rand::{SecureRandom, SystemRandom}; +use ring::signature::{self, EcdsaKeyPair, Ed25519KeyPair, KeyPair, RsaKeyPair}; +#[cfg(test)] +use rustls::crypto::CryptoProvider; +use rustls::crypto::{SignatureScheme, Signer, SigningKey, public_key_to_spki}; +use rustls::error::Error; -/// Parse `der` as any supported key encoding/type, returning -/// the first which works. -pub fn any_supported_type(der: &PrivateKeyDer<'_>) -> Result, Error> { - if let Ok(rsa) = RsaSigningKey::new(der) { - return Ok(Arc::new(rsa)); - } +/// A `SigningKey` for RSA-PKCS1 or RSA-PSS. +pub(super) struct RsaSigningKey { + key: Arc, +} - if let Ok(ecdsa) = any_ecdsa_type(der) { - return Ok(ecdsa); - } +impl RsaSigningKey { + fn to_signer(&self, scheme: SignatureScheme) -> RsaSigner { + let encoding: &dyn signature::RsaEncoding = match scheme { + SignatureScheme::RSA_PKCS1_SHA256 => &signature::RSA_PKCS1_SHA256, + SignatureScheme::RSA_PKCS1_SHA384 => &signature::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA512 => &signature::RSA_PKCS1_SHA512, + SignatureScheme::RSA_PSS_SHA256 => &signature::RSA_PSS_SHA256, + SignatureScheme::RSA_PSS_SHA384 => &signature::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA512 => &signature::RSA_PSS_SHA512, + _ => unreachable!(), + }; - if let PrivateKeyDer::Pkcs8(pkcs8) = der { - if let Ok(eddsa) = any_eddsa_type(pkcs8) { - return Ok(eddsa); + RsaSigner { + key: self.key.clone(), + scheme, + encoding, } } - Err(Error::General( - "failed to parse private key as RSA, ECDSA, or EdDSA".into(), - )) -} - -/// Parse `der` as any ECDSA key type, returning the first which works. -/// -/// Both SEC1 (PEM section starting with 'BEGIN EC PRIVATE KEY') and PKCS8 -/// (PEM section starting with 'BEGIN PRIVATE KEY') encodings are supported. -pub fn any_ecdsa_type(der: &PrivateKeyDer<'_>) -> Result, Error> { - if let Ok(ecdsa_p256) = EcdsaSigningKey::new( - der, - SignatureScheme::ECDSA_NISTP256_SHA256, - &signature::ECDSA_P256_SHA256_ASN1_SIGNING, - ) { - return Ok(Arc::new(ecdsa_p256)); - } - - if let Ok(ecdsa_p384) = EcdsaSigningKey::new( - der, - SignatureScheme::ECDSA_NISTP384_SHA384, - &signature::ECDSA_P384_SHA384_ASN1_SIGNING, - ) { - return Ok(Arc::new(ecdsa_p384)); - } - - Err(Error::General( - "failed to parse ECDSA private key as PKCS#8 or SEC1".into(), - )) + const SCHEMES: &[SignatureScheme] = &[ + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA256, + ]; } -/// Parse `der` as any EdDSA key type, returning the first which works. -/// -/// Note that, at the time of writing, Ed25519 does not have wide support -/// in browsers. It is also not supported by the WebPKI, because the -/// CA/Browser Forum Baseline Requirements do not support it for publicly -/// trusted certificates. -pub fn any_eddsa_type(der: &PrivatePkcs8KeyDer<'_>) -> Result, Error> { - // TODO: Add support for Ed448 - Ok(Arc::new(Ed25519SigningKey::new( - der, - SignatureScheme::ED25519, - )?)) -} +impl SigningKey for RsaSigningKey { + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { + Self::SCHEMES + .iter() + .find(|scheme| offered.contains(scheme)) + .map(|&scheme| Box::new(self.to_signer(scheme)) as Box) + } -/// A `SigningKey` for RSA-PKCS1 or RSA-PSS. -/// -/// This is used by the test suite, so it must be `pub`, but it isn't part of -/// the public, stable, API. -#[doc(hidden)] -pub struct RsaSigningKey { - key: Arc, + fn public_key(&self) -> Option> { + Some(public_key_to_spki( + &alg_id::RSA_ENCRYPTION, + self.key.public_key(), + )) + } } -static ALL_RSA_SCHEMES: &[SignatureScheme] = &[ - SignatureScheme::RSA_PSS_SHA512, - SignatureScheme::RSA_PSS_SHA384, - SignatureScheme::RSA_PSS_SHA256, - SignatureScheme::RSA_PKCS1_SHA512, - SignatureScheme::RSA_PKCS1_SHA384, - SignatureScheme::RSA_PKCS1_SHA256, -]; +impl TryFrom<&PrivateKeyDer<'_>> for RsaSigningKey { + type Error = Error; -impl RsaSigningKey { /// Make a new `RsaSigningKey` from a DER encoding, in either /// PKCS#1 or PKCS#8 format. - pub fn new(der: &PrivateKeyDer<'_>) -> Result { + fn try_from(der: &PrivateKeyDer<'_>) -> Result { let key_pair = match der { PrivateKeyDer::Pkcs1(pkcs1) => RsaKeyPair::from_der(pkcs1.secret_pkcs1_der()), PrivateKeyDer::Pkcs8(pkcs8) => RsaKeyPair::from_pkcs8(pkcs8.secret_pkcs8_der()), @@ -111,7 +79,7 @@ impl RsaSigningKey { } } .map_err(|key_rejected| { - Error::General(format!("failed to parse RSA private key: {}", key_rejected)) + Error::General(format!("failed to parse RSA private key: {key_rejected}")) })?; Ok(Self { @@ -120,31 +88,10 @@ impl RsaSigningKey { } } -impl SigningKey for RsaSigningKey { - fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { - ALL_RSA_SCHEMES - .iter() - .find(|scheme| offered.contains(scheme)) - .map(|scheme| RsaSigner::new(Arc::clone(&self.key), *scheme)) - } - - fn public_key(&self) -> Option> { - Some(public_key_to_spki( - &alg_id::RSA_ENCRYPTION, - self.key.public_key(), - )) - } - - fn algorithm(&self) -> SignatureAlgorithm { - SignatureAlgorithm::RSA - } -} - impl Debug for RsaSigningKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("RsaSigningKey") - .field("algorithm", &self.algorithm()) - .finish() + .finish_non_exhaustive() } } @@ -155,26 +102,6 @@ struct RsaSigner { } impl RsaSigner { - fn new(key: Arc, scheme: SignatureScheme) -> Box { - let encoding: &dyn signature::RsaEncoding = match scheme { - SignatureScheme::RSA_PKCS1_SHA256 => &signature::RSA_PKCS1_SHA256, - SignatureScheme::RSA_PKCS1_SHA384 => &signature::RSA_PKCS1_SHA384, - SignatureScheme::RSA_PKCS1_SHA512 => &signature::RSA_PKCS1_SHA512, - SignatureScheme::RSA_PSS_SHA256 => &signature::RSA_PSS_SHA256, - SignatureScheme::RSA_PSS_SHA384 => &signature::RSA_PSS_SHA384, - SignatureScheme::RSA_PSS_SHA512 => &signature::RSA_PSS_SHA512, - _ => unreachable!(), - }; - - Box::new(Self { - key, - scheme, - encoding, - }) - } -} - -impl Signer for RsaSigner { fn sign(&self, message: &[u8]) -> Result, Error> { let mut sig = vec![0; self.key.public().modulus_len()]; @@ -184,6 +111,12 @@ impl Signer for RsaSigner { .map(|_| sig) .map_err(|_| Error::General("signing failed".to_string())) } +} + +impl Signer for RsaSigner { + fn sign(self: Box, message: &[u8]) -> Result, Error> { + (*self).sign(message) + } fn scheme(&self) -> SignatureScheme { self.scheme @@ -194,28 +127,22 @@ impl Debug for RsaSigner { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("RsaSigner") .field("scheme", &self.scheme) - .finish() + .finish_non_exhaustive() } } -/// A SigningKey that uses exactly one TLS-level SignatureScheme -/// and one ring-level signature::SigningAlgorithm. -/// -/// Compare this to RsaSigningKey, which for a particular key is -/// willing to sign with several algorithms. This is quite poor -/// cryptography practice, but is necessary because a given RSA key -/// is expected to work in TLS1.2 (PKCS#1 signatures) and TLS1.3 -/// (PSS signatures) -- nobody is willing to obtain certificates for -/// different protocol versions. +/// A [`SigningKey`] and [`Signer`] implementation for ECDSA. /// -/// Currently this is only implemented for ECDSA keys. -struct EcdsaSigningKey { +/// Unlike [`RsaSigningKey`]/[`RsaSigner`], where we have one key that supports +/// multiple signature schemes, we can use the same type for both traits here. +#[derive(Clone)] +pub(super) struct EcdsaSigner { key: Arc, scheme: SignatureScheme, } -impl EcdsaSigningKey { - /// Make a new `ECDSASigningKey` from a DER encoding in PKCS#8 or SEC1 +impl EcdsaSigner { + /// Make a new [`EcdsaSigner`] from a DER encoding in PKCS#8 or SEC1 /// format, expecting a key usable with precisely the given signature /// scheme. fn new( @@ -250,8 +177,8 @@ impl EcdsaSigningKey { rng: &dyn SecureRandom, ) -> Result { let pkcs8_prefix = match scheme { - SignatureScheme::ECDSA_NISTP256_SHA256 => &PKCS8_PREFIX_ECDSA_NISTP256, - SignatureScheme::ECDSA_NISTP384_SHA384 => &PKCS8_PREFIX_ECDSA_NISTP384, + SignatureScheme::ECDSA_NISTP256_SHA256 => &Self::PKCS8_PREFIX_ECDSA_NISTP256, + SignatureScheme::ECDSA_NISTP384_SHA384 => &Self::PKCS8_PREFIX_ECDSA_NISTP384, _ => unreachable!(), // all callers are in this file }; @@ -260,35 +187,40 @@ impl EcdsaSigningKey { EcdsaKeyPair::from_pkcs8(sigalg, &pkcs8, rng).map_err(|_| ()) } -} -// This is (line-by-line): -// - INTEGER Version = 0 -// - SEQUENCE (privateKeyAlgorithm) -// - id-ecPublicKey OID -// - prime256v1 OID -const PKCS8_PREFIX_ECDSA_NISTP256: &[u8] = b"\x02\x01\x00\ + fn sign(&self, message: &[u8]) -> Result, Error> { + let rng = SystemRandom::new(); + self.key + .sign(&rng, message) + .map_err(|_| Error::General("signing failed".into())) + .map(|sig| sig.as_ref().into()) + } + + // This is (line-by-line): + // - INTEGER Version = 0 + // - SEQUENCE (privateKeyAlgorithm) + // - id-ecPublicKey OID + // - prime256v1 OID + const PKCS8_PREFIX_ECDSA_NISTP256: &[u8] = b"\x02\x01\x00\ \x30\x13\ \x06\x07\x2a\x86\x48\xce\x3d\x02\x01\ \x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07"; -// This is (line-by-line): -// - INTEGER Version = 0 -// - SEQUENCE (privateKeyAlgorithm) -// - id-ecPublicKey OID -// - secp384r1 OID -const PKCS8_PREFIX_ECDSA_NISTP384: &[u8] = b"\x02\x01\x00\ + // This is (line-by-line): + // - INTEGER Version = 0 + // - SEQUENCE (privateKeyAlgorithm) + // - id-ecPublicKey OID + // - secp384r1 OID + const PKCS8_PREFIX_ECDSA_NISTP384: &[u8] = b"\x02\x01\x00\ \x30\x10\ \x06\x07\x2a\x86\x48\xce\x3d\x02\x01\ \x06\x05\x2b\x81\x04\x00\x22"; +} -impl SigningKey for EcdsaSigningKey { +impl SigningKey for EcdsaSigner { fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { if offered.contains(&self.scheme) { - Some(Box::new(EcdsaSigner { - key: Arc::clone(&self.key), - scheme: self.scheme, - })) + Some(Box::new(self.clone())) } else { None } @@ -303,36 +235,45 @@ impl SigningKey for EcdsaSigningKey { Some(public_key_to_spki(&id, self.key.public_key())) } +} - fn algorithm(&self) -> SignatureAlgorithm { - self.scheme.algorithm() +impl Signer for EcdsaSigner { + fn sign(self: Box, message: &[u8]) -> Result, Error> { + (*self).sign(message) } -} -impl Debug for EcdsaSigningKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("EcdsaSigningKey") - .field("algorithm", &self.algorithm()) - .finish() + fn scheme(&self) -> SignatureScheme { + self.scheme } } -struct EcdsaSigner { - key: Arc, - scheme: SignatureScheme, -} +impl TryFrom<&PrivateKeyDer<'_>> for EcdsaSigner { + type Error = Error; + + /// Parse `der` as any ECDSA key type, returning the first which works. + /// + /// Both SEC1 (PEM section starting with 'BEGIN EC PRIVATE KEY') and PKCS8 + /// (PEM section starting with 'BEGIN PRIVATE KEY') encodings are supported. + fn try_from(der: &PrivateKeyDer<'_>) -> Result { + if let Ok(ecdsa_p256) = Self::new( + der, + SignatureScheme::ECDSA_NISTP256_SHA256, + &signature::ECDSA_P256_SHA256_ASN1_SIGNING, + ) { + return Ok(ecdsa_p256); + } -impl Signer for EcdsaSigner { - fn sign(&self, message: &[u8]) -> Result, Error> { - let rng = SystemRandom::new(); - self.key - .sign(&rng, message) - .map_err(|_| Error::General("signing failed".into())) - .map(|sig| sig.as_ref().into()) - } + if let Ok(ecdsa_p384) = Self::new( + der, + SignatureScheme::ECDSA_NISTP384_SHA384, + &signature::ECDSA_P384_SHA384_ASN1_SIGNING, + ) { + return Ok(ecdsa_p384); + } - fn scheme(&self) -> SignatureScheme { - self.scheme + Err(Error::General( + "failed to parse ECDSA private key as PKCS#8 or SEC1".into(), + )) } } @@ -340,49 +281,30 @@ impl Debug for EcdsaSigner { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("EcdsaSigner") .field("scheme", &self.scheme) - .finish() + .finish_non_exhaustive() } } -/// A SigningKey that uses exactly one TLS-level SignatureScheme -/// and one ring-level signature::SigningAlgorithm. -/// -/// Compare this to RsaSigningKey, which for a particular key is -/// willing to sign with several algorithms. This is quite poor -/// cryptography practice, but is necessary because a given RSA key -/// is expected to work in TLS1.2 (PKCS#1 signatures) and TLS1.3 -/// (PSS signatures) -- nobody is willing to obtain certificates for -/// different protocol versions. +/// A [`SigningKey`] and [`Signer`] implementation for ED25519. /// -/// Currently this is only implemented for Ed25519 keys. -struct Ed25519SigningKey { +/// Unlike [`RsaSigningKey`]/[`RsaSigner`], where we have one key that supports +/// multiple signature schemes, we can use the same type for both traits here. +#[derive(Clone)] +pub(super) struct Ed25519Signer { key: Arc, scheme: SignatureScheme, } -impl Ed25519SigningKey { - /// Make a new `Ed25519SigningKey` from a DER encoding in PKCS#8 format, - /// expecting a key usable with precisely the given signature scheme. - fn new(der: &PrivatePkcs8KeyDer<'_>, scheme: SignatureScheme) -> Result { - match Ed25519KeyPair::from_pkcs8_maybe_unchecked(der.secret_pkcs8_der()) { - Ok(key_pair) => Ok(Self { - key: Arc::new(key_pair), - scheme, - }), - Err(e) => Err(Error::General(format!( - "failed to parse Ed25519 private key: {e}" - ))), - } +impl Ed25519Signer { + fn sign(&self, message: &[u8]) -> Result, Error> { + Ok(self.key.sign(message).as_ref().into()) } } -impl SigningKey for Ed25519SigningKey { +impl SigningKey for Ed25519Signer { fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { if offered.contains(&self.scheme) { - Some(Box::new(Ed25519Signer { - key: Arc::clone(&self.key), - scheme: self.scheme, - })) + Some(Box::new(self.clone())) } else { None } @@ -391,28 +313,11 @@ impl SigningKey for Ed25519SigningKey { fn public_key(&self) -> Option> { Some(public_key_to_spki(&alg_id::ED25519, self.key.public_key())) } - - fn algorithm(&self) -> SignatureAlgorithm { - self.scheme.algorithm() - } -} - -impl Debug for Ed25519SigningKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("Ed25519SigningKey") - .field("algorithm", &self.algorithm()) - .finish() - } -} - -struct Ed25519Signer { - key: Arc, - scheme: SignatureScheme, } impl Signer for Ed25519Signer { - fn sign(&self, message: &[u8]) -> Result, Error> { - Ok(self.key.sign(message).as_ref().into()) + fn sign(self: Box, message: &[u8]) -> Result, Error> { + (*self).sign(message) } fn scheme(&self) -> SignatureScheme { @@ -420,14 +325,92 @@ impl Signer for Ed25519Signer { } } +impl TryFrom<&PrivatePkcs8KeyDer<'_>> for Ed25519Signer { + type Error = Error; + + /// Parse `der` as an Ed25519 key. + /// + /// Note that, at the time of writing, Ed25519 does not have wide support + /// in browsers. It is also not supported by the WebPKI, because the + /// CA/Browser Forum Baseline Requirements do not support it for publicly + /// trusted certificates. + fn try_from(der: &PrivatePkcs8KeyDer<'_>) -> Result { + match Ed25519KeyPair::from_pkcs8_maybe_unchecked(der.secret_pkcs8_der()) { + Ok(key_pair) => Ok(Self { + key: Arc::new(key_pair), + scheme: SignatureScheme::ED25519, + }), + Err(e) => Err(Error::General(format!( + "failed to parse Ed25519 private key: {e}" + ))), + } + } +} + impl Debug for Ed25519Signer { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("Ed25519Signer") .field("scheme", &self.scheme) - .finish() + .finish_non_exhaustive() + } +} + +#[cfg(test)] // Also available for benchmarks +fn load_key( + provider: &CryptoProvider, + der: PrivateKeyDer<'static>, +) -> Result, Error> { + provider + .key_provider + .load_private_key(der) +} + +/// Prepend stuff to `bytes_a` + `bytes_b` to put it in a DER SEQUENCE. +pub(crate) fn wrap_concat_in_sequence(bytes_a: &[u8], bytes_b: &[u8]) -> Vec { + asn1_wrap(DER_SEQUENCE_TAG, bytes_a, bytes_b) +} + +/// Prepend stuff to `bytes` to put it in a DER OCTET STRING. +pub(crate) fn wrap_in_octet_string(bytes: &[u8]) -> Vec { + asn1_wrap(DER_OCTET_STRING_TAG, bytes, &[]) +} + +fn asn1_wrap(tag: u8, bytes_a: &[u8], bytes_b: &[u8]) -> Vec { + let len = bytes_a.len() + bytes_b.len(); + + if len <= 0x7f { + // Short form + let mut ret = Vec::with_capacity(2 + len); + ret.push(tag); + ret.push(len as u8); + ret.extend_from_slice(bytes_a); + ret.extend_from_slice(bytes_b); + ret + } else { + // Long form + let size = len.to_be_bytes(); + let leading_zero_bytes = size + .iter() + .position(|&x| x != 0) + .unwrap_or(size.len()); + assert!(leading_zero_bytes < size.len()); + let encoded_bytes = size.len() - leading_zero_bytes; + + let mut ret = Vec::with_capacity(2 + encoded_bytes + len); + ret.push(tag); + + ret.push(0x80 + encoded_bytes as u8); + ret.extend_from_slice(&size[leading_zero_bytes..]); + + ret.extend_from_slice(bytes_a); + ret.extend_from_slice(bytes_b); + ret } } +const DER_SEQUENCE_TAG: u8 = 0x30; +const DER_OCTET_STRING_TAG: u8 = 0x04; + #[cfg(test)] mod tests { use alloc::format; @@ -435,181 +418,196 @@ mod tests { use pki_types::{PrivatePkcs1KeyDer, PrivateSec1KeyDer}; use super::*; + use crate::DEFAULT_PROVIDER; #[test] fn can_load_ecdsa_nistp256_pkcs8() { - let key = - PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/nistp256key.pkcs8.der")[..]); - assert!(any_eddsa_type(&key).is_err()); + let key = PrivatePkcs8KeyDer::from( + &include_bytes!("../../rustls/src/testdata/nistp256key.pkcs8.der")[..], + ); + assert!(Ed25519Signer::try_from(&key).is_err()); let key = PrivateKeyDer::Pkcs8(key); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_ok()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_ok()); } #[test] fn can_load_ecdsa_nistp256_sec1() { let key = PrivateKeyDer::Sec1(PrivateSec1KeyDer::from( - &include_bytes!("../../testdata/nistp256key.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp256key.der")[..], )); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_ok()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_ok()); } #[test] fn can_sign_ecdsa_nistp256() { let key = PrivateKeyDer::Sec1(PrivateSec1KeyDer::from( - &include_bytes!("../../testdata/nistp256key.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp256key.der")[..], )); - let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "EcdsaSigningKey { algorithm: ECDSA }"); - assert_eq!(k.algorithm(), SignatureAlgorithm::ECDSA); + let k = load_key(&DEFAULT_PROVIDER, key.clone_key()).unwrap(); + assert_eq!( + format!("{k:?}"), + "EcdsaSigner { scheme: ECDSA_NISTP256_SHA256, .. }" + ); - assert!(k - .choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) - .is_none()); - assert!(k - .choose_scheme(&[SignatureScheme::ECDSA_NISTP384_SHA384]) - .is_none()); + assert!( + k.choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) + .is_none() + ); + assert!( + k.choose_scheme(&[SignatureScheme::ECDSA_NISTP384_SHA384]) + .is_none() + ); let s = k .choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) .unwrap(); assert_eq!( - format!("{:?}", s), - "EcdsaSigner { scheme: ECDSA_NISTP256_SHA256 }" + format!("{s:?}"), + "EcdsaSigner { scheme: ECDSA_NISTP256_SHA256, .. }" ); assert_eq!(s.scheme(), SignatureScheme::ECDSA_NISTP256_SHA256); // nb. signature is variable length and asn.1-encoded - assert!(s - .sign(b"hello") - .unwrap() - .starts_with(&[0x30])); + assert!( + s.sign(b"hello") + .unwrap() + .starts_with(&[0x30]) + ); } #[test] fn can_load_ecdsa_nistp384_pkcs8() { - let key = - PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/nistp384key.pkcs8.der")[..]); - assert!(any_eddsa_type(&key).is_err()); + let key = PrivatePkcs8KeyDer::from( + &include_bytes!("../../rustls/src/testdata/nistp384key.pkcs8.der")[..], + ); + assert!(Ed25519Signer::try_from(&key).is_err()); let key = PrivateKeyDer::Pkcs8(key); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_ok()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_ok()); } #[test] fn can_load_ecdsa_nistp384_sec1() { let key = PrivateKeyDer::Sec1(PrivateSec1KeyDer::from( - &include_bytes!("../../testdata/nistp384key.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp384key.der")[..], )); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_ok()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_ok()); } #[test] fn can_sign_ecdsa_nistp384() { let key = PrivateKeyDer::Sec1(PrivateSec1KeyDer::from( - &include_bytes!("../../testdata/nistp384key.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp384key.der")[..], )); - let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "EcdsaSigningKey { algorithm: ECDSA }"); - assert_eq!(k.algorithm(), SignatureAlgorithm::ECDSA); + let k = load_key(&DEFAULT_PROVIDER, key.clone_key()).unwrap(); + assert_eq!( + format!("{k:?}"), + "EcdsaSigner { scheme: ECDSA_NISTP384_SHA384, .. }" + ); - assert!(k - .choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) - .is_none()); - assert!(k - .choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) - .is_none()); + assert!( + k.choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) + .is_none() + ); + assert!( + k.choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) + .is_none() + ); let s = k .choose_scheme(&[SignatureScheme::ECDSA_NISTP384_SHA384]) .unwrap(); assert_eq!( - format!("{:?}", s), - "EcdsaSigner { scheme: ECDSA_NISTP384_SHA384 }" + format!("{s:?}"), + "EcdsaSigner { scheme: ECDSA_NISTP384_SHA384, .. }" ); assert_eq!(s.scheme(), SignatureScheme::ECDSA_NISTP384_SHA384); // nb. signature is variable length and asn.1-encoded - assert!(s - .sign(b"hello") - .unwrap() - .starts_with(&[0x30])); + assert!( + s.sign(b"hello") + .unwrap() + .starts_with(&[0x30]) + ); } #[test] fn can_load_eddsa_pkcs8() { - let key = PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/eddsakey.der")[..]); - assert!(any_eddsa_type(&key).is_ok()); + let key = + PrivatePkcs8KeyDer::from(&include_bytes!("../../rustls/src/testdata/eddsakey.der")[..]); + assert!(Ed25519Signer::try_from(&key).is_ok()); let key = PrivateKeyDer::Pkcs8(key); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_err()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_err()); } #[test] fn can_sign_eddsa() { - let key = PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/eddsakey.der")[..]); + let key = + PrivatePkcs8KeyDer::from(&include_bytes!("../../rustls/src/testdata/eddsakey.der")[..]); - let k = any_eddsa_type(&key).unwrap(); - assert_eq!( - format!("{:?}", k), - "Ed25519SigningKey { algorithm: ED25519 }" - ); - assert_eq!(k.algorithm(), SignatureAlgorithm::ED25519); + let k = Ed25519Signer::try_from(&key).unwrap(); + assert_eq!(format!("{k:?}"), "Ed25519Signer { scheme: ED25519, .. }"); - assert!(k - .choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) - .is_none()); - assert!(k - .choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) - .is_none()); + assert!( + k.choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) + .is_none() + ); + assert!( + k.choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) + .is_none() + ); let s = k .choose_scheme(&[SignatureScheme::ED25519]) .unwrap(); - assert_eq!(format!("{:?}", s), "Ed25519Signer { scheme: ED25519 }"); + assert_eq!(format!("{s:?}"), "Ed25519Signer { scheme: ED25519, .. }"); assert_eq!(s.scheme(), SignatureScheme::ED25519); assert_eq!(s.sign(b"hello").unwrap().len(), 64); } #[test] fn can_load_rsa2048_pkcs8() { - let key = - PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/rsa2048key.pkcs8.der")[..]); - assert!(any_eddsa_type(&key).is_err()); + let key = PrivatePkcs8KeyDer::from( + &include_bytes!("../../rustls/src/testdata/rsa2048key.pkcs8.der")[..], + ); + assert!(Ed25519Signer::try_from(&key).is_err()); let key = PrivateKeyDer::Pkcs8(key); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_err()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_err()); } #[test] fn can_load_rsa2048_pkcs1() { let key = PrivateKeyDer::Pkcs1(PrivatePkcs1KeyDer::from( - &include_bytes!("../../testdata/rsa2048key.pkcs1.der")[..], + &include_bytes!("../../rustls/src/testdata/rsa2048key.pkcs1.der")[..], )); - assert!(any_supported_type(&key).is_ok()); - assert!(any_ecdsa_type(&key).is_err()); + assert!(load_key(&DEFAULT_PROVIDER, key.clone_key()).is_ok()); + assert!(EcdsaSigner::try_from(&key).is_err()); } #[test] fn can_sign_rsa2048() { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/rsa2048key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/rsa2048key.pkcs8.der")[..], )); - let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "RsaSigningKey { algorithm: RSA }"); - assert_eq!(k.algorithm(), SignatureAlgorithm::RSA); + let k = load_key(&DEFAULT_PROVIDER, key.clone_key()).unwrap(); + assert_eq!(format!("{k:?}"), "RsaSigningKey { .. }"); - assert!(k - .choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) - .is_none()); - assert!(k - .choose_scheme(&[SignatureScheme::ED25519]) - .is_none()); + assert!( + k.choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) + .is_none() + ); + assert!( + k.choose_scheme(&[SignatureScheme::ED25519]) + .is_none() + ); let s = k .choose_scheme(&[SignatureScheme::RSA_PSS_SHA256]) .unwrap(); - assert_eq!(format!("{:?}", s), "RsaSigner { scheme: RSA_PSS_SHA256 }"); + assert_eq!(format!("{s:?}"), "RsaSigner { scheme: RSA_PSS_SHA256, .. }"); assert_eq!(s.scheme(), SignatureScheme::RSA_PSS_SHA256); assert_eq!(s.sign(b"hello").unwrap().len(), 256); @@ -629,19 +627,19 @@ mod tests { fn cannot_load_invalid_pkcs8_encoding() { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(&b"invalid"[..])); assert_eq!( - any_supported_type(&key).err(), + load_key(&DEFAULT_PROVIDER, key.clone_key()).err(), Some(Error::General( "failed to parse private key as RSA, ECDSA, or EdDSA".into() )) ); assert_eq!( - any_ecdsa_type(&key).err(), + EcdsaSigner::try_from(&key).err(), Some(Error::General( "failed to parse ECDSA private key as PKCS#8 or SEC1".into() )) ); assert_eq!( - RsaSigningKey::new(&key).err(), + RsaSigningKey::try_from(&key).err(), Some(Error::General( "failed to parse RSA private key: InvalidEncoding".into() )) @@ -649,19 +647,20 @@ mod tests { } } -#[cfg(bench)] +#[cfg(all(test, bench))] mod benchmarks { - use super::{PrivateKeyDer, PrivatePkcs8KeyDer, SignatureScheme}; + use super::*; + use crate::DEFAULT_PROVIDER; #[bench] fn bench_rsa2048_pkcs1_sha256(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/rsa2048key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/rsa2048key.pkcs8.der")[..], )); - let sk = super::any_supported_type(&key).unwrap(); - let signer = sk - .choose_scheme(&[SignatureScheme::RSA_PKCS1_SHA256]) - .unwrap(); + + let signer = RsaSigningKey::try_from(&key) + .unwrap() + .to_signer(SignatureScheme::RSA_PKCS1_SHA256); b.iter(|| { test::black_box( @@ -675,12 +674,12 @@ mod benchmarks { #[bench] fn bench_rsa2048_pss_sha256(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/rsa2048key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/rsa2048key.pkcs8.der")[..], )); - let sk = super::any_supported_type(&key).unwrap(); - let signer = sk - .choose_scheme(&[SignatureScheme::RSA_PSS_SHA256]) - .unwrap(); + + let signer = RsaSigningKey::try_from(&key) + .unwrap() + .to_signer(SignatureScheme::RSA_PSS_SHA256); b.iter(|| { test::black_box( @@ -693,13 +692,9 @@ mod benchmarks { #[bench] fn bench_eddsa(b: &mut test::Bencher) { - let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/eddsakey.der")[..], - )); - let sk = super::any_supported_type(&key).unwrap(); - let signer = sk - .choose_scheme(&[SignatureScheme::ED25519]) - .unwrap(); + let key = + PrivatePkcs8KeyDer::from(&include_bytes!("../../rustls/src/testdata/eddsakey.der")[..]); + let signer = Ed25519Signer::try_from(&key).unwrap(); b.iter(|| { test::black_box( @@ -713,13 +708,10 @@ mod benchmarks { #[bench] fn bench_ecdsa_p256_sha256(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/nistp256key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp256key.pkcs8.der")[..], )); - let sk = super::any_supported_type(&key).unwrap(); - let signer = sk - .choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]) - .unwrap(); + let signer = EcdsaSigner::try_from(&key).unwrap(); b.iter(|| { test::black_box( signer @@ -732,13 +724,10 @@ mod benchmarks { #[bench] fn bench_ecdsa_p384_sha384(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/nistp384key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp384key.pkcs8.der")[..], )); - let sk = super::any_supported_type(&key).unwrap(); - let signer = sk - .choose_scheme(&[SignatureScheme::ECDSA_NISTP384_SHA384]) - .unwrap(); + let signer = EcdsaSigner::try_from(&key).unwrap(); b.iter(|| { test::black_box( signer @@ -751,53 +740,54 @@ mod benchmarks { #[bench] fn bench_load_and_validate_rsa2048(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/rsa2048key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/rsa2048key.pkcs8.der")[..], )); b.iter(|| { - test::black_box(super::any_supported_type(&key).unwrap()); + test::black_box(load_key(&DEFAULT_PROVIDER, key.clone_key()).unwrap()); }); } #[bench] fn bench_load_and_validate_rsa4096(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/rsa4096key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/rsa4096key.pkcs8.der")[..], )); b.iter(|| { - test::black_box(super::any_supported_type(&key).unwrap()); + test::black_box(load_key(&DEFAULT_PROVIDER, key.clone_key()).unwrap()); }); } #[bench] fn bench_load_and_validate_p256(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/nistp256key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp256key.pkcs8.der")[..], )); b.iter(|| { - test::black_box(super::any_ecdsa_type(&key).unwrap()); + test::black_box(EcdsaSigner::try_from(&key).unwrap()); }); } #[bench] fn bench_load_and_validate_p384(b: &mut test::Bencher) { let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - &include_bytes!("../../testdata/nistp384key.pkcs8.der")[..], + &include_bytes!("../../rustls/src/testdata/nistp384key.pkcs8.der")[..], )); b.iter(|| { - test::black_box(super::any_ecdsa_type(&key).unwrap()); + test::black_box(EcdsaSigner::try_from(&key).unwrap()); }); } #[bench] fn bench_load_and_validate_eddsa(b: &mut test::Bencher) { - let key = PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/eddsakey.der")[..]); + let key = + PrivatePkcs8KeyDer::from(&include_bytes!("../../rustls/src/testdata/eddsakey.der")[..]); b.iter(|| { - test::black_box(super::any_eddsa_type(&key).unwrap()); + test::black_box(Ed25519Signer::try_from(&key).unwrap()); }); } diff --git a/rustls/src/testdata/prf-result.1.bin b/rustls-ring/src/test-data/prf-result.1.bin similarity index 100% rename from rustls/src/testdata/prf-result.1.bin rename to rustls-ring/src/test-data/prf-result.1.bin diff --git a/rustls/src/testdata/prf-result.2.bin b/rustls-ring/src/test-data/prf-result.2.bin similarity index 100% rename from rustls/src/testdata/prf-result.2.bin rename to rustls-ring/src/test-data/prf-result.2.bin diff --git a/rustls/src/testdata/prf-result.3.bin b/rustls-ring/src/test-data/prf-result.3.bin similarity index 100% rename from rustls/src/testdata/prf-result.3.bin rename to rustls-ring/src/test-data/prf-result.3.bin diff --git a/rustls-ring/src/ticketer.rs b/rustls-ring/src/ticketer.rs new file mode 100644 index 00000000000..cbd384a8207 --- /dev/null +++ b/rustls-ring/src/ticketer.rs @@ -0,0 +1,205 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::fmt; +use core::fmt::{Debug, Formatter}; +use core::sync::atomic::{AtomicUsize, Ordering}; +use core::time::Duration; + +use ring::aead; +use ring::rand::{SecureRandom, SystemRandom}; +use rustls::crypto::TicketProducer; +use rustls::error::Error; +use subtle::ConstantTimeEq; + +/// A [`TicketProducer`] implementation which can use any *ring* `aead::Algorithm`. +/// +/// It does not enforce any lifetime constraint. +pub(super) struct AeadTicketer { + alg: &'static aead::Algorithm, + key: aead::LessSafeKey, + key_name: [u8; 16], + + /// Tracks the largest ciphertext produced by `encrypt`, and + /// uses it to early-reject `decrypt` queries that are too long. + /// + /// Accepting excessively long ciphertexts means a "Partitioning + /// Oracle Attack" (see ) + /// can be more efficient, though also note that these are thought + /// to be cryptographically hard if the key is full-entropy (as it + /// is here). + maximum_ciphertext_len: AtomicUsize, +} + +impl AeadTicketer { + #[expect(clippy::new_ret_no_self)] + pub(super) fn new() -> Result, Error> { + let mut key = [0u8; 32]; + SystemRandom::new() + .fill(&mut key) + .map_err(|_| Error::FailedToGetRandomBytes)?; + + let key = aead::UnboundKey::new(TICKETER_AEAD, &key).unwrap(); + + let mut key_name = [0u8; 16]; + SystemRandom::new() + .fill(&mut key_name) + .map_err(|_| Error::FailedToGetRandomBytes)?; + + Ok(Box::new(Self { + alg: TICKETER_AEAD, + key: aead::LessSafeKey::new(key), + key_name, + maximum_ciphertext_len: AtomicUsize::new(0), + })) + } +} + +impl TicketProducer for AeadTicketer { + /// Encrypt `message` and return the ciphertext. + fn encrypt(&self, message: &[u8]) -> Option> { + // Random nonce, because a counter is a privacy leak. + let mut nonce_buf = [0u8; 12]; + SystemRandom::new() + .fill(&mut nonce_buf) + .ok()?; + let nonce = aead::Nonce::assume_unique_for_key(nonce_buf); + let aad = aead::Aad::from(self.key_name); + + // ciphertext structure is: + // key_name: [u8; 16] + // nonce: [u8; 12] + // message: [u8, _] + // tag: [u8; 16] + + let mut ciphertext = Vec::with_capacity( + self.key_name.len() + nonce_buf.len() + message.len() + self.key.algorithm().tag_len(), + ); + ciphertext.extend(self.key_name); + ciphertext.extend(nonce_buf); + ciphertext.extend(message); + let ciphertext = self + .key + .seal_in_place_separate_tag( + nonce, + aad, + &mut ciphertext[self.key_name.len() + nonce_buf.len()..], + ) + .map(|tag| { + ciphertext.extend(tag.as_ref()); + ciphertext + }) + .ok()?; + + self.maximum_ciphertext_len + .fetch_max(ciphertext.len(), Ordering::SeqCst); + Some(ciphertext) + } + + /// Decrypt `ciphertext` and recover the original message. + fn decrypt(&self, ciphertext: &[u8]) -> Option> { + if ciphertext.len() + > self + .maximum_ciphertext_len + .load(Ordering::SeqCst) + { + return None; + } + + let (alleged_key_name, ciphertext) = ciphertext.split_at_checked(self.key_name.len())?; + + let (nonce, ciphertext) = ciphertext.split_at_checked(self.alg.nonce_len())?; + + // checking the key_name is the expected one, *and* then putting it into the + // additionally authenticated data is duplicative. this check quickly rejects + // tickets for a different ticketer (see `TicketRotator`), while including it + // in the AAD ensures it is authenticated independent of that check and that + // any attempted attack on the integrity such as [^1] must happen for each + // `key_label`, not over a population of potential keys. this approach + // is overall similar to [^2]. + // + // [^1]: https://eprint.iacr.org/2020/1491.pdf + // [^2]: "Authenticated Encryption with Key Identification", fig 6 + // + if ConstantTimeEq::ct_ne(&self.key_name[..], alleged_key_name).into() { + return None; + } + + // This won't fail since `nonce` has the required length. + let nonce = aead::Nonce::try_assume_unique_for_key(nonce).ok()?; + + let mut out = Vec::from(ciphertext); + + let plain_len = self + .key + .open_in_place(nonce, aead::Aad::from(alleged_key_name), &mut out) + .ok()? + .len(); + out.truncate(plain_len); + + Some(out) + } + + fn lifetime(&self) -> Duration { + // this is not used, as this ticketer is only used via a `TicketRotator` + // that is responsible for defining and managing the lifetime of tickets. + Duration::ZERO + } +} + +impl Debug for AeadTicketer { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // Note: we deliberately omit the key from the debug output. + f.debug_struct("AeadTicketer") + .field("alg", &self.alg) + .finish_non_exhaustive() + } +} + +static TICKETER_AEAD: &aead::Algorithm = &aead::CHACHA20_POLY1305; + +#[cfg(all(test, feature = "std"))] +mod tests { + use rustls::crypto::TicketerFactory; + + use crate::Ring; + + #[test] + fn basic_pairwise_test() { + let t = Ring.ticketer().unwrap(); + let cipher = t.encrypt(b"hello world").unwrap(); + let plain = t.decrypt(&cipher).unwrap(); + assert_eq!(plain, b"hello world"); + } + + #[test] + fn refuses_decrypt_before_encrypt() { + let t = Ring.ticketer().unwrap(); + assert_eq!(t.decrypt(b"hello"), None); + } + + #[test] + fn refuses_decrypt_larger_than_largest_encryption() { + let t = Ring.ticketer().unwrap(); + let mut cipher = t.encrypt(b"hello world").unwrap(); + assert_eq!(t.decrypt(&cipher), Some(b"hello world".to_vec())); + + // obviously this would never work anyway, but this + // and `cannot_decrypt_before_encrypt` exercise the + // first branch in `decrypt()` + cipher.push(0); + assert_eq!(t.decrypt(&cipher), None); + } + + #[test] + fn aeadticketer_is_debug_and_producestickets() { + use alloc::format; + + use super::*; + + let t = AeadTicketer::new().unwrap(); + + let expect = format!("AeadTicketer {{ alg: {TICKETER_AEAD:?}, .. }}"); + assert_eq!(format!("{t:?}"), expect); + assert_eq!(t.lifetime(), Duration::ZERO); + } +} diff --git a/rustls/src/crypto/ring/tls12.rs b/rustls-ring/src/tls12.rs similarity index 54% rename from rustls/src/crypto/ring/tls12.rs rename to rustls-ring/src/tls12.rs index 5bc2926000b..1a1188b1de9 100644 --- a/rustls/src/crypto/ring/tls12.rs +++ b/rustls-ring/src/tls12.rs @@ -1,104 +1,102 @@ use alloc::boxed::Box; -use super::ring_like::aead; -use crate::crypto::cipher::{ - make_tls12_aad, AeadKey, InboundOpaqueMessage, Iv, KeyBlockShape, MessageDecrypter, - MessageEncrypter, Nonce, Tls12AeadAlgorithm, UnsupportedOperationError, NONCE_LEN, +use pki_types::FipsStatus; +use ring::aead; +use rustls::crypto::cipher::{ + AeadKey, EncodedMessage, InboundOpaque, Iv, KeyBlockShape, MessageDecrypter, MessageEncrypter, + NONCE_LEN, Nonce, OutboundOpaque, OutboundPlain, Tls12AeadAlgorithm, UnsupportedOperationError, + make_tls12_aad, }; -use crate::crypto::tls12::PrfUsingHmac; -use crate::crypto::KeyExchangeAlgorithm; -use crate::enums::{CipherSuite, SignatureScheme}; -use crate::error::Error; -use crate::msgs::fragmenter::MAX_FRAGMENT_LEN; -use crate::msgs::message::{ - InboundPlainMessage, OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, -}; -use crate::suites::{CipherSuiteCommon, ConnectionTrafficSecrets, SupportedCipherSuite}; -use crate::tls12::Tls12CipherSuite; +use rustls::crypto::kx::KeyExchangeAlgorithm; +use rustls::crypto::tls12::PrfUsingHmac; +use rustls::crypto::{CipherSuite, SignatureScheme}; +use rustls::error::Error; +use rustls::version::TLS12_VERSION; +use rustls::{CipherSuiteCommon, ConnectionTrafficSecrets, Tls12CipherSuite}; /// The TLS1.2 ciphersuite TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256. -pub static TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - hash_provider: &super::hash::SHA256, - confidentiality_limit: u64::MAX, - }, - kx: KeyExchangeAlgorithm::ECDHE, - sign: TLS12_ECDSA_SCHEMES, - aead_alg: &ChaCha20Poly1305, - prf_provider: &PrfUsingHmac(&super::hmac::HMAC_SHA256), - }); +pub static TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: &Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + hash_provider: &super::hash::SHA256, + confidentiality_limit: u64::MAX, + }, + protocol_version: TLS12_VERSION, + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_ECDSA_SCHEMES, + aead_alg: &ChaCha20Poly1305, + prf_provider: &PrfUsingHmac(&super::hmac::HMAC_SHA256), +}; /// The TLS1.2 ciphersuite TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 -pub static TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - hash_provider: &super::hash::SHA256, - confidentiality_limit: u64::MAX, - }, - kx: KeyExchangeAlgorithm::ECDHE, - sign: TLS12_RSA_SCHEMES, - aead_alg: &ChaCha20Poly1305, - prf_provider: &PrfUsingHmac(&super::hmac::HMAC_SHA256), - }); +pub static TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: &Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + hash_provider: &super::hash::SHA256, + confidentiality_limit: u64::MAX, + }, + protocol_version: TLS12_VERSION, + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_RSA_SCHEMES, + aead_alg: &ChaCha20Poly1305, + prf_provider: &PrfUsingHmac(&super::hmac::HMAC_SHA256), +}; /// The TLS1.2 ciphersuite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 -pub static TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - hash_provider: &super::hash::SHA256, - confidentiality_limit: 1 << 24, - }, - kx: KeyExchangeAlgorithm::ECDHE, - sign: TLS12_RSA_SCHEMES, - aead_alg: &AES128_GCM, - prf_provider: &PrfUsingHmac(&super::hmac::HMAC_SHA256), - }); +pub static TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: &Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + hash_provider: &super::hash::SHA256, + confidentiality_limit: 1 << 24, + }, + protocol_version: TLS12_VERSION, + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_RSA_SCHEMES, + aead_alg: &AES128_GCM, + prf_provider: &PrfUsingHmac(&super::hmac::HMAC_SHA256), +}; /// The TLS1.2 ciphersuite TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 -pub static TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - hash_provider: &super::hash::SHA384, - confidentiality_limit: 1 << 24, - }, - kx: KeyExchangeAlgorithm::ECDHE, - sign: TLS12_RSA_SCHEMES, - aead_alg: &AES256_GCM, - prf_provider: &PrfUsingHmac(&super::hmac::HMAC_SHA384), - }); +pub static TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: &Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + hash_provider: &super::hash::SHA384, + confidentiality_limit: 1 << 24, + }, + protocol_version: TLS12_VERSION, + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_RSA_SCHEMES, + aead_alg: &AES256_GCM, + prf_provider: &PrfUsingHmac(&super::hmac::HMAC_SHA384), +}; /// The TLS1.2 ciphersuite TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 -pub static TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - hash_provider: &super::hash::SHA256, - confidentiality_limit: 1 << 24, - }, - kx: KeyExchangeAlgorithm::ECDHE, - sign: TLS12_ECDSA_SCHEMES, - aead_alg: &AES128_GCM, - prf_provider: &PrfUsingHmac(&super::hmac::HMAC_SHA256), - }); +pub static TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: &Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + hash_provider: &super::hash::SHA256, + confidentiality_limit: 1 << 24, + }, + protocol_version: TLS12_VERSION, + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_ECDSA_SCHEMES, + aead_alg: &AES128_GCM, + prf_provider: &PrfUsingHmac(&super::hmac::HMAC_SHA256), +}; /// The TLS1.2 ciphersuite TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 -pub static TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - hash_provider: &super::hash::SHA384, - confidentiality_limit: 1 << 24, - }, - kx: KeyExchangeAlgorithm::ECDHE, - sign: TLS12_ECDSA_SCHEMES, - aead_alg: &AES256_GCM, - prf_provider: &PrfUsingHmac(&super::hmac::HMAC_SHA384), - }); +pub static TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: &Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + hash_provider: &super::hash::SHA384, + confidentiality_limit: 1 << 24, + }, + protocol_version: TLS12_VERSION, + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_ECDSA_SCHEMES, + aead_alg: &AES256_GCM, + prf_provider: &PrfUsingHmac(&super::hmac::HMAC_SHA384), +}; static TLS12_ECDSA_SCHEMES: &[SignatureScheme] = &[ SignatureScheme::ED25519, @@ -170,7 +168,7 @@ impl Tls12AeadAlgorithm for GcmAlgorithm { }) } - fn fips(&self) -> bool { + fn fips(&self) -> FipsStatus { super::fips() } } @@ -184,7 +182,7 @@ impl Tls12AeadAlgorithm for ChaCha20Poly1305 { ); Box::new(ChaCha20Poly1305MessageDecrypter { dec_key, - dec_offset: Iv::copy(iv), + dec_offset: Iv::new(iv).expect("IV length validated by key_block_shape"), }) } @@ -194,7 +192,7 @@ impl Tls12AeadAlgorithm for ChaCha20Poly1305 { ); Box::new(ChaCha20Poly1305MessageEncrypter { enc_key, - enc_offset: Iv::copy(enc_iv), + enc_offset: Iv::new(enc_iv).expect("IV length validated by key_block_shape"), }) } @@ -216,12 +214,12 @@ impl Tls12AeadAlgorithm for ChaCha20Poly1305 { debug_assert_eq!(aead::NONCE_LEN, iv.len()); Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { key, - iv: Iv::new(iv[..].try_into().unwrap()), + iv: Iv::new(iv).expect("IV length validated by key_block_shape"), }) } - fn fips(&self) -> bool { - false // not fips approved + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated // not fips approved } } @@ -243,9 +241,9 @@ const GCM_OVERHEAD: usize = GCM_EXPLICIT_NONCE_LEN + 16; impl MessageDecrypter for GcmMessageDecrypter { fn decrypt<'a>( &mut self, - mut msg: InboundOpaqueMessage<'a>, + mut msg: EncodedMessage>, seq: u64, - ) -> Result, Error> { + ) -> Result, Error> { let payload = &msg.payload; if payload.len() < GCM_OVERHEAD { return Err(Error::DecryptError); @@ -284,13 +282,13 @@ impl MessageDecrypter for GcmMessageDecrypter { impl MessageEncrypter for GcmMessageEncrypter { fn encrypt( &mut self, - msg: OutboundPlainMessage<'_>, + msg: EncodedMessage>, seq: u64, - ) -> Result { + ) -> Result, Error> { let total_len = self.encrypted_payload_len(msg.payload.len()); - let mut payload = PrefixedPayload::with_capacity(total_len); + let mut payload = OutboundOpaque::with_capacity(total_len); - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).0); + let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).to_array()?); let aad = aead::Aad::from(make_tls12_aad(seq, msg.typ, msg.version, msg.payload.len())); payload.extend_from_slice(&nonce.as_ref()[4..]); payload.extend_from_chunks(&msg.payload); @@ -300,7 +298,11 @@ impl MessageEncrypter for GcmMessageEncrypter { .map(|tag| payload.extend_from_slice(tag.as_ref())) .map_err(|_| Error::EncryptError)?; - Ok(OutboundOpaqueMessage::new(msg.typ, msg.version, payload)) + Ok(EncodedMessage { + typ: msg.typ, + version: msg.version, + payload, + }) } fn encrypted_payload_len(&self, payload_len: usize) -> usize { @@ -329,16 +331,17 @@ const CHACHAPOLY1305_OVERHEAD: usize = 16; impl MessageDecrypter for ChaCha20Poly1305MessageDecrypter { fn decrypt<'a>( &mut self, - mut msg: InboundOpaqueMessage<'a>, + mut msg: EncodedMessage>, seq: u64, - ) -> Result, Error> { + ) -> Result, Error> { let payload = &msg.payload; if payload.len() < CHACHAPOLY1305_OVERHEAD { return Err(Error::DecryptError); } - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.dec_offset, seq).0); + let nonce = + aead::Nonce::assume_unique_for_key(Nonce::new(&self.dec_offset, seq).to_array()?); let aad = aead::Aad::from(make_tls12_aad( seq, msg.typ, @@ -365,13 +368,14 @@ impl MessageDecrypter for ChaCha20Poly1305MessageDecrypter { impl MessageEncrypter for ChaCha20Poly1305MessageEncrypter { fn encrypt( &mut self, - msg: OutboundPlainMessage<'_>, + msg: EncodedMessage>, seq: u64, - ) -> Result { + ) -> Result, Error> { let total_len = self.encrypted_payload_len(msg.payload.len()); - let mut payload = PrefixedPayload::with_capacity(total_len); + let mut payload = OutboundOpaque::with_capacity(total_len); - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.enc_offset, seq).0); + let nonce = + aead::Nonce::assume_unique_for_key(Nonce::new(&self.enc_offset, seq).to_array()?); let aad = aead::Aad::from(make_tls12_aad(seq, msg.typ, msg.version, msg.payload.len())); payload.extend_from_chunks(&msg.payload); @@ -379,7 +383,11 @@ impl MessageEncrypter for ChaCha20Poly1305MessageEncrypter { .seal_in_place_append_tag(nonce, aad, &mut payload) .map_err(|_| Error::EncryptError)?; - Ok(OutboundOpaqueMessage::new(msg.typ, msg.version, payload)) + Ok(EncodedMessage { + typ: msg.typ, + version: msg.version, + payload, + }) } fn encrypted_payload_len(&self, payload_len: usize) -> usize { @@ -402,5 +410,92 @@ fn gcm_iv(write_iv: &[u8], explicit: &[u8]) -> Iv { iv[..4].copy_from_slice(write_iv); iv[4..].copy_from_slice(explicit); - Iv::new(iv) + Iv::new(&iv).expect("IV length is NONCE_LEN, which is within MAX_LEN") +} + +const MAX_FRAGMENT_LEN: usize = 16384; + +#[cfg(test)] +mod tests { + use rustls::crypto::hmac::Hmac; + use rustls::crypto::tls12::prf; + + use crate::hmac; + + // Below known answer tests come from https://mailarchive.ietf.org/arch/msg/tls/fzVCzk-z3FShgGJ6DOXqM1ydxms/ + + #[test] + fn check_sha256() { + let secret = b"\x9b\xbe\x43\x6b\xa9\x40\xf0\x17\xb1\x76\x52\x84\x9a\x71\xdb\x35"; + let seed = b"\xa0\xba\x9f\x93\x6c\xda\x31\x18\x27\xa6\xf7\x96\xff\xd5\x19\x8c"; + let label = b"test label"; + let expect = include_bytes!("test-data/prf-result.1.bin"); + let mut output = [0u8; 100]; + + prf( + &mut output, + &*hmac::HMAC_SHA256.with_key(secret), + label, + seed, + ); + assert_eq!(expect.len(), output.len()); + assert_eq!(expect.to_vec(), output.to_vec()); + } + + #[test] + fn check_sha512() { + let secret = b"\xb0\x32\x35\x23\xc1\x85\x35\x99\x58\x4d\x88\x56\x8b\xbb\x05\xeb"; + let seed = b"\xd4\x64\x0e\x12\xe4\xbc\xdb\xfb\x43\x7f\x03\xe6\xae\x41\x8e\xe5"; + let label = b"test label"; + let expect = include_bytes!("test-data/prf-result.2.bin"); + let mut output = [0u8; 196]; + + prf( + &mut output, + &*hmac::HMAC_SHA512.with_key(secret), + label, + seed, + ); + assert_eq!(expect.len(), output.len()); + assert_eq!(expect.to_vec(), output.to_vec()); + } + + #[test] + fn check_sha384() { + let secret = b"\xb8\x0b\x73\x3d\x6c\xee\xfc\xdc\x71\x56\x6e\xa4\x8e\x55\x67\xdf"; + let seed = b"\xcd\x66\x5c\xf6\xa8\x44\x7d\xd6\xff\x8b\x27\x55\x5e\xdb\x74\x65"; + let label = b"test label"; + let expect = include_bytes!("test-data/prf-result.3.bin"); + let mut output = [0u8; 148]; + + prf( + &mut output, + &*hmac::HMAC_SHA384.with_key(secret), + label, + seed, + ); + assert_eq!(expect.len(), output.len()); + assert_eq!(expect.to_vec(), output.to_vec()); + } +} + +#[cfg(all(test, bench))] +mod benchmarks { + use rustls::crypto::hmac::Hmac; + use rustls::crypto::tls12::prf; + + use crate::hmac; + + #[bench] + fn bench_sha256(b: &mut test::Bencher) { + let label = &b"extended master secret"[..]; + let seed = [0u8; 32]; + let key = &b"secret"[..]; + + b.iter(|| { + let mut out = [0u8; 48]; + prf(&mut out, &*hmac::HMAC_SHA256.with_key(key), &label, &seed); + test::black_box(out); + }); + } } diff --git a/rustls/src/crypto/ring/tls13.rs b/rustls-ring/src/tls13.rs similarity index 53% rename from rustls/src/crypto/ring/tls13.rs rename to rustls-ring/src/tls13.rs index d390b2a4e24..dab09f406cb 100644 --- a/rustls/src/crypto/ring/tls13.rs +++ b/rustls-ring/src/tls13.rs @@ -1,32 +1,28 @@ use alloc::boxed::Box; -use super::ring_like::hkdf::KeyType; -use super::ring_like::{aead, hkdf, hmac}; -use crate::crypto; -use crate::crypto::cipher::{ - make_tls13_aad, AeadKey, InboundOpaqueMessage, Iv, MessageDecrypter, MessageEncrypter, Nonce, - Tls13AeadAlgorithm, UnsupportedOperationError, +use pki_types::FipsStatus; +use ring::hkdf::{self, KeyType}; +use ring::{aead, hmac}; +use rustls::crypto::CipherSuite; +use rustls::crypto::cipher::{ + AeadKey, EncodedMessage, InboundOpaque, Iv, MessageDecrypter, MessageEncrypter, Nonce, + OutboundOpaque, OutboundPlain, Tls13AeadAlgorithm, UnsupportedOperationError, make_tls13_aad, }; -use crate::crypto::tls13::{Hkdf, HkdfExpander, OkmBlock, OutputLengthError}; -use crate::enums::{CipherSuite, ContentType, ProtocolVersion}; -use crate::error::Error; -use crate::msgs::message::{ - InboundPlainMessage, OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, -}; -use crate::suites::{CipherSuiteCommon, ConnectionTrafficSecrets, SupportedCipherSuite}; -use crate::tls13::Tls13CipherSuite; +use rustls::crypto::tls13::{Hkdf, HkdfExpander, OkmBlock, OutputLengthError}; +use rustls::enums::{ContentType, ProtocolVersion}; +use rustls::error::Error; +use rustls::version::TLS13_VERSION; +use rustls::{CipherSuiteCommon, ConnectionTrafficSecrets, Tls13CipherSuite, crypto}; /// The TLS1.3 ciphersuite TLS_CHACHA20_POLY1305_SHA256 -pub static TLS13_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls13(TLS13_CHACHA20_POLY1305_SHA256_INTERNAL); - -pub(crate) static TLS13_CHACHA20_POLY1305_SHA256_INTERNAL: &Tls13CipherSuite = &Tls13CipherSuite { +pub static TLS13_CHACHA20_POLY1305_SHA256: &Tls13CipherSuite = &Tls13CipherSuite { common: CipherSuiteCommon { suite: CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, hash_provider: &super::hash::SHA256, // ref: confidentiality_limit: u64::MAX, }, + protocol_version: TLS13_VERSION, hkdf_provider: &RingHkdf(hkdf::HKDF_SHA256, hmac::HMAC_SHA256), aead_alg: &Chacha20Poly1305Aead(AeadAlgorithm(&aead::CHACHA20_POLY1305)), quic: Some(&super::quic::KeyBuilder { @@ -40,35 +36,33 @@ pub(crate) static TLS13_CHACHA20_POLY1305_SHA256_INTERNAL: &Tls13CipherSuite = & }; /// The TLS1.3 ciphersuite TLS_AES_256_GCM_SHA384 -pub static TLS13_AES_256_GCM_SHA384: SupportedCipherSuite = - SupportedCipherSuite::Tls13(&Tls13CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS13_AES_256_GCM_SHA384, - hash_provider: &super::hash::SHA384, - confidentiality_limit: 1 << 24, - }, - hkdf_provider: &RingHkdf(hkdf::HKDF_SHA384, hmac::HMAC_SHA384), - aead_alg: &Aes256GcmAead(AeadAlgorithm(&aead::AES_256_GCM)), - quic: Some(&super::quic::KeyBuilder { - packet_alg: &aead::AES_256_GCM, - header_alg: &aead::quic::AES_256, - // ref: - confidentiality_limit: 1 << 23, - // ref: - integrity_limit: 1 << 52, - }), - }); +pub static TLS13_AES_256_GCM_SHA384: &Tls13CipherSuite = &Tls13CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS13_AES_256_GCM_SHA384, + hash_provider: &super::hash::SHA384, + confidentiality_limit: 1 << 24, + }, + protocol_version: TLS13_VERSION, + hkdf_provider: &RingHkdf(hkdf::HKDF_SHA384, hmac::HMAC_SHA384), + aead_alg: &Aes256GcmAead(AeadAlgorithm(&aead::AES_256_GCM)), + quic: Some(&super::quic::KeyBuilder { + packet_alg: &aead::AES_256_GCM, + header_alg: &aead::quic::AES_256, + // ref: + confidentiality_limit: 1 << 23, + // ref: + integrity_limit: 1 << 52, + }), +}; /// The TLS1.3 ciphersuite TLS_AES_128_GCM_SHA256 -pub static TLS13_AES_128_GCM_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls13(TLS13_AES_128_GCM_SHA256_INTERNAL); - -pub(crate) static TLS13_AES_128_GCM_SHA256_INTERNAL: &Tls13CipherSuite = &Tls13CipherSuite { +pub static TLS13_AES_128_GCM_SHA256: &Tls13CipherSuite = &Tls13CipherSuite { common: CipherSuiteCommon { suite: CipherSuite::TLS13_AES_128_GCM_SHA256, hash_provider: &super::hash::SHA256, confidentiality_limit: 1 << 24, }, + protocol_version: TLS13_VERSION, hkdf_provider: &RingHkdf(hkdf::HKDF_SHA256, hmac::HMAC_SHA256), aead_alg: &Aes128GcmAead(AeadAlgorithm(&aead::AES_128_GCM)), quic: Some(&super::quic::KeyBuilder { @@ -104,8 +98,8 @@ impl Tls13AeadAlgorithm for Chacha20Poly1305Aead { Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }) } - fn fips(&self) -> bool { - false // chacha20poly1305 not FIPS approved + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated // chacha20poly1305 not FIPS approved } } @@ -132,7 +126,7 @@ impl Tls13AeadAlgorithm for Aes256GcmAead { Ok(ConnectionTrafficSecrets::Aes256Gcm { key, iv }) } - fn fips(&self) -> bool { + fn fips(&self) -> FipsStatus { super::fips() } } @@ -160,7 +154,7 @@ impl Tls13AeadAlgorithm for Aes128GcmAead { Ok(ConnectionTrafficSecrets::Aes128Gcm { key, iv }) } - fn fips(&self) -> bool { + fn fips(&self) -> FipsStatus { super::fips() } } @@ -203,13 +197,13 @@ struct Tls13MessageDecrypter { impl MessageEncrypter for Tls13MessageEncrypter { fn encrypt( &mut self, - msg: OutboundPlainMessage<'_>, + msg: EncodedMessage>, seq: u64, - ) -> Result { + ) -> Result, Error> { let total_len = self.encrypted_payload_len(msg.payload.len()); - let mut payload = PrefixedPayload::with_capacity(total_len); + let mut payload = OutboundOpaque::with_capacity(total_len); - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).0); + let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).to_array()?); let aad = aead::Aad::from(make_tls13_aad(total_len)); payload.extend_from_chunks(&msg.payload); payload.extend_from_slice(&msg.typ.to_array()); @@ -218,13 +212,13 @@ impl MessageEncrypter for Tls13MessageEncrypter { .seal_in_place_append_tag(nonce, aad, &mut payload) .map_err(|_| Error::EncryptError)?; - Ok(OutboundOpaqueMessage::new( - ContentType::ApplicationData, + Ok(EncodedMessage { + typ: ContentType::ApplicationData, // Note: all TLS 1.3 application data records use TLSv1_2 (0x0303) as the legacy record // protocol version, see https://www.rfc-editor.org/rfc/rfc8446#section-5.1 - ProtocolVersion::TLSv1_2, + version: ProtocolVersion::TLSv1_2, payload, - )) + }) } fn encrypted_payload_len(&self, payload_len: usize) -> usize { @@ -235,15 +229,15 @@ impl MessageEncrypter for Tls13MessageEncrypter { impl MessageDecrypter for Tls13MessageDecrypter { fn decrypt<'a>( &mut self, - mut msg: InboundOpaqueMessage<'a>, + mut msg: EncodedMessage>, seq: u64, - ) -> Result, Error> { + ) -> Result, Error> { let payload = &mut msg.payload; if payload.len() < self.dec_key.algorithm().tag_len() { return Err(Error::DecryptError); } - let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).0); + let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, seq).to_array()?); let aad = aead::Aad::from(make_tls13_aad(payload.len())); let plain_len = self .dec_key @@ -294,7 +288,7 @@ impl Hkdf for RingHkdf { crypto::hmac::Tag::new(hmac::sign(&hmac::Key::new(self.1, key.as_ref()), message).as_ref()) } - fn fips(&self) -> bool { + fn fips(&self) -> FipsStatus { super::fips() } } @@ -334,3 +328,146 @@ impl KeyType for Len { self.0 } } + +#[cfg(test)] +mod tests { + use alloc::vec::Vec; + + use rustls::crypto::tls13::{HkdfUsingHmac, expand}; + + use super::Hkdf; + use crate::hmac; + + struct ByteArray([u8; N]); + + impl From<[u8; N]> for ByteArray { + fn from(array: [u8; N]) -> Self { + Self(array) + } + } + + /// Test cases from appendix A in the RFC, minus cases requiring SHA1. + + #[test] + fn test_case_1() { + let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256); + let ikm = &[0x0b; 22]; + let salt = &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + ]; + let info: &[&[u8]] = &[ + &[0xf0, 0xf1, 0xf2], + &[0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9], + ]; + + let output: ByteArray<42> = expand( + hkdf.extract_from_secret(Some(salt), ikm) + .as_ref(), + info, + ); + + assert_eq!( + &output.0, + &[ + 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, + 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, + 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65 + ] + ); + } + + #[test] + fn test_case_2() { + let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256); + let ikm: Vec = (0x00u8..=0x4f).collect(); + let salt: Vec = (0x60u8..=0xaf).collect(); + let info: Vec = (0xb0u8..=0xff).collect(); + + let output: ByteArray<82> = expand( + hkdf.extract_from_secret(Some(&salt), &ikm) + .as_ref(), + &[&info], + ); + + assert_eq!( + &output.0, + &[ + 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a, + 0x49, 0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c, + 0x19, 0xaf, 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb, + 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8, + 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, + 0x3e, 0x87, 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87 + ] + ); + } + + #[test] + fn test_case_3() { + let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256); + let ikm = &[0x0b; 22]; + let salt = &[]; + let info = &[]; + + let output: ByteArray<42> = expand( + hkdf.extract_from_secret(Some(salt), ikm) + .as_ref(), + info, + ); + + assert_eq!( + &output.0, + &[ + 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, + 0x5a, 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f, + 0x3c, 0x73, 0x8d, 0x2d, 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8 + ] + ); + } + + #[test] + fn test_salt_not_provided() { + // can't use test case 7, because we don't have (or want) SHA1. + // + // this output is generated with cryptography.io: + // + // >>> hkdf.HKDF(algorithm=hashes.SHA384(), length=96, salt=None, info=b"hello").derive(b"\x0b" * 40) + + let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA384); + let ikm = &[0x0b; 40]; + let info = &[&b"hell"[..], &b"o"[..]]; + + let output: ByteArray<96> = expand( + hkdf.extract_from_secret(None, ikm) + .as_ref(), + info, + ); + + assert_eq!( + &output.0, + &[ + 0xd5, 0x45, 0xdd, 0x3a, 0xff, 0x5b, 0x19, 0x46, 0xd4, 0x86, 0xfd, 0xb8, 0xd8, 0x88, + 0x2e, 0xe0, 0x1c, 0xc1, 0xa5, 0x48, 0xb6, 0x05, 0x75, 0xe4, 0xd7, 0x5d, 0x0f, 0x5f, + 0x23, 0x40, 0xee, 0x6c, 0x9e, 0x7c, 0x65, 0xd0, 0xee, 0x79, 0xdb, 0xb2, 0x07, 0x1d, + 0x66, 0xa5, 0x50, 0xc4, 0x8a, 0xa3, 0x93, 0x86, 0x8b, 0x7c, 0x69, 0x41, 0x6b, 0x3e, + 0x61, 0x44, 0x98, 0xb8, 0xc2, 0xfc, 0x82, 0x82, 0xae, 0xcd, 0x46, 0xcf, 0xb1, 0x47, + 0xdc, 0xd0, 0x69, 0x0d, 0x19, 0xad, 0xe6, 0x6c, 0x70, 0xfe, 0x87, 0x92, 0x04, 0xb6, + 0x82, 0x2d, 0x97, 0x7e, 0x46, 0x80, 0x4c, 0xe5, 0x76, 0x72, 0xb4, 0xb8 + ] + ); + } + + #[test] + fn test_output_length_bounds() { + let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256); + let ikm = &[]; + let info = &[]; + + let mut output = [0u8; 32 * 255 + 1]; + assert!( + hkdf.extract_from_secret(None, ikm) + .expand_slice(info, &mut output) + .is_err() + ); + } +} diff --git a/rustls-ring/src/verify.rs b/rustls-ring/src/verify.rs new file mode 100644 index 00000000000..97c66225905 --- /dev/null +++ b/rustls-ring/src/verify.rs @@ -0,0 +1,208 @@ +use pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm, alg_id}; +use ring::signature; + +/// An array of all the verification algorithms exported by this crate. +/// +/// This will be empty if the crate is built without the `ring` and `aws-lc-rs` features. +pub static ALL_VERIFICATION_ALGS: &[&dyn SignatureVerificationAlgorithm] = &[ + ECDSA_P256_SHA256, + ECDSA_P256_SHA384, + ECDSA_P384_SHA256, + ECDSA_P384_SHA384, + ED25519, + RSA_PKCS1_2048_8192_SHA256, + RSA_PKCS1_2048_8192_SHA384, + RSA_PKCS1_2048_8192_SHA512, + RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS, + RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS, + RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS, + RSA_PKCS1_3072_8192_SHA384, + RSA_PSS_2048_8192_SHA256_LEGACY_KEY, + RSA_PSS_2048_8192_SHA384_LEGACY_KEY, + RSA_PSS_2048_8192_SHA512_LEGACY_KEY, +]; + +/// A `SignatureVerificationAlgorithm` implemented using *ring*. +#[derive(Debug)] +struct RingAlgorithm { + public_key_alg_id: AlgorithmIdentifier, + signature_alg_id: AlgorithmIdentifier, + verification_alg: &'static dyn signature::VerificationAlgorithm, +} + +impl SignatureVerificationAlgorithm for RingAlgorithm { + fn public_key_alg_id(&self) -> AlgorithmIdentifier { + self.public_key_alg_id + } + + fn signature_alg_id(&self) -> AlgorithmIdentifier { + self.signature_alg_id + } + + fn verify_signature( + &self, + public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), InvalidSignature> { + signature::UnparsedPublicKey::new(self.verification_alg, public_key) + .verify(message, signature) + .map_err(|_| InvalidSignature) + } +} + +/// ECDSA signatures using the P-256 curve and SHA-256. +pub static ECDSA_P256_SHA256: &dyn SignatureVerificationAlgorithm = &RingAlgorithm { + public_key_alg_id: alg_id::ECDSA_P256, + signature_alg_id: alg_id::ECDSA_SHA256, + verification_alg: &signature::ECDSA_P256_SHA256_ASN1, +}; + +/// ECDSA signatures using the P-256 curve and SHA-384. Deprecated. +pub static ECDSA_P256_SHA384: &dyn SignatureVerificationAlgorithm = &RingAlgorithm { + public_key_alg_id: alg_id::ECDSA_P256, + signature_alg_id: alg_id::ECDSA_SHA384, + verification_alg: &signature::ECDSA_P256_SHA384_ASN1, +}; + +/// ECDSA signatures using the P-384 curve and SHA-256. Deprecated. +pub static ECDSA_P384_SHA256: &dyn SignatureVerificationAlgorithm = &RingAlgorithm { + public_key_alg_id: alg_id::ECDSA_P384, + signature_alg_id: alg_id::ECDSA_SHA256, + verification_alg: &signature::ECDSA_P384_SHA256_ASN1, +}; + +/// ECDSA signatures using the P-384 curve and SHA-384. +pub static ECDSA_P384_SHA384: &dyn SignatureVerificationAlgorithm = &RingAlgorithm { + public_key_alg_id: alg_id::ECDSA_P384, + signature_alg_id: alg_id::ECDSA_SHA384, + verification_alg: &signature::ECDSA_P384_SHA384_ASN1, +}; + +/// RSA PKCS#1 1.5 signatures using SHA-256 for keys of 2048-8192 bits. +pub static RSA_PKCS1_2048_8192_SHA256: &dyn SignatureVerificationAlgorithm = &RingAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PKCS1_SHA256, + verification_alg: &signature::RSA_PKCS1_2048_8192_SHA256, +}; + +/// RSA PKCS#1 1.5 signatures using SHA-384 for keys of 2048-8192 bits. +pub static RSA_PKCS1_2048_8192_SHA384: &dyn SignatureVerificationAlgorithm = &RingAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PKCS1_SHA384, + verification_alg: &signature::RSA_PKCS1_2048_8192_SHA384, +}; + +/// RSA PKCS#1 1.5 signatures using SHA-512 for keys of 2048-8192 bits. +pub static RSA_PKCS1_2048_8192_SHA512: &dyn SignatureVerificationAlgorithm = &RingAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PKCS1_SHA512, + verification_alg: &signature::RSA_PKCS1_2048_8192_SHA512, +}; + +/// RSA PKCS#1 1.5 signatures using SHA-256 for keys of 2048-8192 bits, +/// with illegally absent AlgorithmIdentifier parameters. +/// +/// RFC4055 says on sha256WithRSAEncryption and company: +/// +/// > When any of these four object identifiers appears within an +/// > AlgorithmIdentifier, the parameters MUST be NULL. Implementations +/// > MUST accept the parameters being absent as well as present. +/// +/// This algorithm covers the absent case, [`RSA_PKCS1_2048_8192_SHA256`] covers +/// the present case. +pub static RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm = + &RingAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: AlgorithmIdentifier::from_slice(include_bytes!( + "data/alg-rsa-pkcs1-sha256-absent-params.der" + )), + verification_alg: &signature::RSA_PKCS1_2048_8192_SHA256, + }; + +/// RSA PKCS#1 1.5 signatures using SHA-384 for keys of 2048-8192 bits, +/// with illegally absent AlgorithmIdentifier parameters. +/// +/// RFC4055 says on sha256WithRSAEncryption and company: +/// +/// > When any of these four object identifiers appears within an +/// > AlgorithmIdentifier, the parameters MUST be NULL. Implementations +/// > MUST accept the parameters being absent as well as present. +/// +/// This algorithm covers the absent case, [`RSA_PKCS1_2048_8192_SHA384`] covers +/// the present case. +pub static RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm = + &RingAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: AlgorithmIdentifier::from_slice(include_bytes!( + "data/alg-rsa-pkcs1-sha384-absent-params.der" + )), + verification_alg: &signature::RSA_PKCS1_2048_8192_SHA384, + }; + +/// RSA PKCS#1 1.5 signatures using SHA-512 for keys of 2048-8192 bits, +/// with illegally absent AlgorithmIdentifier parameters. +/// +/// RFC4055 says on sha256WithRSAEncryption and company: +/// +/// > When any of these four object identifiers appears within an +/// > AlgorithmIdentifier, the parameters MUST be NULL. Implementations +/// > MUST accept the parameters being absent as well as present. +/// +/// This algorithm covers the absent case, [`RSA_PKCS1_2048_8192_SHA512`] covers +/// the present case. +pub static RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm = + &RingAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: AlgorithmIdentifier::from_slice(include_bytes!( + "data/alg-rsa-pkcs1-sha512-absent-params.der" + )), + verification_alg: &signature::RSA_PKCS1_2048_8192_SHA512, + }; + +/// RSA PKCS#1 1.5 signatures using SHA-384 for keys of 3072-8192 bits. +pub static RSA_PKCS1_3072_8192_SHA384: &dyn SignatureVerificationAlgorithm = &RingAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PKCS1_SHA384, + verification_alg: &signature::RSA_PKCS1_3072_8192_SHA384, +}; + +/// RSA PSS signatures using SHA-256 for keys of 2048-8192 bits and of +/// type rsaEncryption; see [RFC 4055 Section 1.2]. +/// +/// [RFC 4055 Section 1.2]: https://tools.ietf.org/html/rfc4055#section-1.2 +pub static RSA_PSS_2048_8192_SHA256_LEGACY_KEY: &dyn SignatureVerificationAlgorithm = + &RingAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PSS_SHA256, + verification_alg: &signature::RSA_PSS_2048_8192_SHA256, + }; + +/// RSA PSS signatures using SHA-384 for keys of 2048-8192 bits and of +/// type rsaEncryption; see [RFC 4055 Section 1.2]. +/// +/// [RFC 4055 Section 1.2]: https://tools.ietf.org/html/rfc4055#section-1.2 +pub static RSA_PSS_2048_8192_SHA384_LEGACY_KEY: &dyn SignatureVerificationAlgorithm = + &RingAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PSS_SHA384, + verification_alg: &signature::RSA_PSS_2048_8192_SHA384, + }; + +/// RSA PSS signatures using SHA-512 for keys of 2048-8192 bits and of +/// type rsaEncryption; see [RFC 4055 Section 1.2]. +/// +/// [RFC 4055 Section 1.2]: https://tools.ietf.org/html/rfc4055#section-1.2 +pub static RSA_PSS_2048_8192_SHA512_LEGACY_KEY: &dyn SignatureVerificationAlgorithm = + &RingAlgorithm { + public_key_alg_id: alg_id::RSA_ENCRYPTION, + signature_alg_id: alg_id::RSA_PSS_SHA512, + verification_alg: &signature::RSA_PSS_2048_8192_SHA512, + }; + +/// ED25519 signatures according to RFC 8410 +pub static ED25519: &dyn SignatureVerificationAlgorithm = &RingAlgorithm { + public_key_alg_id: alg_id::ED25519, + signature_alg_id: alg_id::ED25519, + verification_alg: &signature::ED25519, +}; diff --git a/rustls-test/Cargo.toml b/rustls-test/Cargo.toml new file mode 100644 index 00000000000..2f87bd73176 --- /dev/null +++ b/rustls-test/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "rustls-test" +version = "0.1.0" +edition = "2021" +publish = false + +[features] +default = ["aws-lc-rs", "brotli", "log", "ring", "zlib"] +aws-lc-rs = ["dep:rustls-aws-lc-rs"] +brotli = ["rustls/brotli"] +fips = ["rustls-aws-lc-rs/fips"] +log = ["rustls/log", "rustls-util/log"] +ring = ["dep:rustls-ring"] +zlib = ["rustls/zlib"] + +[dependencies] +rustls = { path = "../rustls", default-features = false, features = ["webpki"] } +rustls-aws-lc-rs = { path = "../rustls-aws-lc-rs", features = ["std"], optional = true } +rustls-ring = { path = "../rustls-ring", optional = true } +rustls-util = { path = "../rustls-util", default-features = false } + +[dev-dependencies] +env_logger = { workspace = true } +log = { workspace = true } +macro_rules_attribute = { workspace = true } +num-bigint = { workspace = true } +pki-types = { workspace = true } +x509-parser = { workspace = true } +webpki = { workspace = true } +webpki-roots = { workspace = true } + +[lints] +workspace = true diff --git a/rustls-test/benches/data/cert-arstechnica.0.der b/rustls-test/benches/data/cert-arstechnica.0.der new file mode 100644 index 00000000000..5a68def3600 Binary files /dev/null and b/rustls-test/benches/data/cert-arstechnica.0.der differ diff --git a/rustls-test/benches/data/cert-arstechnica.1.der b/rustls-test/benches/data/cert-arstechnica.1.der new file mode 100644 index 00000000000..66c211b49d9 Binary files /dev/null and b/rustls-test/benches/data/cert-arstechnica.1.der differ diff --git a/rustls/src/testdata/cert-arstechnica.2.der b/rustls-test/benches/data/cert-arstechnica.2.der similarity index 100% rename from rustls/src/testdata/cert-arstechnica.2.der rename to rustls-test/benches/data/cert-arstechnica.2.der diff --git a/rustls-test/benches/data/cert-duckduckgo.0.der b/rustls-test/benches/data/cert-duckduckgo.0.der new file mode 100644 index 00000000000..9a64c211f1c Binary files /dev/null and b/rustls-test/benches/data/cert-duckduckgo.0.der differ diff --git a/rustls-test/benches/data/cert-duckduckgo.1.der b/rustls-test/benches/data/cert-duckduckgo.1.der new file mode 100644 index 00000000000..b2b371600ce Binary files /dev/null and b/rustls-test/benches/data/cert-duckduckgo.1.der differ diff --git a/rustls-test/benches/data/cert-duckduckgo.2.der b/rustls-test/benches/data/cert-duckduckgo.2.der new file mode 100644 index 00000000000..1e927a7afe0 Binary files /dev/null and b/rustls-test/benches/data/cert-duckduckgo.2.der differ diff --git a/rustls-test/benches/data/cert-github.0.der b/rustls-test/benches/data/cert-github.0.der new file mode 100644 index 00000000000..ee00f70e5e8 Binary files /dev/null and b/rustls-test/benches/data/cert-github.0.der differ diff --git a/rustls-test/benches/data/cert-github.1.der b/rustls-test/benches/data/cert-github.1.der new file mode 100644 index 00000000000..bd4e397246f Binary files /dev/null and b/rustls-test/benches/data/cert-github.1.der differ diff --git a/rustls-test/benches/data/cert-github.2.der b/rustls-test/benches/data/cert-github.2.der new file mode 100644 index 00000000000..8a65485d0bb Binary files /dev/null and b/rustls-test/benches/data/cert-github.2.der differ diff --git a/rustls-test/benches/data/cert-google.0.der b/rustls-test/benches/data/cert-google.0.der new file mode 100644 index 00000000000..f706cadc2dd Binary files /dev/null and b/rustls-test/benches/data/cert-google.0.der differ diff --git a/rustls-test/benches/data/cert-google.1.der b/rustls-test/benches/data/cert-google.1.der new file mode 100644 index 00000000000..97276816342 Binary files /dev/null and b/rustls-test/benches/data/cert-google.1.der differ diff --git a/rustls/src/testdata/cert-google.2.der b/rustls-test/benches/data/cert-google.2.der similarity index 100% rename from rustls/src/testdata/cert-google.2.der rename to rustls-test/benches/data/cert-google.2.der diff --git a/rustls-test/benches/data/cert-hn.0.der b/rustls-test/benches/data/cert-hn.0.der new file mode 100644 index 00000000000..686d87caaa4 Binary files /dev/null and b/rustls-test/benches/data/cert-hn.0.der differ diff --git a/rustls-test/benches/data/cert-hn.1.der b/rustls-test/benches/data/cert-hn.1.der new file mode 100644 index 00000000000..b187e36c701 Binary files /dev/null and b/rustls-test/benches/data/cert-hn.1.der differ diff --git a/rustls-test/benches/data/cert-reddit.0.der b/rustls-test/benches/data/cert-reddit.0.der new file mode 100644 index 00000000000..40363243ee8 Binary files /dev/null and b/rustls-test/benches/data/cert-reddit.0.der differ diff --git a/rustls-test/benches/data/cert-reddit.1.der b/rustls-test/benches/data/cert-reddit.1.der new file mode 100644 index 00000000000..b2b371600ce Binary files /dev/null and b/rustls-test/benches/data/cert-reddit.1.der differ diff --git a/rustls-test/benches/data/cert-rustlang.0.der b/rustls-test/benches/data/cert-rustlang.0.der new file mode 100644 index 00000000000..d8cef415ec8 Binary files /dev/null and b/rustls-test/benches/data/cert-rustlang.0.der differ diff --git a/rustls-test/benches/data/cert-rustlang.1.der b/rustls-test/benches/data/cert-rustlang.1.der new file mode 100644 index 00000000000..f583b217ae5 Binary files /dev/null and b/rustls-test/benches/data/cert-rustlang.1.der differ diff --git a/rustls-test/benches/data/cert-servo.0.der b/rustls-test/benches/data/cert-servo.0.der new file mode 100644 index 00000000000..b807621035b Binary files /dev/null and b/rustls-test/benches/data/cert-servo.0.der differ diff --git a/rustls-test/benches/data/cert-servo.1.der b/rustls-test/benches/data/cert-servo.1.der new file mode 100644 index 00000000000..95399615ce6 Binary files /dev/null and b/rustls-test/benches/data/cert-servo.1.der differ diff --git a/rustls-test/benches/data/cert-servo.2.der b/rustls-test/benches/data/cert-servo.2.der new file mode 100644 index 00000000000..ad98b4c5085 Binary files /dev/null and b/rustls-test/benches/data/cert-servo.2.der differ diff --git a/rustls-test/benches/data/cert-stackoverflow.0.der b/rustls-test/benches/data/cert-stackoverflow.0.der new file mode 100644 index 00000000000..a0a73350878 Binary files /dev/null and b/rustls-test/benches/data/cert-stackoverflow.0.der differ diff --git a/rustls-test/benches/data/cert-stackoverflow.1.der b/rustls-test/benches/data/cert-stackoverflow.1.der new file mode 100644 index 00000000000..b187e36c701 Binary files /dev/null and b/rustls-test/benches/data/cert-stackoverflow.1.der differ diff --git a/rustls-test/benches/data/cert-twitter.0.der b/rustls-test/benches/data/cert-twitter.0.der new file mode 100644 index 00000000000..c32a5a64f5c Binary files /dev/null and b/rustls-test/benches/data/cert-twitter.0.der differ diff --git a/rustls-test/benches/data/cert-twitter.1.der b/rustls-test/benches/data/cert-twitter.1.der new file mode 100644 index 00000000000..6d4977b41ac Binary files /dev/null and b/rustls-test/benches/data/cert-twitter.1.der differ diff --git a/rustls-test/benches/data/cert-wapo.0.der b/rustls-test/benches/data/cert-wapo.0.der new file mode 100644 index 00000000000..fc830e7fd3d Binary files /dev/null and b/rustls-test/benches/data/cert-wapo.0.der differ diff --git a/rustls-test/benches/data/cert-wapo.1.der b/rustls-test/benches/data/cert-wapo.1.der new file mode 100644 index 00000000000..a0e92647b7b Binary files /dev/null and b/rustls-test/benches/data/cert-wapo.1.der differ diff --git a/rustls-test/benches/data/cert-wapo.2.der b/rustls-test/benches/data/cert-wapo.2.der new file mode 100644 index 00000000000..8a65485d0bb Binary files /dev/null and b/rustls-test/benches/data/cert-wapo.2.der differ diff --git a/rustls-test/benches/data/cert-wikipedia.0.der b/rustls-test/benches/data/cert-wikipedia.0.der new file mode 100644 index 00000000000..20997a0e63a Binary files /dev/null and b/rustls-test/benches/data/cert-wikipedia.0.der differ diff --git a/rustls-test/benches/data/cert-wikipedia.1.der b/rustls-test/benches/data/cert-wikipedia.1.der new file mode 100644 index 00000000000..b187e36c701 Binary files /dev/null and b/rustls-test/benches/data/cert-wikipedia.1.der differ diff --git a/rustls-test/benches/verifybench.rs b/rustls-test/benches/verifybench.rs new file mode 100644 index 00000000000..8a5c8cda83c --- /dev/null +++ b/rustls-test/benches/verifybench.rs @@ -0,0 +1,278 @@ +//! This program does benchmarking of the functions in verify.rs, +//! that do certificate chain validation and signature verification. +//! +//! This uses captured certificate chains for a selection of websites, +//! saved in `data/cert-{SITE}.{I}.der`. +//! +//! To update that data: +//! +//! - delete all `data/cert-*.der`. +//! - run the `admin/capture-certdata` script. +//! - update the verification timestamp near the bottom of this file +//! to the current time. +//! - where a website's chain length changed, reflect that in the list +//! of certificate files below. +//! +//! This does not need to be done regularly; because the verification +//! time is fixed, it only needs doing if a root certificate is +//! distrusted. + +#![cfg(bench)] +#![cfg_attr(bench, feature(test))] + +extern crate test; + +use core::time::Duration; +use std::sync::Arc; + +use pki_types::{CertificateDer, ServerName, UnixTime}; +use rustls::RootCertStore; +use rustls::client::WebPkiServerVerifier; +use rustls::client::danger::{ServerIdentity, ServerVerifier}; +use rustls::crypto::{CertificateIdentity, CryptoProvider, Identity}; + +/// Instantiate the given benchmark functions once for each built-in provider. +/// +/// The selected provider module is bound as `provider`; you can rely on this +/// having the union of the items common to the `crypto::ring` and +/// `crypto::aws_lc_rs` modules. +#[cfg(bench)] +macro_rules! bench_for_each_provider { + ($($tt:tt)+) => { + #[cfg(feature = "ring")] + mod bench_with_ring { + use rustls_ring as provider; + #[allow(unused_imports)] + use super::*; + $($tt)+ + } + + #[cfg(feature = "aws-lc-rs")] + mod bench_with_aws_lc_rs { + use rustls_aws_lc_rs as provider; + #[allow(unused_imports)] + use super::*; + $($tt)+ + } + }; +} + +/* +#[cfg(all(test, bench))] +#[macro_rules_attribute::apply(bench_for_each_provider)] +mod benchmarks { + #[bench] + fn bench_each_provider(b: &mut test::Bencher) { + b.iter(|| super::provider::DEFAULT_PROVIDER); + } +}*/ + +#[macro_rules_attribute::apply(bench_for_each_provider)] +mod benchmarks { + use super::{Context, provider}; + + #[bench] + fn reddit_cert(b: &mut test::Bencher) { + let ctx = Context::new( + &provider::DEFAULT_PROVIDER, + "reddit.com", + &[ + include_bytes!("data/cert-reddit.0.der"), + include_bytes!("data/cert-reddit.1.der"), + ], + ); + b.iter(|| ctx.verify_once()); + } + + #[bench] + fn github_cert(b: &mut test::Bencher) { + let ctx = Context::new( + &provider::DEFAULT_PROVIDER, + "github.com", + &[ + include_bytes!("data/cert-github.0.der"), + include_bytes!("data/cert-github.1.der"), + include_bytes!("data/cert-github.2.der"), + ], + ); + b.iter(|| ctx.verify_once()); + } + + #[bench] + fn arstechnica_cert(b: &mut test::Bencher) { + let ctx = Context::new( + &provider::DEFAULT_PROVIDER, + "arstechnica.com", + &[ + include_bytes!("data/cert-arstechnica.0.der"), + include_bytes!("data/cert-arstechnica.1.der"), + include_bytes!("data/cert-arstechnica.2.der"), + ], + ); + b.iter(|| ctx.verify_once()); + } + + #[bench] + fn servo_cert(b: &mut test::Bencher) { + let ctx = Context::new( + &provider::DEFAULT_PROVIDER, + "servo.org", + &[ + include_bytes!("data/cert-servo.0.der"), + include_bytes!("data/cert-servo.1.der"), + include_bytes!("data/cert-servo.2.der"), + ], + ); + b.iter(|| ctx.verify_once()); + } + + #[bench] + fn twitter_cert(b: &mut test::Bencher) { + let ctx = Context::new( + &provider::DEFAULT_PROVIDER, + "twitter.com", + &[ + include_bytes!("data/cert-twitter.0.der"), + include_bytes!("data/cert-twitter.1.der"), + ], + ); + b.iter(|| ctx.verify_once()); + } + + #[bench] + fn wikipedia_cert(b: &mut test::Bencher) { + let ctx = Context::new( + &provider::DEFAULT_PROVIDER, + "wikipedia.org", + &[ + include_bytes!("data/cert-wikipedia.0.der"), + include_bytes!("data/cert-wikipedia.1.der"), + ], + ); + b.iter(|| ctx.verify_once()); + } + + #[bench] + fn google_cert(b: &mut test::Bencher) { + let ctx = Context::new( + &provider::DEFAULT_PROVIDER, + "www.google.com", + &[ + include_bytes!("data/cert-google.0.der"), + include_bytes!("data/cert-google.1.der"), + include_bytes!("data/cert-google.2.der"), + ], + ); + b.iter(|| ctx.verify_once()); + } + + #[bench] + fn hn_cert(b: &mut test::Bencher) { + let ctx = Context::new( + &provider::DEFAULT_PROVIDER, + "news.ycombinator.com", + &[ + include_bytes!("data/cert-hn.0.der"), + include_bytes!("data/cert-hn.1.der"), + ], + ); + b.iter(|| ctx.verify_once()); + } + + #[bench] + fn stackoverflow_cert(b: &mut test::Bencher) { + let ctx = Context::new( + &provider::DEFAULT_PROVIDER, + "stackoverflow.com", + &[ + include_bytes!("data/cert-stackoverflow.0.der"), + include_bytes!("data/cert-stackoverflow.1.der"), + ], + ); + b.iter(|| ctx.verify_once()); + } + + #[bench] + fn duckduckgo_cert(b: &mut test::Bencher) { + let ctx = Context::new( + &provider::DEFAULT_PROVIDER, + "duckduckgo.com", + &[ + include_bytes!("data/cert-duckduckgo.0.der"), + include_bytes!("data/cert-duckduckgo.1.der"), + include_bytes!("data/cert-duckduckgo.2.der"), + ], + ); + b.iter(|| ctx.verify_once()); + } + + #[bench] + fn rustlang_cert(b: &mut test::Bencher) { + let ctx = Context::new( + &provider::DEFAULT_PROVIDER, + "www.rust-lang.org", + &[ + include_bytes!("data/cert-rustlang.0.der"), + include_bytes!("data/cert-rustlang.1.der"), + ], + ); + b.iter(|| ctx.verify_once()); + } + + #[bench] + fn wapo_cert(b: &mut test::Bencher) { + let ctx = Context::new( + &provider::DEFAULT_PROVIDER, + "www.washingtonpost.com", + &[ + include_bytes!("data/cert-wapo.0.der"), + include_bytes!("data/cert-wapo.1.der"), + ], + ); + b.iter(|| ctx.verify_once()); + } +} + +struct Context { + server_name: ServerName<'static>, + chain: Vec>, + now: UnixTime, + verifier: WebPkiServerVerifier, +} + +impl Context { + fn new(provider: &CryptoProvider, domain: &'static str, certs: &[&'static [u8]]) -> Self { + let mut roots = RootCertStore::empty(); + roots.extend( + webpki_roots::TLS_SERVER_ROOTS + .iter() + .cloned(), + ); + Self { + server_name: domain.try_into().unwrap(), + chain: certs + .iter() + .copied() + .map(|bytes| CertificateDer::from(bytes.to_vec())) + .collect(), + // Feb 4, 2026, around 11:41 UTC + now: UnixTime::since_unix_epoch(Duration::from_secs(1_770_205_316)), + verifier: WebPkiServerVerifier::builder(Arc::new(roots), provider) + .build() + .unwrap(), + } + } + + fn verify_once(&self) { + self.verifier + .verify_identity(&ServerIdentity::new( + &Identity::X509(CertificateIdentity::new( + self.chain[0].clone(), + self.chain[1..].to_vec(), + )), + &self.server_name, + self.now, + )) + .unwrap(); + } +} diff --git a/rustls-test/src/lib.rs b/rustls-test/src/lib.rs new file mode 100644 index 00000000000..c99d3107336 --- /dev/null +++ b/rustls-test/src/lib.rs @@ -0,0 +1,2270 @@ +use core::hash::Hasher; +use core::{fmt, mem}; +use std::borrow::Cow; +use std::io; +use std::sync::{Arc, Mutex, OnceLock}; + +use rustls::client::danger::{ + HandshakeSignatureValid, PeerVerified, ServerIdentity, ServerVerifier, +}; +use rustls::client::{ + ClientSessionKey, ServerVerifierBuilder, Tls13Session, WantsClientCert, WebPkiServerVerifier, +}; +use rustls::crypto::cipher::{ + EncodedMessage, InboundOpaque, MessageDecrypter, MessageEncrypter, Payload, +}; +use rustls::crypto::kx::{NamedGroup, SupportedKxGroup}; +use rustls::crypto::{ + CipherSuite, Credentials, CryptoProvider, Identity, InconsistentKeys, SelectedCredential, + SignatureScheme, SigningKey, SingleCredential, WebPkiSupportedAlgorithms, + verify_tls13_signature, +}; +use rustls::enums::{CertificateType, ContentType, ProtocolVersion}; +use rustls::error::{CertificateError, Error}; +use rustls::pki_types::pem::PemObject; +use rustls::pki_types::{ + CertificateDer, CertificateRevocationListDer, DnsName, PrivateKeyDer, PrivatePkcs8KeyDer, + ServerName, SubjectPublicKeyInfoDer, +}; +use rustls::server::danger::{ClientIdentity, ClientVerifier, SignatureVerificationInput}; +use rustls::server::{ + ClientHello, ClientVerifierBuilder, ServerCredentialResolver, WebPkiClientVerifier, +}; +use rustls::{ + ClientConfig, ClientConnection, ConfigBuilder, Connection, ConnectionTrafficSecrets, + DistinguishedName, RootCertStore, ServerConfig, ServerConnection, SupportedCipherSuite, + WantsVerifier, +}; + +macro_rules! embed_files { + ( + $( + ($name:ident, $keytype:expr, $path:expr); + )+ + ) => { + $( + const $name: &'static [u8] = include_bytes!( + concat!("../../test-ca/", $keytype, "/", $path)); + )+ + + pub fn bytes_for(keytype: &str, path: &str) -> &'static [u8] { + match (keytype, path) { + $( + ($keytype, $path) => $name, + )+ + _ => panic!("unknown keytype {} with path {}", keytype, path), + } + } + } +} + +embed_files! { + (ECDSA_P256_END_PEM_SPKI, "ecdsa-p256", "end.spki.pem"); + (ECDSA_P256_CLIENT_PEM_SPKI, "ecdsa-p256", "client.spki.pem"); + (ECDSA_P256_CA_CERT, "ecdsa-p256", "ca.cert"); + (ECDSA_P256_CA_DER, "ecdsa-p256", "ca.der"); + (ECDSA_P256_CA_KEY, "ecdsa-p256", "ca.key"); + (ECDSA_P256_CLIENT_CERT, "ecdsa-p256", "client.cert"); + (ECDSA_P256_CLIENT_CHAIN, "ecdsa-p256", "client.chain"); + (ECDSA_P256_CLIENT_FULLCHAIN, "ecdsa-p256", "client.fullchain"); + (ECDSA_P256_CLIENT_KEY, "ecdsa-p256", "client.key"); + (ECDSA_P256_END_CRL_PEM, "ecdsa-p256", "end.revoked.crl.pem"); + (ECDSA_P256_CLIENT_CRL_PEM, "ecdsa-p256", "client.revoked.crl.pem"); + (ECDSA_P256_INTERMEDIATE_CRL_PEM, "ecdsa-p256", "inter.revoked.crl.pem"); + (ECDSA_P256_EXPIRED_CRL_PEM, "ecdsa-p256", "end.expired.crl.pem"); + (ECDSA_P256_END_CERT, "ecdsa-p256", "end.cert"); + (ECDSA_P256_END_CHAIN, "ecdsa-p256", "end.chain"); + (ECDSA_P256_END_FULLCHAIN, "ecdsa-p256", "end.fullchain"); + (ECDSA_P256_END_KEY, "ecdsa-p256", "end.key"); + (ECDSA_P256_INTER_CERT, "ecdsa-p256", "inter.cert"); + (ECDSA_P256_INTER_KEY, "ecdsa-p256", "inter.key"); + + (ECDSA_P384_END_PEM_SPKI, "ecdsa-p384", "end.spki.pem"); + (ECDSA_P384_CLIENT_PEM_SPKI, "ecdsa-p384", "client.spki.pem"); + (ECDSA_P384_CA_CERT, "ecdsa-p384", "ca.cert"); + (ECDSA_P384_CA_DER, "ecdsa-p384", "ca.der"); + (ECDSA_P384_CA_KEY, "ecdsa-p384", "ca.key"); + (ECDSA_P384_CLIENT_CERT, "ecdsa-p384", "client.cert"); + (ECDSA_P384_CLIENT_CHAIN, "ecdsa-p384", "client.chain"); + (ECDSA_P384_CLIENT_FULLCHAIN, "ecdsa-p384", "client.fullchain"); + (ECDSA_P384_CLIENT_KEY, "ecdsa-p384", "client.key"); + (ECDSA_P384_END_CRL_PEM, "ecdsa-p384", "end.revoked.crl.pem"); + (ECDSA_P384_CLIENT_CRL_PEM, "ecdsa-p384", "client.revoked.crl.pem"); + (ECDSA_P384_INTERMEDIATE_CRL_PEM, "ecdsa-p384", "inter.revoked.crl.pem"); + (ECDSA_P384_EXPIRED_CRL_PEM, "ecdsa-p384", "end.expired.crl.pem"); + (ECDSA_P384_END_CERT, "ecdsa-p384", "end.cert"); + (ECDSA_P384_END_CHAIN, "ecdsa-p384", "end.chain"); + (ECDSA_P384_END_FULLCHAIN, "ecdsa-p384", "end.fullchain"); + (ECDSA_P384_END_KEY, "ecdsa-p384", "end.key"); + (ECDSA_P384_INTER_CERT, "ecdsa-p384", "inter.cert"); + (ECDSA_P384_INTER_KEY, "ecdsa-p384", "inter.key"); + + (ECDSA_P521_END_PEM_SPKI, "ecdsa-p521", "end.spki.pem"); + (ECDSA_P521_CLIENT_PEM_SPKI, "ecdsa-p521", "client.spki.pem"); + (ECDSA_P521_CA_CERT, "ecdsa-p521", "ca.cert"); + (ECDSA_P521_CA_DER, "ecdsa-p521", "ca.der"); + (ECDSA_P521_CA_KEY, "ecdsa-p521", "ca.key"); + (ECDSA_P521_CLIENT_CERT, "ecdsa-p521", "client.cert"); + (ECDSA_P521_CLIENT_CHAIN, "ecdsa-p521", "client.chain"); + (ECDSA_P521_CLIENT_FULLCHAIN, "ecdsa-p521", "client.fullchain"); + (ECDSA_P521_CLIENT_KEY, "ecdsa-p521", "client.key"); + (ECDSA_P521_END_CRL_PEM, "ecdsa-p521", "end.revoked.crl.pem"); + (ECDSA_P521_CLIENT_CRL_PEM, "ecdsa-p521", "client.revoked.crl.pem"); + (ECDSA_P521_INTERMEDIATE_CRL_PEM, "ecdsa-p521", "inter.revoked.crl.pem"); + (ECDSA_P521_EXPIRED_CRL_PEM, "ecdsa-p521", "end.expired.crl.pem"); + (ECDSA_P521_END_CERT, "ecdsa-p521", "end.cert"); + (ECDSA_P521_END_CHAIN, "ecdsa-p521", "end.chain"); + (ECDSA_P521_END_FULLCHAIN, "ecdsa-p521", "end.fullchain"); + (ECDSA_P521_END_KEY, "ecdsa-p521", "end.key"); + (ECDSA_P521_INTER_CERT, "ecdsa-p521", "inter.cert"); + (ECDSA_P521_INTER_KEY, "ecdsa-p521", "inter.key"); + + (EDDSA_END_PEM_SPKI, "eddsa", "end.spki.pem"); + (EDDSA_CLIENT_PEM_SPKI, "eddsa", "client.spki.pem"); + (EDDSA_CA_CERT, "eddsa", "ca.cert"); + (EDDSA_CA_DER, "eddsa", "ca.der"); + (EDDSA_CA_KEY, "eddsa", "ca.key"); + (EDDSA_CLIENT_CERT, "eddsa", "client.cert"); + (EDDSA_CLIENT_CHAIN, "eddsa", "client.chain"); + (EDDSA_CLIENT_FULLCHAIN, "eddsa", "client.fullchain"); + (EDDSA_CLIENT_KEY, "eddsa", "client.key"); + (EDDSA_END_CRL_PEM, "eddsa", "end.revoked.crl.pem"); + (EDDSA_CLIENT_CRL_PEM, "eddsa", "client.revoked.crl.pem"); + (EDDSA_INTERMEDIATE_CRL_PEM, "eddsa", "inter.revoked.crl.pem"); + (EDDSA_EXPIRED_CRL_PEM, "eddsa", "end.expired.crl.pem"); + (EDDSA_END_CERT, "eddsa", "end.cert"); + (EDDSA_END_CHAIN, "eddsa", "end.chain"); + (EDDSA_END_FULLCHAIN, "eddsa", "end.fullchain"); + (EDDSA_END_KEY, "eddsa", "end.key"); + (EDDSA_INTER_CERT, "eddsa", "inter.cert"); + (EDDSA_INTER_KEY, "eddsa", "inter.key"); + + (RSA_2048_END_PEM_SPKI, "rsa-2048", "end.spki.pem"); + (RSA_2048_CLIENT_PEM_SPKI, "rsa-2048", "client.spki.pem"); + (RSA_2048_CA_CERT, "rsa-2048", "ca.cert"); + (RSA_2048_CA_DER, "rsa-2048", "ca.der"); + (RSA_2048_CA_KEY, "rsa-2048", "ca.key"); + (RSA_2048_CLIENT_CERT, "rsa-2048", "client.cert"); + (RSA_2048_CLIENT_CHAIN, "rsa-2048", "client.chain"); + (RSA_2048_CLIENT_FULLCHAIN, "rsa-2048", "client.fullchain"); + (RSA_2048_CLIENT_KEY, "rsa-2048", "client.key"); + (RSA_2048_END_CRL_PEM, "rsa-2048", "end.revoked.crl.pem"); + (RSA_2048_CLIENT_CRL_PEM, "rsa-2048", "client.revoked.crl.pem"); + (RSA_2048_INTERMEDIATE_CRL_PEM, "rsa-2048", "inter.revoked.crl.pem"); + (RSA_2048_EXPIRED_CRL_PEM, "rsa-2048", "end.expired.crl.pem"); + (RSA_2048_END_CERT, "rsa-2048", "end.cert"); + (RSA_2048_END_CHAIN, "rsa-2048", "end.chain"); + (RSA_2048_END_FULLCHAIN, "rsa-2048", "end.fullchain"); + (RSA_2048_END_KEY, "rsa-2048", "end.key"); + (RSA_2048_INTER_CERT, "rsa-2048", "inter.cert"); + (RSA_2048_INTER_KEY, "rsa-2048", "inter.key"); + + (RSA_3072_END_PEM_SPKI, "rsa-3072", "end.spki.pem"); + (RSA_3072_CLIENT_PEM_SPKI, "rsa-3072", "client.spki.pem"); + (RSA_3072_CA_CERT, "rsa-3072", "ca.cert"); + (RSA_3072_CA_DER, "rsa-3072", "ca.der"); + (RSA_3072_CA_KEY, "rsa-3072", "ca.key"); + (RSA_3072_CLIENT_CERT, "rsa-3072", "client.cert"); + (RSA_3072_CLIENT_CHAIN, "rsa-3072", "client.chain"); + (RSA_3072_CLIENT_FULLCHAIN, "rsa-3072", "client.fullchain"); + (RSA_3072_CLIENT_KEY, "rsa-3072", "client.key"); + (RSA_3072_END_CRL_PEM, "rsa-3072", "end.revoked.crl.pem"); + (RSA_3072_CLIENT_CRL_PEM, "rsa-3072", "client.revoked.crl.pem"); + (RSA_3072_INTERMEDIATE_CRL_PEM, "rsa-3072", "inter.revoked.crl.pem"); + (RSA_3072_EXPIRED_CRL_PEM, "rsa-3072", "end.expired.crl.pem"); + (RSA_3072_END_CERT, "rsa-3072", "end.cert"); + (RSA_3072_END_CHAIN, "rsa-3072", "end.chain"); + (RSA_3072_END_FULLCHAIN, "rsa-3072", "end.fullchain"); + (RSA_3072_END_KEY, "rsa-3072", "end.key"); + (RSA_3072_INTER_CERT, "rsa-3072", "inter.cert"); + (RSA_3072_INTER_KEY, "rsa-3072", "inter.key"); + + (RSA_4096_END_PEM_SPKI, "rsa-4096", "end.spki.pem"); + (RSA_4096_CLIENT_PEM_SPKI, "rsa-4096", "client.spki.pem"); + (RSA_4096_CA_CERT, "rsa-4096", "ca.cert"); + (RSA_4096_CA_DER, "rsa-4096", "ca.der"); + (RSA_4096_CA_KEY, "rsa-4096", "ca.key"); + (RSA_4096_CLIENT_CERT, "rsa-4096", "client.cert"); + (RSA_4096_CLIENT_CHAIN, "rsa-4096", "client.chain"); + (RSA_4096_CLIENT_FULLCHAIN, "rsa-4096", "client.fullchain"); + (RSA_4096_CLIENT_KEY, "rsa-4096", "client.key"); + (RSA_4096_END_CRL_PEM, "rsa-4096", "end.revoked.crl.pem"); + (RSA_4096_CLIENT_CRL_PEM, "rsa-4096", "client.revoked.crl.pem"); + (RSA_4096_INTERMEDIATE_CRL_PEM, "rsa-4096", "inter.revoked.crl.pem"); + (RSA_4096_EXPIRED_CRL_PEM, "rsa-4096", "end.expired.crl.pem"); + (RSA_4096_END_CERT, "rsa-4096", "end.cert"); + (RSA_4096_END_CHAIN, "rsa-4096", "end.chain"); + (RSA_4096_END_FULLCHAIN, "rsa-4096", "end.fullchain"); + (RSA_4096_END_KEY, "rsa-4096", "end.key"); + (RSA_4096_INTER_CERT, "rsa-4096", "inter.cert"); + (RSA_4096_INTER_KEY, "rsa-4096", "inter.key"); +} + +pub fn transfer(left: &mut impl Connection, right: &mut impl Connection) -> usize { + let mut buf = [0u8; 262144]; + let mut total = 0; + + while left.wants_write() { + let sz = { + let into_buf: &mut dyn io::Write = &mut &mut buf[..]; + left.write_tls(into_buf).unwrap() + }; + total += sz; + if sz == 0 { + return total; + } + + let mut offs = 0; + loop { + let from_buf: &mut dyn io::Read = &mut &buf[offs..sz]; + offs += right.read_tls(from_buf).unwrap(); + if sz == offs { + break; + } + } + } + + total +} + +pub fn transfer_eof(conn: &mut impl Connection) { + let empty_buf = [0u8; 0]; + let empty_cursor: &mut dyn io::Read = &mut &empty_buf[..]; + let sz = conn.read_tls(empty_cursor).unwrap(); + assert_eq!(sz, 0); +} + +pub enum Altered { + /// message has been edited in-place (or is unchanged) + InPlace, + /// send these raw bytes instead of the message. + Raw(Vec), +} + +pub fn transfer_altered( + left: &mut impl Connection, + filter: F, + right: &mut impl Connection, +) -> usize +where + F: Fn(&mut EncodedMessage>) -> Altered, +{ + let mut buf = [0u8; 262144]; + let mut total = 0; + + while left.wants_write() { + let sz = { + let into_buf: &mut dyn io::Write = &mut &mut buf[..]; + left.write_tls(into_buf).unwrap() + }; + total += sz; + if sz == 0 { + return total; + } + + let mut offset = 0; + while offset < sz { + assert!( + offset + 5 <= sz, + "incomplete TLS record header at offset {offset}" + ); + + let typ = ContentType::from(buf[offset]); + let version = + ProtocolVersion::from(u16::from_be_bytes([buf[offset + 1], buf[offset + 2]])); + let payload_len = u16::from_be_bytes([buf[offset + 3], buf[offset + 4]]) as usize; + + assert!( + offset + 5 + payload_len <= sz, + "incomplete TLS record payload at offset {offset}" + ); + + let payload = buf[offset + 5..offset + 5 + payload_len].to_vec(); + offset += 5 + payload_len; + + let mut encoded = EncodedMessage { + typ, + version, + payload, + }; + + let message_enc = match filter(&mut encoded) { + Altered::InPlace => { + encoding::message_framing(encoded.typ, encoded.version, encoded.payload.clone()) + } + Altered::Raw(data) => data, + }; + + let message_enc_reader: &mut dyn io::Read = &mut &message_enc[..]; + let len = right + .read_tls(message_enc_reader) + .unwrap(); + assert_eq!(len, message_enc.len()); + } + } + + total +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum KeyType { + Rsa2048, + Rsa3072, + Rsa4096, + EcdsaP256, + EcdsaP384, + EcdsaP521, + Ed25519, +} + +static ALL_KEY_TYPES: &[KeyType] = &[ + KeyType::Rsa2048, + KeyType::Rsa3072, + KeyType::Rsa4096, + KeyType::EcdsaP256, + KeyType::EcdsaP384, + KeyType::EcdsaP521, + KeyType::Ed25519, +]; + +static ALL_KEY_TYPES_EXCEPT_P521: &[KeyType] = &[ + KeyType::Rsa2048, + KeyType::Rsa3072, + KeyType::Rsa4096, + KeyType::EcdsaP256, + KeyType::EcdsaP384, + KeyType::Ed25519, +]; + +impl KeyType { + pub fn all_for_provider(provider: &CryptoProvider) -> &'static [Self] { + match provider + .key_provider + .load_private_key(Self::EcdsaP521.key()) + .is_ok() + { + true => ALL_KEY_TYPES, + false => ALL_KEY_TYPES_EXCEPT_P521, + } + } + + fn bytes_for(&self, part: &str) -> &'static [u8] { + match self { + Self::Rsa2048 => bytes_for("rsa-2048", part), + Self::Rsa3072 => bytes_for("rsa-3072", part), + Self::Rsa4096 => bytes_for("rsa-4096", part), + Self::EcdsaP256 => bytes_for("ecdsa-p256", part), + Self::EcdsaP384 => bytes_for("ecdsa-p384", part), + Self::EcdsaP521 => bytes_for("ecdsa-p521", part), + Self::Ed25519 => bytes_for("eddsa", part), + } + } + + pub fn identity(&self) -> Arc> { + Arc::new( + Identity::from_cert_chain( + CertificateDer::pem_slice_iter(self.bytes_for("end.fullchain")) + .map(|result| result.unwrap()) + .collect(), + ) + .unwrap(), + ) + } + + pub fn spki(&self) -> SubjectPublicKeyInfoDer<'static> { + SubjectPublicKeyInfoDer::from_pem_slice(self.bytes_for("end.spki.pem")).unwrap() + } + + pub fn load_key(&self, provider: &CryptoProvider) -> Box { + provider + .key_provider + .load_private_key(self.key()) + .expect("valid key") + } + + pub fn key(&self) -> PrivateKeyDer<'static> { + PrivatePkcs8KeyDer::from_pem_slice(self.bytes_for("end.key")) + .unwrap() + .into() + } + + pub fn client_identity(&self) -> Arc> { + Arc::new( + Identity::from_cert_chain( + CertificateDer::pem_slice_iter(self.bytes_for("client.fullchain")) + .map(|result| result.unwrap()) + .collect(), + ) + .unwrap(), + ) + } + + pub fn end_entity_crl(&self) -> CertificateRevocationListDer<'static> { + self.crl("end", "revoked") + } + + pub fn client_crl(&self) -> CertificateRevocationListDer<'static> { + self.crl("client", "revoked") + } + + pub fn intermediate_crl(&self) -> CertificateRevocationListDer<'static> { + self.crl("inter", "revoked") + } + + pub fn end_entity_crl_expired(&self) -> CertificateRevocationListDer<'static> { + self.crl("end", "expired") + } + + pub fn client_key(&self) -> PrivateKeyDer<'static> { + PrivatePkcs8KeyDer::from_pem_slice(self.bytes_for("client.key")) + .unwrap() + .into() + } + + pub fn client_spki(&self) -> SubjectPublicKeyInfoDer<'static> { + SubjectPublicKeyInfoDer::from_pem_slice(self.bytes_for("client.spki.pem")).unwrap() + } + + pub fn certified_client_key(&self, provider: &CryptoProvider) -> Result { + let private_key = provider + .key_provider + .load_private_key(self.client_key())?; + let public_key = private_key + .public_key() + .ok_or(Error::InconsistentKeys(InconsistentKeys::Unknown))? + .into_owned(); + Ok(Credentials::new_unchecked( + Arc::new(Identity::RawPublicKey(public_key)), + private_key, + )) + } + + pub fn credentials_with_raw_pub_key( + &self, + provider: &CryptoProvider, + ) -> Result { + let private_key = provider + .key_provider + .load_private_key(self.key())?; + let public_key = private_key + .public_key() + .ok_or(Error::InconsistentKeys(InconsistentKeys::Unknown))? + .into_owned(); + Ok(Credentials::new_unchecked( + Arc::new(Identity::RawPublicKey(public_key)), + private_key, + )) + } + + pub fn credentials_with_cert_chain( + &self, + provider: &CryptoProvider, + ) -> Result { + let private_key = provider + .key_provider + .load_private_key(self.key())?; + Credentials::new(self.identity(), private_key) + } + + fn crl(&self, role: &str, r#type: &str) -> CertificateRevocationListDer<'static> { + CertificateRevocationListDer::from_pem_slice( + self.bytes_for(&format!("{role}.{type}.crl.pem")), + ) + .unwrap() + } + + pub fn ca_distinguished_name(&self) -> DistinguishedName { + match self { + Self::Rsa2048 => &b"0\x1f1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14ponytown RSA 2048 CA"[..], + Self::Rsa3072 => b"0\x1f1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14ponytown RSA 3072 CA", + Self::Rsa4096 => b"0\x1f1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14ponytown RSA 4096 CA", + Self::EcdsaP256 => b"0\x211\x1f0\x1d\x06\x03U\x04\x03\x0c\x16ponytown ECDSA p256 CA", + Self::EcdsaP384 => b"0\x211\x1f0\x1d\x06\x03U\x04\x03\x0c\x16ponytown ECDSA p384 CA", + Self::EcdsaP521 => b"0\x211\x1f0\x1d\x06\x03U\x04\x03\x0c\x16ponytown ECDSA p521 CA", + Self::Ed25519 => b"0\x1c1\x1a0\x18\x06\x03U\x04\x03\x0c\x11ponytown EdDSA CA", + } + .to_vec() + .into() + } + + pub fn client_root_store(&self) -> Arc { + let mut roots = RootCertStore::empty(); + roots.add(self.ca_cert()).unwrap(); + roots.into() + } + + pub fn ca_cert(&self) -> CertificateDer<'_> { + let Identity::X509(id) = &*self.identity() else { + panic!("expected raw key identity"); + }; + + id.intermediates + .iter() + .next_back() + .cloned() + .expect("cert chain cannot be empty") + } +} + +pub trait ServerConfigExt { + fn finish(self, kt: KeyType) -> ServerConfig; +} + +impl ServerConfigExt for ConfigBuilder { + fn finish(self, kt: KeyType) -> ServerConfig { + self.with_no_client_auth() + .with_single_cert(kt.identity(), kt.key()) + .unwrap() + } +} + +pub fn make_server_config(kt: KeyType, provider: &CryptoProvider) -> ServerConfig { + ServerConfig::builder(provider.clone().into()).finish(kt) +} + +pub fn make_server_config_with_kx_groups( + kt: KeyType, + kx_groups: Vec<&'static dyn SupportedKxGroup>, + provider: &CryptoProvider, +) -> ServerConfig { + ServerConfig::builder( + CryptoProvider { + kx_groups: Cow::Owned(kx_groups), + ..provider.clone() + } + .into(), + ) + .finish(kt) +} + +pub fn make_server_config_with_mandatory_client_auth_crls( + kt: KeyType, + crls: Vec>, + provider: &CryptoProvider, +) -> ServerConfig { + make_server_config_with_client_verifier( + kt, + webpki_client_verifier_builder(kt.client_root_store(), provider).with_crls(crls), + provider, + ) +} + +pub fn make_server_config_with_mandatory_client_auth( + kt: KeyType, + provider: &CryptoProvider, +) -> ServerConfig { + make_server_config_with_client_verifier( + kt, + webpki_client_verifier_builder(kt.client_root_store(), provider), + provider, + ) +} + +pub fn make_server_config_with_optional_client_auth( + kt: KeyType, + crls: Vec>, + provider: &CryptoProvider, +) -> ServerConfig { + make_server_config_with_client_verifier( + kt, + webpki_client_verifier_builder(kt.client_root_store(), provider) + .with_crls(crls) + .allow_unknown_revocation_status() + .allow_unauthenticated(), + provider, + ) +} + +pub fn make_server_config_with_client_verifier( + kt: KeyType, + verifier_builder: ClientVerifierBuilder, + provider: &CryptoProvider, +) -> ServerConfig { + ServerConfig::builder(provider.clone().into()) + .with_client_cert_verifier(Arc::new(verifier_builder.build().unwrap())) + .with_single_cert(kt.identity(), kt.key()) + .unwrap() +} + +pub fn make_server_config_with_raw_key_support( + kt: KeyType, + provider: &CryptoProvider, +) -> ServerConfig { + let mut client_verifier = + MockClientVerifier::new(|| Ok(PeerVerified::assertion()), kt, provider); + let server_cert_resolver = Arc::new(SingleCredential::from( + kt.credentials_with_raw_pub_key(provider) + .unwrap(), + )); + client_verifier.expect_raw_public_keys = true; + // We don't support tls1.2 for Raw Public Keys, hence the version is hard-coded. + ServerConfig::builder(provider.clone().into()) + .with_client_cert_verifier(Arc::new(client_verifier)) + .with_server_credential_resolver(server_cert_resolver) + .unwrap() +} + +pub fn make_client_config_with_raw_key_support( + kt: KeyType, + provider: &CryptoProvider, +) -> ClientConfig { + let server_verifier = Arc::new(MockServerVerifier::expects_raw_public_keys(provider)); + let client_cert_resolver = Arc::new(SingleCredential::from( + kt.certified_client_key(provider) + .unwrap(), + )); + // We don't support tls1.2 for Raw Public Keys, hence the version is hard-coded. + ClientConfig::builder(provider.clone().into()) + .dangerous() + .with_custom_certificate_verifier(server_verifier) + .with_client_credential_resolver(client_cert_resolver) + .unwrap() +} + +pub trait ClientConfigExt { + fn finish(self, kt: KeyType) -> ClientConfig; + fn finish_with_creds(self, kt: KeyType) -> ClientConfig; + fn add_root_certs(self, kt: KeyType) -> ConfigBuilder; +} + +impl ClientConfigExt for ConfigBuilder { + fn finish(self, kt: KeyType) -> ClientConfig { + self.add_root_certs(kt) + .with_no_client_auth() + .unwrap() + } + + fn finish_with_creds(self, kt: KeyType) -> ClientConfig { + self.add_root_certs(kt) + .with_client_auth_cert(kt.client_identity(), kt.client_key()) + .unwrap() + } + + fn add_root_certs(self, kt: KeyType) -> ConfigBuilder { + let mut root_store = RootCertStore::empty(); + root_store.add_parsable_certificates( + CertificateDer::pem_slice_iter(kt.bytes_for("ca.cert")).map(|result| result.unwrap()), + ); + + self.with_root_certificates(root_store) + } +} + +pub fn make_client_config(kt: KeyType, provider: &CryptoProvider) -> ClientConfig { + ClientConfig::builder(provider.clone().into()).finish(kt) +} + +pub fn make_client_config_with_kx_groups( + kt: KeyType, + kx_groups: Vec<&'static dyn SupportedKxGroup>, + provider: &CryptoProvider, +) -> ClientConfig { + ClientConfig::builder( + CryptoProvider { + kx_groups: Cow::Owned(kx_groups), + ..provider.clone() + } + .into(), + ) + .finish(kt) +} + +pub fn make_client_config_with_auth(kt: KeyType, provider: &CryptoProvider) -> ClientConfig { + ClientConfig::builder(provider.clone().into()).finish_with_creds(kt) +} + +pub fn make_client_config_with_verifier( + verifier_builder: ServerVerifierBuilder, + provider: &CryptoProvider, +) -> ClientConfig { + ClientConfig::builder(provider.clone().into()) + .dangerous() + .with_custom_certificate_verifier(Arc::new(verifier_builder.build().unwrap())) + .with_no_client_auth() + .unwrap() +} + +pub fn webpki_client_verifier_builder( + roots: Arc, + provider: &CryptoProvider, +) -> ClientVerifierBuilder { + WebPkiClientVerifier::builder(roots, provider) +} + +pub fn webpki_server_verifier_builder( + roots: Arc, + provider: &CryptoProvider, +) -> ServerVerifierBuilder { + WebPkiServerVerifier::builder(roots, provider) +} + +pub fn make_pair(kt: KeyType, provider: &CryptoProvider) -> (ClientConnection, ServerConnection) { + make_pair_for_configs( + make_client_config(kt, provider), + make_server_config(kt, provider), + ) +} + +pub fn make_pair_for_configs( + client_config: ClientConfig, + server_config: ServerConfig, +) -> (ClientConnection, ServerConnection) { + make_pair_for_arc_configs(&Arc::new(client_config), &Arc::new(server_config)) +} + +pub fn make_pair_for_arc_configs( + client_config: &Arc, + server_config: &Arc, +) -> (ClientConnection, ServerConnection) { + ( + client_config + .connect(server_name("localhost")) + .build() + .unwrap(), + ServerConnection::new(server_config.clone()).unwrap(), + ) +} + +/// Return a client and server config that don't share a common cipher suite +pub fn make_disjoint_suite_configs(provider: CryptoProvider) -> (ClientConfig, ServerConfig) { + let kt = KeyType::Rsa2048; + let client_provider = CryptoProvider { + tls13_cipher_suites: provider + .tls13_cipher_suites + .iter() + .copied() + .filter(|cs| cs.common.suite == CipherSuite::TLS13_AES_128_GCM_SHA256) + .collect(), + ..provider.clone() + }; + let server_config = ServerConfig::builder(client_provider.into()).finish(kt); + + let server_provider = CryptoProvider { + tls13_cipher_suites: provider + .tls13_cipher_suites + .iter() + .copied() + .filter(|cs| cs.common.suite == CipherSuite::TLS13_AES_256_GCM_SHA384) + .collect(), + ..provider + }; + let client_config = ClientConfig::builder(server_provider.into()).finish(kt); + + (client_config, server_config) +} + +pub fn do_handshake(client: &mut impl Connection, server: &mut impl Connection) -> (usize, usize) { + let (mut to_client, mut to_server) = (0, 0); + while server.is_handshaking() || client.is_handshaking() { + to_server += transfer(client, server); + server.process_new_packets().unwrap(); + to_client += transfer(server, client); + client.process_new_packets().unwrap(); + } + (to_server, to_client) +} + +#[derive(PartialEq, Debug)] +pub enum ErrorFromPeer { + Client(Error), + Server(Error), +} + +pub fn do_handshake_until_error( + client: &mut ClientConnection, + server: &mut ServerConnection, +) -> Result<(), ErrorFromPeer> { + while server.is_handshaking() || client.is_handshaking() { + transfer(client, server); + server + .process_new_packets() + .map_err(ErrorFromPeer::Server)?; + transfer(server, client); + client + .process_new_packets() + .map_err(ErrorFromPeer::Client)?; + } + + Ok(()) +} + +pub fn do_handshake_altered( + mut client: ClientConnection, + alter_server_message: impl Fn(&mut EncodedMessage>) -> Altered, + alter_client_message: impl Fn(&mut EncodedMessage>) -> Altered, + mut server: ServerConnection, +) -> Result<(), ErrorFromPeer> { + while server.is_handshaking() || client.is_handshaking() { + transfer_altered(&mut client, &alter_client_message, &mut server); + + server + .process_new_packets() + .map_err(ErrorFromPeer::Server)?; + + transfer_altered(&mut server, &alter_server_message, &mut client); + + client + .process_new_packets() + .map_err(ErrorFromPeer::Client)?; + } + + Ok(()) +} + +pub fn do_handshake_until_both_error( + client: &mut ClientConnection, + server: &mut ServerConnection, +) -> Result<(), Vec> { + match do_handshake_until_error(client, server) { + Err(server_err @ ErrorFromPeer::Server(_)) => { + let mut errors = vec![server_err]; + transfer(server, client); + let client_err = client + .process_new_packets() + .map_err(ErrorFromPeer::Client) + .expect_err("client didn't produce error after server error"); + errors.push(client_err); + Err(errors) + } + + Err(client_err @ ErrorFromPeer::Client(_)) => { + let mut errors = vec![client_err]; + transfer(client, server); + let server_err = server + .process_new_packets() + .map_err(ErrorFromPeer::Server) + .expect_err("server didn't produce error after client error"); + errors.push(server_err); + Err(errors) + } + + Ok(()) => Ok(()), + } +} + +pub fn server_name(name: &'static str) -> ServerName<'static> { + name.try_into().unwrap() +} + +/// An object that impls `io::Read` and `io::Write` for testing. +/// +/// The `reads` and `writes` fields set the behaviour of these trait +/// implementations. They return the `WouldBlock` error if not otherwise +/// configured -- `TestNonBlockIo::default()` does this permanently. +/// +/// This object panics on drop if the configured expected reads/writes +/// didn't take place. +#[derive(Debug, Default)] +pub struct TestNonBlockIo { + /// Each `write()` call is satisfied by inspecting this field. + /// + /// If it is empty, `WouldBlock` is returned. Otherwise the write is + /// satisfied by popping a value and returning it (reduced by the size + /// of the write buffer, if needed). + pub writes: Vec, + + /// Each `read()` call is satisfied by inspecting this field. + /// + /// If it is empty, `WouldBlock` is returned. Otherwise the read is + /// satisfied by popping a value and copying it into the output + /// buffer. Each value must be no longer than the buffer for that + /// call. + pub reads: Vec>, +} + +impl io::Read for TestNonBlockIo { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self.reads.pop() { + None => Err(io::ErrorKind::WouldBlock.into()), + Some(data) => { + assert!(data.len() <= buf.len()); + let take = core::cmp::min(data.len(), buf.len()); + buf[..take].clone_from_slice(&data[..take]); + Ok(take) + } + } + } +} + +impl io::Write for TestNonBlockIo { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self.writes.pop() { + None => Err(io::ErrorKind::WouldBlock.into()), + Some(n) => Ok(core::cmp::min(n, buf.len())), + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Drop for TestNonBlockIo { + fn drop(&mut self) { + // ensure the object was exhausted as expected + assert!(self.reads.is_empty()); + assert!(self.writes.is_empty()); + } +} + +pub fn do_suite_and_kx_test( + client_config: ClientConfig, + server_config: ServerConfig, + expect_suite: SupportedCipherSuite, + expect_kx: NamedGroup, + expect_version: ProtocolVersion, +) { + println!( + "do_suite_test {:?} {:?}", + expect_version, + expect_suite.suite() + ); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + assert_eq!(None, client.negotiated_cipher_suite()); + assert_eq!(None, server.negotiated_cipher_suite()); + assert!( + client + .negotiated_key_exchange_group() + .is_none() + ); + assert!( + server + .negotiated_key_exchange_group() + .is_none() + ); + assert_eq!(None, client.protocol_version()); + assert_eq!(None, server.protocol_version()); + assert!(client.is_handshaking()); + assert!(server.is_handshaking()); + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + assert!(client.is_handshaking()); + assert!(server.is_handshaking()); + assert_eq!(None, client.protocol_version()); + assert_eq!(Some(expect_version), server.protocol_version()); + assert_eq!(None, client.negotiated_cipher_suite()); + assert_eq!(Some(expect_suite), server.negotiated_cipher_suite()); + assert!( + client + .negotiated_key_exchange_group() + .is_none() + ); + if matches!(expect_version, ProtocolVersion::TLSv1_2) { + assert!( + server + .negotiated_key_exchange_group() + .is_none() + ); + } else { + assert_eq!( + expect_kx, + server + .negotiated_key_exchange_group() + .unwrap() + .name() + ); + } + + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + assert_eq!(Some(expect_suite), client.negotiated_cipher_suite()); + assert_eq!(Some(expect_suite), server.negotiated_cipher_suite()); + assert_eq!( + expect_kx, + client + .negotiated_key_exchange_group() + .unwrap() + .name() + ); + if matches!(expect_version, ProtocolVersion::TLSv1_2) { + assert!( + server + .negotiated_key_exchange_group() + .is_none() + ); + } else { + assert_eq!( + expect_kx, + server + .negotiated_key_exchange_group() + .unwrap() + .name() + ); + } + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + assert!(!client.is_handshaking()); + assert!(!server.is_handshaking()); + assert_eq!(Some(expect_version), client.protocol_version()); + assert_eq!(Some(expect_version), server.protocol_version()); + assert_eq!(Some(expect_suite), client.negotiated_cipher_suite()); + assert_eq!(Some(expect_suite), server.negotiated_cipher_suite()); + assert_eq!( + expect_kx, + client + .negotiated_key_exchange_group() + .unwrap() + .name() + ); + assert_eq!( + expect_kx, + server + .negotiated_key_exchange_group() + .unwrap() + .name() + ); +} + +#[derive(Debug)] +pub struct MockServerVerifier { + cert_rejection_error: Option, + tls12_signature_error: Option, + tls13_signature_error: Option, + signature_schemes: Vec, + expected_ocsp_response: Option>, + requires_raw_public_keys: bool, + raw_public_key_algorithms: Option, +} + +impl ServerVerifier for MockServerVerifier { + fn verify_identity(&self, identity: &ServerIdentity<'_>) -> Result { + println!("verify_identity({identity:?})"); + if let Some(expected_ocsp) = &self.expected_ocsp_response { + assert_eq!(expected_ocsp, identity.ocsp_response); + } + match &self.cert_rejection_error { + Some(error) => Err(error.clone()), + _ => Ok(PeerVerified::assertion()), + } + } + + fn verify_tls12_signature( + &self, + input: &SignatureVerificationInput<'_>, + ) -> Result { + println!("verify_tls12_signature({input:?})"); + match &self.tls12_signature_error { + Some(error) => Err(error.clone()), + _ => Ok(HandshakeSignatureValid::assertion()), + } + } + + fn verify_tls13_signature( + &self, + input: &SignatureVerificationInput<'_>, + ) -> Result { + println!("verify_tls13_signature({input:?})"); + match &self.tls13_signature_error { + Some(error) => Err(error.clone()), + _ if self.requires_raw_public_keys => verify_tls13_signature( + input, + self.raw_public_key_algorithms + .as_ref() + .unwrap(), + ), + _ => Ok(HandshakeSignatureValid::assertion()), + } + } + + fn supported_verify_schemes(&self) -> Vec { + self.signature_schemes.clone() + } + + fn request_ocsp_response(&self) -> bool { + self.expected_ocsp_response.is_some() + } + + fn supported_certificate_types(&self) -> &'static [CertificateType] { + match self.requires_raw_public_keys { + false => &[CertificateType::X509], + true => &[CertificateType::RawPublicKey], + } + } + + fn hash_config(&self, _: &mut dyn Hasher) {} +} + +impl MockServerVerifier { + pub fn accepts_anything() -> Self { + Self { + cert_rejection_error: None, + ..Default::default() + } + } + + pub fn expects_ocsp_response(response: &[u8]) -> Self { + Self { + expected_ocsp_response: Some(response.to_vec()), + ..Default::default() + } + } + + pub fn rejects_certificate(err: Error) -> Self { + Self { + cert_rejection_error: Some(err), + ..Default::default() + } + } + + pub fn rejects_tls12_signatures(err: Error) -> Self { + Self { + tls12_signature_error: Some(err), + ..Default::default() + } + } + + pub fn rejects_tls13_signatures(err: Error) -> Self { + Self { + tls13_signature_error: Some(err), + ..Default::default() + } + } + + pub fn offers_no_signature_schemes() -> Self { + Self { + signature_schemes: vec![], + ..Default::default() + } + } + + pub fn expects_raw_public_keys(provider: &CryptoProvider) -> Self { + Self { + requires_raw_public_keys: true, + raw_public_key_algorithms: Some(provider.signature_verification_algorithms), + ..Default::default() + } + } +} + +impl Default for MockServerVerifier { + fn default() -> Self { + Self { + cert_rejection_error: None, + tls12_signature_error: None, + tls13_signature_error: None, + signature_schemes: vec![ + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PKCS1_SHA256, + SignatureScheme::ED25519, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP521_SHA512, + ], + expected_ocsp_response: None, + requires_raw_public_keys: false, + raw_public_key_algorithms: None, + } + } +} + +#[derive(Debug)] +pub struct MockClientVerifier { + pub verified: fn() -> Result, + pub subjects: Arc<[DistinguishedName]>, + pub mandatory: bool, + pub offered_schemes: Option>, + expect_raw_public_keys: bool, + raw_public_key_algorithms: Option, + parent: Arc, +} + +impl MockClientVerifier { + pub fn new( + verified: fn() -> Result, + kt: KeyType, + provider: &CryptoProvider, + ) -> Self { + Self { + parent: Arc::new( + webpki_client_verifier_builder(kt.client_root_store(), provider) + .build() + .unwrap(), + ), + verified, + subjects: Arc::from(kt.client_root_store().subjects()), + mandatory: true, + offered_schemes: None, + expect_raw_public_keys: false, + raw_public_key_algorithms: Some(provider.signature_verification_algorithms), + } + } +} + +impl ClientVerifier for MockClientVerifier { + fn verify_identity(&self, _identity: &ClientIdentity<'_>) -> Result { + (self.verified)() + } + + fn verify_tls12_signature( + &self, + input: &SignatureVerificationInput<'_>, + ) -> Result { + if self.expect_raw_public_keys { + Ok(HandshakeSignatureValid::assertion()) + } else { + self.parent + .verify_tls12_signature(input) + } + } + + fn verify_tls13_signature( + &self, + input: &SignatureVerificationInput<'_>, + ) -> Result { + if self.expect_raw_public_keys { + verify_tls13_signature( + input, + self.raw_public_key_algorithms + .as_ref() + .unwrap(), + ) + } else { + self.parent + .verify_tls13_signature(input) + } + } + + fn root_hint_subjects(&self) -> Arc<[DistinguishedName]> { + self.subjects.clone() + } + + fn client_auth_mandatory(&self) -> bool { + self.mandatory + } + + fn supported_verify_schemes(&self) -> Vec { + if let Some(schemes) = &self.offered_schemes { + schemes.clone() + } else { + self.parent.supported_verify_schemes() + } + } + + fn supported_certificate_types(&self) -> &'static [CertificateType] { + match self.expect_raw_public_keys { + false => &[CertificateType::X509], + true => &[CertificateType::RawPublicKey], + } + } +} + +/// This allows injection/receipt of raw messages into a post-handshake connection. +/// +/// It consumes one of the peers, extracts its secrets, and then reconstitutes the +/// message encrypter/decrypter. It does not do fragmentation/joining. +pub struct RawTls { + encrypter: Box, + enc_seq: u64, + decrypter: Box, + dec_seq: u64, +} + +impl RawTls { + /// conn must be post-handshake, and must have been created with `enable_secret_extraction` + pub fn new_client(conn: ClientConnection) -> Self { + let suite = conn.negotiated_cipher_suite().unwrap(); + Self::new( + suite, + conn.dangerous_extract_secrets() + .unwrap(), + ) + } + + /// conn must be post-handshake, and must have been created with `enable_secret_extraction` + pub fn new_server(conn: ServerConnection) -> Self { + let suite = conn.negotiated_cipher_suite().unwrap(); + Self::new( + suite, + conn.dangerous_extract_secrets() + .unwrap(), + ) + } + + fn new(suite: SupportedCipherSuite, secrets: rustls::ExtractedSecrets) -> Self { + let rustls::ExtractedSecrets { + tx: (tx_seq, tx_keys), + rx: (rx_seq, rx_keys), + } = secrets; + + let encrypter = match (tx_keys, suite) { + ( + ConnectionTrafficSecrets::Aes128Gcm { key, iv } + | ConnectionTrafficSecrets::Aes256Gcm { key, iv } + | ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }, + SupportedCipherSuite::Tls13(tls13), + ) => tls13.aead_alg.encrypter(key, iv), + + ( + ConnectionTrafficSecrets::Aes128Gcm { key, iv } + | ConnectionTrafficSecrets::Aes256Gcm { key, iv } + | ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }, + SupportedCipherSuite::Tls12(tls12), + ) => tls12 + .aead_alg + .encrypter(key, &iv.as_ref()[..4], &iv.as_ref()[4..]), + + _ => todo!(), + }; + + let decrypter = match (rx_keys, suite) { + ( + ConnectionTrafficSecrets::Aes128Gcm { key, iv } + | ConnectionTrafficSecrets::Aes256Gcm { key, iv } + | ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }, + SupportedCipherSuite::Tls13(tls13), + ) => tls13.aead_alg.decrypter(key, iv), + + ( + ConnectionTrafficSecrets::Aes128Gcm { key, iv } + | ConnectionTrafficSecrets::Aes256Gcm { key, iv } + | ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }, + SupportedCipherSuite::Tls12(tls12), + ) => tls12 + .aead_alg + .decrypter(key, &iv.as_ref()[..4]), + + _ => todo!(), + }; + + Self { + encrypter, + enc_seq: tx_seq, + decrypter, + dec_seq: rx_seq, + } + } + + pub fn encrypt_and_send( + &mut self, + msg: &EncodedMessage>, + peer: &mut impl Connection, + ) { + let data = self + .encrypter + .encrypt(msg.borrow_outbound(), self.enc_seq) + .unwrap() + .encode(); + self.enc_seq += 1; + peer.read_tls(&mut io::Cursor::new(data)) + .unwrap(); + } + + pub fn receive_and_decrypt( + &mut self, + peer: &mut impl Connection, + f: impl Fn(EncodedMessage<&[u8]>), + ) { + let mut data = vec![]; + peer.write_tls(&mut io::Cursor::new(&mut data)) + .unwrap(); + + // Parse TLS record header: 1 byte type, 2 bytes version, 2 bytes length + assert!(data.len() >= 5, "incomplete TLS record header"); + let typ = ContentType::from(data[0]); + let version = ProtocolVersion::from(u16::from_be_bytes([data[1], data[2]])); + let len = u16::from_be_bytes([data[3], data[4]]) as usize; + let left = &mut data[5..]; + assert_eq!(len, left.len()); + + let inbound = EncodedMessage { + typ, + version, + payload: InboundOpaque(left), + }; + + let msg = self + .decrypter + .decrypt(inbound, self.dec_seq) + .unwrap(); + self.dec_seq += 1; + + println!("receive_and_decrypt: {msg:?}"); + + f(msg); + } +} + +pub fn aes_128_gcm_with_1024_confidentiality_limit( + provider: CryptoProvider, +) -> Arc { + const CONFIDENTIALITY_LIMIT: u64 = 1024; + + // needed to extend lifetime of Tls13CipherSuite to 'static + static TLS13_LIMITED_SUITE: OnceLock = OnceLock::new(); + static TLS12_LIMITED_SUITE: OnceLock = OnceLock::new(); + + let tls13_limited = TLS13_LIMITED_SUITE.get_or_init(|| { + let tls13 = provider + .tls13_cipher_suites + .iter() + .find(|cs| cs.common.suite == CipherSuite::TLS13_AES_128_GCM_SHA256) + .unwrap(); + + rustls::Tls13CipherSuite { + common: rustls::crypto::CipherSuiteCommon { + confidentiality_limit: CONFIDENTIALITY_LIMIT, + ..tls13.common + }, + ..**tls13 + } + }); + + let tls12_limited = TLS12_LIMITED_SUITE.get_or_init(|| { + let tls12 = provider + .tls12_cipher_suites + .iter() + .find(|cs| cs.common.suite == CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + .unwrap(); + + rustls::Tls12CipherSuite { + common: rustls::crypto::CipherSuiteCommon { + confidentiality_limit: CONFIDENTIALITY_LIMIT, + ..tls12.common + }, + ..**tls12 + } + }); + + CryptoProvider { + tls12_cipher_suites: Cow::Owned(vec![tls12_limited]), + tls13_cipher_suites: Cow::Owned(vec![tls13_limited]), + ..provider + } + .into() +} + +pub fn unsafe_plaintext_crypto_provider(provider: CryptoProvider) -> Arc { + static TLS13_PLAIN_SUITE: OnceLock = OnceLock::new(); + + let tls13 = TLS13_PLAIN_SUITE.get_or_init(|| { + let tls13 = provider + .tls13_cipher_suites + .iter() + .find(|cs| cs.common.suite == CipherSuite::TLS13_AES_256_GCM_SHA384) + .unwrap(); + + rustls::Tls13CipherSuite { + aead_alg: &plaintext::Aead, + common: rustls::crypto::CipherSuiteCommon { ..tls13.common }, + ..**tls13 + } + }); + + CryptoProvider { + tls13_cipher_suites: Cow::Owned(vec![tls13]), + ..provider + } + .into() +} + +#[derive(Default, Debug)] +pub struct ServerCheckCertResolve { + pub expected_sni: Option>, + pub expected_sigalgs: Option>, + pub expected_alpn: Option>>, + pub expected_cipher_suites: Option>, + pub expected_server_cert_types: Option>, + pub expected_client_cert_types: Option>, + pub expected_named_groups: Option>, +} + +impl ServerCredentialResolver for ServerCheckCertResolve { + fn resolve(&self, client_hello: &ClientHello<'_>) -> Result { + if client_hello + .signature_schemes() + .is_empty() + { + panic!("no signature schemes shared by client"); + } + + if client_hello.cipher_suites().is_empty() { + panic!("no cipher suites shared by client"); + } + + if let Some(expected_sni) = &self.expected_sni { + let sni = client_hello + .server_name() + .expect("sni unexpectedly absent"); + assert_eq!(expected_sni, sni); + } + + if let Some(expected_sigalgs) = &self.expected_sigalgs { + assert_eq!( + expected_sigalgs, + client_hello.signature_schemes(), + "unexpected signature schemes" + ); + } + + if let Some(expected_alpn) = &self.expected_alpn { + let alpn = client_hello + .alpn() + .expect("alpn unexpectedly absent") + .collect::>(); + assert_eq!(alpn.len(), expected_alpn.len()); + + for (got, wanted) in alpn.iter().zip(expected_alpn.iter()) { + assert_eq!(got, &wanted.as_slice()); + } + } + + if let Some(expected_cipher_suites) = &self.expected_cipher_suites { + assert_eq!( + expected_cipher_suites, + client_hello.cipher_suites(), + "unexpected cipher suites" + ); + } + + if let Some(expected_server_cert) = &self.expected_server_cert_types { + assert_eq!( + expected_server_cert, + client_hello + .server_cert_types() + .expect("Server cert types not present"), + "unexpected server cert" + ); + } + + if let Some(expected_client_cert) = &self.expected_client_cert_types { + assert_eq!( + expected_client_cert, + client_hello + .client_cert_types() + .expect("Client cert types not present"), + "unexpected client cert" + ); + } + + if let Some(expected_named_groups) = &self.expected_named_groups { + assert_eq!( + expected_named_groups, + client_hello + .named_groups() + .expect("Named groups not present"), + ) + } + + Err(Error::NoSuitableCertificate) + } +} + +pub struct OtherSession<'a, C: Connection> { + sess: &'a mut C, + pub reads: usize, + /// Writevs(Chunks(Bytes)) + pub writevs: Vec>>, + fail_ok: bool, + pub short_writes: bool, + pub last_error: Option, + pub buffered: bool, + buffer: Vec>, +} + +impl<'a, C: Connection> OtherSession<'a, C> { + pub fn new(sess: &'a mut C) -> Self { + OtherSession { + sess, + reads: 0, + writevs: vec![], + fail_ok: false, + short_writes: false, + last_error: None, + buffered: false, + buffer: vec![], + } + } + + pub fn new_buffered(sess: &'a mut C) -> Self { + let mut os = OtherSession::new(sess); + os.buffered = true; + os + } + + pub fn new_fails(sess: &'a mut C) -> Self { + let mut os = OtherSession::new(sess); + os.fail_ok = true; + os + } + + fn flush_vectored(&mut self, b: &[io::IoSlice<'_>]) -> io::Result { + let mut total = 0; + let mut chunks = vec![]; + for bytes in b { + let write_len = if self.short_writes { + if bytes.len() > 5 { + bytes.len() / 2 + } else { + bytes.len() + } + } else { + bytes.len() + }; + + let l = self + .sess + .read_tls(&mut io::Cursor::new(&bytes[..write_len]))?; + chunks.push(bytes[..write_len].to_vec()); + total += l; + if bytes.len() != l { + break; + } + } + + let rc = self.sess.process_new_packets(); + if !self.fail_ok { + rc.unwrap(); + } else if rc.is_err() { + self.last_error = rc.err(); + } + + self.writevs.push(chunks); + Ok(total) + } + + pub fn writev_lengths(&self) -> Vec> { + self.writevs + .iter() + .map(|write| { + write + .iter() + .map(|chunk| chunk.len()) + .collect() + }) + .collect() + } + + pub fn message_lengths(&self) -> Vec { + let mut buffer = vec![]; + for writev in &self.writevs { + for chunk in writev { + buffer.append(&mut chunk.clone()); + } + } + + let mut lengths = vec![]; + let mut offset = 0; + while offset < buffer.len() { + let header = &buffer[offset..offset + 5]; + println!( + "message header: ty={:x?} ver={:x?} len={:x?}", + header[0], + &header[1..3], + &header[3..5] + ); + let len = u16::from_be_bytes(header[3..5].try_into().unwrap()) as usize; + lengths.push(header.len() + len); + offset += header.len() + len; + } + + lengths + } +} + +impl io::Read for OtherSession<'_, C> { + fn read(&mut self, mut b: &mut [u8]) -> io::Result { + self.reads += 1; + self.sess.write_tls(&mut b) + } +} + +impl io::Write for OtherSession<'_, C> { + fn write(&mut self, _: &[u8]) -> io::Result { + unreachable!() + } + + fn flush(&mut self) -> io::Result<()> { + if !self.buffer.is_empty() { + let buffer = mem::take(&mut self.buffer); + let slices = buffer + .iter() + .map(|b| io::IoSlice::new(b)) + .collect::>(); + self.flush_vectored(&slices)?; + } + Ok(()) + } + + fn write_vectored(&mut self, b: &[io::IoSlice<'_>]) -> io::Result { + if self.buffered { + self.buffer + .extend(b.iter().map(|s| s.to_vec())); + return Ok(b.iter().map(|s| s.len()).sum()); + } + self.flush_vectored(b) + } +} + +/// Check `reader` has available exactly `bytes` +#[track_caller] +pub fn check_read(reader: &mut dyn io::Read, bytes: &[u8]) { + let mut buf = vec![0u8; bytes.len() + 1]; + assert_eq!(bytes.len(), reader.read(&mut buf).unwrap()); + assert_eq!(bytes, &buf[..bytes.len()]); +} + +/// Check `reader has available exactly `bytes`, followed by EOF +#[track_caller] +pub fn check_read_and_close(reader: &mut dyn io::Read, expect: &[u8]) { + check_read(reader, expect); + assert!(matches!(reader.read(&mut [0u8; 5]), Ok(0))); +} + +/// Check `reader` yields only an error of kind `err_kind` +#[track_caller] +pub fn check_read_err(reader: &mut dyn io::Read, err_kind: io::ErrorKind) { + let mut buf = vec![0u8; 1]; + let err = reader.read(&mut buf).unwrap_err(); + assert!(matches!(err, err if err.kind() == err_kind)) +} + +/// Check `reader` has available exactly `bytes` +#[track_caller] +pub fn check_fill_buf(reader: &mut dyn io::BufRead, bytes: &[u8]) { + let b = reader.fill_buf().unwrap(); + assert_eq!(b, bytes); + let len = b.len(); + reader.consume(len); +} + +/// Check `reader` yields only an error of kind `err_kind` +#[track_caller] +pub fn check_fill_buf_err(reader: &mut dyn io::BufRead, err_kind: io::ErrorKind) { + let err = reader.fill_buf().unwrap_err(); + assert!(matches!(err, err if err.kind() == err_kind)) +} + +pub fn certificate_error_expecting_name(expected: &str) -> CertificateError { + CertificateError::NotValidForNameContext { + expected: ServerName::try_from(expected) + .unwrap() + .to_owned(), + presented: vec![ + // ref. examples/internal/test_ca.rs + r#"DnsName("testserver.com")"#.into(), + r#"DnsName("second.testserver.com")"#.into(), + r#"DnsName("localhost")"#.into(), + "IpAddress(198.51.100.1)".into(), + "IpAddress(2001:db8::1)".into(), + ], + } +} + +mod plaintext { + use rustls::ConnectionTrafficSecrets; + use rustls::crypto::cipher::{ + AeadKey, InboundOpaque, Iv, MessageDecrypter, MessageEncrypter, OutboundOpaque, + OutboundPlain, Tls13AeadAlgorithm, UnsupportedOperationError, + }; + + use super::*; + + pub(super) struct Aead; + + impl Tls13AeadAlgorithm for Aead { + fn encrypter(&self, _key: AeadKey, _iv: Iv) -> Box { + Box::new(Encrypter) + } + + fn decrypter(&self, _key: AeadKey, _iv: Iv) -> Box { + Box::new(Decrypter) + } + + fn key_len(&self) -> usize { + 32 + } + + fn extract_keys( + &self, + _key: AeadKey, + _iv: Iv, + ) -> Result { + Err(UnsupportedOperationError) + } + } + + struct Encrypter; + + impl MessageEncrypter for Encrypter { + fn encrypt( + &mut self, + msg: EncodedMessage>, + _seq: u64, + ) -> Result, Error> { + let mut payload = OutboundOpaque::with_capacity(msg.payload.len()); + payload.extend_from_chunks(&msg.payload); + + Ok(EncodedMessage { + typ: ContentType::ApplicationData, + version: ProtocolVersion::TLSv1_2, + payload, + }) + } + + fn encrypted_payload_len(&self, payload_len: usize) -> usize { + payload_len + } + } + + struct Decrypter; + + impl MessageDecrypter for Decrypter { + fn decrypt<'a>( + &mut self, + msg: EncodedMessage>, + _seq: u64, + ) -> Result, Error> { + Ok(msg.into_plain_message()) + } + } +} + +#[derive(Debug, Clone)] +#[allow(dead_code)] // complete mock, but not 100% used in tests +pub enum ClientStorageOp { + SetKxHint(ClientSessionKey<'static>, NamedGroup), + GetKxHint(ClientSessionKey<'static>, Option), + SetTls12Session(ClientSessionKey<'static>), + GetTls12Session(ClientSessionKey<'static>, bool), + RemoveTls12Session(ClientSessionKey<'static>), + InsertTls13Ticket(ClientSessionKey<'static>), + TakeTls13Ticket(ClientSessionKey<'static>, bool), +} + +pub struct ClientStorage { + storage: Arc, + ops: Mutex>, + alter_max_early_data_size: Option<(u32, u32)>, +} + +impl ClientStorage { + pub fn new() -> Self { + Self { + storage: Arc::new(rustls::client::ClientSessionMemoryCache::new(1024)), + ops: Mutex::new(Vec::new()), + alter_max_early_data_size: None, + } + } + + pub fn alter_max_early_data_size(&mut self, expected: u32, altered: u32) { + self.alter_max_early_data_size = Some((expected, altered)); + } + + pub fn ops(&self) -> Vec { + self.ops.lock().unwrap().clone() + } + + pub fn ops_and_reset(&self) -> Vec { + mem::take(&mut self.ops.lock().unwrap()) + } +} + +impl fmt::Debug for ClientStorage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "(ops: {:?})", self.ops.lock().unwrap()) + } +} + +impl rustls::client::ClientSessionStore for ClientStorage { + fn set_kx_hint(&self, key: ClientSessionKey<'static>, group: NamedGroup) { + self.ops + .lock() + .unwrap() + .push(ClientStorageOp::SetKxHint(key.clone(), group)); + self.storage.set_kx_hint(key, group) + } + + fn kx_hint(&self, key: &ClientSessionKey<'_>) -> Option { + let rc = self.storage.kx_hint(key); + self.ops + .lock() + .unwrap() + .push(ClientStorageOp::GetKxHint(key.to_owned(), rc)); + rc + } + + fn set_tls12_session( + &self, + key: ClientSessionKey<'static>, + value: rustls::client::Tls12Session, + ) { + self.ops + .lock() + .unwrap() + .push(ClientStorageOp::SetTls12Session(key.clone())); + self.storage + .set_tls12_session(key, value) + } + + fn tls12_session(&self, key: &ClientSessionKey<'_>) -> Option { + let rc = self.storage.tls12_session(key); + self.ops + .lock() + .unwrap() + .push(ClientStorageOp::GetTls12Session( + key.to_owned(), + rc.is_some(), + )); + rc + } + + fn remove_tls12_session(&self, key: &ClientSessionKey<'static>) { + self.ops + .lock() + .unwrap() + .push(ClientStorageOp::RemoveTls12Session(key.clone())); + self.storage.remove_tls12_session(key); + } + + fn insert_tls13_ticket(&self, key: ClientSessionKey<'static>, mut value: Tls13Session) { + if let Some((expected, desired)) = self.alter_max_early_data_size { + value._reset_max_early_data_size(expected, desired); + } + + self.ops + .lock() + .unwrap() + .push(ClientStorageOp::InsertTls13Ticket(key.clone())); + self.storage + .insert_tls13_ticket(key, value); + } + + fn take_tls13_ticket(&self, key: &ClientSessionKey<'static>) -> Option { + let rc = self.storage.take_tls13_ticket(key); + self.ops + .lock() + .unwrap() + .push(ClientStorageOp::TakeTls13Ticket(key.clone(), rc.is_some())); + rc + } +} + +pub fn provider_with_one_suite( + provider: &CryptoProvider, + suite: SupportedCipherSuite, +) -> CryptoProvider { + provider_with_suites(provider, &[suite]) +} + +pub fn provider_with_suites( + provider: &CryptoProvider, + suites: &[SupportedCipherSuite], +) -> CryptoProvider { + let mut tls12_cipher_suites = vec![]; + let mut tls13_cipher_suites = vec![]; + + for suite in suites { + match suite { + SupportedCipherSuite::Tls12(suite) => { + tls12_cipher_suites.push(*suite); + } + SupportedCipherSuite::Tls13(suite) => { + tls13_cipher_suites.push(*suite); + } + _ => unreachable!(), + } + } + CryptoProvider { + tls12_cipher_suites: Cow::Owned(tls12_cipher_suites), + tls13_cipher_suites: Cow::Owned(tls13_cipher_suites), + ..provider.clone() + } +} + +pub mod macros { + //! Macros that bring a provider into the current scope. + //! + //! The selected provider module is bound as `provider`; you can rely on this + //! having the union of the public items common to the `rustls::crypto::ring` + //! and `rustls::crypto::aws_lc_rs` modules. + + #[macro_export] + macro_rules! provider_ring { + () => { + #[allow(unused_imports)] + use rustls_ring as provider; + #[allow(dead_code)] + const fn provider_is_aws_lc_rs() -> bool { + false + } + #[allow(dead_code)] + const fn provider_is_ring() -> bool { + true + } + #[allow(dead_code)] + const fn provider_is_fips() -> rustls::pki_types::FipsStatus { + rustls::pki_types::FipsStatus::Unvalidated + } + #[allow(dead_code)] + const ALL_VERSIONS: [rustls::crypto::CryptoProvider; 2] = [ + provider::DEFAULT_TLS12_PROVIDER, + provider::DEFAULT_TLS13_PROVIDER, + ]; + }; + } + + #[macro_export] + macro_rules! provider_aws_lc_rs { + () => { + #[allow(unused_imports)] + use rustls_aws_lc_rs as provider; + #[allow(dead_code)] + const fn provider_is_aws_lc_rs() -> bool { + true + } + #[allow(dead_code)] + const fn provider_is_ring() -> bool { + false + } + #[allow(dead_code)] + const fn provider_is_fips() -> rustls::pki_types::FipsStatus { + if cfg!(feature = "fips") { + rustls::pki_types::FipsStatus::Pending + } else { + rustls::pki_types::FipsStatus::Unvalidated + } + } + #[allow(dead_code)] + const ALL_VERSIONS: [rustls::crypto::CryptoProvider; 2] = [ + provider::DEFAULT_TLS12_PROVIDER, + provider::DEFAULT_TLS13_PROVIDER, + ]; + }; + } +} + +/// Deeply inefficient, test-only TLS encoding helpers +pub mod encoding { + use rustls::crypto::kx::NamedGroup; + use rustls::crypto::{CipherSuite, SignatureScheme}; + use rustls::enums::{ContentType, HandshakeType, ProtocolVersion}; + use rustls::error::AlertDescription; + + /// Return a client hello with mandatory extensions added to `extensions` + /// + /// The returned bytes are handshake-framed, but not message-framed. + pub fn basic_client_hello(mut extensions: Vec) -> Vec { + extensions.push(Extension::new_kx_groups()); + extensions.push(Extension::new_sig_algs()); + extensions.push(Extension::new_versions()); + extensions.push(Extension::new_dummy_key_share()); + client_hello_with_extensions(extensions) + } + + /// Return a client hello with exactly `extensions` + /// + /// The returned bytes are handshake-framed, but not message-framed. + pub fn client_hello_with_extensions(extensions: Vec) -> Vec { + client_hello( + ProtocolVersion::TLSv1_2, + &[0u8; 32], + &[0], + vec![ + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS13_AES_128_GCM_SHA256, + ], + extensions, + ) + } + + pub fn client_hello( + legacy_version: ProtocolVersion, + random: &[u8; 32], + session_id: &[u8], + cipher_suites: Vec, + extensions: Vec, + ) -> Vec { + let mut out = vec![]; + + out.extend_from_slice(&legacy_version.to_array()); + out.extend_from_slice(random); + out.extend_from_slice(session_id); + out.extend(len_u16(vector_of( + cipher_suites + .into_iter() + .map(|cs| cs.to_array()), + ))); + out.extend_from_slice(&[0x01, 0x00]); // only null compression + + let mut exts = vec![]; + for e in extensions { + exts.extend_from_slice(&e.typ.to_be_bytes()); + exts.extend_from_slice(&(e.body.len() as u16).to_be_bytes()); + exts.extend_from_slice(&e.body); + } + + out.extend(len_u16(exts)); + handshake_framing(HandshakeType::ClientHello, out) + } + + pub fn server_hello( + legacy_version: ProtocolVersion, + random: &[u8; 32], + session_id: &[u8], + cipher_suite: CipherSuite, + extensions: Vec, + ) -> Vec { + let mut out = vec![]; + + out.extend_from_slice(&legacy_version.to_array()); + out.extend_from_slice(random); + out.extend_from_slice(session_id); + out.extend_from_slice(&cipher_suite.to_array()); + out.extend_from_slice(&[0x00]); // null compression + + let mut exts = vec![]; + for e in extensions { + exts.extend_from_slice(&e.typ.to_be_bytes()); + exts.extend_from_slice(&(e.body.len() as u16).to_be_bytes()); + exts.extend_from_slice(&e.body); + } + + out.extend(len_u16(exts)); + handshake_framing(HandshakeType::ServerHello, out) + } + + /// Apply handshake framing to `body`. + /// + /// This does not do fragmentation. + pub fn handshake_framing(ty: HandshakeType, body: Vec) -> Vec { + let mut body = len_u24(body); + body.splice(0..0, ty.to_array()); + body + } + + /// Apply message framing to `body`. + pub fn message_framing(ty: ContentType, vers: ProtocolVersion, body: Vec) -> Vec { + let mut body = len_u16(body); + body.splice(0..0, vers.to_array()); + body.splice(0..0, ty.to_array()); + body + } + + #[derive(Clone)] + pub struct Extension { + pub typ: u16, + pub body: Vec, + } + + impl Extension { + pub fn new_sig_algs() -> Self { + Self { + typ: Self::SIGNATURE_ALGORITHMS, + body: len_u16(vector_of( + [ + SignatureScheme::RSA_PKCS1_SHA256, + SignatureScheme::ECDSA_NISTP256_SHA256, + ] + .map(|s| s.to_array()), + )), + } + } + + pub fn new_kx_groups() -> Self { + Self { + typ: Self::ELLIPTIC_CURVES, + body: len_u16(vector_of([NamedGroup::secp256r1.to_array()])), + } + } + + pub fn new_versions() -> Self { + Self { + typ: Self::SUPPORTED_VERSIONS, + body: len_u8(vector_of( + [ProtocolVersion::TLSv1_3, ProtocolVersion::TLSv1_2].map(|v| v.to_array()), + )), + } + } + + pub fn new_dummy_key_share() -> Self { + const SOME_POINT_ON_P256: &[u8] = &[ + 4, 41, 39, 177, 5, 18, 186, 227, 237, 220, 254, 70, 120, 40, 18, 139, 173, 41, 3, + 38, 153, 25, 247, 8, 96, 105, 200, 196, 223, 108, 115, 40, 56, 199, 120, 121, 100, + 234, 172, 0, 229, 146, 31, 177, 73, 138, 96, 244, 96, 103, 102, 179, 217, 104, 80, + 1, 85, 141, 26, 151, 78, 115, 65, 81, 62, + ]; + + let mut share = len_u16(SOME_POINT_ON_P256.to_vec()); + share.splice(0..0, NamedGroup::secp256r1.to_array()); + + Self { + typ: Self::KEY_SHARE, + body: len_u16(share), + } + } + + pub fn new_quic_transport_params(body: &[u8]) -> Self { + Self { + typ: Self::TRANSPORT_PARAMETERS, + body: len_u16(body.to_vec()), + } + } + + pub fn new_alpn(body: &[u8]) -> Self { + Self { + typ: Self::ALPN, + body: len_u16(body.to_vec()), + } + } + + pub const ELLIPTIC_CURVES: u16 = 0x000a; + pub const SIGNATURE_ALGORITHMS: u16 = 0x000d; + pub const SUPPORTED_VERSIONS: u16 = 0x002b; + pub const KEY_SHARE: u16 = 0x0033; + pub const TRANSPORT_PARAMETERS: u16 = 0x0039; + pub const ALPN: u16 = 0x0010; + } + + /// Return a full TLS message containing a fatal alert. + pub fn alert(desc: AlertDescription, suffix: &[u8]) -> Vec { + let mut body = vec![ALERT_LEVEL_FATAL, desc.into()]; + body.extend_from_slice(suffix); + message_framing(ContentType::Alert, ProtocolVersion::TLSv1_2, body) + } + + /// Return a full TLS message containing a warning alert. + pub fn warning_alert(desc: AlertDescription) -> Vec { + message_framing( + ContentType::Alert, + ProtocolVersion::TLSv1_2, + vec![ALERT_LEVEL_WARNING, desc.into()], + ) + } + + /// Prefix with u8 length + pub fn len_u8(mut body: Vec) -> Vec { + body.splice(0..0, [body.len() as u8]); + body + } + + /// Prefix with u16 length + pub fn len_u16(mut body: Vec) -> Vec { + body.splice(0..0, (body.len() as u16).to_be_bytes()); + body + } + + /// Prefix with u24 length + pub fn len_u24(mut body: Vec) -> Vec { + let len = (body.len() as u32).to_be_bytes(); + body.insert(0, len[1]); + body.insert(1, len[2]); + body.insert(2, len[3]); + body + } + + /// Encode each of `items` + pub fn vector_of(items: impl IntoIterator) -> Vec { + items.into_iter().flatten().collect() + } + + const ALERT_LEVEL_WARNING: u8 = 1; + const ALERT_LEVEL_FATAL: u8 = 2; +} diff --git a/rustls-test/tests/api.rs b/rustls-test/tests/api.rs new file mode 100644 index 00000000000..7ac6f6dc994 --- /dev/null +++ b/rustls-test/tests/api.rs @@ -0,0 +1,141 @@ +#![warn(clippy::assertions_on_result_states)] + +use core::cell::RefCell; + +#[cfg(feature = "ring")] +#[path = "."] +mod tests_with_ring { + use super::*; + + rustls_test::provider_ring!(); + + #[path = "api/client_cert_verifier.rs"] + mod client_cert_verifier; + #[path = "api/compress.rs"] + mod compress; + #[path = "api/crypto.rs"] + mod crypto; + #[path = "api/ffdhe.rs"] + mod ffdhe; + #[path = "api/io.rs"] + mod io; + #[path = "api/kx.rs"] + mod kx; + #[path = "api/quic.rs"] + mod quic; + #[path = "api/raw_keys.rs"] + mod raw_keys; + #[path = "api/resolve.rs"] + mod resolve; + #[path = "api/resume.rs"] + mod resume; + #[path = "api/server_cert_verifier.rs"] + mod server_cert_verifier; + #[path = "api/api.rs"] + mod tests; +} + +#[cfg(feature = "aws-lc-rs")] +#[path = "."] +mod tests_with_aws_lc_rs { + use super::*; + + rustls_test::provider_aws_lc_rs!(); + + #[path = "api/client_cert_verifier.rs"] + mod client_cert_verifier; + #[path = "api/compress.rs"] + mod compress; + #[path = "api/crypto.rs"] + mod crypto; + #[path = "api/ffdhe.rs"] + mod ffdhe; + #[path = "api/io.rs"] + mod io; + #[path = "api/kx.rs"] + mod kx; + #[path = "api/quic.rs"] + mod quic; + #[path = "api/raw_keys.rs"] + mod raw_keys; + #[path = "api/resolve.rs"] + mod resolve; + #[path = "api/resume.rs"] + mod resume; + #[path = "api/server_cert_verifier.rs"] + mod server_cert_verifier; + #[path = "api/api.rs"] + mod tests; +} + +// this must be outside tests_with_*, as we want +// one thread_local!, not one per provider. +thread_local!(static COUNTS: RefCell = RefCell::new(LogCounts::new())); + +struct CountingLogger; + +#[allow(dead_code)] +static LOGGER: CountingLogger = CountingLogger; + +#[allow(dead_code)] +impl CountingLogger { + fn install() { + let _ = log::set_logger(&LOGGER); + log::set_max_level(log::LevelFilter::Trace); + } + + fn reset() { + COUNTS.with(|c| { + c.borrow_mut().reset(); + }); + } +} + +impl log::Log for CountingLogger { + fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool { + true + } + + fn log(&self, record: &log::Record<'_>) { + println!("logging at {:?}: {:?}", record.level(), record.args()); + + COUNTS.with(|c| { + c.borrow_mut() + .add(record.level(), format!("{}", record.args())); + }); + } + + fn flush(&self) {} +} + +#[derive(Default, Debug)] +struct LogCounts { + trace: Vec, + debug: Vec, + info: Vec, + warn: Vec, + error: Vec, +} + +impl LogCounts { + fn new() -> Self { + Self { + ..Default::default() + } + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn add(&mut self, level: log::Level, message: String) { + match level { + log::Level::Trace => &mut self.trace, + log::Level::Debug => &mut self.debug, + log::Level::Info => &mut self.info, + log::Level::Warn => &mut self.warn, + log::Level::Error => &mut self.error, + } + .push(message); + } +} diff --git a/rustls-test/tests/api/api.rs b/rustls-test/tests/api/api.rs new file mode 100644 index 00000000000..8a3c088a078 --- /dev/null +++ b/rustls-test/tests/api/api.rs @@ -0,0 +1,1774 @@ +//! Assorted public API tests. + +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] + +use core::fmt::Debug; +use std::borrow::Cow; +use std::io; +use std::sync::{Arc, Mutex}; + +use pki_types::{DnsName, FipsStatus, SubjectPublicKeyInfoDer}; +use provider::cipher_suite; +use rustls::client::Resumption; +use rustls::crypto::cipher::{EncodedMessage, Payload}; +use rustls::crypto::kx::NamedGroup; +use rustls::crypto::{ + CipherSuite, Credentials, CryptoProvider, Identity, InconsistentKeys, SelectedCredential, + SignatureScheme, Signer, SigningKey, +}; +use rustls::enums::{ApplicationProtocol, ContentType, HandshakeType, ProtocolVersion}; +use rustls::error::{AlertDescription, ApiMisuse, CertificateError, Error, PeerMisbehaved}; +use rustls::server::{ + Acceptor, ClientHello, ParsedCertificate, PreferServerOrder, ServerCredentialResolver, +}; +use rustls::{ + ClientConfig, ClientConnection, Connection as _, HandshakeKind, KeyingMaterialExporter, + ServerConfig, ServerConnection, SupportedCipherSuite, +}; +#[cfg(feature = "aws-lc-rs")] +use rustls::{ + client::{EchConfig, EchGreaseConfig, EchMode}, + pki_types::EchConfigListBytes, +}; +#[cfg(feature = "aws-lc-rs")] +use rustls_aws_lc_rs::hpke::ALL_SUPPORTED_SUITES; +use rustls_test::{ + Altered, ClientConfigExt, ClientStorage, ClientStorageOp, ErrorFromPeer, KeyType, + MockServerVerifier, RawTls, ServerConfigExt, do_handshake, do_handshake_until_error, + do_suite_and_kx_test, encoding, make_client_config, make_client_config_with_auth, make_pair, + make_pair_for_arc_configs, make_pair_for_configs, make_server_config, + make_server_config_with_mandatory_client_auth, provider_with_one_suite, provider_with_suites, + server_name, transfer, transfer_altered, unsafe_plaintext_crypto_provider, +}; + +use super::{ + ALL_VERSIONS, COUNTS, CountingLogger, provider, provider_is_aws_lc_rs, provider_is_fips, + provider_is_ring, +}; + +fn alpn_test_error( + server_protos: Vec>, + client_protos: Vec>, + agreed: Option>, + expected_error: Option, +) { + let mut server_config = make_server_config(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + server_config.alpn_protocols = server_protos; + + let server_config = Arc::new(server_config); + + for version_provider in ALL_VERSIONS { + let mut client_config = make_client_config(KeyType::Rsa2048, &version_provider); + client_config + .alpn_protocols + .clone_from(&client_protos); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + assert_eq!(client.alpn_protocol(), None); + assert_eq!(server.alpn_protocol(), None); + let error = do_handshake_until_error(&mut client, &mut server); + assert_eq!(client.alpn_protocol(), agreed.as_ref()); + assert_eq!(server.alpn_protocol(), agreed.as_ref()); + assert_eq!(error.err(), expected_error); + } +} + +fn alpn_test( + server_protos: Vec>, + client_protos: Vec>, + agreed: Option>, +) { + alpn_test_error(server_protos, client_protos, agreed, None) +} + +#[test] +fn alpn() { + // no support + alpn_test(vec![], vec![], None); + + // server support + alpn_test(vec![b"server-proto".into()], vec![], None); + + // client support + alpn_test(vec![], vec![b"client-proto".into()], None); + + // no overlap + alpn_test_error( + vec![b"server-proto".into()], + vec![b"client-proto".into()], + None, + Some(ErrorFromPeer::Server(Error::NoApplicationProtocol)), + ); + + // server chooses preference + alpn_test( + vec![b"server-proto".into(), b"client-proto".into()], + vec![b"client-proto".into(), b"server-proto".into()], + Some(b"server-proto".into()), + ); + + // case sensitive + alpn_test_error( + vec![b"PROTO".into()], + vec![b"proto".into()], + None, + Some(ErrorFromPeer::Server(Error::NoApplicationProtocol)), + ); +} + +#[test] +fn connection_level_alpn_protocols() { + let provider = provider::DEFAULT_PROVIDER; + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); + server_config.alpn_protocols = vec![b"h2".into(), b"http/1.1".into()]; + let server_config = Arc::new(server_config); + + // Config specifies `h2` + let mut client_config = make_client_config(KeyType::Rsa2048, &provider); + client_config.alpn_protocols = vec![b"h2".into()]; + let client_config = Arc::new(client_config); + + // Client relies on config-specified `h2`, server agrees + let mut client = client_config + .connect(server_name("localhost")) + .build() + .unwrap(); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); + do_handshake_until_error(&mut client, &mut server).unwrap(); + assert_eq!(client.alpn_protocol(), Some(&ApplicationProtocol::Http2)); + + // Specify `http/1.1` for the connection, server agrees + let mut client = client_config + .connect(server_name("localhost")) + .with_alpn(vec![ApplicationProtocol::Http11]) + .build() + .unwrap(); + let mut server = ServerConnection::new(server_config).unwrap(); + do_handshake_until_error(&mut client, &mut server).unwrap(); + assert_eq!(client.alpn_protocol(), Some(&ApplicationProtocol::Http11)); +} + +#[test] +fn server_selects_unoffered_alpn_checked() { + let result = unoffered_alpn_test(true); + assert_eq!( + result.err(), + Some(PeerMisbehaved::SelectedUnofferedApplicationProtocol.into()) + ); +} + +#[test] +fn server_selects_unoffered_alpn_unchecked() { + let result = unoffered_alpn_test(false); + assert_ne!( + result.err(), + Some(PeerMisbehaved::SelectedUnofferedApplicationProtocol.into()) + ); +} + +fn unoffered_alpn_test(check_selected_alpn: bool) -> Result { + let mut config = make_client_config(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + config.check_selected_alpn = check_selected_alpn; + let mut client = Arc::new(config) + .connect(server_name("localhost")) + .with_alpn(vec![ApplicationProtocol::Http11]) + .build() + .unwrap(); + client + .write_tls(&mut Vec::new()) + .unwrap(); + client + .read_tls( + &mut encoding::message_framing( + ContentType::Handshake, + ProtocolVersion::TLSv1_2, + encoding::server_hello( + ProtocolVersion::TLSv1_2, + &[b'a'; 32], + &[0], + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + vec![encoding::Extension::new_alpn(b"\x05blorp")], + ), + ) + .as_slice(), + ) + .unwrap(); + client.process_new_packets() +} + +fn version_test( + client_versions: &[ProtocolVersion], + server_versions: &[ProtocolVersion], + result: Option, +) { + let provider = provider::DEFAULT_PROVIDER; + let client_provider = apply_versions(provider.clone(), client_versions); + let server_provider = apply_versions(provider, server_versions); + + let client_config = make_client_config(KeyType::Rsa2048, &client_provider); + let server_config = make_server_config(KeyType::Rsa2048, &server_provider); + + println!("version {client_versions:?} {server_versions:?} -> {result:?}"); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + assert_eq!(client.protocol_version(), None); + assert_eq!(server.protocol_version(), None); + if result.is_none() { + let err = do_handshake_until_error(&mut client, &mut server); + assert!(err.is_err()); + } else { + do_handshake(&mut client, &mut server); + assert_eq!(client.protocol_version(), result); + assert_eq!(server.protocol_version(), result); + } +} + +fn apply_versions(provider: CryptoProvider, versions: &[ProtocolVersion]) -> CryptoProvider { + match versions { + [] + | [ProtocolVersion::TLSv1_3, ProtocolVersion::TLSv1_2] + | [ProtocolVersion::TLSv1_2, ProtocolVersion::TLSv1_3] => provider, + [ProtocolVersion::TLSv1_3] => CryptoProvider { + tls12_cipher_suites: Cow::Borrowed(&[]), + ..provider + }, + [ProtocolVersion::TLSv1_2] => CryptoProvider { + tls13_cipher_suites: Cow::Borrowed(&[]), + ..provider + }, + _ => panic!("unhandled versions {versions:?}"), + } +} + +#[test] +fn versions() { + // default -> 1.3 + version_test(&[], &[], Some(ProtocolVersion::TLSv1_3)); + + // client default, server 1.2 -> 1.2 + version_test( + &[], + &[ProtocolVersion::TLSv1_2], + Some(ProtocolVersion::TLSv1_2), + ); + + // client 1.2, server default -> 1.2 + version_test( + &[ProtocolVersion::TLSv1_2], + &[], + Some(ProtocolVersion::TLSv1_2), + ); + + // client 1.2, server 1.3 -> fail + version_test( + &[ProtocolVersion::TLSv1_2], + &[ProtocolVersion::TLSv1_3], + None, + ); + + // client 1.3, server 1.2 -> fail + version_test( + &[ProtocolVersion::TLSv1_3], + &[ProtocolVersion::TLSv1_2], + None, + ); + + // client 1.3, server 1.2+1.3 -> 1.3 + version_test( + &[ProtocolVersion::TLSv1_3], + &[ProtocolVersion::TLSv1_2, ProtocolVersion::TLSv1_3], + Some(ProtocolVersion::TLSv1_3), + ); + + // client 1.2+1.3, server 1.2 -> 1.2 + version_test( + &[ProtocolVersion::TLSv1_3, ProtocolVersion::TLSv1_2], + &[ProtocolVersion::TLSv1_2], + Some(ProtocolVersion::TLSv1_2), + ); +} + +#[test] +fn config_builder_for_client_rejects_empty_kx_groups() { + assert_eq!( + ClientConfig::builder( + CryptoProvider { + kx_groups: Cow::Borrowed(&[]), + ..provider::DEFAULT_PROVIDER + } + .into() + ) + .with_root_certificates(KeyType::EcdsaP256.client_root_store()) + .with_no_client_auth() + .err(), + Some(ApiMisuse::NoKeyExchangeGroupsConfigured.into()) + ); +} + +#[test] +fn config_builder_for_client_rejects_empty_cipher_suites() { + assert_eq!( + ClientConfig::builder( + CryptoProvider { + tls12_cipher_suites: Cow::Borrowed(&[]), + tls13_cipher_suites: Cow::Borrowed(&[]), + ..provider::DEFAULT_PROVIDER + } + .into() + ) + .with_root_certificates(KeyType::EcdsaP256.client_root_store()) + .with_no_client_auth() + .err(), + Some(ApiMisuse::NoCipherSuitesConfigured.into()) + ); +} + +#[test] +fn config_builder_for_server_rejects_empty_kx_groups() { + assert_eq!( + ServerConfig::builder( + CryptoProvider { + kx_groups: Cow::Borrowed(&[]), + ..provider::DEFAULT_PROVIDER + } + .into() + ) + .with_no_client_auth() + .with_single_cert(KeyType::EcdsaP256.identity(), KeyType::EcdsaP256.key()) + .err(), + Some(ApiMisuse::NoKeyExchangeGroupsConfigured.into()) + ); +} + +#[test] +fn config_builder_for_server_rejects_empty_cipher_suites() { + assert_eq!( + ServerConfig::builder( + CryptoProvider { + tls12_cipher_suites: Cow::Borrowed(&[]), + tls13_cipher_suites: Cow::Borrowed(&[]), + ..provider::DEFAULT_PROVIDER + } + .into() + ) + .with_no_client_auth() + .with_single_cert(KeyType::EcdsaP256.identity(), KeyType::EcdsaP256.key()) + .err(), + Some(ApiMisuse::NoCipherSuitesConfigured.into()) + ); +} + +#[test] +fn config_builder_for_client_with_time() { + ClientConfig::builder_with_details( + provider::DEFAULT_PROVIDER.into(), + Arc::new(rustls::time_provider::DefaultTimeProvider), + ); +} + +#[test] +fn config_builder_for_server_with_time() { + ServerConfig::builder_with_details( + provider::DEFAULT_PROVIDER.into(), + Arc::new(rustls::time_provider::DefaultTimeProvider), + ); +} + +#[test] +fn client_can_get_server_cert() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + for version_provider in ALL_VERSIONS { + let client_config = make_client_config(*kt, &version_provider); + let (mut client, mut server) = + make_pair_for_configs(client_config, make_server_config(*kt, &provider)); + do_handshake(&mut client, &mut server); + assert_eq!(client.peer_identity().unwrap(), &*kt.identity()); + } + } +} + +#[test] +fn client_can_get_server_cert_after_resumption() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let server_config = make_server_config(*kt, &provider); + for version_provider in ALL_VERSIONS { + let client_config = make_client_config(*kt, &version_provider); + let (mut client, mut server) = + make_pair_for_configs(client_config.clone(), server_config.clone()); + do_handshake(&mut client, &mut server); + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Full)); + + let original_certs = client.peer_identity(); + + let (mut client, mut server) = + make_pair_for_configs(client_config.clone(), server_config.clone()); + do_handshake(&mut client, &mut server); + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Resumed)); + + let resumed_certs = client.peer_identity(); + + assert_eq!(original_certs, resumed_certs); + } + } +} + +#[test] +fn server_can_get_client_cert() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let server_config = Arc::new(make_server_config_with_mandatory_client_auth( + *kt, &provider, + )); + + for version_provider in ALL_VERSIONS { + let client_config = make_client_config_with_auth(*kt, &version_provider); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + assert_eq!(server.peer_identity().unwrap(), &*kt.client_identity()); + } + } +} + +#[test] +fn server_can_get_client_cert_after_resumption() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let server_config = Arc::new(make_server_config_with_mandatory_client_auth( + *kt, &provider, + )); + + for version_provider in ALL_VERSIONS { + let client_config = make_client_config_with_auth(*kt, &version_provider); + let client_config = Arc::new(client_config); + let (mut client, mut server) = + make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + let original_certs = server.peer_identity(); + + let (mut client, mut server) = + make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + let resumed_certs = server.peer_identity(); + assert_eq!(original_certs, resumed_certs); + } + } +} + +#[test] +fn test_config_builders_debug() { + if !provider_is_ring() { + return; + } + + let b = ServerConfig::builder( + CryptoProvider { + tls13_cipher_suites: Cow::Owned(vec![cipher_suite::TLS13_CHACHA20_POLY1305_SHA256]), + kx_groups: Cow::Owned(vec![provider::kx_group::X25519]), + ..provider::DEFAULT_PROVIDER + } + .into(), + ); + let _ = format!("{b:?}"); + let b = ServerConfig::builder(provider::DEFAULT_TLS13_PROVIDER.into()); + let _ = format!("{b:?}"); + let b = b.with_no_client_auth(); + let _ = format!("{b:?}"); + + let b = ClientConfig::builder( + CryptoProvider { + tls13_cipher_suites: Cow::Owned(vec![cipher_suite::TLS13_CHACHA20_POLY1305_SHA256]), + kx_groups: Cow::Owned(vec![provider::kx_group::X25519]), + ..provider::DEFAULT_PROVIDER + } + .into(), + ); + let _ = format!("{b:?}"); + let b = ClientConfig::builder(provider::DEFAULT_TLS13_PROVIDER.into()); + let _ = format!("{b:?}"); +} + +#[test] +fn test_tls13_valid_early_plaintext_alert() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + // Perform the start of a TLS 1.3 handshake, sending a client hello to the server. + // The client will not have written a CCS or any encrypted messages to the server yet. + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + // Inject a plaintext alert from the client. The server should accept this since: + // * It hasn't decrypted any messages from the peer yet. + // * The message content type is Alert. + // * The payload size is indicative of a plaintext alert message. + // * The negotiated protocol version is TLS 1.3. + server + .read_tls(&mut io::Cursor::new(&encoding::alert( + AlertDescription::UnknownCa, + &[], + ))) + .unwrap(); + + // The server should process the plaintext alert without error. + assert_eq!( + server.process_new_packets(), + Err(Error::AlertReceived(AlertDescription::UnknownCa)), + ); +} + +#[test] +fn test_tls13_too_short_early_plaintext_alert() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + // Perform the start of a TLS 1.3 handshake, sending a client hello to the server. + // The client will not have written a CCS or any encrypted messages to the server yet. + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + // Inject a plaintext alert from the client. The server should attempt to decrypt this message + // because the payload length is too large to be considered an early plaintext alert. + server + .read_tls(&mut io::Cursor::new(&encoding::alert( + AlertDescription::UnknownCa, + &[0xff], + ))) + .unwrap(); + + // The server should produce a decrypt error trying to decrypt the plaintext alert. + assert_eq!(server.process_new_packets(), Err(Error::DecryptError),); +} + +#[test] +fn test_tls13_late_plaintext_alert() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + // Complete a bi-directional TLS1.3 handshake. After this point no plaintext messages + // should occur. + do_handshake(&mut client, &mut server); + + // Inject a plaintext alert from the client. The server should attempt to decrypt this message. + server + .read_tls(&mut io::Cursor::new(&encoding::alert( + AlertDescription::UnknownCa, + &[], + ))) + .unwrap(); + + // The server should produce a decrypt error, trying to decrypt a plaintext alert. + assert_eq!(server.process_new_packets(), Err(Error::DecryptError)); +} + +#[test] +fn client_error_is_sticky() { + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + client + .read_tls(&mut b"\x16\x03\x03\x00\x08\x0f\x00\x00\x04junk".as_ref()) + .unwrap(); + client + .process_new_packets() + .unwrap_err(); + client + .process_new_packets() + .unwrap_err(); +} + +#[test] +fn server_error_is_sticky() { + let (_, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + server + .read_tls(&mut b"\x16\x03\x03\x00\x08\x0f\x00\x00\x04junk".as_ref()) + .unwrap(); + server + .process_new_packets() + .unwrap_err(); + server + .process_new_packets() + .unwrap_err(); +} + +#[allow(clippy::unnecessary_operation)] +#[test] +fn server_is_send_and_sync() { + let (_, server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + &server as &dyn Send; + &server as &dyn Sync; +} + +#[allow(clippy::unnecessary_operation)] +#[test] +fn client_is_send_and_sync() { + let (client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + &client as &dyn Send; + &client as &dyn Sync; +} + +#[test] +fn server_config_is_clone() { + let _ = make_server_config(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); +} + +#[test] +fn client_config_is_clone() { + let _ = make_client_config(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); +} + +#[test] +fn client_connection_is_debug() { + let (client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + println!("{client:?}"); +} + +#[test] +fn server_connection_is_debug() { + let (_, server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + println!("{server:?}"); +} + +#[test] +fn server_exposes_offered_sni() { + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_PROVIDER; + for version_provider in ALL_VERSIONS { + let client_config = Arc::new(make_client_config(kt, &version_provider)); + let mut client = client_config + .connect(server_name("second.testserver.com")) + .build() + .unwrap(); + + let mut server = + ServerConnection::new(Arc::new(make_server_config(kt, &provider))).unwrap(); + + assert_eq!(None, server.server_name()); + do_handshake(&mut client, &mut server); + assert_eq!( + Some(&DnsName::try_from("second.testserver.com").unwrap()), + server.server_name() + ); + } +} + +#[test] +fn server_exposes_offered_sni_smashed_to_lowercase() { + // webpki actually does this for us in its DnsName type + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_PROVIDER; + for version_provider in ALL_VERSIONS { + let client_config = Arc::new(make_client_config(kt, &version_provider)); + let mut client = client_config + .connect(server_name("SECOND.TESTServer.com")) + .build() + .unwrap(); + + let mut server = + ServerConnection::new(Arc::new(make_server_config(kt, &provider))).unwrap(); + + assert_eq!(None, server.server_name()); + do_handshake(&mut client, &mut server); + assert_eq!( + Some(&DnsName::try_from("second.testserver.com").unwrap()), + server.server_name() + ); + } +} + +#[test] +fn test_keys_match() { + // Consistent: Both of these should have the same SPKI values + Credentials::new(KeyType::Rsa2048.identity(), Box::new(SigningKeySomeSpki)).unwrap(); + + // Inconsistent: These should not have the same SPKI values + assert!(matches!( + Credentials::new(KeyType::EcdsaP256.identity(), Box::new(SigningKeySomeSpki)), + Err(Error::InconsistentKeys(InconsistentKeys::KeyMismatch)) + )); + + // Unknown: This signing key returns None for its SPKI, so we can't tell if the certified key is consistent + assert!(matches!( + Credentials::new(KeyType::Rsa2048.identity(), Box::new(SigningKeyNoneSpki)), + Err(Error::InconsistentKeys(InconsistentKeys::Unknown)) + )); +} + +/// Represents a SigningKey that returns None for its SPKI via the default impl. +#[derive(Debug)] +struct SigningKeyNoneSpki; + +impl SigningKey for SigningKeyNoneSpki { + fn choose_scheme(&self, _offered: &[SignatureScheme]) -> Option> { + unimplemented!("Not meant to be called during tests") + } + + fn public_key(&self) -> Option> { + None + } +} + +/// Represents a SigningKey that returns Some for its SPKI. +#[derive(Debug)] +struct SigningKeySomeSpki; + +impl SigningKey for SigningKeySomeSpki { + fn public_key(&self) -> Option> { + let Identity::X509(identity) = &*KeyType::Rsa2048.identity() else { + panic!("expected X509 identity"); + }; + + let cert = ParsedCertificate::try_from(&identity.end_entity).unwrap(); + Some( + cert.subject_public_key_info() + .into_owned(), + ) + } + + fn choose_scheme(&self, _offered: &[SignatureScheme]) -> Option> { + unimplemented!("Not meant to be called during tests") + } +} + +fn do_exporter_test( + client_config: ClientConfig, + server_config: ServerConfig, +) -> (KeyingMaterialExporter, KeyingMaterialExporter) { + let mut client_secret = [0u8; 64]; + let mut server_secret = [0u8; 64]; + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + assert_eq!(Some(Error::HandshakeNotComplete), client.exporter().err()); + assert_eq!(Some(Error::HandshakeNotComplete), server.exporter().err()); + do_handshake(&mut client, &mut server); + + let client_exporter = client.exporter().unwrap(); + let server_exporter = server.exporter().unwrap(); + + assert_eq!( + client.exporter().err(), + Some(Error::ApiMisuse(ApiMisuse::ExporterAlreadyUsed)), + ); + assert_eq!( + server.exporter().err(), + Some(Error::ApiMisuse(ApiMisuse::ExporterAlreadyUsed)), + ); + + client_exporter + .derive(b"label", Some(b"context"), &mut client_secret) + .unwrap(); + server_exporter + .derive(b"label", Some(b"context"), &mut server_secret) + .unwrap(); + assert_eq!(client_secret.to_vec(), server_secret.to_vec()); + + let mut empty = vec![]; + assert_eq!( + client_exporter + .derive(b"label", Some(b"context"), &mut empty) + .err(), + Some(ApiMisuse::ExporterOutputZeroLength.into()) + ); + assert_eq!( + server_exporter + .derive(b"label", Some(b"context"), &mut empty) + .err(), + Some(ApiMisuse::ExporterOutputZeroLength.into()) + ); + + client_exporter + .derive(b"label", None, &mut client_secret) + .unwrap(); + assert_ne!(client_secret.to_vec(), server_secret.to_vec()); + server_exporter + .derive(b"label", None, &mut server_secret) + .unwrap(); + assert_eq!(client_secret.to_vec(), server_secret.to_vec()); + + (client_exporter, server_exporter) +} + +#[test] +fn test_tls12_exporter() { + let provider = provider::DEFAULT_TLS12_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let client_config = make_client_config(*kt, &provider); + let server_config = make_server_config(*kt, &provider); + + let (client_exporter, _) = do_exporter_test(client_config, server_config); + + // additionally, tls1.2 contexts over 0xffff bytes in length are not prefix-free, + // so outlaw them. + client_exporter + .derive(b"label", Some(&[0; 0xffff]), &mut [0]) + .unwrap(); + assert_eq!( + Error::ApiMisuse(ApiMisuse::ExporterContextTooLong), + client_exporter + .derive(b"label", Some(&[0; 0x10000]), &mut [0]) + .unwrap_err() + ); + } +} + +#[test] +fn test_tls13_exporter() { + let provider = provider::DEFAULT_TLS13_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let client_config = make_client_config(*kt, &provider); + let server_config = make_server_config(*kt, &provider); + + do_exporter_test(client_config, server_config); + } +} + +#[test] +fn test_tls13_exporter_maximum_output_length() { + let provider = provider::DEFAULT_TLS13_PROVIDER; + let client_config = make_client_config(KeyType::EcdsaP256, &provider); + let server_config = make_server_config(KeyType::EcdsaP256, &provider); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake(&mut client, &mut server); + + assert_eq!( + client.negotiated_cipher_suite(), + Some(find_suite(CipherSuite::TLS13_AES_128_GCM_SHA256)) + ); + + let client_exporter = client.exporter().unwrap(); + let server_exporter = server.exporter().unwrap(); + + let mut maximum_allowed_output_client = [0u8; 255 * 32]; + let mut maximum_allowed_output_server = [0u8; 255 * 32]; + + client_exporter + .derive( + b"label", + Some(b"context"), + &mut maximum_allowed_output_client, + ) + .unwrap(); + server_exporter + .derive( + b"label", + Some(b"context"), + &mut maximum_allowed_output_server, + ) + .unwrap(); + + assert_eq!(maximum_allowed_output_client, maximum_allowed_output_server); + + let mut too_long_output = [0u8; 255 * 48 + 1]; + assert_eq!( + client_exporter + .derive(b"label", Some(b"context"), &mut too_long_output) + .err(), + Some(ApiMisuse::ExporterOutputTooLong.into()) + ); + assert_eq!( + server_exporter + .derive(b"label", Some(b"context"), &mut too_long_output) + .err(), + Some(ApiMisuse::ExporterOutputTooLong.into()) + ); +} + +fn find_suite(suite: CipherSuite) -> SupportedCipherSuite { + if let Some(found) = provider::ALL_TLS12_CIPHER_SUITES + .iter() + .find(|cs| cs.common.suite == suite) + { + return SupportedCipherSuite::Tls12(found); + } + + if let Some(found) = provider::ALL_TLS13_CIPHER_SUITES + .iter() + .find(|cs| cs.common.suite == suite) + { + return SupportedCipherSuite::Tls13(found); + } + + panic!("find_suite given unsupported suite {suite:?}"); +} + +fn test_ciphersuites() -> Vec<(ProtocolVersion, KeyType, CipherSuite)> { + let mut v = vec![ + ( + ProtocolVersion::TLSv1_3, + KeyType::Rsa2048, + CipherSuite::TLS13_AES_256_GCM_SHA384, + ), + ( + ProtocolVersion::TLSv1_3, + KeyType::Rsa2048, + CipherSuite::TLS13_AES_128_GCM_SHA256, + ), + ( + ProtocolVersion::TLSv1_2, + KeyType::EcdsaP384, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + ), + ( + ProtocolVersion::TLSv1_2, + KeyType::EcdsaP384, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + ), + ( + ProtocolVersion::TLSv1_2, + KeyType::Rsa2048, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + ), + ( + ProtocolVersion::TLSv1_2, + KeyType::Rsa2048, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + ), + ]; + + if matches!(provider_is_fips(), FipsStatus::Unvalidated) { + v.extend_from_slice(&[ + ( + ProtocolVersion::TLSv1_3, + KeyType::Rsa2048, + CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, + ), + ( + ProtocolVersion::TLSv1_2, + KeyType::EcdsaP256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + ), + ( + ProtocolVersion::TLSv1_2, + KeyType::Rsa2048, + CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + ), + ]); + } + + v +} + +#[test] +fn negotiated_ciphersuite_default() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + do_suite_and_kx_test( + make_client_config(*kt, &provider), + make_server_config(*kt, &provider), + find_suite(CipherSuite::TLS13_AES_128_GCM_SHA256), + expected_kx_for_version(ProtocolVersion::TLSv1_3), + ProtocolVersion::TLSv1_3, + ); + } +} + +#[test] +fn all_suites_covered() { + assert_eq!( + provider::DEFAULT_TLS12_CIPHER_SUITES.len() + provider::DEFAULT_TLS13_CIPHER_SUITES.len(), + test_ciphersuites().len() + ); +} + +#[test] +fn negotiated_ciphersuite_client() { + for (version, kt, suite) in test_ciphersuites() { + let scs = find_suite(suite); + let client_config = + ClientConfig::builder(provider_with_one_suite(&provider::DEFAULT_PROVIDER, scs).into()) + .finish(kt); + + do_suite_and_kx_test( + client_config, + make_server_config(kt, &provider::DEFAULT_PROVIDER), + scs, + expected_kx_for_version(version), + version, + ); + } +} + +#[test] +fn negotiated_ciphersuite_server() { + for (version, kt, suite) in test_ciphersuites() { + let scs = find_suite(suite); + let server_config = + ServerConfig::builder(provider_with_one_suite(&provider::DEFAULT_PROVIDER, scs).into()) + .finish(kt); + + do_suite_and_kx_test( + make_client_config(kt, &provider::DEFAULT_PROVIDER), + server_config, + scs, + expected_kx_for_version(version), + version, + ); + } +} + +#[test] +fn negotiated_ciphersuite_server_ignoring_client_preference() { + for (version, kt, suite) in test_ciphersuites() { + let scs = find_suite(suite); + + // choose a distinct other suite for the same version + let scs_other = match (version, scs.suite()) { + (_, CipherSuite::TLS13_AES_256_GCM_SHA384) => { + find_suite(CipherSuite::TLS13_AES_128_GCM_SHA256) + } + (ProtocolVersion::TLSv1_3, _) => find_suite(CipherSuite::TLS13_AES_256_GCM_SHA384), + (_, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) => { + find_suite(CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + } + (_, _) => find_suite(CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), + }; + assert_ne!(scs, scs_other); + + let mut server_config = ServerConfig::builder( + provider_with_suites(&provider::DEFAULT_PROVIDER, &[scs, scs_other]).into(), + ) + .finish(kt); + server_config.cipher_suite_selector = &PreferServerOrder; + + let client_config = ClientConfig::builder( + provider_with_suites(&provider::DEFAULT_PROVIDER, &[scs_other, scs]).into(), + ) + .finish(kt); + + do_suite_and_kx_test( + client_config, + server_config, + scs, + expected_kx_for_version(version), + version, + ); + } +} + +fn expected_kx_for_version(version: ProtocolVersion) -> NamedGroup { + let is_fips = matches!( + provider_is_fips(), + FipsStatus::Pending | FipsStatus::Certified { .. } + ); + match (version, provider_is_aws_lc_rs(), is_fips) { + (ProtocolVersion::TLSv1_3, true, _) => NamedGroup::X25519MLKEM768, + (_, _, true) => NamedGroup::secp256r1, + (_, _, _) => NamedGroup::X25519, + } +} + +fn assert_lt(left: usize, right: usize) { + if left >= right { + panic!("expected {left} < {right}"); + } +} + +#[test] +fn connection_types_are_not_huge() { + // Arbitrary sizes + assert_lt(size_of::(), 1600); + assert_lt(size_of::(), 1600); +} + +#[test] +fn test_client_rejects_illegal_tls13_ccs() { + fn corrupt_ccs(msg: &mut EncodedMessage>) -> Altered { + if msg.typ == ContentType::ChangeCipherSpec { + println!("seen CCS {:?}", msg.typ); + return Altered::Raw(encoding::message_framing( + ContentType::ChangeCipherSpec, + ProtocolVersion::TLSv1_2, + vec![0x01, 0x02], + )); + } + Altered::InPlace + } + + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + transfer_altered(&mut server, corrupt_ccs, &mut client); + assert_eq!( + client.process_new_packets(), + Err(Error::PeerMisbehaved( + PeerMisbehaved::IllegalMiddleboxChangeCipherSpec + )) + ); +} + +#[test] +fn test_no_warning_logging_during_successful_sessions() { + CountingLogger::install(); + CountingLogger::reset(); + + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + for version_provider in ALL_VERSIONS { + let client_config = make_client_config(*kt, &version_provider); + let (mut client, mut server) = + make_pair_for_configs(client_config, make_server_config(*kt, &provider)); + do_handshake(&mut client, &mut server); + } + } + + if cfg!(feature = "log") { + COUNTS.with(|c| { + println!("After tests: {:?}", c.borrow()); + assert!(c.borrow().warn.is_empty()); + assert!(c.borrow().error.is_empty()); + assert!(c.borrow().info.is_empty()); + assert!(!c.borrow().trace.is_empty()); + assert!(!c.borrow().debug.is_empty()); + }); + } else { + COUNTS.with(|c| { + println!("After tests: {:?}", c.borrow()); + assert!(c.borrow().warn.is_empty()); + assert!(c.borrow().error.is_empty()); + assert!(c.borrow().info.is_empty()); + assert!(c.borrow().trace.is_empty()); + assert!(c.borrow().debug.is_empty()); + }); + } +} + +#[cfg(all(feature = "ring", feature = "aws-lc-rs"))] +#[test] +fn test_explicit_provider_selection() { + let client_config = + ClientConfig::builder(rustls_ring::DEFAULT_PROVIDER.into()).finish(KeyType::Rsa2048); + + let server_config = + ServerConfig::builder(rustls_aws_lc_rs::DEFAULT_PROVIDER.into()).finish(KeyType::Rsa2048); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake(&mut client, &mut server); +} + +#[derive(Debug)] +struct FaultyRandom { + // when empty, `fill_random` requests return `GetRandomFailed` + rand_queue: Mutex<&'static [u8]>, +} + +impl rustls::crypto::SecureRandom for FaultyRandom { + fn fill(&self, output: &mut [u8]) -> Result<(), rustls::crypto::GetRandomFailed> { + let mut queue = self.rand_queue.lock().unwrap(); + + println!( + "fill_random request for {} bytes (got {})", + output.len(), + queue.len() + ); + + if queue.len() < output.len() { + return Err(rustls::crypto::GetRandomFailed); + } + + let fixed_output = &queue[..output.len()]; + output.copy_from_slice(fixed_output); + *queue = &queue[output.len()..]; + Ok(()) + } +} + +#[test] +fn test_client_construction_fails_if_random_source_fails_in_first_request() { + static FAULTY_RANDOM: FaultyRandom = FaultyRandom { + rand_queue: Mutex::new(b""), + }; + + let client_config = ClientConfig::builder( + CryptoProvider { + secure_random: &FAULTY_RANDOM, + ..provider::DEFAULT_PROVIDER + } + .into(), + ) + .finish(KeyType::Rsa2048); + + assert_eq!( + Arc::new(client_config) + .connect(server_name("localhost")) + .build() + .unwrap_err(), + Error::FailedToGetRandomBytes + ); +} + +#[test] +fn test_client_construction_fails_if_random_source_fails_in_second_request() { + static FAULTY_RANDOM: FaultyRandom = FaultyRandom { + rand_queue: Mutex::new(b"nice random number generator huh"), + }; + + let client_config = ClientConfig::builder( + CryptoProvider { + secure_random: &FAULTY_RANDOM, + ..provider::DEFAULT_PROVIDER + } + .into(), + ) + .finish(KeyType::Rsa2048); + + assert_eq!( + Arc::new(client_config) + .connect(server_name("localhost")) + .build() + .unwrap_err(), + Error::FailedToGetRandomBytes + ); +} + +#[test] +fn test_client_construction_requires_66_bytes_of_random_material() { + static FAULTY_RANDOM: FaultyRandom = FaultyRandom { + rand_queue: Mutex::new( + b"nice random number generator !!!!!\ + it's really not very good is it?", + ), + }; + + let client_config = ClientConfig::builder( + CryptoProvider { + secure_random: &FAULTY_RANDOM, + ..provider::DEFAULT_PROVIDER + } + .into(), + ) + .finish(KeyType::Rsa2048); + + Arc::new(client_config) + .connect(server_name("localhost")) + .build() + .expect("check how much random material ClientConnection::new consumes"); +} + +#[test] +fn test_client_removes_tls12_session_if_server_sends_undecryptable_first_message() { + fn inject_corrupt_finished_message(msg: &mut EncodedMessage>) -> Altered { + if msg.typ == ContentType::ChangeCipherSpec { + // interdict "real" ChangeCipherSpec with its encoding, plus a faulty encrypted Finished. + let mut raw_change_cipher_spec = encoding::message_framing( + ContentType::ChangeCipherSpec, + ProtocolVersion::TLSv1_2, + vec![0x01], + ); + let mut corrupt_finished = encoding::message_framing( + ContentType::Handshake, + ProtocolVersion::TLSv1_2, + vec![0u8; 0x28], + ); + + let mut both = vec![]; + both.append(&mut raw_change_cipher_spec); + both.append(&mut corrupt_finished); + + Altered::Raw(both) + } else { + Altered::InPlace + } + } + + let provider = provider::DEFAULT_TLS12_PROVIDER; + let mut client_config = make_client_config(KeyType::Rsa2048, &provider); + let storage = Arc::new(ClientStorage::new()); + client_config.resumption = Resumption::store(storage.clone()); + let client_config = Arc::new(client_config); + let server_config = Arc::new(make_server_config(KeyType::Rsa2048, &provider)); + + // successful handshake to allow resumption + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + // resumption + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + transfer_altered(&mut server, inject_corrupt_finished_message, &mut client); + + // discard storage operations up to this point, to observe the one we want to test for. + storage.ops_and_reset(); + + // client cannot decrypt faulty Finished, and deletes saved session in case + // server resumption is buggy. + assert_eq!( + Some(Error::DecryptError), + client.process_new_packets().err() + ); + + assert!(matches!( + storage.ops()[0], + ClientStorageOp::RemoveTls12Session(_) + )); +} + +#[test] +fn test_client_fips_service_indicator() { + assert_eq!( + make_client_config(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER).fips(), + provider_is_fips() + ); +} + +#[test] +fn test_server_fips_service_indicator() { + assert_eq!( + make_server_config(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER).fips(), + provider_is_fips() + ); +} + +#[test] +fn test_connection_fips_service_indicator() { + let provider = provider::DEFAULT_PROVIDER; + let client_config = Arc::new(make_client_config(KeyType::Rsa2048, &provider)); + let server_config = Arc::new(make_server_config(KeyType::Rsa2048, &provider)); + let conn_pair = make_pair_for_arc_configs(&client_config, &server_config); + // Each connection's FIPS status should reflect the FIPS status of the config it was created + // from. + assert_eq!(client_config.fips(), conn_pair.0.fips()); + assert_eq!(server_config.fips(), conn_pair.1.fips()); +} + +#[test] +fn test_client_fips_service_indicator_includes_require_ems() { + if !matches!( + provider_is_fips(), + FipsStatus::Pending | FipsStatus::Certified { .. } + ) { + return; + } + + let mut client_config = make_client_config(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + assert!(matches!( + client_config.fips(), + FipsStatus::Pending | FipsStatus::Certified { .. } + )); + client_config.require_ems = false; + assert!(matches!(client_config.fips(), FipsStatus::Unvalidated)); +} + +#[test] +fn test_server_fips_service_indicator_includes_require_ems() { + if !matches!( + provider_is_fips(), + FipsStatus::Pending | FipsStatus::Certified { .. } + ) { + return; + } + + let mut server_config = make_server_config(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + assert!(matches!( + server_config.fips(), + FipsStatus::Pending | FipsStatus::Certified { .. } + )); + server_config.require_ems = false; + assert!(matches!(server_config.fips(), FipsStatus::Unvalidated)); +} + +#[cfg(feature = "aws-lc-rs")] +#[test] +fn test_client_fips_service_indicator_includes_ech_hpke_suite() { + if !matches!( + provider_is_fips(), + FipsStatus::Pending | FipsStatus::Certified { .. } + ) { + return; + } + + for suite in ALL_SUPPORTED_SUITES { + let suite_id = suite.suite(); + let config_path = format!( + "tests/data/{:?}-{:?}-{:?}-echconfigs.bin", + suite_id.kem, suite_id.sym.kdf_id, suite_id.sym.aead_id + ); + + let ech_config = EchConfig::new( + EchConfigListBytes::from(std::fs::read(&config_path).unwrap()), + &[*suite], + ) + .unwrap(); + + // A ECH client configuration should only be considered FIPS approved if the + // ECH HPKE suite is itself FIPS approved. + let config = ClientConfig::builder(provider::DEFAULT_TLS13_PROVIDER.into()) + .with_ech(EchMode::Enable(ech_config)); + let config = config.finish(KeyType::Rsa2048); + assert_eq!(config.fips(), suite.fips()); + + // The same applies if an ECH GREASE client configuration is used. + let (public_key, _) = suite.generate_key_pair().unwrap(); + let config = ClientConfig::builder(provider::DEFAULT_TLS13_PROVIDER.into()) + .with_ech(EchMode::Grease(EchGreaseConfig::new(*suite, public_key))); + let config = Arc::new(config.finish(KeyType::Rsa2048)); + assert_eq!(config.fips(), suite.fips()); + + // And a connection made from a client config should retain the fips status of the + // config w.r.t the HPKE suite. + let conn = config + .connect(server_name("example.org")) + .build() + .unwrap(); + assert_eq!(conn.fips(), suite.fips()); + } +} + +#[test] +fn test_illegal_server_renegotiation_attempt_after_tls13_handshake() { + let provider = provider::DEFAULT_TLS13_PROVIDER; + let client_config = make_client_config(KeyType::Rsa2048, &provider); + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); + server_config.enable_secret_extraction = true; + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake(&mut client, &mut server); + + let mut raw_server = RawTls::new_server(server); + + let msg = EncodedMessage { + typ: ContentType::Handshake, + version: ProtocolVersion::TLSv1_3, + payload: Payload::new(encoding::handshake_framing( + HandshakeType::HelloRequest, + vec![], + )), + }; + raw_server.encrypt_and_send(&msg, &mut client); + let err = client + .process_new_packets() + .unwrap_err(); + assert_eq!( + err, + Error::InappropriateHandshakeMessage { + expect_types: vec![HandshakeType::NewSessionTicket, HandshakeType::KeyUpdate], + got_type: HandshakeType::HelloRequest + } + ); +} + +#[test] +fn test_illegal_server_renegotiation_attempt_after_tls12_handshake() { + let provider = provider::DEFAULT_TLS12_PROVIDER; + let client_config = make_client_config(KeyType::Rsa2048, &provider); + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); + server_config.enable_secret_extraction = true; + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake(&mut client, &mut server); + + let mut raw_server = RawTls::new_server(server); + + let msg = EncodedMessage { + typ: ContentType::Handshake, + version: ProtocolVersion::TLSv1_3, + payload: Payload::new(encoding::handshake_framing( + HandshakeType::HelloRequest, + vec![], + )), + }; + + // one is allowed (and elicits a warning alert) + raw_server.encrypt_and_send(&msg, &mut client); + client.process_new_packets().unwrap(); + raw_server.receive_and_decrypt(&mut client, |m| { + assert_eq!(m.version, ProtocolVersion::TLSv1_2); + assert_eq!(m.typ, ContentType::Alert); + assert_eq!(m.payload, &[0x01, 100]); // Warning=1, NoRenegotiation=100 + }); + + // second is fatal + raw_server.encrypt_and_send(&msg, &mut client); + assert_eq!( + client + .process_new_packets() + .unwrap_err(), + Error::PeerMisbehaved(PeerMisbehaved::TooManyRenegotiationRequests) + ); +} + +#[test] +fn test_illegal_client_renegotiation_attempt_after_tls13_handshake() { + let provider = provider::DEFAULT_TLS13_PROVIDER; + let mut client_config = make_client_config(KeyType::Rsa2048, &provider); + client_config.enable_secret_extraction = true; + let server_config = make_server_config(KeyType::Rsa2048, &provider); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake(&mut client, &mut server); + + let mut raw_client = RawTls::new_client(client); + + let msg = EncodedMessage { + typ: ContentType::Handshake, + version: ProtocolVersion::TLSv1_3, + payload: Payload::new(encoding::basic_client_hello(vec![])), + }; + raw_client.encrypt_and_send(&msg, &mut server); + let err = server + .process_new_packets() + .unwrap_err(); + assert_eq!( + format!("{err:?}"), + "InappropriateHandshakeMessage { expect_types: [KeyUpdate], got_type: ClientHello }" + ); +} + +#[test] +fn test_illegal_client_renegotiation_attempt_during_tls12_handshake() { + let provider = provider::DEFAULT_TLS12_PROVIDER; + let server_config = make_server_config(KeyType::Rsa2048, &provider); + let client_config = make_client_config(KeyType::Rsa2048, &provider); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + let mut client_hello = vec![]; + client + .write_tls(&mut io::Cursor::new(&mut client_hello)) + .unwrap(); + + server + .read_tls(&mut io::Cursor::new(&client_hello)) + .unwrap(); + server + .read_tls(&mut io::Cursor::new(&client_hello)) + .unwrap(); + assert_eq!( + server + .process_new_packets() + .unwrap_err(), + Error::InappropriateHandshakeMessage { + expect_types: vec![HandshakeType::ClientKeyExchange], + got_type: HandshakeType::ClientHello + } + ); +} + +#[test] +fn tls13_packed_handshake() { + // transcript requires selection of X25519 + if matches!( + provider_is_fips(), + FipsStatus::Pending | FipsStatus::Certified { .. } + ) { + return; + } + + // regression test for https://github.com/rustls/rustls/issues/2040 + // (did not affect the buffered api) + let client_config = Arc::new( + ClientConfig::builder(unsafe_plaintext_crypto_provider(provider::DEFAULT_PROVIDER)) + .dangerous() + .with_custom_certificate_verifier(Arc::new(MockServerVerifier::rejects_certificate( + CertificateError::UnknownIssuer.into(), + ))) + .with_no_client_auth() + .unwrap(), + ); + + let mut client = client_config + .connect(server_name("localhost")) + .build() + .unwrap(); + + let mut hello = Vec::new(); + client + .write_tls(&mut io::Cursor::new(&mut hello)) + .unwrap(); + + let first_flight = include_bytes!("../data/bug2040-message-1.bin"); + client + .read_tls(&mut io::Cursor::new(first_flight)) + .unwrap(); + client.process_new_packets().unwrap(); + + let second_flight = include_bytes!("../data/bug2040-message-2.bin"); + client + .read_tls(&mut io::Cursor::new(second_flight)) + .unwrap(); + assert_eq!( + client + .process_new_packets() + .unwrap_err(), + Error::InvalidCertificate(CertificateError::UnknownIssuer), + ); +} + +#[test] +fn large_client_hello() { + let (_, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + let hello = include_bytes!("../data/bug2227-clienthello.bin"); + let mut cursor = io::Cursor::new(hello); + loop { + if server.read_tls(&mut cursor).unwrap() == 0 { + break; + } + server.process_new_packets().unwrap(); + } +} + +#[test] +fn large_client_hello_acceptor() { + let mut acceptor = Acceptor::default(); + let hello = include_bytes!("../data/bug2227-clienthello.bin"); + let mut cursor = io::Cursor::new(hello); + loop { + acceptor.read_tls(&mut cursor).unwrap(); + + if let Some(accepted) = acceptor.accept().unwrap() { + println!("{accepted:?}"); + break; + } + } +} + +#[test] +fn acceptor_with_illegal_max_fragment_size() { + let mut server_config = make_server_config(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + server_config.max_fragment_size = Some(31); + + let mut acceptor = Acceptor::default(); + acceptor + .read_tls( + &mut encoding::message_framing( + ContentType::Handshake, + ProtocolVersion::TLSv1_2, + encoding::basic_client_hello(vec![]), + ) + .as_slice(), + ) + .unwrap(); + + let accepted = acceptor.accept().unwrap().unwrap(); + let (err, mut alert) = accepted + .into_connection(Arc::new(server_config)) + .err() + .unwrap(); + + assert_eq!(err, Error::BadMaxFragmentSize); + assert_eq!( + alert + .write(&mut &mut [0u8; 128][..]) + .ok(), + Some(0), + "illegal max fragment size should not send an alert, as it is a local configuration issue" + ); +} + +#[test] +fn excess_client_hello_acceptor() { + // this is a trivial ClientHello, followed by a fragment of a ClientHello + let mut hello = encoding::basic_client_hello(vec![]); + hello.extend(&hello[..10].to_vec()); + let hello = encoding::message_framing(ContentType::Handshake, ProtocolVersion::TLSv1_2, hello); + + let mut acceptor = Acceptor::default(); + acceptor + .read_tls(&mut io::Cursor::new(hello)) + .unwrap(); + let (error, mut alert) = acceptor.accept().unwrap_err(); + assert_eq!(error, PeerMisbehaved::KeyEpochWithPendingFragment.into()); + + let mut alert_buf = vec![]; + alert.write_all(&mut alert_buf).unwrap(); + assert_eq!( + alert_buf, + encoding::alert(AlertDescription::UnexpectedMessage, &[]) + ); +} + +#[test] +fn server_invalid_sni_policy() { + const SERVER_NAME_GOOD: &str = "LXXXxxxXXXR"; + const SERVER_NAME_BAD: &str = "[XXXxxxXXX]"; + const SERVER_NAME_IPV4: &str = "10.11.12.13"; + + fn replace_sni(sni_replacement: &str) -> impl Fn(&mut EncodedMessage>) -> Altered + '_ { + assert_eq!(sni_replacement.len(), SERVER_NAME_GOOD.len()); + move |m: &mut EncodedMessage>| { + if m.typ != ContentType::Handshake { + return Altered::InPlace; + } + + let Some(start) = m + .payload + .windows(SERVER_NAME_GOOD.len()) + .position(|w| w == SERVER_NAME_GOOD.as_bytes()) + else { + return Altered::InPlace; + }; + + m.payload[start..][..SERVER_NAME_GOOD.len()] + .copy_from_slice(sni_replacement.as_bytes()); + Altered::InPlace + } + } + + #[derive(Debug)] + enum ExpectedResult { + Accept, + AcceptNoSni, + Reject, + } + use ExpectedResult::*; + use rustls::server::InvalidSniPolicy as Policy; + let test_cases = [ + (Policy::RejectAll, SERVER_NAME_GOOD, Accept), + (Policy::RejectAll, SERVER_NAME_IPV4, Reject), + (Policy::RejectAll, SERVER_NAME_BAD, Reject), + (Policy::IgnoreAll, SERVER_NAME_GOOD, Accept), + (Policy::IgnoreAll, SERVER_NAME_IPV4, AcceptNoSni), + (Policy::IgnoreAll, SERVER_NAME_BAD, AcceptNoSni), + (Policy::IgnoreIpAddresses, SERVER_NAME_GOOD, Accept), + (Policy::IgnoreIpAddresses, SERVER_NAME_IPV4, AcceptNoSni), + (Policy::IgnoreIpAddresses, SERVER_NAME_BAD, Reject), + ]; + + let accept_result = Err(Error::NoSuitableCertificate); + let reject_result = Err(Error::PeerMisbehaved( + PeerMisbehaved::ServerNameMustContainOneHostName, + )); + + for (policy, sni, expected_result) in test_cases { + let provider = provider::DEFAULT_PROVIDER; + let client_config = make_client_config(KeyType::EcdsaP256, &provider); + let mut server_config = make_server_config(KeyType::EcdsaP256, &provider); + + server_config.cert_resolver = Arc::new(ServerCheckSni { + expect_sni: matches!(expected_result, Accept), + }); + server_config.invalid_sni_policy = policy; + + let mut client = Arc::new(client_config) + .connect(server_name(SERVER_NAME_GOOD)) + .build() + .unwrap(); + let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); + + transfer_altered(&mut client, replace_sni(sni), &mut server); + assert_eq!( + &server.process_new_packets(), + match expected_result { + Accept | AcceptNoSni => &accept_result, + Reject => &reject_result, + } + ); + println!( + "test case (policy: {policy:?}, sni: {sni:?}, expected_result: {expected_result:?}) succeeded!" + ); + } +} + +#[derive(Debug)] +struct ServerCheckSni { + expect_sni: bool, +} + +impl ServerCredentialResolver for ServerCheckSni { + fn resolve(&self, client_hello: &ClientHello<'_>) -> Result { + assert_eq!(client_hello.server_name().is_some(), self.expect_sni); + Err(Error::NoSuitableCertificate) + } +} diff --git a/rustls-test/tests/api/client_cert_verifier.rs b/rustls-test/tests/api/client_cert_verifier.rs new file mode 100644 index 00000000000..ec4ff262b1a --- /dev/null +++ b/rustls-test/tests/api/client_cert_verifier.rs @@ -0,0 +1,351 @@ +//! Tests for configuring and using a [`ClientVerifier`] for a server. + +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] + +use std::sync::Arc; + +use rustls::error::{AlertDescription, CertificateError, Error, InvalidMessage, PeerMisbehaved}; +use rustls::server::danger::PeerVerified; +use rustls::{ServerConfig, ServerConnection}; +use rustls_test::{ + ErrorFromPeer, KeyType, MockClientVerifier, do_handshake, do_handshake_until_both_error, + do_handshake_until_error, make_client_config, make_client_config_with_auth, + make_pair_for_arc_configs, make_server_config_with_client_verifier, + make_server_config_with_mandatory_client_auth, make_server_config_with_optional_client_auth, + server_name, webpki_client_verifier_builder, +}; + +use super::{ALL_VERSIONS, provider}; + +// Client is authorized! +fn ver_ok() -> Result { + Ok(PeerVerified::assertion()) +} + +// Use when we shouldn't even attempt verification +fn ver_unreachable() -> Result { + unreachable!() +} + +// Verifier that returns an error that we can expect +fn ver_err() -> Result { + Err(Error::General("test err".to_string())) +} + +fn server_config_with_verifier( + kt: KeyType, + client_cert_verifier: MockClientVerifier, +) -> ServerConfig { + ServerConfig::builder(provider::DEFAULT_PROVIDER.into()) + .with_client_cert_verifier(Arc::new(client_cert_verifier)) + .with_single_cert(kt.identity(), kt.key()) + .unwrap() +} + +#[test] +// Happy path, we resolve to a root, it is verified OK, should be able to connect +fn client_verifier_works() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider).iter() { + let client_verifier = MockClientVerifier::new(ver_ok, *kt, &provider); + let server_config = server_config_with_verifier(*kt, client_verifier); + let server_config = Arc::new(server_config); + + for version_provider in ALL_VERSIONS { + let client_config = make_client_config_with_auth(*kt, &version_provider); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config.clone()), &server_config); + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!(err, Ok(())); + } + } +} + +// Server offers no verification schemes +#[test] +fn client_verifier_no_schemes() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider).iter() { + let mut client_verifier = MockClientVerifier::new(ver_ok, *kt, &provider); + client_verifier.offered_schemes = Some(vec![]); + let server_config = server_config_with_verifier(*kt, client_verifier); + let server_config = Arc::new(server_config); + + for version_provider in ALL_VERSIONS { + let client_config = make_client_config_with_auth(*kt, &version_provider); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config.clone()), &server_config); + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err, + Err(ErrorFromPeer::Client(Error::InvalidMessage( + InvalidMessage::NoSignatureSchemes, + ))), + ); + } + } +} + +// If we do have a root, we must do auth +#[test] +fn client_verifier_no_auth_yes_root() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider).iter() { + let client_verifier = MockClientVerifier::new(ver_unreachable, *kt, &provider); + + let server_config = server_config_with_verifier(*kt, client_verifier); + let server_config = Arc::new(server_config); + + for version_provider in ALL_VERSIONS { + let client_config = Arc::new(make_client_config(*kt, &version_provider)); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); + let mut client = client_config + .connect(server_name("localhost")) + .build() + .unwrap(); + + let errs = do_handshake_until_both_error(&mut client, &mut server); + assert_eq!( + errs, + Err(vec![ + ErrorFromPeer::Server(Error::PeerMisbehaved( + PeerMisbehaved::NoCertificatesPresented + )), + ErrorFromPeer::Client(Error::AlertReceived( + AlertDescription::CertificateRequired + )) + ]) + ); + } + } +} + +#[test] +// Triple checks we propagate the rustls::Error through +fn client_verifier_fails_properly() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider).iter() { + let client_verifier = MockClientVerifier::new(ver_err, *kt, &provider); + let server_config = server_config_with_verifier(*kt, client_verifier); + let server_config = Arc::new(server_config); + + for version_provider in ALL_VERSIONS { + let client_config = Arc::new(make_client_config_with_auth(*kt, &version_provider)); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); + let mut client = client_config + .connect(server_name("localhost")) + .build() + .unwrap(); + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err, + Err(ErrorFromPeer::Server(Error::General("test err".into()))) + ); + } + } +} + +/// Test that the server handles combination of `offer_client_auth()` returning true +/// and `client_auth_mandatory` returning `Some(false)`. This exercises both the +/// client's and server's ability to "recover" from the server asking for a client +/// certificate and not being given one. +#[test] +fn server_allow_any_anonymous_or_authenticated_client() { + let provider = Arc::new(provider::DEFAULT_PROVIDER); + let kt = KeyType::Rsa2048; + for client_cert_chain in [None, Some(kt.client_identity())] { + let client_auth = Arc::new( + webpki_client_verifier_builder(kt.client_root_store(), &provider) + .allow_unauthenticated() + .build() + .unwrap(), + ); + + let server_config = ServerConfig::builder(provider.clone()) + .with_client_cert_verifier(client_auth) + .with_single_cert(kt.identity(), kt.key()) + .unwrap(); + let server_config = Arc::new(server_config); + + for version_provider in ALL_VERSIONS { + let client_config = if client_cert_chain.is_some() { + make_client_config_with_auth(kt, &version_provider) + } else { + make_client_config(kt, &version_provider) + }; + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + assert_eq!(server.peer_identity(), client_cert_chain.as_deref()); + } + } +} + +#[test] +fn client_auth_works() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let server_config = Arc::new(make_server_config_with_mandatory_client_auth( + *kt, &provider, + )); + + for version_provider in ALL_VERSIONS { + let client_config = make_client_config_with_auth(*kt, &version_provider); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + } + } +} + +#[test] +fn client_mandatory_auth_client_revocation_works() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + // Create a server configuration that includes a CRL that specifies the client certificate + // is revoked. + let relevant_crls = vec![kt.client_crl()]; + // Only check the EE certificate status. See client_mandatory_auth_intermediate_revocation_works + // for testing revocation status of the whole chain. + let ee_verifier_builder = webpki_client_verifier_builder(kt.client_root_store(), &provider) + .with_crls(relevant_crls) + .only_check_end_entity_revocation(); + let revoked_server_config = Arc::new(make_server_config_with_client_verifier( + *kt, + ee_verifier_builder, + &provider, + )); + + // Create a server configuration that includes a CRL that doesn't cover the client certificate, + // and uses the default behaviour of treating unknown revocation status as an error. + let unrelated_crls = vec![kt.intermediate_crl()]; + let ee_verifier_builder = webpki_client_verifier_builder(kt.client_root_store(), &provider) + .with_crls(unrelated_crls.clone()) + .only_check_end_entity_revocation(); + let missing_client_crl_server_config = Arc::new(make_server_config_with_client_verifier( + *kt, + ee_verifier_builder, + &provider, + )); + + // Create a server configuration that includes a CRL that doesn't cover the client certificate, + // but change the builder to allow unknown revocation status. + let ee_verifier_builder = webpki_client_verifier_builder(kt.client_root_store(), &provider) + .with_crls(unrelated_crls.clone()) + .only_check_end_entity_revocation() + .allow_unknown_revocation_status(); + let allow_missing_client_crl_server_config = Arc::new( + make_server_config_with_client_verifier(*kt, ee_verifier_builder, &provider), + ); + + for version_provider in ALL_VERSIONS { + // Connecting to the server with a CRL that indicates the client certificate is revoked + // should fail with the expected error. + let client_config = Arc::new(make_client_config_with_auth(*kt, &version_provider)); + let (mut client, mut server) = + make_pair_for_arc_configs(&client_config, &revoked_server_config); + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err, + Err(ErrorFromPeer::Server(Error::InvalidCertificate( + CertificateError::Revoked + ))) + ); + // Connecting to the server missing CRL information for the client certificate should + // fail with the expected unknown revocation status error. + let (mut client, mut server) = + make_pair_for_arc_configs(&client_config, &missing_client_crl_server_config); + let res = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + res, + Err(ErrorFromPeer::Server(Error::InvalidCertificate( + CertificateError::UnknownRevocationStatus + ))) + ); + // Connecting to the server missing CRL information for the client should not error + // if the server's verifier allows unknown revocation status. + let (mut client, mut server) = + make_pair_for_arc_configs(&client_config, &allow_missing_client_crl_server_config); + do_handshake_until_error(&mut client, &mut server).unwrap(); + } + } +} + +#[test] +fn client_mandatory_auth_intermediate_revocation_works() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + // Create a server configuration that includes a CRL that specifies the intermediate certificate + // is revoked. We check the full chain for revocation status (default), and allow unknown + // revocation status so the EE's unknown revocation status isn't an error. + let crls = vec![kt.intermediate_crl()]; + let full_chain_verifier_builder = + webpki_client_verifier_builder(kt.client_root_store(), &provider) + .with_crls(crls.clone()) + .allow_unknown_revocation_status(); + let full_chain_server_config = Arc::new(make_server_config_with_client_verifier( + *kt, + full_chain_verifier_builder, + &provider, + )); + + // Also create a server configuration that uses the same CRL, but that only checks the EE + // cert revocation status. + let ee_only_verifier_builder = + webpki_client_verifier_builder(kt.client_root_store(), &provider) + .with_crls(crls) + .only_check_end_entity_revocation() + .allow_unknown_revocation_status(); + let ee_server_config = Arc::new(make_server_config_with_client_verifier( + *kt, + ee_only_verifier_builder, + &provider, + )); + + for version_provider in ALL_VERSIONS { + // When checking the full chain, we expect an error - the intermediate is revoked. + let client_config = Arc::new(make_client_config_with_auth(*kt, &version_provider)); + let (mut client, mut server) = + make_pair_for_arc_configs(&client_config, &full_chain_server_config); + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err, + Err(ErrorFromPeer::Server(Error::InvalidCertificate( + CertificateError::Revoked + ))) + ); + // However, when checking just the EE cert we expect no error - the intermediate's + // revocation status should not be checked. + let (mut client, mut server) = + make_pair_for_arc_configs(&client_config, &ee_server_config); + do_handshake_until_error(&mut client, &mut server).unwrap(); + } + } +} + +#[test] +fn client_optional_auth_client_revocation_works() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + // Create a server configuration that includes a CRL that specifies the client certificate + // is revoked. + let crls = vec![kt.client_crl()]; + let server_config = Arc::new(make_server_config_with_optional_client_auth( + *kt, crls, &provider, + )); + + for version_provider in ALL_VERSIONS { + let client_config = make_client_config_with_auth(*kt, &version_provider); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + // Because the client certificate is revoked, the handshake should fail. + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err, + Err(ErrorFromPeer::Server(Error::InvalidCertificate( + CertificateError::Revoked + ))) + ); + } + } +} diff --git a/rustls-test/tests/api/compress.rs b/rustls-test/tests/api/compress.rs new file mode 100644 index 00000000000..a0aa6500161 --- /dev/null +++ b/rustls-test/tests/api/compress.rs @@ -0,0 +1,327 @@ +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] + +#[cfg(feature = "zlib")] +use core::sync::atomic::{AtomicUsize, Ordering}; +#[cfg(feature = "zlib")] +use std::sync::Arc; + +#[cfg(feature = "zlib")] +use rustls::ClientConfig; +use rustls::Connection; +#[cfg(feature = "zlib")] +use rustls::client::Resumption; +#[cfg(feature = "zlib")] +use rustls::crypto::{Credentials, Identity, SingleCredential}; +use rustls::enums::CertificateCompressionAlgorithm; +use rustls::error::{AlertDescription, Error, InvalidMessage, PeerMisbehaved}; +#[cfg(feature = "zlib")] +use rustls::pki_types::CertificateDer; +#[cfg(feature = "zlib")] +use rustls_test::{ClientConfigExt, make_pair_for_arc_configs}; +use rustls_test::{ + ErrorFromPeer, KeyType, do_handshake, do_handshake_until_error, make_client_config, + make_client_config_with_auth, make_pair_for_configs, make_server_config, + make_server_config_with_mandatory_client_auth, transfer, +}; + +use super::provider; + +#[cfg(feature = "zlib")] +#[test] +fn test_server_uses_cached_compressed_certificates() { + static COMPRESS_COUNT: AtomicUsize = AtomicUsize::new(0); + + let provider = provider::DEFAULT_PROVIDER; + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); + server_config.cert_compressors = vec![&CountingCompressor]; + let mut client_config = make_client_config(KeyType::Rsa2048, &provider); + client_config.resumption = Resumption::disabled(); + + let server_config = Arc::new(server_config); + let client_config = Arc::new(client_config); + + for _i in 0..10 { + dbg!(_i); + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + dbg!(client.handshake_kind()); + } + + assert_eq!(COMPRESS_COUNT.load(Ordering::SeqCst), 1); + + #[derive(Debug)] + struct CountingCompressor; + + impl rustls::compress::CertCompressor for CountingCompressor { + fn compress( + &self, + input: Vec, + level: rustls::compress::CompressionLevel, + ) -> Result, rustls::compress::CompressionFailed> { + dbg!(COMPRESS_COUNT.fetch_add(1, Ordering::SeqCst)); + rustls::compress::ZLIB_COMPRESSOR.compress(input, level) + } + + fn algorithm(&self) -> CertificateCompressionAlgorithm { + CertificateCompressionAlgorithm::Zlib + } + } +} + +#[test] +fn test_server_uses_uncompressed_certificate_if_compression_fails() { + let provider = provider::DEFAULT_PROVIDER; + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); + server_config.cert_compressors = vec![&FailingCompressor]; + let mut client_config = make_client_config(KeyType::Rsa2048, &provider); + client_config.cert_decompressors = vec![&NeverDecompressor]; + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake(&mut client, &mut server); +} + +#[test] +fn test_client_uses_uncompressed_certificate_if_compression_fails() { + let provider = provider::DEFAULT_PROVIDER; + let mut server_config = + make_server_config_with_mandatory_client_auth(KeyType::Rsa2048, &provider); + server_config.cert_decompressors = vec![&NeverDecompressor]; + let mut client_config = make_client_config_with_auth(KeyType::Rsa2048, &provider); + client_config.cert_compressors = vec![&FailingCompressor]; + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake(&mut client, &mut server); +} + +#[derive(Debug)] +struct FailingCompressor; + +impl rustls::compress::CertCompressor for FailingCompressor { + fn compress( + &self, + _input: Vec, + _level: rustls::compress::CompressionLevel, + ) -> Result, rustls::compress::CompressionFailed> { + println!("compress called but doesn't work"); + Err(rustls::compress::CompressionFailed) + } + + fn algorithm(&self) -> CertificateCompressionAlgorithm { + CertificateCompressionAlgorithm::Zlib + } +} + +#[derive(Debug)] +struct NeverDecompressor; + +impl rustls::compress::CertDecompressor for NeverDecompressor { + fn decompress( + &self, + _input: &[u8], + _output: &mut [u8], + ) -> Result<(), rustls::compress::DecompressionFailed> { + panic!("NeverDecompressor::decompress should not be called"); + } + + fn algorithm(&self) -> CertificateCompressionAlgorithm { + CertificateCompressionAlgorithm::Zlib + } +} + +#[cfg(feature = "zlib")] +#[test] +fn test_server_can_opt_out_of_compression_cache() { + static COMPRESS_COUNT: AtomicUsize = AtomicUsize::new(0); + + let provider = provider::DEFAULT_PROVIDER; + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); + server_config.cert_compressors = vec![&AlwaysInteractiveCompressor]; + server_config.cert_compression_cache = Arc::new(rustls::compress::CompressionCache::Disabled); + let mut client_config = make_client_config(KeyType::Rsa2048, &provider); + client_config.resumption = Resumption::disabled(); + + let server_config = Arc::new(server_config); + let client_config = Arc::new(client_config); + + for _i in 0..10 { + dbg!(_i); + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + dbg!(client.handshake_kind()); + } + + assert_eq!(COMPRESS_COUNT.load(Ordering::SeqCst), 10); + + #[derive(Debug)] + struct AlwaysInteractiveCompressor; + + impl rustls::compress::CertCompressor for AlwaysInteractiveCompressor { + fn compress( + &self, + input: Vec, + level: rustls::compress::CompressionLevel, + ) -> Result, rustls::compress::CompressionFailed> { + dbg!(COMPRESS_COUNT.fetch_add(1, Ordering::SeqCst)); + assert_eq!(level, rustls::compress::CompressionLevel::Interactive); + rustls::compress::ZLIB_COMPRESSOR.compress(input, level) + } + + fn algorithm(&self) -> CertificateCompressionAlgorithm { + CertificateCompressionAlgorithm::Zlib + } + } +} + +#[test] +fn test_cert_decompression_by_client_produces_invalid_cert_payload() { + let provider = provider::DEFAULT_PROVIDER; + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); + server_config.cert_compressors = vec![&IdentityCompressor]; + let mut client_config = make_client_config(KeyType::Rsa2048, &provider); + client_config.cert_decompressors = vec![&GarbageDecompressor]; + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + assert_eq!( + do_handshake_until_error(&mut client, &mut server), + Err(ErrorFromPeer::Client(Error::InvalidMessage( + InvalidMessage::CertificatePayloadTooLarge + ))) + ); + transfer(&mut client, &mut server); + assert_eq!( + server.process_new_packets(), + Err(Error::AlertReceived(AlertDescription::BadCertificate)) + ); +} + +#[test] +fn test_cert_decompression_by_server_produces_invalid_cert_payload() { + let provider = provider::DEFAULT_PROVIDER; + let mut server_config = + make_server_config_with_mandatory_client_auth(KeyType::Rsa2048, &provider); + server_config.cert_decompressors = vec![&GarbageDecompressor]; + let mut client_config = make_client_config_with_auth(KeyType::Rsa2048, &provider); + client_config.cert_compressors = vec![&IdentityCompressor]; + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + assert_eq!( + do_handshake_until_error(&mut client, &mut server), + Err(ErrorFromPeer::Server(Error::InvalidMessage( + InvalidMessage::CertificatePayloadTooLarge + ))) + ); + transfer(&mut server, &mut client); + assert_eq!( + client.process_new_packets(), + Err(Error::AlertReceived(AlertDescription::BadCertificate)) + ); +} + +#[test] +fn test_cert_decompression_by_server_fails() { + let provider = provider::DEFAULT_PROVIDER; + let mut server_config = + make_server_config_with_mandatory_client_auth(KeyType::Rsa2048, &provider); + server_config.cert_decompressors = vec![&FailingDecompressor]; + let mut client_config = make_client_config_with_auth(KeyType::Rsa2048, &provider); + client_config.cert_compressors = vec![&IdentityCompressor]; + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + assert_eq!( + do_handshake_until_error(&mut client, &mut server), + Err(ErrorFromPeer::Server(Error::PeerMisbehaved( + PeerMisbehaved::InvalidCertCompression + ))) + ); + transfer(&mut server, &mut client); + assert_eq!( + client.process_new_packets(), + Err(Error::AlertReceived(AlertDescription::BadCertificate)) + ); +} + +#[cfg(feature = "zlib")] +#[test] +fn test_cert_decompression_by_server_would_result_in_excessively_large_cert() { + let provider = provider::DEFAULT_PROVIDER; + let server_config = make_server_config_with_mandatory_client_auth(KeyType::Rsa2048, &provider); + + let big_cert = CertificateDer::from(vec![0u8; 0xffff]); + let key = provider::DEFAULT_PROVIDER + .key_provider + .load_private_key(KeyType::Rsa2048.client_key()) + .unwrap(); + let big_cert_and_key = Credentials::new_unchecked( + Arc::new(Identity::from_cert_chain(vec![big_cert]).unwrap()), + key, + ); + let client_config = ClientConfig::builder(Arc::new(provider)) + .add_root_certs(KeyType::Rsa2048) + .with_client_credential_resolver(Arc::new(SingleCredential::from(big_cert_and_key))) + .unwrap(); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + assert_eq!( + do_handshake_until_error(&mut client, &mut server), + Err(ErrorFromPeer::Server(Error::InvalidMessage( + InvalidMessage::CertificatePayloadTooLarge + ))) + ); + transfer(&mut server, &mut client); + assert_eq!( + client.process_new_packets(), + Err(Error::AlertReceived(AlertDescription::BadCertificate)) + ); +} + +#[derive(Debug)] +struct GarbageDecompressor; + +impl rustls::compress::CertDecompressor for GarbageDecompressor { + fn decompress( + &self, + _input: &[u8], + output: &mut [u8], + ) -> Result<(), rustls::compress::DecompressionFailed> { + output.fill(0xff); + Ok(()) + } + + fn algorithm(&self) -> CertificateCompressionAlgorithm { + CertificateCompressionAlgorithm::Zlib + } +} + +#[derive(Debug)] +struct FailingDecompressor; + +impl rustls::compress::CertDecompressor for FailingDecompressor { + fn decompress( + &self, + _input: &[u8], + _output: &mut [u8], + ) -> Result<(), rustls::compress::DecompressionFailed> { + Err(rustls::compress::DecompressionFailed) + } + + fn algorithm(&self) -> CertificateCompressionAlgorithm { + CertificateCompressionAlgorithm::Zlib + } +} + +#[derive(Debug)] +struct IdentityCompressor; + +impl rustls::compress::CertCompressor for IdentityCompressor { + fn compress( + &self, + input: Vec, + _level: rustls::compress::CompressionLevel, + ) -> Result, rustls::compress::CompressionFailed> { + Ok(input.to_vec()) + } + + fn algorithm(&self) -> CertificateCompressionAlgorithm { + CertificateCompressionAlgorithm::Zlib + } +} diff --git a/rustls-test/tests/api/crypto.rs b/rustls-test/tests/api/crypto.rs new file mode 100644 index 00000000000..6b17c8a91b3 --- /dev/null +++ b/rustls-test/tests/api/crypto.rs @@ -0,0 +1,535 @@ +//! Cryptography-related tests, and tests around key material handling. + +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] + +use std::borrow::Cow; +use std::io::{Read, Write}; +use std::sync::{Arc, Mutex}; + +use rustls::crypto::{Credentials, CryptoProvider}; +use rustls::{ + ClientConfig, ClientConnection, Connection, ConnectionTrafficSecrets, Error, KeyLog, + ServerConfig, ServerConnection, SupportedCipherSuite, +}; +use rustls_test::{ + ClientConfigExt, KeyType, ServerConfigExt, aes_128_gcm_with_1024_confidentiality_limit, + do_handshake, make_client_config, make_pair, make_pair_for_arc_configs, make_pair_for_configs, + make_server_config, provider_with_one_suite, transfer, +}; + +use super::provider; +use super::provider::cipher_suite; + +#[test] +fn key_log_for_tls12() { + let client_key_log = Arc::new(KeyLogToVec::new("client")); + let server_key_log = Arc::new(KeyLogToVec::new("server")); + + let provider = provider::DEFAULT_TLS12_PROVIDER; + let kt = KeyType::Rsa2048; + let mut client_config = make_client_config(kt, &provider); + client_config.key_log = client_key_log.clone(); + let client_config = Arc::new(client_config); + + let mut server_config = make_server_config(kt, &provider); + server_config.key_log = server_key_log.clone(); + let server_config = Arc::new(server_config); + + // full handshake + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + let client_full_log = client_key_log.take(); + let server_full_log = server_key_log.take(); + assert_eq!(client_full_log, server_full_log); + assert_eq!(1, client_full_log.len()); + assert_eq!("CLIENT_RANDOM", client_full_log[0].label); + + // resumed + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + let client_resume_log = client_key_log.take(); + let server_resume_log = server_key_log.take(); + assert_eq!(client_resume_log, server_resume_log); + assert_eq!(1, client_resume_log.len()); + assert_eq!("CLIENT_RANDOM", client_resume_log[0].label); + assert_eq!(client_full_log[0].secret, client_resume_log[0].secret); +} + +#[test] +fn key_log_for_tls13() { + let client_key_log = Arc::new(KeyLogToVec::new("client")); + let server_key_log = Arc::new(KeyLogToVec::new("server")); + + let provider = provider::DEFAULT_TLS13_PROVIDER; + let kt = KeyType::Rsa2048; + let mut client_config = make_client_config(kt, &provider); + client_config.key_log = client_key_log.clone(); + let client_config = Arc::new(client_config); + + let mut server_config = make_server_config(kt, &provider); + server_config.key_log = server_key_log.clone(); + let server_config = Arc::new(server_config); + + // full handshake + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + let client_full_log = client_key_log.take(); + let server_full_log = server_key_log.take(); + + assert_eq!(5, client_full_log.len()); + assert_eq!("CLIENT_HANDSHAKE_TRAFFIC_SECRET", client_full_log[0].label); + assert_eq!("SERVER_HANDSHAKE_TRAFFIC_SECRET", client_full_log[1].label); + assert_eq!("CLIENT_TRAFFIC_SECRET_0", client_full_log[2].label); + assert_eq!("SERVER_TRAFFIC_SECRET_0", client_full_log[3].label); + assert_eq!("EXPORTER_SECRET", client_full_log[4].label); + + assert_eq!(client_full_log[0], server_full_log[0]); + assert_eq!(client_full_log[1], server_full_log[1]); + assert_eq!(client_full_log[2], server_full_log[2]); + assert_eq!(client_full_log[3], server_full_log[3]); + assert_eq!(client_full_log[4], server_full_log[4]); + + // resumed + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + let client_resume_log = client_key_log.take(); + let server_resume_log = server_key_log.take(); + + assert_eq!(5, client_resume_log.len()); + assert_eq!( + "CLIENT_HANDSHAKE_TRAFFIC_SECRET", + client_resume_log[0].label + ); + assert_eq!( + "SERVER_HANDSHAKE_TRAFFIC_SECRET", + client_resume_log[1].label + ); + assert_eq!("CLIENT_TRAFFIC_SECRET_0", client_resume_log[2].label); + assert_eq!("SERVER_TRAFFIC_SECRET_0", client_resume_log[3].label); + assert_eq!("EXPORTER_SECRET", client_resume_log[4].label); + + assert_eq!(6, server_resume_log.len()); + assert_eq!("CLIENT_EARLY_TRAFFIC_SECRET", server_resume_log[0].label); + assert_eq!( + "CLIENT_HANDSHAKE_TRAFFIC_SECRET", + server_resume_log[1].label + ); + assert_eq!( + "SERVER_HANDSHAKE_TRAFFIC_SECRET", + server_resume_log[2].label + ); + assert_eq!("CLIENT_TRAFFIC_SECRET_0", server_resume_log[3].label); + assert_eq!("SERVER_TRAFFIC_SECRET_0", server_resume_log[4].label); + assert_eq!("EXPORTER_SECRET", server_resume_log[5].label); + + assert_eq!(client_resume_log[0], server_resume_log[1]); + assert_eq!(client_resume_log[1], server_resume_log[2]); + assert_eq!(client_resume_log[2], server_resume_log[3]); + assert_eq!(client_resume_log[3], server_resume_log[4]); + assert_eq!(client_resume_log[4], server_resume_log[5]); +} +#[derive(Debug)] +struct KeyLogToVec { + label: &'static str, + items: Mutex>, +} + +impl KeyLogToVec { + fn new(who: &'static str) -> Self { + Self { + label: who, + items: Mutex::new(vec![]), + } + } + + fn take(&self) -> Vec { + core::mem::take(&mut self.items.lock().unwrap()) + } +} + +impl KeyLog for KeyLogToVec { + fn log(&self, label: &str, client: &[u8], secret: &[u8]) { + let value = KeyLogItem { + label: label.into(), + client_random: client.into(), + secret: secret.into(), + }; + + println!("key log {:?}: {:?}", self.label, value); + + self.items.lock().unwrap().push(value); + } +} + +#[derive(Debug, PartialEq)] +struct KeyLogItem { + label: String, + client_random: Vec, + secret: Vec, +} + +/// Test that secrets can be extracted and used for encryption/decryption. +#[test] +fn test_secret_extraction_enabled() { + // Normally, secret extraction would be used to configure kTLS (TLS offload + // to the kernel). We want this test to run on any platform, though, so + // instead we just compare secrets for equality. + + // TLS 1.2 and 1.3 have different mechanisms for key exchange and handshake, + // and secrets are stored/extracted differently, so we want to test them both. + // We support 3 different AEAD algorithms (AES-128-GCM mode, AES-256-GCM, and + // Chacha20Poly1305), so that's 2*3 = 6 combinations to test. + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_PROVIDER; + for suite in [ + SupportedCipherSuite::Tls13(cipher_suite::TLS13_AES_128_GCM_SHA256), + SupportedCipherSuite::Tls13(cipher_suite::TLS13_AES_256_GCM_SHA384), + #[cfg(not(feature = "fips"))] + SupportedCipherSuite::Tls13(cipher_suite::TLS13_CHACHA20_POLY1305_SHA256), + SupportedCipherSuite::Tls12(cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), + SupportedCipherSuite::Tls12(cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), + #[cfg(not(feature = "fips"))] + SupportedCipherSuite::Tls12(cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), + ] { + println!("Testing suite {:?}", suite.suite()); + + // Only offer the cipher suite (and protocol version) that we're testing + let mut server_config = + ServerConfig::builder(provider_with_one_suite(&provider, suite).into()) + .with_no_client_auth() + .with_single_cert(kt.identity(), kt.key()) + .unwrap(); + // Opt into secret extraction from both sides + server_config.enable_secret_extraction = true; + let server_config = Arc::new(server_config); + + let mut client_config = make_client_config(kt, &provider); + client_config.enable_secret_extraction = true; + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + do_handshake(&mut client, &mut server); + + // The handshake is finished, we're now able to extract traffic secrets + let client_secrets = client + .dangerous_extract_secrets() + .unwrap(); + let server_secrets = server + .dangerous_extract_secrets() + .unwrap(); + + // Comparing secrets for equality is something you should never have to + // do in production code, so ConnectionTrafficSecrets doesn't implement + // PartialEq/Eq on purpose. Instead, we have to get creative. + fn explode_secrets(s: &ConnectionTrafficSecrets) -> (&[u8], &[u8]) { + match s { + ConnectionTrafficSecrets::Aes128Gcm { key, iv } => (key.as_ref(), iv.as_ref()), + ConnectionTrafficSecrets::Aes256Gcm { key, iv } => (key.as_ref(), iv.as_ref()), + ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv } => { + (key.as_ref(), iv.as_ref()) + } + _ => panic!("unexpected secret type"), + } + } + + fn assert_secrets_equal( + (l_seq, l_sec): (u64, ConnectionTrafficSecrets), + (r_seq, r_sec): (u64, ConnectionTrafficSecrets), + ) { + assert_eq!(l_seq, r_seq); + assert_eq!(explode_secrets(&l_sec), explode_secrets(&r_sec)); + } + + assert_secrets_equal(client_secrets.tx, server_secrets.rx); + assert_secrets_equal(client_secrets.rx, server_secrets.tx); + } +} + +#[test] +fn test_secret_extract_produces_correct_variant() { + fn check(suite: SupportedCipherSuite, f: impl Fn(ConnectionTrafficSecrets) -> bool) { + let kt = KeyType::Rsa2048; + + let provider: Arc = + provider_with_one_suite(&provider::DEFAULT_PROVIDER, suite).into(); + + let mut server_config = ServerConfig::builder(provider.clone()).finish(kt); + + server_config.enable_secret_extraction = true; + let server_config = Arc::new(server_config); + + let mut client_config = ClientConfig::builder(provider).finish(kt); + client_config.enable_secret_extraction = true; + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + do_handshake(&mut client, &mut server); + + let client_secrets = client + .dangerous_extract_secrets() + .unwrap(); + let server_secrets = server + .dangerous_extract_secrets() + .unwrap(); + + assert!(f(client_secrets.tx.1)); + assert!(f(client_secrets.rx.1)); + assert!(f(server_secrets.tx.1)); + assert!(f(server_secrets.rx.1)); + } + + check( + SupportedCipherSuite::Tls13(cipher_suite::TLS13_AES_128_GCM_SHA256), + |sec| matches!(sec, ConnectionTrafficSecrets::Aes128Gcm { .. }), + ); + check( + SupportedCipherSuite::Tls13(cipher_suite::TLS13_AES_256_GCM_SHA384), + |sec| matches!(sec, ConnectionTrafficSecrets::Aes256Gcm { .. }), + ); + check( + SupportedCipherSuite::Tls13(cipher_suite::TLS13_CHACHA20_POLY1305_SHA256), + |sec| matches!(sec, ConnectionTrafficSecrets::Chacha20Poly1305 { .. }), + ); + + check( + SupportedCipherSuite::Tls12(cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), + |sec| matches!(sec, ConnectionTrafficSecrets::Aes128Gcm { .. }), + ); + check( + SupportedCipherSuite::Tls12(cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), + |sec| matches!(sec, ConnectionTrafficSecrets::Aes256Gcm { .. }), + ); + check( + SupportedCipherSuite::Tls12(cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), + |sec| matches!(sec, ConnectionTrafficSecrets::Chacha20Poly1305 { .. }), + ); +} + +/// Test that secrets cannot be extracted unless explicitly enabled, and until +/// the handshake is done. +#[test] +fn test_secret_extraction_disabled_or_too_early() { + let kt = KeyType::Rsa2048; + let provider = Arc::new(CryptoProvider { + tls13_cipher_suites: Cow::Owned(vec![cipher_suite::TLS13_AES_128_GCM_SHA256]), + ..provider::DEFAULT_PROVIDER + }); + + for (server_enable, client_enable) in [(true, false), (false, true)] { + let mut server_config = ServerConfig::builder(provider.clone()) + .with_no_client_auth() + .with_single_cert(kt.identity(), kt.key()) + .unwrap(); + server_config.enable_secret_extraction = server_enable; + let server_config = Arc::new(server_config); + + let mut client_config = make_client_config(kt, &provider); + client_config.enable_secret_extraction = client_enable; + + let client_config = Arc::new(client_config); + + let (client, server) = make_pair_for_arc_configs(&client_config, &server_config); + + assert_eq!( + client.dangerous_extract_secrets().err(), + Some(Error::HandshakeNotComplete), + "extraction should fail until handshake completes" + ); + assert_eq!( + server.dangerous_extract_secrets().err(), + Some(Error::HandshakeNotComplete), + "extraction should fail until handshake completes" + ); + + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + + do_handshake(&mut client, &mut server); + + assert_eq!( + server_enable, + server + .dangerous_extract_secrets() + .is_ok() + ); + assert_eq!( + client_enable, + client + .dangerous_extract_secrets() + .is_ok() + ); + } +} + +#[test] +fn test_refresh_traffic_keys_during_handshake() { + let (mut client, mut server) = make_pair(KeyType::Ed25519, &provider::DEFAULT_PROVIDER); + assert_eq!( + client + .refresh_traffic_keys() + .unwrap_err(), + Error::HandshakeNotComplete + ); + assert_eq!( + server + .refresh_traffic_keys() + .unwrap_err(), + Error::HandshakeNotComplete + ); +} + +#[test] +fn test_refresh_traffic_keys() { + let (mut client, mut server) = make_pair(KeyType::Ed25519, &provider::DEFAULT_PROVIDER); + do_handshake(&mut client, &mut server); + + fn check_both_directions(client: &mut ClientConnection, server: &mut ServerConnection) { + client + .writer() + .write_all(b"to-server-1") + .unwrap(); + server + .writer() + .write_all(b"to-client-1") + .unwrap(); + transfer(client, server); + server.process_new_packets().unwrap(); + + transfer(server, client); + client.process_new_packets().unwrap(); + + let mut buf = [0u8; 16]; + let len = server.reader().read(&mut buf).unwrap(); + assert_eq!(&buf[..len], b"to-server-1"); + + let len = client.reader().read(&mut buf).unwrap(); + assert_eq!(&buf[..len], b"to-client-1"); + } + + check_both_directions(&mut client, &mut server); + client.refresh_traffic_keys().unwrap(); + check_both_directions(&mut client, &mut server); + server.refresh_traffic_keys().unwrap(); + check_both_directions(&mut client, &mut server); +} + +#[test] +fn test_automatic_refresh_traffic_keys() { + const fn encrypted_size(body: usize) -> usize { + let padding = 1; + let header = 5; + let tag = 16; + header + body + padding + tag + } + + const KEY_UPDATE_SIZE: usize = encrypted_size(5); + let provider = aes_128_gcm_with_1024_confidentiality_limit(provider::DEFAULT_PROVIDER); + + let client_config = ClientConfig::builder(provider.clone()).finish(KeyType::Ed25519); + let server_config = ServerConfig::builder(provider).finish(KeyType::Ed25519); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake(&mut client, &mut server); + + for i in 0..(CONFIDENTIALITY_LIMIT + 16) { + let message = format!("{i:08}"); + client + .writer() + .write_all(message.as_bytes()) + .unwrap(); + let transferred = transfer(&mut client, &mut server); + println!( + "{}: {} -> {:?}", + i, + transferred, + server.process_new_packets().unwrap() + ); + + // at CONFIDENTIALITY_LIMIT messages, we also have a key_update message sent + assert_eq!( + transferred, + match i { + CONFIDENTIALITY_LIMIT => KEY_UPDATE_SIZE + encrypted_size(message.len()), + _ => encrypted_size(message.len()), + } + ); + + let mut buf = [0u8; 32]; + let recvd = server.reader().read(&mut buf).unwrap(); + assert_eq!(&buf[..recvd], message.as_bytes()); + } + + // finally, server writes and pumps its key_update response + let message = b"finished"; + server + .writer() + .write_all(message) + .unwrap(); + let transferred = transfer(&mut server, &mut client); + + println!( + "F: {} -> {:?}", + transferred, + client.process_new_packets().unwrap() + ); + assert_eq!(transferred, KEY_UPDATE_SIZE + encrypted_size(message.len())); +} + +#[test] +fn tls12_connection_fails_after_key_reaches_confidentiality_limit() { + let provider = Arc::new(CryptoProvider { + tls13_cipher_suites: Default::default(), + ..Arc::unwrap_or_clone(aes_128_gcm_with_1024_confidentiality_limit(dbg!( + provider::DEFAULT_PROVIDER + ))) + }); + + let client_config = ClientConfig::builder(provider.clone()).finish(KeyType::Ed25519); + let server_config = ServerConfig::builder(provider).finish(KeyType::Ed25519); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake(&mut client, &mut server); + + for i in 0..CONFIDENTIALITY_LIMIT { + let message = format!("{i:08}"); + client + .writer() + .write_all(message.as_bytes()) + .unwrap(); + let transferred = transfer(&mut client, &mut server); + println!( + "{}: {} -> {:?}", + i, + transferred, + server.process_new_packets().unwrap() + ); + + let mut buf = [0u8; 32]; + let recvd = server.reader().read(&mut buf).unwrap(); + + match i { + 1023 => assert_eq!(recvd, 0), + _ => assert_eq!(&buf[..recvd], message.as_bytes()), + } + } +} + +#[test] +fn test_keys_match_for_all_signing_key_types() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let key = provider + .key_provider + .load_private_key(kt.client_key()) + .unwrap(); + let _ = Credentials::new(kt.client_identity(), key).expect("keys match"); + println!("{kt:?} ok"); + } +} + +const CONFIDENTIALITY_LIMIT: u64 = 1024; diff --git a/rustls-test/tests/api/ffdhe.rs b/rustls-test/tests/api/ffdhe.rs new file mode 100644 index 00000000000..b3c1d14a4fd --- /dev/null +++ b/rustls-test/tests/api/ffdhe.rs @@ -0,0 +1,360 @@ +//! This file contains tests that use the test-only FFDHE KX group (defined in submodule `ffdhe`) + +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] + +use std::borrow::Cow; +use std::sync::Arc; + +use num_bigint::BigUint; +use rustls::crypto::kx::ffdhe::{FFDHE2048, FFDHE3072, FFDHE4096, FfdheGroup}; +use rustls::crypto::kx::{ + ActiveKeyExchange, KeyExchangeAlgorithm, NamedGroup, SharedSecret, StartedKeyExchange, + SupportedKxGroup, +}; +use rustls::crypto::{CipherSuite, CipherSuiteCommon, CryptoProvider}; +use rustls::enums::ProtocolVersion; +use rustls::{ClientConfig, ServerConfig, SupportedCipherSuite, Tls12CipherSuite}; +use rustls_test::{ + ClientConfigExt, KeyType, ServerConfigExt, do_handshake, do_suite_and_kx_test, + make_pair_for_arc_configs, make_pair_for_configs, provider_with_one_suite, +}; + +use super::provider; + +#[test] +fn config_builder_for_client_rejects_cipher_suites_without_compatible_kx_groups() { + let bad_crypto_provider = CryptoProvider { + kx_groups: Cow::Owned(vec![&FFDHE2048_KX_GROUP as &dyn SupportedKxGroup]), + tls12_cipher_suites: Cow::Owned(vec![ + provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + &TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + ]), + ..provider::DEFAULT_PROVIDER + }; + + let build_err = ClientConfig::builder(bad_crypto_provider.into()) + .with_root_certificates(KeyType::EcdsaP256.client_root_store()) + .with_no_client_auth() + .unwrap_err() + .to_string(); + + // Current expected error: + // Ciphersuite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 requires [ECDHE] key exchange, but no \ + // [ECDHE]-compatible key exchange groups were present in `CryptoProvider`'s `kx_groups` field + assert!(build_err.contains("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")); + assert!(build_err.contains("ECDHE")); + assert!(build_err.contains("key exchange")); +} + +#[test] +fn ffdhe_ciphersuite() { + use provider::cipher_suite; + + let test_cases = [ + ( + ProtocolVersion::TLSv1_2, + SupportedCipherSuite::Tls12(&TLS_DHE_RSA_WITH_AES_128_GCM_SHA256), + ), + ( + ProtocolVersion::TLSv1_3, + SupportedCipherSuite::Tls13(cipher_suite::TLS13_CHACHA20_POLY1305_SHA256), + ), + ]; + + for (expected_protocol, expected_cipher_suite) in test_cases { + let provider = Arc::new(provider_with_one_suite( + &ffdhe_provider(), + expected_cipher_suite, + )); + let client_config = ClientConfig::builder(provider.clone()).finish(KeyType::Rsa2048); + let server_config = ServerConfig::builder(provider).finish(KeyType::Rsa2048); + do_suite_and_kx_test( + client_config, + server_config, + expected_cipher_suite, + NamedGroup::FFDHE2048, + expected_protocol, + ); + } +} + +#[test] +fn server_avoids_dhe_cipher_suites_when_client_has_no_known_dhe_in_groups_ext() { + let client_config = ClientConfig::builder( + CryptoProvider { + tls12_cipher_suites: Cow::Owned(vec![ + &TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + ]), + tls13_cipher_suites: Cow::Owned(vec![]), + kx_groups: Cow::Owned(vec![&FFDHE4096_KX_GROUP, provider::kx_group::SECP256R1]), + ..provider::DEFAULT_PROVIDER + } + .into(), + ) + .finish(KeyType::Rsa2048); + + let server_config = ServerConfig::builder( + CryptoProvider { + tls12_cipher_suites: Cow::Owned(vec![ + &TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + ]), + kx_groups: Cow::Owned(vec![&FFDHE2048_KX_GROUP, provider::kx_group::SECP256R1]), + ..provider::DEFAULT_PROVIDER + } + .into(), + ) + .finish(KeyType::Rsa2048); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake(&mut client, &mut server); + assert_eq!( + server + .negotiated_cipher_suite() + .unwrap() + .suite(), + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + ); + assert_eq!( + server + .negotiated_key_exchange_group() + .unwrap() + .name(), + NamedGroup::secp256r1, + ) +} + +#[test] +fn server_avoids_cipher_suite_with_no_common_kx_groups() { + let server_config = ServerConfig::builder( + CryptoProvider { + tls12_cipher_suites: Cow::Owned(vec![ + provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + &TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + ]), + tls13_cipher_suites: Cow::Owned(vec![provider::cipher_suite::TLS13_AES_128_GCM_SHA256]), + kx_groups: Cow::Owned(vec![provider::kx_group::SECP256R1, &FFDHE2048_KX_GROUP]), + ..provider::DEFAULT_PROVIDER + } + .into(), + ) + .finish(KeyType::Rsa2048) + .into(); + + let test_cases = [ + ( + // TLS 1.2, have common + vec![ + // this matches: + provider::kx_group::SECP256R1, + &FFDHE2048_KX_GROUP, + ], + ProtocolVersion::TLSv1_2, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + Some(NamedGroup::secp256r1), + ), + ( + vec![ + // this matches: + provider::kx_group::SECP256R1, + &FFDHE3072_KX_GROUP, + ], + ProtocolVersion::TLSv1_2, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + Some(NamedGroup::secp256r1), + ), + ( + vec![ + provider::kx_group::SECP384R1, + // this matches: + &FFDHE2048_KX_GROUP, + ], + ProtocolVersion::TLSv1_2, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + Some(NamedGroup::FFDHE2048), + ), + ( + // TLS 1.3, have common + vec![ + // this matches: + provider::kx_group::SECP256R1, + &FFDHE2048_KX_GROUP, + ], + ProtocolVersion::TLSv1_3, + CipherSuite::TLS13_AES_128_GCM_SHA256, + Some(NamedGroup::secp256r1), + ), + ( + vec![ + // this matches: + provider::kx_group::SECP256R1, + &FFDHE3072_KX_GROUP, + ], + ProtocolVersion::TLSv1_3, + CipherSuite::TLS13_AES_128_GCM_SHA256, + Some(NamedGroup::secp256r1), + ), + ( + vec![ + provider::kx_group::SECP384R1, + // this matches: + &FFDHE2048_KX_GROUP, + ], + ProtocolVersion::TLSv1_3, + CipherSuite::TLS13_AES_128_GCM_SHA256, + Some(NamedGroup::FFDHE2048), + ), + ]; + + for (client_kx_groups, protocol_version, expected_cipher_suite, expected_group) in test_cases { + let provider = CryptoProvider { + tls12_cipher_suites: Cow::Owned(vec![ + provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + &TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + ]), + tls13_cipher_suites: Cow::Owned(vec![provider::cipher_suite::TLS13_AES_128_GCM_SHA256]), + kx_groups: Cow::Owned(client_kx_groups), + ..provider::DEFAULT_PROVIDER + }; + let provider = match protocol_version { + ProtocolVersion::TLSv1_2 => CryptoProvider { + tls13_cipher_suites: Default::default(), + ..provider + }, + ProtocolVersion::TLSv1_3 => CryptoProvider { + tls12_cipher_suites: Default::default(), + ..provider + }, + _ => unreachable!(), + }; + let client_config = ClientConfig::builder(provider.into()) + .finish(KeyType::Rsa2048) + .into(); + + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + assert_eq!( + server + .negotiated_cipher_suite() + .unwrap() + .suite(), + expected_cipher_suite + ); + assert_eq!(server.protocol_version(), Some(protocol_version)); + assert_eq!( + server + .negotiated_key_exchange_group() + .map(|kx| kx.name()), + expected_group, + ); + } +} + +#[test] +fn non_ffdhe_kx_does_not_have_ffdhe_group() { + let non_ffdhe = provider::kx_group::SECP256R1; + assert_eq!(non_ffdhe.ffdhe_group(), None); + let active = non_ffdhe.start().unwrap(); + assert_eq!(active.ffdhe_group(), None); +} + +/// A test-only `CryptoProvider`, only supporting FFDHE key exchange +fn ffdhe_provider() -> CryptoProvider { + CryptoProvider { + tls12_cipher_suites: Cow::Owned(vec![&TLS_DHE_RSA_WITH_AES_128_GCM_SHA256]), + tls13_cipher_suites: Cow::Owned(vec![ + provider::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, + ]), + kx_groups: Cow::Owned(FFDHE_KX_GROUPS.to_vec()), + ..provider::DEFAULT_PROVIDER + } +} + +static FFDHE_KX_GROUPS: &[&dyn SupportedKxGroup] = &[&FFDHE2048_KX_GROUP, &FFDHE3072_KX_GROUP]; + +const FFDHE2048_KX_GROUP: FfdheKxGroup = FfdheKxGroup(NamedGroup::FFDHE2048, FFDHE2048); +const FFDHE3072_KX_GROUP: FfdheKxGroup = FfdheKxGroup(NamedGroup::FFDHE3072, FFDHE3072); +const FFDHE4096_KX_GROUP: FfdheKxGroup = FfdheKxGroup(NamedGroup::FFDHE4096, FFDHE4096); + +/// The (test-only) TLS1.2 ciphersuite TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 +static TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: Tls12CipherSuite = Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + ..provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256.common + }, + kx: KeyExchangeAlgorithm::DHE, + ..*provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 +}; + +#[derive(Debug)] +struct FfdheKxGroup(pub NamedGroup, pub FfdheGroup<'static>); + +impl SupportedKxGroup for FfdheKxGroup { + fn start(&self) -> Result { + let mut x = vec![0; 64]; + ffdhe_provider() + .secure_random + .fill(&mut x)?; + let x = BigUint::from_bytes_be(&x); + + let p = BigUint::from_bytes_be(self.1.p); + let g = BigUint::from_bytes_be(self.1.g); + + let x_pub = g.modpow(&x, &p); + let x_pub = to_bytes_be_with_len(x_pub, self.1.p.len()); + + Ok(StartedKeyExchange::Single(Box::new(ActiveFfdheKx { + x_pub, + x, + p, + group: self.1, + named_group: self.0, + }))) + } + + fn ffdhe_group(&self) -> Option> { + Some(self.1) + } + + fn name(&self) -> NamedGroup { + self.0 + } +} + +struct ActiveFfdheKx { + x_pub: Vec, + x: BigUint, + p: BigUint, + group: FfdheGroup<'static>, + named_group: NamedGroup, +} + +impl ActiveKeyExchange for ActiveFfdheKx { + fn complete(self: Box, peer_pub_key: &[u8]) -> Result { + let peer_pub = BigUint::from_bytes_be(peer_pub_key); + let secret = peer_pub.modpow(&self.x, &self.p); + let secret = to_bytes_be_with_len(secret, self.group.p.len()); + + Ok(SharedSecret::from(&secret[..])) + } + + fn pub_key(&self) -> &[u8] { + &self.x_pub + } + + fn ffdhe_group(&self) -> Option> { + Some(self.group) + } + + fn group(&self) -> NamedGroup { + self.named_group + } +} + +fn to_bytes_be_with_len(n: BigUint, len_bytes: usize) -> Vec { + let mut bytes = n.to_bytes_le(); + bytes.resize(len_bytes, 0); + bytes.reverse(); + bytes +} diff --git a/rustls-test/tests/api/io.rs b/rustls-test/tests/api/io.rs new file mode 100644 index 00000000000..6ec0e6799e9 --- /dev/null +++ b/rustls-test/tests/api/io.rs @@ -0,0 +1,2133 @@ +//! Tests around IO, buffering, and data management. + +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] + +use core::fmt::Debug; +use std::borrow::Cow; +use std::io::{self, BufRead, IoSlice, Read, Write}; +use std::sync::Arc; + +use pki_types::DnsName; +use rustls::crypto::CryptoProvider; +use rustls::crypto::kx::NamedGroup; +use rustls::enums::{ContentType, HandshakeType, ProtocolVersion}; +use rustls::error::{ + AlertDescription, ApiMisuse, Error, InvalidMessage, PeerIncompatible, PeerMisbehaved, +}; +use rustls::{ClientConfig, Connection, ServerConfig, ServerConnection}; +use rustls_test::{ + ClientConfigExt, KeyType, OtherSession, ServerConfigExt, TestNonBlockIo, check_fill_buf, + check_fill_buf_err, check_read, check_read_and_close, check_read_err, do_handshake, encoding, + make_client_config, make_client_config_with_auth, make_disjoint_suite_configs, make_pair, + make_pair_for_arc_configs, make_pair_for_configs, make_server_config, + make_server_config_with_mandatory_client_auth, server_name, transfer, transfer_eof, +}; +use rustls_util::{Stream, StreamOwned, complete_io}; + +use super::{ALL_VERSIONS, provider}; + +#[test] +fn buffered_client_data_sent() { + let server_config = Arc::new(make_server_config( + KeyType::Rsa2048, + &provider::DEFAULT_PROVIDER, + )); + + for version_provider in ALL_VERSIONS { + let client_config = make_client_config(KeyType::Rsa2048, &version_provider); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + assert_eq!(0, server.writer().write(b"").unwrap()); + assert_eq!(5, client.writer().write(b"hello").unwrap()); + + do_handshake(&mut client, &mut server); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + check_read(&mut server.reader(), b"hello"); + } +} + +#[test] +fn buffered_server_data_sent() { + let server_config = Arc::new(make_server_config( + KeyType::Rsa2048, + &provider::DEFAULT_PROVIDER, + )); + + for version_provider in ALL_VERSIONS { + let client_config = make_client_config(KeyType::Rsa2048, &version_provider); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + assert_eq!(0, server.writer().write(b"").unwrap()); + assert_eq!(5, server.writer().write(b"hello").unwrap()); + + do_handshake(&mut client, &mut server); + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + check_read(&mut client.reader(), b"hello"); + } +} + +#[test] +fn buffered_both_data_sent() { + let server_config = Arc::new(make_server_config( + KeyType::Rsa2048, + &provider::DEFAULT_PROVIDER, + )); + + for version_provider in ALL_VERSIONS { + let client_config = make_client_config(KeyType::Rsa2048, &version_provider); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + assert_eq!( + 12, + server + .writer() + .write(b"from-server!") + .unwrap() + ); + assert_eq!( + 12, + client + .writer() + .write(b"from-client!") + .unwrap() + ); + + do_handshake(&mut client, &mut server); + + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + check_read(&mut client.reader(), b"from-server!"); + check_read(&mut server.reader(), b"from-client!"); + } +} + +#[test] +fn server_respects_buffer_limit_pre_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + server.set_buffer_limit(Some(32)); + + assert_eq!( + server + .writer() + .write(b"01234567890123456789") + .unwrap(), + 20 + ); + assert_eq!( + server + .writer() + .write(b"01234567890123456789") + .unwrap(), + 12 + ); + + do_handshake(&mut client, &mut server); + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + check_read(&mut client.reader(), b"01234567890123456789012345678901"); +} + +#[test] +fn server_respects_buffer_limit_pre_handshake_with_vectored_write() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + server.set_buffer_limit(Some(32)); + + assert_eq!( + server + .writer() + .write_vectored(&[ + IoSlice::new(b"01234567890123456789"), + IoSlice::new(b"01234567890123456789") + ]) + .unwrap(), + 32 + ); + + do_handshake(&mut client, &mut server); + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + check_read(&mut client.reader(), b"01234567890123456789012345678901"); +} + +#[test] +fn server_respects_buffer_limit_post_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + // this test will vary in behaviour depending on the default suites + do_handshake(&mut client, &mut server); + server.set_buffer_limit(Some(48)); + + assert_eq!( + server + .writer() + .write(b"01234567890123456789") + .unwrap(), + 20 + ); + assert_eq!( + server + .writer() + .write(b"01234567890123456789") + .unwrap(), + 6 + ); + + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + check_read(&mut client.reader(), b"01234567890123456789012345"); +} + +#[test] +fn client_respects_buffer_limit_pre_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + client.set_buffer_limit(Some(32)); + + assert_eq!( + client + .writer() + .write(b"01234567890123456789") + .unwrap(), + 20 + ); + assert_eq!( + client + .writer() + .write(b"01234567890123456789") + .unwrap(), + 12 + ); + + do_handshake(&mut client, &mut server); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + check_read(&mut server.reader(), b"01234567890123456789012345678901"); +} + +#[test] +fn client_respects_buffer_limit_pre_handshake_with_vectored_write() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + client.set_buffer_limit(Some(32)); + + assert_eq!( + client + .writer() + .write_vectored(&[ + IoSlice::new(b"01234567890123456789"), + IoSlice::new(b"01234567890123456789") + ]) + .unwrap(), + 32 + ); + + do_handshake(&mut client, &mut server); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + check_read(&mut server.reader(), b"01234567890123456789012345678901"); +} + +#[test] +fn client_respects_buffer_limit_post_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + do_handshake(&mut client, &mut server); + client.set_buffer_limit(Some(48)); + + assert_eq!( + client + .writer() + .write(b"01234567890123456789") + .unwrap(), + 20 + ); + assert_eq!( + client + .writer() + .write(b"01234567890123456789") + .unwrap(), + 6 + ); + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + check_read(&mut server.reader(), b"01234567890123456789012345"); +} + +#[test] +fn client_detects_broken_write_vectored_impl() { + // see https://github.com/rustls/rustls/issues/2316 + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + let err = client + .write_tls(&mut BrokenWriteVectored) + .unwrap_err(); + assert_eq!(err.kind(), io::ErrorKind::Other); + assert!(format!("{err:?}").starts_with( + "Custom { kind: Other, error: \"illegal write_vectored return value (9999 > " + )); + + struct BrokenWriteVectored; + + impl Write for BrokenWriteVectored { + fn write_vectored(&mut self, _bufs: &[IoSlice<'_>]) -> io::Result { + Ok(9999) + } + + fn write(&mut self, _buf: &[u8]) -> io::Result { + unreachable!() + } + + fn flush(&mut self) -> io::Result<()> { + unreachable!() + } + } +} + +#[test] +fn buf_read() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + do_handshake(&mut client, &mut server); + + // Write two separate messages ensuring that empty messages are not written + assert_eq!(client.writer().write(b"").unwrap(), 0); + assert_eq!(client.writer().write(b"hello").unwrap(), 5); + transfer(&mut client, &mut server); + assert_eq!(client.writer().write(b"world").unwrap(), 5); + assert_eq!(client.writer().write(b"").unwrap(), 0); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + let mut reader = server.reader(); + // fill_buf() returns each record separately (this is an implementation detail) + assert_eq!(reader.fill_buf().unwrap(), b"hello"); + // partially consuming the buffer is OK + reader.consume(1); + assert_eq!(reader.fill_buf().unwrap(), b"ello"); + // Read::read is compatible with BufRead + let mut b = [0u8; 2]; + reader.read_exact(&mut b).unwrap(); + assert_eq!(b, *b"el"); + assert_eq!(reader.fill_buf().unwrap(), b"lo"); + reader.consume(2); + // once the first packet is consumed, the next one is available + assert_eq!(reader.fill_buf().unwrap(), b"world"); + reader.consume(5); + check_fill_buf_err(&mut reader, io::ErrorKind::WouldBlock); +} + +#[test] +fn server_read_returns_wouldblock_when_no_data() { + let (_, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + assert!(matches!(server.reader().read(&mut [0u8; 1]), + Err(err) if err.kind() == io::ErrorKind::WouldBlock)); +} + +#[test] +fn client_read_returns_wouldblock_when_no_data() { + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + assert!(matches!(client.reader().read(&mut [0u8; 1]), + Err(err) if err.kind() == io::ErrorKind::WouldBlock)); +} + +#[test] +fn server_fill_buf_returns_wouldblock_when_no_data() { + let (_, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + assert!(matches!(server.reader().fill_buf(), + Err(err) if err.kind() == io::ErrorKind::WouldBlock)); +} + +#[test] +fn client_fill_buf_returns_wouldblock_when_no_data() { + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + assert!(matches!(client.reader().fill_buf(), + Err(err) if err.kind() == io::ErrorKind::WouldBlock)); +} + +#[test] +fn new_server_returns_initial_io_state() { + let (_, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + let io_state = server.process_new_packets().unwrap(); + println!("IoState is Debug {io_state:?}"); + assert_eq!(io_state.plaintext_bytes_to_read(), 0); + assert!(!io_state.peer_has_closed()); + assert_eq!(io_state.tls_bytes_to_write(), 0); +} + +#[test] +fn new_client_returns_initial_io_state() { + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + let io_state = client.process_new_packets().unwrap(); + println!("IoState is Debug {io_state:?}"); + assert_eq!(io_state.plaintext_bytes_to_read(), 0); + assert!(!io_state.peer_has_closed()); + assert!(io_state.tls_bytes_to_write() > 200); +} + +#[test] +fn client_complete_io_for_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + assert!(client.is_handshaking()); + let (rdlen, wrlen) = complete_io(&mut OtherSession::new(&mut server), &mut client).unwrap(); + assert!(rdlen > 0 && wrlen > 0); + assert!(!client.is_handshaking()); + assert!(!client.wants_write()); +} + +#[test] +fn buffered_client_complete_io_for_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + assert!(client.is_handshaking()); + let (rdlen, wrlen) = + complete_io(&mut OtherSession::new_buffered(&mut server), &mut client).unwrap(); + assert!(rdlen > 0 && wrlen > 0); + assert!(!client.is_handshaking()); + assert!(!client.wants_write()); +} + +#[test] +fn client_complete_io_for_handshake_eof() { + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + let mut input = io::Cursor::new(Vec::new()); + + assert!(client.is_handshaking()); + let err = complete_io(&mut input, &mut client).unwrap_err(); + assert_eq!(io::ErrorKind::UnexpectedEof, err.kind()); +} + +#[test] +fn client_complete_io_for_write() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let (mut client, mut server) = make_pair(*kt, &provider); + + do_handshake(&mut client, &mut server); + + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut server); + let (rdlen, wrlen) = complete_io(&mut pipe, &mut client).unwrap(); + assert!(rdlen == 0 && wrlen > 0); + println!("{:?}", pipe.writev_lengths()); + assert_eq!(pipe.writev_lengths(), vec![vec![42, 42]]); + } + check_read( + &mut server.reader(), + b"0123456789012345678901234567890123456789", + ); + } +} + +#[test] +fn client_complete_io_with_nonblocking_io() { + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + // absolutely no progress writing ClientHello + assert_eq!( + complete_io(&mut TestNonBlockIo::default(), &mut client) + .unwrap_err() + .kind(), + io::ErrorKind::WouldBlock + ); + + // a little progress writing ClientHello + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + assert_eq!( + complete_io( + &mut TestNonBlockIo { + writes: vec![1], + reads: vec![], + }, + &mut client + ) + .unwrap(), + (0, 1) + ); + + // complete writing ClientHello + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + assert_eq!( + complete_io( + &mut TestNonBlockIo { + writes: vec![4096], + reads: vec![], + }, + &mut client + ) + .unwrap_err() + .kind(), + io::ErrorKind::WouldBlock + ); + + // complete writing ClientHello, partial read of ServerHello + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + let (rd, wr) = dbg!(complete_io( + &mut TestNonBlockIo { + writes: vec![4096], + reads: vec![vec![ContentType::Handshake.into()]], + }, + &mut client + )) + .unwrap(); + assert_eq!(rd, 1); + assert!(wr > 1); + + // data phase: + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + do_handshake(&mut client, &mut server); + + // read + assert_eq!( + complete_io( + &mut TestNonBlockIo { + reads: vec![vec![ContentType::ApplicationData.into()]], + writes: vec![], + }, + &mut client + ) + .unwrap(), + (1, 0) + ); + + // write + client + .writer() + .write_all(b"hello") + .unwrap(); + + // no progress + assert_eq!( + complete_io( + &mut TestNonBlockIo { + reads: vec![], + writes: vec![], + }, + &mut client + ) + .unwrap_err() + .kind(), + io::ErrorKind::WouldBlock + ); + + // some write progress + assert_eq!( + complete_io( + &mut TestNonBlockIo { + reads: vec![], + writes: vec![1], + }, + &mut client + ) + .unwrap(), + (0, 1) + ); +} + +#[test] +fn buffered_client_complete_io_for_write() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let (mut client, mut server) = make_pair(*kt, &provider); + + do_handshake(&mut client, &mut server); + + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + let mut pipe = OtherSession::new_buffered(&mut server); + let (rdlen, wrlen) = complete_io(&mut pipe, &mut client).unwrap(); + assert!(rdlen == 0 && wrlen > 0); + println!("{:?}", pipe.writev_lengths()); + assert_eq!(pipe.writev_lengths(), vec![vec![42, 42]]); + } + check_read( + &mut server.reader(), + b"0123456789012345678901234567890123456789", + ); + } +} + +#[test] +fn client_complete_io_for_read() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let (mut client, mut server) = make_pair(*kt, &provider); + + do_handshake(&mut client, &mut server); + + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut server); + let (rdlen, wrlen) = complete_io(&mut pipe, &mut client).unwrap(); + assert!(rdlen > 0 && wrlen == 0); + assert_eq!(pipe.reads, 1); + } + check_read(&mut client.reader(), b"01234567890123456789"); + } +} + +#[test] +fn server_complete_io_for_handshake() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let (mut client, mut server) = make_pair(*kt, &provider); + + assert!(server.is_handshaking()); + let (rdlen, wrlen) = complete_io(&mut OtherSession::new(&mut client), &mut server).unwrap(); + assert!(rdlen > 0 && wrlen > 0); + assert!(!server.is_handshaking()); + assert!(!server.wants_write()); + } +} + +#[test] +fn server_complete_io_for_handshake_eof() { + let (_, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + let mut input = io::Cursor::new(Vec::new()); + + assert!(server.is_handshaking()); + let err = complete_io(&mut input, &mut server).unwrap_err(); + assert_eq!(io::ErrorKind::UnexpectedEof, err.kind()); +} + +#[test] +fn server_complete_io_for_write() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let (mut client, mut server) = make_pair(*kt, &provider); + + do_handshake(&mut client, &mut server); + + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let (rdlen, wrlen) = complete_io(&mut pipe, &mut server).unwrap(); + assert!(rdlen == 0 && wrlen > 0); + assert_eq!(pipe.writev_lengths(), vec![vec![42, 42]]); + } + check_read( + &mut client.reader(), + b"0123456789012345678901234567890123456789", + ); + } +} + +#[test] +fn server_complete_io_for_write_eof() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let (mut client, mut server) = make_pair(*kt, &provider); + + do_handshake(&mut client, &mut server); + + // Queue 20 bytes to write. + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + const BYTES_BEFORE_EOF: usize = 5; + let mut eof_writer = EofWriter::::default(); + + // Only BYTES_BEFORE_EOF should be written. + let (rdlen, wrlen) = complete_io(&mut eof_writer, &mut server).unwrap(); + assert_eq!(rdlen, 0); + assert_eq!(wrlen, BYTES_BEFORE_EOF); + + // Now nothing should be written. + let (rdlen, wrlen) = complete_io(&mut eof_writer, &mut server).unwrap(); + assert_eq!(rdlen, 0); + assert_eq!(wrlen, 0); + } + } +} + +#[derive(Default)] +struct EofWriter { + written: usize, +} + +impl Write for EofWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + let prev = self.written; + self.written = N.min(self.written + buf.len()); + Ok(self.written - prev) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Read for EofWriter { + fn read(&mut self, _: &mut [u8]) -> io::Result { + panic!() // This is a writer, it should not be read from. + } +} + +#[test] +fn server_complete_io_for_read() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let (mut client, mut server) = make_pair(*kt, &provider); + + do_handshake(&mut client, &mut server); + + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let (rdlen, wrlen) = complete_io(&mut pipe, &mut server).unwrap(); + assert!(rdlen > 0 && wrlen == 0); + assert_eq!(pipe.reads, 1); + } + check_read(&mut server.reader(), b"01234567890123456789"); + } +} + +#[test] +fn server_complete_io_for_handshake_ending_with_alert() { + let (client_config, server_config) = make_disjoint_suite_configs(provider::DEFAULT_PROVIDER); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + assert!(server.is_handshaking()); + + let mut pipe = OtherSession::new_fails(&mut client); + let rc = complete_io(&mut pipe, &mut server); + assert!(rc.is_err(), "server io failed due to handshake failure"); + assert!(!server.wants_write(), "but server did send its alert"); + assert_eq!( + format!("{:?}", pipe.last_error), + "Some(AlertReceived(HandshakeFailure))", + "which was received by client" + ); +} + +#[test] +fn client_stream_write() { + test_client_stream_write(StreamKind::Ref); + test_client_stream_write(StreamKind::Owned); +} + +#[test] +fn server_stream_write() { + test_server_stream_write(StreamKind::Ref); + test_server_stream_write(StreamKind::Owned); +} + +#[derive(Debug, Copy, Clone)] +enum StreamKind { + Owned, + Ref, +} + +fn test_client_stream_write(stream_kind: StreamKind) { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let (mut client, mut server) = make_pair(*kt, &provider); + let data = b"hello"; + { + let mut pipe = OtherSession::new(&mut server); + let mut stream: Box = match stream_kind { + StreamKind::Ref => Box::new(Stream::new(&mut client, &mut pipe)), + StreamKind::Owned => Box::new(StreamOwned::new(client, pipe)), + }; + assert_eq!(stream.write(data).unwrap(), 5); + } + check_read(&mut server.reader(), data); + } +} + +fn test_server_stream_write(stream_kind: StreamKind) { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let (mut client, mut server) = make_pair(*kt, &provider); + let data = b"hello"; + { + let mut pipe = OtherSession::new(&mut client); + let mut stream: Box = match stream_kind { + StreamKind::Ref => Box::new(Stream::new(&mut server, &mut pipe)), + StreamKind::Owned => Box::new(StreamOwned::new(server, pipe)), + }; + assert_eq!(stream.write(data).unwrap(), 5); + } + check_read(&mut client.reader(), data); + } +} + +#[test] +fn client_stream_read() { + test_client_stream_read(StreamKind::Ref, ReadKind::Buf); + test_client_stream_read(StreamKind::Owned, ReadKind::Buf); + test_client_stream_read(StreamKind::Ref, ReadKind::BufRead); + test_client_stream_read(StreamKind::Owned, ReadKind::BufRead); +} + +#[test] +fn server_stream_read() { + test_server_stream_read(StreamKind::Ref, ReadKind::Buf); + test_server_stream_read(StreamKind::Owned, ReadKind::Buf); + test_server_stream_read(StreamKind::Ref, ReadKind::BufRead); + test_server_stream_read(StreamKind::Owned, ReadKind::BufRead); +} + +#[derive(Debug, Copy, Clone)] +enum ReadKind { + Buf, + BufRead, +} + +fn test_stream_read(read_kind: ReadKind, mut stream: impl BufRead, data: &[u8]) { + match read_kind { + ReadKind::Buf => { + check_read(&mut stream, data); + check_read_err(&mut stream, io::ErrorKind::UnexpectedEof) + } + ReadKind::BufRead => { + check_fill_buf(&mut stream, data); + check_fill_buf_err(&mut stream, io::ErrorKind::UnexpectedEof) + } + } +} + +fn test_client_stream_read(stream_kind: StreamKind, read_kind: ReadKind) { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let (mut client, mut server) = make_pair(*kt, &provider); + let data = b"world"; + server.writer().write_all(data).unwrap(); + + { + let mut pipe = OtherSession::new(&mut server); + transfer_eof(&mut client); + + let stream: Box = match stream_kind { + StreamKind::Ref => Box::new(Stream::new(&mut client, &mut pipe)), + StreamKind::Owned => Box::new(StreamOwned::new(client, pipe)), + }; + + test_stream_read(read_kind, stream, data) + } + } +} + +fn test_server_stream_read(stream_kind: StreamKind, read_kind: ReadKind) { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let (mut client, mut server) = make_pair(*kt, &provider); + let data = b"world"; + client.writer().write_all(data).unwrap(); + + { + let mut pipe = OtherSession::new(&mut client); + transfer_eof(&mut server); + + let stream: Box = match stream_kind { + StreamKind::Ref => Box::new(Stream::new(&mut server, &mut pipe)), + StreamKind::Owned => Box::new(StreamOwned::new(server, pipe)), + }; + + test_stream_read(read_kind, stream, data) + } + } +} + +#[test] +fn test_client_write_and_vectored_write_equivalence() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + do_handshake(&mut client, &mut server); + + const N: usize = 1000; + + let data_chunked: Vec> = core::iter::repeat_n(IoSlice::new(b"A"), N).collect(); + let bytes_written_chunked = client + .writer() + .write_vectored(&data_chunked) + .unwrap(); + let bytes_sent_chunked = transfer(&mut client, &mut server); + println!("write_vectored returned {bytes_written_chunked} and sent {bytes_sent_chunked}"); + + let data_contiguous = &[b'A'; N]; + let bytes_written_contiguous = client + .writer() + .write(data_contiguous) + .unwrap(); + let bytes_sent_contiguous = transfer(&mut client, &mut server); + println!("write returned {bytes_written_contiguous} and sent {bytes_sent_contiguous}"); + + assert_eq!(bytes_written_chunked, bytes_written_contiguous); + assert_eq!(bytes_sent_chunked, bytes_sent_contiguous); +} + +#[test] +fn test_write_vectored_degenerate_cases() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + do_handshake(&mut client, &mut server); + + // empty writes accepted and ignored + assert_eq!(client.writer().write_vectored(&[]).ok(), Some(0)); + assert_eq!(server.writer().write_vectored(&[]).ok(), Some(0)); + assert_eq!(transfer(&mut client, &mut server), 0); + assert_eq!(transfer(&mut server, &mut client), 0); + + // single writes equiv. normal writes + assert_eq!( + client + .writer() + .write_vectored(&[IoSlice::new(b"client")]) + .ok(), + Some(6) + ); + assert_eq!( + server + .writer() + .write_vectored(&[IoSlice::new(b"server")]) + .ok(), + Some(6) + ); + assert!(transfer(&mut client, &mut server) > 0); + assert!(transfer(&mut server, &mut client) > 0); + server.process_new_packets().unwrap(); + client.process_new_packets().unwrap(); + + let mut buf = [0; 6]; + assert_eq!(client.reader().read(&mut buf).unwrap(), 6); + assert_eq!(&buf, b"server"); + assert_eq!(server.reader().read(&mut buf).unwrap(), 6); + assert_eq!(&buf, b"client"); +} + +struct FailsWrites { + errkind: io::ErrorKind, + after: usize, +} + +impl Read for FailsWrites { + fn read(&mut self, _b: &mut [u8]) -> io::Result { + Ok(0) + } +} + +impl Write for FailsWrites { + fn write(&mut self, b: &[u8]) -> io::Result { + if self.after > 0 { + self.after -= 1; + Ok(b.len()) + } else { + Err(io::Error::new(self.errkind, "oops")) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[test] +fn stream_write_reports_underlying_io_error_before_plaintext_processed() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + do_handshake(&mut client, &mut server); + + let mut pipe = FailsWrites { + errkind: io::ErrorKind::ConnectionAborted, + after: 0, + }; + client + .writer() + .write_all(b"hello") + .unwrap(); + let mut client_stream = Stream::new(&mut client, &mut pipe); + let rc = client_stream.write(b"world"); + assert!(rc.is_err()); + let err = rc.err().unwrap(); + assert_eq!(err.kind(), io::ErrorKind::ConnectionAborted); +} + +#[test] +fn stream_write_swallows_underlying_io_error_after_plaintext_processed() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + do_handshake(&mut client, &mut server); + + let mut pipe = FailsWrites { + errkind: io::ErrorKind::ConnectionAborted, + after: 1, + }; + client + .writer() + .write_all(b"hello") + .unwrap(); + let mut client_stream = Stream::new(&mut client, &mut pipe); + let rc = client_stream.write(b"world"); + assert_eq!(format!("{rc:?}"), "Ok(5)"); +} + +#[test] +fn client_stream_handshake_error() { + let (client_config, server_config) = make_disjoint_suite_configs(provider::DEFAULT_PROVIDER); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + { + let mut pipe = OtherSession::new_fails(&mut server); + let mut client_stream = Stream::new(&mut client, &mut pipe); + let rc = client_stream.write(b"hello"); + assert!(rc.is_err()); + assert_eq!( + format!("{rc:?}"), + "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" + ); + let rc = client_stream.write(b"hello"); + assert!(rc.is_err()); + assert_eq!( + format!("{rc:?}"), + "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" + ); + } +} + +#[test] +fn client_streamowned_handshake_error() { + let (client_config, server_config) = make_disjoint_suite_configs(provider::DEFAULT_PROVIDER); + let (client, mut server) = make_pair_for_configs(client_config, server_config); + + let pipe = OtherSession::new_fails(&mut server); + let mut client_stream = StreamOwned::new(client, pipe); + let rc = client_stream.write(b"hello"); + assert!(rc.is_err()); + assert_eq!( + format!("{rc:?}"), + "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" + ); + let rc = client_stream.write(b"hello"); + assert!(rc.is_err()); + assert_eq!( + format!("{rc:?}"), + "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" + ); + + let (_, _) = client_stream.into_parts(); +} + +#[test] +fn server_stream_handshake_error() { + let (client_config, server_config) = make_disjoint_suite_configs(provider::DEFAULT_PROVIDER); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + client + .writer() + .write_all(b"world") + .unwrap(); + + { + let mut pipe = OtherSession::new_fails(&mut client); + let mut server_stream = Stream::new(&mut server, &mut pipe); + let mut bytes = [0u8; 5]; + let rc = server_stream.read(&mut bytes); + assert!(rc.is_err()); + assert_eq!( + format!("{rc:?}"), + "Err(Custom { kind: InvalidData, error: PeerIncompatible(NoCipherSuitesInCommon) })" + ); + } +} + +#[test] +fn server_streamowned_handshake_error() { + let (client_config, server_config) = make_disjoint_suite_configs(provider::DEFAULT_PROVIDER); + let (mut client, server) = make_pair_for_configs(client_config, server_config); + + client + .writer() + .write_all(b"world") + .unwrap(); + + let pipe = OtherSession::new_fails(&mut client); + let mut server_stream = StreamOwned::new(server, pipe); + let mut bytes = [0u8; 5]; + let rc = server_stream.read(&mut bytes); + assert!(rc.is_err()); + assert_eq!( + format!("{rc:?}"), + "Err(Custom { kind: InvalidData, error: PeerIncompatible(NoCipherSuitesInCommon) })" + ); +} + +#[test] +fn vectored_write_for_server_appdata() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + do_handshake(&mut client, &mut server); + + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + assert_eq!(84, wrlen); + assert_eq!(pipe.writev_lengths(), vec![vec![42, 42]]); + } + check_read( + &mut client.reader(), + b"0123456789012345678901234567890123456789", + ); +} + +#[test] +fn vectored_write_for_client_appdata() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + do_handshake(&mut client, &mut server); + + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut server); + let wrlen = client.write_tls(&mut pipe).unwrap(); + assert_eq!(84, wrlen); + assert_eq!(pipe.writev_lengths(), vec![vec![42, 42]]); + } + check_read( + &mut server.reader(), + b"0123456789012345678901234567890123456789", + ); +} + +#[test] +fn vectored_write_for_server_handshake_with_half_rtt_data() { + let provider = provider::DEFAULT_PROVIDER; + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); + server_config.send_half_rtt_data = true; + let (mut client, mut server) = make_pair_for_configs( + make_client_config_with_auth(KeyType::Rsa2048, &provider), + server_config, + ); + + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + server + .writer() + .write_all(b"0123456789") + .unwrap(); + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + // don't assert exact sizes here, to avoid a brittle test + assert!(wrlen > 2400); // its pretty big (contains cert chain) + assert_eq!(pipe.writev_lengths().len(), 1); // only one writev + assert_eq!(pipe.writev_lengths()[0].len(), 5); // at least a server hello/ccs/cert/serverkx/0.5rtt data + } + + client.process_new_packets().unwrap(); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + // 2 tickets (in one flight) + assert_eq!(wrlen, 184); + assert_eq!(pipe.writev_lengths(), vec![vec![184]]); + } + + assert!(!server.is_handshaking()); + assert!(!client.is_handshaking()); + check_read(&mut client.reader(), b"012345678901234567890123456789"); +} + +fn check_half_rtt_does_not_work(server_config: ServerConfig) { + let (mut client, mut server) = make_pair_for_configs( + make_client_config_with_auth(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER), + server_config, + ); + + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + server + .writer() + .write_all(b"0123456789") + .unwrap(); + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + // don't assert exact sizes here, to avoid a brittle test + assert!(wrlen > 2400); // its pretty big (contains cert chain) + assert_eq!(pipe.writev_lengths().len(), 1); // only one writev + assert_eq!(pipe.writev_lengths()[0].len(), 3); // at least a server hello/ccs/cert/serverkx data, in one message + } + + // client second flight + client.process_new_packets().unwrap(); + transfer(&mut client, &mut server); + + // when client auth is enabled, we don't sent 0.5-rtt data, as we'd be sending + // it to an unauthenticated peer. so it happens here, in the server's second + // flight (42 and 32 are lengths of appdata sent above). + server.process_new_packets().unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + assert_eq!(wrlen, 258); + assert_eq!(pipe.writev_lengths(), vec![vec![184, 42, 32]]); + } + + assert!(!server.is_handshaking()); + assert!(!client.is_handshaking()); + check_read(&mut client.reader(), b"012345678901234567890123456789"); +} + +#[test] +fn vectored_write_for_server_handshake_no_half_rtt_with_client_auth() { + let mut server_config = make_server_config_with_mandatory_client_auth( + KeyType::Rsa2048, + &provider::DEFAULT_PROVIDER, + ); + server_config.send_half_rtt_data = true; // ask even though it will be ignored + check_half_rtt_does_not_work(server_config); +} + +#[test] +fn vectored_write_for_server_handshake_no_half_rtt_by_default() { + let server_config = make_server_config(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + assert!(!server_config.send_half_rtt_data); + check_half_rtt_does_not_work(server_config); +} + +#[test] +fn vectored_write_for_client_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + client + .writer() + .write_all(b"0123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut server); + let wrlen = client.write_tls(&mut pipe).unwrap(); + // don't assert exact sizes here, to avoid a brittle test + assert!(wrlen > 200); // just the client hello + assert_eq!(pipe.writev_lengths().len(), 1); // only one writev + assert!(pipe.writev_lengths()[0].len() == 1); // only a client hello + } + + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + { + let mut pipe = OtherSession::new(&mut server); + let wrlen = client.write_tls(&mut pipe).unwrap(); + assert_eq!(wrlen, 138); + // CCS, finished, then two application data records + assert_eq!(pipe.writev_lengths(), vec![vec![6, 58, 42, 32]]); + } + + assert!(!server.is_handshaking()); + assert!(!client.is_handshaking()); + check_read(&mut server.reader(), b"012345678901234567890123456789"); +} + +#[test] +fn vectored_write_with_slow_client() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + + client.set_buffer_limit(Some(32)); + + do_handshake(&mut client, &mut server); + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + + { + let mut pipe = OtherSession::new(&mut client); + pipe.short_writes = true; + let wrlen = server.write_tls(&mut pipe).unwrap() + + server.write_tls(&mut pipe).unwrap() + + server.write_tls(&mut pipe).unwrap() + + server.write_tls(&mut pipe).unwrap() + + server.write_tls(&mut pipe).unwrap() + + server.write_tls(&mut pipe).unwrap(); + assert_eq!(42, wrlen); + assert_eq!( + pipe.writev_lengths(), + vec![vec![21], vec![10], vec![5], vec![3], vec![3]] + ); + } + check_read(&mut client.reader(), b"01234567890123456789"); +} + +#[test] +fn test_client_mtu_reduction() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let mut client_config = make_client_config(*kt, &provider); + client_config.max_fragment_size = Some(64); + let (mut client, mut server) = make_pair_for_configs( + client_config, + make_server_config(KeyType::Rsa2048, &provider), + ); + + { + let mut pipe = OtherSession::new(&mut server); + client.write_tls(&mut pipe).unwrap(); + + assert!( + pipe.message_lengths() + .iter() + .all(|x| *x <= 64) + ); + } + } +} + +#[test] +fn test_server_mtu_reduction() { + let provider = provider::DEFAULT_PROVIDER; + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); + server_config.max_fragment_size = Some(64); + server_config.send_half_rtt_data = true; + let (mut client, mut server) = make_pair_for_configs( + make_client_config(KeyType::Rsa2048, &provider), + server_config, + ); + + let big_data = [0u8; 2048]; + server + .writer() + .write_all(&big_data) + .unwrap(); + + let encryption_overhead = 20; // FIXME: see issue #991 + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + server.write_tls(&mut pipe).unwrap(); + + assert!( + pipe.message_lengths() + .iter() + .all(|x| *x <= 64 + encryption_overhead) + ); + } + + client.process_new_packets().unwrap(); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + server.write_tls(&mut pipe).unwrap(); + + assert!( + pipe.message_lengths() + .iter() + .all(|x| *x <= 64 + encryption_overhead) + ); + } + + client.process_new_packets().unwrap(); + check_read(&mut client.reader(), &big_data); +} + +fn check_client_max_fragment_size(size: usize) -> Option { + let provider = provider::DEFAULT_PROVIDER; + let mut client_config = make_client_config(KeyType::Ed25519, &provider); + client_config.max_fragment_size = Some(size); + Arc::new(client_config) + .connect(server_name("localhost")) + .build() + .err() +} + +#[test] +fn bad_client_max_fragment_sizes() { + assert_eq!( + check_client_max_fragment_size(31), + Some(Error::BadMaxFragmentSize) + ); + assert_eq!(check_client_max_fragment_size(32), None); + assert_eq!(check_client_max_fragment_size(64), None); + assert_eq!(check_client_max_fragment_size(1460), None); + assert_eq!(check_client_max_fragment_size(0x4000), None); + assert_eq!(check_client_max_fragment_size(0x4005), None); + assert_eq!( + check_client_max_fragment_size(0x4006), + Some(Error::BadMaxFragmentSize) + ); + assert_eq!( + check_client_max_fragment_size(0xffff), + Some(Error::BadMaxFragmentSize) + ); +} + +#[test] +fn handshakes_complete_and_data_flows_with_gratuitous_max_fragment_sizes() { + // general exercising of msgs::fragmenter and msgs::deframer + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + for version_provider in ALL_VERSIONS { + // no hidden significance to these numbers + for frag_size in [37, 61, 101, 257] { + println!("test kt={kt:?} version={version_provider:?} frag={frag_size:?}"); + let mut client_config = make_client_config(*kt, &version_provider); + client_config.max_fragment_size = Some(frag_size); + let mut server_config = make_server_config(*kt, &provider); + server_config.max_fragment_size = Some(frag_size); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake(&mut client, &mut server); + + // check server -> client data flow + let pattern = (0x00..=0xffu8).collect::>(); + assert_eq!(pattern.len(), server.writer().write(&pattern).unwrap()); + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + check_read(&mut client.reader(), &pattern); + + // and client -> server + assert_eq!(pattern.len(), client.writer().write(&pattern).unwrap()); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + check_read(&mut server.reader(), &pattern); + } + } + } +} + +#[test] +fn test_acceptor() { + use rustls::server::Acceptor; + + let provider = provider::DEFAULT_PROVIDER; + let client_config = Arc::new(make_client_config(KeyType::Ed25519, &provider)); + let mut client = client_config + .connect(server_name("localhost")) + .build() + .unwrap(); + let mut buf = Vec::new(); + client.write_tls(&mut buf).unwrap(); + + let server_config = Arc::new(make_server_config(KeyType::Ed25519, &provider)); + let mut acceptor = Acceptor::default(); + acceptor + .read_tls(&mut buf.as_slice()) + .unwrap(); + let accepted = acceptor.accept().unwrap().unwrap(); + let ch = accepted.client_hello(); + assert_eq!( + ch.server_name(), + Some(&DnsName::try_from("localhost").unwrap()) + ); + assert_eq!( + ch.named_groups().unwrap(), + provider::DEFAULT_PROVIDER + .kx_groups + .iter() + .map(|kx| kx.name()) + .collect::>() + ); + + let server = accepted + .into_connection(server_config) + .unwrap(); + assert!(server.wants_write()); + + // Reusing an acceptor is not allowed + assert_eq!( + acceptor + .read_tls(&mut [0u8].as_ref()) + .err() + .unwrap() + .kind(), + io::ErrorKind::Other, + ); + assert_eq!( + acceptor.accept().err().unwrap().0, + ApiMisuse::AcceptorPolledAfterCompletion.into() + ); + + let mut acceptor = Acceptor::default(); + assert!(acceptor.accept().unwrap().is_none()); + acceptor + .read_tls(&mut &buf[..3]) + .unwrap(); // incomplete message + assert!(acceptor.accept().unwrap().is_none()); + + acceptor + .read_tls(&mut [0x80, 0x00].as_ref()) + .unwrap(); // invalid message (len = 32k bytes) + let (err, mut alert) = acceptor.accept().unwrap_err(); + assert_eq!(err, Error::InvalidMessage(InvalidMessage::MessageTooLarge)); + let mut alert_content = Vec::new(); + let _ = alert.write(&mut alert_content); + let expected = encoding::alert(AlertDescription::DecodeError, &[]); + assert_eq!(alert_content, expected); + + let mut acceptor = Acceptor::default(); + // Minimal valid 1-byte application data message is not a handshake message + acceptor + .read_tls( + &mut encoding::message_framing( + ContentType::ApplicationData, + ProtocolVersion::TLSv1_2, + vec![0x00], + ) + .as_slice(), + ) + .unwrap(); + let (err, mut alert) = acceptor.accept().unwrap_err(); + assert!(matches!(err, Error::InappropriateMessage { .. })); + let mut alert_content = Vec::new(); + alert + .write_all(&mut alert_content) + .unwrap(); + let expected = encoding::alert(AlertDescription::UnexpectedMessage, &[]); + assert_eq!(alert_content, expected); + assert_eq!(format!("{alert:?}"), "AcceptedAlert { .. }"); + + let mut acceptor = Acceptor::default(); + // Minimal 1-byte ClientHello message is not a legal handshake message + acceptor + .read_tls( + &mut encoding::message_framing( + ContentType::Handshake, + ProtocolVersion::TLSv1_2, + encoding::handshake_framing(HandshakeType::ClientHello, vec![0x00]), + ) + .as_slice(), + ) + .unwrap(); + let (err, mut alert) = acceptor.accept().unwrap_err(); + assert!(matches!( + err, + Error::InvalidMessage(InvalidMessage::MissingData(_)) + )); + let mut alert_content = Vec::new(); + alert + .write_all(&mut alert_content) + .unwrap(); + let expected = encoding::alert(AlertDescription::DecodeError, &[]); + assert_eq!(alert_content, expected); +} + +#[test] +fn test_acceptor_rejected_handshake() { + use rustls::server::Acceptor; + + let client_config = + ClientConfig::builder(provider::DEFAULT_TLS13_PROVIDER.into()).finish(KeyType::Ed25519); + let mut client = Arc::new(client_config) + .connect(server_name("localhost")) + .build() + .unwrap(); + let mut buf = Vec::new(); + client.write_tls(&mut buf).unwrap(); + + let server_config = + ServerConfig::builder(provider::DEFAULT_TLS12_PROVIDER.into()).finish(KeyType::Ed25519); + let mut acceptor = Acceptor::default(); + acceptor + .read_tls(&mut buf.as_slice()) + .unwrap(); + let accepted = acceptor.accept().unwrap().unwrap(); + let ch = accepted.client_hello(); + assert_eq!( + ch.server_name(), + Some(&DnsName::try_from("localhost").unwrap()) + ); + + let (err, mut alert) = accepted + .into_connection(server_config.into()) + .unwrap_err(); + assert_eq!( + err, + Error::PeerIncompatible(PeerIncompatible::Tls12NotOfferedOrEnabled) + ); + + let mut alert_content = Vec::new(); + alert + .write_all(&mut alert_content) + .unwrap(); + let expected = encoding::alert(AlertDescription::ProtocolVersion, &[]); + assert_eq!(alert_content, expected); +} + +#[test] +fn test_received_plaintext_backpressure() { + test_plaintext_buffer_limit(None, 16_384); + test_plaintext_buffer_limit(Some(18_000), 18_000); +} + +fn test_plaintext_buffer_limit(limit: Option, plaintext_limit: usize) { + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_PROVIDER; + + let server_config = Arc::new( + ServerConfig::builder( + CryptoProvider { + tls13_cipher_suites: Cow::Owned(vec![ + provider::cipher_suite::TLS13_AES_128_GCM_SHA256, + ]), + ..provider.clone() + } + .into(), + ) + .with_no_client_auth() + .with_single_cert(kt.identity(), kt.key()) + .unwrap(), + ); + + let client_config = Arc::new(make_client_config(kt, &provider)); + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + + if let Some(limit) = limit { + server.set_plaintext_buffer_limit(Some(limit)); + client.set_plaintext_buffer_limit(Some(limit)); + } + + do_handshake(&mut client, &mut server); + + for direction in [false, true] { + let (left, right): (&mut dyn Connection, &mut dyn Connection) = match direction { + false => (&mut client, &mut server), + true => (&mut server, &mut client), + }; + + // Fill the server's received plaintext buffer with 16k bytes + let write_buf = vec![0; plaintext_limit]; + assert_eq!(left.writer().write(&write_buf).unwrap(), plaintext_limit); + let mut network_buf = Vec::with_capacity(plaintext_limit * 2); + let sent = dbg!( + left.write_tls(&mut network_buf) + .unwrap() + ); + let mut read = 0; + while read < sent { + let new = dbg!( + right + .read_tls(&mut &network_buf[read..sent]) + .unwrap() + ); + if new == 4096 { + read += new; + } else { + break; + } + } + right.process_new_packets().unwrap(); + + // Send one more byte from client to server + assert_eq!(left.writer().write(&[1]).unwrap(), 1); + let sent = dbg!( + left.write_tls(&mut network_buf) + .unwrap() + ); + + // Get an error because the received plaintext buffer is full + assert_eq!( + format!( + "{:?}", + right + .read_tls(&mut &network_buf[..sent]) + .unwrap_err() + ), + "Custom { kind: Other, error: \"received plaintext buffer full\" }" + ); + + // Read out some of the plaintext + right + .reader() + .read_exact(&mut [0; 1]) + .unwrap(); + + // Now there's room again in the plaintext buffer + assert_eq!( + right + .read_tls(&mut &network_buf[..sent]) + .unwrap(), + sent + ); + } +} + +#[test] +fn server_flush_does_nothing() { + let (_, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + assert!(matches!(server.writer().flush(), Ok(()))); +} + +#[test] +fn client_flush_does_nothing() { + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + assert!(matches!(client.writer().flush(), Ok(()))); +} + +#[test] +fn server_close_notify() { + let provider = provider::DEFAULT_PROVIDER; + let kt = KeyType::Rsa2048; + let server_config = Arc::new(make_server_config_with_mandatory_client_auth(kt, &provider)); + + for version_provider in ALL_VERSIONS { + let client_config = make_client_config_with_auth(kt, &version_provider); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + + // check that alerts don't overtake appdata + assert_eq!( + 12, + server + .writer() + .write(b"from-server!") + .unwrap() + ); + assert_eq!( + 12, + client + .writer() + .write(b"from-client!") + .unwrap() + ); + server.send_close_notify(); + + transfer(&mut server, &mut client); + let io_state = client.process_new_packets().unwrap(); + assert!(io_state.peer_has_closed()); + check_read_and_close(&mut client.reader(), b"from-server!"); + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + check_read(&mut server.reader(), b"from-client!"); + } +} + +#[test] +fn client_close_notify() { + let provider = provider::DEFAULT_PROVIDER; + let kt = KeyType::Rsa2048; + let server_config = Arc::new(make_server_config_with_mandatory_client_auth(kt, &provider)); + + for version_provider in ALL_VERSIONS { + let client_config = make_client_config_with_auth(kt, &version_provider); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + + // check that alerts don't overtake appdata + assert_eq!( + 12, + server + .writer() + .write(b"from-server!") + .unwrap() + ); + assert_eq!( + 12, + client + .writer() + .write(b"from-client!") + .unwrap() + ); + client.send_close_notify(); + + transfer(&mut client, &mut server); + let io_state = server.process_new_packets().unwrap(); + assert!(io_state.peer_has_closed()); + check_read_and_close(&mut server.reader(), b"from-client!"); + + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + check_read(&mut client.reader(), b"from-server!"); + } +} + +#[test] +fn server_closes_uncleanly() { + let provider = provider::DEFAULT_PROVIDER; + let kt = KeyType::Rsa2048; + let server_config = Arc::new(make_server_config(kt, &provider)); + + for version_provider in ALL_VERSIONS { + let client_config = make_client_config(kt, &version_provider); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + + // check that unclean EOF reporting does not overtake appdata + assert_eq!( + 12, + server + .writer() + .write(b"from-server!") + .unwrap() + ); + assert_eq!( + 12, + client + .writer() + .write(b"from-client!") + .unwrap() + ); + + transfer(&mut server, &mut client); + transfer_eof(&mut client); + let io_state = client.process_new_packets().unwrap(); + assert!(!io_state.peer_has_closed()); + check_read(&mut client.reader(), b"from-server!"); + + check_read_err( + &mut client.reader() as &mut dyn Read, + io::ErrorKind::UnexpectedEof, + ); + + // may still transmit pending frames + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + check_read(&mut server.reader(), b"from-client!"); + } +} + +#[test] +fn client_closes_uncleanly() { + let provider = provider::DEFAULT_PROVIDER; + let kt = KeyType::Rsa2048; + let server_config = Arc::new(make_server_config(kt, &provider)); + + for version_provider in ALL_VERSIONS { + let client_config = make_client_config(kt, &version_provider); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + + // check that unclean EOF reporting does not overtake appdata + assert_eq!( + 12, + server + .writer() + .write(b"from-server!") + .unwrap() + ); + assert_eq!( + 12, + client + .writer() + .write(b"from-client!") + .unwrap() + ); + + transfer(&mut client, &mut server); + transfer_eof(&mut server); + let io_state = server.process_new_packets().unwrap(); + assert!(!io_state.peer_has_closed()); + check_read(&mut server.reader(), b"from-client!"); + + check_read_err( + &mut server.reader() as &mut dyn Read, + io::ErrorKind::UnexpectedEof, + ); + + // may still transmit pending frames + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + check_read(&mut client.reader(), b"from-server!"); + } +} + +#[test] +fn test_complete_io_errors_if_close_notify_received_too_early() { + let mut server = ServerConnection::new(Arc::new(make_server_config( + KeyType::Rsa2048, + &provider::DEFAULT_PROVIDER, + ))) + .unwrap(); + let client_hello_followed_by_close_notify_alert = b"\ + \x16\x03\x01\x00\xc8\x01\x00\x00\xc4\x03\x03\xec\x12\xdd\x17\x64\ + \xa4\x39\xfd\x7e\x8c\x85\x46\xb8\x4d\x1e\xa0\x6e\xb3\xd7\xa0\x51\ + \xf0\x3c\xb8\x17\x47\x0d\x4c\x54\xc5\xdf\x72\x00\x00\x1c\xea\xea\ + \xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9\xcc\xa8\xc0\x13\xc0\x14\ + \x00\x9c\x00\x9d\x00\x2f\x00\x35\x00\x0a\x01\x00\x00\x7f\xda\xda\ + \x00\x00\xff\x01\x00\x01\x00\x00\x00\x00\x16\x00\x14\x00\x00\x11\ + \x77\x77\x77\x2e\x77\x69\x6b\x69\x70\x65\x64\x69\x61\x2e\x6f\x72\ + \x67\x00\x17\x00\x00\x00\x23\x00\x00\x00\x0d\x00\x14\x00\x12\x04\ + \x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01\x02\ + \x01\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x00\x10\ + \x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31\ + \x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x0a\x00\x08\ + \x1a\x1a\x00\x1d\x00\x17\x00\x18\x1a\x1a\x00\x01\x00\ + \x15\x03\x03\x00\x02\x01\x00"; + + let mut stream = FakeStream(client_hello_followed_by_close_notify_alert); + assert_eq!( + complete_io(&mut stream, &mut server) + .unwrap_err() + .kind(), + io::ErrorKind::UnexpectedEof + ); +} + +#[test] +fn test_complete_io_with_no_io_needed() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + do_handshake(&mut client, &mut server); + client + .writer() + .write_all(b"hello") + .unwrap(); + client.send_close_notify(); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + server + .writer() + .write_all(b"hello") + .unwrap(); + server.send_close_notify(); + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + // neither want any IO: both directions are closed. + assert!(!client.wants_write()); + assert!(!client.wants_read()); + assert!(!server.wants_write()); + assert!(!server.wants_read()); + assert_eq!( + complete_io(&mut FakeStream(&[]), &mut client).unwrap(), + (0, 0) + ); + assert_eq!( + complete_io(&mut FakeStream(&[]), &mut server).unwrap(), + (0, 0) + ); +} + +#[test] +fn test_junk_after_close_notify_received() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + do_handshake(&mut client, &mut server); + client + .writer() + .write_all(b"hello") + .unwrap(); + client.send_close_notify(); + + let mut client_buffer = vec![]; + client + .write_tls(&mut io::Cursor::new(&mut client_buffer)) + .unwrap(); + + // add some junk that will be dropped from the deframer buffer + // after the close_notify + client_buffer.extend_from_slice(&[0x17, 0x03, 0x03, 0x01]); + + server + .read_tls(&mut io::Cursor::new(&client_buffer[..])) + .unwrap(); + server.process_new_packets().unwrap(); + server.process_new_packets().unwrap(); // check for desync + + // can read data received prior to close_notify + let mut received_data = [0u8; 128]; + let len = server + .reader() + .read(&mut received_data) + .unwrap(); + assert_eq!(&received_data[..len], b"hello"); + + // but subsequent reads just report clean EOF + assert_eq!( + server + .reader() + .read(&mut received_data) + .unwrap(), + 0 + ); +} + +#[test] +fn test_data_after_close_notify_is_ignored() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + do_handshake(&mut client, &mut server); + + client + .writer() + .write_all(b"before") + .unwrap(); + client.send_close_notify(); + client + .writer() + .write_all(b"after") + .unwrap(); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + let mut received_data = [0u8; 128]; + let count = server + .reader() + .read(&mut received_data) + .unwrap(); + assert_eq!(&received_data[..count], b"before"); + assert_eq!( + server + .reader() + .read(&mut received_data) + .unwrap(), + 0 + ); +} + +#[test] +fn test_close_notify_sent_prior_to_handshake_complete() { + let mut server = ServerConnection::new(Arc::new(make_server_config( + KeyType::EcdsaP256, + &provider::DEFAULT_PROVIDER, + ))) + .unwrap(); + + server + .read_tls( + &mut encoding::message_framing( + ContentType::Handshake, + ProtocolVersion::TLSv1_2, + encoding::basic_client_hello(vec![]), + ) + .as_slice(), + ) + .unwrap(); + server + .read_tls(&mut encoding::warning_alert(AlertDescription::CloseNotify).as_slice()) + .unwrap(); + assert_eq!( + server.process_new_packets().err(), + Some(PeerMisbehaved::IllegalWarningAlert(AlertDescription::CloseNotify).into()) + ); +} + +#[test] +fn test_subsequent_close_notify_ignored() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + client.send_close_notify(); + assert!(transfer(&mut client, &mut server) > 0); + + // does nothing + client.send_close_notify(); + assert_eq!(transfer(&mut client, &mut server), 0); +} + +#[test] +fn test_second_close_notify_after_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + do_handshake(&mut client, &mut server); + client.send_close_notify(); + assert!(transfer(&mut client, &mut server) > 0); + server.process_new_packets().unwrap(); + + // does nothing + client.send_close_notify(); + assert_eq!(transfer(&mut client, &mut server), 0); +} + +#[test] +fn test_read_tls_artificial_eof_after_close_notify() { + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + do_handshake(&mut client, &mut server); + client.send_close_notify(); + assert!(transfer(&mut client, &mut server) > 0); + server.process_new_packets().unwrap(); + + let buf = [1, 2, 3, 4]; + assert_eq!( + server + .read_tls(&mut io::Cursor::new(buf)) + .unwrap(), + 0 + ); +} + +struct FakeStream<'a>(&'a [u8]); + +impl Read for FakeStream<'_> { + fn read(&mut self, b: &mut [u8]) -> io::Result { + let take = core::cmp::min(b.len(), self.0.len()); + let (taken, remain) = self.0.split_at(take); + b[..take].copy_from_slice(taken); + self.0 = remain; + Ok(take) + } +} + +impl Write for FakeStream<'_> { + fn write(&mut self, b: &[u8]) -> io::Result { + Ok(b.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/rustls-test/tests/api/kx.rs b/rustls-test/tests/api/kx.rs new file mode 100644 index 00000000000..8e5e6c82ebf --- /dev/null +++ b/rustls-test/tests/api/kx.rs @@ -0,0 +1,464 @@ +//! Tests for key exchange and group negotiation. + +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] + +use std::borrow::Cow; +use std::sync::Arc; + +use rustls::client::Resumption; +use rustls::crypto::CryptoProvider; +use rustls::crypto::kx::{ + ActiveKeyExchange, HybridKeyExchange, NamedGroup, SharedSecret, StartedKeyExchange, + SupportedKxGroup, +}; +use rustls::enums::{ContentType, ProtocolVersion}; +use rustls::error::{AlertDescription, Error, InvalidMessage, PeerIncompatible, PeerMisbehaved}; +use rustls::{ClientConfig, Connection, HandshakeKind, ServerConfig}; +use rustls_test::{ + ClientConfigExt, ClientStorage, ClientStorageOp, ErrorFromPeer, KeyType, OtherSession, + ServerConfigExt, do_handshake, do_handshake_until_error, encoding, + make_client_config_with_kx_groups, make_pair, make_pair_for_configs, make_server_config, + make_server_config_with_kx_groups, transfer, +}; + +use super::{ALL_VERSIONS, provider}; + +#[test] +fn test_client_config_keyshare() { + let provider = provider::DEFAULT_PROVIDER; + let kx_groups = vec![provider::kx_group::SECP384R1]; + let client_config = + make_client_config_with_kx_groups(KeyType::Rsa2048, kx_groups.clone(), &provider); + let server_config = make_server_config_with_kx_groups(KeyType::Rsa2048, kx_groups, &provider); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake_until_error(&mut client, &mut server).unwrap(); +} + +#[test] +fn test_client_config_keyshare_mismatch() { + let provider = provider::DEFAULT_PROVIDER; + let client_config = make_client_config_with_kx_groups( + KeyType::Rsa2048, + vec![provider::kx_group::SECP384R1], + &provider, + ); + let server_config = make_server_config_with_kx_groups( + KeyType::Rsa2048, + vec![provider::kx_group::X25519], + &provider, + ); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + assert_eq!( + do_handshake_until_error(&mut client, &mut server).err(), + Some(ErrorFromPeer::Server( + PeerIncompatible::NoKxGroupsInCommon.into() + )) + ); +} + +#[test] +fn exercise_all_key_exchange_methods() { + for (version, version_provider) in [ + (ProtocolVersion::TLSv1_3, provider::DEFAULT_TLS13_PROVIDER), + (ProtocolVersion::TLSv1_2, provider::DEFAULT_TLS12_PROVIDER), + ] { + for kx_group in provider::ALL_KX_GROUPS { + if !kx_group + .name() + .usable_for_version(version) + { + continue; + } + + let client_config = make_client_config_with_kx_groups( + KeyType::Rsa2048, + vec![*kx_group], + &version_provider, + ); + let server_config = make_server_config_with_kx_groups( + KeyType::Rsa2048, + vec![*kx_group], + &version_provider, + ); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake_until_error(&mut client, &mut server).unwrap(); + println!("kx_group {:?} is self-consistent", kx_group.name()); + } + } +} + +#[test] +fn test_client_sends_helloretryrequest() { + let provider = provider::DEFAULT_PROVIDER; + // client sends a secp384r1 key share + let mut client_config = make_client_config_with_kx_groups( + KeyType::Rsa2048, + vec![provider::kx_group::SECP384R1, provider::kx_group::X25519], + &provider, + ); + + let storage = Arc::new(ClientStorage::new()); + client_config.resumption = Resumption::store(storage.clone()); + + // but server only accepts x25519, so a HRR is required + let server_config = make_server_config_with_kx_groups( + KeyType::Rsa2048, + vec![provider::kx_group::X25519], + &provider, + ); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + assert_eq!(client.handshake_kind(), None); + assert_eq!(server.handshake_kind(), None); + + // client sends hello + { + let mut pipe = OtherSession::new(&mut server); + let wrlen = client.write_tls(&mut pipe).unwrap(); + assert!(wrlen > 200); + assert_eq!(pipe.writevs.len(), 1); + assert!(pipe.writevs[0].len() == 1); + } + + assert_eq!(client.handshake_kind(), None); + assert_eq!(server.handshake_kind(), None); + + // server sends HRR + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + assert!(wrlen < 100); // just the hello retry request + assert_eq!(pipe.writevs.len(), 1); // only one writev + assert!(pipe.writevs[0].len() == 2); // hello retry request and CCS + } + + assert_eq!(client.handshake_kind(), None); + assert_eq!(server.handshake_kind(), None); + + // client sends fixed hello + { + let mut pipe = OtherSession::new(&mut server); + let wrlen = client.write_tls(&mut pipe).unwrap(); + assert!(wrlen > 200); // just the client hello retry + assert_eq!(pipe.writevs.len(), 1); // only one writev + assert!(pipe.writevs[0].len() == 2); // only a CCS & client hello retry + } + + // server completes handshake + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + assert!(wrlen > 200); + assert_eq!(pipe.writevs.len(), 1); + assert_eq!(pipe.writevs[0].len(), 2); // { server hello / encrypted exts / cert / cert-verify } / finished + } + + assert_eq!( + client.handshake_kind(), + Some(HandshakeKind::FullWithHelloRetryRequest) + ); + assert_eq!( + server.handshake_kind(), + Some(HandshakeKind::FullWithHelloRetryRequest) + ); + + do_handshake_until_error(&mut client, &mut server).unwrap(); + + // client only did following storage queries: + println!("storage {:#?}", storage.ops()); + assert_eq!(storage.ops().len(), 7); + assert!(matches!( + storage.ops()[0], + ClientStorageOp::TakeTls13Ticket(_, false) + )); + assert!(matches!( + storage.ops()[1], + ClientStorageOp::GetTls12Session(_, false) + )); + assert!(matches!( + storage.ops()[2], + ClientStorageOp::GetKxHint(_, None) + )); + assert!(matches!( + storage.ops()[3], + ClientStorageOp::SetKxHint(_, NamedGroup::X25519) + )); + assert!(matches!( + storage.ops()[4], + ClientStorageOp::RemoveTls12Session(_) + )); + // server sends 2 tickets by default + assert!(matches!( + storage.ops()[5], + ClientStorageOp::InsertTls13Ticket(_) + )); + assert!(matches!( + storage.ops()[6], + ClientStorageOp::InsertTls13Ticket(_) + )); +} + +#[test] +fn test_client_attempts_to_use_unsupported_kx_group() { + // common to both client configs + let shared_storage = Arc::new(ClientStorage::new()); + let provider = provider::DEFAULT_PROVIDER; + + // first, client sends a secp-256 share and server agrees. secp-256 is inserted + // into kx group cache. + let mut client_config_1 = make_client_config_with_kx_groups( + KeyType::Rsa2048, + vec![provider::kx_group::SECP256R1], + &provider, + ); + client_config_1.resumption = Resumption::store(shared_storage.clone()); + + // second, client only supports secp-384 and so kx group cache + // contains an unusable value. + let mut client_config_2 = make_client_config_with_kx_groups( + KeyType::Rsa2048, + vec![provider::kx_group::SECP384R1], + &provider, + ); + client_config_2.resumption = Resumption::store(shared_storage.clone()); + + let server_config = make_server_config(KeyType::Rsa2048, &provider); + + // first handshake + let (mut client_1, mut server) = make_pair_for_configs(client_config_1, server_config.clone()); + do_handshake_until_error(&mut client_1, &mut server).unwrap(); + + let ops = shared_storage.ops(); + println!("storage {ops:#?}"); + assert_eq!(ops.len(), 7); + assert!(matches!( + ops[3], + ClientStorageOp::SetKxHint(_, NamedGroup::secp256r1) + )); + + // second handshake + let (mut client_2, mut server) = make_pair_for_configs(client_config_2, server_config); + do_handshake_until_error(&mut client_2, &mut server).unwrap(); + + let ops = shared_storage.ops(); + println!("storage {:?} {:#?}", ops.len(), ops); + assert_eq!(ops.len(), 13); + assert!(matches!(ops[7], ClientStorageOp::TakeTls13Ticket(_, true))); + assert!(matches!( + ops[8], + ClientStorageOp::GetKxHint(_, Some(NamedGroup::secp256r1)) + )); + assert!(matches!( + ops[9], + ClientStorageOp::SetKxHint(_, NamedGroup::secp384r1) + )); +} + +#[test] +fn test_client_sends_share_for_less_preferred_group() { + // this is a test for the case described in: + // https://datatracker.ietf.org/doc/draft-davidben-tls-key-share-prediction/ + + // common to both client configs + let shared_storage = Arc::new(ClientStorage::new()); + let provider = provider::DEFAULT_PROVIDER; + + // first, client sends a secp384r1 share and server agrees. secp384r1 is inserted + // into kx group cache. + let mut client_config_1 = make_client_config_with_kx_groups( + KeyType::Rsa2048, + vec![provider::kx_group::SECP384R1], + &provider, + ); + client_config_1.resumption = Resumption::store(shared_storage.clone()); + + // second, client supports (x25519, secp384r1) and so kx group cache + // contains a supported but less-preferred group. + let mut client_config_2 = make_client_config_with_kx_groups( + KeyType::Rsa2048, + vec![provider::kx_group::X25519, provider::kx_group::SECP384R1], + &provider, + ); + client_config_2.resumption = Resumption::store(shared_storage.clone()); + + let server_config = make_server_config_with_kx_groups( + KeyType::Rsa2048, + provider::ALL_KX_GROUPS.to_vec(), + &provider, + ); + + // first handshake + let (mut client_1, mut server) = make_pair_for_configs(client_config_1, server_config.clone()); + do_handshake_until_error(&mut client_1, &mut server).unwrap(); + assert_eq!( + client_1 + .negotiated_key_exchange_group() + .map(|kxg| kxg.name()), + Some(NamedGroup::secp384r1) + ); + assert_eq!(client_1.handshake_kind(), Some(HandshakeKind::Full)); + + let ops = shared_storage.ops(); + println!("storage {ops:#?}"); + assert_eq!(ops.len(), 7); + assert!(matches!( + ops[3], + ClientStorageOp::SetKxHint(_, NamedGroup::secp384r1) + )); + + // second handshake; HRR'd from secp384r1 to X25519 + // (but resuming is possible, since the session storage is shared) + let (mut client_2, mut server) = make_pair_for_configs(client_config_2, server_config); + do_handshake(&mut client_2, &mut server); + assert_eq!( + client_2 + .negotiated_key_exchange_group() + .map(|kxg| kxg.name()), + Some(NamedGroup::X25519) + ); + assert_eq!( + client_2.handshake_kind(), + Some(HandshakeKind::ResumedWithHelloRetryRequest) + ); +} + +#[test] +fn test_server_rejects_clients_without_any_kx_groups() { + let (_, mut server) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + server + .read_tls( + &mut encoding::message_framing( + ContentType::Handshake, + ProtocolVersion::TLSv1_2, + encoding::client_hello_with_extensions(vec![ + encoding::Extension::new_sig_algs(), + encoding::Extension { + typ: encoding::Extension::ELLIPTIC_CURVES, + body: encoding::len_u16(vec![]), + }, + encoding::Extension { + typ: encoding::Extension::KEY_SHARE, + body: encoding::len_u16(vec![]), + }, + ]), + ) + .as_slice(), + ) + .unwrap(); + assert_eq!( + server.process_new_packets(), + Err(Error::InvalidMessage(InvalidMessage::IllegalEmptyList( + "NamedGroups" + ))) + ); +} + +#[test] +fn test_server_rejects_clients_without_any_kx_group_overlap() { + for version_provider in ALL_VERSIONS { + let (mut client, mut server) = make_pair_for_configs( + make_client_config_with_kx_groups( + KeyType::Rsa2048, + vec![provider::kx_group::X25519], + &version_provider, + ), + ServerConfig::builder( + CryptoProvider { + kx_groups: Cow::Owned(vec![provider::kx_group::SECP384R1]), + ..version_provider + } + .into(), + ) + .finish(KeyType::Rsa2048), + ); + transfer(&mut client, &mut server); + assert_eq!( + server.process_new_packets(), + Err(Error::PeerIncompatible( + PeerIncompatible::NoKxGroupsInCommon + )) + ); + transfer(&mut server, &mut client); + assert_eq!( + client.process_new_packets(), + Err(Error::AlertReceived(AlertDescription::HandshakeFailure)) + ); + } +} + +#[test] +fn hybrid_kx_component_share_offered_but_server_chooses_something_else() { + let kt = KeyType::Rsa2048; + let client_config = ClientConfig::builder( + CryptoProvider { + kx_groups: Cow::Owned(vec![&FakeHybrid, provider::kx_group::SECP384R1]), + ..provider::DEFAULT_PROVIDER + } + .into(), + ) + .finish(kt); + let provider = provider::DEFAULT_PROVIDER; + let server_config = make_server_config(kt, &provider); + + let (mut client_1, mut server) = make_pair_for_configs(client_config, server_config); + let (mut client_2, _) = make_pair(kt, &provider); + + // client_2 supplies the ClientHello, client_1 receives the ServerHello + transfer(&mut client_2, &mut server); + server.process_new_packets().unwrap(); + transfer(&mut server, &mut client_1); + assert_eq!( + client_1 + .process_new_packets() + .unwrap_err(), + PeerMisbehaved::WrongGroupForKeyShare.into() + ); +} + +#[derive(Debug)] +struct FakeHybrid; + +impl SupportedKxGroup for FakeHybrid { + fn start(&self) -> Result { + Ok(StartedKeyExchange::Hybrid(Box::new(FakeHybridActive))) + } + + fn name(&self) -> NamedGroup { + NamedGroup::from(0x1234) + } +} + +struct FakeHybridActive; + +impl ActiveKeyExchange for FakeHybridActive { + fn complete(self: Box, _peer_pub_key: &[u8]) -> Result { + Err(PeerMisbehaved::InvalidKeyShare.into()) + } + + fn pub_key(&self) -> &[u8] { + b"hybrid" + } + + fn group(&self) -> NamedGroup { + FakeHybrid.name() + } +} + +impl HybridKeyExchange for FakeHybridActive { + fn component(&self) -> (NamedGroup, &[u8]) { + (provider::kx_group::SECP384R1.name(), b"classical") + } + + fn complete_component(self: Box, _peer_pub_key: &[u8]) -> Result { + unimplemented!() + } + + fn as_key_exchange(&self) -> &(dyn ActiveKeyExchange + 'static) { + self + } + + fn into_key_exchange(self: Box) -> Box { + self + } +} diff --git a/rustls-test/tests/api/quic.rs b/rustls-test/tests/api/quic.rs new file mode 100644 index 00000000000..e9e720166ff --- /dev/null +++ b/rustls-test/tests/api/quic.rs @@ -0,0 +1,867 @@ +//! Tests for QUIC + +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] + +use std::sync::Arc; + +use rustls::HandshakeKind; +use rustls::client::Resumption; +use rustls::error::{ + AlertDescription, ApiMisuse, Error, InvalidMessage, PeerIncompatible, PeerMisbehaved, +}; +use rustls::quic::{self, Connection, Side}; +use rustls_test::{ + ClientStorage, KeyType, encoding, make_client_config, make_server_config, server_name, +}; + +use super::provider; + +// Returns the sender's next secrets to use, or the receiver's error. +fn step( + send: &mut impl Connection, + recv: &mut impl Connection, +) -> Result, Error> { + let mut buf = Vec::new(); + let change = loop { + let prev = buf.len(); + if let Some(x) = send.write_hs(&mut buf) { + break Some(x); + } + if prev == buf.len() { + break None; + } + }; + + recv.read_hs(&buf)?; + Ok(change) +} + +#[test] +fn test_quic_handshake() { + fn equal_packet_keys(x: &dyn quic::PacketKey, y: &dyn quic::PacketKey) -> bool { + // Check that these two sets of keys are equal. + let mut buf = [0; 32]; + let (header, payload_tag) = buf.split_at_mut(8); + let (payload, tag_buf) = payload_tag.split_at_mut(8); + let tag = x + .encrypt_in_place(42, header, payload, None) + .unwrap(); + tag_buf.copy_from_slice(tag.as_ref()); + + let result = y.decrypt_in_place(42, header, payload_tag, None); + match result { + Ok(payload) => payload == [0; 8], + Err(_) => false, + } + } + + fn compatible_keys(x: &quic::KeyChange, y: &quic::KeyChange) -> bool { + fn keys(kc: &quic::KeyChange) -> &quic::Keys { + match kc { + quic::KeyChange::Handshake { keys } => keys, + quic::KeyChange::OneRtt { keys, .. } => keys, + } + } + + let (x, y) = (keys(x), keys(y)); + equal_packet_keys(x.local.packet.as_ref(), y.remote.packet.as_ref()) + && equal_packet_keys(x.remote.packet.as_ref(), y.local.packet.as_ref()) + } + + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_TLS13_PROVIDER; + let mut client_config = make_client_config(kt, &provider); + client_config.enable_early_data = true; + let client_config = Arc::new(client_config); + let mut server_config = make_server_config(kt, &provider); + server_config.max_early_data_size = 0xffffffff; + let server_config = Arc::new(server_config); + let client_params = &b"client params"[..]; + let server_params = &b"server params"[..]; + + // full handshake + let mut client = quic::ClientConnection::new( + client_config.clone(), + quic::Version::V1, + server_name("localhost"), + client_params.into(), + ) + .unwrap(); + + let mut server = quic::ServerConnection::new( + server_config.clone(), + quic::Version::V1, + server_params.into(), + ) + .unwrap(); + + let client_initial = step(&mut client, &mut server).unwrap(); + assert!(client_initial.is_none()); + assert!(client.zero_rtt_keys().is_none()); + assert_eq!(server.quic_transport_parameters(), Some(client_params)); + let server_hs = step(&mut server, &mut client) + .unwrap() + .unwrap(); + assert!(server.zero_rtt_keys().is_none()); + let client_hs = step(&mut client, &mut server) + .unwrap() + .unwrap(); + assert!(compatible_keys(&server_hs, &client_hs)); + assert!(client.is_handshaking()); + let server_1rtt = step(&mut server, &mut client) + .unwrap() + .unwrap(); + assert!(!client.is_handshaking()); + assert_eq!(client.quic_transport_parameters(), Some(server_params)); + assert!(server.is_handshaking()); + let client_1rtt = step(&mut client, &mut server) + .unwrap() + .unwrap(); + assert!(!server.is_handshaking()); + assert!(compatible_keys(&server_1rtt, &client_1rtt)); + assert!(!compatible_keys(&server_hs, &server_1rtt)); + + assert!( + step(&mut client, &mut server) + .unwrap() + .is_none() + ); + assert!( + step(&mut server, &mut client) + .unwrap() + .is_none() + ); + assert_eq!(client.tls13_tickets_received(), 2); + + // 0-RTT handshake + let mut client = quic::ClientConnection::new( + client_config.clone(), + quic::Version::V1, + server_name("localhost"), + client_params.into(), + ) + .unwrap(); + assert!( + client + .negotiated_cipher_suite() + .is_some() + ); + + let mut server = quic::ServerConnection::new( + server_config.clone(), + quic::Version::V1, + server_params.into(), + ) + .unwrap(); + + step(&mut client, &mut server).unwrap(); + assert_eq!(client.quic_transport_parameters(), Some(server_params)); + { + let client_early = client.zero_rtt_keys().unwrap(); + let server_early = server.zero_rtt_keys().unwrap(); + assert!(equal_packet_keys( + client_early.packet.as_ref(), + server_early.packet.as_ref() + )); + } + step(&mut server, &mut client) + .unwrap() + .unwrap(); + step(&mut client, &mut server) + .unwrap() + .unwrap(); + step(&mut server, &mut client) + .unwrap() + .unwrap(); + assert!(client.is_early_data_accepted()); + + // failed handshake + let mut client = quic::ClientConnection::new( + client_config, + quic::Version::V1, + server_name("example.com"), + client_params.into(), + ) + .unwrap(); + + let mut server = + quic::ServerConnection::new(server_config, quic::Version::V1, server_params.into()) + .unwrap(); + + step(&mut client, &mut server).unwrap(); + step(&mut server, &mut client) + .unwrap() + .unwrap(); + let err = step(&mut server, &mut client) + .err() + .unwrap(); + assert_eq!( + AlertDescription::try_from(&err).ok(), + Some(AlertDescription::BadCertificate) + ); + + // Key updates + + let ( + quic::KeyChange::OneRtt { + next: mut client_secrets, + .. + }, + quic::KeyChange::OneRtt { + next: mut server_secrets, + .. + }, + ) = (client_1rtt, server_1rtt) + else { + unreachable!(); + }; + + let mut client_next = client_secrets.next_packet_keys(); + let mut server_next = server_secrets.next_packet_keys(); + assert!(equal_packet_keys( + client_next.local.as_ref(), + server_next.remote.as_ref() + )); + assert!(equal_packet_keys( + server_next.local.as_ref(), + client_next.remote.as_ref() + )); + + client_next = client_secrets.next_packet_keys(); + server_next = server_secrets.next_packet_keys(); + assert!(equal_packet_keys( + client_next.local.as_ref(), + server_next.remote.as_ref() + )); + assert!(equal_packet_keys( + server_next.local.as_ref(), + client_next.remote.as_ref() + )); +} + +#[test] +fn test_quic_rejects_missing_alpn() { + let client_params = &b"client params"[..]; + let server_params = &b"server params"[..]; + let provider = provider::DEFAULT_TLS13_PROVIDER; + + for &kt in KeyType::all_for_provider(&provider) { + let client_config = make_client_config(kt, &provider); + let client_config = Arc::new(client_config); + + let mut server_config = make_server_config(kt, &provider); + server_config.alpn_protocols = vec![b"foo".into()]; + let server_config = Arc::new(server_config); + + let mut client = quic::ClientConnection::new( + client_config, + quic::Version::V1, + server_name("localhost"), + client_params.into(), + ) + .unwrap(); + let mut server = + quic::ServerConnection::new(server_config, quic::Version::V1, server_params.into()) + .unwrap(); + + let err = step(&mut client, &mut server) + .err() + .unwrap(); + assert_eq!(err, Error::NoApplicationProtocol); + assert_eq!( + AlertDescription::try_from(&err).ok(), + Some(AlertDescription::NoApplicationProtocol) + ); + } +} + +#[test] +fn test_quic_no_tls13_error() { + let provider = provider::DEFAULT_TLS12_PROVIDER; + let mut client_config = make_client_config(KeyType::Ed25519, &provider); + client_config.alpn_protocols = vec![b"foo".into()]; + let client_config = Arc::new(client_config); + + assert_eq!( + quic::ClientConnection::new( + client_config, + quic::Version::V1, + server_name("localhost"), + b"client params".to_vec(), + ) + .err(), + Some(ApiMisuse::QuicRequiresTls13Support.into()) + ); + + let mut server_config = make_server_config(KeyType::Ed25519, &provider); + server_config.alpn_protocols = vec![b"foo".into()]; + let server_config = Arc::new(server_config); + + assert_eq!( + quic::ServerConnection::new(server_config, quic::Version::V1, b"server params".to_vec(),) + .err(), + Some(ApiMisuse::QuicRequiresTls13Support.into()) + ); +} + +#[test] +fn test_quic_invalid_early_data_size() { + let provider = provider::DEFAULT_TLS13_PROVIDER; + let mut server_config = make_server_config(KeyType::Ed25519, &provider); + server_config.alpn_protocols = vec![b"foo".into()]; + + let cases = [ + (None, true), + (Some(0u32), true), + (Some(5), false), + (Some(0xffff_ffff), true), + ]; + + for &(size, ok) in cases.iter() { + println!("early data size case: {size:?}"); + if let Some(new) = size { + server_config.max_early_data_size = new; + } + + let wrapped = Arc::new(server_config.clone()); + assert_eq!( + quic::ServerConnection::new(wrapped, quic::Version::V1, b"server params".to_vec(),) + .is_ok(), + ok + ); + } +} + +#[test] +fn test_quic_read_deframer_failure() { + let provider = provider::DEFAULT_TLS13_PROVIDER; + let server_config = make_server_config(KeyType::EcdsaP256, &provider); + let server_config = Arc::new(server_config); + + let mut server = + quic::ServerConnection::new(server_config, quic::Version::V1, b"server params".to_vec()) + .unwrap(); + + let err = server + .read_hs(&encoding::handshake_framing( + rustls::enums::HandshakeType::ClientHello, + vec![0x00; 32], + )) + .err() + .unwrap(); + assert_eq!(err, InvalidMessage::MissingData("Random").into()); + assert_eq!( + AlertDescription::try_from(&err).ok(), + Some(AlertDescription::DecodeError) + ); +} + +#[test] +fn test_quic_server_no_params_received() { + let provider = provider::DEFAULT_TLS13_PROVIDER; + let server_config = make_server_config(KeyType::EcdsaP256, &provider); + let server_config = Arc::new(server_config); + + let mut server = + quic::ServerConnection::new(server_config, quic::Version::V1, b"server params".to_vec()) + .unwrap(); + + let buf = encoding::basic_client_hello(vec![]); + let err = server + .read_hs(buf.as_slice()) + .err() + .unwrap(); + assert_eq!( + err, + Error::PeerMisbehaved(PeerMisbehaved::MissingQuicTransportParameters) + ); + assert_eq!( + AlertDescription::try_from(&err).ok(), + Some(AlertDescription::MissingExtension) + ); +} + +#[test] +fn test_quic_server_no_tls12() { + let provider = provider::DEFAULT_TLS13_PROVIDER; + let mut server_config = make_server_config(KeyType::Ed25519, &provider); + server_config.alpn_protocols = vec![b"foo".into()]; + let server_config = Arc::new(server_config); + + let mut server = + quic::ServerConnection::new(server_config, quic::Version::V1, b"server params".to_vec()) + .unwrap(); + + let buf = encoding::client_hello_with_extensions(vec![ + encoding::Extension::new_sig_algs(), + encoding::Extension::new_dummy_key_share(), + encoding::Extension::new_kx_groups(), + ]); + let err = server + .read_hs(buf.as_slice()) + .err() + .unwrap(); + assert_eq!( + err, + Error::PeerIncompatible(PeerIncompatible::SupportedVersionsExtensionRequired), + ); + assert_eq!( + AlertDescription::try_from(&err).ok(), + Some(AlertDescription::ProtocolVersion) + ); +} + +fn do_quic_handshake(client: &mut impl Connection, server: &mut impl Connection) { + while client.is_handshaking() || server.is_handshaking() { + quic_transfer(client, server); + quic_transfer(server, client); + } +} + +fn quic_transfer(sender: &mut impl Connection, receiver: &mut impl Connection) { + let mut buf = Vec::new(); + while let Some(_change) = sender.write_hs(&mut buf) { + // In a real QUIC implementation, we would handle key changes here + // For testing, we just continue + } + + if !buf.is_empty() { + receiver.read_hs(&buf).unwrap(); + } +} + +#[test] +fn test_quic_resumption_data_basic() { + let server_params = b"server params"; + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_TLS13_PROVIDER; + + let mut server_config = make_server_config(kt, &provider); + server_config.alpn_protocols = vec![b"foo".into()]; + server_config.max_early_data_size = 0xffff_ffff; + server_config.ticketer = Some( + provider + .ticketer_factory + .ticketer() + .unwrap(), + ); + server_config.send_tls13_tickets = 2; + let server_config = Arc::new(server_config); + + let mut server = + quic::ServerConnection::new(server_config, quic::Version::V1, server_params.to_vec()) + .unwrap(); + + // Initially, no resumption data should be received + assert_eq!(server.received_resumption_data(), None); + + // Set resumption data + let test_data1 = b"test resumption data 1"; + server + .set_resumption_data(test_data1) + .unwrap(); + // Still no received data (server has set data, but hasn't received any from client) + assert_eq!(server.received_resumption_data(), None); + + // Update resumption data with different content + let test_data2 = b"test resumption data 2"; + server + .set_resumption_data(test_data2) + .unwrap(); + // Still no received data + assert_eq!(server.received_resumption_data(), None); + + // Test empty resumption data + server.set_resumption_data(b"").unwrap(); + assert_eq!(server.received_resumption_data(), None); +} + +#[test] +fn test_quic_resumption_data_0rtt() { + let client_params = b"client params"; + let server_params = b"server params"; + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_TLS13_PROVIDER; + + let mut client_config = make_client_config(kt, &provider); + client_config.alpn_protocols = vec![b"foo".into()]; + client_config.enable_early_data = true; + client_config.resumption = Resumption::store(Arc::new(ClientStorage::new())); + let client_config = Arc::new(client_config); + + let mut server_config = make_server_config(kt, &provider); + server_config.alpn_protocols = vec![b"foo".into()]; + server_config.max_early_data_size = 0xffff_ffff; + server_config.ticketer = Some( + provider + .ticketer_factory + .ticketer() + .unwrap(), + ); + server_config.send_tls13_tickets = 2; + let server_config = Arc::new(server_config); + + // QUIC 0-RTT parameters to store in resumption data + let quic_0rtt_params = b"active_connection_id_limit=2,initial_max_data=1048576,initial_max_stream_data_bidi_local=262144,initial_max_stream_data_bidi_remote=262144,initial_max_stream_data_uni=262144,initial_max_streams_bidi=100,initial_max_streams_uni=100,max_datagram_frame_size=1500"; + + // First connection: establish session with 0-RTT parameters + let mut server1 = quic::ServerConnection::new( + server_config.clone(), + quic::Version::V1, + server_params.to_vec(), + ) + .unwrap(); + + server1 + .set_resumption_data(quic_0rtt_params) + .unwrap(); + assert_eq!(server1.received_resumption_data(), None); + + let mut client1 = quic::ClientConnection::new( + client_config.clone(), + quic::Version::V1, + server_name("localhost"), + client_params.to_vec(), + ) + .unwrap(); + + do_quic_handshake(&mut client1, &mut server1); + + // Verify initial connection + assert_eq!(client1.handshake_kind(), Some(HandshakeKind::Full)); + assert_eq!(server1.handshake_kind(), Some(HandshakeKind::Full)); + assert_eq!(server1.received_resumption_data(), None); + + // Second connection: attempt 0-RTT resumption + let mut server2 = + quic::ServerConnection::new(server_config, quic::Version::V1, server_params.to_vec()) + .unwrap(); + + let mut client2 = quic::ClientConnection::new( + client_config, + quic::Version::V1, + server_name("localhost"), + client_params.to_vec(), + ) + .unwrap(); + + // Check negotiated cipher suite for potential 0-RTT + assert!( + client2 + .negotiated_cipher_suite() + .is_some() + ); + + // Start handshake and check transport parameters early + quic_transfer(&mut client2, &mut server2); + assert_eq!( + client2.quic_transport_parameters(), + Some(server_params.as_slice()) + ); + + // Complete the handshake (whether 0-RTT or regular resumption) + do_quic_handshake(&mut client2, &mut server2); + + // Verify resumption worked and parameters were received + assert_eq!(client2.handshake_kind(), Some(HandshakeKind::Resumed)); + assert_eq!(server2.handshake_kind(), Some(HandshakeKind::Resumed)); + assert_eq!( + server2.received_resumption_data(), + Some(quic_0rtt_params.as_slice()), + "Server should receive QUIC 0-RTT parameters from resumption data" + ); + + // Verify server can parse and use the received 0-RTT parameters + if let Some(received_params) = server2.received_resumption_data() { + let params_str = core::str::from_utf8(received_params).unwrap(); + assert!(params_str.contains("active_connection_id_limit=2")); + assert!(params_str.contains("initial_max_data=1048576")); + assert!(params_str.contains("initial_max_stream_data_bidi_local=262144")); + assert!(params_str.contains("initial_max_stream_data_bidi_remote=262144")); + assert!(params_str.contains("initial_max_stream_data_uni=262144")); + assert!(params_str.contains("initial_max_streams_bidi=100")); + assert!(params_str.contains("initial_max_streams_uni=100")); + assert!(params_str.contains("max_datagram_frame_size=1500")); + } +} + +#[test] +fn packet_key_api() { + use provider::cipher_suite::TLS13_AES_128_GCM_SHA256; + use rustls::quic::{Keys, Version}; + + // Test vectors: https://www.rfc-editor.org/rfc/rfc9001.html#name-client-initial + const CONNECTION_ID: &[u8] = &[0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08]; + const PACKET_NUMBER: u64 = 2; + const PLAIN_HEADER: &[u8] = &[ + 0xc3, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0x00, + 0x00, 0x44, 0x9e, 0x00, 0x00, 0x00, 0x02, + ]; + + const PAYLOAD: &[u8] = &[ + 0x06, 0x00, 0x40, 0xf1, 0x01, 0x00, 0x00, 0xed, 0x03, 0x03, 0xeb, 0xf8, 0xfa, 0x56, 0xf1, + 0x29, 0x39, 0xb9, 0x58, 0x4a, 0x38, 0x96, 0x47, 0x2e, 0xc4, 0x0b, 0xb8, 0x63, 0xcf, 0xd3, + 0xe8, 0x68, 0x04, 0xfe, 0x3a, 0x47, 0xf0, 0x6a, 0x2b, 0x69, 0x48, 0x4c, 0x00, 0x00, 0x04, + 0x13, 0x01, 0x13, 0x02, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, + 0x00, 0x0b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0xff, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, + 0x00, 0x10, 0x00, 0x07, 0x00, 0x05, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x00, 0x05, 0x00, 0x05, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, + 0x93, 0x70, 0xb2, 0xc9, 0xca, 0xa4, 0x7f, 0xba, 0xba, 0xf4, 0x55, 0x9f, 0xed, 0xba, 0x75, + 0x3d, 0xe1, 0x71, 0xfa, 0x71, 0xf5, 0x0f, 0x1c, 0xe1, 0x5d, 0x43, 0xe9, 0x94, 0xec, 0x74, + 0xd7, 0x48, 0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04, 0x00, 0x0d, 0x00, 0x10, 0x00, 0x0e, + 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x02, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x00, + 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x1c, 0x00, 0x02, 0x40, 0x01, 0x00, 0x39, 0x00, 0x32, + 0x04, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x05, 0x04, 0x80, 0x00, 0xff, + 0xff, 0x07, 0x04, 0x80, 0x00, 0xff, 0xff, 0x08, 0x01, 0x10, 0x01, 0x04, 0x80, 0x00, 0x75, + 0x30, 0x09, 0x01, 0x10, 0x0f, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0x06, + 0x04, 0x80, 0x00, 0xff, 0xff, + ]; + + let client_keys = Keys::initial( + Version::V1, + TLS13_AES_128_GCM_SHA256, + TLS13_AES_128_GCM_SHA256.quic.unwrap(), + CONNECTION_ID, + Side::Client, + ); + assert_eq!(client_keys.local.packet.tag_len(), 16); + + let mut buf = Vec::new(); + buf.extend(PLAIN_HEADER); + buf.extend(PAYLOAD); + let header_len = PLAIN_HEADER.len(); + let tag_len = client_keys.local.packet.tag_len(); + let padding_len = 1200 - header_len - PAYLOAD.len() - tag_len; + buf.extend(core::iter::repeat_n(0, padding_len)); + let (header, payload) = buf.split_at_mut(header_len); + let tag = client_keys + .local + .packet + .encrypt_in_place(PACKET_NUMBER, header, payload, None) + .unwrap(); + + let sample_len = client_keys.local.header.sample_len(); + let sample = &payload[..sample_len]; + let (first, rest) = header.split_at_mut(1); + client_keys + .local + .header + .encrypt_in_place(sample, &mut first[0], &mut rest[17..21]) + .unwrap(); + buf.extend_from_slice(tag.as_ref()); + + const PROTECTED: &[u8] = &[ + 0xc0, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0x00, + 0x00, 0x44, 0x9e, 0x7b, 0x9a, 0xec, 0x34, 0xd1, 0xb1, 0xc9, 0x8d, 0xd7, 0x68, 0x9f, 0xb8, + 0xec, 0x11, 0xd2, 0x42, 0xb1, 0x23, 0xdc, 0x9b, 0xd8, 0xba, 0xb9, 0x36, 0xb4, 0x7d, 0x92, + 0xec, 0x35, 0x6c, 0x0b, 0xab, 0x7d, 0xf5, 0x97, 0x6d, 0x27, 0xcd, 0x44, 0x9f, 0x63, 0x30, + 0x00, 0x99, 0xf3, 0x99, 0x1c, 0x26, 0x0e, 0xc4, 0xc6, 0x0d, 0x17, 0xb3, 0x1f, 0x84, 0x29, + 0x15, 0x7b, 0xb3, 0x5a, 0x12, 0x82, 0xa6, 0x43, 0xa8, 0xd2, 0x26, 0x2c, 0xad, 0x67, 0x50, + 0x0c, 0xad, 0xb8, 0xe7, 0x37, 0x8c, 0x8e, 0xb7, 0x53, 0x9e, 0xc4, 0xd4, 0x90, 0x5f, 0xed, + 0x1b, 0xee, 0x1f, 0xc8, 0xaa, 0xfb, 0xa1, 0x7c, 0x75, 0x0e, 0x2c, 0x7a, 0xce, 0x01, 0xe6, + 0x00, 0x5f, 0x80, 0xfc, 0xb7, 0xdf, 0x62, 0x12, 0x30, 0xc8, 0x37, 0x11, 0xb3, 0x93, 0x43, + 0xfa, 0x02, 0x8c, 0xea, 0x7f, 0x7f, 0xb5, 0xff, 0x89, 0xea, 0xc2, 0x30, 0x82, 0x49, 0xa0, + 0x22, 0x52, 0x15, 0x5e, 0x23, 0x47, 0xb6, 0x3d, 0x58, 0xc5, 0x45, 0x7a, 0xfd, 0x84, 0xd0, + 0x5d, 0xff, 0xfd, 0xb2, 0x03, 0x92, 0x84, 0x4a, 0xe8, 0x12, 0x15, 0x46, 0x82, 0xe9, 0xcf, + 0x01, 0x2f, 0x90, 0x21, 0xa6, 0xf0, 0xbe, 0x17, 0xdd, 0xd0, 0xc2, 0x08, 0x4d, 0xce, 0x25, + 0xff, 0x9b, 0x06, 0xcd, 0xe5, 0x35, 0xd0, 0xf9, 0x20, 0xa2, 0xdb, 0x1b, 0xf3, 0x62, 0xc2, + 0x3e, 0x59, 0x6d, 0x11, 0xa4, 0xf5, 0xa6, 0xcf, 0x39, 0x48, 0x83, 0x8a, 0x3a, 0xec, 0x4e, + 0x15, 0xda, 0xf8, 0x50, 0x0a, 0x6e, 0xf6, 0x9e, 0xc4, 0xe3, 0xfe, 0xb6, 0xb1, 0xd9, 0x8e, + 0x61, 0x0a, 0xc8, 0xb7, 0xec, 0x3f, 0xaf, 0x6a, 0xd7, 0x60, 0xb7, 0xba, 0xd1, 0xdb, 0x4b, + 0xa3, 0x48, 0x5e, 0x8a, 0x94, 0xdc, 0x25, 0x0a, 0xe3, 0xfd, 0xb4, 0x1e, 0xd1, 0x5f, 0xb6, + 0xa8, 0xe5, 0xeb, 0xa0, 0xfc, 0x3d, 0xd6, 0x0b, 0xc8, 0xe3, 0x0c, 0x5c, 0x42, 0x87, 0xe5, + 0x38, 0x05, 0xdb, 0x05, 0x9a, 0xe0, 0x64, 0x8d, 0xb2, 0xf6, 0x42, 0x64, 0xed, 0x5e, 0x39, + 0xbe, 0x2e, 0x20, 0xd8, 0x2d, 0xf5, 0x66, 0xda, 0x8d, 0xd5, 0x99, 0x8c, 0xca, 0xbd, 0xae, + 0x05, 0x30, 0x60, 0xae, 0x6c, 0x7b, 0x43, 0x78, 0xe8, 0x46, 0xd2, 0x9f, 0x37, 0xed, 0x7b, + 0x4e, 0xa9, 0xec, 0x5d, 0x82, 0xe7, 0x96, 0x1b, 0x7f, 0x25, 0xa9, 0x32, 0x38, 0x51, 0xf6, + 0x81, 0xd5, 0x82, 0x36, 0x3a, 0xa5, 0xf8, 0x99, 0x37, 0xf5, 0xa6, 0x72, 0x58, 0xbf, 0x63, + 0xad, 0x6f, 0x1a, 0x0b, 0x1d, 0x96, 0xdb, 0xd4, 0xfa, 0xdd, 0xfc, 0xef, 0xc5, 0x26, 0x6b, + 0xa6, 0x61, 0x17, 0x22, 0x39, 0x5c, 0x90, 0x65, 0x56, 0xbe, 0x52, 0xaf, 0xe3, 0xf5, 0x65, + 0x63, 0x6a, 0xd1, 0xb1, 0x7d, 0x50, 0x8b, 0x73, 0xd8, 0x74, 0x3e, 0xeb, 0x52, 0x4b, 0xe2, + 0x2b, 0x3d, 0xcb, 0xc2, 0xc7, 0x46, 0x8d, 0x54, 0x11, 0x9c, 0x74, 0x68, 0x44, 0x9a, 0x13, + 0xd8, 0xe3, 0xb9, 0x58, 0x11, 0xa1, 0x98, 0xf3, 0x49, 0x1d, 0xe3, 0xe7, 0xfe, 0x94, 0x2b, + 0x33, 0x04, 0x07, 0xab, 0xf8, 0x2a, 0x4e, 0xd7, 0xc1, 0xb3, 0x11, 0x66, 0x3a, 0xc6, 0x98, + 0x90, 0xf4, 0x15, 0x70, 0x15, 0x85, 0x3d, 0x91, 0xe9, 0x23, 0x03, 0x7c, 0x22, 0x7a, 0x33, + 0xcd, 0xd5, 0xec, 0x28, 0x1c, 0xa3, 0xf7, 0x9c, 0x44, 0x54, 0x6b, 0x9d, 0x90, 0xca, 0x00, + 0xf0, 0x64, 0xc9, 0x9e, 0x3d, 0xd9, 0x79, 0x11, 0xd3, 0x9f, 0xe9, 0xc5, 0xd0, 0xb2, 0x3a, + 0x22, 0x9a, 0x23, 0x4c, 0xb3, 0x61, 0x86, 0xc4, 0x81, 0x9e, 0x8b, 0x9c, 0x59, 0x27, 0x72, + 0x66, 0x32, 0x29, 0x1d, 0x6a, 0x41, 0x82, 0x11, 0xcc, 0x29, 0x62, 0xe2, 0x0f, 0xe4, 0x7f, + 0xeb, 0x3e, 0xdf, 0x33, 0x0f, 0x2c, 0x60, 0x3a, 0x9d, 0x48, 0xc0, 0xfc, 0xb5, 0x69, 0x9d, + 0xbf, 0xe5, 0x89, 0x64, 0x25, 0xc5, 0xba, 0xc4, 0xae, 0xe8, 0x2e, 0x57, 0xa8, 0x5a, 0xaf, + 0x4e, 0x25, 0x13, 0xe4, 0xf0, 0x57, 0x96, 0xb0, 0x7b, 0xa2, 0xee, 0x47, 0xd8, 0x05, 0x06, + 0xf8, 0xd2, 0xc2, 0x5e, 0x50, 0xfd, 0x14, 0xde, 0x71, 0xe6, 0xc4, 0x18, 0x55, 0x93, 0x02, + 0xf9, 0x39, 0xb0, 0xe1, 0xab, 0xd5, 0x76, 0xf2, 0x79, 0xc4, 0xb2, 0xe0, 0xfe, 0xb8, 0x5c, + 0x1f, 0x28, 0xff, 0x18, 0xf5, 0x88, 0x91, 0xff, 0xef, 0x13, 0x2e, 0xef, 0x2f, 0xa0, 0x93, + 0x46, 0xae, 0xe3, 0x3c, 0x28, 0xeb, 0x13, 0x0f, 0xf2, 0x8f, 0x5b, 0x76, 0x69, 0x53, 0x33, + 0x41, 0x13, 0x21, 0x19, 0x96, 0xd2, 0x00, 0x11, 0xa1, 0x98, 0xe3, 0xfc, 0x43, 0x3f, 0x9f, + 0x25, 0x41, 0x01, 0x0a, 0xe1, 0x7c, 0x1b, 0xf2, 0x02, 0x58, 0x0f, 0x60, 0x47, 0x47, 0x2f, + 0xb3, 0x68, 0x57, 0xfe, 0x84, 0x3b, 0x19, 0xf5, 0x98, 0x40, 0x09, 0xdd, 0xc3, 0x24, 0x04, + 0x4e, 0x84, 0x7a, 0x4f, 0x4a, 0x0a, 0xb3, 0x4f, 0x71, 0x95, 0x95, 0xde, 0x37, 0x25, 0x2d, + 0x62, 0x35, 0x36, 0x5e, 0x9b, 0x84, 0x39, 0x2b, 0x06, 0x10, 0x85, 0x34, 0x9d, 0x73, 0x20, + 0x3a, 0x4a, 0x13, 0xe9, 0x6f, 0x54, 0x32, 0xec, 0x0f, 0xd4, 0xa1, 0xee, 0x65, 0xac, 0xcd, + 0xd5, 0xe3, 0x90, 0x4d, 0xf5, 0x4c, 0x1d, 0xa5, 0x10, 0xb0, 0xff, 0x20, 0xdc, 0xc0, 0xc7, + 0x7f, 0xcb, 0x2c, 0x0e, 0x0e, 0xb6, 0x05, 0xcb, 0x05, 0x04, 0xdb, 0x87, 0x63, 0x2c, 0xf3, + 0xd8, 0xb4, 0xda, 0xe6, 0xe7, 0x05, 0x76, 0x9d, 0x1d, 0xe3, 0x54, 0x27, 0x01, 0x23, 0xcb, + 0x11, 0x45, 0x0e, 0xfc, 0x60, 0xac, 0x47, 0x68, 0x3d, 0x7b, 0x8d, 0x0f, 0x81, 0x13, 0x65, + 0x56, 0x5f, 0xd9, 0x8c, 0x4c, 0x8e, 0xb9, 0x36, 0xbc, 0xab, 0x8d, 0x06, 0x9f, 0xc3, 0x3b, + 0xd8, 0x01, 0xb0, 0x3a, 0xde, 0xa2, 0xe1, 0xfb, 0xc5, 0xaa, 0x46, 0x3d, 0x08, 0xca, 0x19, + 0x89, 0x6d, 0x2b, 0xf5, 0x9a, 0x07, 0x1b, 0x85, 0x1e, 0x6c, 0x23, 0x90, 0x52, 0x17, 0x2f, + 0x29, 0x6b, 0xfb, 0x5e, 0x72, 0x40, 0x47, 0x90, 0xa2, 0x18, 0x10, 0x14, 0xf3, 0xb9, 0x4a, + 0x4e, 0x97, 0xd1, 0x17, 0xb4, 0x38, 0x13, 0x03, 0x68, 0xcc, 0x39, 0xdb, 0xb2, 0xd1, 0x98, + 0x06, 0x5a, 0xe3, 0x98, 0x65, 0x47, 0x92, 0x6c, 0xd2, 0x16, 0x2f, 0x40, 0xa2, 0x9f, 0x0c, + 0x3c, 0x87, 0x45, 0xc0, 0xf5, 0x0f, 0xba, 0x38, 0x52, 0xe5, 0x66, 0xd4, 0x45, 0x75, 0xc2, + 0x9d, 0x39, 0xa0, 0x3f, 0x0c, 0xda, 0x72, 0x19, 0x84, 0xb6, 0xf4, 0x40, 0x59, 0x1f, 0x35, + 0x5e, 0x12, 0xd4, 0x39, 0xff, 0x15, 0x0a, 0xab, 0x76, 0x13, 0x49, 0x9d, 0xbd, 0x49, 0xad, + 0xab, 0xc8, 0x67, 0x6e, 0xef, 0x02, 0x3b, 0x15, 0xb6, 0x5b, 0xfc, 0x5c, 0xa0, 0x69, 0x48, + 0x10, 0x9f, 0x23, 0xf3, 0x50, 0xdb, 0x82, 0x12, 0x35, 0x35, 0xeb, 0x8a, 0x74, 0x33, 0xbd, + 0xab, 0xcb, 0x90, 0x92, 0x71, 0xa6, 0xec, 0xbc, 0xb5, 0x8b, 0x93, 0x6a, 0x88, 0xcd, 0x4e, + 0x8f, 0x2e, 0x6f, 0xf5, 0x80, 0x01, 0x75, 0xf1, 0x13, 0x25, 0x3d, 0x8f, 0xa9, 0xca, 0x88, + 0x85, 0xc2, 0xf5, 0x52, 0xe6, 0x57, 0xdc, 0x60, 0x3f, 0x25, 0x2e, 0x1a, 0x8e, 0x30, 0x8f, + 0x76, 0xf0, 0xbe, 0x79, 0xe2, 0xfb, 0x8f, 0x5d, 0x5f, 0xbb, 0xe2, 0xe3, 0x0e, 0xca, 0xdd, + 0x22, 0x07, 0x23, 0xc8, 0xc0, 0xae, 0xa8, 0x07, 0x8c, 0xdf, 0xcb, 0x38, 0x68, 0x26, 0x3f, + 0xf8, 0xf0, 0x94, 0x00, 0x54, 0xda, 0x48, 0x78, 0x18, 0x93, 0xa7, 0xe4, 0x9a, 0xd5, 0xaf, + 0xf4, 0xaf, 0x30, 0x0c, 0xd8, 0x04, 0xa6, 0xb6, 0x27, 0x9a, 0xb3, 0xff, 0x3a, 0xfb, 0x64, + 0x49, 0x1c, 0x85, 0x19, 0x4a, 0xab, 0x76, 0x0d, 0x58, 0xa6, 0x06, 0x65, 0x4f, 0x9f, 0x44, + 0x00, 0xe8, 0xb3, 0x85, 0x91, 0x35, 0x6f, 0xbf, 0x64, 0x25, 0xac, 0xa2, 0x6d, 0xc8, 0x52, + 0x44, 0x25, 0x9f, 0xf2, 0xb1, 0x9c, 0x41, 0xb9, 0xf9, 0x6f, 0x3c, 0xa9, 0xec, 0x1d, 0xde, + 0x43, 0x4d, 0xa7, 0xd2, 0xd3, 0x92, 0xb9, 0x05, 0xdd, 0xf3, 0xd1, 0xf9, 0xaf, 0x93, 0xd1, + 0xaf, 0x59, 0x50, 0xbd, 0x49, 0x3f, 0x5a, 0xa7, 0x31, 0xb4, 0x05, 0x6d, 0xf3, 0x1b, 0xd2, + 0x67, 0xb6, 0xb9, 0x0a, 0x07, 0x98, 0x31, 0xaa, 0xf5, 0x79, 0xbe, 0x0a, 0x39, 0x01, 0x31, + 0x37, 0xaa, 0xc6, 0xd4, 0x04, 0xf5, 0x18, 0xcf, 0xd4, 0x68, 0x40, 0x64, 0x7e, 0x78, 0xbf, + 0xe7, 0x06, 0xca, 0x4c, 0xf5, 0xe9, 0xc5, 0x45, 0x3e, 0x9f, 0x7c, 0xfd, 0x2b, 0x8b, 0x4c, + 0x8d, 0x16, 0x9a, 0x44, 0xe5, 0x5c, 0x88, 0xd4, 0xa9, 0xa7, 0xf9, 0x47, 0x42, 0x41, 0xe2, + 0x21, 0xaf, 0x44, 0x86, 0x00, 0x18, 0xab, 0x08, 0x56, 0x97, 0x2e, 0x19, 0x4c, 0xd9, 0x34, + ]; + + assert_eq!(&buf, PROTECTED); + + let (header, payload) = buf.split_at_mut(header_len); + let (first, rest) = header.split_at_mut(1); + let sample = &payload[..sample_len]; + + let server_keys = Keys::initial( + Version::V1, + TLS13_AES_128_GCM_SHA256, + TLS13_AES_128_GCM_SHA256.quic.unwrap(), + CONNECTION_ID, + Side::Server, + ); + server_keys + .remote + .header + .decrypt_in_place(sample, &mut first[0], &mut rest[17..21]) + .unwrap(); + let payload = server_keys + .remote + .packet + .decrypt_in_place(PACKET_NUMBER, header, payload, None) + .unwrap(); + + assert_eq!(&payload[..PAYLOAD.len()], PAYLOAD); + assert_eq!(payload.len(), buf.len() - header_len - tag_len); +} + +#[test] +fn test_quic_exporter() { + let provider = provider::DEFAULT_TLS13_PROVIDER; + for &kt in KeyType::all_for_provider(&provider) { + let client_config = make_client_config(kt, &provider); + let server_config = make_server_config(kt, &provider); + + let mut server = + quic::ServerConnection::new(server_config.into(), quic::Version::V2, vec![]).unwrap(); + let mut client = quic::ClientConnection::new( + client_config.into(), + quic::Version::V2, + server_name("localhost"), + vec![], + ) + .unwrap(); + + assert_eq!(Some(Error::HandshakeNotComplete), client.exporter().err()); + assert_eq!(Some(Error::HandshakeNotComplete), server.exporter().err()); + + do_quic_handshake(&mut client, &mut server); + + let client_exporter = client.exporter().unwrap(); + let server_exporter = server.exporter().unwrap(); + + assert_eq!( + client.exporter().err(), + Some(Error::ApiMisuse(ApiMisuse::ExporterAlreadyUsed)), + ); + assert_eq!( + server.exporter().err(), + Some(Error::ApiMisuse(ApiMisuse::ExporterAlreadyUsed)), + ); + + let mut client_secret = [0u8; 64]; + let mut server_secret = [0u8; 64]; + client_exporter + .derive(b"label", Some(b"context"), &mut client_secret) + .unwrap(); + server_exporter + .derive(b"label", Some(b"context"), &mut server_secret) + .unwrap(); + assert_eq!(client_secret, server_secret); + } +} + +#[test] +fn test_fragmented_append() { + // Create a QUIC client connection. + let client_config = make_client_config(KeyType::Rsa2048, &provider::DEFAULT_TLS13_PROVIDER); + let client_config = Arc::new(client_config); + let mut client = quic::ClientConnection::new( + client_config, + quic::Version::V1, + server_name("localhost"), + b"client params"[..].into(), + ) + .unwrap(); + + // Construct a message that is too large to fit in a single QUIC packet. + // We want the partial pieces to be large enough to overflow the deframer's + // 4096 byte buffer if mishandled. + let mut out = vec![0; 4096]; + let len_bytes = u32::to_be_bytes(9266_u32); + out[1..4].copy_from_slice(&len_bytes[1..]); + + // Read the message - this will put us into a joining handshake message state, buffering + // 4096 bytes into the deframer buffer. + client.read_hs(&out).unwrap(); + + // Read the message again - once more it isn't a complete message, so we'll try to + // append another 4096 bytes into the deframer buffer. + // + // If the deframer mishandles writing into the used buffer space this will panic with + // an index out of range error: + // range end index 8192 out of range for slice of length 4096 + client.read_hs(&out).unwrap(); +} + +#[test] +fn server_rejects_client_hello_with_trailing_fragment() { + let mut server = quic::ServerConnection::new( + Arc::new(make_server_config( + KeyType::EcdsaP256, + &provider::DEFAULT_TLS13_PROVIDER, + )), + quic::Version::V2, + b"server params".to_vec(), + ) + .unwrap(); + + // this is a trivial ClientHello, followed by a fragment of a ClientHello + let mut hello = + encoding::basic_client_hello(vec![encoding::Extension::new_quic_transport_params( + b"client params", + )]); + hello.extend(&hello[..10].to_vec()); + + assert_eq!( + server.read_hs(&hello).unwrap_err(), + PeerMisbehaved::KeyEpochWithPendingFragment.into() + ); +} diff --git a/rustls-test/tests/api/raw_keys.rs b/rustls-test/tests/api/raw_keys.rs new file mode 100644 index 00000000000..bf0a8174ed6 --- /dev/null +++ b/rustls-test/tests/api/raw_keys.rs @@ -0,0 +1,127 @@ +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] + +use std::sync::Arc; + +use rustls::crypto::Identity; +use rustls::enums::CertificateType; +use rustls::error::{Error, PeerIncompatible}; +use rustls_test::{ + ErrorFromPeer, KeyType, ServerCheckCertResolve, do_handshake, do_handshake_until_error, + make_client_config, make_client_config_with_raw_key_support, make_pair_for_configs, + make_server_config, make_server_config_with_raw_key_support, +}; + +use super::provider; + +#[test] +fn successful_raw_key_connection_and_correct_peer_certificates() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let client_config = make_client_config_with_raw_key_support(*kt, &provider); + let server_config = make_server_config_with_raw_key_support(*kt, &provider); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake(&mut client, &mut server); + + // Test that the client peer certificate is the server's public key + match client.peer_identity() { + Some(Identity::X509(certificates)) => { + assert!(certificates.intermediates.is_empty()); + assert_eq!(certificates.end_entity.as_ref(), kt.spki().as_ref()); + } + Some(Identity::RawPublicKey(spki)) => { + assert_eq!(spki, &kt.spki()); + } + _ => { + unreachable!("Client should have received a certificate") + } + } + + // Test that the server peer certificate is the client's public key + match server.peer_identity() { + Some(Identity::X509(certificates)) => { + assert!(certificates.intermediates.is_empty()); + assert_eq!(certificates.end_entity.as_ref(), kt.client_spki().as_ref()); + } + Some(Identity::RawPublicKey(spki)) => { + assert_eq!(spki, &kt.client_spki()); + } + _ => { + unreachable!("Server should have received a certificate") + } + } + } +} + +#[test] +fn correct_certificate_type_extensions_from_client_hello() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let client_config = make_client_config_with_raw_key_support(*kt, &provider); + let mut server_config = make_server_config_with_raw_key_support(*kt, &provider); + + server_config.cert_resolver = Arc::new(ServerCheckCertResolve { + expected_client_cert_types: Some(vec![CertificateType::RawPublicKey]), + expected_server_cert_types: Some(vec![CertificateType::RawPublicKey]), + ..Default::default() + }); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err.err(), + Some(ErrorFromPeer::Server(Error::NoSuitableCertificate)) + ); + } +} + +#[test] +fn only_client_supports_raw_keys() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let client_config_rpk = make_client_config_with_raw_key_support(*kt, &provider); + let server_config = make_server_config(*kt, &provider); + + let (mut client_rpk, mut server) = make_pair_for_configs(client_config_rpk, server_config); + + // The client + match do_handshake_until_error(&mut client_rpk, &mut server) { + Err(err) => { + assert_eq!( + err, + ErrorFromPeer::Server(Error::PeerIncompatible( + PeerIncompatible::IncorrectCertificateTypeExtension + )) + ) + } + _ => { + unreachable!("Expected error because client is incorrectly configured") + } + } + } +} + +#[test] +fn only_server_supports_raw_keys() { + let provider = provider::DEFAULT_TLS13_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let client_config = make_client_config(*kt, &provider); + let server_config_rpk = make_server_config_with_raw_key_support(*kt, &provider); + + let (mut client, mut server_rpk) = make_pair_for_configs(client_config, server_config_rpk); + + match do_handshake_until_error(&mut client, &mut server_rpk) { + Err(err) => { + assert_eq!( + err, + ErrorFromPeer::Server(Error::PeerIncompatible( + PeerIncompatible::IncorrectCertificateTypeExtension + )) + ) + } + _ => { + unreachable!("Expected error because client is incorrectly configured") + } + } + } +} diff --git a/rustls-test/tests/api/resolve.rs b/rustls-test/tests/api/resolve.rs new file mode 100644 index 00000000000..1a3d5c35464 --- /dev/null +++ b/rustls-test/tests/api/resolve.rs @@ -0,0 +1,596 @@ +//! Tests for choosing a certificate/key during the handshake. + +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] + +use core::hash::Hasher; +use core::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +use pki_types::{CertificateDer, DnsName}; +use rustls::client::{ClientCredentialResolver, CredentialRequest}; +use rustls::crypto::{CipherSuite, Credentials, Identity, SelectedCredential, SignatureScheme}; +use rustls::enums::{ApplicationProtocol, CertificateType, ProtocolVersion}; +use rustls::error::{CertificateError, Error, PeerMisbehaved}; +use rustls::server::{ClientHello, ServerCredentialResolver, ServerNameResolver}; +use rustls::{ + ClientConfig, Connection, DistinguishedName, ServerConfig, ServerConnection, + SupportedCipherSuite, +}; +use rustls_test::{ + ClientConfigExt, ErrorFromPeer, KeyType, ServerCheckCertResolve, + certificate_error_expecting_name, do_handshake_until_error, make_client_config, + make_pair_for_arc_configs, make_pair_for_configs, make_server_config, + make_server_config_with_client_verifier, make_server_config_with_mandatory_client_auth, + provider_with_one_suite, server_name, transfer, webpki_client_verifier_builder, +}; + +use super::{ALL_VERSIONS, provider, provider_is_aws_lc_rs}; + +#[test] +fn server_cert_resolve_with_sni() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let client_config = Arc::new(make_client_config(*kt, &provider)); + let mut server_config = make_server_config(*kt, &provider); + + server_config.cert_resolver = Arc::new(ServerCheckCertResolve { + expected_sni: Some(DnsName::try_from("the.value.from.sni").unwrap()), + ..Default::default() + }); + + let mut client = client_config + .connect(server_name("the.value.from.sni")) + .build() + .unwrap(); + let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); + + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err.err(), + Some(ErrorFromPeer::Server(Error::NoSuitableCertificate)) + ); + } +} + +#[test] +fn server_cert_resolve_with_alpn() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let mut client_config = make_client_config(*kt, &provider); + client_config.alpn_protocols = vec![ + ApplicationProtocol::from(b"foo"), + ApplicationProtocol::from(b"bar"), + ]; + + let mut server_config = make_server_config(*kt, &provider); + server_config.cert_resolver = Arc::new(ServerCheckCertResolve { + expected_alpn: Some(vec![b"foo".to_vec(), b"bar".to_vec()]), + ..Default::default() + }); + + let mut client = Arc::new(client_config) + .connect(server_name("sni-value")) + .build() + .unwrap(); + + let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err.err(), + Some(ErrorFromPeer::Server(Error::NoSuitableCertificate)) + ); + } +} + +#[test] +fn server_cert_resolve_with_named_groups() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let client_config = make_client_config(*kt, &provider); + + let mut server_config = make_server_config(*kt, &provider); + server_config.cert_resolver = Arc::new(ServerCheckCertResolve { + expected_named_groups: Some( + provider + .kx_groups + .iter() + .map(|kx| kx.name()) + .collect(), + ), + ..Default::default() + }); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err.err(), + Some(ErrorFromPeer::Server(Error::NoSuitableCertificate)) + ); + } +} + +#[test] +fn client_trims_terminating_dot() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let client_config = Arc::new(make_client_config(*kt, &provider)); + let mut server_config = make_server_config(*kt, &provider); + + server_config.cert_resolver = Arc::new(ServerCheckCertResolve { + expected_sni: Some(DnsName::try_from("some-host.com").unwrap()), + ..Default::default() + }); + + let mut client = client_config + .connect(server_name("some-host.com.")) + .build() + .unwrap(); + let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); + + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err.err(), + Some(ErrorFromPeer::Server(Error::NoSuitableCertificate)) + ); + } +} + +fn check_sigalgs_reduced_by_ciphersuite( + kt: KeyType, + suite: CipherSuite, + expected_sigalgs: Vec, +) { + let client_config = ClientConfig::builder( + provider_with_one_suite(&provider::DEFAULT_PROVIDER, find_suite(suite)).into(), + ) + .finish(kt); + + let mut server_config = make_server_config(kt, &provider::DEFAULT_PROVIDER); + + server_config.cert_resolver = Arc::new(ServerCheckCertResolve { + expected_sigalgs: Some(expected_sigalgs), + expected_cipher_suites: Some(vec![suite, CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV]), + ..Default::default() + }); + + let mut client = Arc::new(client_config) + .connect(server_name("localhost")) + .build() + .unwrap(); + let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); + + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + Some(ErrorFromPeer::Server(Error::NoSuitableCertificate)), + err.err() + ); +} + +fn find_suite(suite: CipherSuite) -> SupportedCipherSuite { + if let Some(found) = provider::ALL_TLS12_CIPHER_SUITES + .iter() + .find(|cs| cs.common.suite == suite) + { + return SupportedCipherSuite::Tls12(found); + } + + if let Some(found) = provider::ALL_TLS13_CIPHER_SUITES + .iter() + .find(|cs| cs.common.suite == suite) + { + return SupportedCipherSuite::Tls13(found); + } + + panic!("find_suite given unsupported suite {suite:?}"); +} + +#[test] +fn server_cert_resolve_reduces_sigalgs_for_rsa_ciphersuite() { + check_sigalgs_reduced_by_ciphersuite( + KeyType::Rsa2048, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + vec![ + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA256, + ], + ); +} + +#[test] +fn server_cert_resolve_reduces_sigalgs_for_ecdsa_ciphersuite() { + check_sigalgs_reduced_by_ciphersuite( + KeyType::EcdsaP256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + if provider_is_aws_lc_rs() { + vec![ + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::ECDSA_NISTP521_SHA512, + SignatureScheme::ED25519, + ] + } else { + vec![ + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::ED25519, + ] + }, + ); +} + +#[test] +fn client_with_sni_disabled_does_not_send_sni() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let mut server_config = make_server_config(*kt, &provider); + server_config.cert_resolver = Arc::new(ServerCheckNoSni {}); + let server_config = Arc::new(server_config); + + for version_provider in ALL_VERSIONS { + let mut client_config = make_client_config(*kt, &version_provider); + client_config.enable_sni = false; + + let mut client = Arc::new(client_config) + .connect(server_name("value-not-sent")) + .build() + .unwrap(); + + let mut server = ServerConnection::new(server_config.clone()).unwrap(); + let err = do_handshake_until_error(&mut client, &mut server); + dbg!(&err); + assert_eq!( + err.err(), + Some(ErrorFromPeer::Server(Error::NoSuitableCertificate)) + ); + } + } +} + +#[derive(Debug)] +struct ServerCheckNoSni {} + +impl ServerCredentialResolver for ServerCheckNoSni { + fn resolve(&self, client_hello: &ClientHello<'_>) -> Result { + // We expect the client to not send SNI. + assert!(client_hello.server_name().is_none()); + Err(Error::NoSuitableCertificate) + } +} + +#[derive(Debug)] +struct ClientCheckCertResolve { + query_count: AtomicUsize, + expect_queries: usize, + expect_root_hint_subjects: Vec, + expect_sigschemes: Vec, +} + +impl ClientCheckCertResolve { + fn new( + expect_queries: usize, + expect_root_hint_subjects: Vec, + expect_sigschemes: Vec, + ) -> Self { + Self { + query_count: AtomicUsize::new(0), + expect_queries, + expect_root_hint_subjects, + expect_sigschemes, + } + } +} + +impl Drop for ClientCheckCertResolve { + fn drop(&mut self) { + if !std::thread::panicking() { + let count = self.query_count.load(Ordering::SeqCst); + assert_eq!(count, self.expect_queries); + } + } +} + +impl ClientCredentialResolver for ClientCheckCertResolve { + fn resolve(&self, request: &CredentialRequest<'_>) -> Option { + self.query_count + .fetch_add(1, Ordering::SeqCst); + + if request.signature_schemes().is_empty() { + panic!("no signature schemes shared by server"); + } + + assert_eq!(request.signature_schemes(), self.expect_sigschemes); + assert_eq!( + request.root_hint_subjects(), + &self.expect_root_hint_subjects + ); + + None + } + + fn supported_certificate_types(&self) -> &'static [CertificateType] { + &[CertificateType::X509] + } + + fn hash_config(&self, _: &mut dyn Hasher) {} +} + +fn test_client_cert_resolve( + key_type: KeyType, + server_config: Arc, + expected_root_hint_subjects: Vec, +) { + for (version, version_provider) in [ + (ProtocolVersion::TLSv1_3, &provider::DEFAULT_TLS13_PROVIDER), + (ProtocolVersion::TLSv1_2, &provider::DEFAULT_TLS12_PROVIDER), + ] { + println!("{version:?} {key_type:?}:"); + + let client_config = ClientConfig::builder(version_provider.clone().into()) + .add_root_certs(key_type) + .with_client_credential_resolver(Arc::new(ClientCheckCertResolve::new( + 1, + expected_root_hint_subjects.clone(), + default_signature_schemes(version), + ))) + .unwrap(); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + assert_eq!( + do_handshake_until_error(&mut client, &mut server), + Err(ErrorFromPeer::Server(Error::PeerMisbehaved( + PeerMisbehaved::NoCertificatesPresented + ))) + ); + } +} + +fn default_signature_schemes(version: ProtocolVersion) -> Vec { + let mut v = vec![]; + + v.extend_from_slice(&[ + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::ED25519, + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA256, + ]); + + if provider_is_aws_lc_rs() { + v.insert(2, SignatureScheme::ECDSA_NISTP521_SHA512); + } + + if version == ProtocolVersion::TLSv1_2 { + v.extend_from_slice(&[ + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA256, + ]); + } + + v +} + +#[test] +fn client_cert_resolve_default() { + // Test that in the default configuration that a client cert resolver gets the expected + // CA subject hints, and supported signature algorithms. + let provider = provider::DEFAULT_PROVIDER; + for key_type in KeyType::all_for_provider(&provider) { + let server_config = Arc::new(make_server_config_with_mandatory_client_auth( + *key_type, &provider, + )); + + // In a default configuration we expect that the verifier's trust anchors are used + // for the hint subjects. + let expected_root_hint_subjects = vec![key_type.ca_distinguished_name()]; + + test_client_cert_resolve(*key_type, server_config, expected_root_hint_subjects); + } +} + +#[test] +fn client_cert_resolve_server_no_hints() { + // Test that a server can provide no hints and the client cert resolver gets the expected + // arguments. + let provider = provider::DEFAULT_PROVIDER; + for key_type in KeyType::all_for_provider(&provider) { + // Build a verifier with no hint subjects. + let verifier = webpki_client_verifier_builder(key_type.client_root_store(), &provider) + .clear_root_hint_subjects(); + let server_config = make_server_config_with_client_verifier(*key_type, verifier, &provider); + let expected_root_hint_subjects = Vec::default(); // no hints expected. + test_client_cert_resolve(*key_type, server_config.into(), expected_root_hint_subjects); + } +} + +#[test] +fn client_cert_resolve_server_added_hint() { + // Test that a server can add an extra subject above/beyond those found in its trust store + // and the client cert resolver gets the expected arguments. + let provider = provider::DEFAULT_PROVIDER; + let extra_name = DistinguishedName::from( + b"0\x1a1\x180\x16\x06\x03U\x04\x03\x0c\x0fponyland IDK CA".to_vec(), + ); + for key_type in KeyType::all_for_provider(&provider) { + let expected_hint_subjects = vec![key_type.ca_distinguished_name(), extra_name.clone()]; + // Create a verifier that adds the extra_name as a hint subject in addition to the ones + // from the root cert store. + let verifier = webpki_client_verifier_builder(key_type.client_root_store(), &provider) + .add_root_hint_subjects([extra_name.clone()]); + let server_config = make_server_config_with_client_verifier(*key_type, verifier, &provider); + test_client_cert_resolve(*key_type, server_config.into(), expected_hint_subjects); + } +} + +#[test] +fn server_exposes_offered_sni_even_if_resolver_fails() { + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_PROVIDER; + let resolver = ServerNameResolver::new(); + + let mut server_config = make_server_config(kt, &provider); + server_config.cert_resolver = Arc::new(resolver); + let server_config = Arc::new(server_config); + + for version_provider in ALL_VERSIONS { + let client_config = Arc::new(make_client_config(kt, &version_provider)); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); + let mut client = client_config + .connect(server_name("thisdoesNOTexist.com")) + .build() + .unwrap(); + + assert_eq!(None, server.server_name()); + transfer(&mut client, &mut server); + assert_eq!( + server.process_new_packets(), + Err(Error::NoSuitableCertificate) + ); + assert_eq!( + Some(&DnsName::try_from("thisdoesnotexist.com").unwrap()), + server.server_name() + ); + } +} + +#[test] +fn sni_resolver_works() { + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_PROVIDER; + let mut resolver = ServerNameResolver::new(); + let signing_key = kt.load_key(&provider); + resolver + .add( + DnsName::try_from("localhost").unwrap(), + Credentials::new(kt.identity(), signing_key).expect("keys match"), + ) + .unwrap(); + + let mut server_config = make_server_config(kt, &provider); + server_config.cert_resolver = Arc::new(resolver); + let server_config = Arc::new(server_config); + + let mut server1 = ServerConnection::new(server_config.clone()).unwrap(); + let mut client1 = Arc::new(make_client_config(kt, &provider)) + .connect(server_name("localhost")) + .build() + .unwrap(); + let err = do_handshake_until_error(&mut client1, &mut server1); + assert_eq!(err, Ok(())); + + let mut server2 = ServerConnection::new(server_config).unwrap(); + let mut client2 = Arc::new(make_client_config(kt, &provider)) + .connect(server_name("notlocalhost")) + .build() + .unwrap(); + let err = do_handshake_until_error(&mut client2, &mut server2); + assert_eq!( + err, + Err(ErrorFromPeer::Server(Error::NoSuitableCertificate)) + ); +} + +#[test] +fn sni_resolver_rejects_wrong_names() { + let kt = KeyType::Rsa2048; + let mut resolver = ServerNameResolver::new(); + + assert_eq!( + Ok(()), + resolver.add( + DnsName::try_from("localhost").unwrap(), + Credentials::new(kt.identity(), kt.load_key(&provider::DEFAULT_PROVIDER)) + .expect("keys match") + ) + ); + assert_eq!( + Err(Error::InvalidCertificate(certificate_error_expecting_name( + "not-localhost" + ))), + resolver.add( + DnsName::try_from("not-localhost").unwrap(), + Credentials::new(kt.identity(), kt.load_key(&provider::DEFAULT_PROVIDER)) + .expect("keys match") + ) + ); +} + +#[test] +fn sni_resolver_lower_cases_configured_names() { + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_PROVIDER; + let mut resolver = ServerNameResolver::new(); + let signing_key = kt.load_key(&provider); + + assert_eq!( + Ok(()), + resolver.add( + DnsName::try_from("LOCALHOST").unwrap(), + Credentials::new(kt.identity(), signing_key).expect("keys match") + ) + ); + + let mut server_config = make_server_config(kt, &provider); + server_config.cert_resolver = Arc::new(resolver); + let server_config = Arc::new(server_config); + + let mut server1 = ServerConnection::new(server_config).unwrap(); + let mut client1 = Arc::new(make_client_config(kt, &provider)) + .connect(server_name("localhost")) + .build() + .unwrap(); + let err = do_handshake_until_error(&mut client1, &mut server1); + assert_eq!(err, Ok(())); +} + +#[test] +fn sni_resolver_lower_cases_queried_names() { + // actually, the handshake parser does this, but the effect is the same. + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_PROVIDER; + let mut resolver = ServerNameResolver::new(); + let signing_key = kt.load_key(&provider); + + assert_eq!( + Ok(()), + resolver.add( + DnsName::try_from("localhost").unwrap(), + Credentials::new(kt.identity(), signing_key).expect("keys match") + ) + ); + + let mut server_config = make_server_config(kt, &provider); + server_config.cert_resolver = Arc::new(resolver); + let server_config = Arc::new(server_config); + + let mut server1 = ServerConnection::new(server_config).unwrap(); + let mut client1 = Arc::new(make_client_config(kt, &provider)) + .connect(server_name("LOCALHOST")) + .build() + .unwrap(); + let err = do_handshake_until_error(&mut client1, &mut server1); + assert_eq!(err, Ok(())); +} + +#[test] +fn sni_resolver_rejects_bad_certs() { + let kt = KeyType::Rsa2048; + let mut resolver = ServerNameResolver::new(); + + let bad_chain = + Arc::from(Identity::from_cert_chain(vec![CertificateDer::from(vec![0xa0])]).unwrap()); + assert_eq!( + Err(Error::InvalidCertificate(CertificateError::BadEncoding)), + resolver.add( + DnsName::try_from("localhost").unwrap(), + Credentials::new_unchecked(bad_chain, kt.load_key(&provider::DEFAULT_PROVIDER)) + ) + ); +} diff --git a/rustls-test/tests/api/resume.rs b/rustls-test/tests/api/resume.rs new file mode 100644 index 00000000000..6c436186de1 --- /dev/null +++ b/rustls-test/tests/api/resume.rs @@ -0,0 +1,767 @@ +//! Assorted public API tests. + +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] + +use core::sync::atomic::{AtomicUsize, Ordering}; +use std::fmt; +use std::io::{Read, Write}; +use std::sync::Arc; + +use pki_types::FipsStatus; +use rustls::client::Resumption; +use rustls::crypto::kx::NamedGroup; +use rustls::crypto::{CertificateIdentity, Identity}; +use rustls::enums::ProtocolVersion; +use rustls::error::{ApiMisuse, Error, PeerMisbehaved}; +use rustls::server::ServerSessionKey; +use rustls::{ClientConfig, Connection, HandshakeKind, ServerConfig, ServerConnection}; +use rustls_test::{ + ClientConfigExt, ClientStorage, ClientStorageOp, ErrorFromPeer, KeyType, ServerConfigExt, + do_handshake, do_handshake_until_error, make_client_config, make_client_config_with_auth, + make_pair, make_pair_for_arc_configs, make_pair_for_configs, make_server_config, transfer, + webpki_server_verifier_builder, +}; + +use super::{ALL_VERSIONS, provider}; + +#[test] +fn client_only_attempts_resumption_with_compatible_security() { + let provider = provider::DEFAULT_PROVIDER; + let kt = KeyType::Rsa2048; + + let server_config = make_server_config(kt, &provider); + for version_provider in ALL_VERSIONS { + let base_client_config = make_client_config(kt, &version_provider); + let (mut client, mut server) = + make_pair_for_configs(base_client_config.clone(), server_config.clone()); + do_handshake(&mut client, &mut server); + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Full)); + + // base case + let (mut client, mut server) = + make_pair_for_configs(base_client_config.clone(), server_config.clone()); + do_handshake(&mut client, &mut server); + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Resumed)); + + // allowed case, using `clone` + let client_config = ClientConfig::clone(&base_client_config); + let (mut client, mut server) = + make_pair_for_configs(client_config.clone(), server_config.clone()); + do_handshake(&mut client, &mut server); + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Resumed)); + + // disallowed case: unmatching `client_auth_cert_resolver` + let client_config = ClientConfig::builder(Arc::new(version_provider.clone())) + .add_root_certs(kt) + .with_client_credential_resolver( + make_client_config_with_auth(KeyType::EcdsaP256, &version_provider) + .resolver() + .clone(), + ) + .unwrap(); + + let (mut client, mut server) = + make_pair_for_configs(client_config.clone(), server_config.clone()); + do_handshake(&mut client, &mut server); + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Full)); + + // disallowed case: unmatching `verifier` + let mut client_config = ClientConfig::builder(Arc::new(version_provider.clone())) + .dangerous() + .with_custom_certificate_verifier(Arc::new( + webpki_server_verifier_builder(kt.client_root_store(), &version_provider) + .allow_unknown_revocation_status() + .build() + .unwrap(), + )) + .with_client_credential_resolver(client_config.resolver().clone()) + .unwrap(); + client_config.resumption = base_client_config.resumption.clone(); + + let (mut client, mut server) = + make_pair_for_configs(client_config.clone(), server_config.clone()); + do_handshake(&mut client, &mut server); + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Full)); + } +} + +#[test] +fn resumption_combinations() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let server_config = make_server_config(*kt, &provider); + for (version, version_provider) in [ + (ProtocolVersion::TLSv1_2, provider::DEFAULT_TLS12_PROVIDER), + (ProtocolVersion::TLSv1_3, provider::DEFAULT_TLS13_PROVIDER), + ] { + let resumption_data = format!("resumption data {kt:?} {version:?}"); + let client_config = make_client_config(*kt, &version_provider); + let (mut client, mut server) = + make_pair_for_configs(client_config.clone(), server_config.clone()); + server + .set_resumption_data(resumption_data.as_bytes()) + .unwrap(); + do_handshake(&mut client, &mut server); + + let expected_kx = expected_kx_for_version(version); + + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Full)); + assert_eq!(server.handshake_kind(), Some(HandshakeKind::Full)); + assert_eq!( + client + .negotiated_key_exchange_group() + .unwrap() + .name(), + expected_kx + ); + assert_eq!( + server + .negotiated_key_exchange_group() + .unwrap() + .name(), + expected_kx + ); + + let (mut client, mut server) = + make_pair_for_configs(client_config.clone(), server_config.clone()); + do_handshake(&mut client, &mut server); + + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Resumed)); + assert_eq!(server.handshake_kind(), Some(HandshakeKind::Resumed)); + assert_eq!( + server.received_resumption_data(), + Some(resumption_data.as_bytes()) + ); + if version == ProtocolVersion::TLSv1_2 { + assert!( + client + .negotiated_key_exchange_group() + .is_none() + ); + assert!( + server + .negotiated_key_exchange_group() + .is_none() + ); + } else { + assert_eq!( + client + .negotiated_key_exchange_group() + .unwrap() + .name(), + expected_kx + ); + assert_eq!( + server + .negotiated_key_exchange_group() + .unwrap() + .name(), + expected_kx + ); + } + } + } +} + +fn expected_kx_for_version(version: ProtocolVersion) -> NamedGroup { + let is_fips = matches!( + super::provider_is_fips(), + FipsStatus::Pending | FipsStatus::Certified { .. } + ); + match (version, super::provider_is_aws_lc_rs(), is_fips) { + (ProtocolVersion::TLSv1_3, true, _) => NamedGroup::X25519MLKEM768, + (_, _, true) => NamedGroup::secp256r1, + (_, _, _) => NamedGroup::X25519, + } +} + +/// https://github.com/rustls/rustls/issues/797 +#[test] +fn test_client_tls12_no_resume_after_server_downgrade() { + let provider = provider::DEFAULT_PROVIDER; + let mut client_config = make_client_config(KeyType::Ed25519, &provider); + let client_storage = Arc::new(ClientStorage::new()); + client_config.resumption = Resumption::store(client_storage.clone()); + let client_config = Arc::new(client_config); + + let server_config_1 = Arc::new( + ServerConfig::builder(provider::DEFAULT_TLS13_PROVIDER.into()).finish(KeyType::Ed25519), + ); + + let mut server_config_2 = + ServerConfig::builder(provider::DEFAULT_TLS12_PROVIDER.into()).finish(KeyType::Ed25519); + server_config_2.session_storage = Arc::new(rustls::server::NoServerSessionStorage {}); + + dbg!("handshake 1"); + let mut client_1 = client_config + .connect("localhost".try_into().unwrap()) + .build() + .unwrap(); + let mut server_1 = ServerConnection::new(server_config_1).unwrap(); + do_handshake(&mut client_1, &mut server_1); + + assert_eq!(client_storage.ops().len(), 7); + println!("hs1 storage ops: {:#?}", client_storage.ops()); + assert!(matches!( + client_storage.ops()[3], + ClientStorageOp::SetKxHint(_, _) + )); + assert!(matches!( + client_storage.ops()[4], + ClientStorageOp::RemoveTls12Session(_) + )); + assert!(matches!( + client_storage.ops()[5], + ClientStorageOp::InsertTls13Ticket(_) + )); + + dbg!("handshake 2"); + let mut client_2 = client_config + .connect("localhost".try_into().unwrap()) + .build() + .unwrap(); + let mut server_2 = ServerConnection::new(Arc::new(server_config_2)).unwrap(); + do_handshake(&mut client_2, &mut server_2); + println!("hs2 storage ops: {:#?}", client_storage.ops()); + assert_eq!(client_storage.ops().len(), 9); + + // attempt consumes a TLS1.3 ticket + assert!(matches!( + client_storage.ops()[7], + ClientStorageOp::TakeTls13Ticket(_, true) + )); + + // but ends up with TLS1.2 + assert_eq!(client_2.protocol_version(), Some(ProtocolVersion::TLSv1_2)); +} + +#[test] +fn test_tls13_client_resumption_does_not_reuse_tickets() { + let shared_storage = Arc::new(ClientStorage::new()); + let provider = provider::DEFAULT_PROVIDER; + + let mut client_config = make_client_config(KeyType::Rsa2048, &provider); + client_config.resumption = Resumption::store(shared_storage.clone()); + let client_config = Arc::new(client_config); + + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); + server_config.send_tls13_tickets = 5; + let server_config = Arc::new(server_config); + + // first handshake: client obtains 5 tickets from server. + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake_until_error(&mut client, &mut server).unwrap(); + + let ops = shared_storage.ops_and_reset(); + println!("storage {ops:#?}"); + assert_eq!(ops.len(), 10); + assert!(matches!(ops[5], ClientStorageOp::InsertTls13Ticket(_))); + assert!(matches!(ops[6], ClientStorageOp::InsertTls13Ticket(_))); + assert!(matches!(ops[7], ClientStorageOp::InsertTls13Ticket(_))); + assert!(matches!(ops[8], ClientStorageOp::InsertTls13Ticket(_))); + assert!(matches!(ops[9], ClientStorageOp::InsertTls13Ticket(_))); + + // 5 subsequent handshakes: all are resumptions + + // Note: we don't do complete the handshakes, because that means + // we get five additional tickets per connection which is unhelpful + // in this test. It also acts to record a "Happy Eyeballs"-type use + // case, where a client speculatively makes many connection attempts + // in parallel without knowledge of which will work due to underlying + // connectivity uncertainty. + for _ in 0..5 { + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + let ops = shared_storage.ops_and_reset(); + assert!(matches!(ops[0], ClientStorageOp::TakeTls13Ticket(_, true))); + } + + // 6th subsequent handshake: cannot be resumed; we ran out of tickets + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + let ops = shared_storage.ops_and_reset(); + println!("last {ops:?}"); + assert!(matches!(ops[0], ClientStorageOp::TakeTls13Ticket(_, false))); +} + +#[test] +fn tls13_stateful_resumption() { + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_TLS13_PROVIDER; + let client_config = make_client_config(kt, &provider); + let client_config = Arc::new(client_config); + + let mut server_config = make_server_config(kt, &provider); + let storage = Arc::new(ServerStorage::new()); + server_config.session_storage = storage.clone(); + let server_config = Arc::new(server_config); + + // full handshake + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + let (full_c2s, full_s2c) = do_handshake(&mut client, &mut server); + assert_eq!(client.tls13_tickets_received(), 2); + assert_eq!(storage.puts(), 2); + assert_eq!(storage.gets(), 0); + assert_eq!(storage.takes(), 0); + assert_eq!( + client + .peer_identity() + .map(|identity| match identity { + Identity::X509(CertificateIdentity { intermediates, .. }) => intermediates.len(), + _ => 0, + }), + Some(2) + ); + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Full)); + assert_eq!(server.handshake_kind(), Some(HandshakeKind::Full)); + + // resumed + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + let (resume_c2s, resume_s2c) = do_handshake(&mut client, &mut server); + assert!(resume_c2s > full_c2s); + assert!(resume_s2c < full_s2c); + assert_eq!(storage.puts(), 4); + assert_eq!(storage.gets(), 0); + assert_eq!(storage.takes(), 1); + assert_eq!( + client + .peer_identity() + .map(|identity| match identity { + Identity::X509(CertificateIdentity { intermediates, .. }) => intermediates.len(), + _ => 0, + }), + Some(2) + ); + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Resumed)); + assert_eq!(server.handshake_kind(), Some(HandshakeKind::Resumed)); + + // resumed again + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + let (resume2_c2s, resume2_s2c) = do_handshake(&mut client, &mut server); + assert_eq!(resume_s2c, resume2_s2c); + assert_eq!(resume_c2s, resume2_c2s); + assert_eq!(storage.puts(), 6); + assert_eq!(storage.gets(), 0); + assert_eq!(storage.takes(), 2); + assert_eq!( + client + .peer_identity() + .map(|identity| match identity { + Identity::X509(CertificateIdentity { intermediates, .. }) => intermediates.len(), + _ => 0, + }), + Some(2) + ); + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Resumed)); + assert_eq!(server.handshake_kind(), Some(HandshakeKind::Resumed)); +} + +#[test] +fn tls13_stateless_resumption() { + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_TLS13_PROVIDER; + let client_config = make_client_config(kt, &provider); + let client_config = Arc::new(client_config); + + let mut server_config = make_server_config(kt, &provider); + server_config.ticketer = Some( + provider + .ticketer_factory + .ticketer() + .unwrap(), + ); + let storage = Arc::new(ServerStorage::new()); + server_config.session_storage = storage.clone(); + let server_config = Arc::new(server_config); + + // full handshake + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + let (full_c2s, full_s2c) = do_handshake(&mut client, &mut server); + assert_eq!(storage.puts(), 0); + assert_eq!(storage.gets(), 0); + assert_eq!(storage.takes(), 0); + assert_eq!( + client + .peer_identity() + .map(|identity| match identity { + Identity::X509(CertificateIdentity { intermediates, .. }) => intermediates.len(), + _ => 0, + }), + Some(2) + ); + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Full)); + assert_eq!(server.handshake_kind(), Some(HandshakeKind::Full)); + + // resumed + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + let (resume_c2s, resume_s2c) = do_handshake(&mut client, &mut server); + assert!(resume_c2s > full_c2s); + assert!(resume_s2c < full_s2c); + assert_eq!(storage.puts(), 0); + assert_eq!(storage.gets(), 0); + assert_eq!(storage.takes(), 0); + assert_eq!( + client + .peer_identity() + .map(|identity| match identity { + Identity::X509(CertificateIdentity { intermediates, .. }) => intermediates.len(), + _ => 0, + }), + Some(2) + ); + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Resumed)); + assert_eq!(server.handshake_kind(), Some(HandshakeKind::Resumed)); + + // resumed again + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + let (resume2_c2s, resume2_s2c) = do_handshake(&mut client, &mut server); + assert_eq!(resume_s2c, resume2_s2c); + assert_eq!(resume_c2s, resume2_c2s); + assert_eq!(storage.puts(), 0); + assert_eq!(storage.gets(), 0); + assert_eq!(storage.takes(), 0); + assert_eq!( + client + .peer_identity() + .map(|identity| match identity { + Identity::X509(CertificateIdentity { intermediates, .. }) => intermediates.len(), + _ => 0, + }), + Some(2) + ); + assert_eq!(client.handshake_kind(), Some(HandshakeKind::Resumed)); + assert_eq!(server.handshake_kind(), Some(HandshakeKind::Resumed)); +} + +#[test] +fn early_data_not_available() { + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); + assert!(client.early_data().is_none()); +} + +fn early_data_configs() -> (Arc, Arc) { + let kt = KeyType::Rsa2048; + let provider = provider::DEFAULT_PROVIDER; + let mut client_config = make_client_config(kt, &provider); + client_config.enable_early_data = true; + client_config.resumption = Resumption::store(Arc::new(ClientStorage::new())); + + let mut server_config = make_server_config(kt, &provider); + server_config.max_early_data_size = 1234; + (Arc::new(client_config), Arc::new(server_config)) +} + +#[test] +fn early_data_is_available_on_resumption() { + let (client_config, server_config) = early_data_configs(); + + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + assert!(client.early_data().is_some()); + assert_eq!( + client + .early_data() + .unwrap() + .bytes_left(), + 1234 + ); + client + .early_data() + .unwrap() + .flush() + .unwrap(); + assert_eq!( + client + .early_data() + .unwrap() + .write(b"") + .unwrap(), + 0 + ); + assert_eq!( + client + .early_data() + .unwrap() + .write(b"hello") + .unwrap(), + 5 + ); + let client_early_exporter = client + .early_data() + .unwrap() + .exporter() + .unwrap(); + assert_eq!( + client + .early_data() + .unwrap() + .exporter() + .err(), + Some(Error::ApiMisuse(ApiMisuse::ExporterAlreadyUsed)), + ); + do_handshake(&mut client, &mut server); + + let mut received_early_data = [0u8; 5]; + assert_eq!( + server + .early_data() + .expect("early_data didn't happen") + .read(&mut received_early_data) + .expect("early_data failed unexpectedly"), + 5 + ); + assert_eq!(&received_early_data[..], b"hello"); + let server_early_exporter = server + .early_data() + .unwrap() + .exporter() + .unwrap(); + assert_eq!( + server + .early_data() + .unwrap() + .exporter() + .err(), + Some(Error::ApiMisuse(ApiMisuse::ExporterAlreadyUsed)), + ); + + // check exporters agree + let client_secret = client_early_exporter + .derive(b"label", Some(b"context"), [0u8; 32]) + .unwrap(); + let server_secret = server_early_exporter + .derive(b"label", Some(b"context"), [0u8; 32]) + .unwrap(); + assert_eq!(client_secret, server_secret); +} + +#[test] +fn early_data_not_available_on_server_before_client_hello() { + let mut server = ServerConnection::new(Arc::new(make_server_config( + KeyType::Rsa2048, + &provider::DEFAULT_PROVIDER, + ))) + .unwrap(); + assert!(server.early_data().is_none()); +} + +#[test] +fn early_data_is_limited_on_client() { + let (client_config, server_config) = early_data_configs(); + + // warm up + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + assert!(client.early_data().is_some()); + assert_eq!( + client + .early_data() + .unwrap() + .bytes_left(), + 1234 + ); + client + .early_data() + .unwrap() + .flush() + .unwrap(); + assert_eq!( + client + .early_data() + .unwrap() + .write(&[0xaa; 1234 + 1]) + .unwrap(), + 1234 + ); + do_handshake(&mut client, &mut server); + + let mut received_early_data = [0u8; 1234]; + assert_eq!( + server + .early_data() + .expect("early_data didn't happen") + .read(&mut received_early_data) + .expect("early_data failed unexpectedly"), + 1234 + ); + assert_eq!(&received_early_data[..], [0xaa; 1234]); +} + +fn early_data_configs_allowing_client_to_send_excess_data() -> (Arc, Arc) +{ + let (client_config, server_config) = early_data_configs(); + + // adjust client session storage to corrupt received max_early_data_size + let mut client_config = Arc::into_inner(client_config).unwrap(); + let mut storage = ClientStorage::new(); + storage.alter_max_early_data_size(1234, 2024); + client_config.resumption = Resumption::store(Arc::new(storage)); + let client_config = Arc::new(client_config); + + // warm up + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + (client_config, server_config) +} + +#[test] +fn server_detects_excess_early_data() { + let (client_config, server_config) = early_data_configs_allowing_client_to_send_excess_data(); + + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + assert!(client.early_data().is_some()); + assert_eq!( + client + .early_data() + .unwrap() + .bytes_left(), + 2024 + ); + client + .early_data() + .unwrap() + .flush() + .unwrap(); + assert_eq!( + client + .early_data() + .unwrap() + .write(&[0xaa; 2024]) + .unwrap(), + 2024 + ); + assert_eq!( + do_handshake_until_error(&mut client, &mut server), + Err(ErrorFromPeer::Server(Error::PeerMisbehaved( + PeerMisbehaved::TooMuchEarlyDataReceived + ))), + ); +} + +// regression test for https://github.com/rustls/rustls/issues/2096 +#[test] +fn server_detects_excess_streamed_early_data() { + let (client_config, server_config) = early_data_configs_allowing_client_to_send_excess_data(); + + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + assert!(client.early_data().is_some()); + assert_eq!( + client + .early_data() + .unwrap() + .bytes_left(), + 2024 + ); + client + .early_data() + .unwrap() + .flush() + .unwrap(); + assert_eq!( + client + .early_data() + .unwrap() + .write(&[0xaa; 1024]) + .unwrap(), + 1024 + ); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + let mut received_early_data = [0u8; 1024]; + assert_eq!( + server + .early_data() + .expect("early_data didn't happen") + .read(&mut received_early_data) + .expect("early_data failed unexpectedly"), + 1024 + ); + assert_eq!(&received_early_data[..], [0xaa; 1024]); + + assert_eq!( + client + .early_data() + .unwrap() + .write(&[0xbb; 1000]) + .unwrap(), + 1000 + ); + transfer(&mut client, &mut server); + assert_eq!( + server.process_new_packets(), + Err(Error::PeerMisbehaved( + PeerMisbehaved::TooMuchEarlyDataReceived + )) + ); +} + +struct ServerStorage { + storage: Arc, + put_count: AtomicUsize, + get_count: AtomicUsize, + take_count: AtomicUsize, +} + +impl ServerStorage { + fn new() -> Self { + Self { + storage: rustls::server::ServerSessionMemoryCache::new(1024), + put_count: AtomicUsize::new(0), + get_count: AtomicUsize::new(0), + take_count: AtomicUsize::new(0), + } + } + + fn puts(&self) -> usize { + self.put_count.load(Ordering::SeqCst) + } + fn gets(&self) -> usize { + self.get_count.load(Ordering::SeqCst) + } + fn takes(&self) -> usize { + self.take_count.load(Ordering::SeqCst) + } +} + +impl fmt::Debug for ServerStorage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "(put: {:?}, get: {:?}, take: {:?})", + self.put_count, self.get_count, self.take_count + ) + } +} + +impl rustls::server::StoresServerSessions for ServerStorage { + fn put(&self, key: ServerSessionKey<'_>, value: Vec) -> bool { + self.put_count + .fetch_add(1, Ordering::SeqCst); + self.storage.put(key, value) + } + + fn get(&self, key: ServerSessionKey<'_>) -> Option> { + self.get_count + .fetch_add(1, Ordering::SeqCst); + self.storage.get(key) + } + + fn take(&self, key: ServerSessionKey<'_>) -> Option> { + self.take_count + .fetch_add(1, Ordering::SeqCst); + self.storage.take(key) + } + + fn can_cache(&self) -> bool { + true + } +} diff --git a/rustls-test/tests/api/server_cert_verifier.rs b/rustls-test/tests/api/server_cert_verifier.rs new file mode 100644 index 00000000000..ea0009189ee --- /dev/null +++ b/rustls-test/tests/api/server_cert_verifier.rs @@ -0,0 +1,727 @@ +//! Tests for configuring and using a [`ServerVerifier`] for a client. + +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] + +use core::hash::Hasher; +use std::sync::Arc; + +use pki_types::UnixTime; +use rustls::client::danger::{ + HandshakeSignatureValid, PeerVerified, ServerIdentity, ServerVerifier, + SignatureVerificationInput, +}; +use rustls::client::{WebPkiServerVerifier, verify_identity_signed_by_trust_anchor}; +use rustls::crypto::{Credentials, Identity, SelectedCredential, SignatureScheme}; +use rustls::enums::CertificateType; +use rustls::error::{ + AlertDescription, CertificateError, Error, ExtendedKeyPurpose, InvalidMessage, PeerIncompatible, +}; +use rustls::server::{ClientHello, ParsedCertificate, ServerCredentialResolver}; +use rustls::{ClientConfig, DistinguishedName, RootCertStore, ServerConfig, ServerConnection}; +use rustls_test::{ + ErrorFromPeer, KeyType, MockServerVerifier, certificate_error_expecting_name, do_handshake, + do_handshake_until_both_error, do_handshake_until_error, make_client_config, + make_client_config_with_verifier, make_pair_for_arc_configs, make_pair_for_configs, + make_server_config, server_name, webpki_server_verifier_builder, +}; +use webpki::anchor_from_trusted_cert; +use x509_parser::prelude::FromDer; +use x509_parser::x509::X509Name; + +use super::{ALL_VERSIONS, provider}; + +#[test] +fn client_can_override_certificate_verification() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider).iter() { + let verifier = Arc::new(MockServerVerifier::accepts_anything()); + + let server_config = Arc::new(make_server_config(*kt, &provider)); + + for version_provider in ALL_VERSIONS { + let mut client_config = make_client_config(*kt, &version_provider); + client_config + .dangerous() + .set_certificate_verifier(verifier.clone()); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + } + } +} + +#[test] +fn client_can_override_certificate_verification_and_reject_certificate() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider).iter() { + let verifier = Arc::new(MockServerVerifier::rejects_certificate( + CertificateError::ApplicationVerificationFailure.into(), + )); + + let server_config = Arc::new(make_server_config(*kt, &provider)); + + for version_provider in ALL_VERSIONS { + let mut client_config = make_client_config(*kt, &version_provider); + client_config + .dangerous() + .set_certificate_verifier(verifier.clone()); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + let errs = do_handshake_until_both_error(&mut client, &mut server); + assert_eq!( + errs, + Err(vec![ + ErrorFromPeer::Client(CertificateError::ApplicationVerificationFailure.into()), + ErrorFromPeer::Server(Error::AlertReceived(AlertDescription::AccessDenied)), + ]), + ); + } + } +} + +#[test] +fn client_can_override_certificate_verification_and_reject_tls12_signatures() { + let provider = provider::DEFAULT_TLS12_PROVIDER; + for kt in KeyType::all_for_provider(&provider).iter() { + let mut client_config = make_client_config(*kt, &provider); + let verifier = Arc::new(MockServerVerifier::rejects_tls12_signatures( + CertificateError::ApplicationVerificationFailure.into(), + )); + + client_config + .dangerous() + .set_certificate_verifier(verifier); + + let server_config = Arc::new(make_server_config(*kt, &provider)); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + let errs = do_handshake_until_both_error(&mut client, &mut server); + assert_eq!( + errs, + Err(vec![ + ErrorFromPeer::Client(CertificateError::ApplicationVerificationFailure.into()), + ErrorFromPeer::Server(Error::AlertReceived(AlertDescription::AccessDenied)), + ]), + ); + } +} + +#[test] +fn client_can_override_certificate_verification_and_reject_tls13_signatures() { + let provider = provider::DEFAULT_TLS13_PROVIDER; + for kt in KeyType::all_for_provider(&provider).iter() { + let mut client_config = make_client_config(*kt, &provider); + let verifier = Arc::new(MockServerVerifier::rejects_tls13_signatures( + CertificateError::ApplicationVerificationFailure.into(), + )); + + client_config + .dangerous() + .set_certificate_verifier(verifier); + + let server_config = Arc::new(make_server_config(*kt, &provider)); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + let errs = do_handshake_until_both_error(&mut client, &mut server); + assert_eq!( + errs, + Err(vec![ + ErrorFromPeer::Client(CertificateError::ApplicationVerificationFailure.into()), + ErrorFromPeer::Server(Error::AlertReceived(AlertDescription::AccessDenied)), + ]), + ); + } +} + +#[test] +fn client_can_override_certificate_verification_and_offer_no_signature_schemes() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider).iter() { + let verifier = Arc::new(MockServerVerifier::offers_no_signature_schemes()); + + let server_config = Arc::new(make_server_config(*kt, &provider)); + + for version_provider in ALL_VERSIONS { + let mut client_config = make_client_config(*kt, &version_provider); + client_config + .dangerous() + .set_certificate_verifier(verifier.clone()); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + let errs = do_handshake_until_both_error(&mut client, &mut server); + assert_eq!( + errs, + Err(vec![ + ErrorFromPeer::Server(Error::InvalidMessage( + InvalidMessage::NoSignatureSchemes + )), + ErrorFromPeer::Client(Error::AlertReceived(AlertDescription::DecodeError)), + ]) + ); + } + } +} + +#[test] +fn test_pinned_ocsp_response_given_to_custom_server_cert_verifier() { + let ocsp_response = b"hello-ocsp-world!"; + let kt = KeyType::EcdsaP256; + let provider = provider::DEFAULT_PROVIDER; + + for version_provider in ALL_VERSIONS { + let server_config = ServerConfig::builder(provider.clone().into()) + .with_no_client_auth() + .with_single_cert_with_ocsp(kt.identity(), kt.key(), Arc::from(&ocsp_response[..])) + .unwrap(); + + let client_config = ClientConfig::builder(version_provider.into()) + .dangerous() + .with_custom_certificate_verifier(Arc::new(MockServerVerifier::expects_ocsp_response( + ocsp_response, + ))) + .with_no_client_auth() + .unwrap(); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake(&mut client, &mut server); + } +} + +#[test] +fn client_can_request_certain_trusted_cas() { + let provider = provider::DEFAULT_PROVIDER; + // These keys have CAs with different names, which our test needs. + // They also share the same sigalgs, so the server won't pick one over the other based on sigalgs. + let key_types = [KeyType::Rsa2048, KeyType::Rsa3072, KeyType::Rsa4096]; + let cert_resolver = ResolvesCertChainByCaName( + key_types + .iter() + .map(|kt| { + ( + kt.ca_distinguished_name(), + kt.credentials_with_cert_chain(&provider) + .unwrap(), + ) + }) + .collect(), + ); + + let server_config = Arc::new( + ServerConfig::builder(provider.clone().into()) + .with_no_client_auth() + .with_server_credential_resolver(Arc::new(cert_resolver)) + .unwrap(), + ); + + let mut cas_unaware_error_count = 0; + + for key_type in key_types { + let mut root_store = RootCertStore::empty(); + root_store + .add(key_type.ca_cert()) + .unwrap(); + let server_verifier = Arc::new( + WebPkiServerVerifier::builder(Arc::new(root_store), &provider) + .build() + .unwrap(), + ); + + let cas_sending_server_verifier = Arc::new(ServerVerifierWithCasExt { + verifier: server_verifier.clone(), + ca_names: Arc::from(vec![key_type.ca_distinguished_name()]), + }); + + let cas_sending_client_config = ClientConfig::builder(provider.clone().into()) + .dangerous() + .with_custom_certificate_verifier(cas_sending_server_verifier) + .with_no_client_auth() + .unwrap(); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(cas_sending_client_config), &server_config); + do_handshake(&mut client, &mut server); + + let cas_unaware_client_config = ClientConfig::builder(provider.clone().into()) + .dangerous() + .with_custom_certificate_verifier(server_verifier) + .with_no_client_auth() + .unwrap(); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(cas_unaware_client_config), &server_config); + + cas_unaware_error_count += do_handshake_until_error(&mut client, &mut server) + .inspect_err(|e| { + assert!(matches!( + e, + ErrorFromPeer::Client(Error::InvalidCertificate( + CertificateError::UnknownIssuer + )) + )) + }) + .is_err() as usize; + + println!("key type {key_type:?} success!"); + } + + // For cas_unaware clients, all of them should fail except one that happens to + // have the cert the server sends + assert_eq!(cas_unaware_error_count, key_types.len() - 1); +} + +#[test] +fn client_checks_server_certificate_with_given_ip_address() { + fn check_server_name( + client_config: Arc, + server_config: Arc, + name: &'static str, + ) -> Result<(), ErrorFromPeer> { + let mut client = client_config + .connect(server_name(name)) + .build() + .unwrap(); + let mut server = ServerConnection::new(server_config).unwrap(); + do_handshake_until_error(&mut client, &mut server) + } + + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let server_config = Arc::new(make_server_config(*kt, &provider)); + + for version_provider in ALL_VERSIONS { + let client_config = Arc::new(make_client_config(*kt, &version_provider)); + + // positive ipv4 case + assert_eq!( + check_server_name(client_config.clone(), server_config.clone(), "198.51.100.1"), + Ok(()), + ); + + // negative ipv4 case + assert_eq!( + check_server_name(client_config.clone(), server_config.clone(), "198.51.100.2"), + Err(ErrorFromPeer::Client(Error::InvalidCertificate( + certificate_error_expecting_name("198.51.100.2") + ))) + ); + + // positive ipv6 case + assert_eq!( + check_server_name(client_config.clone(), server_config.clone(), "2001:db8::1"), + Ok(()), + ); + + // negative ipv6 case + assert_eq!( + check_server_name(client_config.clone(), server_config.clone(), "2001:db8::2"), + Err(ErrorFromPeer::Client(Error::InvalidCertificate( + certificate_error_expecting_name("2001:db8::2") + ))) + ); + } + } +} + +#[test] +fn client_checks_server_certificate_with_given_name() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let server_config = Arc::new(make_server_config(*kt, &provider)); + + for version_provider in ALL_VERSIONS { + let client_config = Arc::new(make_client_config(*kt, &version_provider)); + let mut client = client_config + .connect(server_name("not-the-right-hostname.com")) + .build() + .unwrap(); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); + + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err, + Err(ErrorFromPeer::Client(Error::InvalidCertificate( + certificate_error_expecting_name("not-the-right-hostname.com") + ))) + ); + } + } +} + +#[test] +fn client_check_server_certificate_ee_revoked() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let server_config = Arc::new(make_server_config(*kt, &provider)); + + // Setup a server verifier that will check the EE certificate's revocation status. + let crls = vec![kt.end_entity_crl()]; + let builder = webpki_server_verifier_builder(kt.client_root_store(), &provider) + .with_crls(crls) + .only_check_end_entity_revocation(); + + for version_provider in ALL_VERSIONS { + let client_config = + make_client_config_with_verifier(builder.clone(), &version_provider); + let mut client = Arc::new(client_config) + .connect(server_name("localhost")) + .build() + .unwrap(); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); + + // We expect the handshake to fail since the server's EE certificate is revoked. + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err, + Err(ErrorFromPeer::Client(Error::InvalidCertificate( + CertificateError::Revoked + ))) + ); + } + } +} + +#[test] +fn client_check_server_certificate_ee_unknown_revocation() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let server_config = Arc::new(make_server_config(*kt, &provider)); + + // Setup a server verifier builder that will check the EE certificate's revocation status, but not + // allow unknown revocation status (the default). We'll provide CRLs that are not relevant + // to the EE cert to ensure its status is unknown. + let unrelated_crls = vec![kt.intermediate_crl()]; + let forbid_unknown_verifier = + webpki_server_verifier_builder(kt.client_root_store(), &provider) + .with_crls(unrelated_crls.clone()) + .only_check_end_entity_revocation(); + + // Also set up a verifier builder that will allow unknown revocation status. + let allow_unknown_verifier = + webpki_server_verifier_builder(kt.client_root_store(), &provider) + .with_crls(unrelated_crls) + .only_check_end_entity_revocation() + .allow_unknown_revocation_status(); + + for version_provider in ALL_VERSIONS { + let client_config = Arc::new(make_client_config_with_verifier( + forbid_unknown_verifier.clone(), + &version_provider, + )); + let mut client = client_config + .connect(server_name("localhost")) + .build() + .unwrap(); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); + + // We expect if we use the forbid_unknown_verifier that the handshake will fail since the + // server's EE certificate's revocation status is unknown given the CRLs we've provided. + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err, + Err(ErrorFromPeer::Client(Error::InvalidCertificate( + CertificateError::UnknownRevocationStatus + ))) + ); + + // We expect if we use the allow_unknown_verifier that the handshake will not fail. + let client_config = + make_client_config_with_verifier(allow_unknown_verifier.clone(), &version_provider); + let mut client = Arc::new(client_config) + .connect(server_name("localhost")) + .build() + .unwrap(); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); + do_handshake_until_error(&mut client, &mut server).unwrap(); + } + } +} + +#[test] +fn client_check_server_certificate_intermediate_revoked() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let server_config = Arc::new(make_server_config(*kt, &provider)); + + // Setup a server verifier builder that will check the full chain revocation status against a CRL + // that marks the intermediate certificate as revoked. We allow unknown revocation status + // so the EE cert's unknown status doesn't cause an error. + let crls = vec![kt.intermediate_crl()]; + let full_chain_verifier_builder = + webpki_server_verifier_builder(kt.client_root_store(), &provider) + .with_crls(crls.clone()) + .allow_unknown_revocation_status(); + + // Also set up a verifier builder that will use the same CRL, but only check the EE certificate + // revocation status. + let ee_verifier_builder = webpki_server_verifier_builder(kt.client_root_store(), &provider) + .with_crls(crls.clone()) + .only_check_end_entity_revocation() + .allow_unknown_revocation_status(); + + for version_provider in ALL_VERSIONS { + let client_config = Arc::new(make_client_config_with_verifier( + full_chain_verifier_builder.clone(), + &version_provider, + )); + let mut client = client_config + .connect(server_name("localhost")) + .build() + .unwrap(); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); + + // We expect the handshake to fail when using the full chain verifier since the intermediate's + // EE certificate is revoked. + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err, + Err(ErrorFromPeer::Client(Error::InvalidCertificate( + CertificateError::Revoked + ))) + ); + + let client_config = + make_client_config_with_verifier(ee_verifier_builder.clone(), &version_provider); + let mut client = Arc::new(client_config) + .connect(server_name("localhost")) + .build() + .unwrap(); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); + // We expect the handshake to succeed when we use the verifier that only checks the EE certificate + // revocation status. The revoked intermediate status should not be checked. + do_handshake_until_error(&mut client, &mut server).unwrap(); + } + } +} + +#[test] +fn client_check_server_certificate_ee_crl_expired() { + let provider = provider::DEFAULT_PROVIDER; + for kt in KeyType::all_for_provider(&provider) { + let server_config = Arc::new(make_server_config(*kt, &provider)); + + // Setup a server verifier that will check the EE certificate's revocation status, with CRL expiration enforced. + let crls = vec![kt.end_entity_crl_expired()]; + let enforce_expiration_builder = + webpki_server_verifier_builder(kt.client_root_store(), &provider) + .with_crls(crls) + .only_check_end_entity_revocation() + .enforce_revocation_expiration(); + + // Also setup a server verifier without CRL expiration enforced. + let crls = vec![kt.end_entity_crl_expired()]; + let ignore_expiration_builder = + webpki_server_verifier_builder(kt.client_root_store(), &provider) + .with_crls(crls) + .only_check_end_entity_revocation(); + + for version_provider in ALL_VERSIONS { + let client_config = Arc::new(make_client_config_with_verifier( + enforce_expiration_builder.clone(), + &version_provider, + )); + let mut client = client_config + .connect(server_name("localhost")) + .build() + .unwrap(); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); + + // We expect the handshake to fail since the CRL is expired. + let err = do_handshake_until_error(&mut client, &mut server); + assert!(matches!( + err, + Err(ErrorFromPeer::Client(Error::InvalidCertificate( + CertificateError::ExpiredRevocationListContext { .. } + ))) + )); + + let client_config = Arc::new(make_client_config_with_verifier( + ignore_expiration_builder.clone(), + &version_provider, + )); + let mut client = client_config + .connect(server_name("localhost")) + .build() + .unwrap(); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); + + // We expect the handshake to succeed when CRL expiration is ignored. + do_handshake_until_error(&mut client, &mut server).unwrap(); + } + } +} + +/// Simple smoke-test of the webpki verify_identity_signed_by_trust_anchor helper API. +/// This public API is intended to be used by consumers implementing their own verifier and +/// so isn't used by the other existing verifier tests. +#[test] +fn client_check_server_certificate_helper_api() { + for kt in KeyType::all_for_provider(&provider::DEFAULT_PROVIDER) { + let Identity::X509(identity) = &*kt.identity() else { + panic!("expected X509 identity"); + }; + + let correct_roots = kt.client_root_store(); + let incorrect_roots = match kt { + KeyType::Rsa2048 => KeyType::EcdsaP256, + _ => KeyType::Rsa2048, + } + .client_root_store(); + // Using the correct trust anchors, we should verify without error. + verify_identity_signed_by_trust_anchor( + &ParsedCertificate::try_from(&identity.end_entity).unwrap(), + &correct_roots, + &identity.intermediates, + UnixTime::now(), + provider::ALL_VERIFICATION_ALGS, + ) + .unwrap(); + // Using the wrong trust anchors, we should get the expected error. + assert_eq!( + verify_identity_signed_by_trust_anchor( + &ParsedCertificate::try_from(&identity.end_entity).unwrap(), + &incorrect_roots, + &identity.intermediates, + UnixTime::now(), + provider::ALL_VERIFICATION_ALGS, + ) + .unwrap_err(), + Error::InvalidCertificate(CertificateError::UnknownIssuer) + ); + } +} + +#[test] +fn client_check_server_valid_purpose() { + let Identity::X509(identity) = &*KeyType::EcdsaP256.client_identity() else { + panic!("expected X509 identity"); + }; + + let trust_anchor = identity.intermediates.last().unwrap(); + let roots = RootCertStore { + roots: vec![ + anchor_from_trusted_cert(trust_anchor) + .unwrap() + .to_owned(), + ], + }; + + let error = verify_identity_signed_by_trust_anchor( + &ParsedCertificate::try_from(&identity.end_entity).unwrap(), + &roots, + &identity.intermediates, + UnixTime::now(), + provider::ALL_VERIFICATION_ALGS, + ) + .unwrap_err(); + assert_eq!( + error, + Error::InvalidCertificate(CertificateError::InvalidPurposeContext { + required: ExtendedKeyPurpose::ServerAuth, + presented: vec![ExtendedKeyPurpose::ClientAuth], + }) + ); + + assert_eq!( + format!("{error}"), + "invalid peer certificate: certificate does not allow extended key usage for \ + server authentication, allows client authentication" + ); +} + +#[derive(Debug)] +struct ResolvesCertChainByCaName(Vec<(DistinguishedName, Credentials)>); + +impl ServerCredentialResolver for ResolvesCertChainByCaName { + fn resolve(&self, client_hello: &ClientHello<'_>) -> Result { + let Some(cas_extension) = client_hello.certificate_authorities() else { + println!( + "ResolvesCertChainByCaName: no CAs extension in ClientHello, returning default cert" + ); + return self.0[0] + .1 + .signer(client_hello.signature_schemes()) + .ok_or(Error::PeerIncompatible( + PeerIncompatible::NoSignatureSchemesInCommon, + )); + }; + + for (name, credentials) in self.0.iter() { + let name = X509Name::from_der(name.as_ref()) + .unwrap() + .1; + if cas_extension.iter().any(|ca_name| { + X509Name::from_der(ca_name.as_ref()).is_ok_and(|(_, ca_name)| ca_name == name) + }) { + println!("ResolvesCertChainByCaName: found matching CA name: {name}"); + return credentials + .signer(client_hello.signature_schemes()) + .ok_or(Error::PeerIncompatible( + PeerIncompatible::NoSignatureSchemesInCommon, + )); + } + } + + println!("ResolvesCertChainByCaName: no matching CA name found, returning default Cert"); + self.0[0] + .1 + .signer(client_hello.signature_schemes()) + .ok_or(Error::PeerIncompatible( + PeerIncompatible::NoSignatureSchemesInCommon, + )) + } +} + +#[derive(Debug)] +struct ServerVerifierWithCasExt { + verifier: Arc, + ca_names: Arc<[DistinguishedName]>, +} + +impl ServerVerifier for ServerVerifierWithCasExt { + fn verify_identity(&self, identity: &ServerIdentity<'_>) -> Result { + self.verifier.verify_identity(identity) + } + + fn verify_tls12_signature( + &self, + input: &SignatureVerificationInput<'_>, + ) -> Result { + self.verifier + .verify_tls12_signature(input) + } + + fn verify_tls13_signature( + &self, + input: &SignatureVerificationInput<'_>, + ) -> Result { + self.verifier + .verify_tls13_signature(input) + } + + fn supported_verify_schemes(&self) -> Vec { + self.verifier.supported_verify_schemes() + } + + fn request_ocsp_response(&self) -> bool { + self.verifier.request_ocsp_response() + } + + fn supported_certificate_types(&self) -> &'static [CertificateType] { + self.verifier + .supported_certificate_types() + } + + fn root_hint_subjects(&self) -> Option> { + println!("ServerVerifierWithCasExt::root_hint_subjects() called!"); + Some(self.ca_names.clone()) + } + + fn hash_config(&self, h: &mut dyn Hasher) { + self.verifier.hash_config(h) + } +} diff --git a/rustls/tests/bogo.rs b/rustls-test/tests/bogo.rs similarity index 89% rename from rustls/tests/bogo.rs rename to rustls-test/tests/bogo.rs index 297a6d744d4..06595dc9d7e 100644 --- a/rustls/tests/bogo.rs +++ b/rustls-test/tests/bogo.rs @@ -20,12 +20,6 @@ fn run_bogo_tests_aws_lc_rs_fips() { run_bogo_tests("aws-lc-rs-fips"); } -#[test] -#[ignore] -fn run_bogo_tests_post_quantum() { - run_bogo_tests("post-quantum"); -} - fn run_bogo_tests(provider: &str) { use std::process::Command; diff --git a/rustls-test/tests/data/DHKEM_P256_HKDF_SHA256-HKDF_SHA256-AES_128_GCM-echconfigs.bin b/rustls-test/tests/data/DHKEM_P256_HKDF_SHA256-HKDF_SHA256-AES_128_GCM-echconfigs.bin new file mode 100644 index 00000000000..4efa66d66a7 Binary files /dev/null and b/rustls-test/tests/data/DHKEM_P256_HKDF_SHA256-HKDF_SHA256-AES_128_GCM-echconfigs.bin differ diff --git a/rustls-test/tests/data/DHKEM_P256_HKDF_SHA256-HKDF_SHA256-AES_256_GCM-echconfigs.bin b/rustls-test/tests/data/DHKEM_P256_HKDF_SHA256-HKDF_SHA256-AES_256_GCM-echconfigs.bin new file mode 100644 index 00000000000..3b5df41459c Binary files /dev/null and b/rustls-test/tests/data/DHKEM_P256_HKDF_SHA256-HKDF_SHA256-AES_256_GCM-echconfigs.bin differ diff --git a/rustls-test/tests/data/DHKEM_P384_HKDF_SHA384-HKDF_SHA384-AES_128_GCM-echconfigs.bin b/rustls-test/tests/data/DHKEM_P384_HKDF_SHA384-HKDF_SHA384-AES_128_GCM-echconfigs.bin new file mode 100644 index 00000000000..2db395b44a0 Binary files /dev/null and b/rustls-test/tests/data/DHKEM_P384_HKDF_SHA384-HKDF_SHA384-AES_128_GCM-echconfigs.bin differ diff --git a/rustls-test/tests/data/DHKEM_P384_HKDF_SHA384-HKDF_SHA384-AES_256_GCM-echconfigs.bin b/rustls-test/tests/data/DHKEM_P384_HKDF_SHA384-HKDF_SHA384-AES_256_GCM-echconfigs.bin new file mode 100644 index 00000000000..017e7778705 Binary files /dev/null and b/rustls-test/tests/data/DHKEM_P384_HKDF_SHA384-HKDF_SHA384-AES_256_GCM-echconfigs.bin differ diff --git a/rustls-test/tests/data/DHKEM_P521_HKDF_SHA512-HKDF_SHA512-AES_128_GCM-echconfigs.bin b/rustls-test/tests/data/DHKEM_P521_HKDF_SHA512-HKDF_SHA512-AES_128_GCM-echconfigs.bin new file mode 100644 index 00000000000..4f47639b448 Binary files /dev/null and b/rustls-test/tests/data/DHKEM_P521_HKDF_SHA512-HKDF_SHA512-AES_128_GCM-echconfigs.bin differ diff --git a/rustls-test/tests/data/DHKEM_P521_HKDF_SHA512-HKDF_SHA512-AES_256_GCM-echconfigs.bin b/rustls-test/tests/data/DHKEM_P521_HKDF_SHA512-HKDF_SHA512-AES_256_GCM-echconfigs.bin new file mode 100644 index 00000000000..055cdb25a8e Binary files /dev/null and b/rustls-test/tests/data/DHKEM_P521_HKDF_SHA512-HKDF_SHA512-AES_256_GCM-echconfigs.bin differ diff --git a/rustls/tests/data/bug2040-message-1.bin b/rustls-test/tests/data/bug2040-message-1.bin similarity index 100% rename from rustls/tests/data/bug2040-message-1.bin rename to rustls-test/tests/data/bug2040-message-1.bin diff --git a/rustls/tests/data/bug2040-message-2.bin b/rustls-test/tests/data/bug2040-message-2.bin similarity index 100% rename from rustls/tests/data/bug2040-message-2.bin rename to rustls-test/tests/data/bug2040-message-2.bin diff --git a/rustls/tests/data/bug2227-clienthello.bin b/rustls-test/tests/data/bug2227-clienthello.bin similarity index 100% rename from rustls/tests/data/bug2227-clienthello.bin rename to rustls-test/tests/data/bug2227-clienthello.bin diff --git a/rustls/tests/runners/key_log_file_env.rs b/rustls-test/tests/key_log_file_env.rs similarity index 71% rename from rustls/tests/runners/key_log_file_env.rs rename to rustls-test/tests/key_log_file_env.rs index 74832344851..d8d76f0f797 100644 --- a/rustls/tests/runners/key_log_file_env.rs +++ b/rustls-test/tests/key_log_file_env.rs @@ -1,28 +1,27 @@ +#![allow(clippy::disallowed_types)] + use std::env; use std::sync::Mutex; -#[macro_use] -mod macros; - #[cfg(feature = "ring")] #[path = "."] mod tests_with_ring { use super::serialized; - provider_ring!(); + rustls_test::provider_ring!(); - #[path = "../key_log_file_env.rs"] + #[path = "key_log_file_env/tests.rs"] mod tests; } -#[cfg(feature = "aws_lc_rs")] +#[cfg(feature = "aws-lc-rs")] #[path = "."] mod tests_with_aws_lc_rs { use super::serialized; - provider_aws_lc_rs!(); + rustls_test::provider_aws_lc_rs!(); - #[path = "../key_log_file_env.rs"] + #[path = "key_log_file_env/tests.rs"] mod tests; } @@ -39,7 +38,7 @@ fn serialized(f: impl FnOnce()) { let _guard = MUTEX.lock().unwrap(); // XXX: NOT thread safe. - env::set_var("SSLKEYLOGFILE", "./sslkeylogfile.txt"); + unsafe { env::set_var("SSLKEYLOGFILE", "./sslkeylogfile.txt") }; f() } diff --git a/rustls/tests/key_log_file_env.rs b/rustls-test/tests/key_log_file_env/tests.rs similarity index 73% rename from rustls/tests/key_log_file_env.rs rename to rustls-test/tests/key_log_file_env/tests.rs index 1d00f0b2aa4..c64ec9f3724 100644 --- a/rustls/tests/key_log_file_env.rs +++ b/rustls-test/tests/key_log_file_env/tests.rs @@ -27,23 +27,25 @@ use std::env; use std::io::Write; use std::sync::Arc; -use super::*; - -mod common; -use common::{ - do_handshake, make_client_config_with_versions, make_pair_for_arc_configs, make_server_config, - transfer, KeyType, +use rustls::Connection; +use rustls_test::{ + KeyType, do_handshake, make_client_config, make_pair_for_arc_configs, make_server_config, + transfer, }; +use rustls_util::KeyLogFile; + +use super::{ALL_VERSIONS, provider, serialized}; #[test] fn exercise_key_log_file_for_client() { serialized(|| { - let server_config = Arc::new(make_server_config(KeyType::Rsa2048)); - env::set_var("SSLKEYLOGFILE", "./sslkeylogfile.txt"); + let provider = provider::DEFAULT_PROVIDER; + let server_config = Arc::new(make_server_config(KeyType::Rsa2048, &provider)); + unsafe { env::set_var("SSLKEYLOGFILE", "./sslkeylogfile.txt") }; - for version in rustls::ALL_VERSIONS { - let mut client_config = make_client_config_with_versions(KeyType::Rsa2048, &[version]); - client_config.key_log = Arc::new(rustls::KeyLogFile::new()); + for version_provider in ALL_VERSIONS { + let mut client_config = make_client_config(KeyType::Rsa2048, &version_provider); + client_config.key_log = Arc::new(KeyLogFile::new()); let (mut client, mut server) = make_pair_for_arc_configs(&Arc::new(client_config), &server_config); @@ -60,15 +62,15 @@ fn exercise_key_log_file_for_client() { #[test] fn exercise_key_log_file_for_server() { serialized(|| { - let mut server_config = make_server_config(KeyType::Rsa2048); + let mut server_config = make_server_config(KeyType::Rsa2048, &provider::DEFAULT_PROVIDER); - env::set_var("SSLKEYLOGFILE", "./sslkeylogfile.txt"); - server_config.key_log = Arc::new(rustls::KeyLogFile::new()); + unsafe { env::set_var("SSLKEYLOGFILE", "./sslkeylogfile.txt") }; + server_config.key_log = Arc::new(KeyLogFile::new()); let server_config = Arc::new(server_config); - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(KeyType::Rsa2048, &[version]); + for version_provider in ALL_VERSIONS { + let client_config = make_client_config(KeyType::Rsa2048, &version_provider); let (mut client, mut server) = make_pair_for_arc_configs(&Arc::new(client_config), &server_config); diff --git a/rustls-test/tests/process_provider.rs b/rustls-test/tests/process_provider.rs new file mode 100644 index 00000000000..226147e8822 --- /dev/null +++ b/rustls-test/tests/process_provider.rs @@ -0,0 +1,32 @@ +#![cfg(any(feature = "ring", feature = "aws-lc-rs"))] + +//! Note that the default test runner builds each test file into a separate +//! executable, and runs tests in an indeterminate order. That restricts us +//! to doing all the desired tests, in series, in one function. + +use rustls::ClientConfig; +use rustls::crypto::CryptoProvider; +#[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] +use rustls_aws_lc_rs as provider; +#[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))] +use rustls_ring as provider; +#[cfg(all(feature = "ring", feature = "aws-lc-rs"))] +use rustls_ring as provider; +use rustls_test::{ClientConfigExt, KeyType}; + +#[cfg(any(feature = "ring", feature = "aws-lc-rs"))] +#[test] +fn test_explicit_choice_required() { + assert!(CryptoProvider::get_default().is_none()); + provider::DEFAULT_PROVIDER + .install_default() + .expect("cannot install"); + CryptoProvider::get_default().expect("provider missing"); + provider::DEFAULT_PROVIDER + .install_default() + .expect_err("install succeeded a second time"); + let provider = CryptoProvider::get_default().expect("provider missing"); + + // does not panic + ClientConfig::builder(provider.clone()).finish(KeyType::Rsa2048); +} diff --git a/rustls-util/Cargo.toml b/rustls-util/Cargo.toml new file mode 100644 index 00000000000..ad885abce49 --- /dev/null +++ b/rustls-util/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "rustls-util" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0 OR ISC OR MIT" +description = "Utilities for use with rustls" +homepage = "https://github.com/rustls/rustls" +repository = "https://github.com/rustls/rustls" + +[features] +default = ["log"] +log = ["dep:log", "rustls/log"] + +[dependencies] +log = { workspace = true, optional = true } +rustls = { path = "../rustls", version = "0.24.0-dev.0", default-features = false } + +[dev-dependencies] +env_logger = { workspace = true } + +[lints] +workspace = true diff --git a/rustls/src/key_log_file.rs b/rustls-util/src/key_log_file.rs similarity index 67% rename from rustls/src/key_log_file.rs rename to rustls-util/src/key_log_file.rs index f0ad95223ad..a020844809e 100644 --- a/rustls/src/key_log_file.rs +++ b/rustls-util/src/key_log_file.rs @@ -1,4 +1,3 @@ -use alloc::vec::Vec; use core::fmt::{Debug, Formatter}; use std::env::var_os; use std::ffi::OsString; @@ -7,8 +6,9 @@ use std::io; use std::io::Write; use std::sync::Mutex; -use crate::log::warn; -use crate::KeyLog; +#[cfg(feature = "log")] +use log::warn; +use rustls::KeyLog; // Internal mutable state for KeyLogFile struct KeyLogFileInner { @@ -25,15 +25,17 @@ impl KeyLogFileInner { }; }; - #[cfg_attr(not(feature = "logging"), allow(unused_variables))] + #[cfg_attr(not(feature = "log"), expect(clippy::manual_ok_err))] let file = match OpenOptions::new() .append(true) .create(true) .open(path) { Ok(f) => Some(f), + #[cfg_attr(not(feature = "log"), expect(unused_variables))] Err(e) => { - warn!("unable to create key log file {:?}: {}", path, e); + #[cfg(feature = "log")] + warn!("unable to create key log file {path:?}: {e}"); None } }; @@ -45,21 +47,18 @@ impl KeyLogFileInner { } fn try_write(&mut self, label: &str, client_random: &[u8], secret: &[u8]) -> io::Result<()> { - let mut file = match self.file { - None => { - return Ok(()); - } - Some(ref f) => f, + let Some(file) = &mut self.file else { + return Ok(()); }; self.buf.truncate(0); - write!(self.buf, "{} ", label)?; + write!(self.buf, "{label} ")?; for b in client_random.iter() { - write!(self.buf, "{:02x}", b)?; + write!(self.buf, "{b:02x}")?; } write!(self.buf, " ")?; for b in secret.iter() { - write!(self.buf, "{:02x}", b)?; + write!(self.buf, "{b:02x}")?; } writeln!(self.buf)?; file.write_all(&self.buf) @@ -71,7 +70,7 @@ impl Debug for KeyLogFileInner { f.debug_struct("KeyLogFileInner") // Note: we omit self.buf deliberately as it may contain key data. .field("file", &self.file) - .finish() + .finish_non_exhaustive() } } @@ -96,7 +95,6 @@ impl KeyLogFile { impl KeyLog for KeyLogFile { fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) { - #[cfg_attr(not(feature = "logging"), allow(unused_variables))] match self .0 .lock() @@ -104,8 +102,10 @@ impl KeyLog for KeyLogFile { .try_write(label, client_random, secret) { Ok(()) => {} + #[cfg_attr(not(feature = "log"), expect(unused_variables))] Err(e) => { - warn!("error writing to key log file: {}", e); + #[cfg(feature = "log")] + warn!("error writing to key log file: {e}"); } } } @@ -114,13 +114,13 @@ impl KeyLog for KeyLogFile { impl Debug for KeyLogFile { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self.0.try_lock() { - Ok(key_log_file) => write!(f, "{:?}", key_log_file), + Ok(key_log_file) => write!(f, "{key_log_file:?}"), Err(_) => write!(f, "KeyLogFile {{ }}"), } } } -#[cfg(all(test, target_os = "linux"))] +#[cfg(all(test, any(target_os = "linux", target_os = "macos")))] mod tests { use super::*; @@ -134,26 +134,39 @@ mod tests { fn test_env_var_is_not_set() { init(); let mut inner = KeyLogFileInner::new(None); - assert!(inner - .try_write("label", b"random", b"secret") - .is_ok()); + assert!( + inner + .try_write("label", b"random", b"secret") + .is_ok() + ); } #[test] fn test_env_var_cannot_be_opened() { init(); let mut inner = KeyLogFileInner::new(Some("/dev/does-not-exist".into())); - assert!(inner - .try_write("label", b"random", b"secret") - .is_ok()); + assert!( + inner + .try_write("label", b"random", b"secret") + .is_ok() + ); } #[test] fn test_env_var_cannot_be_written() { init(); - let mut inner = KeyLogFileInner::new(Some("/dev/full".into())); - assert!(inner - .try_write("label", b"random", b"secret") - .is_err()); + + #[cfg(target_os = "linux")] + const UNWRITABLE_FILE: &str = "/dev/full"; + + #[cfg(target_os = "macos")] + const UNWRITABLE_FILE: &str = "/dev/urandom"; + + let mut inner = KeyLogFileInner::new(Some(UNWRITABLE_FILE.into())); + assert!( + inner + .try_write("label", b"random", b"secret") + .is_err() + ); } } diff --git a/rustls-util/src/lib.rs b/rustls-util/src/lib.rs new file mode 100644 index 00000000000..335f67703ab --- /dev/null +++ b/rustls-util/src/lib.rs @@ -0,0 +1,140 @@ +use std::io; + +mod key_log_file; +pub use key_log_file::KeyLogFile; +use rustls::Connection; + +mod stream; +pub use crate::stream::{Stream, StreamOwned}; + +/// This function uses `io` to complete any outstanding IO for +/// the connection. +/// +/// This is a convenience function which solely uses other parts +/// of the public API. +/// +/// What this means depends on the connection state: +/// +/// - If the connection [`is_handshaking()`], then IO is performed until +/// the handshake is complete. +/// - Otherwise, if [`wants_write()`] is true, [`write_tls()`] is invoked +/// until it is all written. +/// - Otherwise, if [`wants_read()`] is true, [`read_tls()`] is invoked +/// once. +/// +/// The return value is the number of bytes read from and written +/// to `io`, respectively. Once both `read()` and `write()` yield `WouldBlock`, +/// this function will propagate the error. +/// +/// Errors from TLS record handling (i.e., from [`process_new_packets()`]) +/// are wrapped in an `io::ErrorKind::InvalidData`-kind error. +/// +/// [`is_handshaking()`]: rustls::CommonState::is_handshaking +/// [`wants_read()`]: rustls::CommonState::wants_read +/// [`wants_write()`]: rustls::CommonState::wants_write +/// [`write_tls()`]: rustls::ConnectionCommon::write_tls +/// [`read_tls()`]: rustls::ConnectionCommon::read_tls +/// [`process_new_packets()`]: rustls::ConnectionCommon::process_new_packets +pub fn complete_io( + io: &mut (impl io::Read + io::Write), + conn: &mut dyn Connection, +) -> Result<(usize, usize), io::Error> { + let mut eof = false; + let mut wrlen = 0; + let mut rdlen = 0; + loop { + let (mut blocked_write, mut blocked_read) = (None, None); + let until_handshaked = conn.is_handshaking(); + + if !conn.wants_write() && !conn.wants_read() { + // We will make no further progress. + return Ok((rdlen, wrlen)); + } + + while conn.wants_write() { + match conn.write_tls(io) { + Ok(0) => { + io.flush()?; + return Ok((rdlen, wrlen)); // EOF. + } + Ok(n) => wrlen += n, + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + blocked_write = Some(err); + break; + } + Err(err) => return Err(err), + } + } + if wrlen > 0 { + io.flush()?; + } + + if !until_handshaked && wrlen > 0 { + return Ok((rdlen, wrlen)); + } + + // If we want to write, but are WouldBlocked by the underlying IO, *and* + // have no desire to read; that is everything. + if let (Some(_), false) = (&blocked_write, conn.wants_read()) { + return match wrlen { + 0 => Err(blocked_write.unwrap()), + _ => Ok((rdlen, wrlen)), + }; + } + + while !eof && conn.wants_read() { + let read_size = match conn.read_tls(io) { + Ok(0) => { + eof = true; + Some(0) + } + Ok(n) => { + rdlen += n; + Some(n) + } + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + blocked_read = Some(err); + break; + } + Err(err) if err.kind() == io::ErrorKind::Interrupted => None, // nothing to do + Err(err) => return Err(err), + }; + if read_size.is_some() { + break; + } + } + + if let Err(e) = conn.process_new_packets() { + // In case we have an alert to send describing this error, try a last-gasp + // write -- but don't predate the primary error. + let _ignored = conn.write_tls(io); + let _ignored = io.flush(); + return Err(io::Error::new(io::ErrorKind::InvalidData, e)); + }; + + // If we want to read, but are WouldBlocked by the underlying IO, *and* + // have no desire to write; that is everything. + if let (Some(_), false) = (&blocked_read, conn.wants_write()) { + return match rdlen { + 0 => Err(blocked_read.unwrap()), + _ => Ok((rdlen, wrlen)), + }; + } + + // if we're doing IO until handshaked, and we believe we've finished handshaking, + // but process_new_packets() has queued TLS data to send, loop around again to write + // the queued messages. + if until_handshaked && !conn.is_handshaking() && conn.wants_write() { + continue; + } + + let blocked = blocked_write.zip(blocked_read); + match (eof, until_handshaked, conn.is_handshaking(), blocked) { + (_, true, false, _) => return Ok((rdlen, wrlen)), + (_, _, _, Some((e, _))) if rdlen == 0 && wrlen == 0 => return Err(e), + (_, false, _, _) => return Ok((rdlen, wrlen)), + (true, true, true, _) => return Err(io::Error::from(io::ErrorKind::UnexpectedEof)), + _ => {} + } + } +} diff --git a/rustls/src/stream.rs b/rustls-util/src/stream.rs similarity index 67% rename from rustls/src/stream.rs rename to rustls-util/src/stream.rs index a6a394a9123..01e10e3c11b 100644 --- a/rustls/src/stream.rs +++ b/rustls-util/src/stream.rs @@ -1,12 +1,18 @@ -use core::ops::{Deref, DerefMut}; -use std::io::{IoSlice, Read, Result, Write}; +use std::io::{BufRead, IoSlice, Read, Result, Write}; -use crate::conn::{ConnectionCommon, SideData}; +use rustls::Connection; + +use crate::complete_io; /// This type implements `io::Read` and `io::Write`, encapsulating /// a Connection `C` and an underlying transport `T`, such as a socket. /// +/// Relies on [`complete_io()`] to perform the necessary I/O. +/// /// This allows you to use a rustls Connection like a normal stream. +/// +/// [`complete_io()`]: crate::complete_io() +#[expect(clippy::exhaustive_structs)] #[derive(Debug)] pub struct Stream<'a, C: 'a + ?Sized, T: 'a + Read + Write + ?Sized> { /// Our TLS connection @@ -16,11 +22,10 @@ pub struct Stream<'a, C: 'a + ?Sized, T: 'a + Read + Write + ?Sized> { pub sock: &'a mut T, } -impl<'a, C, T, S> Stream<'a, C, T> +impl<'a, C, T> Stream<'a, C, T> where - C: 'a + DerefMut + Deref>, + C: 'a + Connection, T: 'a + Read + Write, - S: SideData, { /// Make a new Stream using the Connection `conn` and socket-like object /// `sock`. This does not fail and does no IO. @@ -32,24 +37,17 @@ where /// If we have data to write, write it all. fn complete_prior_io(&mut self) -> Result<()> { if self.conn.is_handshaking() { - self.conn.complete_io(self.sock)?; + complete_io(self.sock, self.conn)?; } if self.conn.wants_write() { - self.conn.complete_io(self.sock)?; + complete_io(self.sock, self.conn)?; } Ok(()) } -} -impl<'a, C, T, S> Read for Stream<'a, C, T> -where - C: 'a + DerefMut + Deref>, - T: 'a + Read + Write, - S: SideData, -{ - fn read(&mut self, buf: &mut [u8]) -> Result { + fn prepare_read(&mut self) -> Result<()> { self.complete_prior_io()?; // We call complete_io() in a loop since a single call may read only @@ -57,37 +55,55 @@ where // needed to get more plaintext, which we must do if EOF has not been // hit. while self.conn.wants_read() { - if self.conn.complete_io(self.sock)?.0 == 0 { + if complete_io(self.sock, self.conn)?.0 == 0 { break; } } - self.conn.reader().read(buf) + Ok(()) } - #[cfg(read_buf)] - fn read_buf(&mut self, cursor: core::io::BorrowedCursor<'_>) -> Result<()> { - self.complete_prior_io()?; + // Implements `BufRead::fill_buf` but with more flexible lifetimes, so StreamOwned can reuse it + fn fill_buf(mut self) -> Result<&'a [u8]> { + self.prepare_read()?; + self.conn.reader().into_first_chunk() + } +} - // We call complete_io() in a loop since a single call may read only - // a partial packet from the underlying transport. A full packet is - // needed to get more plaintext, which we must do if EOF has not been - // hit. - while self.conn.wants_read() { - if self.conn.complete_io(self.sock)?.0 == 0 { - break; - } +impl<'a, C, T> Read for Stream<'a, C, T> +where + C: 'a + Connection, + T: 'a + Read + Write, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + self.prepare_read()?; + self.conn.reader().read(buf) + } +} + +impl<'a, C, T> BufRead for Stream<'a, C, T> +where + C: 'a + Connection, + T: 'a + Read + Write, +{ + fn fill_buf(&mut self) -> Result<&[u8]> { + // reborrow to get an owned `Stream` + Stream { + conn: self.conn, + sock: self.sock, } + .fill_buf() + } - self.conn.reader().read_buf(cursor) + fn consume(&mut self, amt: usize) { + self.conn.reader().consume(amt) } } -impl<'a, C, T, S> Write for Stream<'a, C, T> +impl<'a, C, T> Write for Stream<'a, C, T> where - C: 'a + DerefMut + Deref>, + C: 'a + Connection, T: 'a + Read + Write, - S: SideData, { fn write(&mut self, buf: &[u8]) -> Result { self.complete_prior_io()?; @@ -97,7 +113,7 @@ where // Try to write the underlying transport here, but don't let // any errors mask the fact we've consumed `len` bytes. // Callers will learn of permanent errors on the next call. - let _ = self.conn.complete_io(self.sock); + let _ = complete_io(self.sock, self.conn); Ok(len) } @@ -113,7 +129,7 @@ where // Try to write the underlying transport here, but don't let // any errors mask the fact we've consumed `len` bytes. // Callers will learn of permanent errors on the next call. - let _ = self.conn.complete_io(self.sock); + let _ = complete_io(self.sock, self.conn); Ok(len) } @@ -123,17 +139,21 @@ where self.conn.writer().flush()?; if self.conn.wants_write() { - self.conn.complete_io(self.sock)?; + complete_io(self.sock, self.conn)?; } Ok(()) } } /// This type implements `io::Read` and `io::Write`, encapsulating -/// and owning a Connection `C` and an underlying blocking transport -/// `T`, such as a socket. +/// and owning a Connection `C` and an underlying transport `T`, such as a socket. +/// +/// Relies on [`complete_io()`] to perform the necessary I/O. /// /// This allows you to use a rustls Connection like a normal stream. +/// +/// [`complete_io()`]: crate::complete_io() +#[expect(clippy::exhaustive_structs)] #[derive(Debug)] pub struct StreamOwned { /// Our connection @@ -143,11 +163,10 @@ pub struct StreamOwned { pub sock: T, } -impl StreamOwned +impl StreamOwned where - C: DerefMut + Deref>, + C: Connection, T: Read + Write, - S: SideData, { /// Make a new StreamOwned taking the Connection `conn` and socket-like /// object `sock`. This does not fail and does no IO. @@ -174,11 +193,10 @@ where } } -impl<'a, C, T, S> StreamOwned +impl<'a, C, T> StreamOwned where - C: DerefMut + Deref>, + C: Connection, T: Read + Write, - S: SideData, { fn as_stream(&'a mut self) -> Stream<'a, C, T> { Stream { @@ -188,27 +206,34 @@ where } } -impl Read for StreamOwned +impl Read for StreamOwned where - C: DerefMut + Deref>, + C: Connection, T: Read + Write, - S: SideData, { fn read(&mut self, buf: &mut [u8]) -> Result { self.as_stream().read(buf) } +} + +impl BufRead for StreamOwned +where + C: Connection, + T: Read + Write, +{ + fn fill_buf(&mut self) -> Result<&[u8]> { + self.as_stream().fill_buf() + } - #[cfg(read_buf)] - fn read_buf(&mut self, cursor: core::io::BorrowedCursor<'_>) -> Result<()> { - self.as_stream().read_buf(cursor) + fn consume(&mut self, amt: usize) { + self.as_stream().consume(amt) } } -impl Write for StreamOwned +impl Write for StreamOwned where - C: DerefMut + Deref>, + C: Connection, T: Read + Write, - S: SideData, { fn write(&mut self, buf: &[u8]) -> Result { self.as_stream().write(buf) @@ -223,9 +248,9 @@ where mod tests { use std::net::TcpStream; + use rustls::{ClientConnection, ServerConnection}; + use super::{Stream, StreamOwned}; - use crate::client::ClientConnection; - use crate::server::ServerConnection; #[test] fn stream_can_be_created_for_connection_and_tcpstream() { diff --git a/rustls/.clippy.toml b/rustls/.clippy.toml new file mode 100644 index 00000000000..893eeb2fa9f --- /dev/null +++ b/rustls/.clippy.toml @@ -0,0 +1,7 @@ +upper-case-acronyms-aggressive = true +check-inconsistent-struct-field-initializers = true + +disallowed-types = [ + { path = "std::sync::Arc", reason = "must use Arc from sync module to support downstream forks targeting architectures without atomic ptrs" }, + { path = "alloc::sync::Arc", reason = "must use Arc from sync module to support downstream forks targeting architectures without atomic ptrs" }, +] diff --git a/rustls/Cargo.toml b/rustls/Cargo.toml index 55541050bea..8003ae042e6 100644 --- a/rustls/Cargo.toml +++ b/rustls/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "rustls" -version = "0.23.21" +version = "0.24.0-dev.0" edition = "2021" -rust-version = "1.71" +rust-version = "1.85" license = "Apache-2.0 OR ISC OR MIT" readme = "../README.md" description = "Rustls is a modern TLS library written in Rust." @@ -12,109 +12,57 @@ categories = ["network-programming", "cryptography"] autobenches = false autotests = false exclude = ["src/testdata", "tests/**"] -build = "build.rs" -[build-dependencies] -rustversion = { version = "1.0.6", optional = true } +[features] +default = ["log", "webpki"] +brotli = ["dep:brotli", "dep:brotli-decompressor"] +log = ["dep:log"] +webpki = ["dep:webpki"] +zlib = ["dep:zlib-rs"] [dependencies] -aws-lc-rs = { workspace = true, optional = true } brotli = { workspace = true, optional = true } brotli-decompressor = { workspace = true, optional = true } hashbrown = { workspace = true, optional = true } log = { workspace = true, optional = true } # only required for no-std -once_cell = { version = "1.16", default-features = false, features = ["alloc", "race"] } -ring = { workspace = true, optional = true } +once_cell = { workspace = true, features = ["std"] } subtle = { workspace = true } -webpki = { workspace = true } -pki-types = { workspace = true } +webpki = { workspace = true, optional = true } +pki-types = { workspace = true, features = ["std"] } zeroize = { workspace = true } zlib-rs = { workspace = true, optional = true } -[features] -default = ["aws_lc_rs", "logging", "std", "tls12"] -std = ["webpki/std", "pki-types/std", "once_cell/std"] -logging = ["log"] -aws_lc_rs = ["dep:aws-lc-rs", "webpki/aws_lc_rs"] -aws-lc-rs = ["aws_lc_rs"] # Alias because Cargo features commonly use `-` -brotli = ["dep:brotli", "dep:brotli-decompressor", "std"] -ring = ["dep:ring", "webpki/ring"] -custom-provider = [] -tls12 = [] -read_buf = ["rustversion", "std"] -fips = ["aws_lc_rs", "aws-lc-rs?/fips"] -zlib = ["dep:zlib-rs"] - [dev-dependencies] -base64 = { workspace = true } bencher = { workspace = true } env_logger = { workspace = true } -hex = { workspace = true } log = { workspace = true } -macro_rules_attribute = { workspace = true } -num-bigint = { workspace = true } rcgen = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } +rustls-test = { workspace = true, default-features = false } time = { workspace = true } webpki-roots = { workspace = true } -x509-parser = { workspace = true } -[[bench]] -name = "benchmarks" -path = "benches/benchmarks.rs" -harness = false -required-features = ["ring"] +[target.'cfg(any(target_arch = "aarch64", target_arch = "x86_64"))'.dev-dependencies] +graviola = { workspace = true } [[example]] name = "test_ca" path = "examples/internal/test_ca.rs" -[[test]] -name = "api" -path = "tests/runners/api.rs" - -[[test]] -name = "api_ffdhe" -path = "tests/runners/api_ffdhe.rs" -required-features = ["tls12"] - -[[test]] -name = "bogo" -path = "tests/bogo.rs" - -[[test]] -name = "client_cert_verifier" -path = "tests/runners/client_cert_verifier.rs" - -[[test]] -name = "ech" -path = "tests/ech.rs" - -[[test]] -name = "key_log_file_env" -path = "tests/runners/key_log_file_env.rs" - -[[test]] -name = "process_provider" -path = "tests/process_provider.rs" - -[[test]] -name = "server_cert_verifier" -path = "tests/runners/server_cert_verifier.rs" - -[[test]] -name = "unbuffered" -path = "tests/runners/unbuffered.rs" - [package.metadata.docs.rs] # all non-default features except fips (cannot build on docs.rs environment) -features = ["read_buf", "ring"] -rustdoc-args = ["--cfg", "docsrs"] +features = ["brotli", "hashbrown", "log", "std", "zlib"] +rustdoc-args = ["--cfg", "rustls_docsrs"] [package.metadata.cargo_check_external_types] allowed_external_types = [ - "rustls_pki_types", - "rustls_pki_types::*", + # --- + "rustls_pki_types", + "rustls_pki_types::*", ] + +[package.metadata.cargo-semver-checks.lints] +enum_no_repr_variant_discriminant_changed = "warn" + +[lints] +workspace = true diff --git a/rustls/benches/benchmarks.rs b/rustls/benches/benchmarks.rs deleted file mode 100644 index cc8016fc7a7..00000000000 --- a/rustls/benches/benchmarks.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![cfg(feature = "ring")] - -use bencher::{benchmark_group, benchmark_main, Bencher}; -use rustls::crypto::ring as provider; - -#[path = "../tests/common/mod.rs"] -mod test_utils; -use std::io; -use std::sync::Arc; - -use rustls::ServerConnection; -use test_utils::*; - -fn bench_ewouldblock(c: &mut Bencher) { - let server_config = make_server_config(KeyType::Rsa2048); - let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); - let mut read_ewouldblock = FailsReads::new(io::ErrorKind::WouldBlock); - c.iter(|| server.read_tls(&mut read_ewouldblock)); -} - -benchmark_group!(benches, bench_ewouldblock); -benchmark_main!(benches); diff --git a/rustls/build.rs b/rustls/build.rs deleted file mode 100644 index a22a4c8176c..00000000000 --- a/rustls/build.rs +++ /dev/null @@ -1,18 +0,0 @@ -/// This build script allows us to enable the `read_buf` language feature only -/// for Rust Nightly. -/// -/// See the comment in lib.rs to understand why we need this. - -#[cfg_attr(feature = "read_buf", rustversion::not(nightly))] -fn main() { - println!("cargo:rustc-check-cfg=cfg(bench)"); - println!("cargo:rustc-check-cfg=cfg(read_buf)"); -} - -#[cfg(feature = "read_buf")] -#[rustversion::nightly] -fn main() { - println!("cargo:rustc-check-cfg=cfg(bench)"); - println!("cargo:rustc-check-cfg=cfg(read_buf)"); - println!("cargo:rustc-cfg=read_buf"); -} diff --git a/rustls/examples/internal/test_ca.rs b/rustls/examples/internal/test_ca.rs index 79e2ab37725..e85902f939e 100644 --- a/rustls/examples/internal/test_ca.rs +++ b/rustls/examples/internal/test_ca.rs @@ -1,24 +1,29 @@ +use core::net::IpAddr; +use core::str::FromStr; +use core::sync::atomic::{AtomicU64, Ordering}; +use core::time::Duration; use std::collections::HashMap; use std::env; use std::fs::{self, File}; use std::io::Write; -use std::net::IpAddr; use std::path::PathBuf; -use std::str::FromStr; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::time::Duration; +use rcgen::string::Ia5String; use rcgen::{ - BasicConstraints, CertificateParams, CertificateRevocationListParams, CertifiedKey, - DistinguishedName, DnType, ExtendedKeyUsagePurpose, Ia5String, IsCa, KeyIdMethod, KeyPair, - KeyUsagePurpose, RevocationReason, RevokedCertParams, RsaKeySize, SanType, SerialNumber, - SignatureAlgorithm, PKCS_ECDSA_P256_SHA256, PKCS_ECDSA_P384_SHA384, PKCS_ECDSA_P521_SHA512, - PKCS_ED25519, PKCS_RSA_SHA256, PKCS_RSA_SHA384, PKCS_RSA_SHA512, + BasicConstraints, Certificate, CertificateParams, CertificateRevocationListParams, + DistinguishedName, DnType, ExtendedKeyUsagePurpose, IsCa, Issuer, KeyIdMethod, KeyPair, + KeyUsagePurpose, PKCS_ECDSA_P256_SHA256, PKCS_ECDSA_P384_SHA384, PKCS_ECDSA_P521_SHA512, + PKCS_ED25519, PKCS_RSA_SHA256, PKCS_RSA_SHA384, PKCS_RSA_SHA512, RevocationReason, + RevokedCertParams, RsaKeySize, SanType, SerialNumber, SignatureAlgorithm, }; use time::OffsetDateTime; -fn main() -> Result<(), Box> { - let mut certified_keys = HashMap::with_capacity(ROLES.len() * SIG_ALGS.len()); +fn main() -> Result<(), Box> { + let mut credentialss = HashMap::< + (Role, &'static SignatureAlgorithm), + (Issuer<'static, KeyPair>, Certificate), + >::with_capacity(ROLES.len() * SIG_ALGS.len()); + for role in ROLES { for alg in SIG_ALGS { // Generate a key pair and serialize it to a PEM encoded file. @@ -29,23 +34,27 @@ fn main() -> Result<(), Box> { // Issue a certificate for the key pair. For trust anchors, this will be self-signed. // Otherwise we dig out the issuer and issuer_key for the issuer, which should have // been produced in earlier iterations based on the careful ordering of roles. - let cert = match role { - Role::TrustAnchor => role - .params(alg) - .self_signed(&key_pair)?, + let (params, cert) = match role { + Role::TrustAnchor => { + let params = role.params(alg); + let cert = params.self_signed(&key_pair)?; + (params, cert) + } Role::Intermediate => { - let issuer: &CertifiedKey = certified_keys + let issuer = credentialss .get(&(Role::TrustAnchor, alg.inner)) .unwrap(); - role.params(alg) - .signed_by(&key_pair, &issuer.cert, &issuer.key_pair)? + let params = role.params(alg); + let cert = params.signed_by(&key_pair, &issuer.0)?; + (params, cert) } Role::EndEntity | Role::Client => { - let issuer = certified_keys + let issuer = credentialss .get(&(Role::Intermediate, alg.inner)) .unwrap(); - role.params(alg) - .signed_by(&key_pair, &issuer.cert, &issuer.key_pair)? + let params = role.params(alg); + let cert = params.signed_by(&key_pair, &issuer.0)?; + (params, cert) } }; @@ -62,29 +71,24 @@ fn main() -> Result<(), Box> { // intermediates this will be the trust anchor, and for client/EE certs this will // be the intermediate. let issuer = match role { - Role::Intermediate => certified_keys + Role::Intermediate => credentialss .get(&(Role::TrustAnchor, alg.inner)) .unwrap(), - Role::EndEntity | Role::Client => certified_keys + Role::EndEntity | Role::Client => credentialss .get(&(Role::Intermediate, alg.inner)) .unwrap(), _ => panic!("unexpected role for CRL generation: {role:?}"), }; - let revoked_crl = crl_for_serial( - cert.params() - .serial_number - .clone() - .unwrap(), - ) - .signed_by(&issuer.cert, &issuer.key_pair)?; + let revoked_crl = + crl_for_serial(params.serial_number.clone().unwrap()).signed_by(&issuer.0)?; let mut revoked_crl_file = File::create( alg.output_directory() .join(format!("{}.revoked.crl.pem", role.label())), )?; revoked_crl_file.write_all(revoked_crl.pem().unwrap().as_bytes())?; - let expired_crl = expired_crl().signed_by(&issuer.cert, &issuer.key_pair)?; + let expired_crl = expired_crl().signed_by(&issuer.0)?; let mut expired_crl_file = File::create( alg.output_directory() .join(format!("{}.expired.crl.pem", role.label())), @@ -95,14 +99,14 @@ fn main() -> Result<(), Box> { // When we're issuing end entity or client certs we have a bit of extra work to do // now that we have full chains in hand. if matches!(role, Role::EndEntity | Role::Client) { - let root = &certified_keys + let root = &credentialss .get(&(Role::TrustAnchor, alg.inner)) .unwrap() - .cert; - let intermediate = &certified_keys + .1; + let intermediate = &credentialss .get(&(Role::Intermediate, alg.inner)) .unwrap() - .cert; + .1; // Write the PEM chain and full chain files for the end entity and client certs. // Chain files include the intermediate and root certs, while full chain files include @@ -129,7 +133,7 @@ fn main() -> Result<(), Box> { raw_public_key_file.write_all(key_pair.public_key_pem().as_bytes())?; } - certified_keys.insert((role, alg.inner), CertifiedKey { cert, key_pair }); + credentialss.insert((role, alg.inner), (Issuer::new(params, key_pair), cert)); } } diff --git a/rustls/src/bs_debug.rs b/rustls/src/bs_debug.rs index 06c723605dc..d935c9c091e 100644 --- a/rustls/src/bs_debug.rs +++ b/rustls/src/bs_debug.rs @@ -31,7 +31,7 @@ impl fmt::Debug for BsDebug<'_> { } else if (0x20..0x7f).contains(&c) { write!(fmt, "{}", c as char)?; } else { - write!(fmt, "\\x{:02x}", c)?; + write!(fmt, "\\x{c:02x}")?; } } write!(fmt, "\"")?; @@ -41,8 +41,8 @@ impl fmt::Debug for BsDebug<'_> { #[cfg(test)] mod tests { + use alloc::vec::Vec; use std::format; - use std::prelude::v1::*; use super::BsDebug; diff --git a/rustls/src/builder.rs b/rustls/src/builder.rs index 85f95bdf436..5183f690d68 100644 --- a/rustls/src/builder.rs +++ b/rustls/src/builder.rs @@ -1,15 +1,11 @@ use alloc::format; -use alloc::sync::Arc; -use alloc::vec::Vec; use core::fmt; use core::marker::PhantomData; use crate::client::EchMode; use crate::crypto::CryptoProvider; -use crate::error::Error; -use crate::msgs::handshake::ALL_KEY_EXCHANGE_ALGORITHMS; +use crate::sync::Arc; use crate::time_provider::TimeProvider; -use crate::versions; #[cfg(doc)] use crate::{ClientConfig, ServerConfig}; @@ -17,56 +13,26 @@ use crate::{ClientConfig, ServerConfig}; /// /// To get one of these, call [`ServerConfig::builder()`] or [`ClientConfig::builder()`]. /// -/// To build a config, you must make at least two decisions (in order): +/// To build a config, you must make at least three decisions (in order): /// +/// - What crypto provider do you want to use? /// - How should this client or server verify certificates provided by its peer? /// - What certificates should this client or server present to its peer? /// /// For settings besides these, see the fields of [`ServerConfig`] and [`ClientConfig`]. /// -/// The usual choice for protocol primitives is to call -/// [`ClientConfig::builder`]/[`ServerConfig::builder`] -/// which will use rustls' default cryptographic provider and safe defaults for ciphersuites and -/// supported protocol versions. +/// The rustls project recommends the crypto provider based on aws-lc-rs for production use. +/// This can be selected by passing in `rustls_aws_lc_rs::DEFAULT_PROVIDER`, +/// which includes safe defaults for cipher suites and protocol versions. /// -/// ``` -/// # #[cfg(feature = "aws_lc_rs")] { -/// # rustls::crypto::aws_lc_rs::default_provider().install_default(); -/// use rustls::{ClientConfig, ServerConfig}; -/// ClientConfig::builder() -/// // ... -/// # ; -/// -/// ServerConfig::builder() -/// // ... -/// # ; -/// # } -/// ``` -/// -/// You may also override the choice of protocol versions: -/// -/// ```no_run -/// # #[cfg(feature = "aws_lc_rs")] { -/// # rustls::crypto::aws_lc_rs::default_provider().install_default(); -/// # use rustls::ServerConfig; -/// ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) -/// // ... -/// # ; -/// # } -/// ``` -/// -/// Overriding the default cryptographic provider introduces a `Result` that must be unwrapped, -/// because the config builder checks for consistency of the choices made. For instance, it's an error to -/// configure only TLS 1.2 cipher suites while specifying that TLS 1.3 should be the only supported protocol -/// version. -/// -/// If you configure a smaller set of protocol primitives than the default, you may get a smaller binary, -/// since the code for the unused ones can be optimized away at link time. -/// -/// After choosing protocol primitives, you must choose (a) how to verify certificates and (b) what certificates +/// After choosing the `CryptoProvider`, you must choose (a) how to verify certificates and (b) what certificates /// (if any) to send to the peer. The methods to do this are specific to whether you're building a ClientConfig /// or a ServerConfig, as tracked by the [`ConfigSide`] type parameter on the various impls of ConfigBuilder. /// +/// A `Result` or `Result` is the outcome of the builder process. +/// The error is used to report consistency problems with the configuration. For example, it's an error +/// to have a `CryptoProvider` that has no cipher suites. +/// /// # ClientConfig certificate configuration /// /// For a client, _certificate verification_ must be configured either by calling one of: @@ -77,19 +43,19 @@ use crate::{ClientConfig, ServerConfig}; /// or disabled using one of: /// - [`ConfigBuilder::with_no_client_auth`] - to not send client authentication (most common) /// - [`ConfigBuilder::with_client_auth_cert`] - to always send a specific certificate -/// - [`ConfigBuilder::with_client_cert_resolver`] - to send a certificate chosen dynamically +/// - [`ConfigBuilder::with_server_credential_resolver`] - to send a certificate chosen dynamically /// /// For example: /// -/// ``` -/// # #[cfg(feature = "aws_lc_rs")] { -/// # rustls::crypto::aws_lc_rs::default_provider().install_default(); +/// ```ignore +/// # use std::sync::Arc; /// # use rustls::ClientConfig; +/// # use rustls::crypto::TEST_PROVIDER as DEFAULT_PROVIDER; /// # let root_certs = rustls::RootCertStore::empty(); -/// ClientConfig::builder() +/// ClientConfig::builder(Arc::new(DEFAULT_PROVIDER)) /// .with_root_certificates(root_certs) -/// .with_no_client_auth(); -/// # } +/// .with_no_client_auth() +/// .unwrap(); /// ``` /// /// # ServerConfig certificate configuration @@ -101,23 +67,23 @@ use crate::{ClientConfig, ServerConfig}; /// Next, _certificate sending_ must be configured by calling one of: /// - [`ConfigBuilder::with_single_cert`] - to send a specific certificate /// - [`ConfigBuilder::with_single_cert_with_ocsp`] - to send a specific certificate, plus stapled OCSP -/// - [`ConfigBuilder::with_cert_resolver`] - to send a certificate chosen dynamically +/// - [`ConfigBuilder::with_server_credential_resolver`] - to send a certificate chosen dynamically /// /// For example: /// -/// ```no_run -/// # #[cfg(feature = "aws_lc_rs")] { -/// # rustls::crypto::aws_lc_rs::default_provider().install_default(); +/// ```ignore +/// # use std::sync::Arc; +/// # use rustls::crypto::Identity; +/// # use rustls::crypto::TEST_PROVIDER as DEFAULT_PROVIDER; /// # use rustls::ServerConfig; /// # let certs = vec![]; /// # let private_key = pki_types::PrivateKeyDer::from( /// # pki_types::PrivatePkcs8KeyDer::from(vec![]) /// # ); -/// ServerConfig::builder() +/// ServerConfig::builder(Arc::new(DEFAULT_PROVIDER)) /// .with_no_client_auth() -/// .with_single_cert(certs, private_key) -/// .expect("bad certificate/key"); -/// # } +/// .with_single_cert(Arc::new(Identity::from_cert_chain(certs).unwrap()), private_key) +/// .expect("bad certificate/key/provider"); /// ``` /// /// # Types @@ -126,7 +92,6 @@ use crate::{ClientConfig, ServerConfig}; /// configuration item is provided exactly once. This is tracked in the `State` type parameter, /// which can have these values: /// -/// - [`WantsVersions`] /// - [`WantsVerifier`] /// - [`WantsClientCert`] /// - [`WantsServerCert`] @@ -141,9 +106,9 @@ use crate::{ClientConfig, ServerConfig}; /// mentioning some of these types. /// /// Additionally, ServerConfig and ClientConfig carry a private field containing a -/// [`CryptoProvider`], from [`ClientConfig::builder_with_provider()`] or -/// [`ServerConfig::builder_with_provider()`]. This determines which cryptographic backend -/// is used. The default is [the process-default provider](`CryptoProvider::get_default`). +/// [`CryptoProvider`], from [`ClientConfig::builder()`] or +/// [`ServerConfig::builder()`]. This determines which cryptographic backend +/// is used. /// /// [builder]: https://rust-unofficial.github.io/patterns/patterns/creational/builder.html /// [typestate]: http://cliffle.com/blog/rust-typestate/ @@ -152,8 +117,8 @@ use crate::{ClientConfig, ServerConfig}; /// [`ClientConfig`]: crate::ClientConfig /// [`ClientConfig::builder()`]: crate::ClientConfig::builder() /// [`ServerConfig::builder()`]: crate::ServerConfig::builder() -/// [`ClientConfig::builder_with_provider()`]: crate::ClientConfig::builder_with_provider() -/// [`ServerConfig::builder_with_provider()`]: crate::ServerConfig::builder_with_provider() +/// [`ClientConfig::builder()`]: crate::ClientConfig::builder() +/// [`ServerConfig::builder()`]: crate::ServerConfig::builder() /// [`ConfigBuilder`]: struct.ConfigBuilder.html#impl-3 /// [`ConfigBuilder`]: struct.ConfigBuilder.html#impl-6 /// [`WantsClientCert`]: crate::client::WantsClientCert @@ -183,85 +148,9 @@ impl fmt::Debug for ConfigBuilder", name,)) + f.debug_struct(&format!("ConfigBuilder<{name}, _>",)) .field("state", &self.state) - .finish() - } -} - -/// Config builder state where the caller must supply TLS protocol versions. -/// -/// For more information, see the [`ConfigBuilder`] documentation. -#[derive(Clone, Debug)] -pub struct WantsVersions {} - -impl ConfigBuilder { - /// Accept the default protocol versions: both TLS1.2 and TLS1.3 are enabled. - pub fn with_safe_default_protocol_versions( - self, - ) -> Result, Error> { - self.with_protocol_versions(versions::DEFAULT_VERSIONS) - } - - /// Use a specific set of protocol versions. - pub fn with_protocol_versions( - self, - versions: &[&'static versions::SupportedProtocolVersion], - ) -> Result, Error> { - let mut any_usable_suite = false; - for suite in &self.provider.cipher_suites { - if versions.contains(&suite.version()) { - any_usable_suite = true; - break; - } - } - - if !any_usable_suite { - return Err(Error::General("no usable cipher suites configured".into())); - } - - if self.provider.kx_groups.is_empty() { - return Err(Error::General("no kx groups configured".into())); - } - - // verifying cipher suites have matching kx groups - let mut supported_kx_algos = Vec::with_capacity(ALL_KEY_EXCHANGE_ALGORITHMS.len()); - for group in self.provider.kx_groups.iter() { - let kx = group.name().key_exchange_algorithm(); - if !supported_kx_algos.contains(&kx) { - supported_kx_algos.push(kx); - } - // Small optimization. We don't need to go over other key exchange groups - // if we already cover all supported key exchange algorithms - if supported_kx_algos.len() == ALL_KEY_EXCHANGE_ALGORITHMS.len() { - break; - } - } - - for cs in self.provider.cipher_suites.iter() { - let cs_kx = cs.key_exchange_algorithms(); - if cs_kx - .iter() - .any(|kx| supported_kx_algos.contains(kx)) - { - continue; - } - let suite_name = cs.common().suite; - return Err(Error::General(alloc::format!( - "Ciphersuite {suite_name:?} requires {cs_kx:?} key exchange, but no {cs_kx:?}-compatible \ - key exchange groups were present in `CryptoProvider`'s `kx_groups` field", - ))); - } - - Ok(ConfigBuilder { - state: WantsVerifier { - versions: versions::EnabledVersions::new(versions), - client_ech_mode: None, - }, - provider: self.provider, - time_provider: self.time_provider, - side: self.side, - }) + .finish_non_exhaustive() } } @@ -270,7 +159,6 @@ impl ConfigBuilder { /// For more information, see the [`ConfigBuilder`] documentation. #[derive(Clone, Debug)] pub struct WantsVerifier { - pub(crate) versions: versions::EnabledVersions, pub(crate) client_ech_mode: Option, } @@ -278,13 +166,10 @@ pub struct WantsVerifier { /// /// [`ClientConfig`]: crate::ClientConfig /// [`ServerConfig`]: crate::ServerConfig -pub trait ConfigSide: sealed::Sealed {} +pub trait ConfigSide: crate::sealed::Sealed {} impl ConfigSide for crate::ClientConfig {} impl ConfigSide for crate::ServerConfig {} -mod sealed { - pub trait Sealed {} - impl Sealed for crate::ClientConfig {} - impl Sealed for crate::ServerConfig {} -} +impl crate::sealed::Sealed for crate::ClientConfig {} +impl crate::sealed::Sealed for crate::ServerConfig {} diff --git a/rustls/src/check.rs b/rustls/src/check.rs index aae5ff6c202..b58d594caf4 100644 --- a/rustls/src/check.rs +++ b/rustls/src/check.rs @@ -1,7 +1,7 @@ use crate::enums::{ContentType, HandshakeType}; use crate::error::Error; use crate::log::warn; -use crate::msgs::message::MessagePayload; +use crate::msgs::MessagePayload; /// For a Message $m, and a HandshakePayload enum member $payload_type, /// return Ok(payload) if $m is both a handshake message and one that @@ -10,13 +10,12 @@ use crate::msgs::message::MessagePayload; macro_rules! require_handshake_msg( ( $m:expr, $handshake_type:path, $payload_type:path ) => ( match &$m.payload { - MessagePayload::Handshake { parsed: $crate::msgs::handshake::HandshakeMessagePayload { - payload: $payload_type(hm), - .. - }, .. } => Ok(hm), + MessagePayload::Handshake { parsed: $crate::msgs::HandshakeMessagePayload( + $payload_type(hm), + ), .. } => Ok(hm), payload => Err($crate::check::inappropriate_handshake_message( payload, - &[$crate::ContentType::Handshake], + &[$crate::enums::ContentType::Handshake], &[$handshake_type])) } ) @@ -26,14 +25,13 @@ macro_rules! require_handshake_msg( macro_rules! require_handshake_msg_move( ( $m:expr, $handshake_type:path, $payload_type:path ) => ( match $m.payload { - MessagePayload::Handshake { parsed: $crate::msgs::handshake::HandshakeMessagePayload { - payload: $payload_type(hm), - .. - }, .. } => Ok(hm), + MessagePayload::Handshake { parsed: $crate::msgs::HandshakeMessagePayload( + $payload_type(hm), + ), .. } => Ok(hm), payload => Err($crate::check::inappropriate_handshake_message( &payload, - &[$crate::ContentType::Handshake], + &[$crate::enums::ContentType::Handshake], &[$handshake_type])) } ) @@ -44,9 +42,8 @@ pub(crate) fn inappropriate_message( content_types: &[ContentType], ) -> Error { warn!( - "Received a {:?} message while expecting {:?}", + "Received a {:?} message while expecting {content_types:?}", payload.content_type(), - content_types ); Error::InappropriateMessage { expect_types: content_types.to_vec(), @@ -61,13 +58,11 @@ pub(crate) fn inappropriate_handshake_message( ) -> Error { match payload { MessagePayload::Handshake { parsed, .. } => { - warn!( - "Received a {:?} handshake message while expecting {:?}", - parsed.typ, handshake_types - ); + let got_type = parsed.0.handshake_type(); + warn!("Received a {got_type:?} handshake message while expecting {handshake_types:?}",); Error::InappropriateHandshakeMessage { expect_types: handshake_types.to_vec(), - got_type: parsed.typ, + got_type, } } payload => inappropriate_message(payload, content_types), diff --git a/rustls/src/client/builder.rs b/rustls/src/client/builder.rs deleted file mode 100644 index bb2a7d66ecc..00000000000 --- a/rustls/src/client/builder.rs +++ /dev/null @@ -1,191 +0,0 @@ -use alloc::sync::Arc; -use alloc::vec::Vec; -use core::marker::PhantomData; - -use pki_types::{CertificateDer, PrivateKeyDer}; - -use super::client_conn::Resumption; -use crate::builder::{ConfigBuilder, WantsVerifier}; -use crate::client::{handy, ClientConfig, EchMode, ResolvesClientCert}; -use crate::error::Error; -use crate::key_log::NoKeyLog; -use crate::msgs::handshake::CertificateChain; -use crate::versions::TLS13; -use crate::webpki::{self, WebPkiServerVerifier}; -use crate::{compress, verify, versions, WantsVersions}; - -impl ConfigBuilder { - /// Enable Encrypted Client Hello (ECH) in the given mode. - /// - /// This implicitly selects TLS 1.3 as the only supported protocol version to meet the - /// requirement to support ECH. - /// - /// The `ClientConfig` that will be produced by this builder will be specific to the provided - /// [`crate::client::EchConfig`] and may not be appropriate for all connections made by the program. - /// In this case the configuration should only be shared by connections intended for domains - /// that offer the provided [`crate::client::EchConfig`] in their DNS zone. - pub fn with_ech( - self, - mode: EchMode, - ) -> Result, Error> { - let mut res = self.with_protocol_versions(&[&TLS13][..])?; - res.state.client_ech_mode = Some(mode); - Ok(res) - } -} - -impl ConfigBuilder { - /// Choose how to verify server certificates. - /// - /// Using this function does not configure revocation. If you wish to - /// configure revocation, instead use: - /// - /// ```diff - /// - .with_root_certificates(root_store) - /// + .with_webpki_verifier( - /// + WebPkiServerVerifier::builder_with_provider(root_store, crypto_provider) - /// + .with_crls(...) - /// + .build()? - /// + ) - /// ``` - pub fn with_root_certificates( - self, - root_store: impl Into>, - ) -> ConfigBuilder { - let algorithms = self - .provider - .signature_verification_algorithms; - self.with_webpki_verifier( - WebPkiServerVerifier::new_without_revocation(root_store, algorithms).into(), - ) - } - - /// Choose how to verify server certificates using a webpki verifier. - /// - /// See [`webpki::WebPkiServerVerifier::builder`] and - /// [`webpki::WebPkiServerVerifier::builder_with_provider`] for more information. - pub fn with_webpki_verifier( - self, - verifier: Arc, - ) -> ConfigBuilder { - ConfigBuilder { - state: WantsClientCert { - versions: self.state.versions, - verifier, - client_ech_mode: self.state.client_ech_mode, - }, - provider: self.provider, - time_provider: self.time_provider, - side: PhantomData, - } - } - - /// Access configuration options whose use is dangerous and requires - /// extra care. - pub fn dangerous(self) -> danger::DangerousClientConfigBuilder { - danger::DangerousClientConfigBuilder { cfg: self } - } -} - -/// Container for unsafe APIs -pub(super) mod danger { - use alloc::sync::Arc; - use core::marker::PhantomData; - - use crate::client::WantsClientCert; - use crate::{verify, ClientConfig, ConfigBuilder, WantsVerifier}; - - /// Accessor for dangerous configuration options. - #[derive(Debug)] - pub struct DangerousClientConfigBuilder { - /// The underlying ClientConfigBuilder - pub cfg: ConfigBuilder, - } - - impl DangerousClientConfigBuilder { - /// Set a custom certificate verifier. - pub fn with_custom_certificate_verifier( - self, - verifier: Arc, - ) -> ConfigBuilder { - ConfigBuilder { - state: WantsClientCert { - versions: self.cfg.state.versions, - verifier, - client_ech_mode: self.cfg.state.client_ech_mode, - }, - provider: self.cfg.provider, - time_provider: self.cfg.time_provider, - side: PhantomData, - } - } - } -} - -/// A config builder state where the caller needs to supply whether and how to provide a client -/// certificate. -/// -/// For more information, see the [`ConfigBuilder`] documentation. -#[derive(Clone)] -pub struct WantsClientCert { - versions: versions::EnabledVersions, - verifier: Arc, - client_ech_mode: Option, -} - -impl ConfigBuilder { - /// Sets a single certificate chain and matching private key for use - /// in client authentication. - /// - /// `cert_chain` is a vector of DER-encoded certificates. - /// `key_der` is a DER-encoded private key as PKCS#1, PKCS#8, or SEC1. The - /// `aws-lc-rs` and `ring` [`CryptoProvider`][crate::CryptoProvider]s support - /// all three encodings, but other `CryptoProviders` may not. - /// - /// This function fails if `key_der` is invalid. - pub fn with_client_auth_cert( - self, - cert_chain: Vec>, - key_der: PrivateKeyDer<'static>, - ) -> Result { - let private_key = self - .provider - .key_provider - .load_private_key(key_der)?; - let resolver = - handy::AlwaysResolvesClientCert::new(private_key, CertificateChain(cert_chain))?; - Ok(self.with_client_cert_resolver(Arc::new(resolver))) - } - - /// Do not support client auth. - pub fn with_no_client_auth(self) -> ClientConfig { - self.with_client_cert_resolver(Arc::new(handy::FailResolveClientCert {})) - } - - /// Sets a custom [`ResolvesClientCert`]. - pub fn with_client_cert_resolver( - self, - client_auth_cert_resolver: Arc, - ) -> ClientConfig { - ClientConfig { - provider: self.provider, - alpn_protocols: Vec::new(), - resumption: Resumption::default(), - max_fragment_size: None, - client_auth_cert_resolver, - versions: self.state.versions, - enable_sni: true, - verifier: self.state.verifier, - key_log: Arc::new(NoKeyLog {}), - enable_secret_extraction: false, - enable_early_data: false, - #[cfg(feature = "tls12")] - require_ems: cfg!(feature = "fips"), - time_provider: self.time_provider, - cert_compressors: compress::default_cert_compressors().to_vec(), - cert_compression_cache: Arc::new(compress::CompressionCache::default()), - cert_decompressors: compress::default_cert_decompressors().to_vec(), - ech_mode: self.state.client_ech_mode, - } - } -} diff --git a/rustls/src/client/client_conn.rs b/rustls/src/client/client_conn.rs deleted file mode 100644 index 854c6b65b67..00000000000 --- a/rustls/src/client/client_conn.rs +++ /dev/null @@ -1,965 +0,0 @@ -use alloc::sync::Arc; -use alloc::vec::Vec; -use core::marker::PhantomData; -use core::ops::{Deref, DerefMut}; -use core::{fmt, mem}; - -use pki_types::{ServerName, UnixTime}; - -use super::handy::NoClientSessionStorage; -use super::hs; -use crate::builder::ConfigBuilder; -use crate::client::{EchMode, EchStatus}; -use crate::common_state::{CommonState, Protocol, Side}; -use crate::conn::{ConnectionCore, UnbufferedConnectionCommon}; -use crate::crypto::{CryptoProvider, SupportedKxGroup}; -use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme}; -use crate::error::Error; -use crate::log::trace; -use crate::msgs::enums::NamedGroup; -use crate::msgs::handshake::ClientExtension; -use crate::msgs::persist; -use crate::suites::SupportedCipherSuite; -#[cfg(feature = "std")] -use crate::time_provider::DefaultTimeProvider; -use crate::time_provider::TimeProvider; -use crate::unbuffered::{EncryptError, TransmitTlsData}; -#[cfg(feature = "std")] -use crate::WantsVerifier; -use crate::{compress, sign, verify, versions, KeyLog, WantsVersions}; -#[cfg(doc)] -use crate::{crypto, DistinguishedName}; - -/// A trait for the ability to store client session data, so that sessions -/// can be resumed in future connections. -/// -/// Generally all data in this interface should be treated as -/// **highly sensitive**, containing enough key material to break all security -/// of the corresponding session. -/// -/// `set_`, `insert_`, `remove_` and `take_` operations are mutating; this isn't -/// expressed in the type system to allow implementations freedom in -/// how to achieve interior mutability. `Mutex` is a common choice. -pub trait ClientSessionStore: fmt::Debug + Send + Sync { - /// Remember what `NamedGroup` the given server chose. - fn set_kx_hint(&self, server_name: ServerName<'static>, group: NamedGroup); - - /// This should return the value most recently passed to `set_kx_hint` - /// for the given `server_name`. - /// - /// If `None` is returned, the caller chooses the first configured group, - /// and an extra round trip might happen if that choice is unsatisfactory - /// to the server. - fn kx_hint(&self, server_name: &ServerName<'_>) -> Option; - - /// Remember a TLS1.2 session. - /// - /// At most one of these can be remembered at a time, per `server_name`. - fn set_tls12_session( - &self, - server_name: ServerName<'static>, - value: persist::Tls12ClientSessionValue, - ); - - /// Get the most recently saved TLS1.2 session for `server_name` provided to `set_tls12_session`. - fn tls12_session( - &self, - server_name: &ServerName<'_>, - ) -> Option; - - /// Remove and forget any saved TLS1.2 session for `server_name`. - fn remove_tls12_session(&self, server_name: &ServerName<'static>); - - /// Remember a TLS1.3 ticket that might be retrieved later from `take_tls13_ticket`, allowing - /// resumption of this session. - /// - /// This can be called multiple times for a given session, allowing multiple independent tickets - /// to be valid at once. The number of times this is called is controlled by the server, so - /// implementations of this trait should apply a reasonable bound of how many items are stored - /// simultaneously. - fn insert_tls13_ticket( - &self, - server_name: ServerName<'static>, - value: persist::Tls13ClientSessionValue, - ); - - /// Return a TLS1.3 ticket previously provided to `add_tls13_ticket`. - /// - /// Implementations of this trait must return each value provided to `add_tls13_ticket` _at most once_. - fn take_tls13_ticket( - &self, - server_name: &ServerName<'static>, - ) -> Option; -} - -/// A trait for the ability to choose a certificate chain and -/// private key for the purposes of client authentication. -pub trait ResolvesClientCert: fmt::Debug + Send + Sync { - /// Resolve a client certificate chain/private key to use as the client's - /// identity. - /// - /// `root_hint_subjects` is an optional list of certificate authority - /// subject distinguished names that the client can use to help - /// decide on a client certificate the server is likely to accept. If - /// the list is empty, the client should send whatever certificate it - /// has. The hints are expected to be DER-encoded X.500 distinguished names, - /// per [RFC 5280 A.1]. See [`DistinguishedName`] for more information - /// on decoding with external crates like `x509-parser`. - /// - /// `sigschemes` is the list of the [`SignatureScheme`]s the server - /// supports. - /// - /// Return `None` to continue the handshake without any client - /// authentication. The server may reject the handshake later - /// if it requires authentication. - /// - /// [RFC 5280 A.1]: https://www.rfc-editor.org/rfc/rfc5280#appendix-A.1 - fn resolve( - &self, - root_hint_subjects: &[&[u8]], - sigschemes: &[SignatureScheme], - ) -> Option>; - - /// Return true if the client only supports raw public keys. - /// - /// See [RFC 7250](https://www.rfc-editor.org/rfc/rfc7250). - fn only_raw_public_keys(&self) -> bool { - false - } - - /// Return true if any certificates at all are available. - fn has_certs(&self) -> bool; -} - -/// Common configuration for (typically) all connections made by a program. -/// -/// Making one of these is cheap, though one of the inputs may be expensive: gathering trust roots -/// from the operating system to add to the [`RootCertStore`] passed to `with_root_certificates()` -/// (the rustls-native-certs crate is often used for this) may take on the order of a few hundred -/// milliseconds. -/// -/// These must be created via the [`ClientConfig::builder()`] or [`ClientConfig::builder_with_provider()`] -/// function. -/// -/// Note that using [`ConfigBuilder::with_ech()`] will produce a common -/// configuration specific to the provided [`crate::client::EchConfig`] that may not be appropriate -/// for all connections made by the program. In this case the configuration should only be shared -/// by connections intended for domains that offer the provided [`crate::client::EchConfig`] in -/// their DNS zone. -/// -/// # Defaults -/// -/// * [`ClientConfig::max_fragment_size`]: the default is `None` (meaning 16kB). -/// * [`ClientConfig::resumption`]: supports resumption with up to 256 server names, using session -/// ids or tickets, with a max of eight tickets per server. -/// * [`ClientConfig::alpn_protocols`]: the default is empty -- no ALPN protocol is negotiated. -/// * [`ClientConfig::key_log`]: key material is not logged. -/// * [`ClientConfig::cert_decompressors`]: depends on the crate features, see [`compress::default_cert_decompressors()`]. -/// * [`ClientConfig::cert_compressors`]: depends on the crate features, see [`compress::default_cert_compressors()`]. -/// * [`ClientConfig::cert_compression_cache`]: caches the most recently used 4 compressions -/// -/// [`RootCertStore`]: crate::RootCertStore -#[derive(Clone, Debug)] -pub struct ClientConfig { - /// Which ALPN protocols we include in our client hello. - /// If empty, no ALPN extension is sent. - pub alpn_protocols: Vec>, - - /// How and when the client can resume a previous session. - pub resumption: Resumption, - - /// The maximum size of plaintext input to be emitted in a single TLS record. - /// A value of None is equivalent to the [TLS maximum] of 16 kB. - /// - /// rustls enforces an arbitrary minimum of 32 bytes for this field. - /// Out of range values are reported as errors from [ClientConnection::new]. - /// - /// Setting this value to a little less than the TCP MSS may improve latency - /// for stream-y workloads. - /// - /// [TLS maximum]: https://datatracker.ietf.org/doc/html/rfc8446#section-5.1 - /// [ClientConnection::new]: crate::client::ClientConnection::new - pub max_fragment_size: Option, - - /// How to decide what client auth certificate/keys to use. - pub client_auth_cert_resolver: Arc, - - /// Whether to send the Server Name Indication (SNI) extension - /// during the client handshake. - /// - /// The default is true. - pub enable_sni: bool, - - /// How to output key material for debugging. The default - /// does nothing. - pub key_log: Arc, - - /// Allows traffic secrets to be extracted after the handshake, - /// e.g. for kTLS setup. - pub enable_secret_extraction: bool, - - /// Whether to send data on the first flight ("early data") in - /// TLS 1.3 handshakes. - /// - /// The default is false. - pub enable_early_data: bool, - - /// If set to `true`, requires the server to support the extended - /// master secret extraction method defined in [RFC 7627]. - /// - /// The default is `true` if the `fips` crate feature is enabled, - /// `false` otherwise. - /// - /// It must be set to `true` to meet FIPS requirement mentioned in section - /// **D.Q Transition of the TLS 1.2 KDF to Support the Extended Master - /// Secret** from [FIPS 140-3 IG.pdf]. - /// - /// [RFC 7627]: https://datatracker.ietf.org/doc/html/rfc7627 - /// [FIPS 140-3 IG.pdf]: https://csrc.nist.gov/csrc/media/Projects/cryptographic-module-validation-program/documents/fips%20140-3/FIPS%20140-3%20IG.pdf - #[cfg(feature = "tls12")] - pub require_ems: bool, - - /// Provides the current system time - pub time_provider: Arc, - - /// Source of randomness and other crypto. - pub(super) provider: Arc, - - /// Supported versions, in no particular order. The default - /// is all supported versions. - pub(super) versions: versions::EnabledVersions, - - /// How to verify the server certificate chain. - pub(super) verifier: Arc, - - /// How to decompress the server's certificate chain. - /// - /// If this is non-empty, the [RFC8779] certificate compression - /// extension is offered, and any compressed certificates are - /// transparently decompressed during the handshake. - /// - /// This only applies to TLS1.3 connections. It is ignored for - /// TLS1.2 connections. - /// - /// [RFC8779]: https://datatracker.ietf.org/doc/rfc8879/ - pub cert_decompressors: Vec<&'static dyn compress::CertDecompressor>, - - /// How to compress the client's certificate chain. - /// - /// If a server supports this extension, and advertises support - /// for one of the compression algorithms included here, the - /// client certificate will be compressed according to [RFC8779]. - /// - /// This only applies to TLS1.3 connections. It is ignored for - /// TLS1.2 connections. - /// - /// [RFC8779]: https://datatracker.ietf.org/doc/rfc8879/ - pub cert_compressors: Vec<&'static dyn compress::CertCompressor>, - - /// Caching for compressed certificates. - /// - /// This is optional: [`compress::CompressionCache::Disabled`] gives - /// a cache that does no caching. - pub cert_compression_cache: Arc, - - /// How to offer Encrypted Client Hello (ECH). The default is to not offer ECH. - pub(super) ech_mode: Option, -} - -impl ClientConfig { - /// Create a builder for a client configuration with - /// [the process-default `CryptoProvider`][CryptoProvider#using-the-per-process-default-cryptoprovider] - /// and safe protocol version defaults. - /// - /// For more information, see the [`ConfigBuilder`] documentation. - #[cfg(feature = "std")] - pub fn builder() -> ConfigBuilder { - Self::builder_with_protocol_versions(versions::DEFAULT_VERSIONS) - } - - /// Create a builder for a client configuration with - /// [the process-default `CryptoProvider`][CryptoProvider#using-the-per-process-default-cryptoprovider] - /// and the provided protocol versions. - /// - /// Panics if - /// - the supported versions are not compatible with the provider (eg. - /// the combination of ciphersuites supported by the provider and supported - /// versions lead to zero cipher suites being usable), - /// - if a `CryptoProvider` cannot be resolved using a combination of - /// the crate features and process default. - /// - /// For more information, see the [`ConfigBuilder`] documentation. - #[cfg(feature = "std")] - pub fn builder_with_protocol_versions( - versions: &[&'static versions::SupportedProtocolVersion], - ) -> ConfigBuilder { - // Safety assumptions: - // 1. that the provider has been installed (explicitly or implicitly) - // 2. that the process-level default provider is usable with the supplied protocol versions. - Self::builder_with_provider(Arc::clone( - CryptoProvider::get_default_or_install_from_crate_features(), - )) - .with_protocol_versions(versions) - .unwrap() - } - - /// Create a builder for a client configuration with a specific [`CryptoProvider`]. - /// - /// This will use the provider's configured ciphersuites. You must additionally choose - /// which protocol versions to enable, using `with_protocol_versions` or - /// `with_safe_default_protocol_versions` and handling the `Result` in case a protocol - /// version is not supported by the provider's ciphersuites. - /// - /// For more information, see the [`ConfigBuilder`] documentation. - #[cfg(feature = "std")] - pub fn builder_with_provider( - provider: Arc, - ) -> ConfigBuilder { - ConfigBuilder { - state: WantsVersions {}, - provider, - time_provider: Arc::new(DefaultTimeProvider), - side: PhantomData, - } - } - /// Create a builder for a client configuration with no default implementation details. - /// - /// This API must be used by `no_std` users. - /// - /// You must provide a specific [`TimeProvider`]. - /// - /// You must provide a specific [`CryptoProvider`]. - /// - /// This will use the provider's configured ciphersuites. You must additionally choose - /// which protocol versions to enable, using `with_protocol_versions` or - /// `with_safe_default_protocol_versions` and handling the `Result` in case a protocol - /// version is not supported by the provider's ciphersuites. - /// - /// For more information, see the [`ConfigBuilder`] documentation. - pub fn builder_with_details( - provider: Arc, - time_provider: Arc, - ) -> ConfigBuilder { - ConfigBuilder { - state: WantsVersions {}, - provider, - time_provider, - side: PhantomData, - } - } - - /// Return true if connections made with this `ClientConfig` will - /// operate in FIPS mode. - /// - /// This is different from [`CryptoProvider::fips()`]: [`CryptoProvider::fips()`] - /// is concerned only with cryptography, whereas this _also_ covers TLS-level - /// configuration that NIST recommends, as well as ECH HPKE suites if applicable. - pub fn fips(&self) -> bool { - let mut is_fips = self.provider.fips(); - - #[cfg(feature = "tls12")] - { - is_fips = is_fips && self.require_ems - } - - if let Some(ech_mode) = &self.ech_mode { - is_fips = is_fips && ech_mode.fips(); - } - - is_fips - } - - /// Return the crypto provider used to construct this client configuration. - pub fn crypto_provider(&self) -> &Arc { - &self.provider - } - - /// Access configuration options whose use is dangerous and requires - /// extra care. - pub fn dangerous(&mut self) -> danger::DangerousClientConfig<'_> { - danger::DangerousClientConfig { cfg: self } - } - - /// We support a given TLS version if it's quoted in the configured - /// versions *and* at least one ciphersuite for this version is - /// also configured. - pub(crate) fn supports_version(&self, v: ProtocolVersion) -> bool { - self.versions.contains(v) - && self - .provider - .cipher_suites - .iter() - .any(|cs| cs.version().version == v) - } - - #[cfg(feature = "std")] - pub(crate) fn supports_protocol(&self, proto: Protocol) -> bool { - self.provider - .cipher_suites - .iter() - .any(|cs| cs.usable_for_protocol(proto)) - } - - pub(super) fn find_cipher_suite(&self, suite: CipherSuite) -> Option { - self.provider - .cipher_suites - .iter() - .copied() - .find(|&scs| scs.suite() == suite) - } - - pub(super) fn find_kx_group( - &self, - group: NamedGroup, - version: ProtocolVersion, - ) -> Option<&'static dyn SupportedKxGroup> { - self.provider - .kx_groups - .iter() - .copied() - .find(|skxg| skxg.usable_for_version(version) && skxg.name() == group) - } - - pub(super) fn current_time(&self) -> Result { - self.time_provider - .current_time() - .ok_or(Error::FailedToGetCurrentTime) - } -} - -/// Configuration for how/when a client is allowed to resume a previous session. -#[derive(Clone, Debug)] -pub struct Resumption { - /// How we store session data or tickets. The default is to use an in-memory - /// [super::handy::ClientSessionMemoryCache]. - pub(super) store: Arc, - - /// What mechanism is used for resuming a TLS 1.2 session. - pub(super) tls12_resumption: Tls12Resumption, -} - -impl Resumption { - /// Create a new `Resumption` that stores data for the given number of sessions in memory. - /// - /// This is the default `Resumption` choice, and enables resuming a TLS 1.2 session with - /// a session id or RFC 5077 ticket. - #[cfg(feature = "std")] - pub fn in_memory_sessions(num: usize) -> Self { - Self { - store: Arc::new(super::handy::ClientSessionMemoryCache::new(num)), - tls12_resumption: Tls12Resumption::SessionIdOrTickets, - } - } - - /// Use a custom [`ClientSessionStore`] implementation to store sessions. - /// - /// By default, enables resuming a TLS 1.2 session with a session id or RFC 5077 ticket. - pub fn store(store: Arc) -> Self { - Self { - store, - tls12_resumption: Tls12Resumption::SessionIdOrTickets, - } - } - - /// Disable all use of session resumption. - pub fn disabled() -> Self { - Self { - store: Arc::new(NoClientSessionStorage), - tls12_resumption: Tls12Resumption::Disabled, - } - } - - /// Configure whether TLS 1.2 sessions may be resumed, and by what mechanism. - /// - /// This is meaningless if you've disabled resumption entirely, which is the case in `no-std` - /// contexts. - pub fn tls12_resumption(mut self, tls12: Tls12Resumption) -> Self { - self.tls12_resumption = tls12; - self - } -} - -impl Default for Resumption { - /// Create an in-memory session store resumption with up to 256 server names, allowing - /// a TLS 1.2 session to resume with a session id or RFC 5077 ticket. - fn default() -> Self { - #[cfg(feature = "std")] - let ret = Self::in_memory_sessions(256); - - #[cfg(not(feature = "std"))] - let ret = Self::disabled(); - - ret - } -} - -/// What mechanisms to support for resuming a TLS 1.2 session. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Tls12Resumption { - /// Disable 1.2 resumption. - Disabled, - /// Support 1.2 resumption using session ids only. - SessionIdOnly, - /// Support 1.2 resumption using session ids or RFC 5077 tickets. - /// - /// See[^1] for why you might like to disable RFC 5077 by instead choosing the `SessionIdOnly` - /// option. Note that TLS 1.3 tickets do not have those issues. - /// - /// [^1]: - SessionIdOrTickets, -} - -/// Container for unsafe APIs -pub(super) mod danger { - use alloc::sync::Arc; - - use super::verify::ServerCertVerifier; - use super::ClientConfig; - - /// Accessor for dangerous configuration options. - #[derive(Debug)] - pub struct DangerousClientConfig<'a> { - /// The underlying ClientConfig - pub cfg: &'a mut ClientConfig, - } - - impl DangerousClientConfig<'_> { - /// Overrides the default `ServerCertVerifier` with something else. - pub fn set_certificate_verifier(&mut self, verifier: Arc) { - self.cfg.verifier = verifier; - } - } -} - -#[derive(Debug, PartialEq)] -enum EarlyDataState { - Disabled, - Ready, - Accepted, - AcceptedFinished, - Rejected, -} - -#[derive(Debug)] -pub(super) struct EarlyData { - state: EarlyDataState, - left: usize, -} - -impl EarlyData { - fn new() -> Self { - Self { - left: 0, - state: EarlyDataState::Disabled, - } - } - - pub(super) fn is_enabled(&self) -> bool { - matches!(self.state, EarlyDataState::Ready | EarlyDataState::Accepted) - } - - #[cfg(feature = "std")] - fn is_accepted(&self) -> bool { - matches!( - self.state, - EarlyDataState::Accepted | EarlyDataState::AcceptedFinished - ) - } - - pub(super) fn enable(&mut self, max_data: usize) { - assert_eq!(self.state, EarlyDataState::Disabled); - self.state = EarlyDataState::Ready; - self.left = max_data; - } - - pub(super) fn rejected(&mut self) { - trace!("EarlyData rejected"); - self.state = EarlyDataState::Rejected; - } - - pub(super) fn accepted(&mut self) { - trace!("EarlyData accepted"); - assert_eq!(self.state, EarlyDataState::Ready); - self.state = EarlyDataState::Accepted; - } - - pub(super) fn finished(&mut self) { - trace!("EarlyData finished"); - self.state = match self.state { - EarlyDataState::Accepted => EarlyDataState::AcceptedFinished, - _ => panic!("bad EarlyData state"), - } - } - - fn check_write_opt(&mut self, sz: usize) -> Option { - match self.state { - EarlyDataState::Disabled => unreachable!(), - EarlyDataState::Ready | EarlyDataState::Accepted => { - let take = if self.left < sz { - mem::replace(&mut self.left, 0) - } else { - self.left -= sz; - sz - }; - - Some(take) - } - EarlyDataState::Rejected | EarlyDataState::AcceptedFinished => None, - } - } -} - -#[cfg(feature = "std")] -mod connection { - use alloc::sync::Arc; - use alloc::vec::Vec; - use core::fmt; - use core::ops::{Deref, DerefMut}; - use std::io; - - use pki_types::ServerName; - - use super::ClientConnectionData; - use crate::client::EchStatus; - use crate::common_state::Protocol; - use crate::conn::{ConnectionCommon, ConnectionCore}; - use crate::error::Error; - use crate::suites::ExtractedSecrets; - use crate::ClientConfig; - - /// Stub that implements io::Write and dispatches to `write_early_data`. - pub struct WriteEarlyData<'a> { - sess: &'a mut ClientConnection, - } - - impl<'a> WriteEarlyData<'a> { - fn new(sess: &'a mut ClientConnection) -> Self { - WriteEarlyData { sess } - } - - /// How many bytes you may send. Writes will become short - /// once this reaches zero. - pub fn bytes_left(&self) -> usize { - self.sess - .inner - .core - .data - .early_data - .bytes_left() - } - } - - impl io::Write for WriteEarlyData<'_> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.sess.write_early_data(buf) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } - } - - impl super::EarlyData { - fn check_write(&mut self, sz: usize) -> io::Result { - self.check_write_opt(sz) - .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput)) - } - - fn bytes_left(&self) -> usize { - self.left - } - } - - /// This represents a single TLS client connection. - pub struct ClientConnection { - inner: ConnectionCommon, - } - - impl fmt::Debug for ClientConnection { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ClientConnection") - .finish() - } - } - - impl ClientConnection { - /// Make a new ClientConnection. `config` controls how - /// we behave in the TLS protocol, `name` is the - /// name of the server we want to talk to. - pub fn new(config: Arc, name: ServerName<'static>) -> Result { - Ok(Self { - inner: ConnectionCore::for_client(config, name, Vec::new(), Protocol::Tcp)?.into(), - }) - } - - /// Returns an `io::Write` implementer you can write bytes to - /// to send TLS1.3 early data (a.k.a. "0-RTT data") to the server. - /// - /// This returns None in many circumstances when the capability to - /// send early data is not available, including but not limited to: - /// - /// - The server hasn't been talked to previously. - /// - The server does not support resumption. - /// - The server does not support early data. - /// - The resumption data for the server has expired. - /// - /// The server specifies a maximum amount of early data. You can - /// learn this limit through the returned object, and writes through - /// it will process only this many bytes. - /// - /// The server can choose not to accept any sent early data -- - /// in this case the data is lost but the connection continues. You - /// can tell this happened using `is_early_data_accepted`. - pub fn early_data(&mut self) -> Option> { - if self - .inner - .core - .data - .early_data - .is_enabled() - { - Some(WriteEarlyData::new(self)) - } else { - None - } - } - - /// Returns True if the server signalled it will process early data. - /// - /// If you sent early data and this returns false at the end of the - /// handshake then the server will not process the data. This - /// is not an error, but you may wish to resend the data. - pub fn is_early_data_accepted(&self) -> bool { - self.inner.core.is_early_data_accepted() - } - - /// Extract secrets, so they can be used when configuring kTLS, for example. - /// Should be used with care as it exposes secret key material. - pub fn dangerous_extract_secrets(self) -> Result { - self.inner.dangerous_extract_secrets() - } - - /// Return the connection's Encrypted Client Hello (ECH) status. - pub fn ech_status(&self) -> EchStatus { - self.inner.core.data.ech_status - } - - /// Return true if the connection was made with a `ClientConfig` that is FIPS compatible. - /// - /// This is different from [`crate::crypto::CryptoProvider::fips()`]: - /// it is concerned only with cryptography, whereas this _also_ covers TLS-level - /// configuration that NIST recommends, as well as ECH HPKE suites if applicable. - pub fn fips(&self) -> bool { - self.inner.core.common_state.fips - } - - fn write_early_data(&mut self, data: &[u8]) -> io::Result { - self.inner - .core - .data - .early_data - .check_write(data.len()) - .map(|sz| { - self.inner - .send_early_plaintext(&data[..sz]) - }) - } - } - - impl Deref for ClientConnection { - type Target = ConnectionCommon; - - fn deref(&self) -> &Self::Target { - &self.inner - } - } - - impl DerefMut for ClientConnection { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } - } - - #[doc(hidden)] - impl<'a> TryFrom<&'a mut crate::Connection> for &'a mut ClientConnection { - type Error = (); - - fn try_from(value: &'a mut crate::Connection) -> Result { - use crate::Connection::*; - match value { - Client(conn) => Ok(conn), - Server(_) => Err(()), - } - } - } - - impl From for crate::Connection { - fn from(conn: ClientConnection) -> Self { - Self::Client(conn) - } - } -} -#[cfg(feature = "std")] -pub use connection::{ClientConnection, WriteEarlyData}; - -impl ConnectionCore { - pub(crate) fn for_client( - config: Arc, - name: ServerName<'static>, - extra_exts: Vec, - proto: Protocol, - ) -> Result { - let mut common_state = CommonState::new(Side::Client); - common_state.set_max_fragment_size(config.max_fragment_size)?; - common_state.protocol = proto; - common_state.enable_secret_extraction = config.enable_secret_extraction; - common_state.fips = config.fips(); - let mut data = ClientConnectionData::new(); - - let mut cx = hs::ClientContext { - common: &mut common_state, - data: &mut data, - // `start_handshake` won't produce plaintext - sendable_plaintext: None, - }; - - let state = hs::start_handshake(name, extra_exts, config, &mut cx)?; - Ok(Self::new(state, data, common_state)) - } - - #[cfg(feature = "std")] - pub(crate) fn is_early_data_accepted(&self) -> bool { - self.data.early_data.is_accepted() - } -} - -/// Unbuffered version of `ClientConnection` -/// -/// See the [`crate::unbuffered`] module docs for more details -pub struct UnbufferedClientConnection { - inner: UnbufferedConnectionCommon, -} - -impl UnbufferedClientConnection { - /// Make a new ClientConnection. `config` controls how we behave in the TLS protocol, `name` is - /// the name of the server we want to talk to. - pub fn new(config: Arc, name: ServerName<'static>) -> Result { - Ok(Self { - inner: ConnectionCore::for_client(config, name, Vec::new(), Protocol::Tcp)?.into(), - }) - } -} - -impl Deref for UnbufferedClientConnection { - type Target = UnbufferedConnectionCommon; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl DerefMut for UnbufferedClientConnection { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - -impl TransmitTlsData<'_, ClientConnectionData> { - /// returns an adapter that allows encrypting early (RTT-0) data before transmitting the - /// already encoded TLS data - /// - /// IF allowed by the protocol - pub fn may_encrypt_early_data(&mut self) -> Option> { - if self - .conn - .core - .data - .early_data - .is_enabled() - { - Some(MayEncryptEarlyData { conn: self.conn }) - } else { - None - } - } -} - -/// Allows encrypting early (RTT-0) data -pub struct MayEncryptEarlyData<'c> { - conn: &'c mut UnbufferedConnectionCommon, -} - -impl MayEncryptEarlyData<'_> { - /// Encrypts `application_data` into the `outgoing_tls` buffer - /// - /// returns the number of bytes that were written into `outgoing_tls`, or an error if - /// the provided buffer was too small. In the error case, `outgoing_tls` is not modified - pub fn encrypt( - &mut self, - early_data: &[u8], - outgoing_tls: &mut [u8], - ) -> Result { - let Some(allowed) = self - .conn - .core - .data - .early_data - .check_write_opt(early_data.len()) - else { - return Err(EarlyDataError::ExceededAllowedEarlyData); - }; - - self.conn - .core - .common_state - .write_plaintext(early_data[..allowed].into(), outgoing_tls) - .map_err(|e| e.into()) - } -} - -/// Errors that may arise when encrypting early (RTT-0) data -#[derive(Debug)] -pub enum EarlyDataError { - /// Cannot encrypt more early data due to imposed limits - ExceededAllowedEarlyData, - /// Encryption error - Encrypt(EncryptError), -} - -impl From for EarlyDataError { - fn from(v: EncryptError) -> Self { - Self::Encrypt(v) - } -} - -impl fmt::Display for EarlyDataError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ExceededAllowedEarlyData => f.write_str("cannot send any more early data"), - Self::Encrypt(e) => fmt::Display::fmt(e, f), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for EarlyDataError {} - -/// State associated with a client connection. -#[derive(Debug)] -pub struct ClientConnectionData { - pub(super) early_data: EarlyData, - pub(super) resumption_ciphersuite: Option, - pub(super) ech_status: EchStatus, -} - -impl ClientConnectionData { - fn new() -> Self { - Self { - early_data: EarlyData::new(), - resumption_ciphersuite: None, - ech_status: EchStatus::NotOffered, - } - } -} - -impl crate::conn::SideData for ClientConnectionData {} diff --git a/rustls/src/client/common.rs b/rustls/src/client/common.rs deleted file mode 100644 index ad5e82b0f70..00000000000 --- a/rustls/src/client/common.rs +++ /dev/null @@ -1,111 +0,0 @@ -use alloc::boxed::Box; -use alloc::sync::Arc; -use alloc::vec::Vec; - -use super::ResolvesClientCert; -use crate::log::{debug, trace}; -use crate::msgs::enums::ExtensionType; -use crate::msgs::handshake::{CertificateChain, DistinguishedName, ServerExtension}; -use crate::{compress, sign, SignatureScheme}; - -#[derive(Debug)] -pub(super) struct ServerCertDetails<'a> { - pub(super) cert_chain: CertificateChain<'a>, - pub(super) ocsp_response: Vec, -} - -impl<'a> ServerCertDetails<'a> { - pub(super) fn new(cert_chain: CertificateChain<'a>, ocsp_response: Vec) -> Self { - Self { - cert_chain, - ocsp_response, - } - } - - pub(super) fn into_owned(self) -> ServerCertDetails<'static> { - let Self { - cert_chain, - ocsp_response, - } = self; - ServerCertDetails { - cert_chain: cert_chain.into_owned(), - ocsp_response, - } - } -} - -pub(super) struct ClientHelloDetails { - pub(super) sent_extensions: Vec, - pub(super) extension_order_seed: u16, - pub(super) offered_cert_compression: bool, -} - -impl ClientHelloDetails { - pub(super) fn new(extension_order_seed: u16) -> Self { - Self { - sent_extensions: Vec::new(), - extension_order_seed, - offered_cert_compression: false, - } - } - - pub(super) fn server_sent_unsolicited_extensions( - &self, - received_exts: &[ServerExtension], - allowed_unsolicited: &[ExtensionType], - ) -> bool { - for ext in received_exts { - let ext_type = ext.ext_type(); - if !self.sent_extensions.contains(&ext_type) && !allowed_unsolicited.contains(&ext_type) - { - trace!("Unsolicited extension {:?}", ext_type); - return true; - } - } - - false - } -} - -pub(super) enum ClientAuthDetails { - /// Send an empty `Certificate` and no `CertificateVerify`. - Empty { auth_context_tls13: Option> }, - /// Send a non-empty `Certificate` and a `CertificateVerify`. - Verify { - certkey: Arc, - signer: Box, - auth_context_tls13: Option>, - compressor: Option<&'static dyn compress::CertCompressor>, - }, -} - -impl ClientAuthDetails { - pub(super) fn resolve( - resolver: &dyn ResolvesClientCert, - canames: Option<&[DistinguishedName]>, - sigschemes: &[SignatureScheme], - auth_context_tls13: Option>, - compressor: Option<&'static dyn compress::CertCompressor>, - ) -> Self { - let acceptable_issuers = canames - .unwrap_or_default() - .iter() - .map(|p| p.as_ref()) - .collect::>(); - - if let Some(certkey) = resolver.resolve(&acceptable_issuers, sigschemes) { - if let Some(signer) = certkey.key.choose_scheme(sigschemes) { - debug!("Attempting client auth"); - return Self::Verify { - certkey, - signer, - auth_context_tls13, - compressor, - }; - } - } - - debug!("Client auth requested but no cert/sigscheme available"); - Self::Empty { auth_context_tls13 } - } -} diff --git a/rustls/src/client/config.rs b/rustls/src/client/config.rs new file mode 100644 index 00000000000..1659020e828 --- /dev/null +++ b/rustls/src/client/config.rs @@ -0,0 +1,808 @@ +use alloc::vec::Vec; +use core::any::Any; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::marker::PhantomData; + +#[cfg(feature = "webpki")] +use pki_types::PrivateKeyDer; +use pki_types::{FipsStatus, ServerName, UnixTime}; + +use super::ech::EchMode; +use super::handy::{ClientSessionMemoryCache, FailResolveClientCert, NoClientSessionStorage}; +use super::{Tls12Session, Tls13Session}; +use crate::builder::{ConfigBuilder, WantsVerifier}; +use crate::client::connection::ClientConnectionBuilder; +#[cfg(doc)] +use crate::crypto; +use crate::crypto::kx::NamedGroup; +use crate::crypto::{CipherSuite, CryptoProvider, SelectedCredential, SignatureScheme, hash}; +#[cfg(feature = "webpki")] +use crate::crypto::{Credentials, Identity, SingleCredential}; +use crate::enums::{ApplicationProtocol, CertificateType, ProtocolVersion}; +use crate::error::{ApiMisuse, Error}; +use crate::key_log::NoKeyLog; +use crate::suites::SupportedCipherSuite; +use crate::sync::Arc; +use crate::time_provider::{DefaultTimeProvider, TimeProvider}; +#[cfg(feature = "webpki")] +use crate::webpki::{self, WebPkiServerVerifier}; +use crate::{DistinguishedName, DynHasher, KeyLog, compress, verify}; + +/// Common configuration for (typically) all connections made by a program. +/// +/// Making one of these is cheap, though one of the inputs may be expensive: gathering trust roots +/// from the operating system to add to the [`RootCertStore`] passed to `with_root_certificates()` +/// (the rustls-native-certs crate is often used for this) may take on the order of a few hundred +/// milliseconds. +/// +/// These must be created via the [`ClientConfig::builder()`] or [`ClientConfig::builder()`] +/// function. +/// +/// Note that using [`ConfigBuilder::with_ech()`] will produce a common +/// configuration specific to the provided [`crate::client::EchConfig`] that may not be appropriate +/// for all connections made by the program. In this case the configuration should only be shared +/// by connections intended for domains that offer the provided [`crate::client::EchConfig`] in +/// their DNS zone. +/// +/// # Defaults +/// +/// * [`ClientConfig::max_fragment_size`]: the default is `None` (meaning 16kB). +/// * [`ClientConfig::resumption`]: supports resumption with up to 256 server names, using session +/// ids or tickets, with a max of eight tickets per server. +/// * [`ClientConfig::alpn_protocols`]: the default is empty -- no ALPN protocol is negotiated. +/// * [`ClientConfig::key_log`]: key material is not logged. +/// * [`ClientConfig::cert_decompressors`]: depends on the crate features, see [`compress::default_cert_decompressors()`]. +/// * [`ClientConfig::cert_compressors`]: depends on the crate features, see [`compress::default_cert_compressors()`]. +/// * [`ClientConfig::cert_compression_cache`]: caches the most recently used 4 compressions +/// +/// [`RootCertStore`]: crate::RootCertStore +#[derive(Clone, Debug)] +pub struct ClientConfig { + /// Which ALPN protocols we include in our client hello. + /// If empty, no ALPN extension is sent. + pub alpn_protocols: Vec>, + + /// Whether to check the selected ALPN was offered. + /// + /// The default is true. + pub check_selected_alpn: bool, + + /// How and when the client can resume a previous session. + /// + /// # Sharing `resumption` between `ClientConfig`s + /// In a program using many `ClientConfig`s it may improve resumption rates + /// (which has a significant impact on connection performance) if those + /// configs share a single `Resumption`. + /// + /// However, resumption is only allowed between two `ClientConfig`s if their + /// `client_auth_cert_resolver` (ie, potential client authentication credentials) + /// and `verifier` (ie, server certificate verification settings): + /// + /// - are the same type (determined by hashing their `TypeId`), and + /// - input the same data into [`ServerVerifier::hash_config()`] and + /// [`ClientCredentialResolver::hash_config()`]. + /// + /// To illustrate, imagine two `ClientConfig`s `A` and `B`. `A` fully validates + /// the server certificate, `B` does not. If `A` and `B` shared a resumption store, + /// it would be possible for a session originated by `B` to be inserted into the + /// store, and then resumed by `A`. This would give a false impression to the user + /// of `A` that the server certificate is fully validated. + /// + /// [`ServerVerifier::hash_config()`]: verify::ServerVerifier::hash_config() + pub resumption: Resumption, + + /// The maximum size of plaintext input to be emitted in a single TLS record. + /// A value of None is equivalent to the [TLS maximum] of 16 kB. + /// + /// rustls enforces an arbitrary minimum of 32 bytes for this field. + /// Out of range values are reported as errors when initializing a connection. + /// + /// Setting this value to a little less than the TCP MSS may improve latency + /// for stream-y workloads. + /// + /// [TLS maximum]: https://datatracker.ietf.org/doc/html/rfc8446#section-5.1 + pub max_fragment_size: Option, + + /// Whether to send the Server Name Indication (SNI) extension + /// during the client handshake. + /// + /// The default is true. + pub enable_sni: bool, + + /// How to output key material for debugging. The default + /// does nothing. + pub key_log: Arc, + + /// Allows traffic secrets to be extracted after the handshake, + /// e.g. for kTLS setup. + pub enable_secret_extraction: bool, + + /// Whether to send data on the first flight ("early data") in + /// TLS 1.3 handshakes. + /// + /// The default is false. + pub enable_early_data: bool, + + /// If set to `true`, requires the server to support the extended + /// master secret extraction method defined in [RFC 7627]. + /// + /// The default is `true` if the configured [`CryptoProvider`] is FIPS-compliant, + /// false otherwise. + /// + /// It must be set to `true` to meet FIPS requirement mentioned in section + /// **D.Q Transition of the TLS 1.2 KDF to Support the Extended Master + /// Secret** from [FIPS 140-3 IG.pdf]. + /// + /// [RFC 7627]: https://datatracker.ietf.org/doc/html/rfc7627 + /// [FIPS 140-3 IG.pdf]: https://csrc.nist.gov/csrc/media/Projects/cryptographic-module-validation-program/documents/fips%20140-3/FIPS%20140-3%20IG.pdf + pub require_ems: bool, + + /// Items that affect the fundamental security properties of a connection. + pub(super) domain: SecurityDomain, + + /// How to decompress the server's certificate chain. + /// + /// If this is non-empty, the [RFC8779] certificate compression + /// extension is offered, and any compressed certificates are + /// transparently decompressed during the handshake. + /// + /// This only applies to TLS1.3 connections. It is ignored for + /// TLS1.2 connections. + /// + /// [RFC8779]: https://datatracker.ietf.org/doc/rfc8879/ + pub cert_decompressors: Vec<&'static dyn compress::CertDecompressor>, + + /// How to compress the client's certificate chain. + /// + /// If a server supports this extension, and advertises support + /// for one of the compression algorithms included here, the + /// client certificate will be compressed according to [RFC8779]. + /// + /// This only applies to TLS1.3 connections. It is ignored for + /// TLS1.2 connections. + /// + /// [RFC8779]: https://datatracker.ietf.org/doc/rfc8879/ + pub cert_compressors: Vec<&'static dyn compress::CertCompressor>, + + /// Caching for compressed certificates. + /// + /// This is optional: [`compress::CompressionCache::Disabled`] gives + /// a cache that does no caching. + pub cert_compression_cache: Arc, + + /// How to offer Encrypted Client Hello (ECH). The default is to not offer ECH. + pub(super) ech_mode: Option, +} + +impl ClientConfig { + /// Create a builder for a client configuration with a specific [`CryptoProvider`]. + /// + /// This will use the provider's configured ciphersuites. + /// + /// For more information, see the [`ConfigBuilder`] documentation. + pub fn builder(provider: Arc) -> ConfigBuilder { + Self::builder_with_details(provider, Arc::new(DefaultTimeProvider)) + } + + /// Create a builder for a client configuration with no default implementation details. + /// + /// This API must be used by `no_std` users. + /// + /// You must provide a specific [`TimeProvider`]. + /// + /// You must provide a specific [`CryptoProvider`]. + /// + /// For more information, see the [`ConfigBuilder`] documentation. + pub fn builder_with_details( + provider: Arc, + time_provider: Arc, + ) -> ConfigBuilder { + ConfigBuilder { + state: WantsVerifier { + client_ech_mode: None, + }, + provider, + time_provider, + side: PhantomData, + } + } + + /// Create a new client connection builder for the given server name. + /// + /// The `ClientConfig` controls how the client behaves; + /// `name` is the name of server we want to talk to. + pub fn connect(self: &Arc, server_name: ServerName<'static>) -> ClientConnectionBuilder { + ClientConnectionBuilder { + config: self.clone(), + name: server_name, + alpn_protocols: None, + } + } + + /// Access configuration options whose use is dangerous and requires + /// extra care. + pub fn dangerous(&mut self) -> danger::DangerousClientConfig<'_> { + danger::DangerousClientConfig { cfg: self } + } + + /// Return the FIPS validation status for connections made with this configuration. + /// + /// This is different from [`CryptoProvider::fips()`]: [`CryptoProvider::fips()`] + /// is concerned only with cryptography, whereas this _also_ covers TLS-level + /// configuration that NIST recommends, as well as ECH HPKE suites if applicable. + pub fn fips(&self) -> FipsStatus { + if !self.require_ems { + return FipsStatus::Unvalidated; + } + + let status = self.domain.provider.fips(); + match &self.ech_mode { + Some(ech) => Ord::min(status, ech.fips()), + None => status, + } + } + + /// Return the crypto provider used to construct this client configuration. + pub fn provider(&self) -> &Arc { + &self.domain.provider + } + + /// Return the resolver for this client configuration. + /// + /// This is the object that determines which credentials to use for client + /// authentication. + pub fn resolver(&self) -> &Arc { + &self.domain.client_auth_cert_resolver + } + + /// Return the resolver for this client configuration. + /// + /// This is the object that determines which credentials to use for client + /// authentication. + pub fn verifier(&self) -> &Arc { + &self.domain.verifier + } + + pub(crate) fn supports_version(&self, v: ProtocolVersion) -> bool { + self.domain.provider.supports_version(v) + } + + pub(super) fn find_cipher_suite(&self, suite: CipherSuite) -> Option { + self.domain + .provider + .iter_cipher_suites() + .find(|&scs| scs.suite() == suite) + } + + pub(super) fn current_time(&self) -> Result { + self.domain + .time_provider + .current_time() + .ok_or(Error::FailedToGetCurrentTime) + } + + /// A hash which partitions this config's use of the [`Self::resumption`] store. + pub(super) fn config_hash(&self) -> [u8; 32] { + self.domain.config_hash + } +} + +struct HashAdapter<'a>(&'a mut dyn hash::Context); + +impl Hasher for HashAdapter<'_> { + fn finish(&self) -> u64 { + // SAFETY: this is private to `SecurityDomain::new`, which guarantees `hash::Output` + // is at least 32 bytes. + u64::from_be_bytes( + self.0.fork_finish().as_ref()[..8] + .try_into() + .unwrap(), + ) + } + + fn write(&mut self, bytes: &[u8]) { + self.0.update(bytes) + } +} + +/// Client session data store for possible future resumption. +/// +/// All data in this interface should be treated as **highly sensitive**, containing enough key +/// material to break all security of the corresponding session. +/// +/// `set_`, `insert_`, `remove_` and `take_` operations are mutating; this isn't +/// expressed in the type system to allow implementations freedom in +/// how to achieve interior mutability. `Mutex` is a common choice. +pub trait ClientSessionStore: fmt::Debug + Send + Sync { + /// Remember what `NamedGroup` the given server chose. + fn set_kx_hint(&self, key: ClientSessionKey<'static>, group: NamedGroup); + + /// Value most recently passed to `set_kx_hint` for the given `key`. + /// + /// If `None` is returned, the caller chooses the first configured group, and an extra round + /// trip might happen if that choice is unsatisfactory to the server. + fn kx_hint(&self, key: &ClientSessionKey<'_>) -> Option; + + /// Remember a TLS1.2 session, allowing resumption of this connection in the future. + /// + /// At most one of these per session key can be remembered at a time. + fn set_tls12_session(&self, key: ClientSessionKey<'static>, value: Tls12Session); + + /// Get the most recently saved TLS1.2 session for `key` provided to `set_tls12_session`. + fn tls12_session(&self, key: &ClientSessionKey<'_>) -> Option; + + /// Remove and forget any saved TLS1.2 session for `key`. + fn remove_tls12_session(&self, key: &ClientSessionKey<'static>); + + /// Remember a TLS1.3 ticket, allowing resumption of this connection in the future. + /// + /// This can be called multiple times for a given session, allowing multiple independent tickets + /// to be valid at once. The number of times this is called is controlled by the server, so + /// implementations of this trait should apply a reasonable bound of how many items are stored + /// simultaneously. + fn insert_tls13_ticket(&self, key: ClientSessionKey<'static>, value: Tls13Session); + + /// Return a TLS1.3 ticket previously provided to `insert_tls13_ticket()`. + /// + /// Implementations of this trait must return each value provided to `insert_tls13_ticket()` _at most once_. + fn take_tls13_ticket(&self, key: &ClientSessionKey<'static>) -> Option; +} + +/// Identifies a security context and server in the [`ClientSessionStore`] interface. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[non_exhaustive] +pub struct ClientSessionKey<'a> { + /// A hash to partition the client storage between different security domains. + pub config_hash: [u8; 32], + + /// Transport-level identity of the server. + pub server_name: ServerName<'a>, +} + +impl ClientSessionKey<'_> { + /// Copy the value to own its contents. + pub fn to_owned(&self) -> ClientSessionKey<'static> { + let Self { + config_hash, + server_name, + } = self; + ClientSessionKey { + config_hash: *config_hash, + server_name: server_name.to_owned(), + } + } +} + +/// A trait for the ability to choose a certificate chain and +/// private key for the purposes of client authentication. +pub trait ClientCredentialResolver: fmt::Debug + Send + Sync { + /// Resolve a client certificate chain/private key to use as the client's identity. + /// + /// The `SelectedCredential` returned from this method contains an identity and a + /// one-time-use [`Signer`] wrapping the private key. This is usually obtained via a + /// [`Credentials`], on which an implementation can call [`Credentials::signer()`]. + /// An implementation can either store long-lived [`Credentials`] values, or instantiate + /// them as needed using one of its constructors. + /// + /// Return `None` to continue the handshake without any client + /// authentication. The server may reject the handshake later + /// if it requires authentication. + /// + /// [RFC 5280 A.1]: https://www.rfc-editor.org/rfc/rfc5280#appendix-A.1 + /// + /// [`Credentials`]: crate::crypto::Credentials + /// [`Credentials::signer()`]: crate::crypto::Credentials::signer + /// [`Signer`]: crate::crypto::Signer + fn resolve(&self, request: &CredentialRequest<'_>) -> Option; + + /// Returns which [`CertificateType`]s this resolver supports. + /// + /// Should return the empty slice if the resolver does not have any credentials to send. + /// Implementations should return the same value every time. + /// + /// See [RFC 7250](https://tools.ietf.org/html/rfc7250) for more information. + fn supported_certificate_types(&self) -> &'static [CertificateType]; + + /// Instance configuration should be input to `h`. + fn hash_config(&self, h: &mut dyn Hasher); +} + +/// Context from the server to inform client credential selection. +pub struct CredentialRequest<'a> { + pub(super) negotiated_type: CertificateType, + pub(super) root_hint_subjects: &'a [DistinguishedName], + pub(super) signature_schemes: &'a [SignatureScheme], +} + +impl CredentialRequest<'_> { + /// List of certificate authority subject distinguished names provided by the server. + /// + /// If the list is empty, the client should send whatever certificate it has. The hints + /// are expected to be DER-encoded X.500 distinguished names, per [RFC 5280 A.1]. Note that + /// the encoding comes from the server and has not been validated by rustls. + /// + /// See [`DistinguishedName`] for more information on decoding with external crates like + /// `x509-parser`. + /// + /// [`DistinguishedName`]: crate::DistinguishedName + pub fn root_hint_subjects(&self) -> &[DistinguishedName] { + self.root_hint_subjects + } + + /// Get the compatible signature schemes. + pub fn signature_schemes(&self) -> &[SignatureScheme] { + self.signature_schemes + } + + /// The negotiated certificate type. + /// + /// If the server does not support [RFC 7250], this will be `CertificateType::X509`. + /// + /// [RFC 7250]: https://tools.ietf.org/html/rfc7250 + pub fn negotiated_type(&self) -> CertificateType { + self.negotiated_type + } +} + +/// Items that affect the fundamental security properties of a connection. +/// +/// This is its own type because `config_hash` depends on the other fields: +/// fields therefore should not be mutated, but an entire object created +/// through [`Self::new`] for any edits. +#[derive(Clone, Debug)] +pub(super) struct SecurityDomain { + /// Provides the current system time + time_provider: Arc, + + /// Source of randomness and other crypto. + provider: Arc, + + /// How to verify the server certificate chain. + verifier: Arc, + + /// How to decide what client auth certificate/keys to use. + client_auth_cert_resolver: Arc, + + config_hash: [u8; 32], +} + +impl SecurityDomain { + pub(crate) fn new( + provider: Arc, + client_auth_cert_resolver: Arc, + verifier: Arc, + time_provider: Arc, + ) -> Self { + // Use a hash function that outputs at least 32 bytes. + let hash = provider + .iter_cipher_suites() + .map(|cs| cs.hash_provider()) + .find(|h| h.output_len() >= 32) + .expect("no suitable cipher suite available (with |H| >= 32)"); // this is -- in practice -- all cipher suites + + let mut h = hash.start(); + let mut adapter = HashAdapter(h.as_mut()); + + // Include TypeId of impl, so two different types with different non-configured + // behavior do not collide even if their `hash_config()`s are the same. + client_auth_cert_resolver + .type_id() + .hash(&mut DynHasher(&mut adapter)); + client_auth_cert_resolver.hash_config(&mut adapter); + + verifier + .type_id() + .hash(&mut DynHasher(&mut adapter)); + verifier.hash_config(&mut adapter); + + time_provider + .type_id() + .hash(&mut DynHasher(&mut adapter)); + + let config_hash = h.finish().as_ref()[..32] + .try_into() + .unwrap(); + + Self { + time_provider, + provider, + verifier, + client_auth_cert_resolver, + config_hash, + } + } + + fn with_verifier(&self, verifier: Arc) -> Self { + let Self { + time_provider, + provider, + verifier: _, + client_auth_cert_resolver, + config_hash: _, + } = self; + Self::new( + provider.clone(), + client_auth_cert_resolver.clone(), + verifier, + time_provider.clone(), + ) + } +} + +/// Configuration for how/when a client is allowed to resume a previous session. +#[derive(Clone, Debug)] +pub struct Resumption { + /// How we store session data or tickets. The default is to use an in-memory + /// [super::handy::ClientSessionMemoryCache]. + pub(super) store: Arc, + + /// What mechanism is used for resuming a TLS 1.2 session. + pub(super) tls12_resumption: Tls12Resumption, +} + +impl Resumption { + /// Create a new `Resumption` that stores data for the given number of sessions in memory. + /// + /// This is the default `Resumption` choice, and enables resuming a TLS 1.2 session with + /// a session id or RFC 5077 ticket. + pub fn in_memory_sessions(num: usize) -> Self { + Self { + store: Arc::new(ClientSessionMemoryCache::new(num)), + tls12_resumption: Tls12Resumption::SessionIdOrTickets, + } + } + + /// Use a custom [`ClientSessionStore`] implementation to store sessions. + /// + /// By default, enables resuming a TLS 1.2 session with a session id or RFC 5077 ticket. + pub fn store(store: Arc) -> Self { + Self { + store, + tls12_resumption: Tls12Resumption::SessionIdOrTickets, + } + } + + /// Disable all use of session resumption. + pub fn disabled() -> Self { + Self { + store: Arc::new(NoClientSessionStorage), + tls12_resumption: Tls12Resumption::Disabled, + } + } + + /// Configure whether TLS 1.2 sessions may be resumed, and by what mechanism. + /// + /// This is meaningless if you've disabled resumption entirely, which is the case in `no-std` + /// contexts. + pub fn tls12_resumption(mut self, tls12: Tls12Resumption) -> Self { + self.tls12_resumption = tls12; + self + } +} + +impl Default for Resumption { + /// Create an in-memory session store resumption with up to 256 server names, allowing + /// a TLS 1.2 session to resume with a session id or RFC 5077 ticket. + fn default() -> Self { + Self::in_memory_sessions(256) + } +} + +/// What mechanisms to support for resuming a TLS 1.2 session. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Tls12Resumption { + /// Disable 1.2 resumption. + Disabled, + /// Support 1.2 resumption using session ids only. + SessionIdOnly, + /// Support 1.2 resumption using session ids or RFC 5077 tickets. + /// + /// See[^1] for why you might like to disable RFC 5077 by instead choosing the `SessionIdOnly` + /// option. Note that TLS 1.3 tickets do not have those issues. + /// + /// [^1]: + SessionIdOrTickets, +} + +impl ConfigBuilder { + /// Choose how to verify server certificates. + /// + /// Using this function does not configure revocation. If you wish to + /// configure revocation, instead use: + /// + /// ```diff + /// - .with_root_certificates(root_store) + /// + .with_webpki_verifier( + /// + WebPkiServerVerifier::builder(root_store, crypto_provider) + /// + .with_crls(...) + /// + .build()? + /// + ) + /// ``` + #[cfg(feature = "webpki")] + pub fn with_root_certificates( + self, + root_store: impl Into>, + ) -> ConfigBuilder { + let algorithms = self + .provider + .signature_verification_algorithms; + self.with_webpki_verifier( + WebPkiServerVerifier::new_without_revocation(root_store, algorithms).into(), + ) + } + + /// Choose how to verify server certificates using a webpki verifier. + /// + /// See [`webpki::WebPkiServerVerifier::builder`] and + /// [`webpki::WebPkiServerVerifier::builder`] for more information. + #[cfg(feature = "webpki")] + pub fn with_webpki_verifier( + self, + verifier: Arc, + ) -> ConfigBuilder { + ConfigBuilder { + state: WantsClientCert { + verifier, + client_ech_mode: self.state.client_ech_mode, + }, + provider: self.provider, + time_provider: self.time_provider, + side: PhantomData, + } + } + + /// Enable Encrypted Client Hello (ECH) in the given mode. + /// + /// This requires TLS 1.3 as the only supported protocol version to meet the requirement + /// to support ECH. At the end, the config building process will return an error if either + /// TLS1.3 _is not_ supported by the provider, or TLS1.2 _is_ supported. + /// + /// The `ClientConfig` that will be produced by this builder will be specific to the provided + /// [`crate::client::EchConfig`] and may not be appropriate for all connections made by the program. + /// In this case the configuration should only be shared by connections intended for domains + /// that offer the provided [`crate::client::EchConfig`] in their DNS zone. + pub fn with_ech(mut self, mode: EchMode) -> Self { + self.state.client_ech_mode = Some(mode); + self + } + + /// Access configuration options whose use is dangerous and requires + /// extra care. + pub fn dangerous(self) -> danger::DangerousClientConfigBuilder { + danger::DangerousClientConfigBuilder { cfg: self } + } +} + +/// A config builder state where the caller needs to supply whether and how to provide a client +/// certificate. +/// +/// For more information, see the [`ConfigBuilder`] documentation. +#[derive(Clone)] +pub struct WantsClientCert { + verifier: Arc, + client_ech_mode: Option, +} + +impl ConfigBuilder { + /// Sets a single certificate chain and matching private key for use + /// in client authentication. + /// + /// `cert_chain` is a vector of DER-encoded certificates. + /// `key_der` is a DER-encoded private key as PKCS#1, PKCS#8, or SEC1. The + /// `aws-lc-rs` and `ring` [`CryptoProvider`]s support + /// all three encodings, but other `CryptoProviders` may not. + /// + /// This function fails if `key_der` is invalid. + #[cfg(feature = "webpki")] + pub fn with_client_auth_cert( + self, + identity: Arc>, + key_der: PrivateKeyDer<'static>, + ) -> Result { + let credentials = Credentials::from_der(identity, key_der, &self.provider)?; + self.with_client_credential_resolver(Arc::new(SingleCredential::from(credentials))) + } + + /// Do not support client auth. + pub fn with_no_client_auth(self) -> Result { + self.with_client_credential_resolver(Arc::new(FailResolveClientCert {})) + } + + /// Sets a custom [`ClientCredentialResolver`]. + pub fn with_client_credential_resolver( + self, + client_auth_cert_resolver: Arc, + ) -> Result { + self.provider.consistency_check()?; + + if self.state.client_ech_mode.is_some() { + match ( + self.provider + .tls12_cipher_suites + .is_empty(), + self.provider + .tls13_cipher_suites + .is_empty(), + ) { + (_, true) => return Err(ApiMisuse::EchRequiresTls13Support.into()), + (false, _) => return Err(ApiMisuse::EchForbidsTls12Support.into()), + (true, false) => {} + }; + } + + let require_ems = !matches!(self.provider.fips(), FipsStatus::Unvalidated); + Ok(ClientConfig { + alpn_protocols: Vec::new(), + check_selected_alpn: true, + resumption: Resumption::default(), + max_fragment_size: None, + enable_sni: true, + key_log: Arc::new(NoKeyLog {}), + enable_secret_extraction: false, + enable_early_data: false, + require_ems, + domain: SecurityDomain::new( + self.provider, + client_auth_cert_resolver, + self.state.verifier, + self.time_provider, + ), + cert_decompressors: compress::default_cert_decompressors().to_vec(), + cert_compressors: compress::default_cert_compressors().to_vec(), + cert_compression_cache: Arc::new(compress::CompressionCache::default()), + ech_mode: self.state.client_ech_mode, + }) + } +} + +/// Container for unsafe APIs +pub(super) mod danger { + use core::marker::PhantomData; + + use crate::client::WantsClientCert; + use crate::client::config::ClientConfig; + use crate::sync::Arc; + use crate::verify::ServerVerifier; + use crate::{ConfigBuilder, WantsVerifier}; + + /// Accessor for dangerous configuration options. + #[derive(Debug)] + pub struct DangerousClientConfig<'a> { + /// The underlying ClientConfig + pub(super) cfg: &'a mut ClientConfig, + } + + impl DangerousClientConfig<'_> { + /// Overrides the default `ServerVerifier` with something else. + pub fn set_certificate_verifier(&mut self, verifier: Arc) { + self.cfg.domain = self.cfg.domain.with_verifier(verifier); + } + } + + /// Accessor for dangerous configuration options. + #[derive(Debug)] + pub struct DangerousClientConfigBuilder { + /// The underlying ClientConfigBuilder + pub(super) cfg: ConfigBuilder, + } + + impl DangerousClientConfigBuilder { + /// Set a custom certificate verifier. + pub fn with_custom_certificate_verifier( + self, + verifier: Arc, + ) -> ConfigBuilder { + ConfigBuilder { + state: WantsClientCert { + verifier, + client_ech_mode: self.cfg.state.client_ech_mode, + }, + provider: self.cfg.provider, + time_provider: self.cfg.time_provider, + side: PhantomData, + } + } + } +} diff --git a/rustls/src/client/connection.rs b/rustls/src/client/connection.rs new file mode 100644 index 00000000000..db0cff9120e --- /dev/null +++ b/rustls/src/client/connection.rs @@ -0,0 +1,449 @@ +use alloc::vec::Vec; +use core::ops::Deref; +use core::{fmt, mem}; +use std::io; + +use pki_types::{FipsStatus, ServerName}; + +use super::config::ClientConfig; +use super::hs::ClientHelloInput; +use crate::client::EchStatus; +use crate::common_state::{CommonState, ConnectionOutputs, EarlyDataEvent, Event, Protocol, Side}; +use crate::conn::private::SideOutput; +use crate::conn::{ + Connection, ConnectionCommon, ConnectionCore, IoState, KeyingMaterialExporter, Reader, + SideCommonOutput, Writer, +}; +#[cfg(doc)] +use crate::crypto; +use crate::enums::ApplicationProtocol; +use crate::error::Error; +use crate::log::trace; +use crate::msgs::ClientExtensionsInput; +use crate::quic::QuicOutput; +use crate::suites::ExtractedSecrets; +use crate::sync::Arc; + +/// This represents a single TLS client connection. +pub struct ClientConnection { + inner: ConnectionCommon, +} + +impl fmt::Debug for ClientConnection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ClientConnection") + .finish_non_exhaustive() + } +} + +impl ClientConnection { + /// Returns an `io::Write` implementer you can write bytes to + /// to send TLS1.3 early data (a.k.a. "0-RTT data") to the server. + /// + /// This returns None in many circumstances when the capability to + /// send early data is not available, including but not limited to: + /// + /// - The server hasn't been talked to previously. + /// - The server does not support resumption. + /// - The server does not support early data. + /// - The resumption data for the server has expired. + /// + /// The server specifies a maximum amount of early data. You can + /// learn this limit through the returned object, and writes through + /// it will process only this many bytes. + /// + /// The server can choose not to accept any sent early data -- + /// in this case the data is lost but the connection continues. You + /// can tell this happened using `is_early_data_accepted`. + pub fn early_data(&mut self) -> Option> { + if self + .inner + .core + .side + .early_data + .is_enabled() + { + Some(WriteEarlyData::new(self)) + } else { + None + } + } + + /// Returns True if the server signalled it will process early data. + /// + /// If you sent early data and this returns false at the end of the + /// handshake then the server will not process the data. This + /// is not an error, but you may wish to resend the data. + pub fn is_early_data_accepted(&self) -> bool { + self.inner.core.is_early_data_accepted() + } + + /// Extract secrets, so they can be used when configuring kTLS, for example. + /// Should be used with care as it exposes secret key material. + pub fn dangerous_extract_secrets(self) -> Result { + self.inner.dangerous_extract_secrets() + } + + /// Return the connection's Encrypted Client Hello (ECH) status. + pub fn ech_status(&self) -> EchStatus { + self.inner.core.side.ech_status + } + + fn write_early_data(&mut self, data: &[u8]) -> io::Result { + self.inner + .core + .side + .early_data + .check_write(data.len()) + .map(|sz| { + self.inner + .send + .send_early_plaintext(&data[..sz]) + }) + } + + /// Returns the number of TLS1.3 tickets that have been received. + pub fn tls13_tickets_received(&self) -> u32 { + self.inner + .core + .common + .recv + .tls13_tickets_received + } +} + +impl Connection for ClientConnection { + fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result { + self.inner.read_tls(rd) + } + + fn write_tls(&mut self, wr: &mut dyn io::Write) -> Result { + self.inner.write_tls(wr) + } + + fn wants_read(&self) -> bool { + self.inner.wants_read() + } + + fn wants_write(&self) -> bool { + self.inner.wants_write() + } + + fn reader(&mut self) -> Reader<'_> { + self.inner.reader() + } + + fn writer(&mut self) -> Writer<'_> { + self.inner.writer() + } + + fn process_new_packets(&mut self) -> Result { + self.inner.process_new_packets() + } + + fn exporter(&mut self) -> Result { + self.inner.exporter() + } + + fn dangerous_extract_secrets(self) -> Result { + self.inner.dangerous_extract_secrets() + } + + fn set_buffer_limit(&mut self, limit: Option) { + self.inner.set_buffer_limit(limit) + } + + fn set_plaintext_buffer_limit(&mut self, limit: Option) { + self.inner + .set_plaintext_buffer_limit(limit) + } + + fn refresh_traffic_keys(&mut self) -> Result<(), Error> { + self.inner.refresh_traffic_keys() + } + + fn send_close_notify(&mut self) { + self.inner.send_close_notify(); + } + + fn is_handshaking(&self) -> bool { + self.inner.is_handshaking() + } + + fn fips(&self) -> FipsStatus { + self.inner.fips + } +} + +impl Deref for ClientConnection { + type Target = ConnectionOutputs; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// Builder for [`ClientConnection`] values. +/// +/// Create one with [`ClientConfig::connect()`]. +pub struct ClientConnectionBuilder { + pub(crate) config: Arc, + pub(crate) name: ServerName<'static>, + pub(crate) alpn_protocols: Option>>, +} + +impl ClientConnectionBuilder { + /// Specify the ALPN protocols to use for this connection. + pub fn with_alpn(mut self, alpn_protocols: Vec>) -> Self { + self.alpn_protocols = Some(alpn_protocols); + self + } + + /// Finalize the builder and create the `ClientConnection`. + pub fn build(self) -> Result { + let Self { + config, + name, + alpn_protocols, + } = self; + + let alpn_protocols = alpn_protocols.unwrap_or_else(|| config.alpn_protocols.clone()); + let fips = config.fips(); + Ok(ClientConnection { + inner: ConnectionCommon::new( + ConnectionCore::for_client( + config, + name, + ClientExtensionsInput::from_alpn(alpn_protocols), + None, + Protocol::Tcp, + )?, + fips, + ), + }) + } +} + +/// Allows writing of early data in resumed TLS 1.3 connections. +/// +/// "Early data" is also known as "0-RTT data". +/// +/// This type implements [`io::Write`]. +pub struct WriteEarlyData<'a> { + sess: &'a mut ClientConnection, +} + +impl<'a> WriteEarlyData<'a> { + fn new(sess: &'a mut ClientConnection) -> Self { + WriteEarlyData { sess } + } + + /// How many bytes you may send. Writes will become short + /// once this reaches zero. + pub fn bytes_left(&self) -> usize { + self.sess + .inner + .core + .side + .early_data + .bytes_left() + } + + /// Returns the "early" exporter that can derive key material for use in early data + /// + /// See [RFC5705][] for general details on what exporters are, and [RFC8446 S7.5][] for + /// specific details on the "early" exporter. + /// + /// **Beware** that the early exporter requires care, as it is subject to the same + /// potential for replay as early data itself. See [RFC8446 appendix E.5.1][] for + /// more detail. + /// + /// This function can be called at most once per connection. This function will error: + /// if called more than once per connection. + /// + /// If you are looking for the normal exporter, this is available from + /// [`Connection::exporter()`]. + /// + /// [RFC5705]: https://datatracker.ietf.org/doc/html/rfc5705 + /// [RFC8446 S7.5]: https://datatracker.ietf.org/doc/html/rfc8446#section-7.5 + /// [RFC8446 appendix E.5.1]: https://datatracker.ietf.org/doc/html/rfc8446#appendix-E.5.1 + /// [`Connection::exporter()`]: crate::conn::Connection::exporter() + pub fn exporter(&mut self) -> Result { + self.sess.inner.core.early_exporter() + } +} + +impl io::Write for WriteEarlyData<'_> { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.sess.write_early_data(buf) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl ConnectionCore { + pub(crate) fn for_client( + config: Arc, + name: ServerName<'static>, + extra_exts: ClientExtensionsInput, + quic: Option<&mut dyn QuicOutput>, + protocol: Protocol, + ) -> Result { + let mut common_state = CommonState::new(Side::Client); + common_state + .send + .set_max_fragment_size(config.max_fragment_size)?; + let mut data = ClientConnectionData::new(); + + let mut output = SideCommonOutput { + side: &mut data, + quic, + common: &mut common_state, + }; + + let input = ClientHelloInput::new(name, &extra_exts, protocol, &mut output, config)?; + let state = input.start_handshake(extra_exts, &mut output)?; + + Ok(Self::new(state, data, common_state)) + } + + pub(crate) fn is_early_data_accepted(&self) -> bool { + self.side.early_data.is_accepted() + } +} + +pub(super) struct EarlyData { + state: EarlyDataState, + left: usize, +} + +impl EarlyData { + fn new() -> Self { + Self { + state: EarlyDataState::Disabled, + left: 0, + } + } + + fn is_enabled(&self) -> bool { + matches!( + self.state, + EarlyDataState::Ready | EarlyDataState::Sending | EarlyDataState::Accepted + ) + } + + fn is_accepted(&self) -> bool { + matches!( + self.state, + EarlyDataState::Accepted | EarlyDataState::AcceptedFinished + ) + } + + fn enable(&mut self, max_data: usize) { + assert_eq!(self.state, EarlyDataState::Disabled); + self.state = EarlyDataState::Ready; + self.left = max_data; + } + + fn start(&mut self) { + assert_eq!(self.state, EarlyDataState::Ready); + self.state = EarlyDataState::Sending; + } + + fn rejected(&mut self) { + trace!("EarlyData rejected"); + self.state = EarlyDataState::Rejected; + } + + fn accepted(&mut self) { + trace!("EarlyData accepted"); + assert_eq!(self.state, EarlyDataState::Sending); + self.state = EarlyDataState::Accepted; + } + + pub(super) fn finished(&mut self) { + trace!("EarlyData finished"); + self.state = match self.state { + EarlyDataState::Accepted => EarlyDataState::AcceptedFinished, + _ => panic!("bad EarlyData state"), + } + } + + fn check_write(&mut self, sz: usize) -> io::Result { + self.check_write_opt(sz) + .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput)) + } + + fn check_write_opt(&mut self, sz: usize) -> Option { + match self.state { + EarlyDataState::Disabled => unreachable!(), + EarlyDataState::Ready | EarlyDataState::Sending | EarlyDataState::Accepted => { + let take = if self.left < sz { + mem::replace(&mut self.left, 0) + } else { + self.left -= sz; + sz + }; + + Some(take) + } + EarlyDataState::Rejected | EarlyDataState::AcceptedFinished => None, + } + } + + fn bytes_left(&self) -> usize { + self.left + } +} + +#[derive(Debug, PartialEq)] +enum EarlyDataState { + Disabled, + Ready, + Sending, + Accepted, + AcceptedFinished, + Rejected, +} + +pub(crate) struct ClientConnectionData { + early_data: EarlyData, + ech_status: EchStatus, +} + +impl ClientConnectionData { + fn new() -> Self { + Self { + early_data: EarlyData::new(), + ech_status: EchStatus::default(), + } + } +} + +/// State associated with a client connection. +#[expect(clippy::exhaustive_structs)] +#[derive(Debug)] +pub struct ClientSide; + +impl crate::conn::SideData for ClientSide {} + +impl crate::conn::private::Side for ClientSide { + type Data = ClientConnectionData; + type State = super::hs::ClientState; +} + +impl SideOutput for ClientConnectionData { + fn emit(&mut self, ev: Event<'_>) { + match ev { + Event::EchStatus(ech) => self.ech_status = ech, + Event::EarlyData(EarlyDataEvent::Accepted) => self.early_data.accepted(), + Event::EarlyData(EarlyDataEvent::Enable(sz)) => self.early_data.enable(sz), + Event::EarlyData(EarlyDataEvent::Finished) => self.early_data.finished(), + Event::EarlyData(EarlyDataEvent::Start) => self.early_data.start(), + Event::EarlyData(EarlyDataEvent::Rejected) => self.early_data.rejected(), + _ => unreachable!(), + } + } +} diff --git a/rustls/src/client/ech.rs b/rustls/src/client/ech.rs index 513852553e6..3a15d71a688 100644 --- a/rustls/src/client/ech.rs +++ b/rustls/src/client/ech.rs @@ -1,38 +1,39 @@ use alloc::boxed::Box; use alloc::vec; use alloc::vec::Vec; +use core::iter; -use pki_types::{DnsName, EchConfigListBytes, ServerName}; +use pki_types::{DnsName, EchConfigListBytes, FipsStatus, ServerName}; use subtle::ConstantTimeEq; -use crate::client::tls13; +use super::config::ClientConfig; +use super::{Retrieved, Tls13Session, tls13}; +use crate::common_state::Protocol; +use crate::crypto::cipher::Payload; use crate::crypto::hash::Hash; -use crate::crypto::hpke::{EncapsulatedSecret, Hpke, HpkePublicKey, HpkeSealer, HpkeSuite}; -use crate::crypto::SecureRandom; +use crate::crypto::hpke::{ + EncapsulatedSecret, Hpke, HpkeKem, HpkePublicKey, HpkeSealer, HpkeSuite, + HpkeSymmetricCipherSuite, +}; +use crate::crypto::{CipherSuite, SecureRandom}; +use crate::enums::ProtocolVersion; +use crate::error::{EncryptedClientHelloError, Error, PeerMisbehaved, RejectedEch}; use crate::hash_hs::{HandshakeHash, HandshakeHashBuffer}; use crate::log::{debug, trace, warn}; -use crate::msgs::base::{Payload, PayloadU16}; -use crate::msgs::codec::{Codec, Reader}; -use crate::msgs::enums::{ExtensionType, HpkeKem}; -use crate::msgs::handshake::{ - ClientExtension, ClientHelloPayload, EchConfigContents, EchConfigPayload, Encoding, - EncryptedClientHello, EncryptedClientHelloOuter, HandshakeMessagePayload, HandshakePayload, - HelloRetryRequest, HpkeKeyConfig, HpkeSymmetricCipherSuite, PresharedKeyBinder, - PresharedKeyOffer, Random, ServerHelloPayload, +use crate::msgs::{ + ClientExtensions, ClientHelloPayload, Codec, EchConfigContents, EchConfigPayload, Encoding, + EncryptedClientHello, EncryptedClientHelloOuter, ExtensionType, HandshakeAlignedProof, + HandshakeMessagePayload, HandshakePayload, HelloRetryRequest, HpkeKeyConfig, Message, + MessagePayload, PresharedKeyBinder, PresharedKeyOffer, Random, Reader, ServerHelloPayload, + ServerNamePayload, SizedPayload, }; -use crate::msgs::message::{Message, MessagePayload}; -use crate::msgs::persist; -use crate::msgs::persist::Retrieved; +use crate::tls13::Tls13CipherSuite; use crate::tls13::key_schedule::{ - server_ech_hrr_confirmation_secret, KeyScheduleEarly, KeyScheduleHandshakeStart, -}; -use crate::CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV; -use crate::{ - AlertDescription, CommonState, EncryptedClientHelloError, Error, HandshakeType, - PeerIncompatible, PeerMisbehaved, ProtocolVersion, Tls13CipherSuite, + KeyScheduleEarlyClient, KeyScheduleHandshakeStart, server_ech_hrr_confirmation_secret, }; /// Controls how Encrypted Client Hello (ECH) is used in a client handshake. +#[non_exhaustive] #[derive(Clone, Debug)] pub enum EchMode { /// ECH is enabled and the ClientHello will be encrypted based on the provided @@ -48,7 +49,7 @@ pub enum EchMode { impl EchMode { /// Returns true if the ECH mode will use a FIPS approved HPKE suite. - pub fn fips(&self) -> bool { + pub fn fips(&self) -> FipsStatus { match self { Self::Enable(ech_config) => ech_config.suite.fips(), Self::Grease(grease_config) => grease_config.suite.fips(), @@ -93,32 +94,75 @@ impl EchConfig { /// One of the provided ECH configurations must be compatible with the HPKE provider's supported /// suites or an error will be returned. /// - /// See the [ech-client.rs] example for a complete example of fetching ECH configs from DNS. + /// See the [`ech-client.rs`] example for a complete example of fetching ECH configs from DNS. /// - /// [ech-client.rs]: https://github.com/rustls/rustls/blob/main/examples/src/bin/ech-client.rs + /// [`ech-client.rs`]: https://github.com/rustls/rustls/blob/main/examples/src/bin/ech-client.rs pub fn new( ech_config_list: EchConfigListBytes<'_>, hpke_suites: &[&'static dyn Hpke], ) -> Result { - let ech_configs = Vec::::read(&mut Reader::init(&ech_config_list)) + let ech_configs = Vec::::read(&mut Reader::new(&ech_config_list)) .map_err(|_| { Error::InvalidEncryptedClientHello(EncryptedClientHelloError::InvalidConfigList) })?; - // Note: we name the index var _i because if the log feature is disabled - // it is unused. - #[cfg_attr(not(feature = "std"), allow(clippy::unused_enumerate_index))] - for (_i, config) in ech_configs.iter().enumerate() { + Self::new_for_configs(ech_configs, hpke_suites) + } + + /// Build an EchConfig for retrying ECH using a retry config from a server's previous rejection + /// + /// Returns an error if the server provided no retry configurations in `RejectedEch`, or if + /// none of the retry configurations are compatible with the supported `hpke_suites`. + pub fn for_retry( + rejection: RejectedEch, + hpke_suites: &[&'static dyn Hpke], + ) -> Result { + let Some(configs) = rejection.retry_configs else { + return Err(EncryptedClientHelloError::NoCompatibleConfig.into()); + }; + + Self::new_for_configs(configs, hpke_suites) + } + + pub(super) fn state( + &self, + server_name: ServerName<'static>, + protocol: Protocol, + config: &ClientConfig, + ) -> Result { + EchState::new( + self, + server_name.clone(), + protocol, + !config + .resolver() + .supported_certificate_types() + .is_empty(), + config.provider().secure_random, + config.enable_sni, + ) + } + + /// Compute the HPKE `SetupBaseS` `info` parameter for this ECH configuration. + /// + /// See . + pub(crate) fn hpke_info(&self) -> Vec { + let mut info = Vec::with_capacity(128); + // "tls ech" || 0x00 || ECHConfig + info.extend_from_slice(b"tls ech\0"); + self.config.encode(&mut info); + info + } + + fn new_for_configs( + ech_configs: Vec, + hpke_suites: &[&'static dyn Hpke], + ) -> Result { + for (i, config) in ech_configs.iter().enumerate() { let contents = match config { EchConfigPayload::V18(contents) => contents, - EchConfigPayload::Unknown { - version: _version, .. - } => { - warn!( - "ECH config {} has unsupported version {:?}", - _i + 1, - _version - ); + EchConfigPayload::Unknown { version, .. } => { + warn!("ECH config {} has unsupported version {:?}", i + 1, version); continue; // Unsupported version. } }; @@ -156,17 +200,6 @@ impl EchConfig { Err(EncryptedClientHelloError::NoCompatibleConfig.into()) } - - /// Compute the HPKE `SetupBaseS` `info` parameter for this ECH configuration. - /// - /// See . - pub(crate) fn hpke_info(&self) -> Vec { - let mut info = Vec::with_capacity(128); - // "tls ech" || 0x00 || ECHConfig - info.extend_from_slice(b"tls ech\0"); - self.config.encode(&mut info); - info - } } /// Configuration for GREASE Encrypted Client Hello. @@ -195,14 +228,15 @@ impl EchGreaseConfig { /// Build a GREASE ECH extension based on the placeholder configuration. /// - /// See for + /// See for /// more information. pub(crate) fn grease_ext( &self, secure_random: &'static dyn SecureRandom, + protocol: Protocol, inner_name: ServerName<'static>, outer_hello: &ClientHelloPayload, - ) -> Result { + ) -> Result { trace!("Preparing GREASE ECH extension"); // Pick a random config id. @@ -219,7 +253,7 @@ impl EchGreaseConfig { key_config: HpkeKeyConfig { config_id: config_id[0], kem_id: HpkeKem::DHKEM_P256_HKDF_SHA256, - public_key: PayloadU16(self.placeholder_key.0.clone()), + public_key: SizedPayload::from(self.placeholder_key.0.clone()), symmetric_cipher_suites: vec![suite.sym], }, maximum_name_length: 0, @@ -229,6 +263,7 @@ impl EchGreaseConfig { suite: self.suite, }, inner_name, + protocol, false, secure_random, false, // Does not matter if we enable/disable SNI here. Inner hello is not used. @@ -236,7 +271,7 @@ impl EchGreaseConfig { // Construct an inner hello using the outer hello - this allows us to know the size of // dummy payload we should use for the GREASE extension. - let encoded_inner_hello = grease_state.encode_inner_hello(outer_hello, None, &None); + let encoded_inner_hello = grease_state.encode_inner_hello(outer_hello, None, None); // Generate a payload of random data equivalent in length to a real inner hello. let payload_len = encoded_inner_hello.len() @@ -251,21 +286,21 @@ impl EchGreaseConfig { secure_random.fill(&mut payload)?; // Return the GREASE extension. - Ok(ClientExtension::EncryptedClientHello( - EncryptedClientHello::Outer(EncryptedClientHelloOuter { - cipher_suite: suite.sym, - config_id: config_id[0], - enc: PayloadU16(grease_state.enc.0), - payload: PayloadU16::new(payload), - }), - )) + Ok(EncryptedClientHello::Outer(EncryptedClientHelloOuter { + cipher_suite: suite.sym, + config_id: config_id[0], + enc: SizedPayload::from(Payload::new(grease_state.enc.0)), + payload: SizedPayload::from(Payload::new(payload)), + })) } } /// An enum representing ECH offer status. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[non_exhaustive] +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] pub enum EchStatus { /// ECH was not offered - it is a normal TLS handshake. + #[default] NotOffered, /// GREASE ECH was sent. This is not considered offering ECH. Grease, @@ -284,7 +319,7 @@ pub(crate) struct EchState { pub(crate) outer_name: DnsName<'static>, // If we're resuming in the inner hello, this is the early key schedule to use for encrypting // early data if the ECH offer is accepted. - pub(crate) early_data_key_schedule: Option, + pub(crate) early_data_key_schedule: Option, // A random value we use for the inner hello. pub(crate) inner_hello_random: Random, // A transcript buffer maintained for the inner hello. Once ECH is confirmed we switch to @@ -292,6 +327,8 @@ pub(crate) struct EchState { pub(crate) inner_hello_transcript: HandshakeHashBuffer, // A source of secure random data. secure_random: &'static dyn SecureRandom, + // The top level protocol + protocol: Protocol, // An HPKE sealer context that can be used for encrypting ECH data. sender: Box, // The ID of the ECH configuration we've chosen - this is included in the outer ECH extension. @@ -317,6 +354,7 @@ impl EchState { pub(crate) fn new( config: &EchConfig, inner_name: ServerName<'static>, + protocol: Protocol, client_auth_enabled: bool, secure_random: &'static dyn SecureRandom, enable_sni: bool, @@ -332,7 +370,7 @@ impl EchState { // we can use to seal messages. let (enc, sender) = config.suite.setup_sealer( &config.hpke_info(), - &HpkePublicKey(key_config.public_key.0.clone()), + &HpkePublicKey(key_config.public_key.to_vec()), )?; // Start a new transcript buffer for the inner hello. @@ -342,17 +380,18 @@ impl EchState { } Ok(Self { + outer_name: config_contents.public_name.clone(), + early_data_key_schedule: None, + inner_hello_random: Random::new(secure_random)?, + inner_hello_transcript, secure_random, sender, config_id: key_config.config_id, inner_name, - outer_name: config_contents.public_name.clone(), maximum_name_length: config_contents.maximum_name_length, cipher_suite: config.suite.suite().sym, + protocol, enc, - inner_hello_random: Random::new(secure_random)?, - inner_hello_transcript, - early_data_key_schedule: None, enable_sni, sent_extensions: Vec::new(), }) @@ -370,7 +409,7 @@ impl EchState { &mut self, mut outer_hello: ClientHelloPayload, retry_req: Option<&HelloRetryRequest>, - resuming: &Option>, + resuming: Option<&Retrieved<&Tls13Session>>, ) -> Result { trace!( "Preparing ECH offer {}", @@ -398,29 +437,26 @@ impl EchState { false => self.enc.0.clone(), }; - fn outer_hello_ext(ctx: &EchState, enc: Vec, payload: Vec) -> ClientExtension { - ClientExtension::EncryptedClientHello(EncryptedClientHello::Outer( - EncryptedClientHelloOuter { - cipher_suite: ctx.cipher_suite, - config_id: ctx.config_id, - enc: PayloadU16::new(enc), - payload: PayloadU16::new(payload), - }, - )) + fn outer_hello_ext(ctx: &EchState, enc: Vec, payload: Vec) -> EncryptedClientHello { + EncryptedClientHello::Outer(EncryptedClientHelloOuter { + cipher_suite: ctx.cipher_suite, + config_id: ctx.config_id, + enc: SizedPayload::from(Payload::new(enc)), + payload: SizedPayload::from(Payload::new(payload)), + }) } // The outer handshake is not permitted to resume a session. If we're resuming in the // inner handshake we remove the PSK extension from the outer hello, replacing it // with a GREASE PSK to implement the "ClientHello Malleability Mitigation" mentioned // in 10.12.3. - if let Some(ClientExtension::PresharedKey(psk_offer)) = outer_hello.extensions.last_mut() { + if let Some(psk_offer) = outer_hello.preshared_key_offer.as_mut() { self.grease_psk(psk_offer)?; } // To compute the encoded AAD we add a placeholder extension with an empty payload. - outer_hello - .extensions - .push(outer_hello_ext(self, enc.clone(), vec![0; payload_len])); + outer_hello.encrypted_client_hello = + Some(outer_hello_ext(self, enc.clone(), vec![0; payload_len])); // Next we compute the proper extension payload. let payload = self @@ -428,10 +464,7 @@ impl EchState { .seal(&outer_hello.get_encoding(), &encoded_inner_hello)?; // And then we replace the placeholder extension with the real one. - outer_hello.extensions.pop(); - outer_hello - .extensions - .push(outer_hello_ext(self, enc, payload)); + outer_hello.encrypted_client_hello = Some(outer_hello_ext(self, enc, payload)); Ok(outer_hello) } @@ -439,8 +472,9 @@ impl EchState { /// Confirm whether an ECH offer was accepted based on examining the server hello. pub(crate) fn confirm_acceptance( self, - ks: &mut KeyScheduleHandshakeStart, + ks: &KeyScheduleHandshakeStart, server_hello: &ServerHelloPayload, + server_hello_encoded: &Payload<'_>, hash: &'static dyn Hash, ) -> Result, Error> { // Start the inner transcript hash now that we know the hash algorithm to use. @@ -452,8 +486,10 @@ impl EchState { // We need to preserve the original inner_transcript to use if this confirmation succeeds. let mut confirmation_transcript = inner_transcript.clone(); - // Add the server hello confirmation - this differs from the standard server hello encoding. - confirmation_transcript.add_message(&Self::server_hello_conf(server_hello)); + // Add the server hello confirmation - this is computed by altering the received + // encoding rather than reencoding it. + confirmation_transcript + .add_message(&Self::server_hello_conf(server_hello, server_hello_encoded)); // Derive a confirmation secret from the inner hello random and the confirmation transcript. let derived = ks.server_ech_confirmation_secret( @@ -485,21 +521,15 @@ impl EchState { &self, hrr: &HelloRetryRequest, cs: &Tls13CipherSuite, - common: &mut CommonState, ) -> Result { // The client checks for the "encrypted_client_hello" extension. - let ech_conf = match hrr.ech() { + let ech_conf = match &hrr.encrypted_client_hello { // If none is found, the server has implicitly rejected ECH. None => return Ok(false), // Otherwise, if it has a length other than 8, the client aborts the // handshake with a "decode_error" alert. - Some(ech_conf) if ech_conf.len() != 8 => { - return Err({ - common.send_fatal_alert( - AlertDescription::DecodeError, - PeerMisbehaved::IllegalHelloRetryRequestWithInvalidEch, - ) - }) + Some(ech_conf) if ech_conf.bytes().len() != 8 => { + return Err(PeerMisbehaved::IllegalHelloRetryRequestWithInvalidEch.into()); } Some(ech_conf) => ech_conf, }; @@ -518,7 +548,7 @@ impl EchState { confirmation_transcript.current_hash(), ); - match ConstantTimeEq::ct_eq(derived.as_ref(), ech_conf).into() { + match ConstantTimeEq::ct_eq(derived.as_ref(), ech_conf.bytes()).into() { true => { trace!("ECH accepted by server in hello retry request"); Ok(true) @@ -534,7 +564,12 @@ impl EchState { /// /// This will start the in-progress transcript using the given `hash`, convert it into an HRR /// buffer, and then add the hello retry message `m`. - pub(crate) fn transcript_hrr_update(&mut self, hash: &'static dyn Hash, m: &Message<'_>) { + pub(crate) fn transcript_hrr_update( + &mut self, + hash: &'static dyn Hash, + m: &Message<'_>, + proof: &HandshakeAlignedProof, + ) { trace!("Updating ECH inner transcript for HRR"); let inner_transcript = self @@ -542,7 +577,7 @@ impl EchState { .clone() .start_hash(hash); - let mut inner_transcript_buffer = inner_transcript.into_hrr_buffer(); + let mut inner_transcript_buffer = inner_transcript.into_hrr_buffer(proof); inner_transcript_buffer.add_message(m); self.inner_hello_transcript = inner_transcript_buffer; } @@ -552,22 +587,18 @@ impl EchState { &mut self, outer_hello: &ClientHelloPayload, retryreq: Option<&HelloRetryRequest>, - resuming: &Option>, + resuming: Option<&Retrieved<&Tls13Session>>, ) -> Vec { // Start building an inner hello using the outer_hello as a template. let mut inner_hello = ClientHelloPayload { // Some information is copied over as-is. client_version: outer_hello.client_version, - session_id: outer_hello.session_id, - compression_methods: outer_hello.compression_methods.clone(), - - // We will build up the included extensions ourselves. - extensions: vec![], // Set the inner hello random to the one we generated when creating the ECH state. // We hold on to the inner_hello_random in the ECH state to use later for confirming // whether ECH was accepted or not. random: self.inner_hello_random, + session_id: outer_hello.session_id, // We remove the empty renegotiation info SCSV from the outer hello's ciphersuite. // Similar to the TLS 1.2 specific extensions we will filter out, this is seen as a @@ -575,18 +606,20 @@ impl EchState { cipher_suites: outer_hello .cipher_suites .iter() - .filter(|cs| **cs != TLS_EMPTY_RENEGOTIATION_INFO_SCSV) - .cloned() + .filter(|cs| **cs != CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + .copied() .collect(), + compression_methods: outer_hello.compression_methods.clone(), + + // We will build up the included extensions ourselves. + extensions: Box::new(ClientExtensions::default()), }; + inner_hello.order_seed = outer_hello.order_seed; + // The inner hello will always have an inner variant of the ECH extension added. // See Section 6.1 rule 4. - inner_hello - .extensions - .push(ClientExtension::EncryptedClientHello( - EncryptedClientHello::Inner, - )); + inner_hello.encrypted_client_hello = Some(EncryptedClientHello::Inner); let inner_sni = match &self.inner_name { // The inner hello only gets a SNI value if enable_sni is true and the inner name @@ -600,14 +633,14 @@ impl EchState { // 2. Add the extension to the inner hello as-is. // 3. Compress the extension, by collecting it into a list of to-be-compressed // extensions we'll handle separately. - let mut compressed_exts = Vec::with_capacity(outer_hello.extensions.len()); - let mut compressed_ext_types = Vec::with_capacity(outer_hello.extensions.len()); - for ext in &outer_hello.extensions { + let outer_extensions = outer_hello.used_extensions_in_encoding_order(); + let mut compressed_exts = Vec::with_capacity(outer_extensions.len()); + for ext in outer_extensions { // Some outer hello extensions are only useful in the context where a TLS 1.3 // connection allows TLS 1.2. This isn't the case for ECH so we skip adding them // to the inner hello. if matches!( - ext.ext_type(), + ext, ExtensionType::ExtendedMasterSecret | ExtensionType::SessionTicket | ExtensionType::ECPointFormats @@ -615,12 +648,10 @@ impl EchState { continue; } - if ext.ext_type() == ExtensionType::ServerName { + if ext == ExtensionType::ServerName { // We may want to replace the outer hello SNI with our own inner hello specific SNI. if let Some(sni_value) = inner_sni { - inner_hello - .extensions - .push(ClientExtension::make_sni(&sni_value.borrow())); + inner_hello.server_name = Some(ServerNamePayload::from(sni_value)); } // We don't want to add, or compress, the SNI from the outer hello. continue; @@ -628,94 +659,68 @@ impl EchState { // Compressed extensions need to be put aside to include in one contiguous block. // Uncompressed extensions get added directly to the inner hello. - if ext.ext_type().ech_compress() { - compressed_exts.push(ext.clone()); - compressed_ext_types.push(ext.ext_type()); - } else { - inner_hello.extensions.push(ext.clone()); + if ext.ech_compress() { + compressed_exts.push(ext); } + + inner_hello.clone_one(outer_hello, ext); } // We've added all the uncompressed extensions. Now we need to add the contiguous - // block of to-be-compressed extensions. Where we do this depends on whether the - // last uncompressed extension is a PSK for resumption. In this case we must - // add the to-be-compressed extensions _before_ the PSK. - let compressed_exts_index = - if let Some(ClientExtension::PresharedKey(_)) = inner_hello.extensions.last() { - inner_hello.extensions.len() - 1 - } else { - inner_hello.extensions.len() - }; - inner_hello.extensions.splice( - compressed_exts_index..compressed_exts_index, - compressed_exts, - ); + // block of to-be-compressed extensions. + inner_hello.contiguous_extensions = compressed_exts.clone(); // Note which extensions we're sending in the inner hello. This may differ from // the outer hello (e.g. the inner hello may omit SNI while the outer hello will // always have the ECH cover name in SNI). - self.sent_extensions = inner_hello - .extensions - .iter() - .map(|ext| ext.ext_type()) - .collect(); + self.sent_extensions = inner_hello.collect_used(); // If we're resuming, we need to update the PSK binder in the inner hello. if let Some(resuming) = resuming.as_ref() { - let mut chp = HandshakeMessagePayload { - typ: HandshakeType::ClientHello, - payload: HandshakePayload::ClientHello(inner_hello), - }; + let mut chp = HandshakeMessagePayload(HandshakePayload::ClientHello(inner_hello)); - // Retain the early key schedule we get from processing the binder. - self.early_data_key_schedule = Some(tls13::fill_in_psk_binder( - resuming, - &self.inner_hello_transcript, - &mut chp, - )); + let key_schedule = + KeyScheduleEarlyClient::new(self.protocol, resuming.suite, resuming.secret.bytes()); + tls13::fill_in_psk_binder(&key_schedule, &self.inner_hello_transcript, &mut chp); + self.early_data_key_schedule = Some(key_schedule); // fill_in_psk_binder works on an owned HandshakeMessagePayload, so we need to // extract our inner hello back out of it to retain ownership. - inner_hello = match chp.payload { + inner_hello = match chp.0 { HandshakePayload::ClientHello(chp) => chp, // Safety: we construct the HMP above and know its type unconditionally. _ => unreachable!(), }; } - trace!("ECH Inner Hello: {:#?}", inner_hello); + trace!("ECH Inner Hello: {inner_hello:#?}"); // Encode the inner hello according to the rules required for ECH. This differs // from the standard encoding in several ways. Notably this is where we will // replace the block of contiguous to-be-compressed extensions with a marker. - let mut encoded_hello = inner_hello.ech_inner_encoding(compressed_ext_types); + let mut encoded_hello = inner_hello.ech_inner_encoding(compressed_exts); // Calculate padding // max_name_len = L - let max_name_len = self.maximum_name_length; + let max_name_len = usize::from(self.maximum_name_length); let max_name_len = if max_name_len > 0 { max_name_len } else { 255 }; - let padding_len = match &self.inner_name { - ServerName::DnsName(name) => { + let name_padding_len = match &inner_hello.server_name { + Some(ServerNamePayload::SingleDnsName(name)) => { // name.len() = D // max(0, L - D) - core::cmp::max( - 0, - max_name_len.saturating_sub(name.as_ref().len() as u8) as usize, - ) - } - _ => { - // L + 9 - // "This is the length of a "server_name" extension with an L-byte name." - // We widen to usize here to avoid overflowing u8 + u8. - max_name_len as usize + 9 + Ord::max(0, max_name_len.saturating_sub(name.as_ref().len())) } + // L + 9 + // "This is the length of a "server_name" extension with an L-byte name." + _ => max_name_len + 9, }; + encoded_hello.extend(iter::repeat_n(0, name_padding_len)); // Let L be the length of the EncodedClientHelloInner with all the padding computed so far // Let N = 31 - ((L - 1) % 32) and add N bytes of padding. - let padding_len = 31 - ((encoded_hello.len() + padding_len - 1) % 32); - encoded_hello.extend(vec![0; padding_len]); + let padding_len = 31 - ((encoded_hello.len() - 1) % 32); + encoded_hello.extend(iter::repeat_n(0, padding_len)); // Construct the inner hello message that will be used for the transcript. let inner_hello_msg = Message { @@ -731,10 +736,9 @@ impl EchState { // (retryreq == None means we're in the "initial ClientHello" case) None => ProtocolVersion::TLSv1_0, }, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::ClientHello, - payload: HandshakePayload::ClientHello(inner_hello), - }), + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ClientHello(inner_hello), + )), }; // Update the inner transcript buffer with the inner hello message. @@ -744,13 +748,16 @@ impl EchState { encoded_hello } - // See https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#name-grease-psk + // See https://datatracker.ietf.org/doc/html/rfc9849#name-grease-psk fn grease_psk(&self, psk_offer: &mut PresharedKeyOffer) -> Result<(), Error> { for ident in psk_offer.identities.iter_mut() { // "For each PSK identity advertised in the ClientHelloInner, the // client generates a random PSK identity with the same length." - self.secure_random - .fill(&mut ident.identity.0)?; + match ident.identity.as_mut() { + Some(ident) => self.secure_random.fill(ident)?, + None => unreachable!(), + } + // "It also generates a random, 32-bit, unsigned integer to use as // the obfuscated_ticket_age." let mut ticket_age = [0_u8; 4]; @@ -776,18 +783,35 @@ impl EchState { Ok(()) } - fn server_hello_conf(server_hello: &ServerHelloPayload) -> Message<'_> { - Self::ech_conf_message(HandshakeMessagePayload { - typ: HandshakeType::ServerHello, - payload: HandshakePayload::ServerHello(server_hello.clone()), - }) + fn server_hello_conf( + server_hello: &ServerHelloPayload, + server_hello_encoded: &Payload<'_>, + ) -> Message<'static> { + // The confirmation is computed over the server hello, which has had + // its `random` field altered to zero the final 8 bytes. + // + // nb. we don't require that we can round-trip a `ServerHelloPayload`, to + // allow for efficiency in its in-memory representation. That means + // we operate here on the received encoding, as the confirmation needs + // to be computed on that. + let mut encoded = server_hello_encoded.clone().into_vec(); + encoded[SERVER_HELLO_ECH_CONFIRMATION_SPAN].fill(0x00); + + Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::Handshake { + encoded: Payload::Owned(encoded), + parsed: HandshakeMessagePayload(HandshakePayload::ServerHello( + server_hello.clone(), + )), + }, + } } fn hello_retry_request_conf(retry_req: &HelloRetryRequest) -> Message<'_> { - Self::ech_conf_message(HandshakeMessagePayload { - typ: HandshakeType::HelloRetryRequest, - payload: HandshakePayload::HelloRetryRequest(retry_req.clone()), - }) + Self::ech_conf_message(HandshakeMessagePayload( + HandshakePayload::HelloRetryRequest(retry_req.clone()), + )) } fn ech_conf_message(hmp: HandshakeMessagePayload<'_>) -> Message<'_> { @@ -803,6 +827,16 @@ impl EchState { } } +/// The last eight bytes of the ServerHello's random, taken from a Handshake message containing it. +/// +/// This has: +/// - a HandshakeType (1 byte), +/// - an exterior length (3 bytes), +/// - the legacy_version (2 bytes), and +/// - the balance of the random field (24 bytes). +const SERVER_HELLO_ECH_CONFIRMATION_SPAN: core::ops::Range = + (1 + 3 + 2 + 24)..(1 + 3 + 2 + 32); + /// Returned from EchState::check_acceptance when the server has accepted the ECH offer. /// /// Holds the state required to continue the handshake with the inner hello from the ECH offer. @@ -812,12 +846,243 @@ pub(crate) struct EchAccepted { pub(crate) sent_extensions: Vec, } -pub(crate) fn fatal_alert_required( - retry_configs: Option>, - common: &mut CommonState, -) -> Error { - common.send_fatal_alert( - AlertDescription::EncryptedClientHelloRequired, - PeerIncompatible::ServerRejectedEncryptedClientHello(retry_configs), - ) +#[cfg(test)] +mod tests { + use std::string::String; + + use super::*; + use crate::crypto::hpke::{HpkeAead, HpkeKdf}; + use crate::crypto::{CipherSuite, TEST_PROVIDER}; + use crate::msgs::{Compression, Random, ServerExtensions, SessionId}; + + #[test] + fn server_hello_conf_alters_server_hello_random() { + let server_hello = ServerHelloPayload { + legacy_version: ProtocolVersion::TLSv1_2, + random: Random([0xffu8; 32]), + session_id: SessionId::empty(), + cipher_suite: CipherSuite::TLS13_AES_256_GCM_SHA384, + compression_method: Compression::Null, + extensions: Box::new(ServerExtensions::default()), + }; + let message = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ServerHello(server_hello.clone()), + )), + }; + let Message { + payload: + MessagePayload::Handshake { + encoded: server_hello_encoded_before, + .. + }, + .. + } = &message + else { + unreachable!("ServerHello is a handshake message"); + }; + + let message = EchState::server_hello_conf(&server_hello, server_hello_encoded_before); + + let Message { + payload: + MessagePayload::Handshake { + encoded: server_hello_encoded_after, + .. + }, + .. + } = &message + else { + unreachable!("ServerHello is a handshake message"); + }; + + assert_eq!( + std::format!("{server_hello_encoded_before:x?}"), + "020000280303ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001302000000", + "beforehand eight bytes at end of Random should be 0xff here ^^^^^^^^^^^^^^^^ " + ); + assert_eq!( + std::format!("{server_hello_encoded_after:x?}"), + "020000280303ffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000001302000000", + " afterwards those bytes are zeroed ^^^^^^^^^^^^^^^^ " + ); + } + + #[test] + fn inner_client_hello_length_conceals_inner_name_length() { + let base_inner_len = inner_hello_encoding_for_name(dns_name_of_len(1), true).len(); + assert!( + base_inner_len % 32 == 0, + "inner hello length must be 32-byte padded" + ); + assert!( + base_inner_len >= 256, + "inner hello must include inner name and its padding" + ); + + for inner_name_len in 1..251 { + assert_eq!( + inner_hello_encoding_for_name(dns_name_of_len(inner_name_len), true).len(), + base_inner_len, + "all inner hello lengths must be invariant wrt inner name length" + ); + } + } + + #[test] + fn inner_client_hello_length_does_not_leak_length_of_omitted_inner_name() { + let base_inner_len = inner_hello_encoding_for_name(dns_name_of_len(1), false).len(); + assert!( + base_inner_len % 32 == 0, + "inner hello length must be 32-byte padded" + ); + assert!( + base_inner_len >= 256, + "inner hello must include maximum_name_length bytes of padding" + ); + + for inner_name_len in 1..251 { + assert_eq!( + inner_hello_encoding_for_name(dns_name_of_len(inner_name_len), false).len(), + base_inner_len, + "all inner hello lengths must be invariant wrt inner name length" + ); + } + } + + fn inner_hello_encoding_for_name(name: DnsName<'static>, enable_sni: bool) -> Vec { + let config = EchConfig { + config: EchConfigPayload::V18(EchConfigContents { + key_config: HpkeKeyConfig { + config_id: 0, + kem_id: MockHpke::SUITE.kem, + public_key: vec![0; 32].into(), + symmetric_cipher_suites: vec![], + }, + maximum_name_length: 255, + public_name: DnsName::try_from("public").unwrap(), + extensions: vec![], + }), + suite: &MockHpke, + }; + + EchState::new( + &config, + ServerName::from(name.clone()), + Protocol::Tcp, + false, + TEST_PROVIDER.secure_random, + enable_sni, + ) + .unwrap() + .encode_inner_hello( + &ClientHelloPayload { + client_version: ProtocolVersion::TLSv1_3, + random: Random([0u8; 32]), + session_id: SessionId::empty(), + cipher_suites: vec![], + compression_methods: vec![Compression::Null], + extensions: Box::new(ClientExtensions { + server_name: Some(ServerNamePayload::from(&name)), + ..Default::default() + }), + }, + None, + None, + ) + } + + fn dns_name_of_len(mut len: usize) -> DnsName<'static> { + let mut s = String::new(); + let labels = len.div_ceil(63); + for _ in 0..labels { + let chars = Ord::min(len, 63); + len -= chars; + for _ in 0..chars { + s.push('a'); + } + if len != 0 { + s.push('.'); + } + } + DnsName::try_from(s).unwrap() + } + + #[derive(Debug)] + struct MockHpke; + + impl MockHpke { + const SUITE: HpkeSuite = HpkeSuite { + kem: HpkeKem::DHKEM_P256_HKDF_SHA256, + sym: HpkeSymmetricCipherSuite { + kdf_id: HpkeKdf::HKDF_SHA256, + aead_id: HpkeAead::AES_128_GCM, + }, + }; + } + + impl Hpke for MockHpke { + #[cfg_attr(coverage_nightly, coverage(off))] + fn seal( + &self, + _info: &[u8], + _aad: &[u8], + _plaintext: &[u8], + _pub_key: &HpkePublicKey, + ) -> Result<(EncapsulatedSecret, Vec), Error> { + todo!() + } + + fn setup_sealer( + &self, + _info: &[u8], + _pub_key: &HpkePublicKey, + ) -> Result<(EncapsulatedSecret, Box), Error> { + Ok((EncapsulatedSecret(vec![]), Box::new(MockHpkeSealer))) + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn open( + &self, + _enc: &EncapsulatedSecret, + _info: &[u8], + _aad: &[u8], + _ciphertext: &[u8], + _secret_key: &crate::crypto::hpke::HpkePrivateKey, + ) -> Result, Error> { + todo!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn setup_opener( + &self, + _enc: &EncapsulatedSecret, + _info: &[u8], + _secret_key: &crate::crypto::hpke::HpkePrivateKey, + ) -> Result, Error> { + todo!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn generate_key_pair( + &self, + ) -> Result<(HpkePublicKey, crate::crypto::hpke::HpkePrivateKey), Error> { + todo!() + } + + fn suite(&self) -> HpkeSuite { + Self::SUITE + } + } + + #[derive(Debug)] + struct MockHpkeSealer; + + impl HpkeSealer for MockHpkeSealer { + #[cfg_attr(coverage_nightly, coverage(off))] + fn seal(&mut self, _aad: &[u8], _plaintext: &[u8]) -> Result, Error> { + todo!() + } + } } diff --git a/rustls/src/client/handy.rs b/rustls/src/client/handy.rs index 78d9326d355..676bb3f4524 100644 --- a/rustls/src/client/handy.rs +++ b/rustls/src/client/handy.rs @@ -1,49 +1,46 @@ -use alloc::sync::Arc; +use core::hash::Hasher; -use pki_types::ServerName; - -use crate::enums::SignatureScheme; -use crate::error::Error; -use crate::msgs::handshake::CertificateChain; -use crate::msgs::persist; -use crate::{client, sign, NamedGroup}; +use super::config::{ClientCredentialResolver, ClientSessionStore}; +use super::{ClientSessionKey, CredentialRequest, Tls12Session, Tls13Session}; +use crate::crypto::SelectedCredential; +use crate::crypto::kx::NamedGroup; +use crate::enums::CertificateType; /// An implementer of `ClientSessionStore` which does nothing. #[derive(Debug)] pub(super) struct NoClientSessionStorage; -impl client::ClientSessionStore for NoClientSessionStorage { - fn set_kx_hint(&self, _: ServerName<'static>, _: NamedGroup) {} +impl ClientSessionStore for NoClientSessionStorage { + fn set_kx_hint(&self, _: ClientSessionKey<'static>, _: NamedGroup) {} - fn kx_hint(&self, _: &ServerName<'_>) -> Option { + fn kx_hint(&self, _: &ClientSessionKey<'_>) -> Option { None } - fn set_tls12_session(&self, _: ServerName<'static>, _: persist::Tls12ClientSessionValue) {} + fn set_tls12_session(&self, _: ClientSessionKey<'static>, _: Tls12Session) {} - fn tls12_session(&self, _: &ServerName<'_>) -> Option { + fn tls12_session(&self, _: &ClientSessionKey<'_>) -> Option { None } - fn remove_tls12_session(&self, _: &ServerName<'_>) {} + fn remove_tls12_session(&self, _: &ClientSessionKey<'_>) {} - fn insert_tls13_ticket(&self, _: ServerName<'static>, _: persist::Tls13ClientSessionValue) {} + fn insert_tls13_ticket(&self, _: ClientSessionKey<'static>, _: Tls13Session) {} - fn take_tls13_ticket(&self, _: &ServerName<'_>) -> Option { + fn take_tls13_ticket(&self, _: &ClientSessionKey<'_>) -> Option { None } } -#[cfg(any(feature = "std", feature = "hashbrown"))] mod cache { use alloc::collections::VecDeque; use core::fmt; - use pki_types::ServerName; - + use super::*; + use crate::client::Tls13Session; + use crate::crypto::kx::NamedGroup; + use crate::limited_cache; use crate::lock::Mutex; - use crate::msgs::persist; - use crate::{limited_cache, NamedGroup}; const MAX_TLS13_TICKETS_PER_SERVER: usize = 8; @@ -51,18 +48,16 @@ mod cache { kx_hint: Option, // Zero or one TLS1.2 sessions. - #[cfg(feature = "tls12")] - tls12: Option, + tls12: Option, // Up to MAX_TLS13_TICKETS_PER_SERVER TLS1.3 tickets, oldest first. - tls13: VecDeque, + tls13: VecDeque, } impl Default for ServerData { fn default() -> Self { Self { kx_hint: None, - #[cfg(feature = "tls12")] tls12: None, tls13: VecDeque::with_capacity(MAX_TLS13_TICKETS_PER_SERVER), } @@ -74,13 +69,12 @@ mod cache { /// /// It enforces a limit on the number of entries to bound memory usage. pub struct ClientSessionMemoryCache { - servers: Mutex, ServerData>>, + servers: Mutex, ServerData>>, } impl ClientSessionMemoryCache { /// Make a new ClientSessionMemoryCache. `size` is the /// maximum number of stored sessions. - #[cfg(feature = "std")] pub fn new(size: usize) -> Self { let max_servers = size.saturating_add(MAX_TLS13_TICKETS_PER_SERVER - 1) / MAX_TLS13_TICKETS_PER_SERVER; @@ -88,82 +82,52 @@ mod cache { servers: Mutex::new(limited_cache::LimitedCache::new(max_servers)), } } - - /// Make a new ClientSessionMemoryCache. `size` is the - /// maximum number of stored sessions. - #[cfg(not(feature = "std"))] - pub fn new(size: usize) -> Self { - let max_servers = size.saturating_add(MAX_TLS13_TICKETS_PER_SERVER - 1) - / MAX_TLS13_TICKETS_PER_SERVER; - Self { - servers: Mutex::new::(limited_cache::LimitedCache::new(max_servers)), - } - } } - impl super::client::ClientSessionStore for ClientSessionMemoryCache { - fn set_kx_hint(&self, server_name: ServerName<'static>, group: NamedGroup) { + impl ClientSessionStore for ClientSessionMemoryCache { + fn set_kx_hint(&self, key: ClientSessionKey<'static>, group: NamedGroup) { self.servers .lock() .unwrap() - .get_or_insert_default_and_edit(server_name, |data| data.kx_hint = Some(group)); + .get_or_insert_default_and_edit(key, |data| data.kx_hint = Some(group)); } - fn kx_hint(&self, server_name: &ServerName<'_>) -> Option { + fn kx_hint(&self, key: &ClientSessionKey<'_>) -> Option { self.servers .lock() .unwrap() - .get(server_name) + .get(key) .and_then(|sd| sd.kx_hint) } - fn set_tls12_session( - &self, - _server_name: ServerName<'static>, - _value: persist::Tls12ClientSessionValue, - ) { - #[cfg(feature = "tls12")] + fn set_tls12_session(&self, key: ClientSessionKey<'static>, value: Tls12Session) { self.servers .lock() .unwrap() - .get_or_insert_default_and_edit(_server_name.clone(), |data| { - data.tls12 = Some(_value) - }); + .get_or_insert_default_and_edit(key.clone(), |data| data.tls12 = Some(value)); } - fn tls12_session( - &self, - _server_name: &ServerName<'_>, - ) -> Option { - #[cfg(not(feature = "tls12"))] - return None; - - #[cfg(feature = "tls12")] + fn tls12_session(&self, key: &ClientSessionKey<'_>) -> Option { self.servers .lock() .unwrap() - .get(_server_name) + .get(key) .and_then(|sd| sd.tls12.as_ref().cloned()) } - fn remove_tls12_session(&self, _server_name: &ServerName<'static>) { - #[cfg(feature = "tls12")] + fn remove_tls12_session(&self, key: &ClientSessionKey<'static>) { self.servers .lock() .unwrap() - .get_mut(_server_name) + .get_mut(key) .and_then(|data| data.tls12.take()); } - fn insert_tls13_ticket( - &self, - server_name: ServerName<'static>, - value: persist::Tls13ClientSessionValue, - ) { + fn insert_tls13_ticket(&self, key: ClientSessionKey<'static>, value: Tls13Session) { self.servers .lock() .unwrap() - .get_or_insert_default_and_edit(server_name.clone(), |data| { + .get_or_insert_default_and_edit(key.clone(), |data| { if data.tls13.len() == data.tls13.capacity() { data.tls13.pop_front(); } @@ -171,14 +135,11 @@ mod cache { }); } - fn take_tls13_ticket( - &self, - server_name: &ServerName<'static>, - ) -> Option { + fn take_tls13_ticket(&self, key: &ClientSessionKey<'static>) -> Option { self.servers .lock() .unwrap() - .get_mut(server_name) + .get_mut(key) .and_then(|data| data.tls13.pop_back()) } } @@ -187,166 +148,107 @@ mod cache { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Note: we omit self.servers as it may contain sensitive data. f.debug_struct("ClientSessionMemoryCache") - .finish() + .finish_non_exhaustive() } } } -#[cfg(any(feature = "std", feature = "hashbrown"))] pub use cache::ClientSessionMemoryCache; #[derive(Debug)] pub(super) struct FailResolveClientCert {} -impl client::ResolvesClientCert for FailResolveClientCert { - fn resolve( - &self, - _root_hint_subjects: &[&[u8]], - _sigschemes: &[SignatureScheme], - ) -> Option> { +impl ClientCredentialResolver for FailResolveClientCert { + fn resolve(&self, _: &CredentialRequest<'_>) -> Option { None } - fn has_certs(&self) -> bool { - false + fn supported_certificate_types(&self) -> &'static [CertificateType] { + &[] } -} -#[derive(Debug)] -pub(super) struct AlwaysResolvesClientCert(Arc); - -impl AlwaysResolvesClientCert { - pub(super) fn new( - private_key: Arc, - chain: CertificateChain<'static>, - ) -> Result { - Ok(Self(Arc::new(sign::CertifiedKey::new( - chain.0, - private_key, - )))) - } -} - -impl client::ResolvesClientCert for AlwaysResolvesClientCert { - fn resolve( - &self, - _root_hint_subjects: &[&[u8]], - _sigschemes: &[SignatureScheme], - ) -> Option> { - Some(Arc::clone(&self.0)) - } - - fn has_certs(&self) -> bool { - true - } -} - -/// An exemplar `ResolvesClientCert` implementation that always resolves to a single -/// [RFC 7250] raw public key. -/// -/// [RFC 7250]: https://tools.ietf.org/html/rfc7250 -#[derive(Clone, Debug)] -pub struct AlwaysResolvesClientRawPublicKeys(Arc); -impl AlwaysResolvesClientRawPublicKeys { - /// Create a new `AlwaysResolvesClientRawPublicKeys` instance. - pub fn new(certified_key: Arc) -> Self { - Self(certified_key) - } -} - -impl client::ResolvesClientCert for AlwaysResolvesClientRawPublicKeys { - fn resolve( - &self, - _root_hint_subjects: &[&[u8]], - _sigschemes: &[SignatureScheme], - ) -> Option> { - Some(Arc::clone(&self.0)) - } - - fn only_raw_public_keys(&self) -> bool { - true - } - - /// Returns true if the resolver is ready to present an identity. - /// - /// Even though the function is called `has_certs`, it returns true - /// although only an RPK (Raw Public Key) is available, not an actual certificate. - fn has_certs(&self) -> bool { - true - } + fn hash_config(&self, _: &mut dyn Hasher) {} } #[cfg(test)] -#[macro_rules_attribute::apply(test_for_each_provider)] mod tests { - use alloc::sync::Arc; - use std::prelude::v1::*; + use alloc::vec::Vec; + use core::time::Duration; - use pki_types::{ServerName, UnixTime}; + use pki_types::{CertificateDer, ServerName, UnixTime}; - use super::provider::cipher_suite; use super::NoClientSessionStorage; - use crate::client::ClientSessionStore; - use crate::msgs::base::PayloadU16; - use crate::msgs::enums::NamedGroup; - use crate::msgs::handshake::CertificateChain; - #[cfg(feature = "tls12")] - use crate::msgs::handshake::SessionId; - use crate::msgs::persist::Tls13ClientSessionValue; - use crate::suites::SupportedCipherSuite; + use crate::client::{ + ClientSessionKey, ClientSessionStore, Tls12Session, Tls13ClientSessionInput, Tls13Session, + }; + use crate::crypto::kx::NamedGroup; + use crate::crypto::{ + CertificateIdentity, CipherSuite, Identity, TEST_PROVIDER, tls12_suite, tls13_suite, + }; + use crate::msgs::{ + NewSessionTicketExtensions, NewSessionTicketPayloadTls13, SessionId, SizedPayload, + }; + use crate::sync::Arc; #[test] fn test_noclientsessionstorage_does_nothing() { let c = NoClientSessionStorage {}; - let name = ServerName::try_from("example.com").unwrap(); + let server_name = ServerName::try_from("example.com").unwrap(); + let key = ClientSessionKey { + config_hash: Default::default(), + server_name, + }; let now = UnixTime::now(); - c.set_kx_hint(name.clone(), NamedGroup::X25519); - assert_eq!(None, c.kx_hint(&name)); + c.set_kx_hint(key.clone(), NamedGroup::X25519); + assert_eq!(None, c.kx_hint(&key)); - #[cfg(feature = "tls12")] { - use crate::msgs::persist::Tls12ClientSessionValue; - let SupportedCipherSuite::Tls12(tls12_suite) = - cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - else { - unreachable!() - }; - c.set_tls12_session( - name.clone(), - Tls12ClientSessionValue::new( - tls12_suite, + key.clone(), + Tls12Session::new( + tls12_suite(CipherSuite(0xff12), &TEST_PROVIDER), SessionId::empty(), - Arc::new(PayloadU16::empty()), - &[], - CertificateChain::default(), + Arc::new(SizedPayload::empty()), + &[0u8; 48], + Identity::X509(CertificateIdentity { + end_entity: CertificateDer::from(&[][..]), + intermediates: Vec::new(), + }), now, - 0, + Duration::ZERO, true, ), ); - assert!(c.tls12_session(&name).is_none()); - c.remove_tls12_session(&name); + assert!(c.tls12_session(&key).is_none()); + c.remove_tls12_session(&key); } - let SupportedCipherSuite::Tls13(tls13_suite) = cipher_suite::TLS13_AES_256_GCM_SHA384 - else { - unreachable!(); - }; c.insert_tls13_ticket( - name.clone(), - Tls13ClientSessionValue::new( - tls13_suite, - Arc::new(PayloadU16::empty()), + key.clone(), + Tls13Session::new( + &NewSessionTicketPayloadTls13 { + lifetime: Duration::ZERO, + age_add: 0, + nonce: SizedPayload::empty(), + ticket: Arc::new(SizedPayload::empty()), + extensions: NewSessionTicketExtensions { + max_early_data_size: None, + }, + }, + Tls13ClientSessionInput { + suite: tls13_suite(CipherSuite(0xff13), &TEST_PROVIDER), + peer_identity: Identity::X509(CertificateIdentity { + end_entity: CertificateDer::from(&[][..]), + intermediates: Vec::new(), + }), + quic_params: None, + }, &[], - CertificateChain::default(), now, - 0, - 0, - 0, ), ); - assert!(c.take_tls13_ticket(&name).is_none()); + + assert!(c.take_tls13_ticket(&key).is_none()); } } diff --git a/rustls/src/client/hs.rs b/rustls/src/client/hs.rs index 3a2aee48055..1fa9220be91 100644 --- a/rustls/src/client/hs.rs +++ b/rustls/src/client/hs.rs @@ -1,437 +1,739 @@ -use alloc::borrow::ToOwned; use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt; use core::ops::Deref; use pki_types::ServerName; -#[cfg(feature = "tls12")] -use super::tls12; -use super::Tls12Resumption; -#[cfg(feature = "logging")] -use crate::bs_debug; +use super::config::{ClientSessionKey, Tls12Resumption}; +use super::ech::{EchMode, EchState, EchStatus}; +use super::{ + ClientHelloDetails, ClientSessionCommon, Retrieved, Tls12Session, Tls13Session, tls12, tls13, +}; use crate::check::inappropriate_handshake_message; -use crate::client::client_conn::ClientConnectionData; -use crate::client::common::ClientHelloDetails; -use crate::client::ech::EchState; -use crate::client::{tls13, ClientConfig, EchMode, EchStatus}; -use crate::common_state::{ - CommonState, HandshakeKind, KxState, RawKeyNegotationResult, RawKeyNegotiationParams, State, +use crate::common_state::{EarlyDataEvent, Event, Output, OutputEvent, Protocol}; +use crate::conn::{Input, StateMachine}; +use crate::crypto::cipher::Payload; +use crate::crypto::kx::{KeyExchangeAlgorithm, StartedKeyExchange, SupportedKxGroup}; +use crate::crypto::{CipherSuite, CryptoProvider, rand}; +use crate::enums::{ + ApplicationProtocol, CertificateType, ContentType, HandshakeType, ProtocolVersion, }; -use crate::conn::ConnectionRandoms; -use crate::crypto::{ActiveKeyExchange, KeyExchangeAlgorithm}; -use crate::enums::{AlertDescription, CipherSuite, ContentType, HandshakeType, ProtocolVersion}; -use crate::error::{Error, PeerIncompatible, PeerMisbehaved}; +use crate::error::{ApiMisuse, Error, PeerIncompatible, PeerMisbehaved}; use crate::hash_hs::HandshakeHashBuffer; +use crate::kernel::KernelState; use crate::log::{debug, trace}; -use crate::msgs::base::Payload; -use crate::msgs::enums::{ - CertificateType, Compression, ECPointFormat, ExtensionType, PSKKeyExchangeMode, -}; -use crate::msgs::handshake::{ - CertificateStatusRequest, ClientExtension, ClientHelloPayload, ClientSessionTicket, - ConvertProtocolNameList, HandshakeMessagePayload, HandshakePayload, HasServerExtensions, - HelloRetryRequest, KeyShareEntry, Random, SessionId, +use crate::msgs::{ + CertificateStatusRequest, ClientExtensions, ClientExtensionsInput, ClientHelloPayload, + ClientSessionTicket, Compression, EncryptedClientHello, ExtensionType, HandshakeMessagePayload, + HandshakePayload, HelloRetryRequest, KeyShareEntry, Message, MessagePayload, + PskKeyExchangeModes, Random, ServerHelloPayload, ServerNamePayload, SessionId, + SupportedEcPointFormats, SupportedProtocolVersions, TransportParameters, }; -use crate::msgs::message::{Message, MessagePayload}; -use crate::msgs::persist; -use crate::tls13::key_schedule::KeyScheduleEarly; -use crate::SupportedCipherSuite; +use crate::sealed::Sealed; +use crate::suites::{PartiallyExtractedSecrets, Suite, SupportedCipherSuite}; +use crate::sync::Arc; +use crate::tls12::Tls12CipherSuite; +use crate::tls13::Tls13CipherSuite; +use crate::tls13::key_schedule::{KeyScheduleEarlyClient, KeyScheduleTrafficSend}; +use crate::{ClientConfig, bs_debug}; + +#[expect(private_interfaces)] +pub(crate) enum ClientState { + ServerHello(Box), + ServerHelloOrHelloRetryRequest(Box), + Tls12(tls12::Tls12State), + Tls13(tls13::Tls13State), +} -pub(super) type NextState<'a> = Box + 'a>; -pub(super) type NextStateOrError<'a> = Result, Error>; -pub(super) type ClientContext<'a> = crate::common_state::Context<'a, ClientConnectionData>; +impl StateMachine for ClientState { + fn handle<'m>(self, input: Input<'m>, output: &mut dyn Output<'m>) -> Result { + match self { + Self::ServerHello(e) => e.handle(input, output), + Self::ServerHelloOrHelloRetryRequest(e) => e.handle(input, output), + Self::Tls12(sm) => sm.handle(input, output), + Self::Tls13(sm) => sm.handle(input, output), + } + } -fn find_session( - server_name: &ServerName<'static>, - config: &ClientConfig, - cx: &mut ClientContext<'_>, -) -> Option> { - let found = config - .resumption - .store - .take_tls13_ticket(server_name) - .map(ClientSessionValue::Tls13) - .or_else(|| { - #[cfg(feature = "tls12")] - { - config - .resumption - .store - .tls12_session(server_name) - .map(ClientSessionValue::Tls12) - } + fn wants_input(&self) -> bool { + true + } - #[cfg(not(feature = "tls12"))] - None - }) - .and_then(|resuming| { - let now = config - .current_time() - .map_err(|_err| debug!("Could not get current time: {_err}")) - .ok()?; - - let retrieved = persist::Retrieved::new(resuming, now); - match retrieved.has_expired() { - false => Some(retrieved), - true => None, - } - }) - .or_else(|| { - debug!("No cached session for {:?}", server_name); - None - }); + fn handle_decrypt_error(&mut self) { + if let Self::Tls12(tls12::Tls12State::Finished(e)) = self { + e.handle_decrypt_error(); + } + } - if let Some(resuming) = &found { - if cx.common.is_quic() { - cx.common.quic.params = resuming - .tls13() - .map(|v| v.quic_params()); + fn into_external_state( + self, + send_keys: &Option>, + ) -> Result<(PartiallyExtractedSecrets, Box), Error> { + match self { + Self::Tls12(tls12::Tls12State::Traffic(e)) => e.into_external_state(send_keys), + Self::Tls13(tls13::Tls13State::Traffic(e)) => e.into_external_state(send_keys), + Self::Tls13(tls13::Tls13State::QuicTraffic(e)) => e.into_external_state(send_keys), + _ => Err(Error::HandshakeNotComplete), } } +} - found +pub(crate) struct ExpectServerHello { + pub(super) input: ClientHelloInput, + pub(super) transcript_buffer: HandshakeHashBuffer, + // The key schedule for sending early data. + // + // If the server accepts the PSK used for early data then + // this is used to compute the rest of the key schedule. + // Otherwise, it is thrown away. + // + // If this is `None` then we do not support early data. + pub(super) early_data_key_schedule: Option<(KeyScheduleEarlyClient, bool)>, + pub(super) offered_key_share: Option, + pub(super) suite: Option, + pub(super) ech_state: Option, + pub(super) ech_status: EchStatus, + pub(super) done_retry: bool, } -pub(super) fn start_handshake( - server_name: ServerName<'static>, - extra_exts: Vec, - config: Arc, - cx: &mut ClientContext<'_>, -) -> NextStateOrError<'static> { - let mut transcript_buffer = HandshakeHashBuffer::new(); - if config - .client_auth_cert_resolver - .has_certs() +impl ExpectServerHello { + fn with_version( + mut self, + server_hello: &ServerHelloPayload, + input: &Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result + where + CryptoProvider: Borrow<[&'static T]>, + SupportedCipherSuite: From<&'static T>, { - transcript_buffer.set_client_auth_enabled(); + if server_hello.compression_method != Compression::Null { + return Err(PeerMisbehaved::SelectedUnofferedCompression.into()); + } + + let allowed_unsolicited = [ExtensionType::RenegotiationInfo]; + if self + .input + .hello + .server_sent_unsolicited_extensions(server_hello, &allowed_unsolicited) + { + return Err(PeerMisbehaved::UnsolicitedServerHelloExtension.into()); + } + + output.output(OutputEvent::ProtocolVersion(T::VERSION)); + + // Extract ALPN protocol + if T::VERSION != ProtocolVersion::TLSv1_3 { + process_alpn_protocol( + output, + &self.input.hello.alpn_protocols, + server_hello + .selected_protocol + .as_ref() + .map(|s| s.as_ref()), + self.input.config.check_selected_alpn, + )?; + } + + // If ECPointFormats extension is supplied by the server, it must contain + // Uncompressed. But it's allowed to be omitted. + if let Some(point_fmts) = &server_hello.ec_point_formats { + if !point_fmts.uncompressed { + return Err(PeerMisbehaved::ServerHelloMustOfferUncompressedEcPoints.into()); + } + } + + let suite = >::borrow(self.input.config.provider()) + .iter() + .find(|cs| cs.common().suite == server_hello.cipher_suite) + .ok_or(PeerMisbehaved::SelectedUnofferedCipherSuite)?; + + match self.suite { + Some(prev_suite) if prev_suite.suite() != suite.common().suite => { + return Err(PeerMisbehaved::SelectedDifferentCipherSuiteAfterRetry.into()); + } + _ => { + debug!("Using ciphersuite {suite:?}"); + self.suite = Some(SupportedCipherSuite::from(suite)); + output.output(OutputEvent::CipherSuite(SupportedCipherSuite::from(suite))); + } + } + + // For TLS1.3, start message encryption using + // handshake_traffic_secret. + suite + .client_handler() + .handle_server_hello(suite, server_hello, input, self, output) } +} - let mut resuming = find_session(&server_name, &config, cx); +impl ExpectServerHello { + fn handle( + self: Box, + input: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { + let server_hello = require_handshake_msg!( + &input.message, + HandshakeType::ServerHello, + HandshakePayload::ServerHello + )?; + trace!("We got ServerHello {server_hello:#?}"); - let key_share = if config.supports_version(ProtocolVersion::TLSv1_3) { - Some(tls13::initial_key_share( - &config, - &server_name, - &mut cx.common.kx_state, - )?) - } else { - None - }; + let config = &self.input.config; + let tls13_supported = config.supports_version(ProtocolVersion::TLSv1_3); + + let server_version = if server_hello.legacy_version == ProtocolVersion::TLSv1_2 { + server_hello + .selected_version + .unwrap_or(server_hello.legacy_version) + } else { + server_hello.legacy_version + }; - let session_id = if let Some(_resuming) = &mut resuming { - debug!("Resuming session"); - - match &mut _resuming.value { - #[cfg(feature = "tls12")] - ClientSessionValue::Tls12(inner) => { - // If we have a ticket, we use the sessionid as a signal that - // we're doing an abbreviated handshake. See section 3.4 in - // RFC5077. - if !inner.ticket().0.is_empty() { - inner.session_id = SessionId::random(config.provider.secure_random)?; + match server_version { + ProtocolVersion::TLSv1_3 if tls13_supported => { + self.with_version::(server_hello, &input, output) + } + ProtocolVersion::TLSv1_2 if config.supports_version(ProtocolVersion::TLSv1_2) => { + if let Some((_, true)) = &self.early_data_key_schedule { + // The client must fail with a dedicated error code if the server + // responds with TLS 1.2 when offering 0-RTT. + return Err(PeerMisbehaved::OfferedEarlyDataWithOldProtocolVersion.into()); } - Some(inner.session_id) + + if server_hello.selected_version.is_some() { + return Err(PeerMisbehaved::SelectedTls12UsingTls13VersionExtension.into()); + } + + self.with_version::(server_hello, &input, output) + } + _ => { + let reason = match server_version { + ProtocolVersion::TLSv1_2 | ProtocolVersion::TLSv1_3 => { + PeerIncompatible::ServerTlsVersionIsDisabledByOurConfig + } + _ => PeerIncompatible::ServerDoesNotSupportTls12Or13, + }; + Err(reason.into()) } - _ => None, } - } else { - debug!("Not resuming any session"); - None - }; + } +} - // https://tools.ietf.org/html/rfc8446#appendix-D.4 - // https://tools.ietf.org/html/draft-ietf-quic-tls-34#section-8.4 - let session_id = match session_id { - Some(session_id) => session_id, - None if cx.common.is_quic() => SessionId::empty(), - None if !config.supports_version(ProtocolVersion::TLSv1_3) => SessionId::empty(), - None => SessionId::random(config.provider.secure_random)?, - }; +struct ExpectServerHelloOrHelloRetryRequest { + next: Box, + extra_exts: ClientExtensionsInput, +} + +impl ExpectServerHelloOrHelloRetryRequest { + fn into_expect_server_hello(self) -> ClientState { + ClientState::ServerHello(self.next) + } - let random = Random::new(config.provider.secure_random)?; - let extension_order_seed = crate::rand::random_u16(config.provider.secure_random)?; + fn handle_hello_retry_request( + mut self, + input: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { + let hrr = require_handshake_msg!( + input.message, + HandshakeType::HelloRetryRequest, + HandshakePayload::HelloRetryRequest + )?; + trace!("Got HRR {hrr:?}"); - let ech_state = match config.ech_mode.as_ref() { - Some(EchMode::Enable(ech_config)) => Some(EchState::new( - ech_config, - server_name.clone(), - config - .client_auth_cert_resolver - .has_certs(), - config.provider.secure_random, - config.enable_sni, - )?), - _ => None, - }; + let proof = input.check_aligned_handshake()?; - emit_client_hello_for_retry( - transcript_buffer, - None, - key_share, - extra_exts, - None, - ClientHelloInput { + // We always send a key share when TLS 1.3 is enabled. + let offered_key_share = self.next.offered_key_share.unwrap(); + + // A retry request is illegal if it contains no cookie and asks for + // retry of a group we already sent. + let config = &self.next.input.config; + + if let (None, Some(req_group)) = (&hrr.cookie, hrr.key_share) { + let offered_hybrid = offered_key_share + .share + .as_hybrid_checked(&config.provider().kx_groups, ProtocolVersion::TLSv1_3) + .map(|(hybrid, _)| hybrid.component().0); + + if req_group == offered_key_share.share.group() || Some(req_group) == offered_hybrid { + return Err(PeerMisbehaved::IllegalHelloRetryRequestWithOfferedGroup.into()); + } + } + + // Or has an empty cookie. + if let Some(cookie) = &hrr.cookie { + if cookie.is_empty() { + return Err(PeerMisbehaved::IllegalHelloRetryRequestWithEmptyCookie.into()); + } + } + + // Or asks us to change nothing. + if hrr.cookie.is_none() && hrr.key_share.is_none() { + return Err(PeerMisbehaved::IllegalHelloRetryRequestWithNoChanges.into()); + } + + // Or does not echo the session_id from our ClientHello: + // + // > the HelloRetryRequest has the same format as a ServerHello message, + // > and the legacy_version, legacy_session_id_echo, cipher_suite, and + // > legacy_compression_method fields have the same meaning + // + // + // and + // + // > A client which receives a legacy_session_id_echo field that does not + // > match what it sent in the ClientHello MUST abort the handshake with an + // > "illegal_parameter" alert. + // + if hrr.session_id != self.next.input.session_id { + return Err(PeerMisbehaved::IllegalHelloRetryRequestWithWrongSessionId.into()); + } + + // Or asks us to talk a protocol we didn't offer, or doesn't support HRR at all. + match hrr.supported_versions { + Some(ProtocolVersion::TLSv1_3) => { + output.output(OutputEvent::ProtocolVersion(ProtocolVersion::TLSv1_3)); + } + _ => { + return Err(PeerMisbehaved::IllegalHelloRetryRequestWithUnsupportedVersion.into()); + } + } + + // Or asks us to use a ciphersuite we didn't offer. + let Some(cs) = config.find_cipher_suite(hrr.cipher_suite) else { + return Err(PeerMisbehaved::IllegalHelloRetryRequestWithUnofferedCipherSuite.into()); + }; + + // Or offers ECH related extensions when we didn't offer ECH. + if self.next.ech_status == EchStatus::NotOffered && hrr.encrypted_client_hello.is_some() { + return Err(PeerMisbehaved::IllegalHelloRetryRequestWithInvalidEch.into()); + } + + // HRR selects the ciphersuite. + output.output(OutputEvent::CipherSuite(cs)); + + // If we offered ECH, we need to confirm that the server accepted it. + match (self.next.ech_state.as_ref(), cs) { + (Some(ech_state), SupportedCipherSuite::Tls13(tls13_cs)) + if !ech_state.confirm_hrr_acceptance(hrr, tls13_cs)? => + { + // If the server did not confirm, then note the new ECH status but + // continue the handshake. We will abort with an ECH required error + // at the end. + self.next.ech_status = EchStatus::Rejected; + output.emit(Event::EchStatus(EchStatus::Rejected)); + } + (Some(_), SupportedCipherSuite::Tls12(_)) => { + unreachable!("ECH state should only be set when TLS 1.3 was negotiated") + } + _ => {} + }; + + // This is the draft19 change where the transcript became a tree + let transcript = self + .next + .transcript_buffer + .start_hash(cs.hash_provider()); + let mut transcript_buffer = transcript.into_hrr_buffer(&proof); + transcript_buffer.add_message(&input.message); + + // If we offered ECH and the server accepted, we also need to update the separate + // ECH transcript with the hello retry request message. + if let Some(ech_state) = self.next.ech_state.as_mut() { + ech_state.transcript_hrr_update(cs.hash_provider(), &input.message, &proof); + } + + let key_share = match hrr.key_share { + Some(group) if group != offered_key_share.share.group() => { + let Some(skxg) = config + .provider() + .find_kx_group(group, ProtocolVersion::TLSv1_3) + else { + return Err( + PeerMisbehaved::IllegalHelloRetryRequestWithUnofferedNamedGroup.into(), + ); + }; + + GroupAndKeyShare::new(skxg)? + } + _ => offered_key_share, + }; + + emit_client_hello_for_retry( + transcript_buffer, + Some(hrr), + Some(key_share), + self.extra_exts, + Some(cs), + self.next.input, + output, + self.next.ech_state, + self.next.ech_status, + ) + } +} + +impl ExpectServerHelloOrHelloRetryRequest { + fn handle<'m>( + self, + input: Input<'m>, + output: &mut dyn Output<'m>, + ) -> Result { + match input.message.payload { + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::ServerHello(..)), + .. + } => self + .into_expect_server_hello() + .handle(input, output), + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::HelloRetryRequest(..)), + .. + } => self.handle_hello_retry_request(input, output), + payload => Err(inappropriate_handshake_message( + &payload, + &[ContentType::Handshake], + &[HandshakeType::ServerHello, HandshakeType::HelloRetryRequest], + )), + } + } +} + +pub(crate) struct ClientHelloInput { + pub(super) config: Arc, + pub(super) resuming: Option>, + pub(super) random: Random, + pub(super) sent_tls13_fake_ccs: bool, + pub(super) hello: ClientHelloDetails, + pub(super) protocol: Protocol, + pub(super) session_id: SessionId, + pub(super) session_key: ClientSessionKey<'static>, + pub(super) prev_ech_ext: Option, +} + +impl ClientHelloInput { + pub(super) fn new( + server_name: ServerName<'static>, + extra_exts: &ClientExtensionsInput, + protocol: Protocol, + output: &mut dyn Output<'_>, + config: Arc, + ) -> Result { + let session_key = ClientSessionKey { + config_hash: config.config_hash(), + server_name, + }; + let mut resuming = ClientSessionValue::retrieve(&session_key, &config, output); + let session_id = match &mut resuming { + Some(resuming) => { + debug!("Resuming session"); + match &mut resuming.value { + ClientSessionValue::Tls12(inner) => { + // If we have a ticket, we use the sessionid as a signal that + // we're doing an abbreviated handshake. See section 3.4 in + // RFC5077. + if !inner.ticket().is_empty() { + inner.session_id = SessionId::random(config.provider().secure_random)?; + } + Some(inner.session_id) + } + _ => None, + } + } + _ => { + debug!("Not resuming any session"); + None + } + }; + + // https://tools.ietf.org/html/rfc8446#appendix-D.4 + // https://tools.ietf.org/html/rfc9001#section-8.4 + let session_id = match session_id { + Some(session_id) => session_id, + None if output.quic().is_some() => SessionId::empty(), + None if !config.supports_version(ProtocolVersion::TLSv1_3) => SessionId::empty(), + None => SessionId::random(config.provider().secure_random)?, + }; + + let hello = ClientHelloDetails::new( + extra_exts + .protocols + .clone() + .unwrap_or_default(), + rand::random_u16(config.provider().secure_random)?, + ); + + let random = Random::new(config.provider().secure_random)?; + Ok(Self { config, resuming, random, - #[cfg(feature = "tls12")] - using_ems: false, sent_tls13_fake_ccs: false, - hello: ClientHelloDetails::new(extension_order_seed), + hello, + protocol, session_id, - server_name, + session_key, prev_ech_ext: None, - }, - cx, - ech_state, - ) -} + }) + } -struct ExpectServerHello { - input: ClientHelloInput, - transcript_buffer: HandshakeHashBuffer, - early_key_schedule: Option, - offered_key_share: Option>, - suite: Option, - ech_state: Option, -} + pub(super) fn start_handshake( + self, + extra_exts: ClientExtensionsInput, + output: &mut dyn Output<'_>, + ) -> Result { + let mut transcript_buffer = HandshakeHashBuffer::new(); + if !self + .config + .resolver() + .supported_certificate_types() + .is_empty() + { + transcript_buffer.set_client_auth_enabled(); + } -struct ExpectServerHelloOrHelloRetryRequest { - next: ExpectServerHello, - extra_exts: Vec, -} + let key_share = if self + .config + .supports_version(ProtocolVersion::TLSv1_3) + { + Some(tls13::initial_key_share(&self.config, &self.session_key)?) + } else { + None + }; -struct ClientHelloInput { - config: Arc, - resuming: Option>, - random: Random, - #[cfg(feature = "tls12")] - using_ems: bool, - sent_tls13_fake_ccs: bool, - hello: ClientHelloDetails, - session_id: SessionId, - server_name: ServerName<'static>, - prev_ech_ext: Option, + let ech_state = match self.config.ech_mode.as_ref() { + Some(EchMode::Enable(ech_config)) => Some(ech_config.state( + self.session_key.server_name.clone(), + self.protocol, + &self.config, + )?), + _ => None, + }; + + emit_client_hello_for_retry( + transcript_buffer, + None, + key_share, + extra_exts, + None, + self, + output, + ech_state, + EchStatus::default(), + ) + } } +/// Emits the initial ClientHello or a ClientHello in response to +/// a HelloRetryRequest. +/// +/// `retryreq` and `suite` are `None` if this is the initial +/// ClientHello. fn emit_client_hello_for_retry( mut transcript_buffer: HandshakeHashBuffer, retryreq: Option<&HelloRetryRequest>, - key_share: Option>, - extra_exts: Vec, + key_share: Option, + extra_exts: ClientExtensionsInput, suite: Option, mut input: ClientHelloInput, - cx: &mut ClientContext<'_>, + output: &mut dyn Output<'_>, mut ech_state: Option, -) -> NextStateOrError<'static> { + mut ech_status: EchStatus, +) -> Result { let config = &input.config; // Defense in depth: the ECH state should be None if ECH is disabled based on config // builder semantics. - let forbids_tls12 = cx.common.is_quic() || ech_state.is_some(); - let support_tls12 = config.supports_version(ProtocolVersion::TLSv1_2) && !forbids_tls12; - let support_tls13 = config.supports_version(ProtocolVersion::TLSv1_3); + let forbids_tls12 = input.protocol.is_quic() || ech_state.is_some(); - let mut supported_versions = Vec::new(); - if support_tls13 { - supported_versions.push(ProtocolVersion::TLSv1_3); - } - - if support_tls12 { - supported_versions.push(ProtocolVersion::TLSv1_2); - } + let supported_versions = SupportedProtocolVersions { + tls13: config.supports_version(ProtocolVersion::TLSv1_3), + tls12: config.supports_version(ProtocolVersion::TLSv1_2) && !forbids_tls12, + }; // should be unreachable thanks to config builder - assert!(!supported_versions.is_empty()); + assert!(supported_versions.any(|_| true)); - // offer groups which are usable for any offered version - let offered_groups = config - .provider - .kx_groups - .iter() - .filter(|skxg| { - supported_versions + let mut exts = Box::new(ClientExtensions { + certificate_status_request: match config + .verifier() + .request_ocsp_response() + { + true => Some(CertificateStatusRequest::build_ocsp()), + false => None, + }, + // offer groups which are usable for any offered version + named_groups: Some( + config + .provider() + .kx_groups .iter() - .any(|v| skxg.usable_for_version(*v)) - }) - .map(|skxg| skxg.name()) - .collect(); - - let mut exts = vec![ - ClientExtension::SupportedVersions(supported_versions), - ClientExtension::NamedGroups(offered_groups), - ClientExtension::SignatureAlgorithms( + .filter_map(|skxg| { + let named_group = skxg.name(); + supported_versions + .any(|v| named_group.usable_for_version(v)) + .then_some(named_group) + }) + .collect(), + ), + signature_schemes: Some( config - .verifier + .verifier() .supported_verify_schemes(), ), - ClientExtension::ExtendedMasterSecretRequest, - ClientExtension::CertificateStatusRequest(CertificateStatusRequest::build_ocsp()), - ]; + protocols: extra_exts.protocols.clone(), + extended_master_secret_request: Some(()), + supported_versions: Some(supported_versions), + ..Default::default() + }); - if support_tls13 { - if let Some(cas_extension) = config.verifier.root_hint_subjects() { - exts.push(ClientExtension::AuthorityNames(cas_extension.to_owned())); + if let Some(TransportParameters::Quic(v)) = &extra_exts.transport_parameters { + exts.transport_parameters = Some(v.clone()); + } + + if supported_versions.tls13 { + if let Some(cas_extension) = config.verifier().root_hint_subjects() { + exts.certificate_authority_names = Some(cas_extension.to_vec()); } } // Send the ECPointFormat extension only if we are proposing ECDHE if config - .provider + .provider() .kx_groups .iter() .any(|skxg| skxg.name().key_exchange_algorithm() == KeyExchangeAlgorithm::ECDHE) { - exts.push(ClientExtension::EcPointFormats( - ECPointFormat::SUPPORTED.to_vec(), - )); + exts.ec_point_formats = Some(SupportedEcPointFormats::default()); } - match (ech_state.as_ref(), config.enable_sni) { + exts.server_name = match (ech_state.as_ref(), config.enable_sni) { // If we have ECH state we have a "cover name" to send in the outer hello // as the SNI domain name. This happens unconditionally so we ignore the // `enable_sni` value. That will be used later to decide what to do for // the protected inner hello's SNI. - (Some(ech_state), _) => exts.push(ClientExtension::make_sni(&ech_state.outer_name)), + (Some(ech_state), _) => Some(ServerNamePayload::from(&ech_state.outer_name)), // If we have no ECH state, and SNI is enabled, try to use the input server_name // for the SNI domain name. - (None, true) => { - if let ServerName::DnsName(dns_name) = &input.server_name { - exts.push(ClientExtension::make_sni(dns_name)) - } - } + (None, true) => match &input.session_key.server_name { + ServerName::DnsName(dns_name) => Some(ServerNamePayload::from(dns_name)), + _ => None, + }, // If we have no ECH state, and SNI is not enabled, there's nothing to do. - (None, false) => {} + (None, false) => None, }; - if let Some(key_share) = &key_share { - debug_assert!(support_tls13); - let mut shares = vec![KeyShareEntry::new(key_share.group(), key_share.pub_key())]; - - if retryreq.is_none() { - // Only for the initial client hello, see if we can send a second KeyShare - // for "free". We only do this if the same algorithm is also supported - // separately by our provider for this version (`find_kx_group` looks that up). - if let Some((component_group, component_share)) = - key_share - .hybrid_component() - .filter(|(group, _)| { - config - .find_kx_group(*group, ProtocolVersion::TLSv1_3) - .is_some() - }) + if let Some(GroupAndKeyShare { share, .. }) = &key_share { + debug_assert!(supported_versions.tls13); + let mut shares = vec![KeyShareEntry::new(share.group(), share.pub_key())]; + + if !retryreq + .map(|rr| rr.key_share.is_some()) + .unwrap_or_default() + { + // Only for the initial client hello, or a HRR that does not specify a kx group, + // see if we can send a second KeyShare for "free". We only do this if the same + // algorithm is also supported separately by our provider for this version + // (via `component_separately_supported`). + if let Some((hybrid, _)) = + share.as_hybrid_checked(&config.provider().kx_groups, ProtocolVersion::TLSv1_3) { + let (component_group, component_share) = hybrid.component(); shares.push(KeyShareEntry::new(component_group, component_share)); } } - exts.push(ClientExtension::KeyShare(shares)); + exts.key_shares = Some(shares); } - if let Some(cookie) = retryreq.and_then(HelloRetryRequest::cookie) { - exts.push(ClientExtension::Cookie(cookie.clone())); + if let Some(cookie) = retryreq.and_then(|hrr| hrr.cookie.as_ref()) { + exts.cookie = Some(cookie.to_vec().into()); } - if support_tls13 { + if supported_versions.tls13 { // We could support PSK_KE here too. Such connections don't // have forward secrecy, and are similar to TLS1.2 resumption. - let psk_modes = vec![PSKKeyExchangeMode::PSK_DHE_KE]; - exts.push(ClientExtension::PresharedKeyModes(psk_modes)); - } - - if !config.alpn_protocols.is_empty() { - exts.push(ClientExtension::Protocols(Vec::from_slices( - &config - .alpn_protocols - .iter() - .map(|proto| &proto[..]) - .collect::>(), - ))); + exts.preshared_key_modes = Some(PskKeyExchangeModes { + psk_dhe: true, + psk: false, + }); } - input.hello.offered_cert_compression = if support_tls13 && !config.cert_decompressors.is_empty() - { - exts.push(ClientExtension::CertificateCompressionAlgorithms( - config - .cert_decompressors - .iter() - .map(|dec| dec.algorithm()) - .collect(), - )); - true - } else { - false - }; - - if config - .client_auth_cert_resolver - .only_raw_public_keys() - { - exts.push(ClientExtension::ClientCertTypes(vec![ - CertificateType::RawPublicKey, - ])); - } + input.hello.offered_cert_compression = + if supported_versions.tls13 && !config.cert_decompressors.is_empty() { + exts.certificate_compression_algorithms = Some( + config + .cert_decompressors + .iter() + .map(|dec| dec.algorithm()) + .collect(), + ); + true + } else { + false + }; - if config - .verifier - .requires_raw_public_keys() - { - exts.push(ClientExtension::ServerCertTypes(vec![ - CertificateType::RawPublicKey, - ])); + let client_certificate_types = config + .resolver() + .supported_certificate_types(); + match client_certificate_types { + &[] | &[CertificateType::X509] => {} + supported => { + exts.client_certificate_types = Some(supported.to_vec()); + } } - // Extra extensions must be placed before the PSK extension - exts.extend(extra_exts.iter().cloned()); + let server_certificate_types = config + .verifier() + .supported_certificate_types(); + match server_certificate_types { + [] => return Err(ApiMisuse::NoSupportedCertificateTypes.into()), + [CertificateType::X509] => {} + supported => { + exts.server_certificate_types = Some(supported.to_vec()); + } + } // If this is a second client hello we're constructing in response to an HRR, and // we've rejected ECH or sent GREASE ECH, then we need to carry forward the // exact same ECH extension we used in the first hello. - if matches!(cx.data.ech_status, EchStatus::Rejected | EchStatus::Grease) & retryreq.is_some() { + if matches!(ech_status, EchStatus::Rejected | EchStatus::Grease) & retryreq.is_some() { if let Some(prev_ech_ext) = input.prev_ech_ext.take() { - exts.push(prev_ech_ext); + exts.encrypted_client_hello = Some(prev_ech_ext); } } // Do we have a SessionID or ticket cached for this host? - let tls13_session = prepare_resumption(&input.resuming, &mut exts, suite, cx, config); + let tls13_session = prepare_resumption(&input.resuming, &mut exts, suite, output, config); + let (tls13_session, early_data_enabled) = match tls13_session { + Some((tls13_session, early_data_enabled)) => (Some(tls13_session), early_data_enabled), + _ => (None, false), + }; // Extensions MAY be randomized // but they also need to keep the same order as the previous ClientHello - exts.sort_by_cached_key(|new_ext| { - match (&cx.data.ech_status, new_ext) { - // When not offering ECH/GREASE, the PSK extension is always last. - (EchStatus::NotOffered, ClientExtension::PresharedKey(..)) => return u32::MAX, - // When ECH or GREASE are in-play, the ECH extension is always last. - (_, ClientExtension::EncryptedClientHello(_)) => return u32::MAX, - // ... and the PSK extension should be second-to-last. - (_, ClientExtension::PresharedKey(..)) => return u32::MAX - 1, - _ => {} - }; - - let seed = ((input.hello.extension_order_seed as u32) << 16) - | (u16::from(new_ext.ext_type()) as u32); - match low_quality_integer_hash(seed) { - u32::MAX => 0, - key => key, - } - }); + exts.order_seed = input.hello.extension_order_seed; let mut cipher_suites: Vec<_> = config - .provider - .cipher_suites - .iter() - .filter_map(|cs| match cs.usable_for_protocol(cx.common.protocol) { + .provider() + .iter_cipher_suites() + .filter_map(|cs| match cs.usable_for_protocol(input.protocol) { true => Some(cs.suite()), false => None, }) .collect(); - // We don't do renegotiation at all, in fact. - cipher_suites.push(CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV); + + if supported_versions.tls12 { + // We don't do renegotiation at all, in fact. + cipher_suites.push(CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV); + } let mut chp_payload = ClientHelloPayload { client_version: ProtocolVersion::TLSv1_2, @@ -447,22 +749,26 @@ fn emit_client_hello_for_retry( .as_ref() .and_then(|mode| match mode { EchMode::Grease(cfg) => Some(cfg.grease_ext( - config.provider.secure_random, - input.server_name.clone(), + config.provider().secure_random, + input.protocol, + input.session_key.server_name.clone(), &chp_payload, )), _ => None, }); - match (cx.data.ech_status, &mut ech_state) { + match (ech_status, &mut ech_state) { // If we haven't offered ECH, or have offered ECH but got a non-rejecting HRR, then // we need to replace the client hello payload with an ECH client hello payload. (EchStatus::NotOffered | EchStatus::Offered, Some(ech_state)) => { // Replace the client hello payload with an ECH client hello payload. - chp_payload = ech_state.ech_hello(chp_payload, retryreq, &tls13_session)?; - cx.data.ech_status = EchStatus::Offered; + chp_payload = ech_state.ech_hello(chp_payload, retryreq, tls13_session.as_ref())?; + ech_status = EchStatus::Offered; + output.emit(Event::EchStatus(ech_status)); // Store the ECH extension in case we need to carry it forward in a subsequent hello. - input.prev_ech_ext = chp_payload.extensions.last().cloned(); + input.prev_ech_ext = chp_payload + .encrypted_client_hello + .clone(); } // If we haven't offered ECH, and have no ECH state, then consider whether to use GREASE // ECH. @@ -470,10 +776,9 @@ fn emit_client_hello_for_retry( if let Some(grease_ext) = ech_grease_ext { // Add the GREASE ECH extension. let grease_ext = grease_ext?; - chp_payload - .extensions - .push(grease_ext.clone()); - cx.data.ech_status = EchStatus::Grease; + chp_payload.encrypted_client_hello = Some(grease_ext.clone()); + ech_status = EchStatus::Grease; + output.emit(Event::EchStatus(ech_status)); // Store the GREASE ECH extension in case we need to carry it forward in a // subsequent hello. input.prev_ech_ext = Some(grease_ext); @@ -483,31 +788,29 @@ fn emit_client_hello_for_retry( } // Note what extensions we sent. - input.hello.sent_extensions = chp_payload - .extensions - .iter() - .map(ClientExtension::ext_type) - .collect(); + input.hello.sent_extensions = chp_payload.collect_used(); - let mut chp = HandshakeMessagePayload { - typ: HandshakeType::ClientHello, - payload: HandshakePayload::ClientHello(chp_payload), - }; + let mut chp = HandshakeMessagePayload(HandshakePayload::ClientHello(chp_payload)); - let early_key_schedule = match (ech_state.as_mut(), tls13_session) { + let tls13_early_data_key_schedule = match (ech_state.as_mut(), tls13_session) { // If we're performing ECH and resuming, then the PSK binder will have been dealt with // separately, and we need to take the early_data_key_schedule computed for the inner hello. (Some(ech_state), Some(tls13_session)) => ech_state .early_data_key_schedule .take() - .map(|schedule| (tls13_session.suite(), schedule)), + .map(|schedule| (tls13_session.suite, schedule)), // When we're not doing ECH and resuming, then the PSK binder need to be filled in as // normal. - (_, Some(tls13_session)) => Some(( - tls13_session.suite(), - tls13::fill_in_psk_binder(&tls13_session, &transcript_buffer, &mut chp), - )), + (_, Some(tls13_session)) => { + let key_schedule = KeyScheduleEarlyClient::new( + input.protocol, + tls13_session.suite, + tls13_session.secret.bytes(), + ); + tls13::fill_in_psk_binder(&key_schedule, &transcript_buffer, &mut chp); + Some((tls13_session.suite, key_schedule)) + } // No early key schedule in other cases. _ => None, @@ -532,59 +835,86 @@ fn emit_client_hello_for_retry( if retryreq.is_some() { // send dummy CCS to fool middleboxes prior // to second client hello - tls13::emit_fake_ccs(&mut input.sent_tls13_fake_ccs, cx.common); + tls13::emit_fake_ccs(&mut input.sent_tls13_fake_ccs, output); } - trace!("Sending ClientHello {:#?}", ch); + trace!("Sending ClientHello {ch:#?}"); transcript_buffer.add_message(&ch); - cx.common.send_msg(ch, false); + output.send_msg(ch, false); // Calculate the hash of ClientHello and use it to derive EarlyTrafficSecret - let early_key_schedule = early_key_schedule.map(|(resuming_suite, schedule)| { - if !cx.data.early_data.is_enabled() { - return schedule; - } - - let (transcript_buffer, random) = match &ech_state { - // When using ECH the early data key schedule is derived based on the inner - // hello transcript and random. - Some(ech_state) => ( - &ech_state.inner_hello_transcript, - &ech_state.inner_hello_random.0, - ), - None => (&transcript_buffer, &input.random.0), - }; + let early_data_key_schedule = + tls13_early_data_key_schedule.map(|(resuming_suite, schedule)| { + if !early_data_enabled { + // No early data if a HelloRetryRequest happens + output.emit(Event::EarlyData(EarlyDataEvent::Rejected)); + return (schedule, false); + } - tls13::derive_early_traffic_secret( - &*config.key_log, - cx, - resuming_suite, - &schedule, - &mut input.sent_tls13_fake_ccs, - transcript_buffer, - random, - ); - schedule - }); + let (transcript_buffer, random) = match &ech_state { + // When using ECH the early data key schedule is derived based on the inner + // hello transcript and random. + Some(ech_state) => ( + &ech_state.inner_hello_transcript, + &ech_state.inner_hello_random.0, + ), + None => (&transcript_buffer, &input.random.0), + }; + + tls13::derive_early_traffic_secret( + &*config.key_log, + output, + resuming_suite.common.hash_provider, + &schedule, + &mut input.sent_tls13_fake_ccs, + transcript_buffer, + random, + ); + (schedule, true) + }); - let next = ExpectServerHello { + let mut next = Box::new(ExpectServerHello { input, transcript_buffer, - early_key_schedule, + early_data_key_schedule, offered_key_share: key_share, suite, ech_state, - }; + ech_status, + done_retry: false, + }); - Ok(if support_tls13 && retryreq.is_none() { - Box::new(ExpectServerHelloOrHelloRetryRequest { next, extra_exts }) + Ok(if supported_versions.tls13 && retryreq.is_none() { + ClientState::ServerHelloOrHelloRetryRequest(Box::new( + ExpectServerHelloOrHelloRetryRequest { next, extra_exts }, + )) } else { - Box::new(next) + next.done_retry = retryreq.is_some(); + ClientState::ServerHello(next) }) } -/// Prepare resumption with the session state retrieved from storage. +pub(super) struct GroupAndKeyShare { + pub(super) group: &'static dyn SupportedKxGroup, + pub(super) share: StartedKeyExchange, +} + +impl GroupAndKeyShare { + pub(super) fn new(group: &'static dyn SupportedKxGroup) -> Result { + Ok(Self { + group, + share: group.start()?, + }) + } +} + +/// Prepares `exts` and `cx` with TLS 1.2 or TLS 1.3 session +/// resumption. +/// +/// - `suite` is `None` if this is the initial ClientHello, or +/// `Some` if we're retrying in response to +/// a HelloRetryRequest. /// /// This function will push onto `exts` to /// @@ -593,17 +923,15 @@ fn emit_client_hello_for_retry( /// (c) send a request for 1.3 early data if allowed and /// (d) send a 1.3 preshared key if we have one. /// -/// For resumption to work, the currently negotiated cipher suite (if available) must be -/// able to resume from the resuming session's cipher suite. -/// -/// If 1.3 resumption can continue, returns the 1.3 session value for further processing. +/// It returns the TLS 1.3 PSKs, if any, for further processing, +/// and a flag indicated whether early data is being attempted. fn prepare_resumption<'a>( - resuming: &'a Option>, - exts: &mut Vec, + resuming: &'a Option>, + exts: &mut ClientExtensions<'_>, suite: Option, - cx: &mut ClientContext<'_>, + output: &mut dyn Output<'_>, config: &ClientConfig, -) -> Option> { +) -> Option<(Retrieved<&'a Tls13Session>, bool)> { // Check whether we're resuming with a non-empty ticket. let resuming = match resuming { Some(resuming) if !resuming.ticket().is_empty() => resuming, @@ -612,7 +940,7 @@ fn prepare_resumption<'a>( && config.resumption.tls12_resumption == Tls12Resumption::SessionIdOrTickets { // If we don't have a ticket, request one. - exts.push(ClientExtension::SessionTicket(ClientSessionTicket::Request)); + exts.session_ticket = Some(ClientSessionTicket::Request); } return None; } @@ -623,9 +951,7 @@ fn prepare_resumption<'a>( if config.supports_version(ProtocolVersion::TLSv1_2) && config.resumption.tls12_resumption == Tls12Resumption::SessionIdOrTickets { - exts.push(ClientExtension::SessionTicket(ClientSessionTicket::Offer( - Payload::new(resuming.ticket()), - ))); + exts.session_ticket = Some(ClientSessionTicket::Offer(Payload::new(resuming.ticket()))); } return None; // TLS 1.2, so nothing to return here }; @@ -637,600 +963,128 @@ fn prepare_resumption<'a>( // If the server selected TLS 1.2, we can't resume. let suite = match suite { Some(SupportedCipherSuite::Tls13(suite)) => Some(suite), - #[cfg(feature = "tls12")] Some(SupportedCipherSuite::Tls12(_)) => return None, None => None, }; // If the selected cipher suite can't select from the session's, we can't resume. if let Some(suite) = suite { - suite.can_resume_from(tls13.suite())?; + suite.can_resume_from(tls13.suite)?; } - tls13::prepare_resumption(config, cx, &tls13, exts, suite.is_some()); - Some(tls13) + let early_data_enabled = + tls13::prepare_resumption(config, output, &tls13, exts, suite.is_some()); + Some((tls13, early_data_enabled)) } pub(super) fn process_alpn_protocol( - common: &mut CommonState, - config: &ClientConfig, - proto: Option<&[u8]>, + output: &mut dyn Output<'_>, + offered_protocols: &[ApplicationProtocol<'_>], + selected: Option<&ApplicationProtocol<'_>>, + check_selected_offered: bool, ) -> Result<(), Error> { - common.alpn_protocol = proto.map(ToOwned::to_owned); - - if let Some(alpn_protocol) = &common.alpn_protocol { - if !config - .alpn_protocols - .contains(alpn_protocol) - { - return Err(common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::SelectedUnofferedApplicationProtocol, - )); + if let Some(alpn_protocol) = selected { + if check_selected_offered && !offered_protocols.contains(alpn_protocol) { + return Err(PeerMisbehaved::SelectedUnofferedApplicationProtocol.into()); } } - // RFC 9001 says: "While ALPN only specifies that servers use this alert, QUIC clients MUST - // use error 0x0178 to terminate a connection when ALPN negotiation fails." We judge that - // the user intended to use ALPN (rather than some out-of-band protocol negotiation - // mechanism) if and only if any ALPN protocols were configured. This defends against badly-behaved - // servers which accept a connection that requires an application-layer protocol they do not - // understand. - if common.is_quic() && common.alpn_protocol.is_none() && !config.alpn_protocols.is_empty() { - return Err(common.send_fatal_alert( - AlertDescription::NoApplicationProtocol, - Error::NoApplicationProtocol, - )); - } - debug!( "ALPN protocol is {:?}", - common - .alpn_protocol + selected .as_ref() - .map(|v| bs_debug::BsDebug(v)) + .map(|v| bs_debug::BsDebug(v.as_ref())) ); - Ok(()) -} -pub(super) fn process_server_cert_type_extension( - common: &mut CommonState, - config: &ClientConfig, - server_cert_extension: Option<&CertificateType>, -) -> Result<(), Error> { - let requires_server_rpk = config - .verifier - .requires_raw_public_keys(); - let server_offers_rpk = matches!(server_cert_extension, Some(CertificateType::RawPublicKey)); - - let raw_key_negotation_params = RawKeyNegotiationParams { - peer_supports_raw_key: server_offers_rpk, - local_expects_raw_key: requires_server_rpk, - extension_type: ExtensionType::ServerCertificateType, - }; - match raw_key_negotation_params.validate_raw_key_negotiation() { - RawKeyNegotationResult::Err(err) => { - Err(common.send_fatal_alert(AlertDescription::HandshakeFailure, err)) - } - _ => Ok(()), + if let Some(protocol) = selected { + output.output(OutputEvent::ApplicationProtocol(protocol.to_owned())); } -} -pub(super) fn process_client_cert_type_extension( - common: &mut CommonState, - config: &ClientConfig, - client_cert_extension: Option<&CertificateType>, -) -> Result<(), Error> { - let requires_client_rpk = config - .client_auth_cert_resolver - .only_raw_public_keys(); - let server_allows_rpk = matches!(client_cert_extension, Some(CertificateType::RawPublicKey)); - - let raw_key_negotation_params = RawKeyNegotiationParams { - peer_supports_raw_key: server_allows_rpk, - local_expects_raw_key: requires_client_rpk, - extension_type: ExtensionType::ClientCertificateType, - }; - match raw_key_negotation_params.validate_raw_key_negotiation() { - RawKeyNegotationResult::Err(err) => { - Err(common.send_fatal_alert(AlertDescription::HandshakeFailure, err)) - } - _ => Ok(()), - } + Ok(()) } -impl State for ExpectServerHello { - fn handle<'m>( - mut self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> NextStateOrError<'m> - where - Self: 'm, - { - let server_hello = - require_handshake_msg!(m, HandshakeType::ServerHello, HandshakePayload::ServerHello)?; - trace!("We got ServerHello {:#?}", server_hello); - - use crate::ProtocolVersion::{TLSv1_2, TLSv1_3}; - let config = &self.input.config; - let tls13_supported = config.supports_version(TLSv1_3); - - let server_version = if server_hello.legacy_version == TLSv1_2 { - server_hello - .supported_versions() - .unwrap_or(server_hello.legacy_version) - } else { - server_hello.legacy_version - }; - - let version = match server_version { - TLSv1_3 if tls13_supported => TLSv1_3, - TLSv1_2 if config.supports_version(TLSv1_2) => { - if cx.data.early_data.is_enabled() && cx.common.early_traffic { - // The client must fail with a dedicated error code if the server - // responds with TLS 1.2 when offering 0-RTT. - return Err(PeerMisbehaved::OfferedEarlyDataWithOldProtocolVersion.into()); - } - - if server_hello - .supported_versions() - .is_some() - { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::SelectedTls12UsingTls13VersionExtension, - ) - }); - } - - TLSv1_2 - } - _ => { - let reason = match server_version { - TLSv1_2 | TLSv1_3 => PeerIncompatible::ServerTlsVersionIsDisabledByOurConfig, - _ => PeerIncompatible::ServerDoesNotSupportTls12Or13, - }; - return Err(cx - .common - .send_fatal_alert(AlertDescription::ProtocolVersion, reason)); - } - }; - - if server_hello.compression_method != Compression::Null { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::SelectedUnofferedCompression, - ) - }); - } - - if server_hello.has_duplicate_extension() { - return Err(cx.common.send_fatal_alert( - AlertDescription::DecodeError, - PeerMisbehaved::DuplicateServerHelloExtensions, - )); - } - - let allowed_unsolicited = [ExtensionType::RenegotiationInfo]; - if self - .input - .hello - .server_sent_unsolicited_extensions(&server_hello.extensions, &allowed_unsolicited) - { - return Err(cx.common.send_fatal_alert( - AlertDescription::UnsupportedExtension, - PeerMisbehaved::UnsolicitedServerHelloExtension, - )); - } - - cx.common.negotiated_version = Some(version); - - // Extract ALPN protocol - if !cx.common.is_tls13() { - process_alpn_protocol(cx.common, config, server_hello.alpn_protocol())?; - } - - // If ECPointFormats extension is supplied by the server, it must contain - // Uncompressed. But it's allowed to be omitted. - if let Some(point_fmts) = server_hello.ecpoints_extension() { - if !point_fmts.contains(&ECPointFormat::Uncompressed) { - return Err(cx.common.send_fatal_alert( - AlertDescription::HandshakeFailure, - PeerMisbehaved::ServerHelloMustOfferUncompressedEcPoints, - )); - } - } - - let suite = config - .find_cipher_suite(server_hello.cipher_suite) - .ok_or_else(|| { - cx.common.send_fatal_alert( - AlertDescription::HandshakeFailure, - PeerMisbehaved::SelectedUnofferedCipherSuite, - ) - })?; - - if version != suite.version().version { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::SelectedUnusableCipherSuiteForVersion, - ) - }); - } - - match self.suite { - Some(prev_suite) if prev_suite != suite => { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::SelectedDifferentCipherSuiteAfterRetry, - ) - }); - } - _ => { - debug!("Using ciphersuite {:?}", suite); - self.suite = Some(suite); - cx.common.suite = Some(suite); - } - } - - // Start our handshake hash, and input the server-hello. - let mut transcript = self - .transcript_buffer - .start_hash(suite.hash_provider()); - transcript.add_message(&m); - - let randoms = ConnectionRandoms::new(self.input.random, server_hello.random); - // For TLS1.3, start message encryption using - // handshake_traffic_secret. - match suite { - SupportedCipherSuite::Tls13(suite) => { - #[allow(clippy::bind_instead_of_map)] - let resuming_session = self - .input - .resuming - .and_then(|resuming| match resuming.value { - ClientSessionValue::Tls13(inner) => Some(inner), - #[cfg(feature = "tls12")] - ClientSessionValue::Tls12(_) => None, - }); - - tls13::handle_server_hello( - self.input.config, - cx, - server_hello, - resuming_session, - self.input.server_name, - randoms, - suite, - transcript, - self.early_key_schedule, - self.input.hello, - // We always send a key share when TLS 1.3 is enabled. - self.offered_key_share.unwrap(), - self.input.sent_tls13_fake_ccs, - &m, - self.ech_state, - ) - } - #[cfg(feature = "tls12")] - SupportedCipherSuite::Tls12(suite) => { - let resuming_session = self - .input - .resuming - .and_then(|resuming| match resuming.value { - ClientSessionValue::Tls12(inner) => Some(inner), - ClientSessionValue::Tls13(_) => None, - }); - - tls12::CompleteServerHelloHandling { - config: self.input.config, - resuming_session, - server_name: self.input.server_name, - randoms, - using_ems: self.input.using_ems, - transcript, - } - .handle_server_hello(cx, suite, server_hello, tls13_supported) - } - } - } - - fn into_owned(self: Box) -> NextState<'static> { - self - } +pub(super) enum ClientSessionValue { + Tls13(Tls13Session), + Tls12(Tls12Session), } -impl ExpectServerHelloOrHelloRetryRequest { - fn into_expect_server_hello(self) -> NextState<'static> { - Box::new(self.next) - } - - fn handle_hello_retry_request( - mut self, - cx: &mut ClientContext<'_>, - m: Message<'_>, - ) -> NextStateOrError<'static> { - let hrr = require_handshake_msg!( - m, - HandshakeType::HelloRetryRequest, - HandshakePayload::HelloRetryRequest - )?; - trace!("Got HRR {:?}", hrr); - - cx.common.check_aligned_handshake()?; - - let cookie = hrr.cookie(); - let req_group = hrr.requested_key_share_group(); - - // We always send a key share when TLS 1.3 is enabled. - let offered_key_share = self.next.offered_key_share.unwrap(); - - // A retry request is illegal if it contains no cookie and asks for - // retry of a group we already sent. - if cookie.is_none() && req_group == Some(offered_key_share.group()) { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::IllegalHelloRetryRequestWithOfferedGroup, - ) - }); - } - - // Or has an empty cookie. - if let Some(cookie) = cookie { - if cookie.0.is_empty() { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::IllegalHelloRetryRequestWithEmptyCookie, - ) - }); - } - } - - // Or has something unrecognised - if hrr.has_unknown_extension() { - return Err(cx.common.send_fatal_alert( - AlertDescription::UnsupportedExtension, - PeerIncompatible::ServerSentHelloRetryRequestWithUnknownExtension, - )); - } - - // Or has the same extensions more than once - if hrr.has_duplicate_extension() { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::DuplicateHelloRetryRequestExtensions, - ) - }); - } - - // Or asks us to change nothing. - if cookie.is_none() && req_group.is_none() { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::IllegalHelloRetryRequestWithNoChanges, - ) - }); - } - - // Or does not echo the session_id from our ClientHello: - // - // > the HelloRetryRequest has the same format as a ServerHello message, - // > and the legacy_version, legacy_session_id_echo, cipher_suite, and - // > legacy_compression_method fields have the same meaning - // - // - // and - // - // > A client which receives a legacy_session_id_echo field that does not - // > match what it sent in the ClientHello MUST abort the handshake with an - // > "illegal_parameter" alert. - // - if hrr.session_id != self.next.input.session_id { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::IllegalHelloRetryRequestWithWrongSessionId, - ) - }); - } - - // Or asks us to talk a protocol we didn't offer, or doesn't support HRR at all. - match hrr.supported_versions() { - Some(ProtocolVersion::TLSv1_3) => { - cx.common.negotiated_version = Some(ProtocolVersion::TLSv1_3); - } - _ => { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::IllegalHelloRetryRequestWithUnsupportedVersion, - ) - }); - } - } - - // Or asks us to use a ciphersuite we didn't offer. - let config = &self.next.input.config; - let Some(cs) = config.find_cipher_suite(hrr.cipher_suite) else { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::IllegalHelloRetryRequestWithUnofferedCipherSuite, - ) - }); - }; - - // Or offers ECH related extensions when we didn't offer ECH. - if cx.data.ech_status == EchStatus::NotOffered && hrr.ech().is_some() { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::UnsupportedExtension, - PeerMisbehaved::IllegalHelloRetryRequestWithInvalidEch, - ) - }); - } - - // HRR selects the ciphersuite. - cx.common.suite = Some(cs); - cx.common.handshake_kind = Some(HandshakeKind::FullWithHelloRetryRequest); - - // If we offered ECH, we need to confirm that the server accepted it. - match (self.next.ech_state.as_ref(), cs.tls13()) { - (Some(ech_state), Some(tls13_cs)) => { - if !ech_state.confirm_hrr_acceptance(hrr, tls13_cs, cx.common)? { - // If the server did not confirm, then note the new ECH status but - // continue the handshake. We will abort with an ECH required error - // at the end. - cx.data.ech_status = EchStatus::Rejected; +impl ClientSessionValue { + fn retrieve( + key: &ClientSessionKey<'static>, + config: &ClientConfig, + output: &mut dyn Output<'_>, + ) -> Option> { + let found = config + .resumption + .store + .take_tls13_ticket(key) + .map(ClientSessionValue::Tls13) + .or_else(|| { + config + .resumption + .store + .tls12_session(key) + .map(ClientSessionValue::Tls12) + }) + .and_then(|resuming| { + let now = config + .current_time() + .map_err(|_err| debug!("Could not get current time: {_err}")) + .ok()?; + + let retrieved = Retrieved::new(resuming, now); + match retrieved.has_expired() { + false => Some(retrieved), + true => None, } - } - (Some(_), None) => { - unreachable!("ECH state should only be set when TLS 1.3 was negotiated") - } - _ => {} - }; - - // This is the draft19 change where the transcript became a tree - let transcript = self - .next - .transcript_buffer - .start_hash(cs.hash_provider()); - let mut transcript_buffer = transcript.into_hrr_buffer(); - transcript_buffer.add_message(&m); - - // If we offered ECH and the server accepted, we also need to update the separate - // ECH transcript with the hello retry request message. - if let Some(ech_state) = self.next.ech_state.as_mut() { - ech_state.transcript_hrr_update(cs.hash_provider(), &m); - } - - // Early data is not allowed after HelloRetryrequest - if cx.data.early_data.is_enabled() { - cx.data.early_data.rejected(); - } - - let key_share = match req_group { - Some(group) if group != offered_key_share.group() => { - let Some(skxg) = config.find_kx_group(group, ProtocolVersion::TLSv1_3) else { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::IllegalHelloRetryRequestWithUnofferedNamedGroup, - )); - }; + }) + .or_else(|| { + debug!("No cached session for {key:?}"); + None + }); - cx.common.kx_state = KxState::Start(skxg); - skxg.start()? + if let Some(quic) = output.quic() { + if let Some(quic_params) = found + .as_ref() + .and_then(|r| r.tls13().map(|v| &v.quic_params)) + { + quic.transport_parameters(quic_params.bytes().to_vec()); } - _ => offered_key_share, - }; - - emit_client_hello_for_retry( - transcript_buffer, - Some(hrr), - Some(key_share), - self.extra_exts, - Some(cs), - self.next.input, - cx, - self.next.ech_state, - ) - } -} - -impl State for ExpectServerHelloOrHelloRetryRequest { - fn handle<'m>( - self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { - MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::ServerHello(..), - .. - }, - .. - } => self - .into_expect_server_hello() - .handle(cx, m), - MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::HelloRetryRequest(..), - .. - }, - .. - } => self.handle_hello_retry_request(cx, m), - payload => Err(inappropriate_handshake_message( - &payload, - &[ContentType::Handshake], - &[HandshakeType::ServerHello, HandshakeType::HelloRetryRequest], - )), } - } - fn into_owned(self: Box) -> NextState<'static> { - self + found } -} - -enum ClientSessionValue { - Tls13(persist::Tls13ClientSessionValue), - #[cfg(feature = "tls12")] - Tls12(persist::Tls12ClientSessionValue), -} -impl ClientSessionValue { - fn common(&self) -> &persist::ClientSessionCommon { + fn common(&self) -> &ClientSessionCommon { match self { Self::Tls13(inner) => &inner.common, - #[cfg(feature = "tls12")] Self::Tls12(inner) => &inner.common, } } - fn tls13(&self) -> Option<&persist::Tls13ClientSessionValue> { + fn tls13(&self) -> Option<&Tls13Session> { match self { Self::Tls13(v) => Some(v), - #[cfg(feature = "tls12")] Self::Tls12(_) => None, } } } impl Deref for ClientSessionValue { - type Target = persist::ClientSessionCommon; + type Target = ClientSessionCommon; fn deref(&self) -> &Self::Target { self.common() } } -fn low_quality_integer_hash(mut x: u32) -> u32 { - x = x - .wrapping_add(0x7ed55d16) - .wrapping_add(x << 12); - x = (x ^ 0xc761c23c) ^ (x >> 19); - x = x - .wrapping_add(0x165667b1) - .wrapping_add(x << 5); - x = x.wrapping_add(0xd3a2646c) ^ (x << 9); - x = x - .wrapping_add(0xfd7046c5) - .wrapping_add(x << 3); - x = (x ^ 0xb55a4f09) ^ (x >> 16); - x +pub(crate) trait ClientHandler: fmt::Debug + Sealed + Send + Sync { + fn handle_server_hello( + &self, + suite: &'static T, + server_hello: &ServerHelloPayload, + input: &Input<'_>, + st: ExpectServerHello, + output: &mut dyn Output<'_>, + ) -> Result; } diff --git a/rustls/src/client/mod.rs b/rustls/src/client/mod.rs new file mode 100644 index 00000000000..bb34ec0fe3e --- /dev/null +++ b/rustls/src/client/mod.rs @@ -0,0 +1,439 @@ +use alloc::vec::Vec; +use core::ops::Deref; +use core::time::Duration; + +use pki_types::UnixTime; +use zeroize::Zeroizing; + +use crate::crypto::cipher::Payload; +use crate::crypto::{CipherSuite, CryptoProvider, Identity, SelectedCredential, SignatureScheme}; +use crate::enums::{ApplicationProtocol, CertificateType}; +use crate::error::{ApiMisuse, Error, InvalidMessage}; +use crate::log::{debug, trace}; +use crate::msgs::{ + CertificateChain, Codec, ExtensionType, MaybeEmpty, NewSessionTicketPayloadTls13, Reader, + ServerExtensions, SessionId, SizedPayload, +}; +use crate::sync::Arc; +use crate::verify::DistinguishedName; +#[cfg(feature = "webpki")] +pub use crate::webpki::{ + ServerVerifierBuilder, VerifierBuilderError, WebPkiServerVerifier, + verify_identity_signed_by_trust_anchor, verify_server_name, +}; +use crate::{Tls12CipherSuite, Tls13CipherSuite, compress}; + +mod config; +pub use config::{ + ClientConfig, ClientCredentialResolver, ClientSessionKey, ClientSessionStore, + CredentialRequest, Resumption, Tls12Resumption, WantsClientCert, +}; + +mod connection; +pub use connection::{ClientConnection, ClientConnectionBuilder, ClientSide, WriteEarlyData}; + +mod ech; +pub use ech::{EchConfig, EchGreaseConfig, EchMode, EchStatus}; + +mod handy; +pub use handy::ClientSessionMemoryCache; + +mod hs; +pub(crate) use hs::ClientHandler; + +mod tls12; +pub(crate) use tls12::TLS12_HANDLER; + +mod tls13; +pub(crate) use tls13::TLS13_HANDLER; + +/// Dangerous configuration that should be audited and used with extreme care. +pub mod danger { + pub use super::config::danger::{DangerousClientConfig, DangerousClientConfigBuilder}; + pub use crate::verify::{ + HandshakeSignatureValid, PeerVerified, ServerIdentity, ServerVerifier, + SignatureVerificationInput, + }; +} + +#[cfg(test)] +mod test; + +pub(crate) struct Retrieved { + pub(crate) value: T, + retrieved_at: UnixTime, +} + +impl Retrieved { + pub(crate) fn new(value: T, retrieved_at: UnixTime) -> Self { + Self { + value, + retrieved_at, + } + } + + pub(crate) fn map(&self, f: impl FnOnce(&T) -> Option<&M>) -> Option> { + Some(Retrieved { + value: f(&self.value)?, + retrieved_at: self.retrieved_at, + }) + } +} + +impl Retrieved<&Tls13Session> { + pub(crate) fn obfuscated_ticket_age(&self) -> u32 { + let age_secs = self + .retrieved_at + .as_secs() + .saturating_sub(self.value.common.epoch); + let age_millis = age_secs as u32 * 1000; + age_millis.wrapping_add(self.value.age_add) + } +} + +impl> Retrieved { + pub(crate) fn has_expired(&self) -> bool { + let common = &*self.value; + common.lifetime != Duration::ZERO + && common + .epoch + .saturating_add(common.lifetime.as_secs()) + < self.retrieved_at.as_secs() + } +} + +impl Deref for Retrieved { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.value + } +} + +/// A stored TLS 1.3 client session value. +#[derive(Debug)] +pub struct Tls13Session { + suite: &'static Tls13CipherSuite, + secret: Zeroizing>, + pub(crate) age_add: u32, + max_early_data_size: u32, + pub(crate) common: ClientSessionCommon, + quic_params: SizedPayload<'static, u16, MaybeEmpty>, +} + +impl Tls13Session { + /// Decode a ticket from the given bytes. + pub fn from_slice(bytes: &[u8], provider: &CryptoProvider) -> Result { + let mut reader = Reader::new(bytes); + let suite = CipherSuite::read(&mut reader)?; + let suite = provider + .tls13_cipher_suites + .iter() + .find(|s| s.common.suite == suite) + .ok_or(ApiMisuse::ResumingFromUnknownCipherSuite(suite))?; + + Ok(Self { + suite: *suite, + secret: Zeroizing::new(SizedPayload::::read(&mut reader)?.into_owned()), + age_add: u32::read(&mut reader)?, + max_early_data_size: u32::read(&mut reader)?, + common: ClientSessionCommon::read(&mut reader)?, + quic_params: SizedPayload::::read(&mut reader)?.into_owned(), + }) + } + + pub(crate) fn new( + ticket: &NewSessionTicketPayloadTls13, + input: Tls13ClientSessionInput, + secret: &[u8], + time_now: UnixTime, + ) -> Self { + Self { + suite: input.suite, + secret: Zeroizing::new(secret.to_vec().into()), + age_add: ticket.age_add, + max_early_data_size: ticket + .extensions + .max_early_data_size + .unwrap_or_default(), + common: ClientSessionCommon::new( + ticket.ticket.clone(), + time_now, + ticket.lifetime, + input.peer_identity, + ), + quic_params: input + .quic_params + .unwrap_or_else(|| SizedPayload::from(Payload::new(Vec::new()))), + } + } + + /// Encode this ticket into `buf` for persistence. + pub fn encode(&self, buf: &mut Vec) { + self.suite.common.suite.encode(buf); + self.secret.encode(buf); + buf.extend_from_slice(&self.age_add.to_be_bytes()); + buf.extend_from_slice(&self.max_early_data_size.to_be_bytes()); + self.common.encode(buf); + self.quic_params.encode(buf); + } + + /// Test only: replace `max_early_data_size` with `new` + #[doc(hidden)] + pub fn _reset_max_early_data_size(&mut self, expected: u32, desired: u32) { + assert_eq!( + self.max_early_data_size, expected, + "max_early_data_size was not expected value" + ); + self.max_early_data_size = desired; + } + + /// Test only: rewind epoch by `delta` seconds. + #[doc(hidden)] + pub fn rewind_epoch(&mut self, delta: u32) { + self.common.epoch -= delta as u64; + } +} + +impl Deref for Tls13Session { + type Target = ClientSessionCommon; + + fn deref(&self) -> &Self::Target { + &self.common + } +} + +/// A "template" for future TLS1.3 client session values. +#[derive(Clone)] +pub(crate) struct Tls13ClientSessionInput { + pub(crate) suite: &'static Tls13CipherSuite, + pub(crate) peer_identity: Identity<'static>, + pub(crate) quic_params: Option>, +} + +/// A stored TLS 1.2 client session value. +#[derive(Debug, Clone)] +pub struct Tls12Session { + suite: &'static Tls12CipherSuite, + pub(crate) session_id: SessionId, + master_secret: Zeroizing<[u8; 48]>, + extended_ms: bool, + #[doc(hidden)] + pub(crate) common: ClientSessionCommon, +} + +impl Tls12Session { + /// Decode a ticket from the given bytes. + pub fn from_slice(bytes: &[u8], provider: &CryptoProvider) -> Result { + let mut reader = Reader::new(bytes); + let suite = CipherSuite::read(&mut reader)?; + let suite = provider + .tls12_cipher_suites + .iter() + .find(|s| s.common.suite == suite) + .ok_or(ApiMisuse::ResumingFromUnknownCipherSuite(suite))?; + + Ok(Self { + suite: *suite, + session_id: SessionId::read(&mut reader)?, + master_secret: Zeroizing::new( + reader + .take_array("MasterSecret") + .copied()?, + ), + extended_ms: matches!(u8::read(&mut reader)?, 1), + common: ClientSessionCommon::read(&mut reader)?, + }) + } + + pub(crate) fn new( + suite: &'static Tls12CipherSuite, + session_id: SessionId, + ticket: Arc>, + master_secret: &[u8; 48], + peer_identity: Identity<'static>, + time_now: UnixTime, + lifetime: Duration, + extended_ms: bool, + ) -> Self { + Self { + suite, + session_id, + master_secret: Zeroizing::new(*master_secret), + extended_ms, + common: ClientSessionCommon::new(ticket, time_now, lifetime, peer_identity), + } + } + + /// Encode this ticket into `buf` for persistence. + pub fn encode(&self, buf: &mut Vec) { + self.suite.common.suite.encode(buf); + self.session_id.encode(buf); + buf.extend_from_slice(&*self.master_secret); + buf.push(self.extended_ms as u8); + self.common.encode(buf); + } + + /// Test only: rewind epoch by `delta` seconds. + #[doc(hidden)] + pub fn rewind_epoch(&mut self, delta: u32) { + self.common.epoch -= delta as u64; + } +} + +impl Deref for Tls12Session { + type Target = ClientSessionCommon; + + fn deref(&self) -> &Self::Target { + &self.common + } +} + +/// Common data for stored client sessions. +#[derive(Debug, Clone)] +pub struct ClientSessionCommon { + pub(crate) ticket: Arc>, + pub(crate) epoch: u64, + lifetime: Duration, + peer_identity: Arc>, +} + +impl ClientSessionCommon { + pub(crate) fn new( + ticket: Arc>, + time_now: UnixTime, + lifetime: Duration, + peer_identity: Identity<'static>, + ) -> Self { + Self { + ticket, + epoch: time_now.as_secs(), + lifetime: Ord::min(lifetime, MAX_TICKET_LIFETIME), + peer_identity: Arc::new(peer_identity), + } + } + + pub(crate) fn peer_identity(&self) -> &Identity<'static> { + &self.peer_identity + } + + pub(crate) fn ticket(&self) -> &[u8] { + (*self.ticket).bytes() + } +} + +impl<'a> Codec<'a> for ClientSessionCommon { + fn encode(&self, bytes: &mut Vec) { + self.ticket.encode(bytes); + bytes.extend_from_slice(&self.epoch.to_be_bytes()); + bytes.extend_from_slice(&self.lifetime.as_secs().to_be_bytes()); + self.peer_identity.encode(bytes); + } + + fn read(r: &mut Reader<'a>) -> Result { + Ok(Self { + ticket: Arc::new(SizedPayload::read(r)?.into_owned()), + epoch: u64::read(r)?, + lifetime: Duration::from_secs(u64::read(r)?), + peer_identity: Arc::new(Identity::read(r)?.into_owned()), + }) + } +} + +#[derive(Debug)] +struct ServerCertDetails { + cert_chain: CertificateChain<'static>, + ocsp_response: Vec, +} + +impl ServerCertDetails { + fn new(cert_chain: CertificateChain<'static>, ocsp_response: Vec) -> Self { + Self { + cert_chain, + ocsp_response, + } + } +} + +struct ClientHelloDetails { + alpn_protocols: Vec>, + sent_extensions: Vec, + extension_order_seed: u16, + offered_cert_compression: bool, +} + +impl ClientHelloDetails { + fn new(alpn_protocols: Vec>, extension_order_seed: u16) -> Self { + Self { + alpn_protocols, + sent_extensions: Vec::new(), + extension_order_seed, + offered_cert_compression: false, + } + } + + fn server_sent_unsolicited_extensions( + &self, + received_exts: &ServerExtensions<'_>, + allowed_unsolicited: &[ExtensionType], + ) -> bool { + let mut extensions = received_exts.collect_used(); + extensions.extend( + received_exts + .unknown_extensions + .iter() + .map(|ext| ExtensionType::from(*ext)), + ); + for ext_type in extensions { + if !self.sent_extensions.contains(&ext_type) && !allowed_unsolicited.contains(&ext_type) + { + trace!("Unsolicited extension {ext_type:?}"); + return true; + } + } + + false + } +} + +enum ClientAuthDetails { + /// Send an empty `Certificate` and no `CertificateVerify`. + Empty { auth_context_tls13: Option> }, + /// Send a non-empty `Certificate` and a `CertificateVerify`. + Verify { + credentials: SelectedCredential, + auth_context_tls13: Option>, + compressor: Option<&'static dyn compress::CertCompressor>, + }, +} + +impl ClientAuthDetails { + fn resolve( + negotiated_type: CertificateType, + resolver: &dyn ClientCredentialResolver, + root_hint_subjects: Option<&[DistinguishedName]>, + signature_schemes: &[SignatureScheme], + auth_context_tls13: Option>, + compressor: Option<&'static dyn compress::CertCompressor>, + ) -> Self { + let server_hello = CredentialRequest { + negotiated_type, + root_hint_subjects: root_hint_subjects.unwrap_or_default(), + signature_schemes, + }; + + if let Some(credentials) = resolver.resolve(&server_hello) { + debug!("Attempting client auth"); + return Self::Verify { + credentials, + auth_context_tls13, + compressor, + }; + } + + debug!("Client auth requested but no cert/sigscheme available"); + Self::Empty { auth_context_tls13 } + } +} + +static MAX_TICKET_LIFETIME: Duration = Duration::from_secs(7 * 24 * 60 * 60); diff --git a/rustls/src/client/test.rs b/rustls/src/client/test.rs new file mode 100644 index 00000000000..47a5fb35510 --- /dev/null +++ b/rustls/src/client/test.rs @@ -0,0 +1,837 @@ +use alloc::borrow::Cow; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::hash::Hasher; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::time::Duration; +use std::sync::OnceLock; +use std::vec; + +use pki_types::{CertificateDer, FipsStatus, ServerName, UnixTime}; + +use super::{Tls12Session, Tls13ClientSessionInput, Tls13Session}; +use crate::client::{ClientConfig, Resumption, Tls12Resumption}; +use crate::crypto::cipher::{EncodedMessage, MessageEncrypter, Payload}; +use crate::crypto::kx::{self, NamedGroup, SharedSecret, StartedKeyExchange, SupportedKxGroup}; +use crate::crypto::test_provider::FakeKeyExchangeGroup; +use crate::crypto::tls13::OkmBlock; +use crate::crypto::{ + CipherSuite, Credentials, CryptoProvider, Identity, SignatureScheme, SingleCredential, + TEST_PROVIDER, tls12_only, tls13_only, tls13_suite, +}; +use crate::enums::{CertificateType, ProtocolVersion}; +use crate::error::{Error, PeerIncompatible, PeerMisbehaved}; +use crate::msgs::{ + CertificateChain, ClientHelloPayload, Codec, Compression, ECCurveType, EcParameters, + HandshakeMessagePayload, HandshakePayload, HelloRetryRequest, HelloRetryRequestExtensions, + KeyShareEntry, MaybeEmpty, Message, MessagePayload, NewSessionTicketExtensions, + NewSessionTicketPayloadTls13, Random, Reader, ServerEcdhParams, ServerExtensions, + ServerHelloPayload, ServerKeyExchange, ServerKeyExchangeParams, ServerKeyExchangePayload, + SessionId, SizedPayload, +}; +use crate::pki_types::PrivateKeyDer; +use crate::pki_types::pem::PemObject; +use crate::sync::Arc; +use crate::tls13::key_schedule::{derive_traffic_iv, derive_traffic_key}; +use crate::verify::{ + HandshakeSignatureValid, PeerVerified, ServerIdentity, ServerVerifier, + SignatureVerificationInput, +}; +use crate::{Connection, DigitallySignedStruct, DistinguishedName, KeyLog, RootCertStore}; + +#[test] +fn tls12_client_session_value_roundtrip() { + let session_id = SessionId::read(&mut Reader::new(&[ + 32, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, 0x20, + ])) + .unwrap(); + + let peer_identity = Identity::X509(crate::crypto::CertificateIdentity { + end_entity: CertificateDer::from(&b"test cert"[..]), + intermediates: vec![], + }); + + let session = Tls12Session::new( + TEST_PROVIDER.tls12_cipher_suites[0], + session_id, + Arc::new(SizedPayload::from(vec![0xde, 0xad, 0xbe, 0xef])), + &[0xab; 48], + peer_identity.clone(), + UnixTime::since_unix_epoch(Duration::from_secs(1234567890)), + Duration::from_secs(3600), + true, // extended_ms + ); + + let mut encoded = Vec::new(); + session.encode(&mut encoded); + let decoded = Tls12Session::from_slice(&encoded, &TEST_PROVIDER).unwrap(); + + assert_eq!(decoded.suite.common.suite, session.suite.common.suite); + assert_eq!(decoded.session_id, session_id); + assert_eq!(&*decoded.master_secret, &*session.master_secret); + assert_eq!(decoded.extended_ms, session.extended_ms); + assert_eq!(decoded.common.ticket(), session.common.ticket()); + assert_eq!(decoded.common.epoch, session.common.epoch); + assert_eq!(*decoded.common.peer_identity(), peer_identity); +} + +#[test] +fn tls13_client_session_value_roundtrip() { + let age_add = 0x12345678_u32; + let peer_identity = Identity::RawPublicKey(pki_types::SubjectPublicKeyInfoDer::from( + &b"raw public key"[..], + )); + + let session = Tls13Session::new( + &NewSessionTicketPayloadTls13 { + lifetime: Duration::from_secs(1800), + age_add, + nonce: SizedPayload::empty(), + ticket: Arc::new(SizedPayload::from(vec![0x11, 0x22, 0x33])), + extensions: NewSessionTicketExtensions { + max_early_data_size: Some(8192), + }, + }, + Tls13ClientSessionInput { + suite: TEST_PROVIDER.tls13_cipher_suites[0], + peer_identity: peer_identity.clone(), + quic_params: Some(SizedPayload::::from(vec![ + 0xaa, 0xbb, 0xcc, 0xdd, + ])), + }, + &[0x55; 48], + UnixTime::since_unix_epoch(Duration::from_secs(9999999)), + ); + + let mut encoded = Vec::new(); + session.encode(&mut encoded); + let decoded = Tls13Session::from_slice(&encoded, &TEST_PROVIDER).unwrap(); + + assert_eq!(decoded.suite.common.suite, session.suite.common.suite); + assert_eq!(decoded.secret.bytes(), session.secret.bytes()); + assert_eq!(decoded.age_add, age_add); + assert_eq!(decoded.max_early_data_size, session.max_early_data_size); + assert_eq!(decoded.quic_params.bytes(), session.quic_params.bytes()); + assert_eq!(decoded.common.ticket(), session.common.ticket()); + assert_eq!(decoded.common.epoch, session.common.epoch); + assert_eq!(*decoded.common.peer_identity(), peer_identity); +} + +/// Tests that session_ticket(35) extension +/// is not sent if the client does not support TLS 1.2. +#[test] +fn test_no_session_ticket_request_on_tls_1_3() { + let mut config = ClientConfig::builder(Arc::new(tls13_only(TEST_PROVIDER.clone()))) + .with_root_certificates(roots()) + .with_no_client_auth() + .unwrap(); + config.resumption = + Resumption::in_memory_sessions(128).tls12_resumption(Tls12Resumption::SessionIdOrTickets); + let ch = client_hello_sent_for_config(config).unwrap(); + assert!(ch.extensions.session_ticket.is_none()); +} + +#[test] +fn test_no_renegotiation_scsv_on_tls_1_3() { + let ch = client_hello_sent_for_config( + ClientConfig::builder(Arc::new(tls13_only(TEST_PROVIDER.clone()))) + .with_root_certificates(roots()) + .with_no_client_auth() + .unwrap(), + ) + .unwrap(); + assert!( + !ch.cipher_suites + .contains(&CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + ); +} + +#[test] +fn test_client_does_not_offer_sha1() { + for provider in [ + tls12_only(TEST_PROVIDER.clone()), + tls13_only(TEST_PROVIDER.clone()), + ] { + let config = ClientConfig::builder(Arc::new(provider)) + .with_root_certificates(roots()) + .with_no_client_auth() + .unwrap(); + let ch = client_hello_sent_for_config(config).unwrap(); + assert!( + !ch.extensions + .signature_schemes + .as_ref() + .unwrap() + .contains(&SignatureScheme::RSA_PKCS1_SHA1), + "sha1 unexpectedly offered" + ); + } +} + +#[test] +fn test_client_rejects_hrr_with_varied_session_id() { + let config = ClientConfig::builder(Arc::new(TEST_PROVIDER.clone())) + .with_root_certificates(roots()) + .with_no_client_auth() + .unwrap(); + let mut conn = Arc::new(config) + .connect(ServerName::try_from("localhost").unwrap()) + .build() + .unwrap(); + let mut sent = Vec::new(); + conn.write_tls(&mut sent).unwrap(); + + // server replies with HRR, but does not echo `session_id` as required. + let hrr = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::HelloRetryRequest(HelloRetryRequest { + cipher_suite: CipherSuite::TLS13_AES_128_GCM_SHA256, + legacy_version: ProtocolVersion::TLSv1_2, + session_id: SessionId::empty(), + extensions: HelloRetryRequestExtensions { + cookie: Some(SizedPayload::from(vec![1, 2, 3, 4])), + ..HelloRetryRequestExtensions::default() + }, + }), + )), + }; + + conn.read_tls(&mut hrr.into_wire_bytes().as_slice()) + .unwrap(); + assert_eq!( + conn.process_new_packets().unwrap_err(), + PeerMisbehaved::IllegalHelloRetryRequestWithWrongSessionId.into() + ); +} + +#[test] +fn test_client_rejects_no_extended_master_secret_extension_when_require_ems_or_fips() { + let mut config = ClientConfig::builder(Arc::new(TEST_PROVIDER.clone())) + .with_root_certificates(roots()) + .with_no_client_auth() + .unwrap(); + if !matches!(config.provider().fips(), FipsStatus::Unvalidated) { + assert!(config.require_ems); + } else { + config.require_ems = true; + } + + let config = Arc::new(config); + let mut conn = config + .connect(ServerName::try_from("localhost").unwrap()) + .build() + .unwrap(); + let mut sent = Vec::new(); + conn.write_tls(&mut sent).unwrap(); + + let sh = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::ServerHello( + ServerHelloPayload { + random: Random::new(config.provider().secure_random).unwrap(), + compression_method: Compression::Null, + cipher_suite: CipherSuite(0xff12), + legacy_version: ProtocolVersion::TLSv1_2, + session_id: SessionId::empty(), + extensions: Box::new(ServerExtensions::default()), + }, + ))), + }; + conn.read_tls(&mut sh.into_wire_bytes().as_slice()) + .unwrap(); + + assert_eq!( + conn.process_new_packets(), + Err(PeerIncompatible::ExtendedMasterSecretExtensionRequired.into()) + ); +} + +#[test] +fn cas_extension_in_client_hello_if_server_verifier_requests_it() { + let cas_sending_server_verifier = + ServerVerifierWithAuthorityNames(Arc::from(vec![DistinguishedName::from( + b"hello".to_vec(), + )])); + + let tls12_provider = tls12_only(TEST_PROVIDER.clone()); + let tls13_provider = tls13_only(TEST_PROVIDER.clone()); + for (provider, cas_extension_expected) in [(tls12_provider, false), (tls13_provider, true)] { + let client_hello = client_hello_sent_for_config( + ClientConfig::builder(provider.into()) + .dangerous() + .with_custom_certificate_verifier(Arc::new(cas_sending_server_verifier.clone())) + .with_no_client_auth() + .unwrap(), + ) + .unwrap(); + assert_eq!( + client_hello + .extensions + .certificate_authority_names + .is_some(), + cas_extension_expected + ); + } +} + +/// Regression test for +#[test] +fn test_client_with_custom_verifier_can_accept_ecdsa_sha1_signatures() { + let Some(provider) = x25519_provider(TEST_PROVIDER.clone()) else { + return; + }; + + let verifier = Arc::new(ExpectSha1EcdsaVerifier::default()); + let config = ClientConfig::builder(Arc::new(provider)) + .dangerous() + .with_custom_certificate_verifier(verifier.clone()) + .with_no_client_auth() + .unwrap(); + + let mut conn = Arc::new(config) + .connect(ServerName::try_from("localhost").unwrap()) + .build() + .unwrap(); + let mut sent = Vec::new(); + conn.write_tls(&mut sent).unwrap(); + + let sh = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::ServerHello( + ServerHelloPayload { + random: Random([0u8; 32]), + compression_method: Compression::Null, + cipher_suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + legacy_version: ProtocolVersion::TLSv1_2, + session_id: SessionId::empty(), + extensions: Box::new(ServerExtensions { + extended_master_secret_ack: Some(()), + ..ServerExtensions::default() + }), + }, + ))), + }; + conn.read_tls(&mut sh.into_wire_bytes().as_slice()) + .unwrap(); + conn.process_new_packets().unwrap(); + + let cert = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::Certificate( + CertificateChain(vec![CertificateDer::from(&b"does not matter"[..])]), + ))), + }; + conn.read_tls(&mut cert.into_wire_bytes().as_slice()) + .unwrap(); + conn.process_new_packets().unwrap(); + + let server_kx = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ServerKeyExchange(ServerKeyExchangePayload::Known( + ServerKeyExchange { + dss: DigitallySignedStruct::new( + SignatureScheme::ECDSA_SHA1_Legacy, + b"also does not matter".to_vec(), + ), + params: ServerKeyExchangeParams::Ecdh(ServerEcdhParams { + curve_params: EcParameters { + curve_type: ECCurveType::NamedCurve, + named_group: NamedGroup::X25519, + }, + public: SizedPayload::from(vec![0xab; 32]), + }), + }, + )), + )), + }; + conn.read_tls(&mut server_kx.into_wire_bytes().as_slice()) + .unwrap(); + conn.process_new_packets().unwrap(); + + let server_done = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ServerHelloDone, + )), + }; + conn.read_tls(&mut server_done.into_wire_bytes().as_slice()) + .unwrap(); + conn.process_new_packets().unwrap(); + + assert!( + verifier + .seen_sha1_signature + .load(Ordering::SeqCst) + ); +} + +#[derive(Debug, Default)] +struct ExpectSha1EcdsaVerifier { + seen_sha1_signature: AtomicBool, +} + +impl ServerVerifier for ExpectSha1EcdsaVerifier { + fn verify_identity(&self, _identity: &ServerIdentity<'_>) -> Result { + Ok(PeerVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + input: &SignatureVerificationInput<'_>, + ) -> Result { + assert_eq!(input.signature.scheme, SignatureScheme::ECDSA_SHA1_Legacy); + self.seen_sha1_signature + .store(true, Ordering::SeqCst); + Ok(HandshakeSignatureValid::assertion()) + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls13_signature( + &self, + _input: &SignatureVerificationInput<'_>, + ) -> Result { + todo!() + } + + fn request_ocsp_response(&self) -> bool { + false + } + + fn supported_verify_schemes(&self) -> Vec { + vec![SignatureScheme::ECDSA_SHA1_Legacy] + } + + fn hash_config(&self, _: &mut dyn Hasher) {} +} + +#[test] +fn test_client_requiring_rpk_rejects_server_that_only_offers_x509_id_by_omission() { + client_requiring_rpk_receives_server_ee( + Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()), + ServerExtensions::default(), + &TEST_PROVIDER, + ); +} + +#[test] +fn test_client_requiring_rpk_rejects_server_that_only_offers_x509_id() { + client_requiring_rpk_receives_server_ee( + Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()), + ServerExtensions { + server_certificate_type: Some(CertificateType::X509), + ..ServerExtensions::default() + }, + &TEST_PROVIDER, + ); +} + +#[test] +fn test_client_requiring_rpk_rejects_server_that_only_demands_x509_by_omission() { + client_requiring_rpk_receives_server_ee( + Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()), + ServerExtensions { + server_certificate_type: Some(CertificateType::RawPublicKey), + ..ServerExtensions::default() + }, + &TEST_PROVIDER, + ); +} + +#[test] +fn test_client_requiring_rpk_rejects_server_that_only_demands_x509() { + client_requiring_rpk_receives_server_ee( + Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()), + ServerExtensions { + client_certificate_type: Some(CertificateType::X509), + server_certificate_type: Some(CertificateType::RawPublicKey), + ..ServerExtensions::default() + }, + &TEST_PROVIDER, + ); +} + +#[test] +fn test_client_requiring_rpk_accepts_rpk_server() { + client_requiring_rpk_receives_server_ee( + Ok(()), + ServerExtensions { + client_certificate_type: Some(CertificateType::RawPublicKey), + server_certificate_type: Some(CertificateType::RawPublicKey), + ..ServerExtensions::default() + }, + &TEST_PROVIDER, + ); +} + +#[track_caller] +fn client_requiring_rpk_receives_server_ee( + expected: Result<(), Error>, + encrypted_extensions: ServerExtensions<'_>, + provider: &CryptoProvider, +) { + let Some(provider) = x25519_provider(provider.clone()) else { + return; + }; + + let provider = Arc::new(CryptoProvider { + tls12_cipher_suites: Cow::default(), + ..provider + }); + + let fake_server_crypto = Arc::new(FakeServerCrypto::new(provider.clone())); + let credentials = client_credentials(&provider); + let mut config = ClientConfig::builder(provider) + .dangerous() + .with_custom_certificate_verifier(Arc::new(ServerVerifierRequiringRpk)) + .with_client_credential_resolver(Arc::new(SingleCredential::from(credentials))) + .unwrap(); + config.key_log = fake_server_crypto.clone(); + + let mut conn = Arc::new(config) + .connect(ServerName::try_from("localhost").unwrap()) + .build() + .unwrap(); + let mut sent = Vec::new(); + conn.write_tls(&mut sent).unwrap(); + + let sh = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::ServerHello( + ServerHelloPayload { + random: Random([0; 32]), + compression_method: Compression::Null, + cipher_suite: CipherSuite::TLS13_AES_128_GCM_SHA256, + legacy_version: ProtocolVersion::TLSv1_3, + session_id: SessionId::empty(), + extensions: Box::new(ServerExtensions { + key_share: Some(KeyShareEntry { + group: NamedGroup::X25519, + payload: SizedPayload::from(vec![0xaa; 32]), + }), + ..ServerExtensions::default() + }), + }, + ))), + }; + conn.read_tls(&mut sh.into_wire_bytes().as_slice()) + .unwrap(); + conn.process_new_packets().unwrap(); + + let ee = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::EncryptedExtensions(Box::new(encrypted_extensions)), + )), + }; + + let mut encrypter = fake_server_crypto.server_handshake_encrypter(); + let enc_ee = encrypter + .encrypt(EncodedMessage::>::from(ee).borrow_outbound(), 0) + .unwrap(); + conn.read_tls(&mut enc_ee.encode().as_slice()) + .unwrap(); + + assert_eq!(conn.process_new_packets().map(|_| ()), expected); +} + +fn client_credentials(provider: &CryptoProvider) -> Credentials { + let key = provider + .key_provider + .load_private_key(client_key()) + .unwrap(); + let identity = Arc::from(Identity::RawPublicKey( + key.public_key().unwrap().into_owned(), + )); + Credentials::new_unchecked(identity, key) +} + +fn client_key() -> PrivateKeyDer<'static> { + PrivateKeyDer::from_pem_reader( + &mut include_bytes!("../../../test-ca/rsa-2048/client.key").as_slice(), + ) + .unwrap() +} + +fn x25519_provider(provider: CryptoProvider) -> Option { + // ensures X25519 is offered irrespective of cfg(feature = "fips"), which eases + // creation of fake server messages. + let x25519 = provider.find_kx_group(NamedGroup::X25519, ProtocolVersion::TLSv1_3)?; + Some(CryptoProvider { + kx_groups: Cow::Owned(vec![x25519]), + ..provider + }) +} + +#[derive(Clone, Debug)] +struct ServerVerifierWithAuthorityNames(Arc<[DistinguishedName]>); + +impl ServerVerifier for ServerVerifierWithAuthorityNames { + fn root_hint_subjects(&self) -> Option> { + Some(self.0.clone()) + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_identity(&self, _identity: &ServerIdentity<'_>) -> Result { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls12_signature( + &self, + _input: &SignatureVerificationInput<'_>, + ) -> Result { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls13_signature( + &self, + _input: &SignatureVerificationInput<'_>, + ) -> Result { + unreachable!() + } + + fn supported_verify_schemes(&self) -> Vec { + vec![SignatureScheme::RSA_PKCS1_SHA1] + } + + fn request_ocsp_response(&self) -> bool { + false + } + + fn hash_config(&self, _: &mut dyn Hasher) {} +} + +#[derive(Debug)] +struct ServerVerifierRequiringRpk; + +impl ServerVerifier for ServerVerifierRequiringRpk { + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_identity(&self, _identity: &ServerIdentity<'_>) -> Result { + todo!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls12_signature( + &self, + _input: &SignatureVerificationInput<'_>, + ) -> Result { + todo!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls13_signature( + &self, + _input: &SignatureVerificationInput<'_>, + ) -> Result { + todo!() + } + + fn supported_verify_schemes(&self) -> Vec { + vec![SignatureScheme::RSA_PKCS1_SHA1] + } + + fn request_ocsp_response(&self) -> bool { + false + } + + fn supported_certificate_types(&self) -> &'static [CertificateType] { + &[CertificateType::RawPublicKey] + } + + fn hash_config(&self, _: &mut dyn Hasher) {} +} + +#[derive(Debug)] +struct FakeServerCrypto { + server_handshake_secret: OnceLock>, + provider: Arc, +} + +impl FakeServerCrypto { + fn new(provider: Arc) -> Self { + Self { + server_handshake_secret: OnceLock::new(), + provider, + } + } + + fn server_handshake_encrypter(&self) -> Box { + let secret = self + .server_handshake_secret + .get() + .unwrap(); + + let cipher_suite = tls13_suite(CipherSuite::TLS13_AES_128_GCM_SHA256, &self.provider); + let expander = cipher_suite + .hkdf_provider + .expander_for_okm(&OkmBlock::new(secret)); + + // Derive Encrypter + let key = derive_traffic_key(expander.as_ref(), cipher_suite.aead_alg); + let iv = derive_traffic_iv(expander.as_ref(), cipher_suite.aead_alg.iv_len()); + cipher_suite.aead_alg.encrypter(key, iv) + } +} + +impl KeyLog for FakeServerCrypto { + fn will_log(&self, _label: &str) -> bool { + true + } + + fn log(&self, label: &str, _client_random: &[u8], secret: &[u8]) { + if label == "SERVER_HANDSHAKE_TRAFFIC_SECRET" { + self.server_handshake_secret + .set(secret.to_vec()) + .unwrap(); + } + } +} + +// invalid with fips, as we can't offer X25519 separately +#[test] +fn hybrid_kx_component_share_offered_if_supported_separately() { + let ch = client_hello_sent_for_config( + ClientConfig::builder(Arc::new(HYBRID_PROVIDER.clone())) + .with_root_certificates(roots()) + .with_no_client_auth() + .unwrap(), + ) + .unwrap(); + + let key_shares = ch + .extensions + .key_shares + .as_ref() + .unwrap(); + assert_eq!(key_shares.len(), 2); + assert_eq!(key_shares[0].group, NamedGroup(0xfe00)); + assert_eq!(key_shares[1].group, NamedGroup(0xfe01)); +} + +#[test] +fn hybrid_kx_component_share_not_offered_unless_supported_separately() { + let provider = CryptoProvider { + kx_groups: Cow::Owned(vec![FAKE_HYBRID as _]), + ..HYBRID_PROVIDER + }; + let ch = client_hello_sent_for_config( + ClientConfig::builder(provider.into()) + .with_root_certificates(roots()) + .with_no_client_auth() + .unwrap(), + ) + .unwrap(); + + let key_shares = ch + .extensions + .key_shares + .as_ref() + .unwrap(); + assert_eq!(key_shares.len(), 1); + assert_eq!(key_shares[0].group, NamedGroup(0xfe00)); +} + +fn client_hello_sent_for_config(config: ClientConfig) -> Result { + let mut conn = Arc::new(config) + .connect(ServerName::try_from("localhost").unwrap()) + .build()?; + let mut bytes = Vec::new(); + conn.write_tls(&mut bytes).unwrap(); + + let message = EncodedMessage::>::read(&mut Reader::new(&bytes)) + .unwrap() + .into_owned(); + match Message::try_from(&message).unwrap() { + Message { + payload: + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::ClientHello(ch)), + .. + }, + .. + } => Ok(ch), + other => panic!("unexpected message {other:?}"), + } +} + +const HYBRID_PROVIDER: CryptoProvider = CryptoProvider { + kx_groups: Cow::Borrowed(&[FAKE_HYBRID, FAKE_KX_GROUP]), + ..TEST_PROVIDER +}; + +const FAKE_HYBRID: &FakeHybrid = &FakeHybrid { + name: NamedGroup(0xfe00), + classical: NamedGroup(0xfe01), +}; +const FAKE_KX_GROUP: &dyn SupportedKxGroup = &FakeKeyExchangeGroup(NamedGroup(0xfe01)); + +#[derive(Clone, Copy, Debug)] +pub(crate) struct FakeHybrid { + name: NamedGroup, + classical: NamedGroup, +} + +impl SupportedKxGroup for FakeHybrid { + fn start(&self) -> Result { + Ok(StartedKeyExchange::Hybrid(Box::new(*self))) + } + + fn name(&self) -> NamedGroup { + self.name + } +} + +impl kx::HybridKeyExchange for FakeHybrid { + fn component(&self) -> (NamedGroup, &[u8]) { + (self.classical, KX_PEER_SHARE) + } + + fn complete_component(self: Box, peer_pub_key: &[u8]) -> Result { + match peer_pub_key { + KX_PEER_SHARE => Ok(SharedSecret::from(KX_SHARED_SECRET)), + _ => Err(Error::from(PeerMisbehaved::InvalidKeyShare)), + } + } + + fn as_key_exchange(&self) -> &(dyn kx::ActiveKeyExchange + 'static) { + FAKE_HYBRID + } + + fn into_key_exchange(self: Box) -> Box { + self + } +} + +impl kx::ActiveKeyExchange for FakeHybrid { + fn complete(self: Box, peer: &[u8]) -> Result { + match peer { + KX_PEER_SHARE => Ok(SharedSecret::from(KX_SHARED_SECRET)), + _ => Err(Error::from(PeerMisbehaved::InvalidKeyShare)), + } + } + + fn pub_key(&self) -> &[u8] { + KX_PEER_SHARE + } + + fn group(&self) -> NamedGroup { + self.name + } +} + +const KX_PEER_SHARE: &[u8] = b"KxPeerShareKxPeerShareKxPeerShare"; +const KX_SHARED_SECRET: &[u8] = b"KxSharedSecretKxSharedSecret"; + +fn roots() -> RootCertStore { + let mut r = RootCertStore::empty(); + r.add(CertificateDer::from_slice(include_bytes!( + "../../../test-ca/rsa-2048/ca.der" + ))) + .unwrap(); + r +} diff --git a/rustls/src/client/tls12.rs b/rustls/src/client/tls12.rs index 0f098d3d5e2..f8e2d20f6de 100644 --- a/rustls/src/client/tls12.rs +++ b/rustls/src/client/tls12.rs @@ -1,92 +1,155 @@ use alloc::borrow::ToOwned; use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; +use core::time::Duration; -use pki_types::ServerName; -pub(super) use server_hello::CompleteServerHelloHandling; +pub(crate) use server_hello::TLS12_HANDLER; use subtle::ConstantTimeEq; -use super::client_conn::ClientConnectionData; -use super::hs::ClientContext; +use super::config::{ClientConfig, ClientSessionKey}; +use super::hs::ClientState; +use super::{ClientAuthDetails, ServerCertDetails, Tls12Session}; +use crate::ConnectionTrafficSecrets; use crate::check::{inappropriate_handshake_message, inappropriate_message}; -use crate::client::common::{ClientAuthDetails, ServerCertDetails}; -use crate::client::{hs, ClientConfig}; -use crate::common_state::{CommonState, HandshakeKind, KxState, Side, State}; -use crate::conn::ConnectionRandoms; -use crate::crypto::KeyExchangeAlgorithm; -use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion}; -use crate::error::{Error, InvalidMessage, PeerIncompatible, PeerMisbehaved}; +use crate::common_state::{HandshakeKind, Output, OutputEvent, Side}; +use crate::conn::kernel::KernelState; +use crate::conn::{ConnectionRandoms, Input}; +use crate::crypto::cipher::{MessageDecrypter, MessageEncrypter, Payload}; +use crate::crypto::kx::KeyExchangeAlgorithm; +use crate::crypto::{Identity, Signer}; +use crate::enums::{CertificateType, ContentType, HandshakeType, ProtocolVersion}; +use crate::error::{ApiMisuse, Error, InvalidMessage, PeerIncompatible, PeerMisbehaved}; use crate::hash_hs::HandshakeHash; use crate::log::{debug, trace, warn}; -use crate::msgs::base::{Payload, PayloadU16, PayloadU8}; -use crate::msgs::ccs::ChangeCipherSpecPayload; -use crate::msgs::handshake::{ - CertificateChain, ClientDhParams, ClientEcdhParams, ClientKeyExchangeParams, - HandshakeMessagePayload, HandshakePayload, NewSessionTicketPayload, ServerKeyExchangeParams, - SessionId, +use crate::msgs::{ + CertificateChain, ChangeCipherSpecPayload, ClientDhParams, ClientEcdhParams, + ClientKeyExchangeParams, HandshakeAlignedProof, HandshakeMessagePayload, HandshakePayload, + Message, MessagePayload, NewSessionTicketPayload, NewSessionTicketPayloadTls13, + ServerKeyExchangeParams, SessionId, SizedPayload, }; -use crate::msgs::message::{Message, MessagePayload}; -use crate::msgs::persist; -use crate::sign::Signer; -use crate::suites::{PartiallyExtractedSecrets, SupportedCipherSuite}; +use crate::suites::{PartiallyExtractedSecrets, Suite}; +use crate::sync::Arc; use crate::tls12::{self, ConnectionSecrets, Tls12CipherSuite}; -use crate::verify::{self, DigitallySignedStruct}; +use crate::tls13::key_schedule::KeyScheduleTrafficSend; +use crate::verify::{self, DigitallySignedStruct, ServerIdentity, SignatureVerificationInput}; + +#[expect(private_interfaces)] +pub(crate) enum Tls12State { + Certificate(Box), + CertificateStatusOrServerKx(Box), + ServerKx(Box), + ServerDoneOrCertReq(Box), + ServerDone(Box), + NewTicket(Box), + ChangeCipherSpec(Box), + Finished(Box), + Traffic(Box), +} + +impl Tls12State { + pub(crate) fn handle<'m>( + self, + input: Input<'m>, + output: &mut dyn Output<'m>, + ) -> Result { + match self { + Self::Certificate(e) => e.handle(input, output), + Self::CertificateStatusOrServerKx(e) => e.handle(input, output), + Self::ServerKx(e) => e.handle(input, output), + Self::ServerDoneOrCertReq(e) => e.handle(input, output), + Self::ServerDone(e) => e.handle(input, output), + Self::NewTicket(e) => e.handle(input, output), + Self::ChangeCipherSpec(e) => e.handle(input, output), + Self::Finished(e) => e.handle(input, output), + Self::Traffic(e) => e.handle(input, output), + } + } +} mod server_hello { use super::*; - use crate::msgs::enums::ExtensionType; - use crate::msgs::handshake::{HasServerExtensions, ServerHelloPayload}; - - pub(in crate::client) struct CompleteServerHelloHandling { - pub(in crate::client) config: Arc, - pub(in crate::client) resuming_session: Option, - pub(in crate::client) server_name: ServerName<'static>, - pub(in crate::client) randoms: ConnectionRandoms, - pub(in crate::client) using_ems: bool, - pub(in crate::client) transcript: HandshakeHash, - } + use crate::client::hs::{ + ClientHandler, ClientHelloInput, ClientSessionValue, ClientState, ExpectServerHello, + }; + use crate::msgs::ServerHelloPayload; + use crate::sealed::Sealed; - impl CompleteServerHelloHandling { - pub(in crate::client) fn handle_server_hello( - mut self, - cx: &mut ClientContext<'_>, + pub(crate) static TLS12_HANDLER: &dyn ClientHandler = &Handler; + + #[derive(Debug)] + struct Handler; + + impl ClientHandler for Handler { + fn handle_server_hello( + &self, suite: &'static Tls12CipherSuite, server_hello: &ServerHelloPayload, - tls13_supported: bool, - ) -> hs::NextStateOrError<'static> { - self.randoms + Input { message, .. }: &Input<'_>, + st: ExpectServerHello, + output: &mut dyn Output<'_>, + ) -> Result { + // Start our handshake hash, and input the server-hello. + let mut transcript = st + .transcript_buffer + .start_hash(suite.common.hash_provider); + transcript.add_message(message); + + let mut randoms = ConnectionRandoms::new(st.input.random, server_hello.random); + randoms .server .clone_from_slice(&server_hello.random.0[..]); // Look for TLS1.3 downgrade signal in server random // both the server random and TLS12_DOWNGRADE_SENTINEL are // public values and don't require constant time comparison - let has_downgrade_marker = self.randoms.server[24..] == tls12::DOWNGRADE_SENTINEL; - if tls13_supported && has_downgrade_marker { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::AttemptedDowngradeToTls12WhenTls13IsSupported, - ) - }); + let has_downgrade_marker = randoms.server[24..] == tls12::DOWNGRADE_SENTINEL; + if st + .input + .config + .supports_version(ProtocolVersion::TLSv1_3) + && has_downgrade_marker + { + return Err(PeerMisbehaved::AttemptedDowngradeToTls12WhenTls13IsSupported.into()); } - // Doing EMS? - self.using_ems = server_hello.ems_support_acked(); - if self.config.require_ems && !self.using_ems { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::HandshakeFailure, - PeerIncompatible::ExtendedMasterSecretExtensionRequired, - ) + // If we didn't have an input session to resume, and we sent a session ID, + // that implies we sent a TLS 1.3 legacy_session_id for compatibility purposes. + // In this instance since we're now continuing a TLS 1.2 handshake the server + // should not have echoed it back: it's a randomly generated session ID it couldn't + // have known. + if st.input.resuming.is_none() + && !st.input.session_id.is_empty() + && st.input.session_id == server_hello.session_id + { + return Err(PeerMisbehaved::ServerEchoedCompatibilitySessionId.into()); + } + + let ClientHelloInput { + config, + session_key, + .. + } = st.input; + + let resuming_session = st + .input + .resuming + .and_then(|resuming| match resuming.value { + ClientSessionValue::Tls12(inner) => Some(inner), + ClientSessionValue::Tls13(_) => None, }); + + // Doing EMS? + let using_ems = server_hello + .extended_master_secret_ack + .is_some(); + if config.require_ems && !using_ems { + return Err(PeerIncompatible::ExtendedMasterSecretExtensionRequired.into()); } // Might the server send a ticket? let must_issue_new_ticket = if server_hello - .find_extension(ExtensionType::SessionTicket) + .session_ticket_ack .is_some() { debug!("Server supports tickets"); @@ -98,225 +161,192 @@ mod server_hello { // Might the server send a CertificateStatus between Certificate and // ServerKeyExchange? let may_send_cert_status = server_hello - .find_extension(ExtensionType::StatusRequest) + .certificate_status_request_ack .is_some(); if may_send_cert_status { debug!("Server may staple OCSP response"); } // See if we're successfully resuming. - if let Some(resuming) = self.resuming_session { + if let Some(resuming) = resuming_session { if resuming.session_id == server_hello.session_id { debug!("Server agreed to resume"); // Is the server telling lies about the ciphersuite? - if resuming.suite() != suite { + if resuming.suite != suite { return Err(PeerMisbehaved::ResumptionOfferedWithVariedCipherSuite.into()); } // And about EMS support? - if resuming.extended_ms() != self.using_ems { + if resuming.extended_ms != using_ems { return Err(PeerMisbehaved::ResumptionOfferedWithVariedEms.into()); } let secrets = - ConnectionSecrets::new_resume(self.randoms, suite, resuming.secret()); - self.config.key_log.log( + ConnectionSecrets::new_resume(randoms, suite, &resuming.master_secret); + config.key_log.log( "CLIENT_RANDOM", &secrets.randoms.client, - &secrets.master_secret, - ); - cx.common - .start_encryption_tls12(&secrets, Side::Client); - - // Since we're resuming, we verified the certificate and - // proof of possession in the prior session. - cx.common.peer_certificates = Some( - resuming - .server_cert_chain() - .clone() - .into_owned(), + secrets.master_secret(), ); - cx.common.handshake_kind = Some(HandshakeKind::Resumed); - let cert_verified = verify::ServerCertVerified::assertion(); + + let (dec, enc) = secrets.make_cipher_pair(Side::Client); + output.output(OutputEvent::HandshakeKind(HandshakeKind::Resumed)); + let cert_verified = verify::PeerVerified::assertion(); let sig_verified = verify::HandshakeSignatureValid::assertion(); + let hs = HandshakeState { + config, + session_id: server_hello.session_id, + session_key, + using_ems, + transcript, + }; return if must_issue_new_ticket { Ok(Box::new(ExpectNewTicket { - config: self.config, + hs, secrets, - resuming_session: Some(resuming), - session_id: server_hello.session_id, - server_name: self.server_name, - using_ems: self.using_ems, - transcript: self.transcript, - resuming: true, + // Since we're resuming, we verified the certificate and + // proof of possession in the prior session. + peer_identity: resuming.peer_identity().clone(), + resuming: Some((resuming, enc)), + pending_decrypter: dec, cert_verified, sig_verified, - })) + }) + .into()) } else { Ok(Box::new(ExpectCcs { - config: self.config, + hs, secrets, - resuming_session: Some(resuming), - session_id: server_hello.session_id, - server_name: self.server_name, - using_ems: self.using_ems, - transcript: self.transcript, + peer_identity: resuming.peer_identity().clone(), + resuming: Some((resuming, enc)), + pending_decrypter: dec, ticket: None, - resuming: true, cert_verified, sig_verified, - })) + }) + .into()) }; } } - cx.common.handshake_kind = Some(HandshakeKind::Full); + output.output(OutputEvent::HandshakeKind(HandshakeKind::Full)); Ok(Box::new(ExpectCertificate { - config: self.config, - resuming_session: None, - session_id: server_hello.session_id, - server_name: self.server_name, - randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, + hs: HandshakeState { + config, + session_id: server_hello.session_id, + session_key, + using_ems, + transcript, + }, + randoms, suite, may_send_cert_status, must_issue_new_ticket, - })) + negotiated_client_type: server_hello.client_certificate_type, + }) + .into()) } } + + impl Sealed for Handler {} } struct ExpectCertificate { - config: Arc, - resuming_session: Option, - session_id: SessionId, - server_name: ServerName<'static>, + hs: HandshakeState, randoms: ConnectionRandoms, - using_ems: bool, - transcript: HandshakeHash, - pub(super) suite: &'static Tls12CipherSuite, + suite: &'static Tls12CipherSuite, may_send_cert_status: bool, must_issue_new_ticket: bool, + negotiated_client_type: Option, } -impl State for ExpectCertificate { - fn handle<'m>( +impl ExpectCertificate { + fn handle( mut self: Box, - _cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - self.transcript.add_message(&m); + Input { message, .. }: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { + self.hs.transcript.add_message(&message); let server_cert_chain = require_handshake_msg_move!( - m, + message, HandshakeType::Certificate, HandshakePayload::Certificate )?; if self.may_send_cert_status { Ok(Box::new(ExpectCertificateStatusOrServerKx { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, + hs: self.hs, randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, suite: self.suite, - server_cert_chain, + server_cert_chain: server_cert_chain.into_owned(), must_issue_new_ticket: self.must_issue_new_ticket, - })) + negotiated_client_type: self.negotiated_client_type, + }) + .into()) } else { - let server_cert = ServerCertDetails::new(server_cert_chain, vec![]); - Ok(Box::new(ExpectServerKx { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, + hs: self.hs, randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, suite: self.suite, - server_cert, + server_cert: ServerCertDetails::new(server_cert_chain.into_owned(), vec![]), must_issue_new_ticket: self.must_issue_new_ticket, - })) + negotiated_client_type: self.negotiated_client_type, + }) + .into()) } } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::Certificate(value)) } } -struct ExpectCertificateStatusOrServerKx<'m> { - config: Arc, - resuming_session: Option, - session_id: SessionId, - server_name: ServerName<'static>, +struct ExpectCertificateStatusOrServerKx { + hs: HandshakeState, randoms: ConnectionRandoms, - using_ems: bool, - transcript: HandshakeHash, suite: &'static Tls12CipherSuite, - server_cert_chain: CertificateChain<'m>, + server_cert_chain: CertificateChain<'static>, must_issue_new_ticket: bool, + negotiated_client_type: Option, } -impl State for ExpectCertificateStatusOrServerKx<'_> { - fn handle<'m>( +impl ExpectCertificateStatusOrServerKx { + fn handle( self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { + input: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { + match input.message.payload { MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::ServerKeyExchange(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::ServerKeyExchange(..)), .. - } => Box::new(ExpectServerKx { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, + } => ExpectServerKx { + hs: self.hs, randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, suite: self.suite, server_cert: ServerCertDetails::new(self.server_cert_chain, vec![]), must_issue_new_ticket: self.must_issue_new_ticket, - }) - .handle(cx, m), + negotiated_client_type: self.negotiated_client_type, + } + .handle_input(input), + MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateStatus(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateStatus(..)), .. - } => Box::new(ExpectCertificateStatus { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, + } => ExpectCertificateStatus { + hs: self.hs, randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, suite: self.suite, server_cert_chain: self.server_cert_chain, must_issue_new_ticket: self.must_issue_new_ticket, - }) - .handle(cx, m), + negotiated_client_type: self.negotiated_client_type, + } + .handle_input(input), + payload => Err(inappropriate_handshake_message( &payload, &[ContentType::Handshake], @@ -327,48 +357,28 @@ impl State for ExpectCertificateStatusOrServerKx<'_> { )), } } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - Box::new(ExpectCertificateStatusOrServerKx { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, - randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, - suite: self.suite, - server_cert_chain: self.server_cert_chain.into_owned(), - must_issue_new_ticket: self.must_issue_new_ticket, - }) +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::CertificateStatusOrServerKx(value)) } } -struct ExpectCertificateStatus<'a> { - config: Arc, - resuming_session: Option, - session_id: SessionId, - server_name: ServerName<'static>, +struct ExpectCertificateStatus { + hs: HandshakeState, randoms: ConnectionRandoms, - using_ems: bool, - transcript: HandshakeHash, suite: &'static Tls12CipherSuite, - server_cert_chain: CertificateChain<'a>, + server_cert_chain: CertificateChain<'static>, must_issue_new_ticket: bool, + negotiated_client_type: Option, } -impl State for ExpectCertificateStatus<'_> { - fn handle<'m>( - mut self: Box, - _cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - self.transcript.add_message(&m); +impl ExpectCertificateStatus { + fn handle_input(mut self, Input { message, .. }: Input<'_>) -> Result { + self.hs.transcript.add_message(&message); let server_cert_ocsp_response = require_handshake_msg_move!( - m, + message, HandshakeType::CertificateStatus, HandshakePayload::CertificateStatus )? @@ -376,157 +386,117 @@ impl State for ExpectCertificateStatus<'_> { trace!( "Server stapled OCSP response is {:?}", - &server_cert_ocsp_response + server_cert_ocsp_response ); let server_cert = ServerCertDetails::new(self.server_cert_chain, server_cert_ocsp_response); Ok(Box::new(ExpectServerKx { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, + hs: self.hs, randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, suite: self.suite, server_cert, must_issue_new_ticket: self.must_issue_new_ticket, - })) - } - - fn into_owned(self: Box) -> hs::NextState<'static> { - Box::new(ExpectCertificateStatus { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, - randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, - suite: self.suite, - server_cert_chain: self.server_cert_chain.into_owned(), - must_issue_new_ticket: self.must_issue_new_ticket, + negotiated_client_type: self.negotiated_client_type, }) + .into()) } } -struct ExpectServerKx<'a> { - config: Arc, - resuming_session: Option, - session_id: SessionId, - server_name: ServerName<'static>, +struct ExpectServerKx { + hs: HandshakeState, randoms: ConnectionRandoms, - using_ems: bool, - transcript: HandshakeHash, suite: &'static Tls12CipherSuite, - server_cert: ServerCertDetails<'a>, + server_cert: ServerCertDetails, must_issue_new_ticket: bool, + negotiated_client_type: Option, } -impl State for ExpectServerKx<'_> { - fn handle<'m>( - mut self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { +impl ExpectServerKx { + fn handle_input(mut self, Input { message, .. }: Input<'_>) -> Result { let opaque_kx = require_handshake_msg!( - m, + message, HandshakeType::ServerKeyExchange, HandshakePayload::ServerKeyExchange )?; - self.transcript.add_message(&m); + self.hs.transcript.add_message(&message); let kx = opaque_kx .unwrap_given_kxa(self.suite.kx) - .ok_or_else(|| { - cx.common.send_fatal_alert( - AlertDescription::DecodeError, - InvalidMessage::MissingKeyExchange, - ) - })?; + .ok_or(InvalidMessage::MissingKeyExchange)?; // Save the signature and signed parameters for later verification. let mut kx_params = Vec::new(); kx.params.encode(&mut kx_params); let server_kx = ServerKxDetails::new(kx_params, kx.dss); - #[cfg_attr(not(feature = "logging"), allow(unused_variables))] - { - match &kx.params { - ServerKeyExchangeParams::Ecdh(ecdhe) => { - debug!("ECDHE curve is {:?}", ecdhe.curve_params) - } - ServerKeyExchangeParams::Dh(dhe) => { - debug!("DHE params are p = {:?}, g = {:?}", dhe.dh_p, dhe.dh_g) - } + match &kx.params { + ServerKeyExchangeParams::Ecdh(ecdhe) => { + debug!("ECDHE curve is {:?}", ecdhe.curve_params) + } + ServerKeyExchangeParams::Dh(dhe) => { + debug!("DHE params are p = {:?}, g = {:?}", dhe.dh_p, dhe.dh_g) } } Ok(Box::new(ExpectServerDoneOrCertReq { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, + hs: self.hs, randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, suite: self.suite, server_cert: self.server_cert, server_kx, must_issue_new_ticket: self.must_issue_new_ticket, - })) + negotiated_client_type: self.negotiated_client_type, + }) + .into()) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - Box::new(ExpectServerKx { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, - randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, - suite: self.suite, - server_cert: self.server_cert.into_owned(), - must_issue_new_ticket: self.must_issue_new_ticket, - }) +impl ExpectServerKx { + fn handle( + self: Box, + input: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { + self.handle_input(input) + } +} + +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::ServerKx(value)) } } fn emit_certificate( transcript: &mut HandshakeHash, - cert_chain: CertificateChain<'static>, - common: &mut CommonState, + cert_chain: CertificateChain<'_>, + output: &mut dyn Output<'_>, ) { let cert = Message { version: ProtocolVersion::TLSv1_2, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::Certificate, - payload: HandshakePayload::Certificate(cert_chain), - }), + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::Certificate( + cert_chain, + ))), }; transcript.add_message(&cert); - common.send_msg(cert, false); + output.send_msg(cert, false); } fn emit_client_kx( transcript: &mut HandshakeHash, kxa: KeyExchangeAlgorithm, - common: &mut CommonState, + output: &mut dyn Output<'_>, pub_key: &[u8], ) { let mut buf = Vec::new(); match kxa { KeyExchangeAlgorithm::ECDHE => ClientKeyExchangeParams::Ecdh(ClientEcdhParams { - public: PayloadU8::new(pub_key.to_vec()), + public: pub_key.to_vec().into(), }), KeyExchangeAlgorithm::DHE => ClientKeyExchangeParams::Dh(ClientDhParams { - public: PayloadU16::new(pub_key.to_vec()), + public: SizedPayload::from(Payload::new(pub_key.to_vec())), }), } .encode(&mut buf); @@ -534,20 +504,19 @@ fn emit_client_kx( let ckx = Message { version: ProtocolVersion::TLSv1_2, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::ClientKeyExchange, - payload: HandshakePayload::ClientKeyExchange(pubkey), - }), + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ClientKeyExchange(pubkey), + )), }; transcript.add_message(&ckx); - common.send_msg(ckx, false); + output.send_msg(ckx, false); } fn emit_certverify( transcript: &mut HandshakeHash, - signer: &dyn Signer, - common: &mut CommonState, + signer: Box, + output: &mut dyn Output<'_>, ) -> Result<(), Error> { let message = transcript .take_handshake_buf() @@ -559,45 +528,45 @@ fn emit_certverify( let m = Message { version: ProtocolVersion::TLSv1_2, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::CertificateVerify, - payload: HandshakePayload::CertificateVerify(body), - }), + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::CertificateVerify(body), + )), }; transcript.add_message(&m); - common.send_msg(m, false); + output.send_msg(m, false); Ok(()) } -fn emit_ccs(common: &mut CommonState) { - let ccs = Message { - version: ProtocolVersion::TLSv1_2, - payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}), - }; - - common.send_msg(ccs, false); +fn emit_ccs(output: &mut dyn Output<'_>) { + output.send_msg( + Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}), + }, + false, + ); } fn emit_finished( secrets: &ConnectionSecrets, transcript: &mut HandshakeHash, - common: &mut CommonState, + output: &mut dyn Output<'_>, + proof: &HandshakeAlignedProof, ) { let vh = transcript.current_hash(); - let verify_data = secrets.client_verify_data(&vh); - let verify_data_payload = Payload::new(verify_data); + let verify_data = secrets.client_verify_data(&vh, proof); + let verify_data_payload = Payload::Borrowed(&verify_data); let f = Message { version: ProtocolVersion::TLSv1_2, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::Finished, - payload: HandshakePayload::Finished(verify_data_payload), - }), + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::Finished( + verify_data_payload, + ))), }; transcript.add_message(&f); - common.send_msg(f, true); + output.send_msg(f, true); } struct ServerKxDetails { @@ -617,121 +586,81 @@ impl ServerKxDetails { // --- Either a CertificateRequest, or a ServerHelloDone. --- // Existence of the CertificateRequest tells us the server is asking for // client auth. Otherwise we go straight to ServerHelloDone. -struct ExpectServerDoneOrCertReq<'a> { - config: Arc, - resuming_session: Option, - session_id: SessionId, - server_name: ServerName<'static>, +struct ExpectServerDoneOrCertReq { + hs: HandshakeState, randoms: ConnectionRandoms, - using_ems: bool, - transcript: HandshakeHash, suite: &'static Tls12CipherSuite, - server_cert: ServerCertDetails<'a>, + server_cert: ServerCertDetails, server_kx: ServerKxDetails, must_issue_new_ticket: bool, + negotiated_client_type: Option, } -impl State for ExpectServerDoneOrCertReq<'_> { - fn handle<'m>( +impl ExpectServerDoneOrCertReq { + fn handle( mut self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { + input: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { if matches!( - m.payload, + input.message.payload, MessagePayload::Handshake { - parsed: HandshakeMessagePayload { - payload: HandshakePayload::CertificateRequest(_), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateRequest(_)), .. } ) { - Box::new(ExpectCertificateRequest { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, + ExpectCertificateRequest { + hs: self.hs, randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, suite: self.suite, server_cert: self.server_cert, server_kx: self.server_kx, must_issue_new_ticket: self.must_issue_new_ticket, - }) - .handle(cx, m) + negotiated_client_type: self.negotiated_client_type, + } + .handle_input(input) } else { - self.transcript.abandon_client_auth(); + self.hs.transcript.abandon_client_auth(); - Box::new(ExpectServerDone { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, + ExpectServerDone { + hs: self.hs, randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, suite: self.suite, server_cert: self.server_cert, server_kx: self.server_kx, client_auth: None, must_issue_new_ticket: self.must_issue_new_ticket, - }) - .handle(cx, m) + } + .handle_input(input, output) } } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - Box::new(ExpectServerDoneOrCertReq { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, - randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, - suite: self.suite, - server_cert: self.server_cert.into_owned(), - server_kx: self.server_kx, - must_issue_new_ticket: self.must_issue_new_ticket, - }) +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::ServerDoneOrCertReq(value)) } } -struct ExpectCertificateRequest<'a> { - config: Arc, - resuming_session: Option, - session_id: SessionId, - server_name: ServerName<'static>, +struct ExpectCertificateRequest { + hs: HandshakeState, randoms: ConnectionRandoms, - using_ems: bool, - transcript: HandshakeHash, suite: &'static Tls12CipherSuite, - server_cert: ServerCertDetails<'a>, + server_cert: ServerCertDetails, server_kx: ServerKxDetails, must_issue_new_ticket: bool, + negotiated_client_type: Option, } -impl State for ExpectCertificateRequest<'_> { - fn handle<'m>( - mut self: Box, - _cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { +impl ExpectCertificateRequest { + fn handle_input(mut self, Input { message, .. }: Input<'_>) -> Result { let certreq = require_handshake_msg!( - m, + message, HandshakeType::CertificateRequest, HandshakePayload::CertificateRequest )?; - self.transcript.add_message(&m); - debug!("Got CertificateRequest {:?}", certreq); + self.hs.transcript.add_message(&message); + debug!("Got CertificateRequest {certreq:?}"); // The RFC jovially describes the design here as 'somewhat complicated' // and 'somewhat underspecified'. So thanks for that. @@ -742,9 +671,9 @@ impl State for ExpectCertificateRequest<'_> { const NO_CONTEXT: Option> = None; // TLS 1.2 doesn't use a context. let no_compression = None; // or compression let client_auth = ClientAuthDetails::resolve( - self.config - .client_auth_cert_resolver - .as_ref(), + self.negotiated_client_type + .unwrap_or(CertificateType::X509), + self.hs.config.resolver().as_ref(), Some(&certreq.canames), &certreq.sigschemes, NO_CONTEXT, @@ -752,69 +681,37 @@ impl State for ExpectCertificateRequest<'_> { ); Ok(Box::new(ExpectServerDone { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, + hs: self.hs, randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, suite: self.suite, server_cert: self.server_cert, server_kx: self.server_kx, client_auth: Some(client_auth), must_issue_new_ticket: self.must_issue_new_ticket, - })) - } - - fn into_owned(self: Box) -> hs::NextState<'static> { - Box::new(ExpectCertificateRequest { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, - randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, - suite: self.suite, - server_cert: self.server_cert.into_owned(), - server_kx: self.server_kx, - must_issue_new_ticket: self.must_issue_new_ticket, }) + .into()) } } -struct ExpectServerDone<'a> { - config: Arc, - resuming_session: Option, - session_id: SessionId, - server_name: ServerName<'static>, +struct ExpectServerDone { + hs: HandshakeState, randoms: ConnectionRandoms, - using_ems: bool, - transcript: HandshakeHash, suite: &'static Tls12CipherSuite, - server_cert: ServerCertDetails<'a>, + server_cert: ServerCertDetails, server_kx: ServerKxDetails, client_auth: Option, must_issue_new_ticket: bool, } -impl State for ExpectServerDone<'_> { - fn handle<'m>( - self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { +impl ExpectServerDone { + fn handle_input( + mut self, + input: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { + match input.message.payload { MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::ServerHelloDone, - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::ServerHelloDone), .. } => {} payload => { @@ -826,15 +723,16 @@ impl State for ExpectServerDone<'_> { } } - let mut st = *self; - st.transcript.add_message(&m); + self.hs + .transcript + .add_message(&input.message); - cx.common.check_aligned_handshake()?; + let proof = input.check_aligned_handshake()?; - trace!("Server cert is {:?}", st.server_cert.cert_chain); - debug!("Server DNS name is {:?}", st.server_name); + trace!("Server cert is {:?}", self.server_cert.cert_chain); + debug!("Server DNS name is {:?}", self.hs.session_key.server_name); - let suite = st.suite; + let suite = self.suite; // 1. Verify the cert chain. // 2. Verify that the top certificate signed their kx. @@ -849,27 +747,18 @@ impl State for ExpectServerDone<'_> { // 5. emit a Finished, our first encrypted message under the new keys. // 1. - let (end_entity, intermediates) = st - .server_cert - .cert_chain - .split_first() - .ok_or(Error::NoCertificatesPresented)?; + let identity = Identity::from_peer(self.server_cert.cert_chain.0, CertificateType::X509)? + .ok_or(PeerMisbehaved::NoCertificatesPresented)?; - let now = st.config.current_time()?; - - let cert_verified = st + let cert_verified = self + .hs .config - .verifier - .verify_server_cert( - end_entity, - intermediates, - &st.server_name, - &st.server_cert.ocsp_response, - now, - ) - .map_err(|err| { - cx.common - .send_cert_verify_error_alert(err) + .verifier() + .verify_identity(&ServerIdentity { + identity: &identity, + server_name: &self.hs.session_key.server_name, + ocsp_response: &self.server_cert.ocsp_response, + now: self.hs.config.current_time()?, })?; // 2. @@ -877,57 +766,59 @@ impl State for ExpectServerDone<'_> { // It's ClientHello.random || ServerHello.random || ServerKeyExchange.params let sig_verified = { let mut message = Vec::new(); - message.extend_from_slice(&st.randoms.client); - message.extend_from_slice(&st.randoms.server); - message.extend_from_slice(&st.server_kx.kx_params); + message.extend_from_slice(&self.randoms.client); + message.extend_from_slice(&self.randoms.server); + message.extend_from_slice(&self.server_kx.kx_params); // Check the signature is compatible with the ciphersuite. - let sig = &st.server_kx.kx_sig; - if !SupportedCipherSuite::from(suite) - .usable_for_signature_algorithm(sig.scheme.algorithm()) - { + let signature = &self.server_kx.kx_sig; + if !suite.usable_for_signature_scheme(signature.scheme) { warn!( "peer signed kx with wrong algorithm (got {:?} expect {:?})", - sig.scheme.algorithm(), + signature.scheme.algorithm(), suite.sign ); return Err(PeerMisbehaved::SignedKxWithWrongAlgorithm.into()); } - st.config - .verifier - .verify_tls12_signature(&message, end_entity, sig) - .map_err(|err| { - cx.common - .send_cert_verify_error_alert(err) + self.hs + .config + .verifier() + .verify_tls12_signature(&SignatureVerificationInput { + message: &message, + signer: &identity.as_signer(), + signature, })? }; - cx.common.peer_certificates = Some(st.server_cert.cert_chain.into_owned()); // 3. - if let Some(client_auth) = &st.client_auth { + if let Some(client_auth) = &self.client_auth { let certs = match client_auth { ClientAuthDetails::Empty { .. } => CertificateChain::default(), - ClientAuthDetails::Verify { certkey, .. } => CertificateChain(certkey.cert.clone()), + ClientAuthDetails::Verify { credentials, .. } => { + CertificateChain::from_signer(credentials) + } }; - emit_certificate(&mut st.transcript, certs, cx.common); + emit_certificate(&mut self.hs.transcript, certs, output); } // 4a. let kx_params = tls12::decode_kx_params::( - st.suite.kx, - cx.common, - &st.server_kx.kx_params, + self.suite.kx, + &self.server_kx.kx_params, )?; let maybe_skxg = match &kx_params { - ServerKeyExchangeParams::Ecdh(ecdh) => st + ServerKeyExchangeParams::Ecdh(ecdh) => self + .hs .config + .provider() .find_kx_group(ecdh.curve_params.named_group, ProtocolVersion::TLSv1_2), ServerKeyExchangeParams::Dh(dh) => { let ffdhe_group = dh.as_ffdhe_group(); - st.config - .provider + self.hs + .config + .provider() .kx_groups .iter() .find(|kxg| kxg.ffdhe_group() == Some(ffdhe_group)) @@ -935,25 +826,21 @@ impl State for ExpectServerDone<'_> { } }; let Some(skxg) = maybe_skxg else { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::SelectedUnofferedKxGroup, - )); + return Err(PeerMisbehaved::SelectedUnofferedKxGroup.into()); }; - cx.common.kx_state = KxState::Start(skxg); - let kx = skxg.start()?; + let kx = skxg.start()?.into_single(); // 4b. - let mut transcript = st.transcript; - emit_client_kx(&mut transcript, st.suite.kx, cx.common, kx.pub_key()); + emit_client_kx(&mut self.hs.transcript, self.suite.kx, output, kx.pub_key()); // Note: EMS handshake hash only runs up to ClientKeyExchange. - let ems_seed = st + let ems_seed = self + .hs .using_ems - .then(|| transcript.current_hash()); + .then(|| self.hs.transcript.current_hash()); // 4c. - if let Some(ClientAuthDetails::Verify { signer, .. }) = &st.client_auth { - emit_certverify(&mut transcript, signer.as_ref(), cx.common)?; + if let Some(ClientAuthDetails::Verify { credentials, .. }) = self.client_auth { + emit_certverify(&mut self.hs.transcript, credentials.signer, output)?; } // 4d. Derive secrets. @@ -963,156 +850,139 @@ impl State for ExpectServerDone<'_> { kx, kx_params.pub_key(), ems_seed, - st.randoms, + self.randoms, suite, - ) - .map_err(|err| { - cx.common - .send_fatal_alert(AlertDescription::IllegalParameter, err) - })?; - cx.common.kx_state.complete(); + )?; + output.output(OutputEvent::KeyExchangeGroup(skxg)); // 4e. CCS. We are definitely going to switch on encryption. - emit_ccs(cx.common); + emit_ccs(output); // 4f. Now commit secrets. - st.config.key_log.log( + self.hs.config.key_log.log( "CLIENT_RANDOM", &secrets.randoms.client, - &secrets.master_secret, + secrets.master_secret(), + ); + + let (dec, encrypter) = secrets.make_cipher_pair(Side::Client); + output.send().set_encrypter( + encrypter, + secrets + .suite() + .common + .confidentiality_limit, ); - cx.common - .start_encryption_tls12(&secrets, Side::Client); - cx.common - .record_layer - .start_encrypting(); // 5. - emit_finished(&secrets, &mut transcript, cx.common); + emit_finished(&secrets, &mut self.hs.transcript, output, &proof); - if st.must_issue_new_ticket { + if self.must_issue_new_ticket { Ok(Box::new(ExpectNewTicket { - config: st.config, + hs: self.hs, secrets, - resuming_session: st.resuming_session, - session_id: st.session_id, - server_name: st.server_name, - using_ems: st.using_ems, - transcript, - resuming: false, + peer_identity: identity, + resuming: None, + pending_decrypter: dec, cert_verified, sig_verified, - })) + }) + .into()) } else { Ok(Box::new(ExpectCcs { - config: st.config, + hs: self.hs, secrets, - resuming_session: st.resuming_session, - session_id: st.session_id, - server_name: st.server_name, - using_ems: st.using_ems, - transcript, + peer_identity: identity, + resuming: None, + pending_decrypter: dec, ticket: None, - resuming: false, cert_verified, sig_verified, - })) + }) + .into()) } } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - Box::new(ExpectServerDone { - config: self.config, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, - randoms: self.randoms, - using_ems: self.using_ems, - transcript: self.transcript, - suite: self.suite, - server_cert: self.server_cert.into_owned(), - server_kx: self.server_kx, - client_auth: self.client_auth, - must_issue_new_ticket: self.must_issue_new_ticket, - }) +impl ExpectServerDone { + fn handle( + self: Box, + input: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { + self.handle_input(input, output) + } +} + +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::ServerDone(value)) } } struct ExpectNewTicket { - config: Arc, + hs: HandshakeState, secrets: ConnectionSecrets, - resuming_session: Option, - session_id: SessionId, - server_name: ServerName<'static>, - using_ems: bool, - transcript: HandshakeHash, - resuming: bool, - cert_verified: verify::ServerCertVerified, + peer_identity: Identity<'static>, + resuming: Option<(Tls12Session, Box)>, + pending_decrypter: Box, + cert_verified: verify::PeerVerified, sig_verified: verify::HandshakeSignatureValid, } -impl State for ExpectNewTicket { - fn handle<'m>( +impl ExpectNewTicket { + fn handle( mut self: Box, - _cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - self.transcript.add_message(&m); + Input { message, .. }: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { + self.hs.transcript.add_message(&message); let nst = require_handshake_msg_move!( - m, + message, HandshakeType::NewSessionTicket, HandshakePayload::NewSessionTicket )?; Ok(Box::new(ExpectCcs { - config: self.config, + hs: self.hs, secrets: self.secrets, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, - using_ems: self.using_ems, - transcript: self.transcript, - ticket: Some(nst), resuming: self.resuming, + peer_identity: self.peer_identity, + pending_decrypter: self.pending_decrypter, + ticket: Some(nst), cert_verified: self.cert_verified, sig_verified: self.sig_verified, - })) + }) + .into()) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::NewTicket(value)) } } // -- Waiting for their CCS -- struct ExpectCcs { - config: Arc, + hs: HandshakeState, secrets: ConnectionSecrets, - resuming_session: Option, - session_id: SessionId, - server_name: ServerName<'static>, - using_ems: bool, - transcript: HandshakeHash, + peer_identity: Identity<'static>, + resuming: Option<(Tls12Session, Box)>, + pending_decrypter: Box, ticket: Option, - resuming: bool, - cert_verified: verify::ServerCertVerified, + cert_verified: verify::PeerVerified, sig_verified: verify::HandshakeSignatureValid, } -impl State for ExpectCcs { - fn handle<'m>( +impl ExpectCcs { + fn handle( self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { + input: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { + match input.message.payload { MessagePayload::ChangeCipherSpec(..) => {} payload => { return Err(inappropriate_message( @@ -1123,186 +993,205 @@ impl State for ExpectCcs { } // CCS should not be received interleaved with fragmented handshake-level // message. - cx.common.check_aligned_handshake()?; + let proof = input.check_aligned_handshake()?; // Note: msgs layer validates trivial contents of CCS. - cx.common - .record_layer - .start_decrypting(); + output + .receive() + .decrypt_state + .set_message_decrypter(self.pending_decrypter, &proof); Ok(Box::new(ExpectFinished { - config: self.config, - secrets: self.secrets, - resuming_session: self.resuming_session, - session_id: self.session_id, - server_name: self.server_name, - using_ems: self.using_ems, - transcript: self.transcript, - ticket: self.ticket, + hs: self.hs, + peer_identity: self.peer_identity, resuming: self.resuming, + ticket: self.ticket, + secrets: self.secrets, cert_verified: self.cert_verified, sig_verified: self.sig_verified, - })) + }) + .into()) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::ChangeCipherSpec(value)) } } -struct ExpectFinished { - config: Arc, - resuming_session: Option, - session_id: SessionId, - server_name: ServerName<'static>, - using_ems: bool, - transcript: HandshakeHash, +pub(super) struct ExpectFinished { + hs: HandshakeState, + peer_identity: Identity<'static>, + resuming: Option<(Tls12Session, Box)>, ticket: Option, secrets: ConnectionSecrets, - resuming: bool, - cert_verified: verify::ServerCertVerified, + cert_verified: verify::PeerVerified, sig_verified: verify::HandshakeSignatureValid, } impl ExpectFinished { // -- Waiting for their finished -- - fn save_session(&mut self, cx: &ClientContext<'_>) { + fn save_session(&mut self) { // Save a ticket. If we got a new ticket, save that. Otherwise, save the // original ticket again. let (mut ticket, lifetime) = match self.ticket.take() { Some(nst) => (nst.ticket, nst.lifetime_hint), - None => (Arc::new(PayloadU16::empty()), 0), + None => ( + Arc::new(SizedPayload::from(Payload::new(Vec::new()))), + Duration::ZERO, + ), }; - if ticket.0.is_empty() { - if let Some(resuming_session) = &mut self.resuming_session { - ticket = resuming_session.ticket(); + if ticket.is_empty() { + if let Some((resuming_session, _)) = &mut self.resuming { + ticket = resuming_session.ticket.clone(); } } - if self.session_id.is_empty() && ticket.0.is_empty() { + if self.hs.session_id.is_empty() && ticket.is_empty() { debug!("Session not saved: server didn't allocate id or ticket"); return; } - let Ok(now) = self.config.current_time() else { + let Ok(now) = self.hs.config.current_time() else { debug!("Could not get current time"); return; }; - let session_value = persist::Tls12ClientSessionValue::new( + let session_value = Tls12Session::new( self.secrets.suite(), - self.session_id, + self.hs.session_id, ticket, self.secrets.master_secret(), - cx.common - .peer_certificates - .clone() - .unwrap_or_default(), + self.peer_identity.clone(), now, lifetime, - self.using_ems, + self.hs.using_ems, ); - self.config + self.hs + .config .resumption .store - .set_tls12_session(self.server_name.clone(), session_value); + .set_tls12_session(self.hs.session_key.clone(), session_value); } } -impl State for ExpectFinished { - fn handle<'m>( +impl ExpectFinished { + fn handle( self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { + input: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { let mut st = *self; - let finished = - require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?; + let finished = require_handshake_msg!( + input.message, + HandshakeType::Finished, + HandshakePayload::Finished + )?; - cx.common.check_aligned_handshake()?; + let proof = input.check_aligned_handshake()?; // Work out what verify_data we expect. - let vh = st.transcript.current_hash(); - let expect_verify_data = st.secrets.server_verify_data(&vh); + let vh = st.hs.transcript.current_hash(); + let expect_verify_data = st + .secrets + .server_verify_data(&vh, &proof); // Constant-time verification of this is relatively unimportant: they only // get one chance. But it can't hurt. - let _fin_verified = + let fin_verified = match ConstantTimeEq::ct_eq(&expect_verify_data[..], finished.bytes()).into() { true => verify::FinishedMessageVerified::assertion(), false => { - return Err(cx - .common - .send_fatal_alert(AlertDescription::DecryptError, Error::DecryptError)); + return Err(PeerMisbehaved::IncorrectFinished.into()); } }; // Hash this message too. - st.transcript.add_message(&m); + st.hs + .transcript + .add_message(&input.message); + + st.save_session(); + + if let Some((_, encrypter)) = st.resuming.take() { + emit_ccs(output); + output.send().set_encrypter( + encrypter, + st.secrets + .suite() + .common + .confidentiality_limit, + ); + emit_finished(&st.secrets, &mut st.hs.transcript, output, &proof); + } - st.save_session(cx); + let extracted_secrets = st + .hs + .config + .enable_secret_extraction + .then(|| st.secrets.extract_secrets(Side::Client)); - if st.resuming { - emit_ccs(cx.common); - cx.common - .record_layer - .start_encrypting(); - emit_finished(&st.secrets, &mut st.transcript, cx.common); - } + output.output(OutputEvent::PeerIdentity(st.peer_identity)); + output.output(OutputEvent::Exporter(st.secrets.into_exporter())); + output.start_traffic(); - cx.common - .start_traffic(&mut cx.sendable_plaintext); Ok(Box::new(ExpectTraffic { - secrets: st.secrets, + extracted_secrets, _cert_verified: st.cert_verified, _sig_verified: st.sig_verified, - _fin_verified, - })) + _fin_verified: fin_verified, + }) + .into()) } // we could not decrypt the encrypted handshake message with session resumption // this might mean that the ticket was invalid for some reason, so we remove it // from the store to restart a session from scratch - fn handle_decrypt_error(&self) { - if self.resuming { - self.config + pub(super) fn handle_decrypt_error(&self) { + if self.resuming.is_some() { + self.hs + .config .resumption .store - .remove_tls12_session(&self.server_name); + .remove_tls12_session(&self.hs.session_key); } } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::Finished(value)) } } +struct HandshakeState { + config: Arc, + session_id: SessionId, + session_key: ClientSessionKey<'static>, + using_ems: bool, + transcript: HandshakeHash, +} + // -- Traffic transit state -- -struct ExpectTraffic { - secrets: ConnectionSecrets, - _cert_verified: verify::ServerCertVerified, +pub(super) struct ExpectTraffic { + // only `Some` if `config.enable_secret_extraction` is true + extracted_secrets: Option>, + _cert_verified: verify::PeerVerified, _sig_verified: verify::HandshakeSignatureValid, _fin_verified: verify::FinishedMessageVerified, } -impl State for ExpectTraffic { +impl ExpectTraffic { fn handle<'m>( self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { - MessagePayload::ApplicationData(payload) => cx - .common - .take_received_plaintext(payload), + Input { message, .. }: Input<'m>, + output: &mut dyn Output<'m>, + ) -> Result { + match message.payload { + MessagePayload::ApplicationData(payload) => output.received_plaintext(payload), payload => { return Err(inappropriate_message( &payload, @@ -1310,26 +1199,38 @@ impl State for ExpectTraffic { )); } } - Ok(self) + Ok(self.into()) } - fn export_keying_material( - &self, - output: &mut [u8], - label: &[u8], - context: Option<&[u8]>, - ) -> Result<(), Error> { - self.secrets - .export_keying_material(output, label, context); - Ok(()) + pub(super) fn into_external_state( + mut self: Box, + _send_keys: &Option>, + ) -> Result<(PartiallyExtractedSecrets, Box), Error> { + match self.extracted_secrets.take() { + Some(extracted_secrets) => Ok((extracted_secrets?, self)), + None => Err(ApiMisuse::SecretExtractionRequiresPriorOptIn.into()), + } + } +} + +impl KernelState for ExpectTraffic { + fn update_rx_secret(&mut self) -> Result { + Err(ApiMisuse::KeyUpdateNotAvailableForTls12.into()) } - fn extract_secrets(&self) -> Result { - self.secrets - .extract_secrets(Side::Client) + #[cfg_attr(coverage_nightly, coverage(off))] + fn handle_new_session_ticket( + &self, + _message: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + Err(Error::Unreachable( + "TLS 1.2 session tickets may not be sent once the handshake has completed", + )) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::Traffic(value)) } } diff --git a/rustls/src/client/tls13.rs b/rustls/src/client/tls13.rs index 6794579ab8e..eb2378c33c5 100644 --- a/rustls/src/client/tls13.rs +++ b/rustls/src/client/tls13.rs @@ -1,222 +1,291 @@ use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; -use pki_types::ServerName; use subtle::ConstantTimeEq; -use super::client_conn::ClientConnectionData; -use super::hs::ClientContext; +use super::config::{ClientConfig, ClientSessionKey, ClientSessionStore}; +use super::ech::EchStatus; +use super::hs::{ + ClientHandler, ClientHelloInput, ClientSessionValue, ClientState, ExpectServerHello, + GroupAndKeyShare, process_alpn_protocol, +}; +use super::{ + ClientAuthDetails, ClientHelloDetails, Retrieved, ServerCertDetails, Tls13ClientSessionInput, + Tls13Session, +}; use crate::check::inappropriate_handshake_message; -use crate::client::common::{ClientAuthDetails, ClientHelloDetails, ServerCertDetails}; -use crate::client::ech::{self, EchState, EchStatus}; -use crate::client::{hs, ClientConfig, ClientSessionStore}; use crate::common_state::{ - CommonState, HandshakeFlightTls13, HandshakeKind, KxState, Protocol, Side, State, + EarlyDataEvent, Event, HandshakeFlightTls13, HandshakeKind, Output, OutputEvent, Side, }; -use crate::conn::ConnectionRandoms; -use crate::crypto::{ActiveKeyExchange, SharedSecret}; -use crate::enums::{ - AlertDescription, ContentType, HandshakeType, ProtocolVersion, SignatureScheme, +use crate::conn::kernel::KernelState; +use crate::conn::{ConnectionRandoms, Input, TrafficTemperCounters}; +use crate::crypto::cipher::Payload; +use crate::crypto::hash::Hash; +use crate::crypto::kx::{ActiveKeyExchange, HybridKeyExchange, SharedSecret, StartedKeyExchange}; +use crate::crypto::{Identity, SelectedCredential, SignatureScheme, Signer}; +use crate::enums::{CertificateType, ContentType, HandshakeType, ProtocolVersion}; +use crate::error::{ + ApiMisuse, Error, InvalidMessage, PeerIncompatible, PeerMisbehaved, RejectedEch, }; -use crate::error::{Error, InvalidMessage, PeerIncompatible, PeerMisbehaved}; use crate::hash_hs::{HandshakeHash, HandshakeHashBuffer}; use crate::log::{debug, trace, warn}; -use crate::msgs::base::{Payload, PayloadU8}; -use crate::msgs::ccs::ChangeCipherSpecPayload; -use crate::msgs::codec::{Codec, Reader}; -use crate::msgs::enums::{ExtensionType, KeyUpdateRequest}; -use crate::msgs::handshake::{ - CertificatePayloadTls13, ClientExtension, EchConfigPayload, HandshakeMessagePayload, - HandshakePayload, HasServerExtensions, KeyShareEntry, NewSessionTicketPayloadTls13, - PresharedKeyIdentity, PresharedKeyOffer, ServerExtension, ServerHelloPayload, - CERTIFICATE_MAX_SIZE_LIMIT, +use crate::msgs::{ + CERTIFICATE_MAX_SIZE_LIMIT, CertificatePayloadTls13, ChangeCipherSpecPayload, ClientExtensions, + Codec, EchConfigPayload, ExtensionType, HandshakeMessagePayload, HandshakePayload, + KeyShareEntry, KeyUpdateRequest, MaybeEmpty, Message, MessagePayload, + NewSessionTicketPayloadTls13, PresharedKeyBinder, PresharedKeyIdentity, PresharedKeyOffer, + Reader, ServerExtensions, ServerHelloPayload, SizedPayload, }; -use crate::msgs::message::{Message, MessagePayload}; -use crate::msgs::persist; -use crate::sign::{CertifiedKey, Signer}; +use crate::sealed::Sealed; use crate::suites::PartiallyExtractedSecrets; +use crate::sync::Arc; use crate::tls13::key_schedule::{ - KeyScheduleEarly, KeyScheduleHandshake, KeySchedulePreHandshake, KeyScheduleTraffic, - ResumptionSecret, + KeyScheduleEarlyClient, KeyScheduleHandshake, KeySchedulePreHandshake, KeyScheduleResumption, + KeyScheduleTrafficReceive, KeyScheduleTrafficSend, }; use crate::tls13::{ - construct_client_verify_message, construct_server_verify_message, Tls13CipherSuite, + Tls13CipherSuite, construct_client_verify_message, construct_server_verify_message, }; -use crate::verify::{self, DigitallySignedStruct}; -use crate::{compress, crypto, KeyLog}; +use crate::verify::{self, DigitallySignedStruct, ServerIdentity, SignatureVerificationInput}; +use crate::{ConnectionTrafficSecrets, KeyLog, compress, crypto}; + +#[expect(private_interfaces)] +pub(crate) enum Tls13State { + EncryptedExtensions(Box), + CertificateOrCompressedCertificateOrCertReq( + Box, + ), + CertificateOrCompressedCertificate(Box), + CertificateOrCertReq(Box), + Certificate(Box), + CertificateVerify(Box), + Finished(Box), + Traffic(Box), + QuicTraffic(Box), +} -// Extensions we expect in plaintext in the ServerHello. -static ALLOWED_PLAINTEXT_EXTS: &[ExtensionType] = &[ - ExtensionType::KeyShare, - ExtensionType::PreSharedKey, - ExtensionType::SupportedVersions, -]; +impl Tls13State { + pub(crate) fn handle<'m>( + self, + input: Input<'m>, + output: &mut dyn Output<'m>, + ) -> Result { + match self { + Self::EncryptedExtensions(e) => e.handle(input, output), + Self::CertificateOrCompressedCertificateOrCertReq(e) => e.handle(input, output), + Self::CertificateOrCompressedCertificate(e) => e.handle(input, output), + Self::CertificateOrCertReq(e) => e.handle(input, output), + Self::Certificate(e) => e.handle(input, output), + Self::CertificateVerify(e) => e.handle(input, output), + Self::Finished(e) => e.handle(input, output), + Self::Traffic(e) => e.handle(input, output), + Self::QuicTraffic(e) => e.handle(input, output), + } + } +} -// Only the intersection of things we offer, and those disallowed -// in TLS1.3 -static DISALLOWED_TLS13_EXTS: &[ExtensionType] = &[ - ExtensionType::ECPointFormats, - ExtensionType::SessionTicket, - ExtensionType::RenegotiationInfo, - ExtensionType::ExtendedMasterSecret, -]; +pub(crate) static TLS13_HANDLER: &dyn ClientHandler = &Handler; -pub(super) fn handle_server_hello( - config: Arc, - cx: &mut ClientContext<'_>, - server_hello: &ServerHelloPayload, - mut resuming_session: Option, - server_name: ServerName<'static>, - mut randoms: ConnectionRandoms, - suite: &'static Tls13CipherSuite, - mut transcript: HandshakeHash, - early_key_schedule: Option, - mut hello: ClientHelloDetails, - our_key_share: Box, - mut sent_tls13_fake_ccs: bool, - server_hello_msg: &Message<'_>, - ech_state: Option, -) -> hs::NextStateOrError<'static> { - validate_server_hello(cx.common, server_hello)?; - - let their_key_share = server_hello - .key_share() - .ok_or_else(|| { - cx.common.send_fatal_alert( - AlertDescription::MissingExtension, - PeerMisbehaved::MissingKeyShare, - ) - })?; - - let our_key_share = KeyExchangeChoice::new(&config, cx, our_key_share, their_key_share) - .map_err(|_| { - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::WrongGroupForKeyShare, - ) - })?; - - let key_schedule_pre_handshake = if let (Some(selected_psk), Some(early_key_schedule)) = - (server_hello.psk_index(), early_key_schedule) - { - if let Some(ref resuming) = resuming_session { - let Some(resuming_suite) = suite.can_resume_from(resuming.suite()) else { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::ResumptionOfferedWithIncompatibleCipherSuite, - ) - }); - }; +#[derive(Debug)] +struct Handler; - // If the server varies the suite here, we will have encrypted early data with - // the wrong suite. - if cx.data.early_data.is_enabled() && resuming_suite != suite { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::EarlyDataOfferedWithVariedCipherSuite, - ) - }); - } +impl ClientHandler for Handler { + /// `early_data_key_schedule` is `Some` if we sent the + /// "early_data" extension to the server. + fn handle_server_hello( + &self, + suite: &'static Tls13CipherSuite, + server_hello: &ServerHelloPayload, + input: &Input<'_>, + mut st: ExpectServerHello, + output: &mut dyn Output<'_>, + ) -> Result { + // Start our handshake hash, and input the server-hello. + let mut transcript = st + .transcript_buffer + .start_hash(suite.common.hash_provider); + transcript.add_message(&input.message); + + let mut randoms = ConnectionRandoms::new(st.input.random, server_hello.random); + + if !server_hello.only_contains(ALLOWED_PLAINTEXT_EXTS) { + return Err(PeerMisbehaved::UnexpectedCleartextExtension.into()); + } + + let their_key_share = server_hello + .key_share + .as_ref() + .ok_or(PeerMisbehaved::MissingKeyShare)?; + + let ClientHelloInput { + config, + resuming, + mut sent_tls13_fake_ccs, + mut hello, + session_key, + protocol, + .. + } = st.input; + + let mut resuming_session = match resuming { + Some(Retrieved { + value: ClientSessionValue::Tls13(value), + .. + }) => Some(value), + _ => None, + }; - if selected_psk != 0 { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::SelectedInvalidPsk, + // We always send a key share when TLS 1.3 is enabled. + let our_key_share = st.offered_key_share.unwrap(); + let our_key_share = KeyExchangeChoice::new(&config, output, our_key_share, their_key_share) + .map_err(|_| PeerMisbehaved::WrongGroupForKeyShare)?; + + let (key_schedule_pre_handshake, in_early_traffic) = + match (server_hello.preshared_key, st.early_data_key_schedule) { + (Some(selected_psk), Some((early_key_schedule, in_early_traffic))) => { + match &resuming_session { + Some(resuming) => { + let Some(resuming_suite) = suite.can_resume_from(resuming.suite) else { + return Err( + PeerMisbehaved::ResumptionOfferedWithIncompatibleCipherSuite + .into(), + ); + }; + + // If the server varies the suite here, we will have encrypted early data with + // the wrong suite. + if in_early_traffic && resuming_suite != suite { + return Err( + PeerMisbehaved::EarlyDataOfferedWithVariedCipherSuite.into() + ); + } + + if selected_psk != 0 { + return Err(PeerMisbehaved::SelectedInvalidPsk.into()); + } + + debug!("Resuming using PSK"); + // The key schedule has been initialized and set in fill_in_psk_binder() + } + _ => { + return Err(PeerMisbehaved::SelectedUnofferedPsk.into()); + } + } + ( + KeySchedulePreHandshake::from(early_key_schedule), + in_early_traffic, ) - }); - } + } + _ => { + debug!("Not resuming"); + // Discard the early data key schedule. + output.emit(Event::EarlyData(EarlyDataEvent::Rejected)); + resuming_session.take(); + ( + KeySchedulePreHandshake::new(Side::Client, protocol, suite), + false, + ) + } + }; - debug!("Resuming using PSK"); - // The key schedule has been initialized and set in fill_in_psk_binder() - } else { - return Err(PeerMisbehaved::SelectedUnofferedPsk.into()); + let shared_secret = our_key_share.complete(their_key_share.payload.bytes())?; + let key_schedule = key_schedule_pre_handshake.into_handshake(shared_secret); + + // If we have ECH state, check that the server accepted our offer. + if let Some(ech_state) = st.ech_state { + let Message { + payload: + MessagePayload::Handshake { + encoded: server_hello_encoded, + .. + }, + .. + } = &input.message + else { + unreachable!("ServerHello is a handshake message"); + }; + st.ech_status = match ech_state.confirm_acceptance( + &key_schedule, + server_hello, + server_hello_encoded, + suite.common.hash_provider, + )? { + // The server accepted our ECH offer, so complete the inner transcript with the + // server hello message, and switch the relevant state to the copies for the + // inner client hello. + Some(mut accepted) => { + accepted + .transcript + .add_message(&input.message); + transcript = accepted.transcript; + randoms.client = accepted.random.0; + hello.sent_extensions = accepted.sent_extensions; + EchStatus::Accepted + } + // The server rejected our ECH offer. + None => EchStatus::Rejected, + }; + output.emit(Event::EchStatus(st.ech_status)); } - KeySchedulePreHandshake::from(early_key_schedule) - } else { - debug!("Not resuming"); - // Discard the early data key schedule. - cx.data.early_data.rejected(); - cx.common.early_traffic = false; - resuming_session.take(); - KeySchedulePreHandshake::new(suite) - }; - cx.common.kx_state.complete(); - let shared_secret = our_key_share - .complete(&their_key_share.payload.0) - .map_err(|err| { - cx.common - .send_fatal_alert(AlertDescription::IllegalParameter, err) - })?; - - let mut key_schedule = key_schedule_pre_handshake.into_handshake(shared_secret); - - // If we have ECH state, check that the server accepted our offer. - if let Some(ech_state) = ech_state { - cx.data.ech_status = match ech_state.confirm_acceptance( - &mut key_schedule, - server_hello, - suite.common.hash_provider, - )? { - // The server accepted our ECH offer, so complete the inner transcript with the - // server hello message, and switch the relevant state to the copies for the - // inner client hello. - Some(mut accepted) => { - accepted - .transcript - .add_message(server_hello_msg); - transcript = accepted.transcript; - randoms.client = accepted.random.0; - hello.sent_extensions = accepted.sent_extensions; - EchStatus::Accepted - } - // The server rejected our ECH offer. - None => EchStatus::Rejected, - }; - } + // Remember what KX group the server liked for next time. + config + .resumption + .store + .set_kx_hint(session_key.clone(), their_key_share.group); + + // If we change keying when a subsequent handshake message is being joined, + // the two halves will have different record layer protections. Disallow this. + let proof = input.check_aligned_handshake()?; + + let hash_at_client_recvd_server_hello = transcript.current_hash(); + let key_schedule = key_schedule.derive_client_handshake_secrets( + in_early_traffic, + hash_at_client_recvd_server_hello, + suite, + &*config.key_log, + &randoms.client, + output, + &proof, + ); - // Remember what KX group the server liked for next time. - config - .resumption - .store - .set_kx_hint(server_name.clone(), their_key_share.group); - - // If we change keying when a subsequent handshake message is being joined, - // the two halves will have different record layer protections. Disallow this. - cx.common.check_aligned_handshake()?; - - let hash_at_client_recvd_server_hello = transcript.current_hash(); - let key_schedule = key_schedule.derive_client_handshake_secrets( - cx.data.early_data.is_enabled(), - hash_at_client_recvd_server_hello, - suite, - &*config.key_log, - &randoms.client, - cx.common, - ); + if !key_schedule.protocol().is_quic() { + emit_fake_ccs(&mut sent_tls13_fake_ccs, output); + } - emit_fake_ccs(&mut sent_tls13_fake_ccs, cx.common); - - Ok(Box::new(ExpectEncryptedExtensions { - config, - resuming_session, - server_name, - randoms, - suite, - transcript, - key_schedule, - hello, - })) + output.output(OutputEvent::HandshakeKind( + match (&resuming_session, st.done_retry) { + (Some(_), true) => HandshakeKind::ResumedWithHelloRetryRequest, + (None, true) => HandshakeKind::FullWithHelloRetryRequest, + (Some(_), false) => HandshakeKind::Resumed, + (None, false) => HandshakeKind::Full, + }, + )); + + Ok(Box::new(ExpectEncryptedExtensions { + hs: HandshakeState { + config, + session_key, + randoms, + transcript, + key_schedule, + }, + resuming_session, + suite, + hello, + ech_status: st.ech_status, + in_early_traffic, + }) + .into()) + } } +impl Sealed for Handler {} + enum KeyExchangeChoice { Whole(Box), - Component(Box), + Component(Box), } impl KeyExchangeChoice { @@ -224,69 +293,59 @@ impl KeyExchangeChoice { /// based on the selection of the server expressed in `their_key_share`. fn new( config: &Arc, - cx: &mut ClientContext<'_>, - our_key_share: Box, + output: &mut dyn Output<'_>, + our_key_share: GroupAndKeyShare, their_key_share: &KeyShareEntry, ) -> Result { - if our_key_share.group() == their_key_share.group { - return Ok(Self::Whole(our_key_share)); + if our_key_share.share.group() == their_key_share.group { + output.output(OutputEvent::KeyExchangeGroup(our_key_share.group)); + return Ok(Self::Whole(our_key_share.share.into_single())); } - let (component_group, _) = our_key_share - .hybrid_component() + let (hybrid_key_share, actual_skxg) = our_key_share + .share + .as_hybrid_checked(&config.provider().kx_groups, ProtocolVersion::TLSv1_3) .ok_or(())?; - if component_group != their_key_share.group { + if hybrid_key_share.component().0 != their_key_share.group { return Err(()); } + let StartedKeyExchange::Hybrid(hybrid_key_share) = our_key_share.share else { + return Err(()); // unreachable due to `as_hybrid_checked` + }; + // correct the record for the benefit of accuracy of // `negotiated_key_exchange_group()` - let actual_skxg = config - .find_kx_group(component_group, ProtocolVersion::TLSv1_3) - .ok_or(())?; - cx.common.kx_state = KxState::Start(actual_skxg); + output.output(OutputEvent::KeyExchangeGroup(actual_skxg)); - Ok(Self::Component(our_key_share)) + Ok(Self::Component(hybrid_key_share)) } fn complete(self, peer_pub_key: &[u8]) -> Result { match self { Self::Whole(akx) => akx.complete(peer_pub_key), - Self::Component(akx) => akx.complete_hybrid_component(peer_pub_key), - } - } -} - -fn validate_server_hello( - common: &mut CommonState, - server_hello: &ServerHelloPayload, -) -> Result<(), Error> { - for ext in &server_hello.extensions { - if !ALLOWED_PLAINTEXT_EXTS.contains(&ext.ext_type()) { - return Err(common.send_fatal_alert( - AlertDescription::UnsupportedExtension, - PeerMisbehaved::UnexpectedCleartextExtension, - )); + Self::Component(akx) => akx.complete_component(peer_pub_key), } } - - Ok(()) } pub(super) fn initial_key_share( config: &ClientConfig, - server_name: &ServerName<'_>, - kx_state: &mut KxState, -) -> Result, Error> { + session_key: &ClientSessionKey<'_>, +) -> Result { let group = config .resumption .store - .kx_hint(server_name) - .and_then(|group_name| config.find_kx_group(group_name, ProtocolVersion::TLSv1_3)) + .kx_hint(session_key) + .and_then(|group_name| { + config + .provider() + .find_kx_group(group_name, ProtocolVersion::TLSv1_3) + }) .unwrap_or_else(|| { config - .provider + .provider() .kx_groups .iter() .copied() @@ -294,57 +353,64 @@ pub(super) fn initial_key_share( .expect("No kx groups configured") }); - *kx_state = KxState::Start(group); - group.start() + GroupAndKeyShare::new(group) } /// This implements the horrifying TLS1.3 hack where PSK binders have a /// data dependency on the message they are contained within. pub(super) fn fill_in_psk_binder( - resuming: &persist::Tls13ClientSessionValue, + key_schedule: &KeyScheduleEarlyClient, transcript: &HandshakeHashBuffer, hmp: &mut HandshakeMessagePayload<'_>, -) -> KeyScheduleEarly { - // We need to know the hash function of the suite we're trying to resume into. - let suite = resuming.suite(); - let suite_hash = suite.common.hash_provider; - +) { // The binder is calculated over the clienthello, but doesn't include itself or its // length, or the length of its container. let binder_plaintext = hmp.encoding_for_binder_signing(); - let handshake_hash = transcript.hash_given(suite_hash, &binder_plaintext); + let handshake_hash = transcript.hash_given(key_schedule.hash(), &binder_plaintext); // Run a fake key_schedule to simulate what the server will do if it chooses // to resume. - let key_schedule = KeyScheduleEarly::new(suite, resuming.secret()); let real_binder = key_schedule.resumption_psk_binder_key_and_sign_verify_data(&handshake_hash); - if let HandshakePayload::ClientHello(ref mut ch) = hmp.payload { - ch.set_psk_binder(real_binder.as_ref()); + if let HandshakePayload::ClientHello(ch) = &mut hmp.0 { + if let Some(PresharedKeyOffer { + binders, + identities, + }) = &mut ch.preshared_key_offer + { + // the caller of this function must have set up the desired identity, and a + // matching (dummy) binder; or else the binder we compute here will be incorrect. + // See `prepare_resumption()`. + debug_assert_eq!(identities.len(), 1); + debug_assert_eq!(binders.len(), 1); + debug_assert_eq!(binders[0].as_ref().len(), real_binder.as_ref().len()); + binders[0] = PresharedKeyBinder::from(real_binder.as_ref().to_vec()); + } }; - - key_schedule } pub(super) fn prepare_resumption( config: &ClientConfig, - cx: &mut ClientContext<'_>, - resuming_session: &persist::Retrieved<&persist::Tls13ClientSessionValue>, - exts: &mut Vec, + output: &mut dyn Output<'_>, + resuming_session: &Retrieved<&Tls13Session>, + exts: &mut ClientExtensions<'_>, doing_retry: bool, -) { - let resuming_suite = resuming_session.suite(); - cx.common.suite = Some(resuming_suite.into()); - cx.data.resumption_ciphersuite = Some(resuming_suite.into()); +) -> bool { + let resuming_suite = resuming_session.suite; + output.output(OutputEvent::CipherSuite(resuming_suite.into())); // The EarlyData extension MUST be supplied together with the // PreSharedKey extension. - let max_early_data_size = resuming_session.max_early_data_size(); - if config.enable_early_data && max_early_data_size > 0 && !doing_retry { - cx.data - .early_data - .enable(max_early_data_size as usize); - exts.push(ClientExtension::EarlyData); - } + let max_early_data_size = resuming_session.max_early_data_size; + let early_data_enabled = if config.enable_early_data && max_early_data_size > 0 && !doing_retry + { + output.emit(Event::EarlyData(EarlyDataEvent::Enable( + max_early_data_size as usize, + ))); + exts.early_data_request = Some(()); + true + } else { + false + }; // Finally, and only for TLS1.3 with a ticket resumption, include a binder // for our ticket. This must go last. @@ -361,297 +427,322 @@ pub(super) fn prepare_resumption( let psk_identity = PresharedKeyIdentity::new(resuming_session.ticket().to_vec(), obfuscated_ticket_age); - let psk_ext = PresharedKeyOffer::new(psk_identity, binder); - exts.push(ClientExtension::PresharedKey(psk_ext)); + let psk_offer = PresharedKeyOffer::new(psk_identity, binder); + exts.preshared_key_offer = Some(psk_offer); + early_data_enabled } pub(super) fn derive_early_traffic_secret( key_log: &dyn KeyLog, - cx: &mut ClientContext<'_>, - resuming_suite: &'static Tls13CipherSuite, - early_key_schedule: &KeyScheduleEarly, + output: &mut dyn Output<'_>, + hash_alg: &'static dyn Hash, + early_key_schedule: &KeyScheduleEarlyClient, sent_tls13_fake_ccs: &mut bool, transcript_buffer: &HandshakeHashBuffer, client_random: &[u8; 32], ) { - // For middlebox compatibility - emit_fake_ccs(sent_tls13_fake_ccs, cx.common); + if !early_key_schedule.protocol().is_quic() { + // For middlebox compatibility + emit_fake_ccs(sent_tls13_fake_ccs, output); + } - let client_hello_hash = transcript_buffer.hash_given(resuming_suite.common.hash_provider, &[]); + let client_hello_hash = transcript_buffer.hash_given(hash_alg, &[]); early_key_schedule.client_early_traffic_secret( &client_hello_hash, key_log, client_random, - cx.common, + output, ); + output.output(OutputEvent::EarlyExporter( + early_key_schedule.early_exporter(&client_hello_hash, key_log, client_random), + )); + // Now the client can send encrypted early data - cx.common.early_traffic = true; + output.emit(Event::EarlyData(EarlyDataEvent::Start)); trace!("Starting early data traffic"); } -pub(super) fn emit_fake_ccs(sent_tls13_fake_ccs: &mut bool, common: &mut CommonState) { - if common.is_quic() { - return; - } - +pub(super) fn emit_fake_ccs(sent_tls13_fake_ccs: &mut bool, output: &mut dyn Output<'_>) { if core::mem::replace(sent_tls13_fake_ccs, true) { return; } - let m = Message { - version: ProtocolVersion::TLSv1_2, - payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}), - }; - common.send_msg(m, false); + output.send_msg( + Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}), + }, + false, + ); } fn validate_encrypted_extensions( - common: &mut CommonState, hello: &ClientHelloDetails, - exts: &Vec, + exts: &ServerExtensions<'_>, ) -> Result<(), Error> { - if exts.has_duplicate_extension() { - return Err(common.send_fatal_alert( - AlertDescription::DecodeError, - PeerMisbehaved::DuplicateEncryptedExtensions, - )); - } - if hello.server_sent_unsolicited_extensions(exts, &[]) { - return Err(common.send_fatal_alert( - AlertDescription::UnsupportedExtension, - PeerMisbehaved::UnsolicitedEncryptedExtension, - )); + return Err(PeerMisbehaved::UnsolicitedEncryptedExtension.into()); } - for ext in exts { - if ALLOWED_PLAINTEXT_EXTS.contains(&ext.ext_type()) - || DISALLOWED_TLS13_EXTS.contains(&ext.ext_type()) - { - return Err(common.send_fatal_alert( - AlertDescription::UnsupportedExtension, - PeerMisbehaved::DisallowedEncryptedExtension, - )); - } + if exts.contains_any(ALLOWED_PLAINTEXT_EXTS) || exts.contains_any(DISALLOWED_TLS13_EXTS) { + return Err(PeerMisbehaved::DisallowedEncryptedExtension.into()); } Ok(()) } struct ExpectEncryptedExtensions { - config: Arc, - resuming_session: Option, - server_name: ServerName<'static>, - randoms: ConnectionRandoms, + hs: HandshakeState, + resuming_session: Option, suite: &'static Tls13CipherSuite, - transcript: HandshakeHash, - key_schedule: KeyScheduleHandshake, hello: ClientHelloDetails, + ech_status: EchStatus, + in_early_traffic: bool, } -impl State for ExpectEncryptedExtensions { - fn handle<'m>( +impl ExpectEncryptedExtensions { + fn handle( mut self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { + Input { message, .. }: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { let exts = require_handshake_msg!( - m, + message, HandshakeType::EncryptedExtensions, HandshakePayload::EncryptedExtensions )?; - debug!("TLS1.3 encrypted extensions: {:?}", exts); - self.transcript.add_message(&m); + debug!("TLS1.3 encrypted extensions: {exts:?}"); + self.hs.transcript.add_message(&message); + + validate_encrypted_extensions(&self.hello, exts)?; + + let selected_alpn = exts + .selected_protocol + .as_ref() + .map(|protocol| protocol.as_ref()); + process_alpn_protocol( + output, + &self.hello.alpn_protocols, + selected_alpn, + self.hs.config.check_selected_alpn, + )?; + + // RFC 9001 says: "While ALPN only specifies that servers use this alert, QUIC clients MUST + // use error 0x0178 to terminate a connection when ALPN negotiation fails." We judge that + // the user intended to use ALPN (rather than some out-of-band protocol negotiation + // mechanism) if and only if any ALPN protocols were configured. This defends against badly-behaved + // servers which accept a connection that requires an application-layer protocol they do not + // understand. + if self + .hs + .key_schedule + .protocol() + .is_quic() + && selected_alpn.is_none() + && !self.hello.alpn_protocols.is_empty() + { + return Err(Error::NoApplicationProtocol); + } + + check_cert_type( + self.hs + .config + .resolver() + .supported_certificate_types(), + exts.client_certificate_type, + )?; - validate_encrypted_extensions(cx.common, &self.hello, exts)?; - hs::process_alpn_protocol(cx.common, &self.config, exts.alpn_protocol())?; - hs::process_client_cert_type_extension(cx.common, &self.config, exts.client_cert_type())?; - hs::process_server_cert_type_extension(cx.common, &self.config, exts.server_cert_type())?; + check_cert_type( + self.hs + .config + .verifier() + .supported_certificate_types(), + exts.server_certificate_type, + )?; - let ech_retry_configs = match (cx.data.ech_status, exts.server_ech_extension()) { + let ech_retry_configs = match (self.ech_status, &exts.encrypted_client_hello_ack) { // If we didn't offer ECH, or ECH was accepted, but the server sent an ECH encrypted // extension with retry configs, we must error. (EchStatus::NotOffered | EchStatus::Accepted, Some(_)) => { - return Err(cx.common.send_fatal_alert( - AlertDescription::UnsupportedExtension, - PeerMisbehaved::UnsolicitedEchExtension, - )) + return Err(PeerMisbehaved::UnsolicitedEchExtension.into()); } // If we offered ECH, and it was rejected, store the retry configs (if any) from // the server's ECH extension. We will return them in an error produced at the end // of the handshake. - (EchStatus::Rejected, ext) => ext.map(|ext| ext.retry_configs.to_vec()), + (EchStatus::Rejected, ext) => ext + .as_ref() + .map(|ext| ext.retry_configs.to_vec()), _ => None, }; + let ech = Ech { + retry_configs: ech_retry_configs, + status: self.ech_status, + }; + // QUIC transport parameters - if cx.common.is_quic() { - match exts.quic_params_extension() { - Some(params) => cx.common.quic.params = Some(params), - None => { - return Err(cx - .common - .missing_extension(PeerMisbehaved::MissingQuicTransportParameters)); - } - } - } + let quic_params = if let Some(quic) = output.quic() { + let Some(quic_params) = exts.transport_parameters.as_ref() else { + return Err(PeerMisbehaved::MissingQuicTransportParameters.into()); + }; - if let Some(resuming_session) = self.resuming_session { - let was_early_traffic = cx.common.early_traffic; - if was_early_traffic { - if exts.early_data_extension_offered() { - cx.data.early_data.accepted(); - } else { - cx.data.early_data.rejected(); - cx.common.early_traffic = false; + quic.transport_parameters(quic_params.clone().into_vec()); + Some(SizedPayload::from(Payload::new( + quic_params.clone().into_vec(), + ))) + } else { + None + }; + + match self.resuming_session { + Some(resuming_session) => { + if self.in_early_traffic { + match exts.early_data_ack { + Some(()) => output.emit(Event::EarlyData(EarlyDataEvent::Accepted)), + None => { + output.emit(Event::EarlyData(EarlyDataEvent::Rejected)); + // If no early traffic, set the encryption key for handshakes + self.hs + .key_schedule + .set_handshake_encrypter(output.send()); + self.in_early_traffic = false; + } + } } - } - if was_early_traffic && !cx.common.early_traffic { - // If no early traffic, set the encryption key for handshakes - self.key_schedule - .set_handshake_encrypter(cx.common); + // We *don't* reverify the certificate chain here: resumption is a + // continuation of the previous session in terms of security policy. + let cert_verified = verify::PeerVerified::assertion(); + let sig_verified = verify::HandshakeSignatureValid::assertion(); + Ok(Box::new(ExpectFinished { + hs: self.hs, + session_input: Tls13ClientSessionInput { + suite: self.suite, + peer_identity: resuming_session.peer_identity().clone(), + quic_params, + }, + client_auth: None, + cert_verified, + sig_verified, + ech, + in_early_traffic: self.in_early_traffic, + }) + .into()) } + _ => { + if exts.early_data_ack.is_some() { + return Err(PeerMisbehaved::EarlyDataExtensionWithoutResumption.into()); + } - cx.common.peer_certificates = Some( - resuming_session - .server_cert_chain() - .clone(), - ); - cx.common.handshake_kind = Some(HandshakeKind::Resumed); - - // We *don't* reverify the certificate chain here: resumption is a - // continuation of the previous session in terms of security policy. - let cert_verified = verify::ServerCertVerified::assertion(); - let sig_verified = verify::HandshakeSignatureValid::assertion(); - Ok(Box::new(ExpectFinished { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, - suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, - client_auth: None, - cert_verified, - sig_verified, - ech_retry_configs, - })) - } else { - if exts.early_data_extension_offered() { - return Err(PeerMisbehaved::EarlyDataExtensionWithoutResumption.into()); - } - cx.common - .handshake_kind - .get_or_insert(HandshakeKind::Full); - - Ok(if self.hello.offered_cert_compression { - Box::new(ExpectCertificateOrCompressedCertificateOrCertReq { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, - suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, - ech_retry_configs, - }) - } else { - Box::new(ExpectCertificateOrCertReq { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, - suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, - ech_retry_configs, + let expected_certificate_type = exts + .server_certificate_type + .unwrap_or_default(); + Ok(if self.hello.offered_cert_compression { + Box::new(ExpectCertificateOrCompressedCertificateOrCertReq { + hs: self.hs, + suite: self.suite, + quic_params, + ech, + expected_certificate_type, + negotiated_client_type: exts.client_certificate_type, + }) + .into() + } else { + Box::new(ExpectCertificateOrCertReq { + hs: self.hs, + suite: self.suite, + quic_params, + ech, + expected_certificate_type, + negotiated_client_type: exts.client_certificate_type, + }) + .into() }) - }) + } } } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::EncryptedExtensions(value)) + } +} + +fn check_cert_type( + client_supported: &[CertificateType], + server_negotiated: Option, +) -> Result<(), Error> { + match server_negotiated { + None if client_supported.is_empty() + || client_supported.contains(&CertificateType::X509) => + { + Ok(()) + } + Some(ct) if client_supported.contains(&ct) => Ok(()), + _ => Err(Error::PeerIncompatible( + PeerIncompatible::IncorrectCertificateTypeExtension, + )), } } struct ExpectCertificateOrCompressedCertificateOrCertReq { - config: Arc, - server_name: ServerName<'static>, - randoms: ConnectionRandoms, + hs: HandshakeState, suite: &'static Tls13CipherSuite, - transcript: HandshakeHash, - key_schedule: KeyScheduleHandshake, - ech_retry_configs: Option>, + quic_params: Option>, + ech: Ech, + expected_certificate_type: CertificateType, + negotiated_client_type: Option, } -impl State for ExpectCertificateOrCompressedCertificateOrCertReq { - fn handle<'m>( +impl ExpectCertificateOrCompressedCertificateOrCertReq { + fn handle( self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { + input: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { + match input.message.payload { MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateTls13(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateTls13(..)), .. - } => Box::new(ExpectCertificate { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, + } => ExpectCertificate { + hs: self.hs, suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, + quic_params: self.quic_params, client_auth: None, - message_already_in_transcript: false, - ech_retry_configs: self.ech_retry_configs, - }) - .handle(cx, m), + ech: self.ech, + expected_certificate_type: self.expected_certificate_type, + } + .handle_input(input), + MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CompressedCertificate(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CompressedCertificate(..)), .. - } => Box::new(ExpectCompressedCertificate { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, + } => ExpectCompressedCertificate { + hs: self.hs, suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, + quic_params: self.quic_params, client_auth: None, - ech_retry_configs: self.ech_retry_configs, - }) - .handle(cx, m), + ech: self.ech, + expected_certificate_type: self.expected_certificate_type, + } + .handle_input(input), + MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateRequestTls13(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateRequestTls13(..)), .. - } => Box::new(ExpectCertificateRequest { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, + } => ExpectCertificateRequest { + hs: self.hs, suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, + quic_params: self.quic_params, offered_cert_compression: true, - ech_retry_configs: self.ech_retry_configs, - }) - .handle(cx, m), + ech: self.ech, + expected_certificate_type: self.expected_certificate_type, + negotiated_client_type: self.negotiated_client_type, + } + .handle_input(input), + payload => Err(inappropriate_handshake_message( &payload, &[ContentType::Handshake], @@ -663,70 +754,58 @@ impl State for ExpectCertificateOrCompressedCertificateOrC )), } } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::CertificateOrCompressedCertificateOrCertReq( + value, + )) } } struct ExpectCertificateOrCompressedCertificate { - config: Arc, - server_name: ServerName<'static>, - randoms: ConnectionRandoms, + hs: HandshakeState, suite: &'static Tls13CipherSuite, - transcript: HandshakeHash, - key_schedule: KeyScheduleHandshake, + quic_params: Option>, client_auth: Option, - ech_retry_configs: Option>, + ech: Ech, + expected_certificate_type: CertificateType, } -impl State for ExpectCertificateOrCompressedCertificate { - fn handle<'m>( +impl ExpectCertificateOrCompressedCertificate { + fn handle( self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { + input: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { + match input.message.payload { MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateTls13(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateTls13(..)), .. - } => Box::new(ExpectCertificate { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, + } => ExpectCertificate { + hs: self.hs, suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, + quic_params: self.quic_params, client_auth: self.client_auth, - message_already_in_transcript: false, - ech_retry_configs: self.ech_retry_configs, - }) - .handle(cx, m), + ech: self.ech, + expected_certificate_type: self.expected_certificate_type, + } + .handle_input(input), + MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CompressedCertificate(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CompressedCertificate(..)), .. - } => Box::new(ExpectCompressedCertificate { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, + } => ExpectCompressedCertificate { + hs: self.hs, suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, + quic_params: self.quic_params, client_auth: self.client_auth, - ech_retry_configs: self.ech_retry_configs, - }) - .handle(cx, m), + ech: self.ech, + expected_certificate_type: self.expected_certificate_type, + } + .handle_input(input), + payload => Err(inappropriate_handshake_message( &payload, &[ContentType::Handshake], @@ -737,69 +816,57 @@ impl State for ExpectCertificateOrCompressedCertificate { )), } } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::CertificateOrCompressedCertificate(value)) } } struct ExpectCertificateOrCertReq { - config: Arc, - server_name: ServerName<'static>, - randoms: ConnectionRandoms, + hs: HandshakeState, suite: &'static Tls13CipherSuite, - transcript: HandshakeHash, - key_schedule: KeyScheduleHandshake, - ech_retry_configs: Option>, + quic_params: Option>, + ech: Ech, + expected_certificate_type: CertificateType, + negotiated_client_type: Option, } -impl State for ExpectCertificateOrCertReq { - fn handle<'m>( +impl ExpectCertificateOrCertReq { + fn handle( self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { + input: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { + match input.message.payload { MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateTls13(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateTls13(..)), .. - } => Box::new(ExpectCertificate { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, + } => ExpectCertificate { + hs: self.hs, suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, + quic_params: self.quic_params, client_auth: None, - message_already_in_transcript: false, - ech_retry_configs: self.ech_retry_configs, - }) - .handle(cx, m), + ech: self.ech, + expected_certificate_type: self.expected_certificate_type, + } + .handle_input(input), + MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateRequestTls13(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateRequestTls13(..)), .. - } => Box::new(ExpectCertificateRequest { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, + } => ExpectCertificateRequest { + hs: self.hs, suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, + quic_params: self.quic_params, offered_cert_compression: false, - ech_retry_configs: self.ech_retry_configs, - }) - .handle(cx, m), + ech: self.ech, + expected_certificate_type: self.expected_certificate_type, + negotiated_client_type: self.negotiated_client_type, + } + .handle_input(input), + payload => Err(inappropriate_handshake_message( &payload, &[ContentType::Handshake], @@ -810,9 +877,11 @@ impl State for ExpectCertificateOrCertReq { )), } } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::CertificateOrCertReq(value)) } } @@ -820,263 +889,188 @@ impl State for ExpectCertificateOrCertReq { // Certificate. Unfortunately the CertificateRequest type changed in an annoying way // in TLS1.3. struct ExpectCertificateRequest { - config: Arc, - server_name: ServerName<'static>, - randoms: ConnectionRandoms, + hs: HandshakeState, suite: &'static Tls13CipherSuite, - transcript: HandshakeHash, - key_schedule: KeyScheduleHandshake, + quic_params: Option>, offered_cert_compression: bool, - ech_retry_configs: Option>, + ech: Ech, + expected_certificate_type: CertificateType, + negotiated_client_type: Option, } -impl State for ExpectCertificateRequest { - fn handle<'m>( - mut self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { +impl ExpectCertificateRequest { + fn handle_input(mut self, Input { message, .. }: Input<'_>) -> Result { let certreq = &require_handshake_msg!( - m, + message, HandshakeType::CertificateRequest, HandshakePayload::CertificateRequestTls13 )?; - self.transcript.add_message(&m); - debug!("Got CertificateRequest {:?}", certreq); + self.hs.transcript.add_message(&message); + debug!("Got CertificateRequest {certreq:?}"); // Fortunately the problems here in TLS1.2 and prior are corrected in // TLS1.3. // Must be empty during handshake. - if !certreq.context.0.is_empty() { + if !certreq.context.is_empty() { warn!("Server sent non-empty certreq context"); - return Err(cx.common.send_fatal_alert( - AlertDescription::DecodeError, - InvalidMessage::InvalidCertRequest, - )); + return Err(InvalidMessage::InvalidCertRequest.into()); } - let no_sigschemes = Vec::new(); let compat_sigschemes = certreq - .sigalgs_extension() - .unwrap_or(&no_sigschemes) + .extensions + .signature_algorithms + .as_deref() + .unwrap_or_default() .iter() - .cloned() + .copied() .filter(SignatureScheme::supported_in_tls13) .collect::>(); if compat_sigschemes.is_empty() { - return Err(cx.common.send_fatal_alert( - AlertDescription::HandshakeFailure, - PeerIncompatible::NoCertificateRequestSignatureSchemesInCommon, - )); + return Err(PeerIncompatible::NoCertificateRequestSignatureSchemesInCommon.into()); } let compat_compressor = certreq - .certificate_compression_extension() + .extensions + .certificate_compression_algorithms + .as_deref() .and_then(|offered| { - self.config + self.hs + .config .cert_compressors .iter() .find(|compressor| offered.contains(&compressor.algorithm())) }) - .cloned(); + .copied(); let client_auth = ClientAuthDetails::resolve( - self.config - .client_auth_cert_resolver - .as_ref(), - certreq.authorities_extension(), + self.negotiated_client_type + .unwrap_or(CertificateType::X509), + self.hs.config.resolver().as_ref(), + certreq + .extensions + .authority_names + .as_deref(), &compat_sigschemes, - Some(certreq.context.0.clone()), + Some(certreq.context.to_vec()), compat_compressor, ); Ok(if self.offered_cert_compression { Box::new(ExpectCertificateOrCompressedCertificate { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, + hs: self.hs, suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, + quic_params: self.quic_params, client_auth: Some(client_auth), - ech_retry_configs: self.ech_retry_configs, + ech: self.ech, + expected_certificate_type: self.expected_certificate_type, }) + .into() } else { Box::new(ExpectCertificate { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, + hs: self.hs, suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, + quic_params: self.quic_params, client_auth: Some(client_auth), - message_already_in_transcript: false, - ech_retry_configs: self.ech_retry_configs, + ech: self.ech, + expected_certificate_type: self.expected_certificate_type, }) + .into() }) } - - fn into_owned(self: Box) -> hs::NextState<'static> { - self - } } struct ExpectCompressedCertificate { - config: Arc, - server_name: ServerName<'static>, - randoms: ConnectionRandoms, + hs: HandshakeState, suite: &'static Tls13CipherSuite, - transcript: HandshakeHash, - key_schedule: KeyScheduleHandshake, + quic_params: Option>, client_auth: Option, - ech_retry_configs: Option>, + ech: Ech, + expected_certificate_type: CertificateType, } -impl State for ExpectCompressedCertificate { - fn handle<'m>( - mut self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - self.transcript.add_message(&m); +impl ExpectCompressedCertificate { + fn handle_input(mut self, Input { message, .. }: Input<'_>) -> Result { + self.hs.transcript.add_message(&message); let compressed_cert = require_handshake_msg_move!( - m, + message, HandshakeType::CompressedCertificate, HandshakePayload::CompressedCertificate )?; let selected_decompressor = self + .hs .config .cert_decompressors .iter() .find(|item| item.algorithm() == compressed_cert.alg); let Some(decompressor) = selected_decompressor else { - return Err(cx.common.send_fatal_alert( - AlertDescription::BadCertificate, - PeerMisbehaved::SelectedUnofferedCertCompression, - )); + return Err(PeerMisbehaved::SelectedUnofferedCertCompression.into()); }; if compressed_cert.uncompressed_len as usize > CERTIFICATE_MAX_SIZE_LIMIT { - return Err(cx.common.send_fatal_alert( - AlertDescription::BadCertificate, - InvalidMessage::MessageTooLarge, - )); + return Err(InvalidMessage::CertificatePayloadTooLarge.into()); } let mut decompress_buffer = vec![0u8; compressed_cert.uncompressed_len as usize]; if let Err(compress::DecompressionFailed) = - decompressor.decompress(compressed_cert.compressed.0.bytes(), &mut decompress_buffer) + decompressor.decompress(compressed_cert.compressed.bytes(), &mut decompress_buffer) { - return Err(cx.common.send_fatal_alert( - AlertDescription::BadCertificate, - PeerMisbehaved::InvalidCertCompression, - )); + return Err(PeerMisbehaved::InvalidCertCompression.into()); } - let cert_payload = - match CertificatePayloadTls13::read(&mut Reader::init(&decompress_buffer)) { - Ok(cm) => cm, - Err(err) => { - return Err(cx - .common - .send_fatal_alert(AlertDescription::BadCertificate, err)); - } - }; + let cert_payload = CertificatePayloadTls13::read(&mut Reader::new(&decompress_buffer))?; trace!( "Server certificate decompressed using {:?} ({} bytes -> {})", compressed_cert.alg, - compressed_cert - .compressed - .0 - .bytes() - .len(), + compressed_cert.compressed.bytes().len(), compressed_cert.uncompressed_len, ); - let m = Message { - version: ProtocolVersion::TLSv1_3, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::Certificate, - payload: HandshakePayload::CertificateTls13(cert_payload.into_owned()), - }), - }; - - Box::new(ExpectCertificate { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, + ExpectCertificate { + hs: self.hs, suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, + quic_params: self.quic_params, client_auth: self.client_auth, - message_already_in_transcript: true, - ech_retry_configs: self.ech_retry_configs, - }) - .handle(cx, m) - } - - fn into_owned(self: Box) -> hs::NextState<'static> { - self + ech: self.ech, + expected_certificate_type: self.expected_certificate_type, + } + .handle_cert_payload(cert_payload) } } struct ExpectCertificate { - config: Arc, - server_name: ServerName<'static>, - randoms: ConnectionRandoms, + hs: HandshakeState, suite: &'static Tls13CipherSuite, - transcript: HandshakeHash, - key_schedule: KeyScheduleHandshake, + quic_params: Option>, client_auth: Option, - message_already_in_transcript: bool, - ech_retry_configs: Option>, + ech: Ech, + expected_certificate_type: CertificateType, } -impl State for ExpectCertificate { - fn handle<'m>( - mut self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - if !self.message_already_in_transcript { - self.transcript.add_message(&m); - } - let cert_chain = require_handshake_msg_move!( - m, +impl ExpectCertificate { + fn handle_input(mut self, Input { message, .. }: Input<'_>) -> Result { + self.hs.transcript.add_message(&message); + + self.handle_cert_payload(require_handshake_msg_move!( + message, HandshakeType::Certificate, HandshakePayload::CertificateTls13 - )?; + )?) + } + fn handle_cert_payload( + self, + cert_chain: CertificatePayloadTls13<'_>, + ) -> Result { // This is only non-empty for client auth. - if !cert_chain.context.0.is_empty() { - return Err(cx.common.send_fatal_alert( - AlertDescription::DecodeError, - InvalidMessage::InvalidCertRequest, - )); + if !cert_chain.context.is_empty() { + return Err(InvalidMessage::InvalidCertRequest.into()); } - if cert_chain.any_entry_has_duplicate_extension() - || cert_chain.any_entry_has_unknown_extension() - { - return Err(cx.common.send_fatal_alert( - AlertDescription::UnsupportedExtension, - PeerMisbehaved::BadCertChainExtensions, - )); - } - let end_entity_ocsp = cert_chain.end_entity_ocsp(); + let end_entity_ocsp = cert_chain.end_entity_ocsp().to_vec(); let server_cert = ServerCertDetails::new( cert_chain .into_certificate_chain() @@ -1085,47 +1079,53 @@ impl State for ExpectCertificate { ); Ok(Box::new(ExpectCertificateVerify { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, + hs: self.hs, suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, + quic_params: self.quic_params, server_cert, client_auth: self.client_auth, - ech_retry_configs: self.ech_retry_configs, - })) + ech: self.ech, + expected_certificate_type: self.expected_certificate_type, + }) + .into()) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl ExpectCertificate { + fn handle( + self: Box, + input: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { + self.handle_input(input) + } +} + +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::Certificate(value)) } } // --- TLS1.3 CertificateVerify --- -struct ExpectCertificateVerify<'a> { - config: Arc, - server_name: ServerName<'static>, - randoms: ConnectionRandoms, +struct ExpectCertificateVerify { + hs: HandshakeState, suite: &'static Tls13CipherSuite, - transcript: HandshakeHash, - key_schedule: KeyScheduleHandshake, - server_cert: ServerCertDetails<'a>, + quic_params: Option>, + server_cert: ServerCertDetails, client_auth: Option, - ech_retry_configs: Option>, + ech: Ech, + expected_certificate_type: CertificateType, } -impl State for ExpectCertificateVerify<'_> { - fn handle<'m>( +impl ExpectCertificateVerify { + fn handle( mut self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { + Input { message, .. }: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { let cert_verify = require_handshake_msg!( - m, + message, HandshakeType::CertificateVerify, HandshakePayload::CertificateVerify )?; @@ -1133,119 +1133,107 @@ impl State for ExpectCertificateVerify<'_> { trace!("Server cert is {:?}", self.server_cert.cert_chain); // 1. Verify the certificate chain. - let (end_entity, intermediates) = self - .server_cert - .cert_chain - .split_first() - .ok_or(Error::NoCertificatesPresented)?; - - let now = self.config.current_time()?; + let identity = Identity::from_peer( + self.server_cert.cert_chain.0, + self.expected_certificate_type, + )? + .ok_or(PeerMisbehaved::NoCertificatesPresented)?; let cert_verified = self + .hs .config - .verifier - .verify_server_cert( - end_entity, - intermediates, - &self.server_name, - &self.server_cert.ocsp_response, - now, - ) - .map_err(|err| { - cx.common - .send_cert_verify_error_alert(err) + .verifier() + .verify_identity(&ServerIdentity { + identity: &identity, + server_name: &self.hs.session_key.server_name, + ocsp_response: &self.server_cert.ocsp_response, + now: self.hs.config.current_time()?, })?; // 2. Verify their signature on the handshake. - let handshake_hash = self.transcript.current_hash(); + let handshake_hash = self.hs.transcript.current_hash(); let sig_verified = self + .hs .config - .verifier - .verify_tls13_signature( - construct_server_verify_message(&handshake_hash).as_ref(), - end_entity, - cert_verify, - ) - .map_err(|err| { - cx.common - .send_cert_verify_error_alert(err) + .verifier() + .verify_tls13_signature(&SignatureVerificationInput { + message: construct_server_verify_message(&handshake_hash).as_ref(), + signer: &identity.as_signer(), + signature: cert_verify, })?; - cx.common.peer_certificates = Some(self.server_cert.cert_chain.into_owned()); - self.transcript.add_message(&m); + self.hs.transcript.add_message(&message); Ok(Box::new(ExpectFinished { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, - suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, + hs: self.hs, + session_input: Tls13ClientSessionInput { + suite: self.suite, + peer_identity: identity, + quic_params: self.quic_params, + }, client_auth: self.client_auth, cert_verified, sig_verified, - ech_retry_configs: self.ech_retry_configs, - })) + ech: self.ech, + in_early_traffic: false, + }) + .into()) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - Box::new(ExpectCertificateVerify { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, - suite: self.suite, - transcript: self.transcript, - key_schedule: self.key_schedule, - server_cert: self.server_cert.into_owned(), - client_auth: self.client_auth, - ech_retry_configs: self.ech_retry_configs, - }) +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::CertificateVerify(value)) } } fn emit_compressed_certificate_tls13( flight: &mut HandshakeFlightTls13<'_>, - certkey: &CertifiedKey, + credentials: &SelectedCredential, auth_context: Option>, compressor: &dyn compress::CertCompressor, config: &ClientConfig, ) { - let mut cert_payload = CertificatePayloadTls13::new(certkey.cert.iter(), None); - cert_payload.context = PayloadU8::new(auth_context.clone().unwrap_or_default()); + let mut cert_payload = + CertificatePayloadTls13::new(credentials.identity.as_certificates(), None); + cert_payload.context = auth_context + .clone() + .unwrap_or_default() + .into(); let Ok(compressed) = config .cert_compression_cache .compression_for(compressor, &cert_payload) else { - return emit_certificate_tls13(flight, Some(certkey), auth_context); + return emit_certificate_tls13(flight, Some(credentials), auth_context); }; - flight.add(HandshakeMessagePayload { - typ: HandshakeType::CompressedCertificate, - payload: HandshakePayload::CompressedCertificate(compressed.compressed_cert_payload()), - }); + flight.add(HandshakeMessagePayload( + HandshakePayload::CompressedCertificate(compressed.compressed_cert_payload()), + )); } fn emit_certificate_tls13( flight: &mut HandshakeFlightTls13<'_>, - certkey: Option<&CertifiedKey>, + credentials: Option<&SelectedCredential>, auth_context: Option>, ) { - let certs = certkey - .map(|ck| ck.cert.as_ref()) - .unwrap_or(&[][..]); - let mut cert_payload = CertificatePayloadTls13::new(certs.iter(), None); - cert_payload.context = PayloadU8::new(auth_context.unwrap_or_default()); - - flight.add(HandshakeMessagePayload { - typ: HandshakeType::Certificate, - payload: HandshakePayload::CertificateTls13(cert_payload), - }); + let mut cert_payload = match credentials { + Some(credentials) => { + CertificatePayloadTls13::new(credentials.identity.as_certificates(), None) + } + None => CertificatePayloadTls13::new([].into_iter(), None), + }; + + cert_payload.context = auth_context.unwrap_or_default().into(); + flight.add(HandshakeMessagePayload(HandshakePayload::CertificateTls13( + cert_payload, + ))); } fn emit_certverify_tls13( flight: &mut HandshakeFlightTls13<'_>, - signer: &dyn Signer, + signer: Box, ) -> Result<(), Error> { let message = construct_client_verify_message(&flight.transcript.current_hash()); @@ -1253,94 +1241,91 @@ fn emit_certverify_tls13( let sig = signer.sign(message.as_ref())?; let dss = DigitallySignedStruct::new(scheme, sig); - flight.add(HandshakeMessagePayload { - typ: HandshakeType::CertificateVerify, - payload: HandshakePayload::CertificateVerify(dss), - }); + flight.add(HandshakeMessagePayload( + HandshakePayload::CertificateVerify(dss), + )); Ok(()) } -fn emit_finished_tls13(flight: &mut HandshakeFlightTls13<'_>, verify_data: &crypto::hmac::Tag) { +fn emit_finished_tls13( + flight: &mut HandshakeFlightTls13<'_>, + verify_data: &crypto::hmac::PublicTag, +) { let verify_data_payload = Payload::new(verify_data.as_ref()); - flight.add(HandshakeMessagePayload { - typ: HandshakeType::Finished, - payload: HandshakePayload::Finished(verify_data_payload), - }); + flight.add(HandshakeMessagePayload(HandshakePayload::Finished( + verify_data_payload, + ))); } -fn emit_end_of_early_data_tls13(transcript: &mut HandshakeHash, common: &mut CommonState) { - if common.is_quic() { - return; - } - +fn emit_end_of_early_data_tls13(transcript: &mut HandshakeHash, output: &mut dyn Output<'_>) { let m = Message { version: ProtocolVersion::TLSv1_3, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::EndOfEarlyData, - payload: HandshakePayload::EndOfEarlyData, - }), + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::EndOfEarlyData, + )), }; transcript.add_message(&m); - common.send_msg(m, true); + output.send_msg(m, true); } struct ExpectFinished { - config: Arc, - server_name: ServerName<'static>, - randoms: ConnectionRandoms, - suite: &'static Tls13CipherSuite, - transcript: HandshakeHash, - key_schedule: KeyScheduleHandshake, + hs: HandshakeState, + session_input: Tls13ClientSessionInput, client_auth: Option, - cert_verified: verify::ServerCertVerified, + cert_verified: verify::PeerVerified, sig_verified: verify::HandshakeSignatureValid, - ech_retry_configs: Option>, + ech: Ech, + in_early_traffic: bool, } -impl State for ExpectFinished { - fn handle<'m>( +impl ExpectFinished { + fn handle( self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { + input: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { let mut st = *self; - let finished = - require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?; + let finished = require_handshake_msg!( + input.message, + HandshakeType::Finished, + HandshakePayload::Finished + )?; - let handshake_hash = st.transcript.current_hash(); + let proof = input.check_aligned_handshake()?; + let handshake_hash = st.hs.transcript.current_hash(); let expect_verify_data = st + .hs .key_schedule - .sign_server_finish(&handshake_hash); + .sign_server_finish(&handshake_hash, &proof); let fin = match ConstantTimeEq::ct_eq(expect_verify_data.as_ref(), finished.bytes()).into() { true => verify::FinishedMessageVerified::assertion(), false => { - return Err(cx - .common - .send_fatal_alert(AlertDescription::DecryptError, Error::DecryptError)); + return Err(PeerMisbehaved::IncorrectFinished.into()); } }; - st.transcript.add_message(&m); + st.hs + .transcript + .add_message(&input.message); - let hash_after_handshake = st.transcript.current_hash(); + let hash_after_handshake = st.hs.transcript.current_hash(); /* The EndOfEarlyData message to server is still encrypted with early data keys, * but appears in the transcript after the server Finished. */ - if cx.common.early_traffic { - emit_end_of_early_data_tls13(&mut st.transcript, cx.common); - cx.common.early_traffic = false; - cx.data.early_data.finished(); - st.key_schedule - .set_handshake_encrypter(cx.common); + if st.in_early_traffic { + if !st.hs.key_schedule.protocol().is_quic() { + emit_end_of_early_data_tls13(&mut st.hs.transcript, output); + } + output.emit(Event::EarlyData(EarlyDataEvent::Finished)); + st.hs + .key_schedule + .set_handshake_encrypter(output.send()); } - let mut flight = HandshakeFlightTls13::new(&mut st.transcript); + let mut flight = HandshakeFlightTls13::new(&mut st.hs.transcript); /* Send our authentication/finished messages. These are still encrypted * with our handshake keys. */ @@ -1354,211 +1339,221 @@ impl State for ExpectFinished { ClientAuthDetails::Verify { auth_context_tls13: auth_context, .. - } if cx.data.ech_status == EchStatus::Rejected => { + } if st.ech.status == EchStatus::Rejected => { // If ECH was offered, and rejected, we MUST respond with // an empty certificate message. emit_certificate_tls13(&mut flight, None, auth_context); } ClientAuthDetails::Verify { - certkey, - signer, + credentials, auth_context_tls13: auth_context, compressor, } => { if let Some(compressor) = compressor { emit_compressed_certificate_tls13( &mut flight, - &certkey, + &credentials, auth_context, compressor, - &st.config, + &st.hs.config, ); } else { - emit_certificate_tls13(&mut flight, Some(&certkey), auth_context); + emit_certificate_tls13(&mut flight, Some(&credentials), auth_context); } - emit_certverify_tls13(&mut flight, signer.as_ref())?; + emit_certverify_tls13(&mut flight, credentials.signer)?; } } } let (key_schedule_pre_finished, verify_data) = st + .hs .key_schedule .into_pre_finished_client_traffic( hash_after_handshake, flight.transcript.current_hash(), - &*st.config.key_log, - &st.randoms.client, + &*st.hs.config.key_log, + &st.hs.randoms.client, ); emit_finished_tls13(&mut flight, &verify_data); - flight.finish(cx.common); + flight.finish(output); /* We're now sure this server supports TLS1.3. But if we run out of TLS1.3 tickets * when connecting to it again, we definitely don't want to attempt a TLS1.2 resumption. */ - st.config + st.hs + .config .resumption .store - .remove_tls12_session(&st.server_name); + .remove_tls12_session(&st.hs.session_key); /* Now move to our application traffic keys. */ - cx.common.check_aligned_handshake()?; - let key_schedule_traffic = key_schedule_pre_finished.into_traffic(cx.common); - cx.common - .start_traffic(&mut cx.sendable_plaintext); + let (key_schedule, exporter, resumption) = + key_schedule_pre_finished.into_traffic(output, st.hs.transcript.current_hash(), &proof); + let (key_schedule_send, key_schedule_recv) = key_schedule.split(); + + output.output(OutputEvent::PeerIdentity( + st.session_input.peer_identity.clone(), + )); + output.output(OutputEvent::Exporter(Box::new(exporter))); + output + .send() + .update_key_schedule(Box::new(key_schedule_send)); + output.start_traffic(); // Now that we've reached the end of the normal handshake we must enforce ECH acceptance by // sending an alert and returning an error (potentially with retry configs) if the server // did not accept our ECH offer. - if cx.data.ech_status == EchStatus::Rejected { - return Err(ech::fatal_alert_required(st.ech_retry_configs, cx.common)); + if st.ech.status == EchStatus::Rejected { + return Err(RejectedEch { + retry_configs: st.ech.retry_configs, + } + .into()); } + let protocol = key_schedule_recv.protocol(); + let st = ExpectTraffic { - config: Arc::clone(&st.config), - session_storage: Arc::clone(&st.config.resumption.store), - server_name: st.server_name, - suite: st.suite, - transcript: st.transcript, - key_schedule: key_schedule_traffic, + config: st.hs.config.clone(), + session_storage: st.hs.config.resumption.store.clone(), + session_key: st.hs.session_key, + session_input: st.session_input, + key_schedule_recv, + resumption, + counters: TrafficTemperCounters::default(), _cert_verified: st.cert_verified, _sig_verified: st.sig_verified, _fin_verified: fin, }; - Ok(match cx.common.is_quic() { - true => Box::new(ExpectQuicTraffic(st)), - false => Box::new(st), + Ok(match protocol.is_quic() { + true => Box::new(ExpectQuicTraffic(st)).into(), + false => Box::new(st).into(), }) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::Finished(value)) } } +struct HandshakeState { + config: Arc, + session_key: ClientSessionKey<'static>, + randoms: ConnectionRandoms, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, +} + // -- Traffic transit state (TLS1.3) -- // In this state we can be sent tickets, key updates, // and application data. -struct ExpectTraffic { +pub(super) struct ExpectTraffic { config: Arc, session_storage: Arc, - server_name: ServerName<'static>, - suite: &'static Tls13CipherSuite, - transcript: HandshakeHash, - key_schedule: KeyScheduleTraffic, - _cert_verified: verify::ServerCertVerified, + session_key: ClientSessionKey<'static>, + session_input: Tls13ClientSessionInput, + key_schedule_recv: KeyScheduleTrafficReceive, + resumption: KeyScheduleResumption, + counters: TrafficTemperCounters, + _cert_verified: verify::PeerVerified, _sig_verified: verify::HandshakeSignatureValid, _fin_verified: verify::FinishedMessageVerified, } impl ExpectTraffic { - fn handle_new_ticket_tls13( - &mut self, - cx: &mut ClientContext<'_>, - nst: &NewSessionTicketPayloadTls13, - ) -> Result<(), Error> { - if nst.has_duplicate_extension() { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::DuplicateNewSessionTicketExtensions, - )); - } - - let handshake_hash = self.transcript.current_hash(); - let secret = ResumptionSecret::new(&self.key_schedule, &handshake_hash) - .derive_ticket_psk(&nst.nonce.0); + fn handle_new_ticket_impl(&self, nst: &NewSessionTicketPayloadTls13) -> Result<(), Error> { + let secret = self + .resumption + .derive_ticket_psk(nst.nonce.bytes()); let now = self.config.current_time()?; - - #[allow(unused_mut)] - let mut value = persist::Tls13ClientSessionValue::new( - self.suite, - Arc::clone(&nst.ticket), - secret.as_ref(), - cx.common - .peer_certificates - .clone() - .unwrap_or_default(), - now, - nst.lifetime, - nst.age_add, - nst.max_early_data_size() - .unwrap_or_default(), - ); - - if cx.common.is_quic() { - if let Some(sz) = nst.max_early_data_size() { + let value = Tls13Session::new(nst, self.session_input.clone(), secret.as_ref(), now); + if self + .key_schedule_recv + .protocol() + .is_quic() + { + if let Some(sz) = nst.extensions.max_early_data_size { if sz != 0 && sz != 0xffff_ffff { return Err(PeerMisbehaved::InvalidMaxEarlyDataSize.into()); } } - - if let Some(ref quic_params) = &cx.common.quic.params { - value.set_quic_params(quic_params); - } } self.session_storage - .insert_tls13_ticket(self.server_name.clone(), value); + .insert_tls13_ticket(self.session_key.clone(), value); Ok(()) } + fn handle_new_ticket_tls13( + &self, + output: &mut dyn Output<'_>, + nst: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + let received = &mut output.receive().tls13_tickets_received; + *received = received.saturating_add(1); + self.handle_new_ticket_impl(nst) + } + fn handle_key_update( &mut self, - common: &mut CommonState, + input: Input<'_>, + output: &mut dyn Output<'_>, key_update_request: &KeyUpdateRequest, ) -> Result<(), Error> { - if let Protocol::Quic = common.protocol { - return Err(common.send_fatal_alert( - AlertDescription::UnexpectedMessage, - PeerMisbehaved::KeyUpdateReceivedInQuicConnection, - )); + if self + .key_schedule_recv + .protocol() + .is_quic() + { + return Err(PeerMisbehaved::KeyUpdateReceivedInQuicConnection.into()); } // Mustn't be interleaved with other handshake messages. - common.check_aligned_handshake()?; + let proof = input.check_aligned_handshake()?; - if common.should_update_key(key_update_request)? { - self.key_schedule - .update_encrypter_and_notify(common); + match *key_update_request { + KeyUpdateRequest::UpdateNotRequested => {} + KeyUpdateRequest::UpdateRequested => output.send().ensure_key_update_queued(), + _ => return Err(InvalidMessage::InvalidKeyUpdate.into()), } // Update our read-side keys. - self.key_schedule - .update_decrypter(common); + self.key_schedule_recv + .update_decrypter(output.receive(), &proof); Ok(()) } } -impl State for ExpectTraffic { +impl ExpectTraffic { fn handle<'m>( mut self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { - MessagePayload::ApplicationData(payload) => cx - .common - .take_received_plaintext(payload), + input: Input<'m>, + output: &mut dyn Output<'m>, + ) -> Result { + match input.message.payload { + MessagePayload::ApplicationData(payload) => { + self.counters.received_app_data(); + output.received_plaintext(payload); + } MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::NewSessionTicketTls13(ref new_ticket), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::NewSessionTicketTls13(new_ticket)), .. - } => self.handle_new_ticket_tls13(cx, new_ticket)?, + } => { + self.counters + .received_handshake_message()?; + self.handle_new_ticket_tls13(output, &new_ticket)? + } MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::KeyUpdate(ref key_update), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::KeyUpdate(key_update)), .. - } => self.handle_key_update(cx.common, key_update)?, + } => { + self.counters + .received_handshake_message()?; + self.handle_key_update(input, output, &key_update)? + } payload => { return Err(inappropriate_handshake_message( &payload, @@ -1568,66 +1563,126 @@ impl State for ExpectTraffic { } } - Ok(self) + Ok(self.into()) + } + + pub(super) fn into_external_state( + self: Box, + send_keys: &Option>, + ) -> Result<(PartiallyExtractedSecrets, Box), Error> { + if !self.config.enable_secret_extraction { + return Err(ApiMisuse::SecretExtractionRequiresPriorOptIn.into()); + } + let Some(send_keys) = send_keys else { + return Err(Error::Unreachable( + "send_keys required for TLS1.3 into_external_state", + )); + }; + Ok(( + PartiallyExtractedSecrets { + tx: send_keys.extract()?, + rx: self.key_schedule_recv.extract()?, + }, + self, + )) } +} - fn send_key_update_request(&mut self, common: &mut CommonState) -> Result<(), Error> { - self.key_schedule - .request_key_update_and_update_encrypter(common) +impl KernelState for ExpectTraffic { + fn update_rx_secret(&mut self) -> Result { + self.key_schedule_recv + .refresh_traffic_secret() } - fn export_keying_material( + fn handle_new_session_ticket( &self, - output: &mut [u8], - label: &[u8], - context: Option<&[u8]>, + message: &NewSessionTicketPayloadTls13, ) -> Result<(), Error> { - self.key_schedule - .export_keying_material(output, label, context) - } - - fn extract_secrets(&self) -> Result { - self.key_schedule - .extract_secrets(Side::Client) + self.handle_new_ticket_impl(message) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::Traffic(value)) } } -struct ExpectQuicTraffic(ExpectTraffic); +pub(super) struct ExpectQuicTraffic(ExpectTraffic); -impl State for ExpectQuicTraffic { - fn handle<'m>( - mut self: Box, - cx: &mut ClientContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { +impl ExpectQuicTraffic { + fn handle( + self: Box, + Input { message, .. }: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { let nst = require_handshake_msg!( - m, + message, HandshakeType::NewSessionTicket, HandshakePayload::NewSessionTicketTls13 )?; self.0 - .handle_new_ticket_tls13(cx, nst)?; - Ok(self) + .handle_new_ticket_tls13(output, nst)?; + Ok(self.into()) } - fn export_keying_material( - &self, - output: &mut [u8], - label: &[u8], - context: Option<&[u8]>, - ) -> Result<(), Error> { - self.0 - .export_keying_material(output, label, context) + pub(super) fn into_external_state( + self: Box, + send_keys: &Option>, + ) -> Result<(PartiallyExtractedSecrets, Box), Error> { + if !self.0.config.enable_secret_extraction { + return Err(ApiMisuse::SecretExtractionRequiresPriorOptIn.into()); + } + let Some(send_keys) = send_keys else { + return Err(Error::Unreachable( + "send_keys required for TLS1.3 into_external_state", + )); + }; + Ok(( + PartiallyExtractedSecrets { + tx: send_keys.extract()?, + rx: self.0.key_schedule_recv.extract()?, + }, + self, + )) + } +} + +impl KernelState for ExpectQuicTraffic { + fn update_rx_secret(&mut self) -> Result { + Err(Error::Unreachable( + "KeyUpdate is not supported for QUIC connections", + )) + } + + fn handle_new_session_ticket(&self, nst: &NewSessionTicketPayloadTls13) -> Result<(), Error> { + self.0.handle_new_ticket_impl(nst) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ClientState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::QuicTraffic(value)) } } + +struct Ech { + status: EchStatus, + retry_configs: Option>, +} + +// Extensions we expect in plaintext in the ServerHello. +const ALLOWED_PLAINTEXT_EXTS: &[ExtensionType] = &[ + ExtensionType::KeyShare, + ExtensionType::PreSharedKey, + ExtensionType::SupportedVersions, +]; + +// Only the intersection of things we offer, and those disallowed +// in TLS1.3 +const DISALLOWED_TLS13_EXTS: &[ExtensionType] = &[ + ExtensionType::ECPointFormats, + ExtensionType::SessionTicket, + ExtensionType::RenegotiationInfo, + ExtensionType::ExtendedMasterSecret, +]; diff --git a/rustls/src/common_state.rs b/rustls/src/common_state.rs index 9b9ab3f7f4c..93be00d4c4d 100644 --- a/rustls/src/common_state.rs +++ b/rustls/src/common_state.rs @@ -1,93 +1,37 @@ use alloc::boxed::Box; use alloc::vec::Vec; - -use pki_types::CertificateDer; - -use crate::crypto::SupportedKxGroup; -use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion}; -use crate::error::{Error, InvalidMessage, PeerMisbehaved}; +use core::fmt; +use core::ops::{Deref, DerefMut, Range}; + +use pki_types::DnsName; + +use crate::client::EchStatus; +use crate::conn::{Exporter, ReceivePath, SendOutput, SendPath}; +use crate::crypto::Identity; +use crate::crypto::cipher::Payload; +use crate::crypto::kx::SupportedKxGroup; +use crate::enums::{ApplicationProtocol, ProtocolVersion}; +use crate::error::{AlertDescription, Error}; use crate::hash_hs::HandshakeHash; -use crate::log::{debug, error, warn}; -use crate::msgs::alert::AlertMessagePayload; -use crate::msgs::base::Payload; -use crate::msgs::codec::Codec; -use crate::msgs::enums::{AlertLevel, ExtensionType, KeyUpdateRequest}; -use crate::msgs::fragmenter::MessageFragmenter; -use crate::msgs::handshake::{CertificateChain, HandshakeMessagePayload}; -use crate::msgs::message::{ - Message, MessagePayload, OutboundChunks, OutboundOpaqueMessage, OutboundPlainMessage, - PlainMessage, +use crate::msgs::{ + AlertLevel, Codec, Delocator, HandshakeMessagePayload, Locator, Message, MessagePayload, }; -use crate::record_layer::PreEncryptAction; -use crate::suites::{PartiallyExtractedSecrets, SupportedCipherSuite}; -#[cfg(feature = "tls12")] -use crate::tls12::ConnectionSecrets; -use crate::unbuffered::{EncryptError, InsufficientSizeError}; -use crate::vecbuf::ChunkVecBuffer; -use crate::{quic, record_layer, PeerIncompatible}; +use crate::quic::{self, QuicOutput}; +use crate::suites::SupportedCipherSuite; /// Connection state common to both client and server connections. pub struct CommonState { - pub(crate) negotiated_version: Option, - pub(crate) handshake_kind: Option, - pub(crate) side: Side, - pub(crate) record_layer: record_layer::RecordLayer, - pub(crate) suite: Option, - pub(crate) kx_state: KxState, - pub(crate) alpn_protocol: Option>, - pub(crate) aligned_handshake: bool, - pub(crate) may_send_application_data: bool, - pub(crate) may_receive_application_data: bool, - pub(crate) early_traffic: bool, - sent_fatal_alert: bool, - /// If the peer has signaled end of stream. - pub(crate) has_received_close_notify: bool, - #[cfg(feature = "std")] - pub(crate) has_seen_eof: bool, - pub(crate) peer_certificates: Option>, - message_fragmenter: MessageFragmenter, - pub(crate) received_plaintext: ChunkVecBuffer, - pub(crate) sendable_tls: ChunkVecBuffer, - queued_key_update_message: Option>, - - /// Protocol whose key schedule should be used. Unused for TLS < 1.3. - pub(crate) protocol: Protocol, - pub(crate) quic: quic::Quic, - pub(crate) enable_secret_extraction: bool, - temper_counters: TemperCounters, - pub(crate) refresh_traffic_keys_pending: bool, - pub(crate) fips: bool, + pub(crate) outputs: ConnectionOutputs, + pub(crate) send: SendPath, + pub(crate) recv: ReceivePath, } impl CommonState { pub(crate) fn new(side: Side) -> Self { Self { - negotiated_version: None, - handshake_kind: None, - side, - record_layer: record_layer::RecordLayer::new(), - suite: None, - kx_state: KxState::default(), - alpn_protocol: None, - aligned_handshake: true, - may_send_application_data: false, - may_receive_application_data: false, - early_traffic: false, - sent_fatal_alert: false, - has_received_close_notify: false, - #[cfg(feature = "std")] - has_seen_eof: false, - peer_certificates: None, - message_fragmenter: MessageFragmenter::default(), - received_plaintext: ChunkVecBuffer::new(Some(DEFAULT_RECEIVED_PLAINTEXT_LIMIT)), - sendable_tls: ChunkVecBuffer::new(Some(DEFAULT_BUFFER_LIMIT)), - queued_key_update_message: None, - protocol: Protocol::Tcp, - quic: quic::Quic::default(), - enable_secret_extraction: false, - temper_counters: TemperCounters::default(), - refresh_traffic_keys_pending: false, - fips: false, + outputs: ConnectionOutputs::default(), + send: SendPath::default(), + recv: ReceivePath::new(side), } } @@ -95,7 +39,18 @@ impl CommonState { /// /// [`Connection::write_tls`]: crate::Connection::write_tls pub fn wants_write(&self) -> bool { - !self.sendable_tls.is_empty() + !self.send.sendable_tls.is_empty() + } + + /// Queues a `close_notify` warning alert to be sent in the next + /// [`Connection::write_tls`] call. This informs the peer that the + /// connection is being closed. + /// + /// Does nothing if any `close_notify` or fatal alert was already sent. + /// + /// [`Connection::write_tls`]: crate::Connection::write_tls + pub fn send_close_notify(&mut self) { + self.send.send_close_notify() } /// Returns true if the connection is currently performing the TLS handshake. @@ -106,32 +61,55 @@ impl CommonState { /// /// [`Connection::process_new_packets()`]: crate::Connection::process_new_packets pub fn is_handshaking(&self) -> bool { - !(self.may_send_application_data && self.may_receive_application_data) + !(self.send.may_send_application_data && self.recv.may_receive_application_data) } +} + +impl Deref for CommonState { + type Target = ConnectionOutputs; + fn deref(&self) -> &Self::Target { + &self.outputs + } +} + +impl DerefMut for CommonState { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.outputs + } +} + +impl fmt::Debug for CommonState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CommonState") + .finish_non_exhaustive() + } +} + +/// Facts about the connection learned through the handshake. +#[derive(Default)] +pub struct ConnectionOutputs { + negotiated_version: Option, + handshake_kind: Option, + suite: Option, + negotiated_kx_group: Option<&'static dyn SupportedKxGroup>, + alpn_protocol: Option>, + peer_identity: Option>, + pub(crate) exporter: Option>, + pub(crate) early_exporter: Option>, +} + +impl ConnectionOutputs { /// Retrieves the certificate chain or the raw public key used by the peer to authenticate. /// - /// The order of the certificate chain is as it appears in the TLS - /// protocol: the first certificate relates to the peer, the - /// second certifies the first, the third certifies the second, and - /// so on. - /// - /// When using raw public keys, the first and only element is the raw public key. - /// /// This is made available for both full and resumed handshakes. /// - /// For clients, this is the certificate chain or the raw public key of the server. - /// - /// For servers, this is the certificate chain or the raw public key of the client, - /// if client authentication was completed. + /// For clients, this is the identity of the server. For servers, this is the identity of the + /// client, if client authentication was completed. /// /// The return value is None until this value is available. - /// - /// Note: the return type of the 'certificate', when using raw public keys is `CertificateDer<'static>` - /// even though this should technically be a `SubjectPublicKeyInfoDer<'static>`. - /// This choice simplifies the API and ensures backwards compatibility. - pub fn peer_certificates(&self) -> Option<&[CertificateDer<'static>]> { - self.peer_certificates.as_deref() + pub fn peer_identity(&self) -> Option<&Identity<'static>> { + self.peer_identity.as_ref() } /// Retrieves the protocol agreed with the peer via ALPN. @@ -139,13 +117,13 @@ impl CommonState { /// A return value of `None` after handshake completion /// means no protocol was agreed (because no protocols /// were offered or accepted by the peer). - pub fn alpn_protocol(&self) -> Option<&[u8]> { - self.get_alpn_protocol() + pub fn alpn_protocol(&self) -> Option<&ApplicationProtocol<'static>> { + self.alpn_protocol.as_ref() } - /// Retrieves the ciphersuite agreed with the peer. + /// Retrieves the cipher suite agreed with the peer. /// - /// This returns None until the ciphersuite is agreed. + /// This returns None until the cipher suite is agreed. pub fn negotiated_cipher_suite(&self) -> Option { self.suite } @@ -156,14 +134,11 @@ impl CommonState { /// the type of handshake, and the protocol version. /// /// If [`CommonState::is_handshaking()`] is true this function will return `None`. - /// Similarly, if the [`CommonState::handshake_kind()`] is [`HandshakeKind::Resumed`] - /// and the [`CommonState::protocol_version()`] is TLS 1.2, then no key exchange will have + /// Similarly, if the [`ConnectionOutputs::handshake_kind()`] is [`HandshakeKind::Resumed`] + /// and the [`ConnectionOutputs::protocol_version()`] is TLS 1.2, then no key exchange will have /// occurred and this function will return `None`. pub fn negotiated_key_exchange_group(&self) -> Option<&'static dyn SupportedKxGroup> { - match self.kx_state { - KxState::Complete(group) => Some(group), - _ => None, - } + self.negotiated_kx_group } /// Retrieves the protocol version agreed with the peer. @@ -183,593 +158,56 @@ impl CommonState { self.handshake_kind } - pub(crate) fn is_tls13(&self) -> bool { - matches!(self.negotiated_version, Some(ProtocolVersion::TLSv1_3)) - } - - pub(crate) fn process_main_protocol( - &mut self, - msg: Message<'_>, - mut state: Box>, - data: &mut Data, - sendable_plaintext: Option<&mut ChunkVecBuffer>, - ) -> Result>, Error> { - // For TLS1.2, outside of the handshake, send rejection alerts for - // renegotiation requests. These can occur any time. - if self.may_receive_application_data && !self.is_tls13() { - let reject_ty = match self.side { - Side::Client => HandshakeType::HelloRequest, - Side::Server => HandshakeType::ClientHello, - }; - if msg.is_handshake_type(reject_ty) { - self.temper_counters - .received_renegotiation_request()?; - self.send_warning_alert(AlertDescription::NoRenegotiation); - return Ok(state); - } - } - - let mut cx = Context { - common: self, - data, - sendable_plaintext, - }; - match state.handle(&mut cx, msg) { - Ok(next) => { - state = next.into_owned(); - Ok(state) - } - Err(e @ Error::InappropriateMessage { .. }) - | Err(e @ Error::InappropriateHandshakeMessage { .. }) => { - Err(self.send_fatal_alert(AlertDescription::UnexpectedMessage, e)) - } - Err(e) => Err(e), - } - } - - pub(crate) fn write_plaintext( - &mut self, - payload: OutboundChunks<'_>, - outgoing_tls: &mut [u8], - ) -> Result { - if payload.is_empty() { - return Ok(0); - } - - let fragments = self - .message_fragmenter - .fragment_payload( - ContentType::ApplicationData, - ProtocolVersion::TLSv1_2, - payload.clone(), - ); - - for f in 0..fragments.len() { - match self - .record_layer - .pre_encrypt_action(f as u64) - { - PreEncryptAction::Nothing => {} - PreEncryptAction::RefreshOrClose => match self.negotiated_version { - Some(ProtocolVersion::TLSv1_3) => { - // driven by caller, as we don't have the `State` here - self.refresh_traffic_keys_pending = true; - } - _ => { - error!("traffic keys exhausted, closing connection to prevent security failure"); - self.send_close_notify(); - return Err(EncryptError::EncryptExhausted); - } - }, - PreEncryptAction::Refuse => { - return Err(EncryptError::EncryptExhausted); - } - } - } - - self.perhaps_write_key_update(); - - self.check_required_size(outgoing_tls, fragments)?; - - let fragments = self - .message_fragmenter - .fragment_payload( - ContentType::ApplicationData, - ProtocolVersion::TLSv1_2, - payload, - ); - - Ok(self.write_fragments(outgoing_tls, fragments)) - } - - // Changing the keys must not span any fragmented handshake - // messages. Otherwise the defragmented messages will have - // been protected with two different record layer protections, - // which is illegal. Not mentioned in RFC. - pub(crate) fn check_aligned_handshake(&mut self) -> Result<(), Error> { - if !self.aligned_handshake { - Err(self.send_fatal_alert( - AlertDescription::UnexpectedMessage, - PeerMisbehaved::KeyEpochWithPendingFragment, - )) - } else { - Ok(()) - } - } - - /// Fragment `m`, encrypt the fragments, and then queue - /// the encrypted fragments for sending. - pub(crate) fn send_msg_encrypt(&mut self, m: PlainMessage) { - let iter = self - .message_fragmenter - .fragment_message(&m); - for m in iter { - self.send_single_fragment(m); - } - } - - /// Like send_msg_encrypt, but operate on an appdata directly. - fn send_appdata_encrypt(&mut self, payload: OutboundChunks<'_>, limit: Limit) -> usize { - // Here, the limit on sendable_tls applies to encrypted data, - // but we're respecting it for plaintext data -- so we'll - // be out by whatever the cipher+record overhead is. That's a - // constant and predictable amount, so it's not a terrible issue. - let len = match limit { - #[cfg(feature = "std")] - Limit::Yes => self - .sendable_tls - .apply_limit(payload.len()), - Limit::No => payload.len(), - }; - - let iter = self - .message_fragmenter - .fragment_payload( - ContentType::ApplicationData, - ProtocolVersion::TLSv1_2, - payload.split_at(len).0, - ); - for m in iter { - self.send_single_fragment(m); - } - - len - } + pub(super) fn into_kernel_parts(self) -> Option<(ProtocolVersion, SupportedCipherSuite)> { + let Self { + negotiated_version, + suite, + .. + } = self; - fn send_single_fragment(&mut self, m: OutboundPlainMessage<'_>) { - if m.typ == ContentType::Alert { - // Alerts are always sendable -- never quashed by a PreEncryptAction. - let em = self.record_layer.encrypt_outgoing(m); - self.queue_tls_message(em); - return; - } - - match self - .record_layer - .next_pre_encrypt_action() - { - PreEncryptAction::Nothing => {} - - // Close connection once we start to run out of - // sequence space. - PreEncryptAction::RefreshOrClose => { - match self.negotiated_version { - Some(ProtocolVersion::TLSv1_3) => { - // driven by caller, as we don't have the `State` here - self.refresh_traffic_keys_pending = true; - } - _ => { - error!("traffic keys exhausted, closing connection to prevent security failure"); - self.send_close_notify(); - return; - } - } - } - - // Refuse to wrap counter at all costs. This - // is basically untestable unfortunately. - PreEncryptAction::Refuse => { - return; - } - }; - - let em = self.record_layer.encrypt_outgoing(m); - self.queue_tls_message(em); - } - - fn send_plain_non_buffering(&mut self, payload: OutboundChunks<'_>, limit: Limit) -> usize { - debug_assert!(self.may_send_application_data); - debug_assert!(self.record_layer.is_encrypting()); - - if payload.is_empty() { - // Don't send empty fragments. - return 0; - } - - self.send_appdata_encrypt(payload, limit) - } - - /// Mark the connection as ready to send application data. - /// - /// Also flush `sendable_plaintext` if it is `Some`. - pub(crate) fn start_outgoing_traffic( - &mut self, - sendable_plaintext: &mut Option<&mut ChunkVecBuffer>, - ) { - self.may_send_application_data = true; - if let Some(sendable_plaintext) = sendable_plaintext { - self.flush_plaintext(sendable_plaintext); - } - } - - /// Mark the connection as ready to send and receive application data. - /// - /// Also flush `sendable_plaintext` if it is `Some`. - pub(crate) fn start_traffic(&mut self, sendable_plaintext: &mut Option<&mut ChunkVecBuffer>) { - self.may_receive_application_data = true; - self.start_outgoing_traffic(sendable_plaintext); - } - - /// Send any buffered plaintext. Plaintext is buffered if - /// written during handshake. - fn flush_plaintext(&mut self, sendable_plaintext: &mut ChunkVecBuffer) { - if !self.may_send_application_data { - return; - } - - while let Some(buf) = sendable_plaintext.pop() { - self.send_plain_non_buffering(buf.as_slice().into(), Limit::No); - } - } - - // Put m into sendable_tls for writing. - fn queue_tls_message(&mut self, m: OutboundOpaqueMessage) { - self.perhaps_write_key_update(); - self.sendable_tls.append(m.encode()); - } - - pub(crate) fn perhaps_write_key_update(&mut self) { - if let Some(message) = self.queued_key_update_message.take() { - self.sendable_tls.append(message); + match (negotiated_version, suite) { + (Some(version), Some(suite)) => Some((version, suite)), + _ => None, } } +} - /// Send a raw TLS message, fragmenting it if needed. - pub(crate) fn send_msg(&mut self, m: Message<'_>, must_encrypt: bool) { - { - if let Protocol::Quic = self.protocol { - if let MessagePayload::Alert(alert) = m.payload { - self.quic.alert = Some(alert.description); - } else { - debug_assert!( - matches!( - m.payload, - MessagePayload::Handshake { .. } | MessagePayload::HandshakeFlight(_) - ), - "QUIC uses TLS for the cryptographic handshake only" - ); - let mut bytes = Vec::new(); - m.payload.encode(&mut bytes); - self.quic - .hs_queue - .push_back((must_encrypt, bytes)); - } - return; +impl ConnectionOutput for ConnectionOutputs { + fn handle(&mut self, ev: OutputEvent<'_>) { + match ev { + OutputEvent::ApplicationProtocol(protocol) => { + self.alpn_protocol = Some(ApplicationProtocol::from(protocol.as_ref()).to_owned()) } - } - if !must_encrypt { - let msg = &m.into(); - let iter = self - .message_fragmenter - .fragment_message(msg); - for m in iter { - self.queue_tls_message(m.to_unencrypted_opaque()); + OutputEvent::CipherSuite(suite) => self.suite = Some(suite), + OutputEvent::EarlyExporter(exporter) => self.early_exporter = Some(exporter), + OutputEvent::Exporter(exporter) => self.exporter = Some(exporter), + OutputEvent::HandshakeKind(hk) => { + assert!(self.handshake_kind.is_none()); + self.handshake_kind = Some(hk); } - } else { - self.send_msg_encrypt(m.into()); - } - } - - pub(crate) fn take_received_plaintext(&mut self, bytes: Payload<'_>) { - self.received_plaintext - .append(bytes.into_vec()); - } - - #[cfg(feature = "tls12")] - pub(crate) fn start_encryption_tls12(&mut self, secrets: &ConnectionSecrets, side: Side) { - let (dec, enc) = secrets.make_cipher_pair(side); - self.record_layer - .prepare_message_encrypter( - enc, - secrets - .suite() - .common - .confidentiality_limit, - ); - self.record_layer - .prepare_message_decrypter(dec); - } - - pub(crate) fn missing_extension(&mut self, why: PeerMisbehaved) -> Error { - self.send_fatal_alert(AlertDescription::MissingExtension, why) - } - - fn send_warning_alert(&mut self, desc: AlertDescription) { - warn!("Sending warning alert {:?}", desc); - self.send_warning_alert_no_log(desc); - } - - pub(crate) fn process_alert(&mut self, alert: &AlertMessagePayload) -> Result<(), Error> { - // Reject unknown AlertLevels. - if let AlertLevel::Unknown(_) = alert.level { - return Err(self.send_fatal_alert( - AlertDescription::IllegalParameter, - Error::AlertReceived(alert.description), - )); - } - - // If we get a CloseNotify, make a note to declare EOF to our - // caller. But do not treat unauthenticated alerts like this. - if self.may_receive_application_data && alert.description == AlertDescription::CloseNotify { - self.has_received_close_notify = true; - return Ok(()); - } - - // Warnings are nonfatal for TLS1.2, but outlawed in TLS1.3 - // (except, for no good reason, user_cancelled). - let err = Error::AlertReceived(alert.description); - if alert.level == AlertLevel::Warning { - self.temper_counters - .received_warning_alert()?; - if self.is_tls13() && alert.description != AlertDescription::UserCanceled { - return Err(self.send_fatal_alert(AlertDescription::DecodeError, err)); + OutputEvent::KeyExchangeGroup(kxg) => { + assert!(self.negotiated_kx_group.is_none()); + self.negotiated_kx_group = Some(kxg); } - - // Some implementations send pointless `user_canceled` alerts, don't log them - // in release mode (https://bugs.openjdk.org/browse/JDK-8323517). - if alert.description != AlertDescription::UserCanceled || cfg!(debug_assertions) { - warn!("TLS alert warning received: {alert:?}"); + OutputEvent::PeerIdentity(identity) => self.peer_identity = Some(identity), + OutputEvent::ProtocolVersion(ver) => { + self.negotiated_version = Some(ver); } - - return Ok(()); } - - Err(err) - } - - pub(crate) fn send_cert_verify_error_alert(&mut self, err: Error) -> Error { - self.send_fatal_alert( - match &err { - Error::InvalidCertificate(e) => e.clone().into(), - Error::PeerMisbehaved(_) => AlertDescription::IllegalParameter, - _ => AlertDescription::HandshakeFailure, - }, - err, - ) - } - - pub(crate) fn send_fatal_alert( - &mut self, - desc: AlertDescription, - err: impl Into, - ) -> Error { - debug_assert!(!self.sent_fatal_alert); - let m = Message::build_alert(AlertLevel::Fatal, desc); - self.send_msg(m, self.record_layer.is_encrypting()); - self.sent_fatal_alert = true; - err.into() - } - - /// Queues a `close_notify` warning alert to be sent in the next - /// [`Connection::write_tls`] call. This informs the peer that the - /// connection is being closed. - /// - /// Does nothing if any `close_notify` or fatal alert was already sent. - /// - /// [`Connection::write_tls`]: crate::Connection::write_tls - pub fn send_close_notify(&mut self) { - if self.sent_fatal_alert { - return; - } - debug!("Sending warning alert {:?}", AlertDescription::CloseNotify); - self.sent_fatal_alert = true; - self.send_warning_alert_no_log(AlertDescription::CloseNotify); - } - - pub(crate) fn eager_send_close_notify( - &mut self, - outgoing_tls: &mut [u8], - ) -> Result { - self.send_close_notify(); - self.check_required_size(outgoing_tls, [].into_iter())?; - Ok(self.write_fragments(outgoing_tls, [].into_iter())) - } - - fn send_warning_alert_no_log(&mut self, desc: AlertDescription) { - let m = Message::build_alert(AlertLevel::Warning, desc); - self.send_msg(m, self.record_layer.is_encrypting()); - } - - fn check_required_size<'a>( - &self, - outgoing_tls: &mut [u8], - fragments: impl Iterator>, - ) -> Result<(), EncryptError> { - let mut required_size = self.sendable_tls.len(); - - for m in fragments { - required_size += m.encoded_len(&self.record_layer); - } - - if required_size > outgoing_tls.len() { - return Err(EncryptError::InsufficientSize(InsufficientSizeError { - required_size, - })); - } - - Ok(()) - } - - fn write_fragments<'a>( - &mut self, - outgoing_tls: &mut [u8], - fragments: impl Iterator>, - ) -> usize { - let mut written = 0; - - // Any pre-existing encrypted messages in `sendable_tls` must - // be output before encrypting any of the `fragments`. - while let Some(message) = self.sendable_tls.pop() { - let len = message.len(); - outgoing_tls[written..written + len].copy_from_slice(&message); - written += len; - } - - for m in fragments { - let em = self - .record_layer - .encrypt_outgoing(m) - .encode(); - - let len = em.len(); - outgoing_tls[written..written + len].copy_from_slice(&em); - written += len; - } - - written - } - - pub(crate) fn set_max_fragment_size(&mut self, new: Option) -> Result<(), Error> { - self.message_fragmenter - .set_max_fragment_size(new) - } - - pub(crate) fn get_alpn_protocol(&self) -> Option<&[u8]> { - self.alpn_protocol - .as_ref() - .map(AsRef::as_ref) - } - - /// Returns true if the caller should call [`Connection::read_tls`] as soon - /// as possible. - /// - /// If there is pending plaintext data to read with [`Connection::reader`], - /// this returns false. If your application respects this mechanism, - /// only one full TLS message will be buffered by rustls. - /// - /// [`Connection::reader`]: crate::Connection::reader - /// [`Connection::read_tls`]: crate::Connection::read_tls - pub fn wants_read(&self) -> bool { - // We want to read more data all the time, except when we have unprocessed plaintext. - // This provides back-pressure to the TCP buffers. We also don't want to read more after - // the peer has sent us a close notification. - // - // In the handshake case we don't have readable plaintext before the handshake has - // completed, but also don't want to read if we still have sendable tls. - self.received_plaintext.is_empty() - && !self.has_received_close_notify - && (self.may_send_application_data || self.sendable_tls.is_empty()) - } - - pub(crate) fn current_io_state(&self) -> IoState { - IoState { - tls_bytes_to_write: self.sendable_tls.len(), - plaintext_bytes_to_read: self.received_plaintext.len(), - peer_has_closed: self.has_received_close_notify, - } - } - - pub(crate) fn is_quic(&self) -> bool { - self.protocol == Protocol::Quic - } - - pub(crate) fn should_update_key( - &mut self, - key_update_request: &KeyUpdateRequest, - ) -> Result { - self.temper_counters - .received_key_update_request()?; - - match key_update_request { - KeyUpdateRequest::UpdateNotRequested => Ok(false), - KeyUpdateRequest::UpdateRequested => Ok(self.queued_key_update_message.is_none()), - _ => Err(self.send_fatal_alert( - AlertDescription::IllegalParameter, - InvalidMessage::InvalidKeyUpdate, - )), - } - } - - pub(crate) fn enqueue_key_update_notification(&mut self) { - let message = PlainMessage::from(Message::build_key_update_notify()); - self.queued_key_update_message = Some( - self.record_layer - .encrypt_outgoing(message.borrow_outbound()) - .encode(), - ); - } - - pub(crate) fn received_tls13_change_cipher_spec(&mut self) -> Result<(), Error> { - self.temper_counters - .received_tls13_change_cipher_spec() } } -#[cfg(feature = "std")] -impl CommonState { - /// Send plaintext application data, fragmenting and - /// encrypting it as it goes out. - /// - /// If internal buffers are too small, this function will not accept - /// all the data. - pub(crate) fn buffer_plaintext( - &mut self, - payload: OutboundChunks<'_>, - sendable_plaintext: &mut ChunkVecBuffer, - ) -> usize { - self.perhaps_write_key_update(); - self.send_plain(payload, Limit::Yes, sendable_plaintext) - } - - pub(crate) fn send_early_plaintext(&mut self, data: &[u8]) -> usize { - debug_assert!(self.early_traffic); - debug_assert!(self.record_layer.is_encrypting()); - - if data.is_empty() { - // Don't send empty fragments. - return 0; - } - - self.send_appdata_encrypt(data.into(), Limit::Yes) - } - - /// Encrypt and send some plaintext `data`. `limit` controls - /// whether the per-connection buffer limits apply. - /// - /// Returns the number of bytes written from `data`: this might - /// be less than `data.len()` if buffer limits were exceeded. - fn send_plain( - &mut self, - payload: OutboundChunks<'_>, - limit: Limit, - sendable_plaintext: &mut ChunkVecBuffer, - ) -> usize { - if !self.may_send_application_data { - // If we haven't completed handshaking, buffer - // plaintext to send once we do. - let len = match limit { - Limit::Yes => sendable_plaintext.append_limited_copy(payload), - Limit::No => sendable_plaintext.append(payload.to_vec()), - }; - return len; - } - - self.send_plain_non_buffering(payload, limit) - } +/// Send an alert via `output` if `error` specifies one. +pub(crate) fn maybe_send_fatal_alert(send: &mut dyn SendOutput, error: &Error) { + let Ok(alert) = AlertDescription::try_from(error) else { + return; + }; + send.send_alert(AlertLevel::Fatal, alert); } /// Describes which sort of handshake happened. #[derive(Debug, PartialEq, Clone, Copy)] +#[non_exhaustive] pub enum HandshakeKind { /// A full handshake. /// @@ -780,7 +218,7 @@ pub enum HandshakeKind { /// A full TLS1.3 handshake, with an extra round-trip for a `HelloRetryRequest`. /// /// The server can respond with a `HelloRetryRequest` if the initial `ClientHello` - /// is unacceptable for several reasons, the most likely if no supported key + /// is unacceptable for several reasons, the most likely being if no supported key /// shares were offered by the client. FullWithHelloRetryRequest, @@ -790,234 +228,133 @@ pub enum HandshakeKind { /// full ones, but can only happen when the peers have previously done a full /// handshake together, and then remember data about it. Resumed, -} - -/// Values of this structure are returned from [`Connection::process_new_packets`] -/// and tell the caller the current I/O state of the TLS connection. -/// -/// [`Connection::process_new_packets`]: crate::Connection::process_new_packets -#[derive(Debug, Eq, PartialEq)] -pub struct IoState { - tls_bytes_to_write: usize, - plaintext_bytes_to_read: usize, - peer_has_closed: bool, -} - -impl IoState { - /// How many bytes could be written by [`Connection::write_tls`] if called - /// right now. A non-zero value implies [`CommonState::wants_write`]. - /// - /// [`Connection::write_tls`]: crate::Connection::write_tls - pub fn tls_bytes_to_write(&self) -> usize { - self.tls_bytes_to_write - } - /// How many plaintext bytes could be obtained via [`std::io::Read`] - /// without further I/O. - pub fn plaintext_bytes_to_read(&self) -> usize { - self.plaintext_bytes_to_read - } - - /// True if the peer has sent us a close_notify alert. This is - /// the TLS mechanism to securely half-close a TLS connection, - /// and signifies that the peer will not send any further data - /// on this connection. + /// A resumed handshake, with an extra round-trip for a `HelloRetryRequest`. /// - /// This is also signalled via returning `Ok(0)` from - /// [`std::io::Read`], after all the received bytes have been - /// retrieved. - pub fn peer_has_closed(&self) -> bool { - self.peer_has_closed - } + /// The server can respond with a `HelloRetryRequest` if the initial `ClientHello` + /// is unacceptable for several reasons, but this does not prevent the client + /// from resuming. + ResumedWithHelloRetryRequest, } -pub(crate) trait State: Send + Sync { - fn handle<'m>( - self: Box, - cx: &mut Context<'_, Data>, - message: Message<'m>, - ) -> Result + 'm>, Error> - where - Self: 'm; +/// The route for handshake state machine to surface determinations about the connection. +pub(crate) trait Output<'m> { + fn emit(&mut self, ev: Event<'_>); - fn export_keying_material( - &self, - _output: &mut [u8], - _label: &[u8], - _context: Option<&[u8]>, - ) -> Result<(), Error> { - Err(Error::HandshakeNotComplete) - } + fn output(&mut self, ev: OutputEvent<'_>); - fn extract_secrets(&self) -> Result { - Err(Error::HandshakeNotComplete) - } + fn send_msg(&mut self, m: Message<'_>, must_encrypt: bool); - fn send_key_update_request(&mut self, _common: &mut CommonState) -> Result<(), Error> { - Err(Error::HandshakeNotComplete) + fn quic(&mut self) -> Option<&mut dyn QuicOutput> { + None } - fn handle_decrypt_error(&self) {} - - fn into_owned(self: Box) -> Box + 'static>; -} - -pub(crate) struct Context<'a, Data> { - pub(crate) common: &'a mut CommonState, - pub(crate) data: &'a mut Data, - /// Buffered plaintext. This is `Some` if any plaintext was written during handshake and `None` - /// otherwise. - pub(crate) sendable_plaintext: Option<&'a mut ChunkVecBuffer>, -} + fn received_plaintext(&mut self, _payload: Payload<'m>) {} -/// Side of the connection. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Side { - /// A client initiates the connection. - Client, - /// A server waits for a client to connect. - Server, -} + fn start_traffic(&mut self); -impl Side { - pub(crate) fn peer(&self) -> Self { - match self { - Self::Client => Self::Server, - Self::Server => Self::Client, - } - } -} + fn receive(&mut self) -> &mut ReceivePath; -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub(crate) enum Protocol { - Tcp, - Quic, + fn send(&mut self) -> &mut dyn SendOutput; } -enum Limit { - #[cfg(feature = "std")] - Yes, - No, +pub(crate) trait ConnectionOutput { + fn handle(&mut self, ev: OutputEvent<'_>); } -#[derive(Debug)] -pub(super) struct RawKeyNegotiationParams { - pub(super) peer_supports_raw_key: bool, - pub(super) local_expects_raw_key: bool, - pub(super) extension_type: ExtensionType, +/// The set of events output by the low-level handshake state machine. +pub(crate) enum Event<'a> { + EarlyApplicationData(Payload<'a>), + EarlyData(EarlyDataEvent), + EchStatus(EchStatus), + ReceivedServerName(Option>), + ResumptionData(Vec), } -impl RawKeyNegotiationParams { - pub(super) fn validate_raw_key_negotiation(&self) -> RawKeyNegotationResult { - match (self.local_expects_raw_key, self.peer_supports_raw_key) { - (true, true) => RawKeyNegotationResult::Negotiated(self.extension_type), - (false, false) => RawKeyNegotationResult::NotNegotiated, - (true, false) => RawKeyNegotationResult::Err(Error::PeerIncompatible( - PeerIncompatible::IncorrectCertificateTypeExtension, - )), - (false, true) => RawKeyNegotationResult::Err(Error::PeerIncompatible( - PeerIncompatible::UnsolicitedCertificateTypeExtension, - )), - } - } +pub(crate) enum OutputEvent<'a> { + ApplicationProtocol(ApplicationProtocol<'a>), + CipherSuite(SupportedCipherSuite), + EarlyExporter(Box), + Exporter(Box), + HandshakeKind(HandshakeKind), + KeyExchangeGroup(&'static dyn SupportedKxGroup), + PeerIdentity(Identity<'static>), + ProtocolVersion(ProtocolVersion), } -#[derive(Debug)] -pub(crate) enum RawKeyNegotationResult { - Negotiated(ExtensionType), - NotNegotiated, - Err(Error), +pub(crate) enum EarlyDataEvent { + /// server: we accepted an early_data offer + Accepted, + /// client: declares the maximum amount of early data that can be sent + Enable(usize), + /// client: early data can now be sent using the record layer as normal + Start, + /// client: early data phase has closed after sending EndOfEarlyData + Finished, + /// client: the server rejected our request for early data + Rejected, } -/// Tracking technically-allowed protocol actions -/// that we limit to avoid denial-of-service vectors. -struct TemperCounters { - allowed_warning_alerts: u8, - allowed_renegotiation_requests: u8, - allowed_key_update_requests: u8, - allowed_middlebox_ccs: u8, +/// Lifetime-erased equivalent to [`Payload`] +/// +/// Stores an index into [`Payload`] buffer enabling in-place decryption +/// without holding a lifetime to the receive buffer. +pub(crate) enum UnborrowedPayload { + Unborrowed(Range), + Owned(Vec), } -impl TemperCounters { - fn received_warning_alert(&mut self) -> Result<(), Error> { - match self.allowed_warning_alerts { - 0 => Err(PeerMisbehaved::TooManyWarningAlertsReceived.into()), - _ => { - self.allowed_warning_alerts -= 1; - Ok(()) - } - } - } - - fn received_renegotiation_request(&mut self) -> Result<(), Error> { - match self.allowed_renegotiation_requests { - 0 => Err(PeerMisbehaved::TooManyRenegotiationRequests.into()), - _ => { - self.allowed_renegotiation_requests -= 1; - Ok(()) - } - } - } - - fn received_key_update_request(&mut self) -> Result<(), Error> { - match self.allowed_key_update_requests { - 0 => Err(PeerMisbehaved::TooManyKeyUpdateRequests.into()), - _ => { - self.allowed_key_update_requests -= 1; - Ok(()) - } +impl UnborrowedPayload { + /// Convert [`Payload`] into [`UnborrowedPayload`] which stores a range + /// into the [`Payload`] slice without borrowing such that it can be later + /// reborrowed. + /// + /// # Panics + /// + /// Passed [`Locator`] must have been created from the same slice which + /// contains the payload. + pub(crate) fn unborrow(locator: &Locator, payload: Payload<'_>) -> Self { + match payload { + Payload::Borrowed(payload) => Self::Unborrowed(locator.locate(payload)), + Payload::Owned(payload) => Self::Owned(payload), } } - fn received_tls13_change_cipher_spec(&mut self) -> Result<(), Error> { - match self.allowed_middlebox_ccs { - 0 => Err(PeerMisbehaved::IllegalMiddleboxChangeCipherSpec.into()), - _ => { - self.allowed_middlebox_ccs -= 1; - Ok(()) - } + /// Convert [`UnborrowedPayload`] back into [`Payload`] + /// + /// # Panics + /// + /// Passed [`Delocator`] must have been created from the same slice that + /// [`UnborrowedPayload`] was originally unborrowed from. + pub(crate) fn reborrow<'b>(self, delocator: &Delocator<'b>) -> Payload<'b> { + match self { + Self::Unborrowed(range) => Payload::Borrowed(delocator.slice_from_range(&range)), + Self::Owned(payload) => Payload::Owned(payload), } } } -impl Default for TemperCounters { - fn default() -> Self { - Self { - // cf. BoringSSL `kMaxWarningAlerts` - // - allowed_warning_alerts: 4, - - // we rebuff renegotiation requests with a `NoRenegotiation` warning alerts. - // a second request after this is fatal. - allowed_renegotiation_requests: 1, - - // cf. BoringSSL `kMaxKeyUpdates` - // - allowed_key_update_requests: 32, - - // At most two CCS are allowed: one after each ClientHello (recall a second - // ClientHello happens after a HelloRetryRequest). - // - // note BoringSSL allows up to 32. - allowed_middlebox_ccs: 2, - } - } +/// Side of the connection. +#[expect(clippy::exhaustive_enums)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Side { + /// A client initiates the connection. + Client, + /// A server waits for a client to connect. + Server, } -#[derive(Debug, Default)] -pub(crate) enum KxState { - #[default] - None, - Start(&'static dyn SupportedKxGroup), - Complete(&'static dyn SupportedKxGroup), +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub(crate) enum Protocol { + /// TCP-TLS, standardized in RFC5246 and RFC8446 + Tcp, + /// QUIC, standardized in RFC9001 + Quic(quic::Version), } -impl KxState { - pub(crate) fn complete(&mut self) { - debug_assert!(matches!(self, Self::Start(_))); - if let Self::Start(group) = self { - *self = Self::Complete(*group); - } +impl Protocol { + pub(crate) fn is_quic(&self) -> bool { + matches!(self, Self::Quic(_)) } } @@ -1041,23 +378,18 @@ impl<'a, const TLS13: bool> HandshakeFlight<'a, TLS13> { .add(&self.body[start_len..]); } - pub(crate) fn finish(self, common: &mut CommonState) { - common.send_msg( - Message { - version: match TLS13 { - true => ProtocolVersion::TLSv1_3, - false => ProtocolVersion::TLSv1_2, - }, - payload: MessagePayload::HandshakeFlight(Payload::new(self.body)), + pub(crate) fn finish(self, output: &mut dyn Output<'_>) { + let m = Message { + version: match TLS13 { + true => ProtocolVersion::TLSv1_3, + false => ProtocolVersion::TLSv1_2, }, - TLS13, - ); + payload: MessagePayload::HandshakeFlight(Payload::new(self.body)), + }; + + output.send_msg(m, TLS13); } } -#[cfg(feature = "tls12")] pub(crate) type HandshakeFlightTls12<'a> = HandshakeFlight<'a, false>; pub(crate) type HandshakeFlightTls13<'a> = HandshakeFlight<'a, true>; - -const DEFAULT_RECEIVED_PLAINTEXT_LIMIT: usize = 16 * 1024; -pub(crate) const DEFAULT_BUFFER_LIMIT: usize = 64 * 1024; diff --git a/rustls/src/compress.rs b/rustls/src/compress.rs index e6a2f028d96..3b61408bf6b 100644 --- a/rustls/src/compress.rs +++ b/rustls/src/compress.rs @@ -32,18 +32,15 @@ //! [cc_cd]: crate::ClientConfig::cert_decompressors //! [sc_cd]: crate::ServerConfig::cert_decompressors -#[cfg(feature = "std")] use alloc::collections::VecDeque; -use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt::Debug; -#[cfg(feature = "std")] use std::sync::Mutex; +use crate::crypto::cipher::Payload; use crate::enums::CertificateCompressionAlgorithm; -use crate::msgs::base::{Payload, PayloadU24}; -use crate::msgs::codec::Codec; -use crate::msgs::handshake::{CertificatePayloadTls13, CompressedCertificatePayload}; +use crate::msgs::{CertificatePayloadTls13, Codec, CompressedCertificatePayload, SizedPayload}; +use crate::sync::Arc; /// Returns the supported `CertDecompressor` implementations enabled /// by crate features. @@ -102,6 +99,7 @@ pub trait CertCompressor: Debug + Send + Sync { } /// A hint for how many resources to dedicate to a compression. +#[non_exhaustive] #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum CompressionLevel { /// This compression is happening interactively during a handshake. @@ -116,17 +114,20 @@ pub enum CompressionLevel { } /// A content-less error for when `CertDecompressor::decompress` fails. +#[expect(clippy::exhaustive_structs)] #[derive(Debug)] pub struct DecompressionFailed; /// A content-less error for when `CertCompressor::compress` fails. +#[expect(clippy::exhaustive_structs)] #[derive(Debug)] pub struct CompressionFailed; #[cfg(feature = "zlib")] mod feat_zlib_rs { - use zlib_rs::c_api::Z_BEST_COMPRESSION; - use zlib_rs::{deflate, inflate, ReturnCode}; + use zlib_rs::{ + DeflateConfig, InflateConfig, ReturnCode, compress_bound, compress_slice, decompress_slice, + }; use super::*; @@ -139,7 +140,7 @@ mod feat_zlib_rs { impl CertDecompressor for ZlibRsDecompressor { fn decompress(&self, input: &[u8], output: &mut [u8]) -> Result<(), DecompressionFailed> { let output_len = output.len(); - match inflate::uncompress_slice(output, input, inflate::InflateConfig::default()) { + match decompress_slice(output, input, InflateConfig::default()) { (output_filled, ReturnCode::Ok) if output_filled.len() == output_len => Ok(()), (_, _) => Err(DecompressionFailed), } @@ -162,12 +163,12 @@ mod feat_zlib_rs { input: Vec, level: CompressionLevel, ) -> Result, CompressionFailed> { - let mut output = alloc::vec![0u8; deflate::compress_bound(input.len())]; + let mut output = alloc::vec![0u8; compress_bound(input.len())]; let config = match level { - CompressionLevel::Interactive => deflate::DeflateConfig::default(), - CompressionLevel::Amortized => deflate::DeflateConfig::new(Z_BEST_COMPRESSION), + CompressionLevel::Interactive => DeflateConfig::default(), + CompressionLevel::Amortized => DeflateConfig::best_compression(), }; - let (output_filled, rc) = deflate::compress_slice(&mut output, &input, config); + let (output_filled, rc) = compress_slice(&mut output, &input, config); if rc != ReturnCode::Ok { return Err(CompressionFailed); } @@ -271,6 +272,7 @@ pub use feat_brotli::{BROTLI_COMPRESSOR, BROTLI_DECOMPRESSOR}; /// The prospect of being able to reuse a given compression for many connections /// means we can afford to spend more time on that compression (by passing /// `CompressionLevel::Amortized` to the compressor). +#[expect(clippy::exhaustive_enums)] #[derive(Debug)] pub enum CompressionCache { /// No caching happens, and compression happens each time using @@ -278,14 +280,12 @@ pub enum CompressionCache { Disabled, /// Compressions are stored in an LRU cache. - #[cfg(feature = "std")] Enabled(CompressionCacheInner), } /// Innards of an enabled CompressionCache. /// /// You cannot make one of these directly. Use [`CompressionCache::new`]. -#[cfg(feature = "std")] #[derive(Debug)] pub struct CompressionCacheInner { /// Maximum size of underlying storage. @@ -300,7 +300,6 @@ pub struct CompressionCacheInner { impl CompressionCache { /// Make a `CompressionCache` that stores up to `size` compressed /// certificate messages. - #[cfg(feature = "std")] pub fn new(size: usize) -> Self { if size == 0 { return Self::Disabled; @@ -324,13 +323,10 @@ impl CompressionCache { ) -> Result, CompressionFailed> { match self { Self::Disabled => Self::uncached_compression(compressor, original), - - #[cfg(feature = "std")] Self::Enabled(_) => self.compression_for_impl(compressor, original), } } - #[cfg(feature = "std")] fn compression_for_impl( &self, compressor: &dyn CertCompressor, @@ -343,7 +339,7 @@ impl CompressionCache { // context is a per-connection quantity, and included in the compressed data. // it is not suitable for inclusion in the cache. - if !original.context.0.is_empty() { + if !original.context.is_empty() { return Self::uncached_compression(compressor, original); } @@ -358,7 +354,7 @@ impl CompressionCache { if item.algorithm == algorithm && item.original == encoding { // this item is now MRU let item = cache.remove(i).unwrap(); - cache.push_back(Arc::clone(&item)); + cache.push_back(item.clone()); return Ok(item); } } @@ -373,7 +369,7 @@ impl CompressionCache { compressed: CompressedCertificatePayload { alg: algorithm, uncompressed_len, - compressed: PayloadU24(Payload::new(compressed)), + compressed: SizedPayload::from(Payload::new(compressed)), }, }); @@ -384,7 +380,7 @@ impl CompressionCache { if cache.len() == max_size { cache.pop_front(); } - cache.push_back(Arc::clone(&new_entry)); + cache.push_back(new_entry.clone()); Ok(new_entry) } @@ -406,7 +402,7 @@ impl CompressionCache { compressed: CompressedCertificatePayload { alg: algorithm, uncompressed_len, - compressed: PayloadU24(Payload::new(compressed)), + compressed: SizedPayload::from(Payload::new(compressed)), }, })) } @@ -414,20 +410,11 @@ impl CompressionCache { impl Default for CompressionCache { fn default() -> Self { - #[cfg(feature = "std")] - { - // 4 entries allows 2 certificate chains times 2 compression algorithms - Self::new(4) - } - - #[cfg(not(feature = "std"))] - { - Self::Disabled - } + // 4 entries allows 2 certificate chains times 2 compression algorithms + Self::new(4) } } -#[cfg_attr(not(feature = "std"), allow(dead_code))] #[derive(Debug)] pub(crate) struct CompressionCacheEntry { // cache key is algorithm + original: @@ -534,12 +521,12 @@ mod tests { let cache = CompressionCache::default(); - let cert = CertificateDer::from(vec![1]); + let certs = [CertificateDer::from(vec![1])].into_iter(); - let cert1 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"1")); - let cert2 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"2")); - let cert3 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"3")); - let cert4 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"4")); + let cert1 = CertificatePayloadTls13::new(certs.clone(), Some(b"1")); + let cert2 = CertificatePayloadTls13::new(certs.clone(), Some(b"2")); + let cert3 = CertificatePayloadTls13::new(certs.clone(), Some(b"3")); + let cert4 = CertificatePayloadTls13::new(certs.clone(), Some(b"4")); // insert zlib (1), (2), (3), (4) diff --git a/rustls/src/conn.rs b/rustls/src/conn.rs deleted file mode 100644 index f69f6408e96..00000000000 --- a/rustls/src/conn.rs +++ /dev/null @@ -1,1194 +0,0 @@ -use alloc::boxed::Box; -use core::fmt::Debug; -use core::mem; -use core::ops::{Deref, DerefMut, Range}; -#[cfg(feature = "std")] -use std::io; - -use crate::common_state::{CommonState, Context, IoState, State, DEFAULT_BUFFER_LIMIT}; -use crate::enums::{AlertDescription, ContentType, ProtocolVersion}; -use crate::error::{Error, PeerMisbehaved}; -use crate::log::trace; -use crate::msgs::deframer::buffers::{BufferProgress, DeframerVecBuffer, Delocator, Locator}; -use crate::msgs::deframer::handshake::HandshakeDeframer; -use crate::msgs::deframer::DeframerIter; -use crate::msgs::handshake::Random; -use crate::msgs::message::{InboundPlainMessage, Message, MessagePayload}; -use crate::record_layer::Decrypted; -use crate::suites::{ExtractedSecrets, PartiallyExtractedSecrets}; -use crate::vecbuf::ChunkVecBuffer; - -pub(crate) mod unbuffered; - -#[cfg(feature = "std")] -mod connection { - use alloc::vec::Vec; - use core::fmt::Debug; - use core::ops::{Deref, DerefMut}; - use std::io; - - use crate::common_state::{CommonState, IoState}; - use crate::error::Error; - use crate::msgs::message::OutboundChunks; - use crate::suites::ExtractedSecrets; - use crate::vecbuf::ChunkVecBuffer; - use crate::ConnectionCommon; - - /// A client or server connection. - #[derive(Debug)] - pub enum Connection { - /// A client connection - Client(crate::client::ClientConnection), - /// A server connection - Server(crate::server::ServerConnection), - } - - impl Connection { - /// Read TLS content from `rd`. - /// - /// See [`ConnectionCommon::read_tls()`] for more information. - pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result { - match self { - Self::Client(conn) => conn.read_tls(rd), - Self::Server(conn) => conn.read_tls(rd), - } - } - - /// Writes TLS messages to `wr`. - /// - /// See [`ConnectionCommon::write_tls()`] for more information. - pub fn write_tls(&mut self, wr: &mut dyn io::Write) -> Result { - self.sendable_tls.write_to(wr) - } - - /// Returns an object that allows reading plaintext. - pub fn reader(&mut self) -> Reader<'_> { - match self { - Self::Client(conn) => conn.reader(), - Self::Server(conn) => conn.reader(), - } - } - - /// Returns an object that allows writing plaintext. - pub fn writer(&mut self) -> Writer<'_> { - match self { - Self::Client(conn) => Writer::new(&mut **conn), - Self::Server(conn) => Writer::new(&mut **conn), - } - } - - /// Processes any new packets read by a previous call to [`Connection::read_tls`]. - /// - /// See [`ConnectionCommon::process_new_packets()`] for more information. - pub fn process_new_packets(&mut self) -> Result { - match self { - Self::Client(conn) => conn.process_new_packets(), - Self::Server(conn) => conn.process_new_packets(), - } - } - - /// Derives key material from the agreed connection secrets. - /// - /// See [`ConnectionCommon::export_keying_material()`] for more information. - pub fn export_keying_material>( - &self, - output: T, - label: &[u8], - context: Option<&[u8]>, - ) -> Result { - match self { - Self::Client(conn) => conn.export_keying_material(output, label, context), - Self::Server(conn) => conn.export_keying_material(output, label, context), - } - } - - /// This function uses `io` to complete any outstanding IO for this connection. - /// - /// See [`ConnectionCommon::complete_io()`] for more information. - pub fn complete_io(&mut self, io: &mut T) -> Result<(usize, usize), io::Error> - where - Self: Sized, - T: io::Read + io::Write, - { - match self { - Self::Client(conn) => conn.complete_io(io), - Self::Server(conn) => conn.complete_io(io), - } - } - - /// Extract secrets, so they can be used when configuring kTLS, for example. - /// Should be used with care as it exposes secret key material. - pub fn dangerous_extract_secrets(self) -> Result { - match self { - Self::Client(client) => client.dangerous_extract_secrets(), - Self::Server(server) => server.dangerous_extract_secrets(), - } - } - - /// Sets a limit on the internal buffers - /// - /// See [`ConnectionCommon::set_buffer_limit()`] for more information. - pub fn set_buffer_limit(&mut self, limit: Option) { - match self { - Self::Client(client) => client.set_buffer_limit(limit), - Self::Server(server) => server.set_buffer_limit(limit), - } - } - - /// Sends a TLS1.3 `key_update` message to refresh a connection's keys - /// - /// See [`ConnectionCommon::refresh_traffic_keys()`] for more information. - pub fn refresh_traffic_keys(&mut self) -> Result<(), Error> { - match self { - Self::Client(client) => client.refresh_traffic_keys(), - Self::Server(server) => server.refresh_traffic_keys(), - } - } - } - - impl Deref for Connection { - type Target = CommonState; - - fn deref(&self) -> &Self::Target { - match self { - Self::Client(conn) => &conn.core.common_state, - Self::Server(conn) => &conn.core.common_state, - } - } - } - - impl DerefMut for Connection { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - Self::Client(conn) => &mut conn.core.common_state, - Self::Server(conn) => &mut conn.core.common_state, - } - } - } - - /// A structure that implements [`std::io::Read`] for reading plaintext. - pub struct Reader<'a> { - pub(super) received_plaintext: &'a mut ChunkVecBuffer, - pub(super) has_received_close_notify: bool, - pub(super) has_seen_eof: bool, - } - - impl Reader<'_> { - /// Check the connection's state if no bytes are available for reading. - fn check_no_bytes_state(&self) -> io::Result<()> { - match (self.has_received_close_notify, self.has_seen_eof) { - // cleanly closed; don't care about TCP EOF: express this as Ok(0) - (true, _) => Ok(()), - // unclean closure - (false, true) => Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - UNEXPECTED_EOF_MESSAGE, - )), - // connection still going, but needs more data: signal `WouldBlock` so that - // the caller knows this - (false, false) => Err(io::ErrorKind::WouldBlock.into()), - } - } - } - - impl io::Read for Reader<'_> { - /// Obtain plaintext data received from the peer over this TLS connection. - /// - /// If the peer closes the TLS session cleanly, this returns `Ok(0)` once all - /// the pending data has been read. No further data can be received on that - /// connection, so the underlying TCP connection should be half-closed too. - /// - /// If the peer closes the TLS session uncleanly (a TCP EOF without sending a - /// `close_notify` alert) this function returns a `std::io::Error` of type - /// `ErrorKind::UnexpectedEof` once any pending data has been read. - /// - /// Note that support for `close_notify` varies in peer TLS libraries: many do not - /// support it and uncleanly close the TCP connection (this might be - /// vulnerable to truncation attacks depending on the application protocol). - /// This means applications using rustls must both handle EOF - /// from this function, *and* unexpected EOF of the underlying TCP connection. - /// - /// If there are no bytes to read, this returns `Err(ErrorKind::WouldBlock.into())`. - /// - /// You may learn the number of bytes available at any time by inspecting - /// the return of [`Connection::process_new_packets`]. - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let len = self.received_plaintext.read(buf)?; - if len > 0 || buf.is_empty() { - return Ok(len); - } - - self.check_no_bytes_state() - .map(|()| len) - } - - /// Obtain plaintext data received from the peer over this TLS connection. - /// - /// If the peer closes the TLS session, this returns `Ok(())` without filling - /// any more of the buffer once all the pending data has been read. No further - /// data can be received on that connection, so the underlying TCP connection - /// should be half-closed too. - /// - /// If the peer closes the TLS session uncleanly (a TCP EOF without sending a - /// `close_notify` alert) this function returns a `std::io::Error` of type - /// `ErrorKind::UnexpectedEof` once any pending data has been read. - /// - /// Note that support for `close_notify` varies in peer TLS libraries: many do not - /// support it and uncleanly close the TCP connection (this might be - /// vulnerable to truncation attacks depending on the application protocol). - /// This means applications using rustls must both handle EOF - /// from this function, *and* unexpected EOF of the underlying TCP connection. - /// - /// If there are no bytes to read, this returns `Err(ErrorKind::WouldBlock.into())`. - /// - /// You may learn the number of bytes available at any time by inspecting - /// the return of [`Connection::process_new_packets`]. - #[cfg(read_buf)] - fn read_buf(&mut self, mut cursor: core::io::BorrowedCursor<'_>) -> io::Result<()> { - let before = cursor.written(); - self.received_plaintext - .read_buf(cursor.reborrow())?; - let len = cursor.written() - before; - if len > 0 || cursor.capacity() == 0 { - return Ok(()); - } - - self.check_no_bytes_state() - } - } - - const UNEXPECTED_EOF_MESSAGE: &str = - "peer closed connection without sending TLS close_notify: \ -https://docs.rs/rustls/latest/rustls/manual/_03_howto/index.html#unexpected-eof"; - - /// A structure that implements [`std::io::Write`] for writing plaintext. - pub struct Writer<'a> { - sink: &'a mut dyn PlaintextSink, - } - - impl<'a> Writer<'a> { - /// Create a new Writer. - /// - /// This is not an external interface. Get one of these objects - /// from [`Connection::writer`]. - pub(crate) fn new(sink: &'a mut dyn PlaintextSink) -> Self { - Writer { sink } - } - } - - impl io::Write for Writer<'_> { - /// Send the plaintext `buf` to the peer, encrypting - /// and authenticating it. Once this function succeeds - /// you should call [`Connection::write_tls`] which will output the - /// corresponding TLS records. - /// - /// This function buffers plaintext sent before the - /// TLS handshake completes, and sends it as soon - /// as it can. See [`ConnectionCommon::set_buffer_limit`] to control - /// the size of this buffer. - fn write(&mut self, buf: &[u8]) -> io::Result { - self.sink.write(buf) - } - - fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { - self.sink.write_vectored(bufs) - } - - fn flush(&mut self) -> io::Result<()> { - self.sink.flush() - } - } - - /// Internal trait implemented by the [`ServerConnection`]/[`ClientConnection`] - /// allowing them to be the subject of a [`Writer`]. - /// - /// [`ServerConnection`]: crate::ServerConnection - /// [`ClientConnection`]: crate::ClientConnection - pub(crate) trait PlaintextSink { - fn write(&mut self, buf: &[u8]) -> io::Result; - fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result; - fn flush(&mut self) -> io::Result<()>; - } - - impl PlaintextSink for ConnectionCommon { - fn write(&mut self, buf: &[u8]) -> io::Result { - let len = self - .core - .common_state - .buffer_plaintext(buf.into(), &mut self.sendable_plaintext); - self.core.maybe_refresh_traffic_keys(); - Ok(len) - } - - fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { - let payload_owner: Vec<&[u8]>; - let payload = match bufs.len() { - 0 => return Ok(0), - 1 => OutboundChunks::Single(bufs[0].deref()), - _ => { - payload_owner = bufs - .iter() - .map(|io_slice| io_slice.deref()) - .collect(); - - OutboundChunks::new(&payload_owner) - } - }; - let len = self - .core - .common_state - .buffer_plaintext(payload, &mut self.sendable_plaintext); - self.core.maybe_refresh_traffic_keys(); - Ok(len) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } - } -} - -#[cfg(feature = "std")] -pub use connection::{Connection, Reader, Writer}; - -#[derive(Debug)] -pub(crate) struct ConnectionRandoms { - pub(crate) client: [u8; 32], - pub(crate) server: [u8; 32], -} - -impl ConnectionRandoms { - pub(crate) fn new(client: Random, server: Random) -> Self { - Self { - client: client.0, - server: server.0, - } - } -} - -/// Interface shared by client and server connections. -pub struct ConnectionCommon { - pub(crate) core: ConnectionCore, - deframer_buffer: DeframerVecBuffer, - sendable_plaintext: ChunkVecBuffer, -} - -impl ConnectionCommon { - /// Processes any new packets read by a previous call to - /// [`Connection::read_tls`]. - /// - /// Errors from this function relate to TLS protocol errors, and - /// are fatal to the connection. Future calls after an error will do - /// no new work and will return the same error. After an error is - /// received from [`process_new_packets`], you should not call [`read_tls`] - /// any more (it will fill up buffers to no purpose). However, you - /// may call the other methods on the connection, including `write`, - /// `send_close_notify`, and `write_tls`. Most likely you will want to - /// call `write_tls` to send any alerts queued by the error and then - /// close the underlying connection. - /// - /// Success from this function comes with some sundry state data - /// about the connection. - /// - /// [`read_tls`]: Connection::read_tls - /// [`process_new_packets`]: Connection::process_new_packets - #[inline] - pub fn process_new_packets(&mut self) -> Result { - self.core - .process_new_packets(&mut self.deframer_buffer, &mut self.sendable_plaintext) - } - - /// Derives key material from the agreed connection secrets. - /// - /// This function fills in `output` with `output.len()` bytes of key - /// material derived from the master session secret using `label` - /// and `context` for diversification. Ownership of the buffer is taken - /// by the function and returned via the Ok result to ensure no key - /// material leaks if the function fails. - /// - /// See RFC5705 for more details on what this does and is for. - /// - /// For TLS1.3 connections, this function does not use the - /// "early" exporter at any point. - /// - /// This function fails if called prior to the handshake completing; - /// check with [`CommonState::is_handshaking`] first. - /// - /// This function fails if `output.len()` is zero. - #[inline] - pub fn export_keying_material>( - &self, - output: T, - label: &[u8], - context: Option<&[u8]>, - ) -> Result { - self.core - .export_keying_material(output, label, context) - } - - /// Extract secrets, so they can be used when configuring kTLS, for example. - /// Should be used with care as it exposes secret key material. - pub fn dangerous_extract_secrets(self) -> Result { - if !self.enable_secret_extraction { - return Err(Error::General("Secret extraction is disabled".into())); - } - - let st = self.core.state?; - - let record_layer = self.core.common_state.record_layer; - let PartiallyExtractedSecrets { tx, rx } = st.extract_secrets()?; - Ok(ExtractedSecrets { - tx: (record_layer.write_seq(), tx), - rx: (record_layer.read_seq(), rx), - }) - } - - /// Sets a limit on the internal buffers used to buffer - /// unsent plaintext (prior to completing the TLS handshake) - /// and unsent TLS records. This limit acts only on application - /// data written through [`Connection::writer`]. - /// - /// By default the limit is 64KB. The limit can be set - /// at any time, even if the current buffer use is higher. - /// - /// [`None`] means no limit applies, and will mean that written - /// data is buffered without bound -- it is up to the application - /// to appropriately schedule its plaintext and TLS writes to bound - /// memory usage. - /// - /// For illustration: `Some(1)` means a limit of one byte applies: - /// [`Connection::writer`] will accept only one byte, encrypt it and - /// add a TLS header. Once this is sent via [`Connection::write_tls`], - /// another byte may be sent. - /// - /// # Internal write-direction buffering - /// rustls has two buffers whose size are bounded by this setting: - /// - /// ## Buffering of unsent plaintext data prior to handshake completion - /// - /// Calls to [`Connection::writer`] before or during the handshake - /// are buffered (up to the limit specified here). Once the - /// handshake completes this data is encrypted and the resulting - /// TLS records are added to the outgoing buffer. - /// - /// ## Buffering of outgoing TLS records - /// - /// This buffer is used to store TLS records that rustls needs to - /// send to the peer. It is used in these two circumstances: - /// - /// - by [`Connection::process_new_packets`] when a handshake or alert - /// TLS record needs to be sent. - /// - by [`Connection::writer`] post-handshake: the plaintext is - /// encrypted and the resulting TLS record is buffered. - /// - /// This buffer is emptied by [`Connection::write_tls`]. - /// - /// [`Connection::writer`]: crate::Connection::writer - /// [`Connection::write_tls`]: crate::Connection::write_tls - /// [`Connection::process_new_packets`]: crate::Connection::process_new_packets - pub fn set_buffer_limit(&mut self, limit: Option) { - self.sendable_plaintext.set_limit(limit); - self.sendable_tls.set_limit(limit); - } - - /// Sends a TLS1.3 `key_update` message to refresh a connection's keys. - /// - /// This call refreshes our encryption keys. Once the peer receives the message, - /// it refreshes _its_ encryption and decryption keys and sends a response. - /// Once we receive that response, we refresh our decryption keys to match. - /// At the end of this process, keys in both directions have been refreshed. - /// - /// Note that this process does not happen synchronously: this call just - /// arranges that the `key_update` message will be included in the next - /// `write_tls` output. - /// - /// This fails with `Error::HandshakeNotComplete` if called before the initial - /// handshake is complete, or if a version prior to TLS1.3 is negotiated. - /// - /// # Usage advice - /// Note that other implementations (including rustls) may enforce limits on - /// the number of `key_update` messages allowed on a given connection to prevent - /// denial of service. Therefore, this should be called sparingly. - /// - /// rustls implicitly and automatically refreshes traffic keys when needed - /// according to the selected cipher suite's cryptographic constraints. There - /// is therefore no need to call this manually to avoid cryptographic keys - /// "wearing out". - /// - /// The main reason to call this manually is to roll keys when it is known - /// a connection will be idle for a long period. - pub fn refresh_traffic_keys(&mut self) -> Result<(), Error> { - self.core.refresh_traffic_keys() - } -} - -#[cfg(feature = "std")] -impl ConnectionCommon { - /// Returns an object that allows reading plaintext. - pub fn reader(&mut self) -> Reader<'_> { - let common = &mut self.core.common_state; - Reader { - received_plaintext: &mut common.received_plaintext, - // Are we done? i.e., have we processed all received messages, and received a - // close_notify to indicate that no new messages will arrive? - has_received_close_notify: common.has_received_close_notify, - has_seen_eof: common.has_seen_eof, - } - } - - /// Returns an object that allows writing plaintext. - pub fn writer(&mut self) -> Writer<'_> { - Writer::new(self) - } - - /// This function uses `io` to complete any outstanding IO for - /// this connection. - /// - /// This is a convenience function which solely uses other parts - /// of the public API. - /// - /// What this means depends on the connection state: - /// - /// - If the connection [`is_handshaking`], then IO is performed until - /// the handshake is complete. - /// - Otherwise, if [`wants_write`] is true, [`write_tls`] is invoked - /// until it is all written. - /// - Otherwise, if [`wants_read`] is true, [`read_tls`] is invoked - /// once. - /// - /// The return value is the number of bytes read from and written - /// to `io`, respectively. - /// - /// This function will block if `io` blocks. - /// - /// Errors from TLS record handling (i.e., from [`process_new_packets`]) - /// are wrapped in an `io::ErrorKind::InvalidData`-kind error. - /// - /// [`is_handshaking`]: CommonState::is_handshaking - /// [`wants_read`]: CommonState::wants_read - /// [`wants_write`]: CommonState::wants_write - /// [`write_tls`]: ConnectionCommon::write_tls - /// [`read_tls`]: ConnectionCommon::read_tls - /// [`process_new_packets`]: ConnectionCommon::process_new_packets - pub fn complete_io(&mut self, io: &mut T) -> Result<(usize, usize), io::Error> - where - Self: Sized, - T: io::Read + io::Write, - { - let mut eof = false; - let mut wrlen = 0; - let mut rdlen = 0; - - loop { - let until_handshaked = self.is_handshaking(); - - if !self.wants_write() && !self.wants_read() { - // We will make no further progress. - return Ok((rdlen, wrlen)); - } - - while self.wants_write() { - match self.write_tls(io)? { - 0 => { - io.flush()?; - return Ok((rdlen, wrlen)); // EOF. - } - n => wrlen += n, - } - } - io.flush()?; - - if !until_handshaked && wrlen > 0 { - return Ok((rdlen, wrlen)); - } - - while !eof && self.wants_read() { - let read_size = match self.read_tls(io) { - Ok(0) => { - eof = true; - Some(0) - } - Ok(n) => { - rdlen += n; - Some(n) - } - Err(ref err) if err.kind() == io::ErrorKind::Interrupted => None, // nothing to do - Err(err) => return Err(err), - }; - if read_size.is_some() { - break; - } - } - - match self.process_new_packets() { - Ok(_) => {} - Err(e) => { - // In case we have an alert to send describing this error, - // try a last-gasp write -- but don't predate the primary - // error. - let _ignored = self.write_tls(io); - let _ignored = io.flush(); - - return Err(io::Error::new(io::ErrorKind::InvalidData, e)); - } - }; - - // if we're doing IO until handshaked, and we believe we've finished handshaking, - // but process_new_packets() has queued TLS data to send, loop around again to write - // the queued messages. - if until_handshaked && !self.is_handshaking() && self.wants_write() { - continue; - } - - match (eof, until_handshaked, self.is_handshaking()) { - (_, true, false) => return Ok((rdlen, wrlen)), - (_, false, _) => return Ok((rdlen, wrlen)), - (true, true, true) => return Err(io::Error::from(io::ErrorKind::UnexpectedEof)), - (..) => {} - } - } - } - - /// Extract the first handshake message. - /// - /// This is a shortcut to the `process_new_packets()` -> `process_msg()` -> - /// `process_handshake_messages()` path, specialized for the first handshake message. - pub(crate) fn first_handshake_message(&mut self) -> Result>, Error> { - let mut buffer_progress = self.core.hs_deframer.progress(); - - let res = self - .core - .deframe( - None, - self.deframer_buffer.filled_mut(), - &mut buffer_progress, - ) - .map(|opt| opt.map(|pm| Message::try_from(pm).map(|m| m.into_owned()))); - - match res? { - Some(Ok(msg)) => { - self.deframer_buffer - .discard(buffer_progress.take_discard()); - Ok(Some(msg)) - } - Some(Err(err)) => Err(self.send_fatal_alert(AlertDescription::DecodeError, err)), - None => Ok(None), - } - } - - pub(crate) fn replace_state(&mut self, new: Box>) { - self.core.state = Ok(new); - } - - /// Read TLS content from `rd` into the internal buffer. - /// - /// Due to the internal buffering, `rd` can supply TLS messages in arbitrary-sized chunks (like - /// a socket or pipe might). - /// - /// You should call [`process_new_packets()`] each time a call to this function succeeds in order - /// to empty the incoming TLS data buffer. - /// - /// This function returns `Ok(0)` when the underlying `rd` does so. This typically happens when - /// a socket is cleanly closed, or a file is at EOF. Errors may result from the IO done through - /// `rd`; additionally, errors of `ErrorKind::Other` are emitted to signal backpressure: - /// - /// * In order to empty the incoming TLS data buffer, you should call [`process_new_packets()`] - /// each time a call to this function succeeds. - /// * In order to empty the incoming plaintext data buffer, you should empty it through - /// the [`reader()`] after the call to [`process_new_packets()`]. - /// - /// This function also returns `Ok(0)` once a `close_notify` alert has been successfully - /// received. No additional data is ever read in this state. - /// - /// [`process_new_packets()`]: ConnectionCommon::process_new_packets - /// [`reader()`]: ConnectionCommon::reader - pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result { - if self.received_plaintext.is_full() { - return Err(io::Error::new( - io::ErrorKind::Other, - "received plaintext buffer full", - )); - } - - if self.has_received_close_notify { - return Ok(0); - } - - let res = self - .deframer_buffer - .read(rd, self.core.hs_deframer.is_active()); - if let Ok(0) = res { - self.has_seen_eof = true; - } - res - } - - /// Writes TLS messages to `wr`. - /// - /// On success, this function returns `Ok(n)` where `n` is a number of bytes written to `wr` - /// (after encoding and encryption). - /// - /// After this function returns, the connection buffer may not yet be fully flushed. The - /// [`CommonState::wants_write`] function can be used to check if the output buffer is empty. - pub fn write_tls(&mut self, wr: &mut dyn io::Write) -> Result { - self.sendable_tls.write_to(wr) - } -} - -impl<'a, Data> From<&'a mut ConnectionCommon> for Context<'a, Data> { - fn from(conn: &'a mut ConnectionCommon) -> Self { - Self { - common: &mut conn.core.common_state, - data: &mut conn.core.data, - sendable_plaintext: Some(&mut conn.sendable_plaintext), - } - } -} - -impl Deref for ConnectionCommon { - type Target = CommonState; - - fn deref(&self) -> &Self::Target { - &self.core.common_state - } -} - -impl DerefMut for ConnectionCommon { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.core.common_state - } -} - -impl From> for ConnectionCommon { - fn from(core: ConnectionCore) -> Self { - Self { - core, - deframer_buffer: DeframerVecBuffer::default(), - sendable_plaintext: ChunkVecBuffer::new(Some(DEFAULT_BUFFER_LIMIT)), - } - } -} - -/// Interface shared by unbuffered client and server connections. -pub struct UnbufferedConnectionCommon { - pub(crate) core: ConnectionCore, - wants_write: bool, -} - -impl From> for UnbufferedConnectionCommon { - fn from(core: ConnectionCore) -> Self { - Self { - core, - wants_write: false, - } - } -} - -impl Deref for UnbufferedConnectionCommon { - type Target = CommonState; - - fn deref(&self) -> &Self::Target { - &self.core.common_state - } -} - -pub(crate) struct ConnectionCore { - pub(crate) state: Result>, Error>, - pub(crate) data: Data, - pub(crate) common_state: CommonState, - pub(crate) hs_deframer: HandshakeDeframer, - - /// We limit consecutive empty fragments to avoid a route for the peer to send - /// us significant but fruitless traffic. - seen_consecutive_empty_fragments: u8, -} - -impl ConnectionCore { - pub(crate) fn new(state: Box>, data: Data, common_state: CommonState) -> Self { - Self { - state: Ok(state), - data, - common_state, - hs_deframer: HandshakeDeframer::default(), - seen_consecutive_empty_fragments: 0, - } - } - - pub(crate) fn process_new_packets( - &mut self, - deframer_buffer: &mut DeframerVecBuffer, - sendable_plaintext: &mut ChunkVecBuffer, - ) -> Result { - let mut state = match mem::replace(&mut self.state, Err(Error::HandshakeNotComplete)) { - Ok(state) => state, - Err(e) => { - self.state = Err(e.clone()); - return Err(e); - } - }; - - let mut buffer_progress = self.hs_deframer.progress(); - - loop { - let res = self.deframe( - Some(&*state), - deframer_buffer.filled_mut(), - &mut buffer_progress, - ); - - let opt_msg = match res { - Ok(opt_msg) => opt_msg, - Err(e) => { - self.state = Err(e.clone()); - deframer_buffer.discard(buffer_progress.take_discard()); - return Err(e); - } - }; - - let Some(msg) = opt_msg else { - break; - }; - - match self.process_msg(msg, state, Some(sendable_plaintext)) { - Ok(new) => state = new, - Err(e) => { - self.state = Err(e.clone()); - deframer_buffer.discard(buffer_progress.take_discard()); - return Err(e); - } - } - - if self - .common_state - .has_received_close_notify - { - // "Any data received after a closure alert has been received MUST be ignored." - // -- - // This is data that has already been accepted in `read_tls`. - buffer_progress.add_discard(deframer_buffer.filled().len()); - break; - } - - deframer_buffer.discard(buffer_progress.take_discard()); - } - - deframer_buffer.discard(buffer_progress.take_discard()); - self.state = Ok(state); - Ok(self.common_state.current_io_state()) - } - - /// Pull a message out of the deframer and send any messages that need to be sent as a result. - fn deframe<'b>( - &mut self, - state: Option<&dyn State>, - buffer: &'b mut [u8], - buffer_progress: &mut BufferProgress, - ) -> Result>, Error> { - // before processing any more of `buffer`, return any extant messages from `hs_deframer` - if self.hs_deframer.has_message_ready() { - Ok(self.take_handshake_message(buffer, buffer_progress)) - } else { - self.process_more_input(state, buffer, buffer_progress) - } - } - - fn take_handshake_message<'b>( - &mut self, - buffer: &'b mut [u8], - buffer_progress: &mut BufferProgress, - ) -> Option> { - self.hs_deframer - .iter(buffer) - .next() - .map(|(message, discard)| { - buffer_progress.add_discard(discard); - message - }) - } - - fn process_more_input<'b>( - &mut self, - state: Option<&dyn State>, - buffer: &'b mut [u8], - buffer_progress: &mut BufferProgress, - ) -> Result>, Error> { - let version_is_tls13 = matches!( - self.common_state.negotiated_version, - Some(ProtocolVersion::TLSv1_3) - ); - - let locator = Locator::new(buffer); - - loop { - let mut iter = DeframerIter::new(&mut buffer[buffer_progress.processed()..]); - - let (message, processed) = loop { - let message = match iter.next().transpose() { - Ok(Some(message)) => message, - Ok(None) => return Ok(None), - Err(err) => return Err(self.handle_deframe_error(err, state)), - }; - - let allowed_plaintext = match message.typ { - // CCS messages are always plaintext. - ContentType::ChangeCipherSpec => true, - // Alerts are allowed to be plaintext if-and-only-if: - // * The negotiated protocol version is TLS 1.3. - In TLS 1.2 it is unambiguous when - // keying changes based on the CCS message. Only TLS 1.3 requires these heuristics. - // * We have not yet decrypted any messages from the peer - if we have we don't - // expect any plaintext. - // * The payload size is indicative of a plaintext alert message. - ContentType::Alert - if version_is_tls13 - && !self - .common_state - .record_layer - .has_decrypted() - && message.payload.len() <= 2 => - { - true - } - // In other circumstances, we expect all messages to be encrypted. - _ => false, - }; - - if allowed_plaintext && !self.hs_deframer.is_active() { - break (message.into_plain_message(), iter.bytes_consumed()); - } - - let message = match self - .common_state - .record_layer - .decrypt_incoming(message) - { - // failed decryption during trial decryption is not allowed to be - // interleaved with partial handshake data. - Ok(None) if !self.hs_deframer.is_aligned() => { - return Err( - PeerMisbehaved::RejectedEarlyDataInterleavedWithHandshakeMessage.into(), - ) - } - - // failed decryption during trial decryption. - Ok(None) => continue, - - Ok(Some(message)) => message, - - Err(err) => return Err(self.handle_deframe_error(err, state)), - }; - - let Decrypted { - want_close_before_decrypt, - plaintext, - } = message; - - if want_close_before_decrypt { - self.common_state.send_close_notify(); - } - - break (plaintext, iter.bytes_consumed()); - }; - - if !self.hs_deframer.is_aligned() && message.typ != ContentType::Handshake { - // "Handshake messages MUST NOT be interleaved with other record - // types. That is, if a handshake message is split over two or more - // records, there MUST NOT be any other records between them." - // https://www.rfc-editor.org/rfc/rfc8446#section-5.1 - return Err(PeerMisbehaved::MessageInterleavedWithHandshakeMessage.into()); - } - - match message.payload.len() { - 0 => { - if self.seen_consecutive_empty_fragments - == ALLOWED_CONSECUTIVE_EMPTY_FRAGMENTS_MAX - { - return Err(PeerMisbehaved::TooManyEmptyFragments.into()); - } - self.seen_consecutive_empty_fragments += 1; - } - _ => { - self.seen_consecutive_empty_fragments = 0; - } - }; - - buffer_progress.add_processed(processed); - - // do an end-run around the borrow checker, converting `message` (containing - // a borrowed slice) to an unborrowed one (containing a `Range` into the - // same buffer). the reborrow happens inside the branch that returns the - // message. - // - // is fixed by -Zpolonius - // https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md#problem-case-3-conditional-control-flow-across-functions - let unborrowed = InboundUnborrowedMessage::unborrow(&locator, message); - - if unborrowed.typ != ContentType::Handshake { - let message = unborrowed.reborrow(&Delocator::new(buffer)); - buffer_progress.add_discard(processed); - return Ok(Some(message)); - } - - let message = unborrowed.reborrow(&Delocator::new(buffer)); - self.hs_deframer - .input_message(message, &locator, buffer_progress.processed()); - self.hs_deframer.coalesce(buffer)?; - - self.common_state.aligned_handshake = self.hs_deframer.is_aligned(); - - if self.hs_deframer.has_message_ready() { - // trial decryption finishes with the first handshake message after it started. - self.common_state - .record_layer - .finish_trial_decryption(); - - return Ok(self.take_handshake_message(buffer, buffer_progress)); - } - } - } - - fn handle_deframe_error(&mut self, error: Error, state: Option<&dyn State>) -> Error { - match error { - error @ Error::InvalidMessage(_) => { - if self.common_state.is_quic() { - self.common_state.quic.alert = Some(AlertDescription::DecodeError); - error - } else { - self.common_state - .send_fatal_alert(AlertDescription::DecodeError, error) - } - } - Error::PeerSentOversizedRecord => self - .common_state - .send_fatal_alert(AlertDescription::RecordOverflow, error), - Error::DecryptError => { - if let Some(state) = state { - state.handle_decrypt_error(); - } - self.common_state - .send_fatal_alert(AlertDescription::BadRecordMac, error) - } - - error => error, - } - } - - fn process_msg( - &mut self, - msg: InboundPlainMessage<'_>, - state: Box>, - sendable_plaintext: Option<&mut ChunkVecBuffer>, - ) -> Result>, Error> { - // Drop CCS messages during handshake in TLS1.3 - if msg.typ == ContentType::ChangeCipherSpec - && !self - .common_state - .may_receive_application_data - && self.common_state.is_tls13() - { - if !msg.is_valid_ccs() { - // "An implementation which receives any other change_cipher_spec value or - // which receives a protected change_cipher_spec record MUST abort the - // handshake with an "unexpected_message" alert." - return Err(self.common_state.send_fatal_alert( - AlertDescription::UnexpectedMessage, - PeerMisbehaved::IllegalMiddleboxChangeCipherSpec, - )); - } - - self.common_state - .received_tls13_change_cipher_spec()?; - trace!("Dropping CCS"); - return Ok(state); - } - - // Now we can fully parse the message payload. - let msg = match Message::try_from(msg) { - Ok(msg) => msg, - Err(err) => { - return Err(self - .common_state - .send_fatal_alert(AlertDescription::DecodeError, err)); - } - }; - - // For alerts, we have separate logic. - if let MessagePayload::Alert(alert) = &msg.payload { - self.common_state.process_alert(alert)?; - return Ok(state); - } - - self.common_state - .process_main_protocol(msg, state, &mut self.data, sendable_plaintext) - } - - pub(crate) fn export_keying_material>( - &self, - mut output: T, - label: &[u8], - context: Option<&[u8]>, - ) -> Result { - if output.as_mut().is_empty() { - return Err(Error::General( - "export_keying_material with zero-length output".into(), - )); - } - - match self.state.as_ref() { - Ok(st) => st - .export_keying_material(output.as_mut(), label, context) - .map(|_| output), - Err(e) => Err(e.clone()), - } - } - - /// Trigger a `refresh_traffic_keys` if required by `CommonState`. - fn maybe_refresh_traffic_keys(&mut self) { - if mem::take( - &mut self - .common_state - .refresh_traffic_keys_pending, - ) { - let _ = self.refresh_traffic_keys(); - } - } - - fn refresh_traffic_keys(&mut self) -> Result<(), Error> { - match &mut self.state { - Ok(st) => st.send_key_update_request(&mut self.common_state), - Err(e) => Err(e.clone()), - } - } -} - -/// Data specific to the peer's side (client or server). -pub trait SideData: Debug {} - -/// An InboundPlainMessage which does not borrow its payload, but -/// references a range that can later be borrowed. -struct InboundUnborrowedMessage { - typ: ContentType, - version: ProtocolVersion, - bounds: Range, -} - -impl InboundUnborrowedMessage { - fn unborrow(locator: &Locator, msg: InboundPlainMessage<'_>) -> Self { - Self { - typ: msg.typ, - version: msg.version, - bounds: locator.locate(msg.payload), - } - } - - fn reborrow<'b>(self, delocator: &Delocator<'b>) -> InboundPlainMessage<'b> { - InboundPlainMessage { - typ: self.typ, - version: self.version, - payload: delocator.slice_from_range(&self.bounds), - } - } -} - -/// cf. BoringSSL's `kMaxEmptyRecords` -/// -const ALLOWED_CONSECUTIVE_EMPTY_FRAGMENTS_MAX: u8 = 32; diff --git a/rustls/src/conn/kernel.rs b/rustls/src/conn/kernel.rs new file mode 100644 index 00000000000..1fa28c847f5 --- /dev/null +++ b/rustls/src/conn/kernel.rs @@ -0,0 +1,223 @@ +//! Kernel connection API. +//! +//! This module gives you the bare minimum you need to implement a TLS connection +//! that does its own encryption and decryption while still using rustls to manage +//! connection secrets and session tickets. It is intended for use cases like kTLS +//! where you want to use rustls to establish the connection but want to use +//! something else to do the encryption/decryption after that. +//! +//! There are only two things that [`KernelConnection`] is able to do: +//! 1. Compute new traffic secrets when a key update occurs. +//! 2. Save received session tickets sent by a server peer. +//! +//! That's it. Everything else you will need to implement yourself. +//! +//! # Cipher Suite Confidentiality Limits +//! Some cipher suites (notably AES-GCM) have vulnerabilities where they are no +//! longer secure once a certain number of messages have been sent. Normally, +//! rustls tracks how many messages have been written or read and will +//! automatically either refresh keys or emit an error when approaching the +//! confidentiality limit of the cipher suite. +//! +//! [`KernelConnection`] has no way to track this. It is the responsibility +//! of the user of the API to track approximately how many messages have been +//! sent and either refresh the traffic keys or abort the connection before the +//! confidentiality limit is reached. +//! +//! You can find the current confidentiality limit by looking at +//! [`CipherSuiteCommon::confidentiality_limit`] for the cipher suite selected +//! by the connection. +//! +//! [`CipherSuiteCommon::confidentiality_limit`]: crate::CipherSuiteCommon::confidentiality_limit +//! [`KernelConnection`]: crate::kernel::KernelConnection + +use alloc::boxed::Box; +use core::marker::PhantomData; + +use crate::client::ClientSide; +use crate::enums::ProtocolVersion; +use crate::error::ApiMisuse; +use crate::msgs::{Codec, NewSessionTicketPayloadTls13}; +use crate::tls13::key_schedule::KeyScheduleTrafficSend; +use crate::{ConnectionOutputs, ConnectionTrafficSecrets, Error, SupportedCipherSuite}; + +/// A kernel connection. +/// +/// This does not directly wrap a kernel connection, rather it gives you the +/// minimal interfaces you need to implement a well-behaved TLS connection on +/// top of kTLS. +/// +/// See the [`crate::kernel`] module docs for more details. +pub struct KernelConnection { + state: Box, + tls13_key_schedule: Option>, + + negotiated_version: ProtocolVersion, + suite: SupportedCipherSuite, + + _side: PhantomData, +} + +impl KernelConnection { + pub(crate) fn new( + state: Box, + outputs: ConnectionOutputs, + tls13_key_schedule: Option>, + ) -> Result { + let (negotiated_version, suite) = outputs + .into_kernel_parts() + .ok_or(Error::HandshakeNotComplete)?; + Ok(Self { + state, + tls13_key_schedule, + + negotiated_version, + suite, + + _side: PhantomData, + }) + } + + /// Retrieves the cipher suite agreed with the peer. + pub fn negotiated_cipher_suite(&self) -> SupportedCipherSuite { + self.suite + } + + /// Retrieves the protocol version agreed with the peer. + pub fn protocol_version(&self) -> ProtocolVersion { + self.negotiated_version + } + + /// Update the traffic secret used for encrypting messages sent to the peer. + /// + /// Returns the new traffic secret and initial sequence number to use. + /// + /// In order to use the new secret you should send a TLS 1.3 key update to + /// the peer and then use the new traffic secrets to encrypt any future + /// messages. + /// + /// Note that it is only possible to update the traffic secrets on a TLS 1.3 + /// connection. Attempting to do so on a non-TLS 1.3 connection will result + /// in an error. + pub fn update_tx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), Error> { + match &mut self.tls13_key_schedule { + // The sequence number always starts at 0 after a key update. + Some(ks) => ks + .refresh_traffic_secret() + .map(|secret| (0, secret)), + None => Err(ApiMisuse::KeyUpdateNotAvailableForTls12.into()), + } + } + + /// Update the traffic secret used for decrypting messages received from the + /// peer. + /// + /// Returns the new traffic secret and initial sequence number to use. + /// + /// You should call this method once you receive a TLS 1.3 key update message + /// from the peer. + /// + /// Note that it is only possible to update the traffic secrets on a TLS 1.3 + /// connection. Attempting to do so on a non-TLS 1.3 connection will result + /// in an error. + pub fn update_rx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), Error> { + // The sequence number always starts at 0 after a key update. + self.state + .update_rx_secret() + .map(|secret| (0, secret)) + } +} + +impl KernelConnection { + /// Handle a `new_session_ticket` message from the peer. + /// + /// This will register the session ticket within with rustls so that it can + /// be used to establish future TLS connections. + /// + /// # Getting the right payload + /// + /// This method expects to be passed the inner payload of the handshake + /// message. This means that you will need to parse the header of the + /// handshake message in order to determine the correct payload to pass in. + /// The message format is described in [RFC 8446 section 4][0]. `payload` + /// should not include the `msg_type` or `length` fields. + /// + /// Code to parse out the payload should look something like this + /// ```no_run + /// use rustls::enums::{ContentType, HandshakeType}; + /// use rustls::kernel::KernelConnection; + /// use rustls::client::ClientSide; + /// + /// # fn doctest(conn: &mut KernelConnection, typ: ContentType, message: &[u8]) -> Result<(), rustls::Error> { + /// let conn: &mut KernelConnection = // ... + /// # conn; + /// let typ: ContentType = // ... + /// # typ; + /// let mut message: &[u8] = // ... + /// # message; + /// + /// // Processing for other messages not included in this example + /// assert_eq!(typ, ContentType::Handshake); + /// + /// // There may be multiple handshake payloads within a single handshake message. + /// while !message.is_empty() { + /// let (typ, len, rest) = match message { + /// &[typ, a, b, c, ref rest @ ..] => ( + /// HandshakeType::from(typ), + /// u32::from_be_bytes([0, a, b, c]) as usize, + /// rest + /// ), + /// _ => panic!("error handling not included in this example") + /// }; + /// + /// // Processing for other messages not included in this example. + /// assert_eq!(typ, HandshakeType::NewSessionTicket); + /// assert!(rest.len() >= len, "invalid handshake message"); + /// + /// let (payload, rest) = rest.split_at(len); + /// message = rest; + /// + /// conn.handle_new_session_ticket(payload)?; + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// This method will return an error if: + /// - This connection is not a TLS 1.3 connection (in TLS 1.2 session tickets + /// are sent as part of the handshake). + /// - The provided payload is not a valid `new_session_ticket` payload or has + /// extra unparsed trailing data. + /// - An error occurs while the connection updates the session ticket store. + /// + /// [0]: https://datatracker.ietf.org/doc/html/rfc8446#section-4 + pub fn handle_new_session_ticket(&mut self, payload: &[u8]) -> Result<(), Error> { + // We want to return a more specific error here first if this is called + // on a non-TLS 1.3 connection since a parsing error isn't the real issue + // here. + if self.protocol_version() != ProtocolVersion::TLSv1_3 { + return Err(Error::General( + "TLS 1.2 session tickets may not be sent once the handshake has completed".into(), + )); + } + + let nst = NewSessionTicketPayloadTls13::read_bytes(payload)?; + self.state + .handle_new_session_ticket(&nst) + } +} + +pub(crate) trait KernelState: Send + Sync { + /// Update the traffic secret for the specified direction on the connection. + fn update_rx_secret(&mut self) -> Result; + + /// Handle a new session ticket. + /// + /// This will only ever be called for client connections, as [`KernelConnection`] + /// only exposes the relevant API for client connections. + fn handle_new_session_ticket( + &self, + message: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error>; +} diff --git a/rustls/src/conn/mod.rs b/rustls/src/conn/mod.rs new file mode 100644 index 00000000000..44bc23272a8 --- /dev/null +++ b/rustls/src/conn/mod.rs @@ -0,0 +1,916 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::fmt::{self, Debug}; +use core::ops::{Deref, DerefMut}; +use std::io::{self, BufRead, Read}; + +use kernel::KernelConnection; +use pki_types::FipsStatus; + +use crate::common_state::{ + CommonState, ConnectionOutput, ConnectionOutputs, Event, Output, OutputEvent, UnborrowedPayload, +}; +use crate::error::{ApiMisuse, Error}; +use crate::kernel::KernelState; +use crate::msgs::{Delocator, Message, Random, TlsInputBuffer, VecInput}; +use crate::quic::QuicOutput; +use crate::suites::{ExtractedSecrets, PartiallyExtractedSecrets}; +use crate::tls13::key_schedule::KeyScheduleTrafficSend; +use crate::vecbuf::ChunkVecBuffer; + +// pub so that it can be re-exported from the crate root +pub mod kernel; + +mod receive; +use receive::JoinOutput; +pub(crate) use receive::{Input, ReceivePath, TrafficTemperCounters}; + +mod send; +use send::DEFAULT_BUFFER_LIMIT; +pub(crate) use send::{SendOutput, SendPath}; + +use crate::crypto::cipher::OutboundPlain; + +/// A trait generalizing over buffered client or server connections. +pub trait Connection: Debug + Deref { + /// Read TLS content from `rd` into the internal buffer. + /// + /// Due to the internal buffering, `rd` can supply TLS messages in arbitrary-sized chunks (like + /// a socket or pipe might). + /// + /// You should call [`process_new_packets()`] each time a call to this function succeeds in order + /// to empty the incoming TLS data buffer. + /// + /// This function returns `Ok(0)` when the underlying `rd` does so. This typically happens when + /// a socket is cleanly closed, or a file is at EOF. Errors may result from the IO done through + /// `rd`; additionally, errors of `ErrorKind::Other` are emitted to signal backpressure: + /// + /// * In order to empty the incoming TLS data buffer, you should call [`process_new_packets()`] + /// each time a call to this function succeeds. + /// * In order to empty the incoming plaintext data buffer, you should empty it through + /// the [`reader()`] after the call to [`process_new_packets()`]. + /// + /// This function also returns `Ok(0)` once a `close_notify` alert has been successfully + /// received. No additional data is ever read in this state. + /// + /// [`process_new_packets()`]: Connection::process_new_packets + /// [`reader()`]: Connection::reader + fn read_tls(&mut self, rd: &mut dyn Read) -> Result; + + /// Writes TLS messages to `wr`. + /// + /// On success, this function returns `Ok(n)` where `n` is a number of bytes written to `wr` + /// (after encoding and encryption). + /// + /// After this function returns, the connection buffer may not yet be fully flushed. The + /// [`Connection::wants_write()`] function can be used to check if the output buffer is + /// empty. + fn write_tls(&mut self, wr: &mut dyn io::Write) -> Result; + + /// Returns true if the caller should call [`Connection::read_tls`] as soon + /// as possible. + /// + /// If there is pending plaintext data to read with [`Connection::reader`], + /// this returns false. If your application respects this mechanism, + /// only one full TLS message will be buffered by rustls. + /// + /// [`Connection::reader`]: crate::Connection::reader + /// [`Connection::read_tls`]: crate::Connection::read_tls + fn wants_read(&self) -> bool; + + /// Returns true if the caller should call [`Connection::write_tls`] as soon as possible. + /// + /// [`Connection::write_tls`]: crate::Connection::write_tls + fn wants_write(&self) -> bool; + + /// Returns an object that allows reading plaintext. + fn reader(&mut self) -> Reader<'_>; + + /// Returns an object that allows writing plaintext. + fn writer(&mut self) -> Writer<'_>; + + /// Processes any new packets read by a previous call to + /// [`Connection::read_tls`]. + /// + /// Errors from this function relate to TLS protocol errors, and + /// are fatal to the connection. Future calls after an error will do + /// no new work and will return the same error. After an error is + /// received from [`process_new_packets()`], you should not call [`read_tls()`] + /// any more (it will fill up buffers to no purpose). However, you + /// may call the other methods on the connection, including `write`, + /// `send_close_notify`, and `write_tls`. Most likely you will want to + /// call `write_tls` to send any alerts queued by the error and then + /// close the underlying connection. + /// + /// Success from this function comes with some sundry state data + /// about the connection. + /// + /// [`process_new_packets()`]: Connection::process_new_packets + /// [`read_tls()`]: Connection::read_tls + fn process_new_packets(&mut self) -> Result; + + /// Returns an object that can derive key material from the agreed connection secrets. + /// + /// See [RFC5705][] for more details on what this is for. + /// + /// This function can be called at most once per connection. + /// + /// This function will error: + /// + /// - if called prior to the handshake completing; (check with + /// [`Connection::is_handshaking()`] first). + /// - if called more than once per connection. + /// + /// [RFC5705]: https://datatracker.ietf.org/doc/html/rfc5705 + fn exporter(&mut self) -> Result; + + /// Extract secrets, so they can be used when configuring kTLS, for example. + /// + /// Should be used with care as it exposes secret key material. + fn dangerous_extract_secrets(self) -> Result; + + /// Sets a limit on the internal buffers used to buffer + /// unsent plaintext (prior to completing the TLS handshake) + /// and unsent TLS records. This limit acts only on application + /// data written through [`Connection::writer`]. + /// + /// By default the limit is 64KB. The limit can be set + /// at any time, even if the current buffer use is higher. + /// + /// [`None`] means no limit applies, and will mean that written + /// data is buffered without bound -- it is up to the application + /// to appropriately schedule its plaintext and TLS writes to bound + /// memory usage. + /// + /// For illustration: `Some(1)` means a limit of one byte applies: + /// [`Connection::writer`] will accept only one byte, encrypt it and + /// add a TLS header. Once this is sent via [`Connection::write_tls`], + /// another byte may be sent. + /// + /// # Internal write-direction buffering + /// rustls has two buffers whose size are bounded by this setting: + /// + /// ## Buffering of unsent plaintext data prior to handshake completion + /// + /// Calls to [`Connection::writer`] before or during the handshake + /// are buffered (up to the limit specified here). Once the + /// handshake completes this data is encrypted and the resulting + /// TLS records are added to the outgoing buffer. + /// + /// ## Buffering of outgoing TLS records + /// + /// This buffer is used to store TLS records that rustls needs to + /// send to the peer. It is used in these two circumstances: + /// + /// - by [`Connection::process_new_packets`] when a handshake or alert + /// TLS record needs to be sent. + /// - by [`Connection::writer`] post-handshake: the plaintext is + /// encrypted and the resulting TLS record is buffered. + /// + /// This buffer is emptied by [`Connection::write_tls`]. + /// + /// [`Connection::writer`]: crate::Connection::writer + /// [`Connection::write_tls`]: crate::Connection::write_tls + /// [`Connection::process_new_packets`]: crate::Connection::process_new_packets + fn set_buffer_limit(&mut self, limit: Option); + + /// Sets a limit on the internal buffers used to buffer decoded plaintext. + /// + /// See [`Self::set_buffer_limit`] for more information on how limits are applied. + fn set_plaintext_buffer_limit(&mut self, limit: Option); + + /// Sends a TLS1.3 `key_update` message to refresh a connection's keys. + /// + /// This call refreshes our encryption keys. Once the peer receives the message, + /// it refreshes _its_ encryption and decryption keys and sends a response. + /// Once we receive that response, we refresh our decryption keys to match. + /// At the end of this process, keys in both directions have been refreshed. + /// + /// Note that this process does not happen synchronously: this call just + /// arranges that the `key_update` message will be included in the next + /// `write_tls` output. + /// + /// This fails with `Error::HandshakeNotComplete` if called before the initial + /// handshake is complete, or if a version prior to TLS1.3 is negotiated. + /// + /// # Usage advice + /// Note that other implementations (including rustls) may enforce limits on + /// the number of `key_update` messages allowed on a given connection to prevent + /// denial of service. Therefore, this should be called sparingly. + /// + /// rustls implicitly and automatically refreshes traffic keys when needed + /// according to the selected cipher suite's cryptographic constraints. There + /// is therefore no need to call this manually to avoid cryptographic keys + /// "wearing out". + /// + /// The main reason to call this manually is to roll keys when it is known + /// a connection will be idle for a long period. + fn refresh_traffic_keys(&mut self) -> Result<(), Error>; + + /// Queues a `close_notify` warning alert to be sent in the next + /// [`Connection::write_tls`] call. This informs the peer that the + /// connection is being closed. + /// + /// Does nothing if any `close_notify` or fatal alert was already sent. + /// + /// [`Connection::write_tls`]: crate::Connection::write_tls + fn send_close_notify(&mut self); + + /// Returns true if the connection is currently performing the TLS handshake. + /// + /// During this time plaintext written to the connection is buffered in memory. After + /// [`Connection::process_new_packets()`] has been called, this might start to return `false` + /// while the final handshake packets still need to be extracted from the connection's buffers. + /// + /// [`Connection::process_new_packets()`]: crate::Connection::process_new_packets + fn is_handshaking(&self) -> bool; + + /// Return the FIPS validation status of the connection. + /// + /// This is different from [`CryptoProvider::fips()`][]: + /// it is concerned only with cryptography, whereas this _also_ covers TLS-level + /// configuration that NIST recommends, as well as ECH HPKE suites if applicable. + /// + /// [`CryptoProvider::fips()`]: crate::crypto::CryptoProvider::fips() + fn fips(&self) -> FipsStatus; +} + +/// A structure that implements [`std::io::Read`] for reading plaintext. +pub struct Reader<'a> { + pub(super) received_plaintext: &'a mut ChunkVecBuffer, + pub(super) has_received_close_notify: bool, + pub(super) has_seen_eof: bool, +} + +impl<'a> Reader<'a> { + /// Check the connection's state if no bytes are available for reading. + fn check_no_bytes_state(&self) -> io::Result<()> { + match (self.has_received_close_notify, self.has_seen_eof) { + // cleanly closed; don't care about TCP EOF: express this as Ok(0) + (true, _) => Ok(()), + // unclean closure + (false, true) => Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + UNEXPECTED_EOF_MESSAGE, + )), + // connection still going, but needs more data: signal `WouldBlock` so that + // the caller knows this + (false, false) => Err(io::ErrorKind::WouldBlock.into()), + } + } + + /// Obtain a chunk of plaintext data received from the peer over this TLS connection. + /// + /// This method consumes `self` so that it can return a slice whose lifetime is bounded by + /// the [`Connection`] that created this [`Reader`]. + pub fn into_first_chunk(self) -> io::Result<&'a [u8]> { + match self.received_plaintext.chunk() { + Some(chunk) => Ok(chunk), + None => { + self.check_no_bytes_state()?; + Ok(&[]) + } + } + } +} + +impl Read for Reader<'_> { + /// Obtain plaintext data received from the peer over this TLS connection. + /// + /// If the peer closes the TLS session cleanly, this returns `Ok(0)` once all + /// the pending data has been read. No further data can be received on that + /// connection, so the underlying TCP connection should be half-closed too. + /// + /// If the peer closes the TLS session uncleanly (a TCP EOF without sending a + /// `close_notify` alert) this function returns a `std::io::Error` of type + /// `ErrorKind::UnexpectedEof` once any pending data has been read. + /// + /// Note that support for `close_notify` varies in peer TLS libraries: many do not + /// support it and uncleanly close the TCP connection (this might be + /// vulnerable to truncation attacks depending on the application protocol). + /// This means applications using rustls must both handle EOF + /// from this function, *and* unexpected EOF of the underlying TCP connection. + /// + /// If there are no bytes to read, this returns `Err(ErrorKind::WouldBlock.into())`. + /// + /// You may learn the number of bytes available at any time by inspecting + /// the return of [`Connection::process_new_packets`]. + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = self.received_plaintext.read(buf)?; + if len > 0 || buf.is_empty() { + return Ok(len); + } + + self.check_no_bytes_state() + .map(|()| len) + } +} + +impl BufRead for Reader<'_> { + /// Obtain a chunk of plaintext data received from the peer over this TLS connection. + /// This reads the same data as [`Reader::read()`], but returns a reference instead of + /// copying the data. + /// + /// The caller should call [`Reader::consume()`] afterward to advance the buffer. + /// + /// See [`Reader::into_first_chunk()`] for a version of this function that returns a + /// buffer with a longer lifetime. + fn fill_buf(&mut self) -> io::Result<&[u8]> { + Reader { + // reborrow + received_plaintext: self.received_plaintext, + ..*self + } + .into_first_chunk() + } + + fn consume(&mut self, amt: usize) { + self.received_plaintext + .consume_first_chunk(amt) + } +} + +const UNEXPECTED_EOF_MESSAGE: &str = "peer closed connection without sending TLS close_notify: \ +https://docs.rs/rustls/latest/rustls/manual/_03_howto/index.html#unexpected-eof"; + +/// A structure that implements [`std::io::Write`] for writing plaintext. +pub struct Writer<'a> { + sink: &'a mut dyn PlaintextSink, +} + +impl<'a> Writer<'a> { + /// Create a new Writer. + /// + /// This is not an external interface. Get one of these objects + /// from [`Connection::writer`]. + pub(crate) fn new(sink: &'a mut dyn PlaintextSink) -> Self { + Writer { sink } + } +} + +impl io::Write for Writer<'_> { + /// Send the plaintext `buf` to the peer, encrypting + /// and authenticating it. Once this function succeeds + /// you should call [`Connection::write_tls`] which will output the + /// corresponding TLS records. + /// + /// This function buffers plaintext sent before the + /// TLS handshake completes, and sends it as soon + /// as it can. See [`Connection::set_buffer_limit()`] to control + /// the size of this buffer. + fn write(&mut self, buf: &[u8]) -> io::Result { + self.sink.write(buf) + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.sink.write_vectored(bufs) + } + + fn flush(&mut self) -> io::Result<()> { + self.sink.flush() + } +} + +/// Internal trait implemented by the [`ServerConnection`]/[`ClientConnection`] +/// allowing them to be the subject of a [`Writer`]. +/// +/// [`ServerConnection`]: crate::ServerConnection +/// [`ClientConnection`]: crate::ClientConnection +pub(crate) trait PlaintextSink { + fn write(&mut self, buf: &[u8]) -> io::Result; + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result; + fn flush(&mut self) -> io::Result<()>; +} + +impl PlaintextSink for ConnectionCommon { + fn write(&mut self, buf: &[u8]) -> io::Result { + let len = self + .core + .common + .send + .buffer_plaintext(buf.into(), &mut self.buffers.sendable_plaintext); + self.send.maybe_refresh_traffic_keys(); + Ok(len) + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + let payload_owner: Vec<&[u8]>; + let payload = match bufs.len() { + 0 => return Ok(0), + 1 => OutboundPlain::Single(bufs[0].deref()), + _ => { + payload_owner = bufs + .iter() + .map(|io_slice| io_slice.deref()) + .collect(); + + OutboundPlain::new(&payload_owner) + } + }; + let len = self + .core + .common + .send + .buffer_plaintext(payload, &mut self.buffers.sendable_plaintext); + self.send.maybe_refresh_traffic_keys(); + Ok(len) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// An object of this type can export keying material. +pub struct KeyingMaterialExporter { + pub(crate) inner: Box, +} + +impl KeyingMaterialExporter { + /// Derives key material from the agreed connection secrets. + /// + /// This function fills in `output` with `output.len()` bytes of key + /// material derived from a master connection secret using `label` + /// and `context` for diversification. Ownership of the buffer is taken + /// by the function and returned via the Ok result to ensure no key + /// material leaks if the function fails. + /// + /// See [RFC5705][] for more details on what this does and is for. In + /// other libraries this is often named `SSL_export_keying_material()` + /// or `SslExportKeyingMaterial()`. + /// + /// This function is not meaningful if `output.len()` is zero and will + /// return an error in that case. + /// + /// [RFC5705]: https://datatracker.ietf.org/doc/html/rfc5705 + pub fn derive>( + &self, + label: &[u8], + context: Option<&[u8]>, + mut output: T, + ) -> Result { + if output.as_mut().is_empty() { + return Err(ApiMisuse::ExporterOutputZeroLength.into()); + } + + self.inner + .derive(label, context, output.as_mut()) + .map(|_| output) + } +} + +impl Debug for KeyingMaterialExporter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("KeyingMaterialExporter") + .finish_non_exhaustive() + } +} + +/// This trait is for any object that can export keying material. +/// +/// The terminology comes from [RFC5705](https://datatracker.ietf.org/doc/html/rfc5705) +/// but doesn't really involve "exporting" key material (in the usual meaning of "export" +/// -- of moving an artifact from one domain to another) but is best thought of as key +/// diversification using an existing secret. That secret is implicit in this interface, +/// so is assumed to be held by `self`. The secret should be zeroized in `drop()`. +/// +/// There are several such internal implementations, depending on the context +/// and protocol version. +pub(crate) trait Exporter: Send + Sync { + /// Fills in `output` with derived keying material. + /// + /// This is deterministic depending on a base secret (implicit in `self`), + /// plus the `label` and `context` values. + /// + /// Must fill in `output` entirely, or return an error. + fn derive(&self, label: &[u8], context: Option<&[u8]>, output: &mut [u8]) -> Result<(), Error>; +} + +#[derive(Debug)] +pub(crate) struct ConnectionRandoms { + pub(crate) client: [u8; 32], + pub(crate) server: [u8; 32], +} + +impl ConnectionRandoms { + pub(crate) fn new(client: Random, server: Random) -> Self { + Self { + client: client.0, + server: server.0, + } + } +} + +/// TLS connection state with side-specific data (`Side`). +/// +/// This is one of the core abstractions of the rustls API. It represents a single connection +/// to a peer, and holds all the state associated with that connection. Note that it does +/// not hold any IO objects: the application is responsible for reading and writing TLS records. +/// If you want an object that does hold IO objects, see `rustls_util::Stream` and +/// `rustls_util::StreamOwned`. +/// +/// This object is generic over the `Side` type parameter, which must implement the marker trait +/// [`SideData`]. This is used to store side-specific data. +pub(crate) struct ConnectionCommon { + pub(crate) core: ConnectionCore, + pub(crate) fips: FipsStatus, + buffers: Buffers, +} + +impl ConnectionCommon { + pub(crate) fn new(core: ConnectionCore, fips: FipsStatus) -> Self { + Self { + core, + fips, + buffers: Buffers::new(), + } + } + + #[inline] + pub(crate) fn process_new_packets(&mut self) -> Result { + loop { + let Some(payload) = self + .core + .process_new_packets(&mut self.buffers.deframer_buffer, None)? + else { + break; + }; + + let payload = + payload.reborrow(&Delocator::new(self.buffers.deframer_buffer.slice_mut())); + self.buffers + .received_plaintext + .append(payload.into_vec()); + self.buffers.deframer_buffer.discard( + self.core + .common + .recv + .deframer + .take_discard(), + ); + } + + // Release unsent buffered plaintext. + if self.send.may_send_application_data + && !self + .buffers + .sendable_plaintext + .is_empty() + { + self.core + .common + .send + .send_buffered_plaintext(&mut self.buffers.sendable_plaintext); + } + + Ok(self.current_io_state()) + } + + pub(crate) fn wants_read(&self) -> bool { + // We want to read more data all the time, except when we have unprocessed plaintext. + // This provides back-pressure to the TCP buffers. We also don't want to read more after + // the peer has sent us a close notification. + // + // In the handshake case we don't have readable plaintext before the handshake has + // completed, but also don't want to read if we still have sendable tls. + self.buffers + .received_plaintext + .is_empty() + && !self.recv.has_received_close_notify + && (self.send.may_send_application_data || self.send.sendable_tls.is_empty()) + } + + pub(crate) fn exporter(&mut self) -> Result { + self.core.exporter() + } + + /// Extract secrets, so they can be used when configuring kTLS, for example. + /// Should be used with care as it exposes secret key material. + pub(crate) fn dangerous_extract_secrets(self) -> Result { + self.core.dangerous_extract_secrets() + } + + pub(crate) fn set_buffer_limit(&mut self, limit: Option) { + self.buffers + .sendable_plaintext + .set_limit(limit); + self.send.sendable_tls.set_limit(limit); + } + + pub(crate) fn set_plaintext_buffer_limit(&mut self, limit: Option) { + self.buffers + .received_plaintext + .set_limit(limit); + } + + pub(crate) fn refresh_traffic_keys(&mut self) -> Result<(), Error> { + self.core + .common + .send + .refresh_traffic_keys() + } + + pub(crate) fn current_io_state(&self) -> IoState { + let common_state = &self.core.common; + IoState { + tls_bytes_to_write: common_state.send.sendable_tls.len(), + plaintext_bytes_to_read: self.buffers.received_plaintext.len(), + peer_has_closed: common_state + .recv + .has_received_close_notify, + } + } +} + +impl ConnectionCommon { + /// Returns an object that allows reading plaintext. + pub(crate) fn reader(&mut self) -> Reader<'_> { + let common = &mut self.core.common; + let has_received_close_notify = common.recv.has_received_close_notify; + Reader { + received_plaintext: &mut self.buffers.received_plaintext, + // Are we done? i.e., have we processed all received messages, and received a + // close_notify to indicate that no new messages will arrive? + has_received_close_notify, + has_seen_eof: self.buffers.has_seen_eof, + } + } + + /// Returns an object that allows writing plaintext. + pub(crate) fn writer(&mut self) -> Writer<'_> { + Writer::new(self) + } + + pub(crate) fn read_tls(&mut self, rd: &mut dyn Read) -> Result { + if self + .buffers + .received_plaintext + .is_full() + { + return Err(io::Error::other("received plaintext buffer full")); + } + + if self.recv.has_received_close_notify { + return Ok(0); + } + + let res = self.buffers.deframer_buffer.read(rd); + if let Ok(0) = res { + self.buffers.has_seen_eof = true; + } + res + } + + pub(crate) fn write_tls(&mut self, wr: &mut dyn io::Write) -> Result { + self.send.sendable_tls.write_to(wr) + } +} + +impl Deref for ConnectionCommon { + type Target = CommonState; + + fn deref(&self) -> &Self::Target { + &self.core.common + } +} + +impl DerefMut for ConnectionCommon { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.core.common + } +} + +/// Common items for buffered, std::io-using connections. +pub(crate) struct Buffers { + deframer_buffer: VecInput, + pub(crate) received_plaintext: ChunkVecBuffer, + pub(crate) sendable_plaintext: ChunkVecBuffer, + pub(crate) has_seen_eof: bool, +} + +impl Buffers { + fn new() -> Self { + Self { + deframer_buffer: VecInput::default(), + received_plaintext: ChunkVecBuffer::new(Some(DEFAULT_RECEIVED_PLAINTEXT_LIMIT)), + sendable_plaintext: ChunkVecBuffer::new(Some(DEFAULT_BUFFER_LIMIT)), + has_seen_eof: false, + } + } +} + +/// Values of this structure are returned from [`Connection::process_new_packets`] +/// and tell the caller the current I/O state of the TLS connection. +/// +/// [`Connection::process_new_packets`]: crate::Connection::process_new_packets +#[derive(Debug, Eq, PartialEq)] +pub struct IoState { + tls_bytes_to_write: usize, + plaintext_bytes_to_read: usize, + peer_has_closed: bool, +} + +impl IoState { + /// How many bytes could be written by [`Connection::write_tls`] if called + /// right now. A non-zero value implies [`CommonState::wants_write`]. + /// + /// [`Connection::write_tls`]: crate::Connection::write_tls + pub fn tls_bytes_to_write(&self) -> usize { + self.tls_bytes_to_write + } + + /// How many plaintext bytes could be obtained via [`std::io::Read`] + /// without further I/O. + pub fn plaintext_bytes_to_read(&self) -> usize { + self.plaintext_bytes_to_read + } + + /// True if the peer has sent us a close_notify alert. This is + /// the TLS mechanism to securely half-close a TLS connection, + /// and signifies that the peer will not send any further data + /// on this connection. + /// + /// This is also signalled via returning `Ok(0)` from + /// [`std::io::Read`], after all the received bytes have been + /// retrieved. + pub fn peer_has_closed(&self) -> bool { + self.peer_has_closed + } +} + +pub(crate) struct ConnectionCore { + pub(crate) state: Result, + pub(crate) side: Side::Data, + pub(crate) common: CommonState, +} + +impl ConnectionCore { + pub(crate) fn new(state: Side::State, side: Side::Data, common: CommonState) -> Self { + Self { + state: Ok(state), + side, + common, + } + } + + #[inline] + pub(crate) fn process_new_packets<'a>( + &'a mut self, + buffer: &mut dyn TlsInputBuffer, + quic: Option<&'a mut dyn QuicOutput>, + ) -> Result, Error> { + let mut output = JoinOutput { + outputs: &mut self.common.outputs, + quic, + send: &mut self.common.send, + side: &mut self.side, + }; + + self.common + .recv + .process_new_packets::(buffer, &mut self.state, &mut output) + } + + pub(crate) fn dangerous_extract_secrets(self) -> Result { + Ok(self + .dangerous_into_kernel_connection()? + .0) + } + + pub(crate) fn dangerous_into_kernel_connection( + mut self, + ) -> Result<(ExtractedSecrets, KernelConnection), Error> { + if self.common.is_handshaking() { + return Err(Error::HandshakeNotComplete); + } + Self::from_parts_into_kernel_connection( + &mut self.common.send, + self.common.recv, + self.common.outputs, + self.state?, + ) + } + + pub(crate) fn from_parts_into_kernel_connection( + send: &mut SendPath, + recv: ReceivePath, + outputs: ConnectionOutputs, + state: Side::State, + ) -> Result<(ExtractedSecrets, KernelConnection), Error> { + if !send.sendable_tls.is_empty() { + return Err(ApiMisuse::SecretExtractionWithPendingSendableData.into()); + } + + let read_seq = recv.decrypt_state.read_seq(); + let write_seq = send.encrypt_state.write_seq(); + + let tls13_key_schedule = send.tls13_key_schedule.take(); + + let (secrets, state) = state.into_external_state(&tls13_key_schedule)?; + let secrets = ExtractedSecrets { + tx: (write_seq, secrets.tx), + rx: (read_seq, secrets.rx), + }; + let external = KernelConnection::new(state, outputs, tls13_key_schedule)?; + + Ok((secrets, external)) + } + + pub(crate) fn exporter(&mut self) -> Result { + match self.common.exporter.take() { + Some(inner) => Ok(KeyingMaterialExporter { inner }), + None if self.common.is_handshaking() => Err(Error::HandshakeNotComplete), + None => Err(ApiMisuse::ExporterAlreadyUsed.into()), + } + } + + pub(crate) fn early_exporter(&mut self) -> Result { + match self.common.early_exporter.take() { + Some(inner) => Ok(KeyingMaterialExporter { inner }), + None => Err(ApiMisuse::ExporterAlreadyUsed.into()), + } + } +} + +pub(crate) struct SideCommonOutput<'a, 'q> { + pub(crate) side: &'a mut dyn SideOutput, + pub(crate) quic: Option<&'q mut dyn QuicOutput>, + pub(crate) common: &'a mut CommonState, +} + +impl<'q> Output<'_> for SideCommonOutput<'_, 'q> { + fn emit(&mut self, ev: Event<'_>) { + self.side.emit(ev); + } + + fn output(&mut self, ev: OutputEvent<'_>) { + self.common.outputs.handle(ev); + } + + fn send_msg(&mut self, m: Message<'_>, must_encrypt: bool) { + match self.quic() { + Some(quic) => quic.send_msg(m, must_encrypt), + None => self + .common + .send + .send_msg(m, must_encrypt), + } + } + + fn quic(&mut self) -> Option<&mut dyn QuicOutput> { + match self.quic.as_mut() { + Some(q) => Some(&mut **q), + None => None, + } + } + + fn start_traffic(&mut self) { + self.common + .recv + .may_receive_application_data = true; + self.common + .send + .start_outgoing_traffic(); + } + + fn receive(&mut self) -> &mut ReceivePath { + &mut self.common.recv + } + + fn send(&mut self) -> &mut dyn SendOutput { + &mut self.common.send + } +} + +/// Data specific to the peer's side (client or server). +#[expect(private_bounds)] +pub trait SideData: private::Side {} + +pub(crate) mod private { + use super::*; + + pub(crate) trait Side: Debug { + /// Data storage type. + type Data: SideOutput; + /// State machine type. + type State: StateMachine; + } + + pub(crate) trait SideOutput { + fn emit(&mut self, ev: Event<'_>); + } +} + +use private::SideOutput; + +pub(crate) trait StateMachine: Sized { + fn handle<'m>(self, input: Input<'m>, output: &mut dyn Output<'m>) -> Result; + fn wants_input(&self) -> bool; + fn handle_decrypt_error(&mut self); + fn into_external_state( + self, + send_keys: &Option>, + ) -> Result<(PartiallyExtractedSecrets, Box), Error>; +} + +const DEFAULT_RECEIVED_PLAINTEXT_LIMIT: usize = 16 * 1024; diff --git a/rustls/src/conn/receive.rs b/rustls/src/conn/receive.rs new file mode 100644 index 00000000000..27740d13e55 --- /dev/null +++ b/rustls/src/conn/receive.rs @@ -0,0 +1,635 @@ +use core::marker::PhantomData; +use core::mem; +use core::ops::Range; + +use super::SendOutput; +use crate::SideData; +use crate::common_state::{ + ConnectionOutput, ConnectionOutputs, Event, Output, OutputEvent, Side, UnborrowedPayload, + maybe_send_fatal_alert, +}; +use crate::conn::StateMachine; +use crate::conn::private::SideOutput; +use crate::crypto::cipher::{Decrypted, DecryptionState, EncodedMessage, Payload}; +use crate::enums::{ContentType, HandshakeType, ProtocolVersion}; +use crate::error::{AlertDescription, Error, PeerMisbehaved}; +use crate::log::{trace, warn}; +use crate::msgs::{ + AlertLevel, AlertLevelName, AlertMessagePayload, Deframed, Deframer, Delocator, + HandshakeAlignedProof, Locator, Message, MessagePayload, TlsInputBuffer, +}; +use crate::quic::QuicOutput; + +pub(crate) struct ReceivePath { + side: Side, + pub(crate) decrypt_state: DecryptionState, + pub(crate) may_receive_application_data: bool, + /// If the peer has signaled end of stream. + pub(crate) has_received_close_notify: bool, + temper_counters: TemperCounters, + pub(crate) negotiated_version: Option, + pub(crate) deframer: Deframer, + + /// We limit consecutive empty fragments to avoid a route for the peer to send + /// us significant but fruitless traffic. + seen_consecutive_empty_fragments: u8, + + pub(crate) tls13_tickets_received: u32, +} + +impl ReceivePath { + pub(crate) fn new(side: Side) -> Self { + Self { + side, + decrypt_state: DecryptionState::new(), + may_receive_application_data: false, + has_received_close_notify: false, + temper_counters: TemperCounters::default(), + negotiated_version: None, + deframer: Deframer::default(), + seen_consecutive_empty_fragments: 0, + tls13_tickets_received: 0, + } + } + + pub(super) fn process_new_packets<'a, 'm, Side: SideData>( + &mut self, + input: &'m mut dyn TlsInputBuffer, + state: &mut Result, + output: &mut JoinOutput<'a>, + ) -> Result, Error> { + let mut st = match mem::replace(state, Err(Error::HandshakeNotComplete)) { + Ok(state) => state, + Err(e) => { + *state = Err(e.clone()); + return Err(e); + } + }; + + let mut plaintext = None; + while st.wants_input() { + let buffer = input.slice_mut(); + let locator = Locator::new(buffer); + let res = self.deframe(buffer); + + let mut output = CaptureAppData { + recv: self, + other: &mut *output, + plaintext_locator: &locator, + received_plaintext: &mut plaintext, + _message_lifetime: PhantomData, + }; + + let opt_msg = match res { + Ok(opt_msg) => opt_msg, + Err(e) => { + maybe_send_fatal_alert(output.other.send, &e); + if let Error::DecryptError = e { + st.handle_decrypt_error(); + } + *state = Err(e.clone()); + input.discard(self.deframer.take_discard()); + return Err(e); + } + }; + + let Some(msg) = opt_msg else { + break; + }; + + let Decrypted { + plaintext: msg, + want_close_before_decrypt, + } = msg; + + if want_close_before_decrypt { + output + .other + .send + .send_alert(AlertLevel::Warning, AlertDescription::CloseNotify); + } + + let hs_aligned = output.recv.deframer.aligned(); + let result = match output + .recv + .receive_message(msg, hs_aligned, output.other.send) + { + Ok(Some(input)) => st.handle(input, &mut output), + Ok(None) => Ok(st), + Err(e) => Err(e), + }; + + match result { + Ok(new) => st = new, + Err(e) => { + maybe_send_fatal_alert(output.other.send, &e); + *state = Err(e.clone()); + input.discard(self.deframer.take_discard()); + return Err(e); + } + } + + if self.has_received_close_notify { + // "Any data received after a closure alert has been received MUST be ignored." + // -- + // This is data that has already been accepted in `read_tls`. + let entirety = input.slice_mut().len(); + input.discard(entirety); + break; + } + + if let Some(payload) = plaintext.take() { + *state = Ok(st); + return Ok(Some(payload)); + } + + input.discard(self.deframer.take_discard()); + } + + input.discard(self.deframer.take_discard()); + *state = Ok(st); + Ok(None) + } + + /// Pull a message out of the deframer and send any messages that need to be sent as a result. + fn deframe<'b>(&mut self, buffer: &'b mut [u8]) -> Result>, Error> { + let version_is_tls13 = matches!(self.negotiated_version, Some(ProtocolVersion::TLSv1_3)); + + let locator = Locator::new(buffer); + + let mut want_close_before_decrypt = false; + loop { + // before processing any more of `buffer`, return any extant messages from `deframer` + if let Some(span) = self.deframer.complete_span() { + let plaintext = self.deframer.message(span, buffer); + + // trial decryption finishes with the first handshake message after it started. + self.decrypt_state + .finish_trial_decryption(); + + return Ok(Some(Decrypted { + plaintext, + want_close_before_decrypt, + })); + } + + let (message, bounds) = loop { + let (message, bounds) = match self.deframer.deframe(buffer) { + Some(Ok(Deframed { message, bounds })) => (message, bounds), + Some(Err(err)) => return Err(err), + None => return Ok(None), + }; + + let allowed_plaintext = match message.typ { + // CCS messages are always plaintext. + ContentType::ChangeCipherSpec => true, + // Alerts are allowed to be plaintext if-and-only-if: + // * The negotiated protocol version is TLS 1.3. - In TLS 1.2 it is unambiguous when + // keying changes based on the CCS message. Only TLS 1.3 requires these heuristics. + // * We have not yet decrypted any messages from the peer - if we have we don't + // expect any plaintext. + // * The payload size is indicative of a plaintext alert message. + ContentType::Alert + if version_is_tls13 + && !self.decrypt_state.has_decrypted() + && message.payload.len() <= 2 => + { + true + } + // In other circumstances, we expect all messages to be encrypted. + _ => false, + }; + + if allowed_plaintext && !self.deframer.is_active() { + break ( + Decrypted { + plaintext: message.into_plain_message(), + want_close_before_decrypt: false, + }, + bounds, + ); + } + + match self + .decrypt_state + .decrypt_incoming(message) + { + // failed decryption during trial decryption is not allowed to be + // interleaved with partial handshake data. + Ok(None) if self.deframer.aligned().is_none() => { + return Err( + PeerMisbehaved::RejectedEarlyDataInterleavedWithHandshakeMessage.into(), + ); + } + + // failed decryption during trial decryption. + Ok(None) => continue, + + Ok(Some(decrypted)) => { + // After decryption, the payload is shorter + let bounds = locator.locate(decrypted.plaintext.payload); + break (decrypted, bounds); + } + + Err(err) => return Err(err), + } + }; + + want_close_before_decrypt = message.want_close_before_decrypt; + let Decrypted { + plaintext: message, + want_close_before_decrypt: _, + } = message; + + if self.deframer.aligned().is_none() && message.typ != ContentType::Handshake { + // "Handshake messages MUST NOT be interleaved with other record + // types. That is, if a handshake message is split over two or more + // records, there MUST NOT be any other records between them." + // https://www.rfc-editor.org/rfc/rfc8446#section-5.1 + return Err(PeerMisbehaved::MessageInterleavedWithHandshakeMessage.into()); + } + + match message.payload.len() { + 0 => { + if self.seen_consecutive_empty_fragments + == ALLOWED_CONSECUTIVE_EMPTY_FRAGMENTS_MAX + { + return Err(PeerMisbehaved::TooManyEmptyFragments.into()); + } + self.seen_consecutive_empty_fragments += 1; + } + _ => { + self.seen_consecutive_empty_fragments = 0; + } + }; + + // do an end-run around the borrow checker, converting `message` (containing + // a borrowed slice) to an unborrowed one (containing a `Range` into the + // same buffer). the reborrow happens inside the branch that returns the + // message. + // + // is fixed by -Zpolonius + // https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md#problem-case-3-conditional-control-flow-across-functions + let unborrowed = InboundUnborrowedMessage::unborrow(&locator, message); + + if unborrowed.typ != ContentType::Handshake { + let message = unborrowed.reborrow(&Delocator::new(buffer)); + self.deframer.discard_processed(); + return Ok(Some(Decrypted { + plaintext: message, + want_close_before_decrypt, + })); + } + + let message = unborrowed.reborrow(&Delocator::new(buffer)); + self.deframer + .input_message(message, bounds); + self.deframer.coalesce(buffer)?; + } + } + + /// Take a TLS message `msg` and map it into an `Input` + /// + /// `Input` is the input to our state machine. + /// + /// The message is mapped into `None` if it should be dropped with no further + /// action. + /// + /// Otherwise the caller must present the returned `Input` to the state machine to + /// progress the connection. + pub(crate) fn receive_message<'a>( + &mut self, + msg: EncodedMessage<&'a [u8]>, + aligned_handshake: Option, + send: &mut dyn SendOutput, + ) -> Result>, Error> { + // Drop CCS messages during handshake in TLS1.3 + if msg.typ == ContentType::ChangeCipherSpec && self.drop_tls13_ccs(&msg)? { + trace!("Dropping CCS"); + return Ok(None); + } + + // Now we can fully parse the message payload. + let message = Message::try_from(msg)?; + + // For alerts, we have separate logic. + if let MessagePayload::Alert(alert) = &message.payload { + self.process_alert(alert)?; + return Ok(None); + } + + // For TLS1.2, outside of the handshake, send rejection alerts for + // renegotiation requests. These can occur any time. + if self.reject_renegotiation_request(&message, send)? { + return Ok(None); + } + + Ok(Some(Input { + message, + aligned_handshake, + })) + } + + fn drop_tls13_ccs(&mut self, msg: &EncodedMessage<&'_ [u8]>) -> Result { + if self.may_receive_application_data + || !matches!(self.negotiated_version, Some(ProtocolVersion::TLSv1_3)) + { + return Ok(false); + } + + if !msg.is_valid_ccs() { + // "An implementation which receives any other change_cipher_spec value or + // which receives a protected change_cipher_spec record MUST abort the + // handshake with an "unexpected_message" alert." + return Err(PeerMisbehaved::IllegalMiddleboxChangeCipherSpec.into()); + } + + self.temper_counters + .received_tls13_change_cipher_spec()?; + Ok(true) + } + + fn reject_renegotiation_request( + &mut self, + msg: &Message<'_>, + send: &mut dyn SendOutput, + ) -> Result { + if !self.may_receive_application_data + || matches!(self.negotiated_version, Some(ProtocolVersion::TLSv1_3)) + { + return Ok(false); + } + + let reject_ty = match self.side { + Side::Client => HandshakeType::HelloRequest, + Side::Server => HandshakeType::ClientHello, + }; + + if msg.handshake_type() != Some(reject_ty) { + return Ok(false); + } + self.temper_counters + .received_renegotiation_request()?; + let desc = AlertDescription::NoRenegotiation; + warn!("sending warning alert {desc:?}"); + send.send_alert(AlertLevel::Warning, desc); + Ok(true) + } + + fn process_alert(&mut self, alert: &AlertMessagePayload) -> Result<(), Error> { + // Reject unknown AlertLevels. + if AlertLevelName::try_from(alert.level).is_err() { + return Err(PeerMisbehaved::IllegalAlertLevel(alert.level.0, alert.description).into()); + } + + // If we get a CloseNotify, make a note to declare EOF to our + // caller. But do not treat unauthenticated alerts like this. + if self.may_receive_application_data && alert.description == AlertDescription::CloseNotify { + self.has_received_close_notify = true; + return Ok(()); + } + + // Warnings are nonfatal for TLS1.2, but outlawed in TLS1.3 + // (except, for no good reason, user_cancelled). + let err = Error::AlertReceived(alert.description); + if alert.level == AlertLevel::Warning { + self.temper_counters + .received_warning_alert()?; + if matches!(self.negotiated_version, Some(ProtocolVersion::TLSv1_3)) + && alert.description != AlertDescription::UserCanceled + { + return Err(PeerMisbehaved::IllegalWarningAlert(alert.description).into()); + } + + // Some implementations send pointless `user_canceled` alerts, don't log them + // in release mode (https://bugs.openjdk.org/browse/JDK-8323517). + if alert.description != AlertDescription::UserCanceled || cfg!(debug_assertions) { + warn!("TLS alert warning received: {alert:?}"); + } + + return Ok(()); + } + + Err(err) + } +} + +struct CaptureAppData<'a, 'j, 'm> { + recv: &'a mut ReceivePath, + other: &'a mut JoinOutput<'j>, + /// Store a [`Locator`] initialized from the current receive buffer + /// + /// Allows received plaintext data to be unborrowed and stored in + /// `received_plaintext` for in-place decryption. + plaintext_locator: &'a Locator, + /// Unborrowed received plaintext data + /// + /// Set if plaintext data was received. + /// + /// Plaintext data may be reborrowed using a [`Delocator`] which was + /// initialized from the same slice as `plaintext_locator`. + received_plaintext: &'a mut Option, + _message_lifetime: PhantomData<&'m ()>, +} + +impl<'m> Output<'m> for CaptureAppData<'_, '_, 'm> { + fn emit(&mut self, ev: Event<'_>) { + self.other.side.emit(ev) + } + + fn output(&mut self, ev: OutputEvent<'_>) { + if let OutputEvent::ProtocolVersion(ver) = ev { + self.recv.negotiated_version = Some(ver); + self.other.send.negotiated_version(ver); + } + self.other.outputs.handle(ev); + } + + fn send_msg(&mut self, m: Message<'_>, must_encrypt: bool) { + match self.other.quic.as_deref_mut() { + Some(quic) => quic.send_msg(m, must_encrypt), + None => self + .other + .send + .send_msg(m, must_encrypt), + } + } + + fn quic(&mut self) -> Option<&mut dyn QuicOutput> { + match &mut self.other.quic { + Some(quic) => Some(*quic), + None => None, + } + } + + fn received_plaintext(&mut self, payload: Payload<'m>) { + // Receive plaintext data [`Payload<'_>`]. + // + // Since [`Context`] does not hold a lifetime to the receive buffer the + // passed [`Payload`] will have it's lifetime erased by storing an index + // into the receive buffer as an [`UnborrowedPayload`]. This enables the + // data to be later reborrowed after it has been decrypted in-place. + let previous = self + .received_plaintext + .replace(UnborrowedPayload::unborrow(self.plaintext_locator, payload)); + debug_assert!(previous.is_none(), "overwrote plaintext data"); + } + + fn start_traffic(&mut self) { + self.recv.may_receive_application_data = true; + self.other.send.start_traffic(); + } + + fn receive(&mut self) -> &mut ReceivePath { + self.recv + } + + fn send(&mut self) -> &mut dyn SendOutput { + self.other.send + } +} + +pub(super) struct JoinOutput<'a> { + pub(super) outputs: &'a mut ConnectionOutputs, + pub(super) quic: Option<&'a mut dyn QuicOutput>, + pub(super) send: &'a mut dyn SendOutput, + pub(super) side: &'a mut dyn SideOutput, +} + +/// Tracking technically-allowed protocol actions +/// that we limit to avoid denial-of-service vectors. +struct TemperCounters { + allowed_warning_alerts: u8, + allowed_renegotiation_requests: u8, + allowed_middlebox_ccs: u8, +} + +impl TemperCounters { + fn received_warning_alert(&mut self) -> Result<(), Error> { + match self.allowed_warning_alerts { + 0 => Err(PeerMisbehaved::TooManyWarningAlertsReceived.into()), + _ => { + self.allowed_warning_alerts -= 1; + Ok(()) + } + } + } + + fn received_renegotiation_request(&mut self) -> Result<(), Error> { + match self.allowed_renegotiation_requests { + 0 => Err(PeerMisbehaved::TooManyRenegotiationRequests.into()), + _ => { + self.allowed_renegotiation_requests -= 1; + Ok(()) + } + } + } + + fn received_tls13_change_cipher_spec(&mut self) -> Result<(), Error> { + match self.allowed_middlebox_ccs { + 0 => Err(PeerMisbehaved::IllegalMiddleboxChangeCipherSpec.into()), + _ => { + self.allowed_middlebox_ccs -= 1; + Ok(()) + } + } + } +} + +impl Default for TemperCounters { + fn default() -> Self { + Self { + // cf. BoringSSL `kMaxWarningAlerts` + // + allowed_warning_alerts: 4, + + // we rebuff renegotiation requests with a `NoRenegotiation` warning alerts. + // a second request after this is fatal. + allowed_renegotiation_requests: 1, + + // At most two CCS are allowed: one after each ClientHello (recall a second + // ClientHello happens after a HelloRetryRequest). + // + // note BoringSSL allows up to 32. + allowed_middlebox_ccs: 2, + } + } +} + +pub(crate) struct TrafficTemperCounters { + allowed_consecutive_handshake_messages: u8, +} + +impl TrafficTemperCounters { + pub(crate) fn received_handshake_message(&mut self) -> Result<(), Error> { + match self.allowed_consecutive_handshake_messages { + 0 => Err(PeerMisbehaved::TooManyConsecutiveHandshakeMessagesAfterHandshake.into()), + _ => { + self.allowed_consecutive_handshake_messages -= 1; + Ok(()) + } + } + } + + pub(crate) fn received_app_data(&mut self) { + self.allowed_consecutive_handshake_messages = Self::MAX_CONSECUTIVE_HANDSHAKE_MESSAGES; + } + + // cf. BoringSSL `kMaxKeyUpdates` + // + const MAX_CONSECUTIVE_HANDSHAKE_MESSAGES: u8 = 32; +} + +impl Default for TrafficTemperCounters { + fn default() -> Self { + Self { + allowed_consecutive_handshake_messages: Self::MAX_CONSECUTIVE_HANDSHAKE_MESSAGES, + } + } +} + +pub(crate) struct Input<'a> { + pub(crate) message: Message<'a>, + pub(crate) aligned_handshake: Option, +} + +impl Input<'_> { + // Changing the keys must not span any fragmented handshake + // messages. Otherwise the defragmented messages will have + // been protected with two different record layer protections, + // which is illegal. Not mentioned in RFC. + pub(crate) fn check_aligned_handshake(&self) -> Result { + self.aligned_handshake + .ok_or_else(|| PeerMisbehaved::KeyEpochWithPendingFragment.into()) + } +} + +/// An [`EncodedMessage>`] which does not borrow its payload, but +/// references a range that can later be borrowed. +struct InboundUnborrowedMessage { + typ: ContentType, + version: ProtocolVersion, + bounds: Range, +} + +impl InboundUnborrowedMessage { + fn unborrow(locator: &Locator, msg: EncodedMessage<&'_ [u8]>) -> Self { + Self { + typ: msg.typ, + version: msg.version, + bounds: locator.locate(msg.payload), + } + } + + fn reborrow<'b>(self, delocator: &Delocator<'b>) -> EncodedMessage<&'b [u8]> { + EncodedMessage { + typ: self.typ, + version: self.version, + payload: delocator.slice_from_range(&self.bounds), + } + } +} + +/// cf. BoringSSL's `kMaxEmptyRecords` +/// +const ALLOWED_CONSECUTIVE_EMPTY_FRAGMENTS_MAX: u8 = 32; diff --git a/rustls/src/conn/send.rs b/rustls/src/conn/send.rs new file mode 100644 index 00000000000..cf6a69e281c --- /dev/null +++ b/rustls/src/conn/send.rs @@ -0,0 +1,336 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; + +use crate::crypto::cipher::{ + EncodedMessage, EncryptionState, MessageEncrypter, OutboundPlain, Payload, PreEncryptAction, +}; +use crate::enums::{ContentType, ProtocolVersion}; +use crate::error::{AlertDescription, Error}; +use crate::log::{debug, error}; +use crate::msgs::{AlertLevel, Message, MessageFragmenter}; +use crate::tls13::key_schedule::KeyScheduleTrafficSend; +use crate::vecbuf::ChunkVecBuffer; + +/// The data path from us to the peer. +pub(crate) struct SendPath { + pub(crate) encrypt_state: EncryptionState, + pub(crate) may_send_application_data: bool, + pub(crate) may_send_half_rtt_data: bool, + has_sent_fatal_alert: bool, + /// If we signaled end of stream. + pub(crate) has_sent_close_notify: bool, + message_fragmenter: MessageFragmenter, + pub(crate) sendable_tls: ChunkVecBuffer, + queued_key_update_message: Option>, + pub(crate) refresh_traffic_keys_pending: bool, + negotiated_version: Option, + pub(crate) tls13_key_schedule: Option>, +} + +impl SendPath { + #[expect(dead_code)] + pub(crate) fn write_plaintext( + &mut self, + payload: OutboundPlain<'_>, + ) -> Result>, Error> { + let fragments = self + .message_fragmenter + .fragment_payload( + ContentType::ApplicationData, + ProtocolVersion::TLSv1_2, + payload, + ); + + for f in 0..fragments.len() { + self.preflight_encrypt(f)?; + } + + self.perhaps_write_key_update(); + for m in fragments { + self.sendable_tls.append( + self.encrypt_state + .encrypt_outgoing(m) + .encode(), + ); + } + + Ok(self.sendable_tls.take()) + } + + pub(crate) fn send_early_plaintext(&mut self, data: &[u8]) -> usize { + debug_assert!(self.encrypt_state.is_encrypting()); + + // Limit on `sendable_tls` should apply to encrypted data but is enforced + // for plaintext data instead which does not include cipher+record overhead. + let len = self + .sendable_tls + .apply_limit(data.len()); + if len == 0 { + // Don't send empty fragments. + return 0; + } + + self.send_appdata_encrypt(data[..len].into()) + } + + pub(crate) fn send_close_notify(&mut self) { + if self.has_sent_close_notify { + return; + } + debug!("Sending warning alert {:?}", AlertDescription::CloseNotify); + self.has_sent_close_notify = true; + self.send_alert(AlertLevel::Warning, AlertDescription::CloseNotify); + } + + pub(crate) fn send_alert(&mut self, level: AlertLevel, desc: AlertDescription) { + match level { + AlertLevel::Fatal if self.has_sent_fatal_alert => return, + AlertLevel::Fatal => self.has_sent_fatal_alert = true, + _ => {} + }; + self.send_msg( + Message::build_alert(level, desc), + self.encrypt_state.is_encrypting(), + ); + } + + /// Like send_msg_encrypt, but operate on an appdata directly. + fn send_appdata_encrypt(&mut self, payload: OutboundPlain<'_>) -> usize { + let len = payload.len(); + self.send_messages( + self.message_fragmenter + .fragment_payload( + ContentType::ApplicationData, + ProtocolVersion::TLSv1_2, + payload, + ), + ); + len + } + + /// Encrypt and queue a single fragment. + fn send_messages<'a>( + &mut self, + iter: impl ExactSizeIterator>>, + ) { + for m in iter { + // Alerts are always sendable -- never quashed by a PreEncryptAction. + if m.typ != ContentType::Alert && self.preflight_encrypt(0).is_err() { + return; + } + + self.perhaps_write_key_update(); + self.sendable_tls.append( + self.encrypt_state + .encrypt_outgoing(m) + .encode(), + ); + } + } + + fn preflight_encrypt(&mut self, n: usize) -> Result<(), Error> { + match self + .encrypt_state + .pre_encrypt_action(n as u64) + { + None => Ok(()), + + // Close connection once we start to run out of sequence space. + Some(PreEncryptAction::RefreshOrClose) => { + match self.negotiated_version { + // driven by caller, as we don't have the `State` here + Some(ProtocolVersion::TLSv1_3) => { + self.refresh_traffic_keys_pending = true; + Ok(()) + } + _ => { + error!( + "traffic keys exhausted, closing connection to prevent security failure" + ); + self.send_close_notify(); + Err(Error::EncryptError) + } + } + } + + // Refuse to wrap counter at all costs. This is basically untestable unfortunately. + Some(PreEncryptAction::Refuse) => Err(Error::EncryptError), + } + } + + /// Send plaintext application data, fragmenting and + /// encrypting it as it goes out. + /// + /// If internal buffers are too small, this function will not accept + /// all the data. + pub(crate) fn buffer_plaintext( + &mut self, + payload: OutboundPlain<'_>, + sendable_plaintext: &mut ChunkVecBuffer, + ) -> usize { + self.perhaps_write_key_update(); + if !self.may_send_application_data { + // If we haven't completed handshaking, buffer + // plaintext to send once we do. + return sendable_plaintext.append_limited_copy(payload); + } + + // Limit on `sendable_tls` should apply to encrypted data but is enforced + // for plaintext data instead which does not include cipher+record overhead. + let len = self + .sendable_tls + .apply_limit(payload.len()); + if len == 0 { + // Don't send empty fragments. + return 0; + } + + debug_assert!(self.encrypt_state.is_encrypting()); + self.send_appdata_encrypt(payload.split_at(len).0) + } + + pub(crate) fn send_buffered_plaintext(&mut self, plaintext: &mut ChunkVecBuffer) { + while let Some(buf) = plaintext.pop() { + self.send_appdata_encrypt(buf.as_slice().into()); + } + } + + pub(crate) fn start_outgoing_traffic(&mut self) { + self.may_send_application_data = true; + debug_assert!(self.encrypt_state.is_encrypting()); + } + + fn perhaps_write_key_update(&mut self) { + if let Some(message) = self.queued_key_update_message.take() { + self.sendable_tls.append(message); + } + } + + pub(crate) fn set_max_fragment_size(&mut self, new: Option) -> Result<(), Error> { + self.message_fragmenter + .set_max_fragment_size(new) + } + + pub(crate) fn ensure_key_update_queued(&mut self) { + if self.queued_key_update_message.is_some() { + return; + } + + let message = EncodedMessage::>::from(Message::build_key_update_notify()); + self.queued_key_update_message = Some( + self.encrypt_state + .encrypt_outgoing(message.borrow_outbound()) + .encode(), + ); + + if let Some(mut ks) = self.tls13_key_schedule.take() { + ks.update_encrypter_for_key_update(self); + self.tls13_key_schedule = Some(ks); + } + } + + /// Trigger a `refresh_traffic_keys` if required. + pub(crate) fn maybe_refresh_traffic_keys(&mut self) { + if self.refresh_traffic_keys_pending { + let _ = self.refresh_traffic_keys(); + } + } + + pub(crate) fn refresh_traffic_keys(&mut self) -> Result<(), Error> { + let ks = self.tls13_key_schedule.take(); + + let Some(mut ks) = ks else { + return Err(Error::HandshakeNotComplete); + }; + + ks.request_key_update_and_update_encrypter(self); + self.refresh_traffic_keys_pending = false; + self.tls13_key_schedule = Some(ks); + Ok(()) + } +} + +impl SendOutput for SendPath { + fn negotiated_version(&mut self, version: ProtocolVersion) { + self.negotiated_version = Some(version); + } + + fn ensure_key_update_queued(&mut self) { + self.ensure_key_update_queued(); + } + + fn set_encrypter(&mut self, encrypter: Box, max_messages: u64) { + self.encrypt_state + .set_message_encrypter(encrypter, max_messages); + } + + fn update_key_schedule(&mut self, schedule: Box) { + self.tls13_key_schedule = Some(schedule); + } + + fn send_alert(&mut self, level: AlertLevel, desc: AlertDescription) { + self.send_alert(level, desc); + } + + fn start_traffic(&mut self) { + self.may_send_half_rtt_data = true; + self.start_outgoing_traffic(); + } + + /// Send a raw TLS message, fragmenting it if needed. + fn send_msg(&mut self, m: Message<'_>, must_encrypt: bool) { + let encoded = EncodedMessage::from(m); + if must_encrypt { + self.send_messages( + self.message_fragmenter + .fragment_message(&encoded), + ); + return; + } + + let iter = self + .message_fragmenter + .fragment_message(&encoded); + self.perhaps_write_key_update(); + for m in iter { + self.sendable_tls + .append(m.to_unencrypted_opaque().encode()); + } + } +} + +impl Default for SendPath { + fn default() -> Self { + Self { + encrypt_state: EncryptionState::new(), + may_send_application_data: false, + may_send_half_rtt_data: false, + has_sent_fatal_alert: false, + has_sent_close_notify: false, + message_fragmenter: MessageFragmenter::default(), + sendable_tls: ChunkVecBuffer::new(Some(DEFAULT_BUFFER_LIMIT)), + queued_key_update_message: None, + refresh_traffic_keys_pending: false, + negotiated_version: None, + tls13_key_schedule: None, + } + } +} + +pub(crate) trait SendOutput { + fn negotiated_version(&mut self, version: ProtocolVersion); + + fn ensure_key_update_queued(&mut self); + + fn set_encrypter(&mut self, cipher: Box, max_messages: u64); + + fn update_key_schedule(&mut self, schedule: Box); + + fn send_alert(&mut self, level: AlertLevel, desc: AlertDescription); + + fn start_traffic(&mut self); + + fn send_msg(&mut self, m: Message<'_>, must_encrypt: bool); +} + +pub(super) const DEFAULT_BUFFER_LIMIT: usize = 64 * 1024; diff --git a/rustls/src/conn/unbuffered.rs b/rustls/src/conn/unbuffered.rs deleted file mode 100644 index ef8146e098e..00000000000 --- a/rustls/src/conn/unbuffered.rs +++ /dev/null @@ -1,582 +0,0 @@ -//! Unbuffered connection API - -use alloc::vec::Vec; -use core::num::NonZeroUsize; -use core::{fmt, mem}; -#[cfg(feature = "std")] -use std::error::Error as StdError; - -use super::UnbufferedConnectionCommon; -use crate::client::ClientConnectionData; -use crate::msgs::deframer::buffers::DeframerSliceBuffer; -use crate::server::ServerConnectionData; -use crate::Error; - -impl UnbufferedConnectionCommon { - /// Processes the TLS records in `incoming_tls` buffer until a new [`UnbufferedStatus`] is - /// reached. - pub fn process_tls_records<'c, 'i>( - &'c mut self, - incoming_tls: &'i mut [u8], - ) -> UnbufferedStatus<'c, 'i, ClientConnectionData> { - self.process_tls_records_common(incoming_tls, |_| None, |_, _, ()| unreachable!()) - } -} - -impl UnbufferedConnectionCommon { - /// Processes the TLS records in `incoming_tls` buffer until a new [`UnbufferedStatus`] is - /// reached. - pub fn process_tls_records<'c, 'i>( - &'c mut self, - incoming_tls: &'i mut [u8], - ) -> UnbufferedStatus<'c, 'i, ServerConnectionData> { - self.process_tls_records_common( - incoming_tls, - |conn| conn.pop_early_data(), - |conn, incoming_tls, chunk| ReadEarlyData::new(conn, incoming_tls, chunk).into(), - ) - } -} - -impl UnbufferedConnectionCommon { - fn process_tls_records_common<'c, 'i, T>( - &'c mut self, - incoming_tls: &'i mut [u8], - mut check: impl FnMut(&mut Self) -> Option, - execute: impl FnOnce(&'c mut Self, &'i mut [u8], T) -> ConnectionState<'c, 'i, Data>, - ) -> UnbufferedStatus<'c, 'i, Data> { - let mut buffer = DeframerSliceBuffer::new(incoming_tls); - let mut buffer_progress = self.core.hs_deframer.progress(); - - let (discard, state) = loop { - if let Some(value) = check(self) { - break (buffer.pending_discard(), execute(self, incoming_tls, value)); - } - - if let Some(chunk) = self - .core - .common_state - .received_plaintext - .pop() - { - break ( - buffer.pending_discard(), - ReadTraffic::new(self, incoming_tls, chunk).into(), - ); - } - - if let Some(chunk) = self - .core - .common_state - .sendable_tls - .pop() - { - break ( - buffer.pending_discard(), - EncodeTlsData::new(self, chunk).into(), - ); - } - - let deframer_output = - match self - .core - .deframe(None, buffer.filled_mut(), &mut buffer_progress) - { - Err(err) => { - buffer.queue_discard(buffer_progress.take_discard()); - return UnbufferedStatus { - discard: buffer.pending_discard(), - state: Err(err), - }; - } - Ok(r) => r, - }; - - if let Some(msg) = deframer_output { - let mut state = - match mem::replace(&mut self.core.state, Err(Error::HandshakeNotComplete)) { - Ok(state) => state, - Err(e) => { - buffer.queue_discard(buffer_progress.take_discard()); - self.core.state = Err(e.clone()); - return UnbufferedStatus { - discard: buffer.pending_discard(), - state: Err(e), - }; - } - }; - - match self.core.process_msg(msg, state, None) { - Ok(new) => state = new, - - Err(e) => { - buffer.queue_discard(buffer_progress.take_discard()); - self.core.state = Err(e.clone()); - return UnbufferedStatus { - discard: buffer.pending_discard(), - state: Err(e), - }; - } - } - - buffer.queue_discard(buffer_progress.take_discard()); - - self.core.state = Ok(state); - } else if self.wants_write { - break ( - buffer.pending_discard(), - TransmitTlsData { conn: self }.into(), - ); - } else if self - .core - .common_state - .has_received_close_notify - { - break (buffer.pending_discard(), ConnectionState::Closed); - } else if self - .core - .common_state - .may_send_application_data - { - break ( - buffer.pending_discard(), - ConnectionState::WriteTraffic(WriteTraffic { conn: self }), - ); - } else { - break (buffer.pending_discard(), ConnectionState::BlockedHandshake); - } - }; - - UnbufferedStatus { - discard, - state: Ok(state), - } - } -} - -/// The current status of the `UnbufferedConnection*` -#[must_use] -#[derive(Debug)] -pub struct UnbufferedStatus<'c, 'i, Data> { - /// Number of bytes to discard - /// - /// After the `state` field of this object has been handled, `discard` bytes must be - /// removed from the *front* of the `incoming_tls` buffer that was passed to - /// the [`UnbufferedConnectionCommon::process_tls_records`] call that returned this object. - /// - /// This discard operation MUST happen *before* - /// [`UnbufferedConnectionCommon::process_tls_records`] is called again. - pub discard: usize, - - /// The current state of the handshake process - /// - /// This value MUST be handled prior to calling - /// [`UnbufferedConnectionCommon::process_tls_records`] again. See the documentation on the - /// variants of [`ConnectionState`] for more details. - pub state: Result, Error>, -} - -/// The state of the [`UnbufferedConnectionCommon`] object -#[non_exhaustive] // for forwards compatibility; to support caller-side certificate verification -pub enum ConnectionState<'c, 'i, Data> { - /// One, or more, application data records are available - /// - /// See [`ReadTraffic`] for more details on how to use the enclosed object to access - /// the received data. - ReadTraffic(ReadTraffic<'c, 'i, Data>), - - /// Connection has been cleanly closed by the peer - Closed, - - /// One, or more, early (RTT-0) data records are available - ReadEarlyData(ReadEarlyData<'c, 'i, Data>), - - /// A Handshake record is ready for encoding - /// - /// Call [`EncodeTlsData::encode`] on the enclosed object, providing an `outgoing_tls` - /// buffer to store the encoding - EncodeTlsData(EncodeTlsData<'c, Data>), - - /// Previously encoded handshake records need to be transmitted - /// - /// Transmit the contents of the `outgoing_tls` buffer that was passed to previous - /// [`EncodeTlsData::encode`] calls to the peer. - /// - /// After transmitting the contents, call [`TransmitTlsData::done`] on the enclosed object. - /// The transmitted contents MUST not be sent to the peer more than once so they SHOULD be - /// discarded at this point. - /// - /// At some stages of the handshake process, it's possible to send application-data alongside - /// handshake records. Call [`TransmitTlsData::may_encrypt_app_data`] on the enclosed - /// object to probe if that's allowed. - TransmitTlsData(TransmitTlsData<'c, Data>), - - /// More TLS data is needed to continue with the handshake - /// - /// Request more data from the peer and append the contents to the `incoming_tls` buffer that - /// was passed to [`UnbufferedConnectionCommon::process_tls_records`]. - BlockedHandshake, - - /// The handshake process has been completed. - /// - /// [`WriteTraffic::encrypt`] can be called on the enclosed object to encrypt application - /// data into an `outgoing_tls` buffer. Similarly, [`WriteTraffic::queue_close_notify`] can - /// be used to encrypt a close_notify alert message into a buffer to signal the peer that the - /// connection is being closed. Data written into `outgoing_buffer` by either method MAY be - /// transmitted to the peer during this state. - /// - /// Once this state has been reached, data MAY be requested from the peer and appended to an - /// `incoming_tls` buffer that will be passed to a future - /// [`UnbufferedConnectionCommon::process_tls_records`] invocation. When enough data has been - /// appended to `incoming_tls`, [`UnbufferedConnectionCommon::process_tls_records`] will yield - /// the [`ConnectionState::ReadTraffic`] state. - WriteTraffic(WriteTraffic<'c, Data>), -} - -impl<'c, 'i, Data> From> for ConnectionState<'c, 'i, Data> { - fn from(v: ReadTraffic<'c, 'i, Data>) -> Self { - Self::ReadTraffic(v) - } -} - -impl<'c, 'i, Data> From> for ConnectionState<'c, 'i, Data> { - fn from(v: ReadEarlyData<'c, 'i, Data>) -> Self { - Self::ReadEarlyData(v) - } -} - -impl<'c, Data> From> for ConnectionState<'c, '_, Data> { - fn from(v: EncodeTlsData<'c, Data>) -> Self { - Self::EncodeTlsData(v) - } -} - -impl<'c, Data> From> for ConnectionState<'c, '_, Data> { - fn from(v: TransmitTlsData<'c, Data>) -> Self { - Self::TransmitTlsData(v) - } -} - -impl fmt::Debug for ConnectionState<'_, '_, Data> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ReadTraffic(..) => f.debug_tuple("ReadTraffic").finish(), - - Self::Closed => write!(f, "Closed"), - - Self::ReadEarlyData(..) => f.debug_tuple("ReadEarlyData").finish(), - - Self::EncodeTlsData(..) => f.debug_tuple("EncodeTlsData").finish(), - - Self::TransmitTlsData(..) => f - .debug_tuple("TransmitTlsData") - .finish(), - - Self::BlockedHandshake => f - .debug_tuple("BlockedHandshake") - .finish(), - - Self::WriteTraffic(..) => f.debug_tuple("WriteTraffic").finish(), - } - } -} - -/// Application data is available -pub struct ReadTraffic<'c, 'i, Data> { - _conn: &'c mut UnbufferedConnectionCommon, - // for forwards compatibility; to support in-place decryption in the future - _incoming_tls: &'i mut [u8], - chunk: Vec, - taken: bool, -} - -impl<'c, 'i, Data> ReadTraffic<'c, 'i, Data> { - fn new( - _conn: &'c mut UnbufferedConnectionCommon, - _incoming_tls: &'i mut [u8], - chunk: Vec, - ) -> Self { - Self { - _conn, - _incoming_tls, - chunk, - taken: false, - } - } - - /// Decrypts and returns the next available app-data record - // TODO deprecate in favor of `Iterator` implementation, which requires in-place decryption - pub fn next_record(&mut self) -> Option, Error>> { - if self.taken { - None - } else { - self.taken = true; - Some(Ok(AppDataRecord { - discard: 0, - payload: &self.chunk, - })) - } - } - - /// Returns the payload size of the next app-data record *without* decrypting it - /// - /// Returns `None` if there are no more app-data records - pub fn peek_len(&self) -> Option { - if self.taken { - None - } else { - NonZeroUsize::new(self.chunk.len()) - } - } -} - -/// Early application-data is available. -pub struct ReadEarlyData<'c, 'i, Data> { - _conn: &'c mut UnbufferedConnectionCommon, - // for forwards compatibility; to support in-place decryption in the future - _incoming_tls: &'i mut [u8], - chunk: Vec, - taken: bool, -} - -impl<'c, 'i, Data> ReadEarlyData<'c, 'i, Data> { - fn new( - _conn: &'c mut UnbufferedConnectionCommon, - _incoming_tls: &'i mut [u8], - chunk: Vec, - ) -> Self { - Self { - _conn, - _incoming_tls, - chunk, - taken: false, - } - } -} - -impl ReadEarlyData<'_, '_, ServerConnectionData> { - /// decrypts and returns the next available app-data record - // TODO deprecate in favor of `Iterator` implementation, which requires in-place decryption - pub fn next_record(&mut self) -> Option, Error>> { - if self.taken { - None - } else { - self.taken = true; - Some(Ok(AppDataRecord { - discard: 0, - payload: &self.chunk, - })) - } - } - - /// returns the payload size of the next app-data record *without* decrypting it - /// - /// returns `None` if there are no more app-data records - pub fn peek_len(&self) -> Option { - if self.taken { - None - } else { - NonZeroUsize::new(self.chunk.len()) - } - } -} - -/// A decrypted application-data record -pub struct AppDataRecord<'i> { - /// Number of additional bytes to discard - /// - /// This number MUST be added to the value of [`UnbufferedStatus.discard`] *prior* to the - /// discard operation. See [`UnbufferedStatus.discard`] for more details - pub discard: usize, - - /// The payload of the app-data record - pub payload: &'i [u8], -} - -/// Allows encrypting app-data -pub struct WriteTraffic<'c, Data> { - conn: &'c mut UnbufferedConnectionCommon, -} - -impl WriteTraffic<'_, Data> { - /// Encrypts `application_data` into the `outgoing_tls` buffer - /// - /// Returns the number of bytes that were written into `outgoing_tls`, or an error if - /// the provided buffer is too small. In the error case, `outgoing_tls` is not modified - pub fn encrypt( - &mut self, - application_data: &[u8], - outgoing_tls: &mut [u8], - ) -> Result { - self.conn - .core - .maybe_refresh_traffic_keys(); - self.conn - .core - .common_state - .write_plaintext(application_data.into(), outgoing_tls) - } - - /// Encrypts a close_notify warning alert in `outgoing_tls` - /// - /// Returns the number of bytes that were written into `outgoing_tls`, or an error if - /// the provided buffer is too small. In the error case, `outgoing_tls` is not modified - pub fn queue_close_notify(&mut self, outgoing_tls: &mut [u8]) -> Result { - self.conn - .core - .common_state - .eager_send_close_notify(outgoing_tls) - } - - /// Arranges for a TLS1.3 `key_update` to be sent. - /// - /// This consumes the `WriteTraffic` state: to actually send the message, - /// call [`UnbufferedConnectionCommon::process_tls_records`] again which will - /// return a `ConnectionState::EncodeTlsData` that emits the `key_update` - /// message. - /// - /// See [`ConnectionCommon::refresh_traffic_keys()`] for full documentation, - /// including why you might call this and in what circumstances it will fail. - /// - /// [`ConnectionCommon::refresh_traffic_keys()`]: crate::ConnectionCommon::refresh_traffic_keys - pub fn refresh_traffic_keys(self) -> Result<(), Error> { - self.conn.core.refresh_traffic_keys() - } -} - -/// A handshake record must be encoded -pub struct EncodeTlsData<'c, Data> { - conn: &'c mut UnbufferedConnectionCommon, - chunk: Option>, -} - -impl<'c, Data> EncodeTlsData<'c, Data> { - fn new(conn: &'c mut UnbufferedConnectionCommon, chunk: Vec) -> Self { - Self { - conn, - chunk: Some(chunk), - } - } - - /// Encodes a handshake record into the `outgoing_tls` buffer - /// - /// Returns the number of bytes that were written into `outgoing_tls`, or an error if - /// the provided buffer is too small. In the error case, `outgoing_tls` is not modified - pub fn encode(&mut self, outgoing_tls: &mut [u8]) -> Result { - let Some(chunk) = self.chunk.take() else { - return Err(EncodeError::AlreadyEncoded); - }; - - let required_size = chunk.len(); - - if required_size > outgoing_tls.len() { - self.chunk = Some(chunk); - Err(InsufficientSizeError { required_size }.into()) - } else { - let written = chunk.len(); - outgoing_tls[..written].copy_from_slice(&chunk); - - self.conn.wants_write = true; - - Ok(written) - } - } -} - -/// Previously encoded TLS data must be transmitted -pub struct TransmitTlsData<'c, Data> { - pub(crate) conn: &'c mut UnbufferedConnectionCommon, -} - -impl TransmitTlsData<'_, Data> { - /// Signals that the previously encoded TLS data has been transmitted - pub fn done(self) { - self.conn.wants_write = false; - } - - /// Returns an adapter that allows encrypting application data - /// - /// If allowed at this stage of the handshake process - pub fn may_encrypt_app_data(&mut self) -> Option> { - if self - .conn - .core - .common_state - .may_send_application_data - { - Some(WriteTraffic { conn: self.conn }) - } else { - None - } - } -} - -/// Errors that may arise when encoding a handshake record -#[derive(Debug)] -pub enum EncodeError { - /// Provided buffer was too small - InsufficientSize(InsufficientSizeError), - - /// The handshake record has already been encoded; do not call `encode` again - AlreadyEncoded, -} - -impl From for EncodeError { - fn from(v: InsufficientSizeError) -> Self { - Self::InsufficientSize(v) - } -} - -impl fmt::Display for EncodeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::InsufficientSize(InsufficientSizeError { required_size }) => write!( - f, - "cannot encode due to insufficient size, {} bytes are required", - required_size - ), - Self::AlreadyEncoded => "cannot encode, data has already been encoded".fmt(f), - } - } -} - -#[cfg(feature = "std")] -impl StdError for EncodeError {} - -/// Errors that may arise when encrypting application data -#[derive(Debug)] -pub enum EncryptError { - /// Provided buffer was too small - InsufficientSize(InsufficientSizeError), - - /// Encrypter has been exhausted - EncryptExhausted, -} - -impl From for EncryptError { - fn from(v: InsufficientSizeError) -> Self { - Self::InsufficientSize(v) - } -} - -impl fmt::Display for EncryptError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::InsufficientSize(InsufficientSizeError { required_size }) => write!( - f, - "cannot encrypt due to insufficient size, {required_size} bytes are required" - ), - Self::EncryptExhausted => f.write_str("encrypter has been exhausted"), - } - } -} - -#[cfg(feature = "std")] -impl StdError for EncryptError {} - -/// Provided buffer was too small -#[derive(Clone, Copy, Debug)] -pub struct InsufficientSizeError { - /// buffer must be at least this size - pub required_size: usize, -} diff --git a/rustls/src/crypto/aws_lc_rs/mod.rs b/rustls/src/crypto/aws_lc_rs/mod.rs deleted file mode 100644 index c31874aa92d..00000000000 --- a/rustls/src/crypto/aws_lc_rs/mod.rs +++ /dev/null @@ -1,283 +0,0 @@ -use alloc::sync::Arc; -use alloc::vec::Vec; - -// aws-lc-rs has a -- roughly -- ring-compatible API, so we just reuse all that -// glue here. The shared files should always use `super::ring_like` to access a -// ring-compatible crate, and `super::ring_shim` to bridge the gaps where they are -// small. -pub(crate) use aws_lc_rs as ring_like; -use pki_types::PrivateKeyDer; -use webpki::aws_lc_rs as webpki_algs; - -use crate::crypto::{CryptoProvider, KeyProvider, SecureRandom}; -use crate::enums::SignatureScheme; -use crate::rand::GetRandomFailed; -use crate::sign::SigningKey; -use crate::suites::SupportedCipherSuite; -use crate::webpki::WebPkiSupportedAlgorithms; -use crate::{Error, OtherError}; - -/// Hybrid public key encryption (HPKE). -pub mod hpke; -/// Using software keys for authentication. -pub mod sign; - -#[path = "../ring/hash.rs"] -pub(crate) mod hash; -#[path = "../ring/hmac.rs"] -pub(crate) mod hmac; -#[path = "../ring/kx.rs"] -pub(crate) mod kx; -#[path = "../ring/quic.rs"] -pub(crate) mod quic; -#[cfg(any(feature = "std", feature = "hashbrown"))] -pub(crate) mod ticketer; -#[cfg(feature = "tls12")] -pub(crate) mod tls12; -pub(crate) mod tls13; - -/// A `CryptoProvider` backed by aws-lc-rs. -pub fn default_provider() -> CryptoProvider { - CryptoProvider { - cipher_suites: DEFAULT_CIPHER_SUITES.to_vec(), - kx_groups: default_kx_groups(), - signature_verification_algorithms: SUPPORTED_SIG_ALGS, - secure_random: &AwsLcRs, - key_provider: &AwsLcRs, - } -} - -fn default_kx_groups() -> Vec<&'static dyn SupportedKxGroup> { - #[cfg(feature = "fips")] - { - ALL_KX_GROUPS - .iter() - .filter(|cs| cs.fips()) - .copied() - .collect() - } - #[cfg(not(feature = "fips"))] - { - ALL_KX_GROUPS.to_vec() - } -} - -#[derive(Debug)] -struct AwsLcRs; - -impl SecureRandom for AwsLcRs { - fn fill(&self, buf: &mut [u8]) -> Result<(), GetRandomFailed> { - use ring_like::rand::SecureRandom; - - ring_like::rand::SystemRandom::new() - .fill(buf) - .map_err(|_| GetRandomFailed) - } - - fn fips(&self) -> bool { - fips() - } -} - -impl KeyProvider for AwsLcRs { - fn load_private_key( - &self, - key_der: PrivateKeyDer<'static>, - ) -> Result, Error> { - sign::any_supported_type(&key_der) - } - - fn fips(&self) -> bool { - fips() - } -} - -/// The cipher suite configuration that an application should use by default. -/// -/// This will be [`ALL_CIPHER_SUITES`] sans any supported cipher suites that -/// shouldn't be enabled by most applications. -pub static DEFAULT_CIPHER_SUITES: &[SupportedCipherSuite] = &[ - // TLS1.3 suites - tls13::TLS13_AES_256_GCM_SHA384, - tls13::TLS13_AES_128_GCM_SHA256, - #[cfg(not(feature = "fips"))] - tls13::TLS13_CHACHA20_POLY1305_SHA256, - // TLS1.2 suites - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - #[cfg(all(feature = "tls12", not(feature = "fips")))] - tls12::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - #[cfg(all(feature = "tls12", not(feature = "fips")))] - tls12::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, -]; - -/// A list of all the cipher suites supported by the rustls aws-lc-rs provider. -pub static ALL_CIPHER_SUITES: &[SupportedCipherSuite] = &[ - // TLS1.3 suites - tls13::TLS13_AES_256_GCM_SHA384, - tls13::TLS13_AES_128_GCM_SHA256, - tls13::TLS13_CHACHA20_POLY1305_SHA256, - // TLS1.2 suites - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, -]; - -/// All defined cipher suites supported by aws-lc-rs appear in this module. -pub mod cipher_suite { - #[cfg(feature = "tls12")] - pub use super::tls12::{ - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - }; - pub use super::tls13::{ - TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256, - }; -} - -/// A `WebPkiSupportedAlgorithms` value that reflects webpki's capabilities when -/// compiled against aws-lc-rs. -static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms { - all: &[ - webpki_algs::ECDSA_P256_SHA256, - webpki_algs::ECDSA_P256_SHA384, - webpki_algs::ECDSA_P384_SHA256, - webpki_algs::ECDSA_P384_SHA384, - webpki_algs::ECDSA_P521_SHA256, - webpki_algs::ECDSA_P521_SHA384, - webpki_algs::ECDSA_P521_SHA512, - webpki_algs::ED25519, - webpki_algs::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, - webpki_algs::RSA_PSS_2048_8192_SHA384_LEGACY_KEY, - webpki_algs::RSA_PSS_2048_8192_SHA512_LEGACY_KEY, - webpki_algs::RSA_PKCS1_2048_8192_SHA256, - webpki_algs::RSA_PKCS1_2048_8192_SHA384, - webpki_algs::RSA_PKCS1_2048_8192_SHA512, - webpki_algs::RSA_PKCS1_3072_8192_SHA384, - ], - mapping: &[ - // Note: for TLS1.2 the curve is not fixed by SignatureScheme. For TLS1.3 it is. - ( - SignatureScheme::ECDSA_NISTP384_SHA384, - &[ - webpki_algs::ECDSA_P384_SHA384, - webpki_algs::ECDSA_P256_SHA384, - webpki_algs::ECDSA_P521_SHA384, - ], - ), - ( - SignatureScheme::ECDSA_NISTP256_SHA256, - &[ - webpki_algs::ECDSA_P256_SHA256, - webpki_algs::ECDSA_P384_SHA256, - webpki_algs::ECDSA_P521_SHA256, - ], - ), - ( - SignatureScheme::ECDSA_NISTP521_SHA512, - &[webpki_algs::ECDSA_P521_SHA512], - ), - (SignatureScheme::ED25519, &[webpki_algs::ED25519]), - ( - SignatureScheme::RSA_PSS_SHA512, - &[webpki_algs::RSA_PSS_2048_8192_SHA512_LEGACY_KEY], - ), - ( - SignatureScheme::RSA_PSS_SHA384, - &[webpki_algs::RSA_PSS_2048_8192_SHA384_LEGACY_KEY], - ), - ( - SignatureScheme::RSA_PSS_SHA256, - &[webpki_algs::RSA_PSS_2048_8192_SHA256_LEGACY_KEY], - ), - ( - SignatureScheme::RSA_PKCS1_SHA512, - &[webpki_algs::RSA_PKCS1_2048_8192_SHA512], - ), - ( - SignatureScheme::RSA_PKCS1_SHA384, - &[webpki_algs::RSA_PKCS1_2048_8192_SHA384], - ), - ( - SignatureScheme::RSA_PKCS1_SHA256, - &[webpki_algs::RSA_PKCS1_2048_8192_SHA256], - ), - ], -}; - -/// All defined key exchange groups supported by aws-lc-rs appear in this module. -/// -/// [`ALL_KX_GROUPS`] is provided as an array of all of these values. -pub mod kx_group { - pub use super::kx::{SECP256R1, SECP384R1, X25519}; -} - -pub use kx::ALL_KX_GROUPS; -#[cfg(any(feature = "std", feature = "hashbrown"))] -pub use ticketer::Ticketer; - -use super::SupportedKxGroup; - -/// Compatibility shims between ring 0.16.x and 0.17.x API -mod ring_shim { - use super::ring_like; - use crate::crypto::SharedSecret; - - pub(super) fn agree_ephemeral( - priv_key: ring_like::agreement::EphemeralPrivateKey, - peer_key: &ring_like::agreement::UnparsedPublicKey<&[u8]>, - ) -> Result { - ring_like::agreement::agree_ephemeral(priv_key, peer_key, (), |secret| { - Ok(SharedSecret::from(secret)) - }) - } -} - -/// Are we in FIPS mode? -pub(super) fn fips() -> bool { - aws_lc_rs::try_fips_mode().is_ok() -} - -pub(super) fn unspecified_err(_e: aws_lc_rs::error::Unspecified) -> Error { - #[cfg(feature = "std")] - { - Error::Other(OtherError(Arc::new(_e))) - } - #[cfg(not(feature = "std"))] - { - Error::Other(OtherError()) - } -} - -#[cfg(test)] -mod tests { - #[cfg(feature = "fips")] - #[test] - fn default_suites_are_fips() { - assert!(super::DEFAULT_CIPHER_SUITES - .iter() - .all(|scs| scs.fips())); - } - - #[cfg(not(feature = "fips"))] - #[test] - fn default_suites() { - assert_eq!(super::DEFAULT_CIPHER_SUITES, super::ALL_CIPHER_SUITES); - } -} diff --git a/rustls/src/crypto/aws_lc_rs/ticketer.rs b/rustls/src/crypto/aws_lc_rs/ticketer.rs deleted file mode 100644 index dfed6b743be..00000000000 --- a/rustls/src/crypto/aws_lc_rs/ticketer.rs +++ /dev/null @@ -1,415 +0,0 @@ -use alloc::boxed::Box; -use alloc::sync::Arc; -use alloc::vec::Vec; -use core::fmt; -use core::fmt::{Debug, Formatter}; -use core::sync::atomic::{AtomicUsize, Ordering}; - -use aws_lc_rs::cipher::{ - DecryptionContext, PaddedBlockDecryptingKey, PaddedBlockEncryptingKey, UnboundCipherKey, - AES_256, AES_256_KEY_LEN, AES_CBC_IV_LEN, -}; -use aws_lc_rs::{hmac, iv}; - -use super::ring_like::rand::{SecureRandom, SystemRandom}; -use super::unspecified_err; -use crate::error::Error; -#[cfg(debug_assertions)] -use crate::log::debug; -use crate::polyfill::try_split_at; -use crate::rand::GetRandomFailed; -use crate::server::ProducesTickets; - -/// A concrete, safe ticket creation mechanism. -pub struct Ticketer {} - -impl Ticketer { - /// Make the recommended `Ticketer`. This produces tickets - /// with a 12 hour life and randomly generated keys. - /// - /// The `Ticketer` uses the [RFC 5077 §4] "Recommended Ticket Construction", - /// using AES 256 for encryption and HMAC-SHA256 for ciphertext authentication. - /// - /// [RFC 5077 §4]: https://www.rfc-editor.org/rfc/rfc5077#section-4 - #[cfg(feature = "std")] - pub fn new() -> Result, Error> { - Ok(Arc::new(crate::ticketer::TicketRotator::new( - 6 * 60 * 60, - make_ticket_generator, - )?)) - } - - /// Make the recommended `Ticketer`. This produces tickets - /// with a 12 hour life and randomly generated keys. - /// - /// The `Ticketer` uses the [RFC 5077 §4] "Recommended Ticket Construction", - /// using AES 256 for encryption and HMAC-SHA256 for ciphertext authentication. - /// - /// [RFC 5077 §4]: https://www.rfc-editor.org/rfc/rfc5077#section-4 - #[cfg(not(feature = "std"))] - pub fn new( - time_provider: &'static dyn TimeProvider, - ) -> Result, Error> { - Ok(Arc::new(crate::ticketer::TicketSwitcher::new::( - 6 * 60 * 60, - make_ticket_generator, - time_provider, - )?)) - } -} - -fn make_ticket_generator() -> Result, GetRandomFailed> { - // NOTE(XXX): Unconditionally mapping errors to `GetRandomFailed` here is slightly - // misleading in some cases (e.g. failure to construct a padded block cipher encrypting key). - // However, we can't change the return type expected from a `TicketSwitcher` `generator` - // without breaking semver. - // Tracking in https://github.com/rustls/rustls/issues/2074 - Ok(Box::new( - Rfc5077Ticketer::new().map_err(|_| GetRandomFailed)?, - )) -} - -/// An RFC 5077 "Recommended Ticket Construction" implementation of a [`Ticketer`]. -struct Rfc5077Ticketer { - aes_encrypt_key: PaddedBlockEncryptingKey, - aes_decrypt_key: PaddedBlockDecryptingKey, - hmac_key: hmac::Key, - key_name: [u8; 16], - lifetime: u32, - maximum_ciphertext_len: AtomicUsize, -} - -impl Rfc5077Ticketer { - fn new() -> Result { - let rand = SystemRandom::new(); - - // Generate a random AES 256 key to use for AES CBC encryption. - let mut aes_key = [0u8; AES_256_KEY_LEN]; - rand.fill(&mut aes_key) - .map_err(|_| GetRandomFailed)?; - - // Convert the raw AES 256 key bytes into encrypting and decrypting keys using CBC mode and - // PKCS#7 padding. We don't want to store just the raw key bytes as constructing the - // cipher keys has some setup overhead. We can't store just the `UnboundCipherKey` since - // constructing the padded encrypt/decrypt specific types consume the `UnboundCipherKey`. - let aes_encrypt_key = - UnboundCipherKey::new(&AES_256, &aes_key[..]).map_err(unspecified_err)?; - let aes_encrypt_key = - PaddedBlockEncryptingKey::cbc_pkcs7(aes_encrypt_key).map_err(unspecified_err)?; - - // Convert the raw AES 256 key bytes into a decrypting key using CBC PKCS#7 padding. - let aes_decrypt_key = - UnboundCipherKey::new(&AES_256, &aes_key[..]).map_err(unspecified_err)?; - let aes_decrypt_key = - PaddedBlockDecryptingKey::cbc_pkcs7(aes_decrypt_key).map_err(unspecified_err)?; - - // Generate a random HMAC SHA256 key to use for HMAC authentication. - let hmac_key = hmac::Key::generate(hmac::HMAC_SHA256, &rand).map_err(unspecified_err)?; - - // Generate a random key name. - let mut key_name = [0u8; 16]; - rand.fill(&mut key_name) - .map_err(|_| GetRandomFailed)?; - - Ok(Self { - aes_encrypt_key, - aes_decrypt_key, - hmac_key, - key_name, - lifetime: 60 * 60 * 12, - maximum_ciphertext_len: AtomicUsize::new(0), - }) - } -} - -impl ProducesTickets for Rfc5077Ticketer { - fn enabled(&self) -> bool { - true - } - - fn lifetime(&self) -> u32 { - self.lifetime - } - - /// Encrypt `message` and return the ciphertext. - fn encrypt(&self, message: &[u8]) -> Option> { - // Encrypt the ticket state - the cipher module handles generating a random IV of - // appropriate size, returning it in the `DecryptionContext`. - let mut encrypted_state = Vec::from(message); - let dec_ctx = self - .aes_encrypt_key - .encrypt(&mut encrypted_state) - .ok()?; - let iv: &[u8] = (&dec_ctx).try_into().ok()?; - - // Produce the MAC tag over the relevant context & encrypted state. - // Quoting RFC 5077: - // "The Message Authentication Code (MAC) is calculated using HMAC-SHA-256 over - // key_name (16 octets) and IV (16 octets), followed by the length of - // the encrypted_state field (2 octets) and its contents (variable - // length)." - let mut hmac_data = - Vec::with_capacity(self.key_name.len() + iv.len() + 2 + encrypted_state.len()); - hmac_data.extend(&self.key_name); - hmac_data.extend(iv); - hmac_data.extend( - u16::try_from(encrypted_state.len()) - .ok()? - .to_be_bytes(), - ); - hmac_data.extend(&encrypted_state); - let tag = hmac::sign(&self.hmac_key, &hmac_data); - let tag = tag.as_ref(); - - // Combine the context, the encrypted state, and the tag to produce the final ciphertext. - // Ciphertext structure is: - // key_name: [u8; 16] - // iv: [u8; 16] - // encrypted_state: [u8, _] - // mac tag: [u8; 32] - let mut ciphertext = - Vec::with_capacity(self.key_name.len() + iv.len() + encrypted_state.len() + tag.len()); - ciphertext.extend(self.key_name); - ciphertext.extend(iv); - ciphertext.extend(encrypted_state); - ciphertext.extend(tag); - - self.maximum_ciphertext_len - .fetch_max(ciphertext.len(), Ordering::SeqCst); - - Some(ciphertext) - } - - fn decrypt(&self, ciphertext: &[u8]) -> Option> { - if ciphertext.len() - > self - .maximum_ciphertext_len - .load(Ordering::SeqCst) - { - #[cfg(debug_assertions)] - debug!("rejected over-length ticket"); - return None; - } - - // Split off the key name from the remaining ciphertext. - let (alleged_key_name, ciphertext) = try_split_at(ciphertext, self.key_name.len())?; - - // Split off the IV from the remaining ciphertext. - let (iv, ciphertext) = try_split_at(ciphertext, AES_CBC_IV_LEN)?; - - // And finally, split the encrypted state from the tag. - let tag_len = self - .hmac_key - .algorithm() - .digest_algorithm() - .output_len(); - let (enc_state, mac) = try_split_at(ciphertext, ciphertext.len() - tag_len)?; - - // Reconstitute the HMAC data to verify the tag. - let mut hmac_data = - Vec::with_capacity(alleged_key_name.len() + iv.len() + 2 + enc_state.len()); - hmac_data.extend(alleged_key_name); - hmac_data.extend(iv); - hmac_data.extend( - u16::try_from(enc_state.len()) - .ok()? - .to_be_bytes(), - ); - hmac_data.extend(enc_state); - hmac::verify(&self.hmac_key, &hmac_data, mac).ok()?; - - // Convert the raw IV back into an appropriate decryption context. - let iv = iv::FixedLength::try_from(iv).ok()?; - let dec_context = DecryptionContext::Iv128(iv); - - // And finally, decrypt the encrypted state. - let mut out = Vec::from(enc_state); - let plaintext = self - .aes_decrypt_key - .decrypt(&mut out, dec_context) - .ok()?; - - Some(plaintext.into()) - } -} - -impl Debug for Rfc5077Ticketer { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // Note: we deliberately omit keys from the debug output. - f.debug_struct("Rfc5077Ticketer") - .field("lifetime", &self.lifetime) - .finish() - } -} - -#[cfg(test)] -mod tests { - use core::time::Duration; - - use pki_types::UnixTime; - - use super::*; - - #[test] - fn basic_pairwise_test() { - let t = Ticketer::new().unwrap(); - assert!(t.enabled()); - let cipher = t.encrypt(b"hello world").unwrap(); - let plain = t.decrypt(&cipher).unwrap(); - assert_eq!(plain, b"hello world"); - } - - #[test] - fn refuses_decrypt_before_encrypt() { - let t = Ticketer::new().unwrap(); - assert_eq!(t.decrypt(b"hello"), None); - } - - #[test] - fn refuses_decrypt_larger_than_largest_encryption() { - let t = Ticketer::new().unwrap(); - let mut cipher = t.encrypt(b"hello world").unwrap(); - assert_eq!(t.decrypt(&cipher), Some(b"hello world".to_vec())); - - // obviously this would never work anyway, but this - // and `cannot_decrypt_before_encrypt` exercise the - // first branch in `decrypt()` - cipher.push(0); - assert_eq!(t.decrypt(&cipher), None); - } - - #[test] - fn ticketrotator_switching_test() { - let t = Arc::new(crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap()); - let now = UnixTime::now(); - let cipher1 = t.encrypt(b"ticket 1").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - { - // Trigger new ticketer - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 10, - ))); - } - let cipher2 = t.encrypt(b"ticket 2").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - { - // Trigger new ticketer - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 20, - ))); - } - let cipher3 = t.encrypt(b"ticket 3").unwrap(); - assert!(t.decrypt(&cipher1).is_none()); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3"); - } - - #[test] - fn ticketrotator_remains_usable_over_temporary_ticketer_creation_failure() { - let mut t = crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap(); - let now = UnixTime::now(); - let cipher1 = t.encrypt(b"ticket 1").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - t.generator = fail_generator; - { - // Failed new ticketer; this means we still need to - // rotate. - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 10, - ))); - } - - // check post-failure encryption/decryption still works - let cipher2 = t.encrypt(b"ticket 2").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - - // do the rotation for real - t.generator = make_ticket_generator; - { - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 20, - ))); - } - let cipher3 = t.encrypt(b"ticket 3").unwrap(); - assert!(t.decrypt(&cipher1).is_some()); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3"); - } - - #[test] - fn ticketswitcher_switching_test() { - #[expect(deprecated)] - let t = Arc::new(crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap()); - let now = UnixTime::now(); - let cipher1 = t.encrypt(b"ticket 1").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - { - // Trigger new ticketer - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 10, - ))); - } - let cipher2 = t.encrypt(b"ticket 2").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - { - // Trigger new ticketer - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 20, - ))); - } - let cipher3 = t.encrypt(b"ticket 3").unwrap(); - assert!(t.decrypt(&cipher1).is_none()); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3"); - } - - #[test] - fn ticketswitcher_recover_test() { - #[expect(deprecated)] - let mut t = crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap(); - let now = UnixTime::now(); - let cipher1 = t.encrypt(b"ticket 1").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - t.generator = fail_generator; - { - // Failed new ticketer - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 10, - ))); - } - t.generator = make_ticket_generator; - let cipher2 = t.encrypt(b"ticket 2").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - { - // recover - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 20, - ))); - } - let cipher3 = t.encrypt(b"ticket 3").unwrap(); - assert!(t.decrypt(&cipher1).is_none()); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3"); - } - - #[test] - fn rfc5077ticketer_is_debug_and_producestickets() { - use alloc::format; - - use super::*; - - let t = make_ticket_generator().unwrap(); - - assert_eq!(format!("{:?}", t), "Rfc5077Ticketer { lifetime: 43200 }"); - assert!(t.enabled()); - assert_eq!(t.lifetime(), 43200); - } - - fn fail_generator() -> Result, GetRandomFailed> { - Err(GetRandomFailed) - } -} diff --git a/rustls/src/crypto/cipher.rs b/rustls/src/crypto/cipher.rs deleted file mode 100644 index 64901a577bf..00000000000 --- a/rustls/src/crypto/cipher.rs +++ /dev/null @@ -1,350 +0,0 @@ -use alloc::boxed::Box; -use alloc::string::ToString; -use core::fmt; - -use zeroize::Zeroize; - -use crate::enums::{ContentType, ProtocolVersion}; -use crate::error::Error; -use crate::msgs::codec; -pub use crate::msgs::message::{ - BorrowedPayload, InboundOpaqueMessage, InboundPlainMessage, OutboundChunks, - OutboundOpaqueMessage, OutboundPlainMessage, PlainMessage, PrefixedPayload, -}; -use crate::suites::ConnectionTrafficSecrets; - -/// Factory trait for building `MessageEncrypter` and `MessageDecrypter` for a TLS1.3 cipher suite. -pub trait Tls13AeadAlgorithm: Send + Sync { - /// Build a `MessageEncrypter` for the given key/iv. - fn encrypter(&self, key: AeadKey, iv: Iv) -> Box; - - /// Build a `MessageDecrypter` for the given key/iv. - fn decrypter(&self, key: AeadKey, iv: Iv) -> Box; - - /// The length of key in bytes required by `encrypter()` and `decrypter()`. - fn key_len(&self) -> usize; - - /// Convert the key material from `key`/`iv`, into a `ConnectionTrafficSecrets` item. - /// - /// May return [`UnsupportedOperationError`] if the AEAD algorithm is not a supported - /// variant of `ConnectionTrafficSecrets`. - fn extract_keys( - &self, - key: AeadKey, - iv: Iv, - ) -> Result; - - /// Return `true` if this is backed by a FIPS-approved implementation. - fn fips(&self) -> bool { - false - } -} - -/// Factory trait for building `MessageEncrypter` and `MessageDecrypter` for a TLS1.2 cipher suite. -pub trait Tls12AeadAlgorithm: Send + Sync + 'static { - /// Build a `MessageEncrypter` for the given key/iv and extra key block (which can be used for - /// improving explicit nonce size security, if needed). - /// - /// The length of `key` is set by [`KeyBlockShape::enc_key_len`]. - /// - /// The length of `iv` is set by [`KeyBlockShape::fixed_iv_len`]. - /// - /// The length of `extra` is set by [`KeyBlockShape::explicit_nonce_len`]. - fn encrypter(&self, key: AeadKey, iv: &[u8], extra: &[u8]) -> Box; - - /// Build a `MessageDecrypter` for the given key/iv. - /// - /// The length of `key` is set by [`KeyBlockShape::enc_key_len`]. - /// - /// The length of `iv` is set by [`KeyBlockShape::fixed_iv_len`]. - fn decrypter(&self, key: AeadKey, iv: &[u8]) -> Box; - - /// Return a `KeyBlockShape` that defines how large the `key_block` is and how it - /// is split up prior to calling `encrypter()`, `decrypter()` and/or `extract_keys()`. - fn key_block_shape(&self) -> KeyBlockShape; - - /// Convert the key material from `key`/`iv`, into a `ConnectionTrafficSecrets` item. - /// - /// The length of `key` is set by [`KeyBlockShape::enc_key_len`]. - /// - /// The length of `iv` is set by [`KeyBlockShape::fixed_iv_len`]. - /// - /// The length of `extra` is set by [`KeyBlockShape::explicit_nonce_len`]. - /// - /// May return [`UnsupportedOperationError`] if the AEAD algorithm is not a supported - /// variant of `ConnectionTrafficSecrets`. - fn extract_keys( - &self, - key: AeadKey, - iv: &[u8], - explicit: &[u8], - ) -> Result; - - /// Return `true` if this is backed by a FIPS-approved implementation. - fn fips(&self) -> bool { - false - } -} - -/// An error indicating that the AEAD algorithm does not support the requested operation. -#[derive(Debug, Eq, PartialEq, Clone, Copy)] -pub struct UnsupportedOperationError; - -impl From for Error { - fn from(value: UnsupportedOperationError) -> Self { - Self::General(value.to_string()) - } -} - -impl fmt::Display for UnsupportedOperationError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "operation not supported") - } -} - -#[cfg(feature = "std")] -impl std::error::Error for UnsupportedOperationError {} - -/// How a TLS1.2 `key_block` is partitioned. -/// -/// Note: ciphersuites with non-zero `mac_key_length` are not currently supported. -pub struct KeyBlockShape { - /// How long keys are. - /// - /// `enc_key_length` terminology is from the standard ([RFC5246 A.6]). - /// - /// [RFC5246 A.6]: - pub enc_key_len: usize, - - /// How long the fixed part of the 'IV' is. - /// - /// `fixed_iv_length` terminology is from the standard ([RFC5246 A.6]). - /// - /// This isn't usually an IV, but we continue the - /// terminology misuse to match the standard. - /// - /// [RFC5246 A.6]: - pub fixed_iv_len: usize, - - /// This is a non-standard extension which extends the - /// key block to provide an initial explicit nonce offset, - /// in a deterministic and safe way. GCM needs this, - /// chacha20poly1305 works this way by design. - pub explicit_nonce_len: usize, -} - -/// Objects with this trait can decrypt TLS messages. -pub trait MessageDecrypter: Send + Sync { - /// Decrypt the given TLS message `msg`, using the sequence number - /// `seq` which can be used to derive a unique [`Nonce`]. - fn decrypt<'a>( - &mut self, - msg: InboundOpaqueMessage<'a>, - seq: u64, - ) -> Result, Error>; -} - -/// Objects with this trait can encrypt TLS messages. -pub trait MessageEncrypter: Send + Sync { - /// Encrypt the given TLS message `msg`, using the sequence number - /// `seq` which can be used to derive a unique [`Nonce`]. - fn encrypt( - &mut self, - msg: OutboundPlainMessage<'_>, - seq: u64, - ) -> Result; - - /// Return the length of the ciphertext that results from encrypting plaintext of - /// length `payload_len` - fn encrypted_payload_len(&self, payload_len: usize) -> usize; -} - -impl dyn MessageEncrypter { - pub(crate) fn invalid() -> Box { - Box::new(InvalidMessageEncrypter {}) - } -} - -impl dyn MessageDecrypter { - pub(crate) fn invalid() -> Box { - Box::new(InvalidMessageDecrypter {}) - } -} - -/// A write or read IV. -#[derive(Default)] -pub struct Iv([u8; NONCE_LEN]); - -impl Iv { - /// Create a new `Iv` from a byte array, of precisely `NONCE_LEN` bytes. - #[cfg(feature = "tls12")] - pub fn new(value: [u8; NONCE_LEN]) -> Self { - Self(value) - } - - /// Create a new `Iv` from a byte slice, of precisely `NONCE_LEN` bytes. - #[cfg(feature = "tls12")] - pub fn copy(value: &[u8]) -> Self { - debug_assert_eq!(value.len(), NONCE_LEN); - let mut iv = Self::new(Default::default()); - iv.0.copy_from_slice(value); - iv - } -} - -impl From<[u8; NONCE_LEN]> for Iv { - fn from(bytes: [u8; NONCE_LEN]) -> Self { - Self(bytes) - } -} - -impl AsRef<[u8]> for Iv { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -/// A nonce. This is unique for all messages on a connection. -pub struct Nonce(pub [u8; NONCE_LEN]); - -impl Nonce { - /// Combine an `Iv` and sequence number to produce a unique nonce. - /// - /// This is `iv ^ seq` where `seq` is encoded as a 96-bit big-endian integer. - #[inline] - pub fn new(iv: &Iv, seq: u64) -> Self { - let mut nonce = Self([0u8; NONCE_LEN]); - codec::put_u64(seq, &mut nonce.0[4..]); - - nonce - .0 - .iter_mut() - .zip(iv.0.iter()) - .for_each(|(nonce, iv)| { - *nonce ^= *iv; - }); - - nonce - } -} - -/// Size of TLS nonces (incorrectly termed "IV" in standard) for all supported ciphersuites -/// (AES-GCM, Chacha20Poly1305) -pub const NONCE_LEN: usize = 12; - -/// Returns a TLS1.3 `additional_data` encoding. -/// -/// See RFC8446 s5.2 for the `additional_data` definition. -#[inline] -pub fn make_tls13_aad(payload_len: usize) -> [u8; 5] { - let version = ProtocolVersion::TLSv1_2.to_array(); - [ - ContentType::ApplicationData.into(), - // Note: this is `legacy_record_version`, i.e. TLS1.2 even for TLS1.3. - version[0], - version[1], - (payload_len >> 8) as u8, - (payload_len & 0xff) as u8, - ] -} - -/// Returns a TLS1.2 `additional_data` encoding. -/// -/// See RFC5246 s6.2.3.3 for the `additional_data` definition. -#[inline] -pub fn make_tls12_aad( - seq: u64, - typ: ContentType, - vers: ProtocolVersion, - len: usize, -) -> [u8; TLS12_AAD_SIZE] { - let mut out = [0; TLS12_AAD_SIZE]; - codec::put_u64(seq, &mut out[0..]); - out[8] = typ.into(); - codec::put_u16(vers.into(), &mut out[9..]); - codec::put_u16(len as u16, &mut out[11..]); - out -} - -const TLS12_AAD_SIZE: usize = 8 + 1 + 2 + 2; - -/// A key for an AEAD algorithm. -/// -/// This is a value type for a byte string up to `AeadKey::MAX_LEN` bytes in length. -pub struct AeadKey { - buf: [u8; Self::MAX_LEN], - used: usize, -} - -impl AeadKey { - #[cfg(feature = "tls12")] - pub(crate) fn new(buf: &[u8]) -> Self { - debug_assert!(buf.len() <= Self::MAX_LEN); - let mut key = Self::from([0u8; Self::MAX_LEN]); - key.buf[..buf.len()].copy_from_slice(buf); - key.used = buf.len(); - key - } - - pub(crate) fn with_length(self, len: usize) -> Self { - assert!(len <= self.used); - Self { - buf: self.buf, - used: len, - } - } - - /// Largest possible AEAD key in the ciphersuites we support. - pub(crate) const MAX_LEN: usize = 32; -} - -impl Drop for AeadKey { - fn drop(&mut self) { - self.buf.zeroize(); - } -} - -impl AsRef<[u8]> for AeadKey { - fn as_ref(&self) -> &[u8] { - &self.buf[..self.used] - } -} - -impl From<[u8; Self::MAX_LEN]> for AeadKey { - fn from(bytes: [u8; Self::MAX_LEN]) -> Self { - Self { - buf: bytes, - used: Self::MAX_LEN, - } - } -} - -/// A `MessageEncrypter` which doesn't work. -struct InvalidMessageEncrypter {} - -impl MessageEncrypter for InvalidMessageEncrypter { - fn encrypt( - &mut self, - _m: OutboundPlainMessage<'_>, - _seq: u64, - ) -> Result { - Err(Error::EncryptError) - } - - fn encrypted_payload_len(&self, payload_len: usize) -> usize { - payload_len - } -} - -/// A `MessageDecrypter` which doesn't work. -struct InvalidMessageDecrypter {} - -impl MessageDecrypter for InvalidMessageDecrypter { - fn decrypt<'a>( - &mut self, - _m: InboundOpaqueMessage<'a>, - _seq: u64, - ) -> Result, Error> { - Err(Error::DecryptError) - } -} diff --git a/rustls/src/crypto/cipher/messages.rs b/rustls/src/crypto/cipher/messages.rs new file mode 100644 index 00000000000..b470b4c6e84 --- /dev/null +++ b/rustls/src/crypto/cipher/messages.rs @@ -0,0 +1,618 @@ +use alloc::vec::Vec; +use core::fmt; +use core::ops::{Deref, DerefMut, Range}; + +use crate::crypto::cipher::EncryptionState; +use crate::enums::{ContentType, ProtocolVersion}; +use crate::error::{Error, InvalidMessage, PeerMisbehaved}; +use crate::msgs::{Codec, HEADER_SIZE, MAX_FRAGMENT_LEN, Reader, hex, read_opaque_message_header}; + +/// A TLS message with encoded (but not necessarily encrypted) payload. +#[expect(clippy::exhaustive_structs)] +#[derive(Clone, Debug)] +pub struct EncodedMessage

{ + /// The content type of this message. + pub typ: ContentType, + /// The protocol version of this message. + pub version: ProtocolVersion, + /// The payload of this message. + pub payload: P, +} + +impl

EncodedMessage

{ + /// Create a new `EncodedMessage` with the given fields. + pub fn new(typ: ContentType, version: ProtocolVersion, payload: P) -> Self { + Self { + typ, + version, + payload, + } + } +} + +impl<'a> EncodedMessage> { + /// Construct by decoding from a [`Reader`]. + /// + /// `MessageError` allows callers to distinguish between valid prefixes (might + /// become valid if we read more data) and invalid data. + pub(crate) fn read(r: &mut Reader<'a>) -> Result { + let (typ, version, len) = read_opaque_message_header(r)?; + + let content = r + .take(len as usize) + .ok_or(MessageError::TooShortForLength)?; + + Ok(Self { + typ, + version, + payload: Payload::Borrowed(content), + }) + } + + /// Convert into an unencrypted [`EncodedMessage`] (without decrypting). + pub fn into_unencrypted_opaque(self) -> EncodedMessage { + EncodedMessage { + typ: self.typ, + version: self.version, + payload: OutboundOpaque::from(self.payload.bytes()), + } + } + + /// Borrow as an [`EncodedMessage>`]. + pub fn borrow_outbound(&'a self) -> EncodedMessage> { + EncodedMessage { + typ: self.typ, + version: self.version, + payload: self.payload.bytes().into(), + } + } + + /// Convert into an owned `EncodedMessage>`. + pub fn into_owned(self) -> Self { + Self { + typ: self.typ, + version: self.version, + payload: self.payload.into_owned(), + } + } +} + +impl EncodedMessage<&'_ [u8]> { + /// Returns true if the payload is a CCS message. + /// + /// We passthrough ChangeCipherSpec messages in the deframer without decrypting them. + /// Note: this is prior to the record layer, so is unencrypted. See + /// third paragraph of section 5 in RFC8446. + pub(crate) fn is_valid_ccs(&self) -> bool { + self.typ == ContentType::ChangeCipherSpec && self.payload == [0x01] + } +} + +impl<'a> EncodedMessage> { + /// For TLS1.3 (only), checks the length msg.payload is valid and removes the padding. + /// + /// Returns an error if the message (pre-unpadding) is too long, or the padding is invalid, + /// or the message (post-unpadding) is too long. + pub fn into_tls13_unpadded_message(mut self) -> Result, Error> { + let payload = &mut self.payload; + + if payload.len() > MAX_FRAGMENT_LEN + 1 { + return Err(Error::PeerSentOversizedRecord); + } + + self.typ = unpad_tls13_payload(payload); + if self.typ == ContentType(0) { + return Err(PeerMisbehaved::IllegalTlsInnerPlaintext.into()); + } + + if payload.len() > MAX_FRAGMENT_LEN { + return Err(Error::PeerSentOversizedRecord); + } + + self.version = ProtocolVersion::TLSv1_3; + Ok(self.into_plain_message()) + } + + /// Force conversion into a plaintext message. + /// + /// `range` restricts the resulting message: this function panics if it is out of range for + /// the underlying message payload. + /// + /// This should only be used for messages that are known to be in plaintext. Otherwise, the + /// [`EncodedMessage>`] should be decrypted into an + /// `EncodedMessage<&'_ [u8]>` using a `MessageDecrypter`. + pub fn into_plain_message_range(self, range: Range) -> EncodedMessage<&'a [u8]> { + EncodedMessage { + typ: self.typ, + version: self.version, + payload: &self.payload.into_inner()[range], + } + } + + /// Force conversion into a plaintext message. + /// + /// This should only be used for messages that are known to be in plaintext. Otherwise, the + /// [`EncodedMessage>`] should be decrypted into a + /// `EncodedMessage<&'a [u8]>` using a `MessageDecrypter`. + pub fn into_plain_message(self) -> EncodedMessage<&'a [u8]> { + EncodedMessage { + typ: self.typ, + version: self.version, + payload: self.payload.into_inner(), + } + } +} + +impl EncodedMessage> { + pub(crate) fn to_unencrypted_opaque(&self) -> EncodedMessage { + let mut payload = OutboundOpaque::with_capacity(self.payload.len()); + payload.extend_from_chunks(&self.payload); + EncodedMessage { + typ: self.typ, + version: self.version, + payload, + } + } + + #[expect(dead_code)] + pub(crate) fn encoded_len(&self, record_layer: &EncryptionState) -> usize { + HEADER_SIZE + record_layer.encrypted_len(self.payload.len()) + } +} + +impl EncodedMessage { + /// Encode this message to a vector of bytes. + pub fn encode(self) -> Vec { + let length = self.payload.len() as u16; + let mut encoded_payload = self.payload.0; + encoded_payload[0] = self.typ.into(); + encoded_payload[1..3].copy_from_slice(&self.version.to_array()); + encoded_payload[3..5].copy_from_slice(&(length).to_be_bytes()); + encoded_payload + } +} + +/// A collection of borrowed plaintext slices. +/// +/// Warning: OutboundChunks does not guarantee that the simplest variant is used. +/// Multiple can hold non fragmented or empty payloads. +#[non_exhaustive] +#[derive(Debug, Clone)] +pub enum OutboundPlain<'a> { + /// A single byte slice. + /// + /// Contrary to `Multiple`, this uses a single pointer indirection + Single(&'a [u8]), + /// A collection of chunks (byte slices). + Multiple { + /// A collection of byte slices that hold the buffered data. + chunks: &'a [&'a [u8]], + /// The start cursor into the first chunk. + start: usize, + /// The end cursor into the last chunk. + end: usize, + }, +} + +impl<'a> OutboundPlain<'a> { + /// Create a payload from a slice of byte slices. + /// If fragmented the cursors are added by default: start = 0, end = length + pub fn new(chunks: &'a [&'a [u8]]) -> Self { + if chunks.len() == 1 { + Self::Single(chunks[0]) + } else { + Self::Multiple { + chunks, + start: 0, + end: chunks + .iter() + .map(|chunk| chunk.len()) + .sum(), + } + } + } + + /// Create a payload with a single empty slice + pub fn new_empty() -> Self { + Self::Single(&[]) + } + + /// Flatten the slice of byte slices to an owned vector of bytes + pub fn to_vec(&self) -> Vec { + let mut vec = Vec::with_capacity(self.len()); + self.copy_to_vec(&mut vec); + vec + } + + /// Append all bytes to a vector + pub fn copy_to_vec(&self, vec: &mut Vec) { + match *self { + Self::Single(chunk) => vec.extend_from_slice(chunk), + Self::Multiple { chunks, start, end } => { + let mut size = 0; + for chunk in chunks.iter() { + let psize = size; + let len = chunk.len(); + size += len; + if size <= start || psize >= end { + continue; + } + let start = start.saturating_sub(psize); + let end = if end - psize < len { end - psize } else { len }; + vec.extend_from_slice(&chunk[start..end]); + } + } + } + } + + /// Split self in two, around an index + /// Works similarly to `split_at` in the core library, except it doesn't panic if out of bound + pub(crate) fn split_at(&self, mid: usize) -> (Self, Self) { + match *self { + Self::Single(chunk) => { + let mid = Ord::min(mid, chunk.len()); + (Self::Single(&chunk[..mid]), Self::Single(&chunk[mid..])) + } + Self::Multiple { chunks, start, end } => { + let mid = Ord::min(start + mid, end); + ( + Self::Multiple { + chunks, + start, + end: mid, + }, + Self::Multiple { + chunks, + start: mid, + end, + }, + ) + } + } + } + + /// Returns true if the payload is empty + pub(crate) fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns the cumulative length of all chunks + #[expect(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + match self { + Self::Single(chunk) => chunk.len(), + Self::Multiple { start, end, .. } => end - start, + } + } +} + +impl<'a> From<&'a [u8]> for OutboundPlain<'a> { + fn from(payload: &'a [u8]) -> Self { + Self::Single(payload) + } +} + +/// A payload buffer with space reserved at the front for a TLS message header. +/// +/// `EncodedMessage` is named `TLSPlaintext` in the standard. +/// +/// This outbound type owns all memory for its interior parts. +/// It results from encryption and is used for io write. +#[derive(Clone, Debug)] +pub struct OutboundOpaque(Vec); + +impl OutboundOpaque { + /// Create a new value with the given payload capacity. + /// + /// (The actual capacity of the returned value will be at least `HEADER_SIZE + capacity`.) + pub fn with_capacity(capacity: usize) -> Self { + let mut prefixed_payload = Vec::with_capacity(HEADER_SIZE + capacity); + prefixed_payload.resize(HEADER_SIZE, 0); + Self(prefixed_payload) + } + + /// Append bytes from a slice. + pub fn extend_from_slice(&mut self, slice: &[u8]) { + self.0.extend_from_slice(slice) + } + + /// Append bytes from an `OutboundChunks`. + pub fn extend_from_chunks(&mut self, chunks: &OutboundPlain<'_>) { + chunks.copy_to_vec(&mut self.0) + } + + /// Truncate the payload to the given length (plus header). + pub fn truncate(&mut self, len: usize) { + self.0.truncate(len + HEADER_SIZE) + } + + fn len(&self) -> usize { + self.0.len() - HEADER_SIZE + } +} + +impl AsRef<[u8]> for OutboundOpaque { + fn as_ref(&self) -> &[u8] { + &self.0[HEADER_SIZE..] + } +} + +impl AsMut<[u8]> for OutboundOpaque { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0[HEADER_SIZE..] + } +} + +impl<'a> Extend<&'a u8> for OutboundOpaque { + fn extend>(&mut self, iter: T) { + self.0.extend(iter) + } +} + +impl From<&[u8]> for OutboundOpaque { + fn from(content: &[u8]) -> Self { + let mut payload = Vec::with_capacity(HEADER_SIZE + content.len()); + payload.extend(&[0u8; HEADER_SIZE]); + payload.extend(content); + Self(payload) + } +} + +impl From<&[u8; N]> for OutboundOpaque { + fn from(content: &[u8; N]) -> Self { + Self::from(&content[..]) + } +} + +/// An externally length'd payload +/// +/// When encountered in an [`EncodedMessage`], it represents a plaintext payload. It can be +/// decrypted from an [`InboundOpaque`] or encrypted into an [`OutboundOpaque`], +/// and it is also used for joining and fragmenting. +#[non_exhaustive] +#[derive(Clone, Eq, PartialEq)] +pub enum Payload<'a> { + /// Borrowed payload + Borrowed(&'a [u8]), + /// Owned payload + Owned(Vec), +} + +impl<'a> Payload<'a> { + /// A reference to the payload's bytes + pub fn bytes(&'a self) -> &'a [u8] { + match self { + Self::Borrowed(bytes) => bytes, + Self::Owned(bytes) => bytes, + } + } + + pub(crate) fn into_owned(self) -> Payload<'static> { + Payload::Owned(self.into_vec()) + } + + pub(crate) fn into_vec(self) -> Vec { + match self { + Self::Borrowed(bytes) => bytes.to_vec(), + Self::Owned(bytes) => bytes, + } + } + + pub(crate) fn read(r: &mut Reader<'a>) -> Self { + Self::Borrowed(r.rest()) + } +} + +impl Payload<'static> { + /// Create a new owned payload from the given `bytes`. + pub fn new(bytes: impl Into>) -> Self { + Self::Owned(bytes.into()) + } +} + +impl<'a> Codec<'a> for Payload<'a> { + fn encode(&self, bytes: &mut Vec) { + bytes.extend_from_slice(self.bytes()); + } + + fn read(r: &mut Reader<'a>) -> Result { + Ok(Self::read(r)) + } +} + +impl fmt::Debug for Payload<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + hex(f, self.bytes()) + } +} + +/// A borrowed payload buffer. +#[expect(clippy::exhaustive_structs)] +pub struct InboundOpaque<'a>(pub &'a mut [u8]); + +impl<'a> InboundOpaque<'a> { + /// Truncate the payload to `len` bytes. + pub fn truncate(&mut self, len: usize) { + if len >= self.len() { + return; + } + + self.0 = core::mem::take(&mut self.0) + .split_at_mut(len) + .0; + } + + pub(crate) fn into_inner(self) -> &'a mut [u8] { + self.0 + } + + pub(crate) fn pop(&mut self) -> Option { + if self.is_empty() { + return None; + } + + let len = self.len(); + let last = self[len - 1]; + self.truncate(len - 1); + Some(last) + } +} + +impl Deref for InboundOpaque<'_> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl DerefMut for InboundOpaque<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0 + } +} + +/// Decode a TLS1.3 `TLSInnerPlaintext` encoding. +/// +/// `p` is a message payload, immediately post-decryption. This function +/// removes zero padding bytes, until a non-zero byte is encountered which is +/// the content type, which is returned. See RFC8446 s5.2. +/// +/// ContentType(0) is returned if the message payload is empty or all zeroes. +fn unpad_tls13_payload(p: &mut InboundOpaque<'_>) -> ContentType { + loop { + match p.pop() { + Some(0) => {} + Some(content_type) => return ContentType::from(content_type), + None => return ContentType(0), + } + } +} + +/// Errors from trying to parse a TLS message. +#[expect(missing_docs)] +#[non_exhaustive] +#[derive(Debug)] +pub enum MessageError { + TooShortForHeader, + TooShortForLength, + InvalidEmptyPayload, + MessageTooLarge, + InvalidContentType, + UnknownProtocolVersion, +} + +#[cfg(test)] +mod tests { + use std::{println, vec}; + + use super::*; + + #[test] + fn split_at_with_single_slice() { + let owner: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7]; + let borrowed_payload = OutboundPlain::Single(owner); + + let (before, after) = borrowed_payload.split_at(6); + println!("before:{before:?}\nafter:{after:?}"); + assert_eq!(before.to_vec(), &[0, 1, 2, 3, 4, 5]); + assert_eq!(after.to_vec(), &[6, 7]); + } + + #[test] + fn split_at_with_multiple_slices() { + let owner: Vec<&[u8]> = vec![&[0, 1, 2, 3], &[4, 5], &[6, 7, 8], &[9, 10, 11, 12]]; + let borrowed_payload = OutboundPlain::new(&owner); + + let (before, after) = borrowed_payload.split_at(3); + println!("before:{before:?}\nafter:{after:?}"); + assert_eq!(before.to_vec(), &[0, 1, 2]); + assert_eq!(after.to_vec(), &[3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + + let (before, after) = borrowed_payload.split_at(8); + println!("before:{before:?}\nafter:{after:?}"); + assert_eq!(before.to_vec(), &[0, 1, 2, 3, 4, 5, 6, 7]); + assert_eq!(after.to_vec(), &[8, 9, 10, 11, 12]); + + let (before, after) = borrowed_payload.split_at(11); + println!("before:{before:?}\nafter:{after:?}"); + assert_eq!(before.to_vec(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + assert_eq!(after.to_vec(), &[11, 12]); + } + + #[test] + fn split_out_of_bounds() { + let owner: Vec<&[u8]> = vec![&[0, 1, 2, 3], &[4, 5], &[6, 7, 8], &[9, 10, 11, 12]]; + + let single_payload = OutboundPlain::Single(owner[0]); + let (before, after) = single_payload.split_at(17); + println!("before:{before:?}\nafter:{after:?}"); + assert_eq!(before.to_vec(), &[0, 1, 2, 3]); + assert!(after.is_empty()); + + let multiple_payload = OutboundPlain::new(&owner); + let (before, after) = multiple_payload.split_at(17); + println!("before:{before:?}\nafter:{after:?}"); + assert_eq!(before.to_vec(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + assert!(after.is_empty()); + + let empty_payload = OutboundPlain::new_empty(); + let (before, after) = empty_payload.split_at(17); + println!("before:{before:?}\nafter:{after:?}"); + assert!(before.is_empty()); + assert!(after.is_empty()); + } + + #[test] + fn empty_slices_mixed() { + let owner: Vec<&[u8]> = vec![&[], &[], &[0], &[], &[1, 2], &[], &[3], &[4], &[], &[]]; + let mut borrowed_payload = OutboundPlain::new(&owner); + let mut fragment_count = 0; + let mut fragment; + let expected_fragments: &[&[u8]] = &[&[0, 1], &[2, 3], &[4]]; + + while !borrowed_payload.is_empty() { + (fragment, borrowed_payload) = borrowed_payload.split_at(2); + println!("{fragment:?}"); + assert_eq!(&expected_fragments[fragment_count], &fragment.to_vec()); + fragment_count += 1; + } + assert_eq!(fragment_count, expected_fragments.len()); + } + + #[test] + fn exhaustive_splitting() { + let owner: Vec = (0..127).collect(); + let slices = (0..7) + .map(|i| &owner[((1 << i) - 1)..((1 << (i + 1)) - 1)]) + .collect::>(); + let payload = OutboundPlain::new(&slices); + + assert_eq!(payload.to_vec(), owner); + println!("{payload:#?}"); + + for start in 0..128 { + for end in start..128 { + for mid in 0..(end - start) { + let witness = owner[start..end].split_at(mid); + let split_payload = payload + .split_at(end) + .0 + .split_at(start) + .1 + .split_at(mid); + assert_eq!( + witness.0, + split_payload.0.to_vec(), + "start: {start}, mid:{mid}, end:{end}" + ); + assert_eq!( + witness.1, + split_payload.1.to_vec(), + "start: {start}, mid:{mid}, end:{end}" + ); + } + } + } + } +} diff --git a/rustls/src/crypto/cipher/mod.rs b/rustls/src/crypto/cipher/mod.rs new file mode 100644 index 00000000000..e63086ff392 --- /dev/null +++ b/rustls/src/crypto/cipher/mod.rs @@ -0,0 +1,588 @@ +use alloc::boxed::Box; +use alloc::string::ToString; +use core::{array, fmt}; + +use pki_types::FipsStatus; +use zeroize::Zeroize; + +use crate::enums::{ContentType, ProtocolVersion}; +use crate::error::{ApiMisuse, Error}; +use crate::msgs::{put_u16, put_u64}; +use crate::suites::ConnectionTrafficSecrets; + +mod messages; +pub use messages::{ + EncodedMessage, InboundOpaque, MessageError, OutboundOpaque, OutboundPlain, Payload, +}; + +mod record_layer; +pub(crate) use record_layer::{Decrypted, DecryptionState, EncryptionState, PreEncryptAction}; + +/// Factory trait for building `MessageEncrypter` and `MessageDecrypter` for a TLS1.3 cipher suite. +pub trait Tls13AeadAlgorithm: Send + Sync { + /// Build a `MessageEncrypter` for the given key/iv. + fn encrypter(&self, key: AeadKey, iv: Iv) -> Box; + + /// Build a `MessageDecrypter` for the given key/iv. + fn decrypter(&self, key: AeadKey, iv: Iv) -> Box; + + /// The length of key in bytes required by `encrypter()` and `decrypter()`. + fn key_len(&self) -> usize; + + /// The length of IV in bytes required by `encrypter()` and `decrypter()`. + fn iv_len(&self) -> usize { + NONCE_LEN + } + + /// Convert the key material from `key`/`iv`, into a `ConnectionTrafficSecrets` item. + /// + /// May return [`UnsupportedOperationError`] if the AEAD algorithm is not a supported + /// variant of `ConnectionTrafficSecrets`. + fn extract_keys( + &self, + key: AeadKey, + iv: Iv, + ) -> Result; + + /// Return `true` if this is backed by a FIPS-approved implementation. + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated + } +} + +/// Factory trait for building `MessageEncrypter` and `MessageDecrypter` for a TLS1.2 cipher suite. +pub trait Tls12AeadAlgorithm: Send + Sync + 'static { + /// Build a `MessageEncrypter` for the given key/iv and extra key block (which can be used for + /// improving explicit nonce size security, if needed). + /// + /// The length of `key` is set by [`KeyBlockShape::enc_key_len`]. + /// + /// The length of `iv` is set by [`KeyBlockShape::fixed_iv_len`]. + /// + /// The length of `extra` is set by [`KeyBlockShape::explicit_nonce_len`]. + fn encrypter(&self, key: AeadKey, iv: &[u8], extra: &[u8]) -> Box; + + /// Build a `MessageDecrypter` for the given key/iv. + /// + /// The length of `key` is set by [`KeyBlockShape::enc_key_len`]. + /// + /// The length of `iv` is set by [`KeyBlockShape::fixed_iv_len`]. + fn decrypter(&self, key: AeadKey, iv: &[u8]) -> Box; + + /// Return a `KeyBlockShape` that defines how large the `key_block` is and how it + /// is split up prior to calling `encrypter()`, `decrypter()` and/or `extract_keys()`. + fn key_block_shape(&self) -> KeyBlockShape; + + /// Convert the key material from `key`/`iv`, into a `ConnectionTrafficSecrets` item. + /// + /// The length of `key` is set by [`KeyBlockShape::enc_key_len`]. + /// + /// The length of `iv` is set by [`KeyBlockShape::fixed_iv_len`]. + /// + /// The length of `extra` is set by [`KeyBlockShape::explicit_nonce_len`]. + /// + /// May return [`UnsupportedOperationError`] if the AEAD algorithm is not a supported + /// variant of `ConnectionTrafficSecrets`. + fn extract_keys( + &self, + key: AeadKey, + iv: &[u8], + explicit: &[u8], + ) -> Result; + + /// Return the FIPS validation status of this implementation. + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated + } +} + +/// An error indicating that the AEAD algorithm does not support the requested operation. +#[expect(clippy::exhaustive_structs)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct UnsupportedOperationError; + +impl From for Error { + fn from(value: UnsupportedOperationError) -> Self { + Self::General(value.to_string()) + } +} + +impl fmt::Display for UnsupportedOperationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "operation not supported") + } +} + +impl core::error::Error for UnsupportedOperationError {} + +/// How a TLS1.2 `key_block` is partitioned. +/// +/// Note: ciphersuites with non-zero `mac_key_length` are not currently supported. +#[expect(clippy::exhaustive_structs)] +pub struct KeyBlockShape { + /// How long keys are. + /// + /// `enc_key_length` terminology is from the standard ([RFC5246 A.6]). + /// + /// [RFC5246 A.6]: + pub enc_key_len: usize, + + /// How long the fixed part of the 'IV' is. + /// + /// `fixed_iv_length` terminology is from the standard ([RFC5246 A.6]). + /// + /// This isn't usually an IV, but we continue the + /// terminology misuse to match the standard. + /// + /// [RFC5246 A.6]: + pub fixed_iv_len: usize, + + /// This is a non-standard extension which extends the + /// key block to provide an initial explicit nonce offset, + /// in a deterministic and safe way. GCM needs this, + /// chacha20poly1305 works this way by design. + pub explicit_nonce_len: usize, +} + +/// Objects with this trait can decrypt TLS messages. +pub trait MessageDecrypter: Send + Sync { + /// Decrypt the given TLS message `msg`, using the sequence number + /// `seq` which can be used to derive a unique [`Nonce`]. + fn decrypt<'a>( + &mut self, + msg: EncodedMessage>, + seq: u64, + ) -> Result, Error>; +} + +/// Objects with this trait can encrypt TLS messages. +pub trait MessageEncrypter: Send + Sync { + /// Encrypt the given TLS message `msg`, using the sequence number + /// `seq` which can be used to derive a unique [`Nonce`]. + fn encrypt( + &mut self, + msg: EncodedMessage>, + seq: u64, + ) -> Result, Error>; + + /// Return the length of the ciphertext that results from encrypting plaintext of + /// length `payload_len` + fn encrypted_payload_len(&self, payload_len: usize) -> usize; +} + +/// A write or read IV. +#[derive(Default, Clone)] +pub struct Iv { + buf: [u8; Self::MAX_LEN], + used: usize, +} + +impl Iv { + /// Create a new `Iv` from a byte slice. + /// + /// Returns an error if the length of `value` exceeds [`Self::MAX_LEN`]. + pub fn new(value: &[u8]) -> Result { + if value.len() > Self::MAX_LEN { + return Err(ApiMisuse::IvLengthExceedsMaximum { + actual: value.len(), + maximum: Self::MAX_LEN, + } + .into()); + } + let mut buf = [0u8; Self::MAX_LEN]; + buf[..value.len()].copy_from_slice(value); + Ok(Self { + buf, + used: value.len(), + }) + } + + /// Return the IV length. + #[expect(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.used + } + + /// Maximum supported IV length. + pub const MAX_LEN: usize = 16; +} + +impl From<[u8; NONCE_LEN]> for Iv { + fn from(bytes: [u8; NONCE_LEN]) -> Self { + Self::new(&bytes).expect("NONCE_LEN is within MAX_LEN") + } +} + +impl AsRef<[u8]> for Iv { + fn as_ref(&self) -> &[u8] { + &self.buf[..self.used] + } +} + +/// A nonce. This is unique for all messages on a connection. +pub struct Nonce { + buf: [u8; Iv::MAX_LEN], + len: usize, +} + +impl Nonce { + /// Combine an `Iv` and sequence number to produce a unique nonce. + /// + /// This is `iv ^ seq` where `seq` is encoded as a big-endian integer. + #[inline] + pub fn new(iv: &Iv, seq: u64) -> Self { + Self::new_inner(None, iv, seq) + } + + /// Creates a unique nonce based on the multipath `path_id`, the `iv` and packet number `pn`. + /// + /// The nonce is computed as the XOR between the `iv` and the big-endian integer formed + /// by concatenating `path_id` (or 0) and `pn`. + pub fn quic(path_id: Option, iv: &Iv, pn: u64) -> Self { + Self::new_inner(path_id, iv, pn) + } + + /// Creates a unique nonce based on the iv and sequence number seq. + #[inline] + fn new_inner(path_id: Option, iv: &Iv, seq: u64) -> Self { + let iv_len = iv.len(); + let mut buf = [0u8; Iv::MAX_LEN]; + + if iv_len >= 8 { + put_u64(seq, &mut buf[iv_len - 8..iv_len]); + if let Some(path_id) = path_id { + if iv_len >= 12 { + buf[iv_len - 12..iv_len - 8].copy_from_slice(&path_id.to_be_bytes()); + } + } + } else { + let seq_bytes = seq.to_be_bytes(); + buf[..iv_len].copy_from_slice(&seq_bytes[8 - iv_len..]); + } + + buf[..iv_len] + .iter_mut() + .zip(iv.as_ref()) + .for_each(|(s, iv)| *s ^= *iv); + + Self { buf, len: iv_len } + } + + /// Convert to a fixed-size array of length `N`. + /// + /// Returns an error if the nonce length is not `N`. + /// + /// For standard nonces, use `nonce.to_array::()?` or just `nonce.to_array()?` + /// which defaults to `NONCE_LEN`. + pub fn to_array(&self) -> Result<[u8; N], Error> { + if self.len != N { + return Err(ApiMisuse::NonceArraySizeMismatch { + expected: N, + actual: self.len, + } + .into()); + } + Ok(self.buf[..N] + .try_into() + .expect("nonce buffer conversion failed")) + } + + /// Return the nonce value. + pub fn as_bytes(&self) -> &[u8] { + &self.buf[..self.len] + } + + /// Return the nonce length. + #[expect(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.len + } +} + +impl AsRef<[u8]> for Nonce { + fn as_ref(&self) -> &[u8] { + &self.buf[..self.len] + } +} + +/// Size of TLS nonces (incorrectly termed "IV" in standard) for all supported ciphersuites +/// (AES-GCM, Chacha20Poly1305) +pub const NONCE_LEN: usize = 12; + +/// Returns a TLS1.3 `additional_data` encoding. +/// +/// See RFC8446 s5.2 for the `additional_data` definition. +#[inline] +pub fn make_tls13_aad(payload_len: usize) -> [u8; 5] { + let version = ProtocolVersion::TLSv1_2.to_array(); + [ + ContentType::ApplicationData.into(), + // Note: this is `legacy_record_version`, i.e. TLS1.2 even for TLS1.3. + version[0], + version[1], + (payload_len >> 8) as u8, + (payload_len & 0xff) as u8, + ] +} + +/// Returns a TLS1.2 `additional_data` encoding. +/// +/// See RFC5246 s6.2.3.3 for the `additional_data` definition. +#[inline] +pub fn make_tls12_aad( + seq: u64, + typ: ContentType, + vers: ProtocolVersion, + len: usize, +) -> [u8; TLS12_AAD_SIZE] { + let mut out = [0; TLS12_AAD_SIZE]; + put_u64(seq, &mut out[0..]); + out[8] = typ.into(); + put_u16(vers.into(), &mut out[9..]); + put_u16(len as u16, &mut out[11..]); + out +} + +const TLS12_AAD_SIZE: usize = 8 + 1 + 2 + 2; + +/// A key for an AEAD algorithm. +/// +/// This is a value type for a byte string up to `AeadKey::MAX_LEN` bytes in length. +pub struct AeadKey { + buf: [u8; Self::MAX_LEN], + used: usize, +} + +impl AeadKey { + pub(crate) fn new(buf: &[u8]) -> Self { + debug_assert!(buf.len() <= Self::MAX_LEN); + let mut key = Self::from([0u8; Self::MAX_LEN]); + key.buf[..buf.len()].copy_from_slice(buf); + key.used = buf.len(); + key + } + + pub(crate) fn with_length(self, len: usize) -> Self { + assert!(len <= self.used); + Self { + buf: self.buf, + used: len, + } + } + + /// Largest possible AEAD key in the ciphersuites we support. + pub(crate) const MAX_LEN: usize = 32; +} + +impl Drop for AeadKey { + #[inline(never)] + fn drop(&mut self) { + self.buf.zeroize(); + } +} + +impl AsRef<[u8]> for AeadKey { + fn as_ref(&self) -> &[u8] { + &self.buf[..self.used] + } +} + +impl From<[u8; Self::MAX_LEN]> for AeadKey { + fn from(bytes: [u8; Self::MAX_LEN]) -> Self { + Self { + buf: bytes, + used: Self::MAX_LEN, + } + } +} + +impl From<[u8; 16]> for AeadKey { + fn from(buf: [u8; 16]) -> Self { + Self { + buf: array::from_fn(|i| if i < 16 { buf[i] } else { 0 }), + used: 16, + } + } +} + +#[cfg(test)] +pub(crate) struct FakeAead; + +#[cfg(test)] +impl Tls12AeadAlgorithm for FakeAead { + fn encrypter(&self, _: AeadKey, _: &[u8], _: &[u8]) -> Box { + todo!() + } + + fn decrypter(&self, _: AeadKey, _: &[u8]) -> Box { + todo!() + } + + fn key_block_shape(&self) -> KeyBlockShape { + todo!() + } + + fn extract_keys( + &self, + _: AeadKey, + _: &[u8], + _: &[u8], + ) -> Result { + Err(UnsupportedOperationError) + } + + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Using test values provided in the spec in + /// + #[test] + fn multipath_nonce() { + const PATH_ID: u32 = 3; + const PN: u64 = 54321; + const IV: [u8; 16] = 0x6b26114b9cba2b63a9e8dd4fu128.to_be_bytes(); + const EXPECTED_NONCE: [u8; 16] = 0x6b2611489cba2b63a9e8097eu128.to_be_bytes(); + let nonce = Nonce::quic(Some(PATH_ID), &Iv::new(&IV[4..]).unwrap(), PN); + assert_eq!(&EXPECTED_NONCE[4..], nonce.as_bytes()); + } + + #[test] + fn iv_len() { + let iv = Iv::new(&[1u8; NONCE_LEN]).unwrap(); + assert_eq!(iv.len(), NONCE_LEN); + + let short_iv = Iv::new(&[1u8, 2, 3]).unwrap(); + assert_eq!(short_iv.len(), 3); + + let empty_iv = Iv::new(&[]).unwrap(); + assert_eq!(empty_iv.len(), 0); + } + + #[test] + fn iv_as_ref() { + let iv_data = [1u8, 2, 3, 4, 5]; + let iv = Iv::new(&iv_data).unwrap(); + let iv_ref: &[u8] = iv.as_ref(); + assert_eq!(iv_ref, &iv_data); + } + + #[test] + fn nonce_with_short_iv() { + let short_iv = Iv::new(&[0xAA, 0xBB, 0xCC, 0xDD]).unwrap(); + let seq = 0x1122334455667788u64; + let nonce = Nonce::new(&short_iv, seq); + + // The nonce should XOR the last 4 bytes of seq with the IV + assert_eq!(nonce.len(), 4); + let seq_bytes = seq.to_be_bytes(); + let expected = [ + 0xAA ^ seq_bytes[4], + 0xBB ^ seq_bytes[5], + 0xCC ^ seq_bytes[6], + 0xDD ^ seq_bytes[7], + ]; + assert_eq!(nonce.as_bytes(), &expected); + } + + #[test] + fn nonce_len() { + let iv = Iv::new(&[1u8; NONCE_LEN]).unwrap(); + let nonce = Nonce::new(&iv, 42); + assert_eq!(nonce.len(), NONCE_LEN); + + let short_iv = Iv::new(&[1u8, 2]).unwrap(); + let short_nonce = Nonce::new(&short_iv, 42); + assert_eq!(short_nonce.len(), 2); + } + + #[test] + fn nonce_as_ref() { + let iv = Iv::new(&[1u8; NONCE_LEN]).unwrap(); + let nonce = Nonce::new(&iv, 42); + let nonce_ref: &[u8] = nonce.as_ref(); + assert_eq!(nonce_ref.len(), NONCE_LEN); + } + + #[test] + fn nonce_to_array_correct_size() { + let iv = Iv::new(&[1u8; NONCE_LEN]).unwrap(); + let nonce = Nonce::new(&iv, 42); + let array: [u8; NONCE_LEN] = nonce.to_array().unwrap(); + assert_eq!(array.len(), NONCE_LEN); + } + + #[test] + fn nonce_to_array_wrong_size() { + let iv = Iv::new(&[1u8; NONCE_LEN]).unwrap(); + let nonce = Nonce::new(&iv, 42); + let result: Result<[u8; 16], Error> = nonce.to_array(); + assert!(matches!( + result, + Err(Error::ApiMisuse(ApiMisuse::NonceArraySizeMismatch { + expected: 16, + actual: NONCE_LEN + })) + )); + } + + #[test] + fn nonce_to_array_variable_length_error() { + // Create an IV with a non-standard length (8 bytes instead of 12) + let short_iv = Iv::new(&[0xAAu8; 8]).unwrap(); + let nonce = Nonce::new(&short_iv, 42); + + // Attempting to convert to standard NONCE_LEN should fail + let result: Result<[u8; NONCE_LEN], Error> = nonce.to_array(); + if let Err(Error::ApiMisuse(ApiMisuse::NonceArraySizeMismatch { expected, actual })) = + result + { + assert_eq!(expected, NONCE_LEN); + assert_eq!(actual, 8); + } else { + panic!("Expected Error::ApiMisuse(NonceArraySizeMismatch)"); + } + + // But converting to the correct length should work + let result_correct: Result<[u8; 8], Error> = nonce.to_array(); + assert!(result_correct.is_ok()); + } + + #[test] + fn nonce_xor_with_iv() { + let iv_data = [0xFFu8; NONCE_LEN]; + let iv = Iv::new(&iv_data).unwrap(); + let seq = 0x0000000000000001u64; + let nonce = Nonce::new(&iv, seq); + + // The last byte should be 0xFF XOR 0x01 = 0xFE + let nonce_bytes = nonce.as_bytes(); + assert_eq!(nonce_bytes[NONCE_LEN - 1], 0xFE); + } + + #[test] + fn iv_length_exceeds_maximum() { + let too_long_iv = [0xAAu8; Iv::MAX_LEN + 1]; + let result = Iv::new(&too_long_iv); + + assert!(matches!( + result, + Err(Error::ApiMisuse(ApiMisuse::IvLengthExceedsMaximum { + actual: 17, + maximum: 16 + })) + )); + } + + #[test] + fn aead_key_16_bytes() { + let bytes = [0xABu8; 16]; + let key = AeadKey::from(bytes); + assert_eq!(key.as_ref(), &bytes); + } +} diff --git a/rustls/src/record_layer.rs b/rustls/src/crypto/cipher/record_layer.rs similarity index 57% rename from rustls/src/record_layer.rs rename to rustls/src/crypto/cipher/record_layer.rs index 8c2ea6b9b68..05a17fe288f 100644 --- a/rustls/src/record_layer.rs +++ b/rustls/src/crypto/cipher/record_layer.rs @@ -1,33 +1,96 @@ use alloc::boxed::Box; use core::cmp::min; -use crate::crypto::cipher::{InboundOpaqueMessage, MessageDecrypter, MessageEncrypter}; +use crate::crypto::cipher::{ + EncodedMessage, InboundOpaque, MessageDecrypter, MessageEncrypter, OutboundOpaque, + OutboundPlain, +}; use crate::error::Error; use crate::log::trace; -use crate::msgs::message::{InboundPlainMessage, OutboundOpaqueMessage, OutboundPlainMessage}; +use crate::msgs::HandshakeAlignedProof; -#[derive(PartialEq)] -enum DirectionState { - /// No keying material. - Invalid, +/// Record layer that tracks encryption keys. +pub(crate) struct EncryptionState { + message_encrypter: Option>, + write_seq_max: u64, + write_seq: u64, +} + +impl EncryptionState { + /// Create new record layer with no keys. + pub(crate) fn new() -> Self { + Self { + message_encrypter: None, + write_seq_max: 0, + write_seq: 0, + } + } - /// Keying material present, but not yet in use. - Prepared, + /// Encrypt a TLS message. + /// + /// `plain` is a TLS message we'd like to send. This function + /// panics if the requisite keying material hasn't been established yet. + pub(crate) fn encrypt_outgoing( + &mut self, + plain: EncodedMessage>, + ) -> EncodedMessage { + assert!(self.pre_encrypt_action(0) != Some(PreEncryptAction::Refuse)); + let seq = self.write_seq; + self.write_seq += 1; + self.message_encrypter + .as_mut() + .unwrap() + .encrypt(plain, seq) + .unwrap() + } - /// Keying material in use. - Active, + /// Set and start using the given `MessageEncrypter` for future outgoing + /// message encryption. + pub(crate) fn set_message_encrypter( + &mut self, + cipher: Box, + max_messages: u64, + ) { + *self = Self { + message_encrypter: Some(cipher), + write_seq_max: min(SEQ_SOFT_LIMIT, max_messages), + write_seq: 0, + }; + } + + /// Return a remedial action when we are near to encrypting too many messages. + /// + /// `add` is added to the current sequence number. `add` as `0` means + /// "the next message processed by `encrypt_outgoing`" + pub(crate) fn pre_encrypt_action(&self, add: u64) -> Option { + match self.write_seq.saturating_add(add) { + v if v == self.write_seq_max => Some(PreEncryptAction::RefreshOrClose), + SEQ_HARD_LIMIT.. => Some(PreEncryptAction::Refuse), + _ => None, + } + } + + pub(crate) fn encrypted_len(&self, payload_len: usize) -> usize { + self.message_encrypter + .as_ref() + .map(|enc| enc.encrypted_payload_len(payload_len)) + .unwrap_or_default() + } + + pub(crate) fn is_encrypting(&self) -> bool { + self.message_encrypter.is_some() + } + + pub(crate) fn write_seq(&self) -> u64 { + self.write_seq + } } -/// Record layer that tracks decryption and encryption keys. -pub(crate) struct RecordLayer { - message_encrypter: Box, - message_decrypter: Box, - write_seq_max: u64, - write_seq: u64, +/// Record layer that tracks decryption keys. +pub(crate) struct DecryptionState { + message_decrypter: Option>, read_seq: u64, has_decrypted: bool, - encrypt_state: DirectionState, - decrypt_state: DirectionState, // Message encrypted with other keys may be encountered, so failures // should be swallowed by the caller. This struct tracks the amount @@ -35,18 +98,13 @@ pub(crate) struct RecordLayer { trial_decryption_len: Option, } -impl RecordLayer { +impl DecryptionState { /// Create new record layer with no keys. pub(crate) fn new() -> Self { Self { - message_encrypter: ::invalid(), - message_decrypter: ::invalid(), - write_seq_max: 0, - write_seq: 0, + message_decrypter: None, read_seq: 0, has_decrypted: false, - encrypt_state: DirectionState::Invalid, - decrypt_state: DirectionState::Invalid, trial_decryption_len: None, } } @@ -58,14 +116,14 @@ impl RecordLayer { /// an error is returned. pub(crate) fn decrypt_incoming<'a>( &mut self, - encr: InboundOpaqueMessage<'a>, + encr: EncodedMessage>, ) -> Result>, Error> { - if self.decrypt_state != DirectionState::Active { + let Some(decrypter) = &mut self.message_decrypter else { return Ok(Some(Decrypted { want_close_before_decrypt: false, plaintext: encr.into_plain_message(), })); - } + }; // Set to `true` if the peer appears to getting close to encrypting // too many messages with this key. @@ -78,10 +136,7 @@ impl RecordLayer { let want_close_before_decrypt = self.read_seq == SEQ_SOFT_LIMIT; let encrypted_len = encr.payload.len(); - match self - .message_decrypter - .decrypt(encr, self.read_seq) - { + match decrypter.decrypt(encr, self.read_seq) { Ok(plaintext) => { self.read_seq += 1; if !self.has_decrypted { @@ -100,74 +155,15 @@ impl RecordLayer { } } - /// Encrypt a TLS message. - /// - /// `plain` is a TLS message we'd like to send. This function - /// panics if the requisite keying material hasn't been established yet. - pub(crate) fn encrypt_outgoing( - &mut self, - plain: OutboundPlainMessage<'_>, - ) -> OutboundOpaqueMessage { - debug_assert!(self.encrypt_state == DirectionState::Active); - assert!(self.next_pre_encrypt_action() != PreEncryptAction::Refuse); - let seq = self.write_seq; - self.write_seq += 1; - self.message_encrypter - .encrypt(plain, seq) - .unwrap() - } - - /// Prepare to use the given `MessageEncrypter` for future message encryption. - /// It is not used until you call `start_encrypting`. - pub(crate) fn prepare_message_encrypter( + /// Set and start using the given `MessageDecrypter` for future incoming + /// message decryption. + pub(crate) fn set_message_decrypter( &mut self, - cipher: Box, - max_messages: u64, + cipher: Box, + _proof: &HandshakeAlignedProof, ) { - self.message_encrypter = cipher; - self.write_seq = 0; - self.write_seq_max = min(SEQ_SOFT_LIMIT, max_messages); - self.encrypt_state = DirectionState::Prepared; - } - - /// Prepare to use the given `MessageDecrypter` for future message decryption. - /// It is not used until you call `start_decrypting`. - pub(crate) fn prepare_message_decrypter(&mut self, cipher: Box) { - self.message_decrypter = cipher; + self.message_decrypter = Some(cipher); self.read_seq = 0; - self.decrypt_state = DirectionState::Prepared; - } - - /// Start using the `MessageEncrypter` previously provided to the previous - /// call to `prepare_message_encrypter`. - pub(crate) fn start_encrypting(&mut self) { - debug_assert!(self.encrypt_state == DirectionState::Prepared); - self.encrypt_state = DirectionState::Active; - } - - /// Start using the `MessageDecrypter` previously provided to the previous - /// call to `prepare_message_decrypter`. - pub(crate) fn start_decrypting(&mut self) { - debug_assert!(self.decrypt_state == DirectionState::Prepared); - self.decrypt_state = DirectionState::Active; - } - - /// Set and start using the given `MessageEncrypter` for future outgoing - /// message encryption. - pub(crate) fn set_message_encrypter( - &mut self, - cipher: Box, - max_messages: u64, - ) { - self.prepare_message_encrypter(cipher, max_messages); - self.start_encrypting(); - } - - /// Set and start using the given `MessageDecrypter` for future incoming - /// message decryption. - pub(crate) fn set_message_decrypter(&mut self, cipher: Box) { - self.prepare_message_decrypter(cipher); - self.start_decrypting(); self.trial_decryption_len = None; } @@ -178,9 +174,10 @@ impl RecordLayer { &mut self, cipher: Box, max_length: usize, + _proof: &HandshakeAlignedProof, ) { - self.prepare_message_decrypter(cipher); - self.start_decrypting(); + self.message_decrypter = Some(cipher); + self.read_seq = 0; self.trial_decryption_len = Some(max_length); } @@ -188,45 +185,16 @@ impl RecordLayer { self.trial_decryption_len = None; } - pub(crate) fn next_pre_encrypt_action(&self) -> PreEncryptAction { - self.pre_encrypt_action(0) - } - - /// Return a remedial action when we are near to encrypting too many messages. - /// - /// `add` is added to the current sequence number. `add` as `0` means - /// "the next message processed by `encrypt_outgoing`" - pub(crate) fn pre_encrypt_action(&self, add: u64) -> PreEncryptAction { - match self.write_seq.saturating_add(add) { - v if v == self.write_seq_max => PreEncryptAction::RefreshOrClose, - SEQ_HARD_LIMIT.. => PreEncryptAction::Refuse, - _ => PreEncryptAction::Nothing, - } - } - - pub(crate) fn is_encrypting(&self) -> bool { - self.encrypt_state == DirectionState::Active - } - /// Return true if we have ever decrypted a message. This is used in place /// of checking the read_seq since that will be reset on key updates. pub(crate) fn has_decrypted(&self) -> bool { self.has_decrypted } - pub(crate) fn write_seq(&self) -> u64 { - self.write_seq - } - pub(crate) fn read_seq(&self) -> u64 { self.read_seq } - pub(crate) fn encrypted_len(&self, payload_len: usize) -> usize { - self.message_encrypter - .encrypted_payload_len(payload_len) - } - fn doing_trial_decryption(&mut self, requested: usize) -> bool { match self .trial_decryption_len @@ -247,14 +215,11 @@ pub(crate) struct Decrypted<'a> { /// Whether the peer appears to be getting close to encrypting too many messages with this key. pub(crate) want_close_before_decrypt: bool, /// The decrypted message. - pub(crate) plaintext: InboundPlainMessage<'a>, + pub(crate) plaintext: EncodedMessage<&'a [u8]>, } #[derive(Debug, Eq, PartialEq)] pub(crate) enum PreEncryptAction { - /// No action is needed before calling `encrypt_outgoing` - Nothing, - /// A `key_update` request should be sent ASAP. /// /// If that is not possible (for example, the connection is TLS1.2), a `close_notify` @@ -266,70 +231,65 @@ pub(crate) enum PreEncryptAction { Refuse, } -const SEQ_SOFT_LIMIT: u64 = 0xffff_ffff_ffff_0000u64; -const SEQ_HARD_LIMIT: u64 = 0xffff_ffff_ffff_fffeu64; +/// When to take action to avoid sequence space exhaustion. +/// +/// This gives a margin in which any action can have an effect, prior to `SEQ_HARD_LIMIT` +/// being reached. +const SEQ_SOFT_LIMIT: u64 = u64::MAX - 0xffff; + +/// When to refuse further encryptions. +const SEQ_HARD_LIMIT: u64 = u64::MAX - 1; #[cfg(test)] mod tests { use super::*; + use crate::enums::{ContentType, ProtocolVersion}; + use crate::msgs::Deframer; #[test] fn test_has_decrypted() { - use crate::{ContentType, ProtocolVersion}; - struct PassThroughDecrypter; impl MessageDecrypter for PassThroughDecrypter { fn decrypt<'a>( &mut self, - m: InboundOpaqueMessage<'a>, + m: EncodedMessage>, _: u64, - ) -> Result, Error> { + ) -> Result, Error> { Ok(m.into_plain_message()) } } // A record layer starts out invalid, having never decrypted. - let mut record_layer = RecordLayer::new(); - assert!(matches!( - record_layer.decrypt_state, - DirectionState::Invalid - )); + let mut record_layer = DecryptionState::new(); + assert!(record_layer.message_decrypter.is_none()); assert_eq!(record_layer.read_seq, 0); assert!(!record_layer.has_decrypted()); - // Preparing the record layer should update the decrypt state, but shouldn't affect whether it + // Initializing the record layer should update the decrypt state, but shouldn't affect whether it // has decrypted. - record_layer.prepare_message_decrypter(Box::new(PassThroughDecrypter)); - assert!(matches!( - record_layer.decrypt_state, - DirectionState::Prepared - )); - assert_eq!(record_layer.read_seq, 0); - assert!(!record_layer.has_decrypted()); - - // Starting decryption should update the decrypt state, but not affect whether it has decrypted. - record_layer.start_decrypting(); - assert!(matches!(record_layer.decrypt_state, DirectionState::Active)); + let deframer = Deframer::default(); + record_layer + .set_message_decrypter(Box::new(PassThroughDecrypter), &deframer.aligned().unwrap()); + assert!(record_layer.message_decrypter.is_some()); assert_eq!(record_layer.read_seq, 0); assert!(!record_layer.has_decrypted()); // Decrypting a message should update the read_seq and track that we have now performed // a decryption. record_layer - .decrypt_incoming(InboundOpaqueMessage::new( + .decrypt_incoming(EncodedMessage::new( ContentType::Handshake, ProtocolVersion::TLSv1_2, - &mut [0xC0, 0xFF, 0xEE], + InboundOpaque(&mut [0xC0, 0xFF, 0xEE]), )) .unwrap(); - assert!(matches!(record_layer.decrypt_state, DirectionState::Active)); assert_eq!(record_layer.read_seq, 1); assert!(record_layer.has_decrypted()); // Resetting the record layer message decrypter (as if a key update occurred) should reset // the read_seq number, but not our knowledge of whether we have decrypted previously. - record_layer.set_message_decrypter(Box::new(PassThroughDecrypter)); - assert!(matches!(record_layer.decrypt_state, DirectionState::Active)); + record_layer + .set_message_decrypter(Box::new(PassThroughDecrypter), &deframer.aligned().unwrap()); assert_eq!(record_layer.read_seq, 0); assert!(record_layer.has_decrypted()); } diff --git a/rustls/src/crypto/enums.rs b/rustls/src/crypto/enums.rs new file mode 100644 index 00000000000..91fbfbc4838 --- /dev/null +++ b/rustls/src/crypto/enums.rs @@ -0,0 +1,554 @@ +use crate::crypto::hash; + +enum_builder! { + /// The `CipherSuite` TLS protocol enum. Values in this enum are taken + /// from the various RFCs covering TLS, and are listed by IANA. + pub struct CipherSuite(pub u16); + + enum CipherSuiteName { + /// The `TLS_DHE_RSA_WITH_AES_128_GCM_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 => 0x009e, + + /// The `TLS_DHE_RSA_WITH_AES_256_GCM_SHA384` cipher suite. Recommended=Y. Defined in + /// + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 => 0x009f, + + /// The `TLS_DHE_PSK_WITH_AES_128_GCM_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 => 0x00aa, + + /// The `TLS_DHE_PSK_WITH_AES_256_GCM_SHA384` cipher suite. Recommended=Y. Defined in + /// + TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 => 0x00ab, + + /// The `TLS_AES_128_GCM_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS13_AES_128_GCM_SHA256 => 0x1301, + + /// The `TLS_AES_256_GCM_SHA384` cipher suite. Recommended=Y. Defined in + /// + TLS13_AES_256_GCM_SHA384 => 0x1302, + + /// The `TLS_CHACHA20_POLY1305_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS13_CHACHA20_POLY1305_SHA256 => 0x1303, + + /// The `TLS_AES_128_CCM_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS13_AES_128_CCM_SHA256 => 0x1304, + + /// The `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => 0xc02b, + + /// The `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384` cipher suite. Recommended=Y. Defined in + /// + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => 0xc02c, + + /// The `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => 0xc02f, + + /// The `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384` cipher suite. Recommended=Y. Defined in + /// + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => 0xc030, + + /// The `TLS_DHE_RSA_WITH_AES_128_CCM` cipher suite. Recommended=Y. Defined in + /// + TLS_DHE_RSA_WITH_AES_128_CCM => 0xc09e, + + /// The `TLS_DHE_RSA_WITH_AES_256_CCM` cipher suite. Recommended=Y. Defined in + /// + TLS_DHE_RSA_WITH_AES_256_CCM => 0xc09f, + + /// The `TLS_DHE_PSK_WITH_AES_128_CCM` cipher suite. Recommended=Y. Defined in + /// + TLS_DHE_PSK_WITH_AES_128_CCM => 0xc0a6, + + /// The `TLS_DHE_PSK_WITH_AES_256_CCM` cipher suite. Recommended=Y. Defined in + /// + TLS_DHE_PSK_WITH_AES_256_CCM => 0xc0a7, + + /// The `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => 0xcca8, + + /// The `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => 0xcca9, + + /// The `TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => 0xccaa, + + /// The `TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 => 0xccac, + + /// The `TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 => 0xccad, + + /// The `TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256 => 0xd001, + + /// The `TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384` cipher suite. Recommended=Y. Defined in + /// + TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384 => 0xd002, + + /// The `TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256` cipher suite. Recommended=Y. Defined in + /// + TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256 => 0xd005, + + /// The `TLS_RSA_WITH_AES_128_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_WITH_AES_128_CBC_SHA => 0x002f, + + /// The `TLS_DHE_RSA_WITH_AES_128_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_DHE_RSA_WITH_AES_128_CBC_SHA => 0x0033, + + /// The `TLS_RSA_WITH_AES_256_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_WITH_AES_256_CBC_SHA => 0x0035, + + /// The `TLS_DHE_RSA_WITH_AES_256_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_DHE_RSA_WITH_AES_256_CBC_SHA => 0x0039, + + /// The `TLS_RSA_WITH_AES_128_CBC_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_WITH_AES_128_CBC_SHA256 => 0x003c, + + /// The `TLS_RSA_WITH_AES_256_CBC_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_WITH_AES_256_CBC_SHA256 => 0x003d, + + /// The `TLS_DHE_RSA_WITH_AES_128_CBC_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 => 0x0067, + + /// The `TLS_DHE_RSA_WITH_AES_256_CBC_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 => 0x006b, + + /// The `TLS_PSK_WITH_AES_128_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_PSK_WITH_AES_128_CBC_SHA => 0x008c, + + /// The `TLS_PSK_WITH_AES_256_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_PSK_WITH_AES_256_CBC_SHA => 0x008d, + + /// The `TLS_DHE_PSK_WITH_AES_128_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_DHE_PSK_WITH_AES_128_CBC_SHA => 0x0090, + + /// The `TLS_DHE_PSK_WITH_AES_256_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_DHE_PSK_WITH_AES_256_CBC_SHA => 0x0091, + + /// The `TLS_RSA_PSK_WITH_AES_128_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_PSK_WITH_AES_128_CBC_SHA => 0x0094, + + /// The `TLS_RSA_PSK_WITH_AES_256_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_PSK_WITH_AES_256_CBC_SHA => 0x0095, + + /// The `TLS_RSA_WITH_AES_128_GCM_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_WITH_AES_128_GCM_SHA256 => 0x009c, + + /// The `TLS_RSA_WITH_AES_256_GCM_SHA384` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_WITH_AES_256_GCM_SHA384 => 0x009d, + + /// The `TLS_PSK_WITH_AES_128_GCM_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_PSK_WITH_AES_128_GCM_SHA256 => 0x00a8, + + /// The `TLS_PSK_WITH_AES_256_GCM_SHA384` cipher suite. Recommended=N. Defined in + /// + TLS_PSK_WITH_AES_256_GCM_SHA384 => 0x00a9, + + /// The `TLS_RSA_PSK_WITH_AES_128_GCM_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 => 0x00ac, + + /// The `TLS_RSA_PSK_WITH_AES_256_GCM_SHA384` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 => 0x00ad, + + /// The `TLS_PSK_WITH_AES_128_CBC_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_PSK_WITH_AES_128_CBC_SHA256 => 0x00ae, + + /// The `TLS_PSK_WITH_AES_256_CBC_SHA384` cipher suite. Recommended=N. Defined in + /// + TLS_PSK_WITH_AES_256_CBC_SHA384 => 0x00af, + + /// The `TLS_DHE_PSK_WITH_AES_128_CBC_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 => 0x00b2, + + /// The `TLS_DHE_PSK_WITH_AES_256_CBC_SHA384` cipher suite. Recommended=N. Defined in + /// + TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 => 0x00b3, + + /// The `TLS_RSA_PSK_WITH_AES_128_CBC_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 => 0x00b6, + + /// The `TLS_RSA_PSK_WITH_AES_256_CBC_SHA384` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 => 0x00b7, + + /// The `TLS_SM4_GCM_SM3` cipher suite. Recommended=N. Defined in + /// + TLS13_SM4_GCM_SM3 => 0x00c6, + + /// The `TLS_SM4_CCM_SM3` cipher suite. Recommended=N. Defined in + /// + TLS13_SM4_CCM_SM3 => 0x00c7, + + /// The `TLS_EMPTY_RENEGOTIATION_INFO_SCSV` cipher suite. Recommended=N. Defined in + /// + TLS_EMPTY_RENEGOTIATION_INFO_SCSV => 0x00ff, + + /// The `TLS_AES_128_CCM_8_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS13_AES_128_CCM_8_SHA256 => 0x1305, + + /// The `TLS_FALLBACK_SCSV` cipher suite. Recommended=N. Defined in + /// + TLS_FALLBACK_SCSV => 0x5600, + + /// The `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA => 0xc009, + + /// The `TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA => 0xc00a, + + /// The `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA => 0xc013, + + /// The `TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA => 0xc014, + + /// The `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 => 0xc023, + + /// The `TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 => 0xc024, + + /// The `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 => 0xc027, + + /// The `TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 => 0xc028, + + /// The `TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA => 0xc035, + + /// The `TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA => 0xc036, + + /// The `TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 => 0xc037, + + /// The `TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 => 0xc038, + + /// The `TLS_RSA_WITH_AES_128_CCM` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_WITH_AES_128_CCM => 0xc09c, + + /// The `TLS_RSA_WITH_AES_256_CCM` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_WITH_AES_256_CCM => 0xc09d, + + /// The `TLS_RSA_WITH_AES_128_CCM_8` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_WITH_AES_128_CCM_8 => 0xc0a0, + + /// The `TLS_RSA_WITH_AES_256_CCM_8` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_WITH_AES_256_CCM_8 => 0xc0a1, + + /// The `TLS_DHE_RSA_WITH_AES_128_CCM_8` cipher suite. Recommended=N. Defined in + /// + TLS_DHE_RSA_WITH_AES_128_CCM_8 => 0xc0a2, + + /// The `TLS_DHE_RSA_WITH_AES_256_CCM_8` cipher suite. Recommended=N. Defined in + /// + TLS_DHE_RSA_WITH_AES_256_CCM_8 => 0xc0a3, + + /// The `TLS_PSK_WITH_AES_128_CCM` cipher suite. Recommended=N. Defined in + /// + TLS_PSK_WITH_AES_128_CCM => 0xc0a4, + + /// The `TLS_PSK_WITH_AES_256_CCM` cipher suite. Recommended=N. Defined in + /// + TLS_PSK_WITH_AES_256_CCM => 0xc0a5, + + /// The `TLS_PSK_WITH_AES_128_CCM_8` cipher suite. Recommended=N. Defined in + /// + TLS_PSK_WITH_AES_128_CCM_8 => 0xc0a8, + + /// The `TLS_PSK_WITH_AES_256_CCM_8` cipher suite. Recommended=N. Defined in + /// + TLS_PSK_WITH_AES_256_CCM_8 => 0xc0a9, + + /// The `TLS_PSK_DHE_WITH_AES_128_CCM_8` cipher suite. Recommended=N. Defined in + /// + TLS_PSK_DHE_WITH_AES_128_CCM_8 => 0xc0aa, + + /// The `TLS_PSK_DHE_WITH_AES_256_CCM_8` cipher suite. Recommended=N. Defined in + /// + TLS_PSK_DHE_WITH_AES_256_CCM_8 => 0xc0ab, + + /// The `TLS_ECDHE_ECDSA_WITH_AES_128_CCM` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_ECDSA_WITH_AES_128_CCM => 0xc0ac, + + /// The `TLS_ECDHE_ECDSA_WITH_AES_256_CCM` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_ECDSA_WITH_AES_256_CCM => 0xc0ad, + + /// The `TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 => 0xc0ae, + + /// The `TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 => 0xc0af, + + /// The `TLS_SHA256_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_SHA256_SHA256 => 0xc0b4, + + /// The `TLS_SHA384_SHA384` cipher suite. Recommended=N. Defined in + /// + TLS_SHA384_SHA384 => 0xc0b5, + + /// The `TLS_PSK_WITH_CHACHA20_POLY1305_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 => 0xccab, + + /// The `TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 => 0xccae, + + /// The `TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256` cipher suite. Recommended=N. Defined in + /// + TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256 => 0xd003, + } +} + +enum_builder! { + /// The `SignatureScheme` TLS protocol enum. Values in this enum are taken + /// from the various RFCs covering TLS, and are listed by IANA. + pub struct SignatureScheme(pub u16); + + enum SignatureSchemeName { + RSA_PKCS1_SHA1 => 0x0201, + ECDSA_SHA1_Legacy => 0x0203, + RSA_PKCS1_SHA256 => 0x0401, + ECDSA_NISTP256_SHA256 => 0x0403, + RSA_PKCS1_SHA384 => 0x0501, + ECDSA_NISTP384_SHA384 => 0x0503, + RSA_PKCS1_SHA512 => 0x0601, + ECDSA_NISTP521_SHA512 => 0x0603, + /// + SM2_SM3 => 0x0708, + RSA_PSS_SHA256 => 0x0804, + RSA_PSS_SHA384 => 0x0805, + RSA_PSS_SHA512 => 0x0806, + ED25519 => 0x0807, + ED448 => 0x0808, + // https://datatracker.ietf.org/doc/html/draft-ietf-tls-mldsa-00#name-iana-considerations + ML_DSA_44 => 0x0904, + ML_DSA_65 => 0x0905, + ML_DSA_87 => 0x0906, + } +} + +impl SignatureScheme { + pub(crate) fn algorithm(&self) -> SignatureAlgorithm { + match *self { + Self::RSA_PKCS1_SHA1 + | Self::RSA_PKCS1_SHA256 + | Self::RSA_PKCS1_SHA384 + | Self::RSA_PKCS1_SHA512 + | Self::RSA_PSS_SHA256 + | Self::RSA_PSS_SHA384 + | Self::RSA_PSS_SHA512 => SignatureAlgorithm::RSA, + Self::ECDSA_SHA1_Legacy + | Self::ECDSA_NISTP256_SHA256 + | Self::ECDSA_NISTP384_SHA384 + | Self::ECDSA_NISTP521_SHA512 => SignatureAlgorithm::ECDSA, + Self::ED25519 => SignatureAlgorithm::ED25519, + Self::ED448 => SignatureAlgorithm::ED448, + _ => SignatureAlgorithm(0), + } + } + + /// Whether a particular `SignatureScheme` is allowed for TLS protocol signatures + /// in TLS1.3. + /// + /// This prevents (eg) RSA_PKCS1_SHA256 being offered or accepted, even if our + /// verifier supports it for other protocol versions. + /// + /// See RFC8446 s4.2.3: + /// + /// This is a denylist so that newly-allocated `SignatureScheme`s values are + /// allowed in TLS1.3 by default. + pub(crate) fn supported_in_tls13(&self) -> bool { + let [hash, sign] = self.to_array(); + + // This covers both disallowing SHA1 items in `SignatureScheme`, and + // old hash functions. See the section beginning "Legacy algorithms:" + // and item starting "In TLS 1.2, the extension contained hash/signature + // pairs" in RFC8446 section 4.2.3. + match HashAlgorithm::from(hash) { + HashAlgorithm::NONE + | HashAlgorithm::MD5 + | HashAlgorithm::SHA1 + | HashAlgorithm::SHA224 => return false, + _ => (), + }; + + // RSA-PKCS1 is also disallowed for TLS1.3, see the section beginning + // "RSASSA-PKCS1-v1_5 algorithms:" in RFC8446 section 4.2.3. + // + // (nb. SignatureAlgorithm::RSA is RSA-PKCS1, and does not cover RSA-PSS + // or RSAE-PSS.) + // + // This also covers the outlawing of DSA mentioned elsewhere in 4.2.3. + !matches!( + SignatureAlgorithm::from(sign), + SignatureAlgorithm::Anonymous | SignatureAlgorithm::RSA | SignatureAlgorithm::DSA + ) + } +} + +enum_builder! { + /// The `HashAlgorithm` TLS protocol enum. Values in this enum are taken + /// from the various RFCs covering TLS, and are listed by IANA. + pub struct HashAlgorithm(pub u8); + + enum HashAlgorithmName { + NONE => 0x00, + MD5 => 0x01, + SHA1 => 0x02, + SHA224 => 0x03, + SHA256 => 0x04, + SHA384 => 0x05, + SHA512 => 0x06, + } +} + +impl HashAlgorithm { + /// Returns the hash of the empty input. + /// + /// This returns `None` for some hash algorithms, so the caller + /// should be prepared to do the computation themselves in this case. + pub(crate) fn hash_for_empty_input(&self) -> Option { + match *self { + Self::SHA256 => Some(hash::Output::new( + b"\xe3\xb0\xc4\x42\x98\xfc\x1c\x14\ + \x9a\xfb\xf4\xc8\x99\x6f\xb9\x24\ + \x27\xae\x41\xe4\x64\x9b\x93\x4c\ + \xa4\x95\x99\x1b\x78\x52\xb8\x55", + )), + Self::SHA384 => Some(hash::Output::new( + b"\x38\xb0\x60\xa7\x51\xac\x96\x38\ + \x4c\xd9\x32\x7e\xb1\xb1\xe3\x6a\ + \x21\xfd\xb7\x11\x14\xbe\x07\x43\ + \x4c\x0c\xc7\xbf\x63\xf6\xe1\xda\ + \x27\x4e\xde\xbf\xe7\x6f\x65\xfb\ + \xd5\x1a\xd2\xf1\x48\x98\xb9\x5b", + )), + _ => None, + } + } +} + +enum_builder! { + /// The `SignatureAlgorithm` TLS protocol enum. Values in this enum are taken + /// from the various RFCs covering TLS, and are listed by IANA. + pub struct SignatureAlgorithm(pub u8); + + enum SignatureAlgorithmName { + Anonymous => 0x00, + RSA => 0x01, + DSA => 0x02, + ECDSA => 0x03, + ED25519 => 0x07, + ED448 => 0x08, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::msgs::test_enum8; + + #[test] + fn test_enums() { + test_enum8::(HashAlgorithm::NONE, HashAlgorithm::SHA512); + test_enum8::(SignatureAlgorithm::Anonymous, SignatureAlgorithm::ECDSA); + } + + #[test] + fn tls13_signature_restrictions() { + // rsa-pkcs1 denied + assert!(!SignatureScheme::RSA_PKCS1_SHA1.supported_in_tls13()); + assert!(!SignatureScheme::RSA_PKCS1_SHA256.supported_in_tls13()); + assert!(!SignatureScheme::RSA_PKCS1_SHA384.supported_in_tls13()); + assert!(!SignatureScheme::RSA_PKCS1_SHA512.supported_in_tls13()); + + // dsa denied + assert!(!SignatureScheme::from(0x0201).supported_in_tls13()); + assert!(!SignatureScheme::from(0x0202).supported_in_tls13()); + assert!(!SignatureScheme::from(0x0203).supported_in_tls13()); + assert!(!SignatureScheme::from(0x0204).supported_in_tls13()); + assert!(!SignatureScheme::from(0x0205).supported_in_tls13()); + assert!(!SignatureScheme::from(0x0206).supported_in_tls13()); + + // common + assert!(SignatureScheme::ED25519.supported_in_tls13()); + assert!(SignatureScheme::ED448.supported_in_tls13()); + assert!(SignatureScheme::RSA_PSS_SHA256.supported_in_tls13()); + assert!(SignatureScheme::RSA_PSS_SHA384.supported_in_tls13()); + assert!(SignatureScheme::RSA_PSS_SHA512.supported_in_tls13()); + + // rsa_pss_rsae_* + assert!(SignatureScheme::from(0x0804).supported_in_tls13()); + assert!(SignatureScheme::from(0x0805).supported_in_tls13()); + assert!(SignatureScheme::from(0x0806).supported_in_tls13()); + + // ecdsa_brainpool* + assert!(SignatureScheme::from(0x081a).supported_in_tls13()); + assert!(SignatureScheme::from(0x081b).supported_in_tls13()); + assert!(SignatureScheme::from(0x081c).supported_in_tls13()); + + // sm2sig_sm3 (RFC 8998) + assert!(SignatureScheme::SM2_SM3.supported_in_tls13()); + } +} diff --git a/rustls/src/crypto/hash.rs b/rustls/src/crypto/hash.rs index 214dad4e0f8..767e11b56ff 100644 --- a/rustls/src/crypto/hash.rs +++ b/rustls/src/crypto/hash.rs @@ -1,6 +1,8 @@ use alloc::boxed::Box; -pub use crate::msgs::enums::HashAlgorithm; +use pki_types::FipsStatus; + +use super::enums::HashAlgorithm; /// Describes a single cryptographic hash function. /// @@ -19,9 +21,9 @@ pub trait Hash: Send + Sync { /// Which hash function this is, eg, `HashAlgorithm::SHA256`. fn algorithm(&self) -> HashAlgorithm; - /// Return `true` if this is backed by a FIPS-approved implementation. - fn fips(&self) -> bool { - false + /// Return the FIPS validation status of this implementation. + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated } } diff --git a/rustls/src/crypto/hmac.rs b/rustls/src/crypto/hmac.rs index 6960b7e65b3..16882b660a6 100644 --- a/rustls/src/crypto/hmac.rs +++ b/rustls/src/crypto/hmac.rs @@ -1,5 +1,7 @@ use alloc::boxed::Box; +use core::mem; +use pki_types::FipsStatus; use zeroize::Zeroize; /// A concrete HMAC implementation, for a single cryptographic hash function. @@ -13,30 +15,37 @@ pub trait Hmac: Send + Sync { /// Give the length of the underlying hash function. In RFC2104 terminology this is `L`. fn hash_output_len(&self) -> usize; - /// Return `true` if this is backed by a FIPS-approved implementation. - fn fips(&self) -> bool { - false + /// Return the FIPS validation status of this implementation. + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated } } -/// A HMAC tag, stored as a value. +/// A secret HMAC tag, stored as a value. +/// +/// The value is considered secret and sensitive, and is zeroized +/// on drop. +/// +/// This is suitable if the value is (for example) used as key +/// material. #[derive(Clone)] -pub struct Tag { - buf: [u8; Self::MAX_LEN], - used: usize, -} +pub struct Tag(PublicTag); impl Tag { /// Build a tag by copying a byte slice. /// /// The slice can be up to [`Tag::MAX_LEN`] bytes in length. pub fn new(bytes: &[u8]) -> Self { - let mut tag = Self { - buf: [0u8; Self::MAX_LEN], - used: bytes.len(), - }; - tag.buf[..bytes.len()].copy_from_slice(bytes); - tag + Self(PublicTag::new(bytes)) + } + + /// Declare this tag is public. + /// + /// Uses of this function should explain why this tag is public. + pub(crate) fn into_public(self) -> PublicTag { + let public = self.0.clone(); + mem::forget(self); + public } /// Maximum supported HMAC tag size: supports up to SHA512. @@ -44,12 +53,48 @@ impl Tag { } impl Drop for Tag { + #[inline(never)] fn drop(&mut self) { - self.buf.zeroize(); + self.0.buf.zeroize(); } } impl AsRef<[u8]> for Tag { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +/// A non-secret HMAC tag, stored as a value. +/// +/// A value of this type is **not** zeroized on drop. +/// +/// A tag is "public" if it is published on the wire, as opposed to +/// being used as key material. For example, the `verify_data` field +/// of TLS `Finished` messages are public (as they are published on +/// the wire in TLS1.2, or sent encrypted under pre-authenticated +/// secrets in TLS1.3). +#[derive(Clone)] +pub(crate) struct PublicTag { + buf: [u8; Tag::MAX_LEN], + used: usize, +} + +impl PublicTag { + /// Build a tag by copying a byte slice. + /// + /// The slice can be up to [`Tag::MAX_LEN`] bytes in length. + pub(crate) fn new(bytes: &[u8]) -> Self { + let mut tag = Self { + buf: [0u8; Tag::MAX_LEN], + used: bytes.len(), + }; + tag.buf[..bytes.len()].copy_from_slice(bytes); + tag + } +} + +impl AsRef<[u8]> for PublicTag { fn as_ref(&self) -> &[u8] { &self.buf[..self.used] } diff --git a/rustls/src/crypto/hpke.rs b/rustls/src/crypto/hpke.rs index a08f7ca62d0..0ac995c7195 100644 --- a/rustls/src/crypto/hpke.rs +++ b/rustls/src/crypto/hpke.rs @@ -2,13 +2,15 @@ use alloc::boxed::Box; use alloc::vec::Vec; use core::fmt::Debug; +use pki_types::FipsStatus; use zeroize::Zeroize; -use crate::msgs::enums::HpkeKem; -use crate::msgs::handshake::HpkeSymmetricCipherSuite; use crate::Error; +use crate::error::InvalidMessage; +use crate::msgs::{Codec, ListLength, Reader, TlsListElement}; /// An HPKE suite, specifying a key encapsulation mechanism and a symmetric cipher suite. +#[expect(clippy::exhaustive_structs)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct HpkeSuite { /// The choice of HPKE key encapsulation mechanism. @@ -78,9 +80,9 @@ pub trait Hpke: Debug + Send + Sync { /// on the suite's DH KEM algorithm. fn generate_key_pair(&self) -> Result<(HpkePublicKey, HpkePrivateKey), Error>; - /// Return whether the HPKE instance is FIPS compatible. - fn fips(&self) -> bool { - false + /// Return the FIPS validation status of the HPKE instance. + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated } /// Return the [HpkeSuite] that this HPKE instance supports. @@ -107,6 +109,7 @@ pub trait HpkeOpener: Debug + Send + Sync + 'static { } /// An HPKE public key. +#[expect(clippy::exhaustive_structs)] #[derive(Clone, Debug)] pub struct HpkePublicKey(pub Vec); @@ -127,12 +130,117 @@ impl From> for HpkePrivateKey { } impl Drop for HpkePrivateKey { + #[inline(never)] fn drop(&mut self) { self.0.zeroize(); } } +/// An HPKE symmetric cipher suite, combining a KDF and an AEAD algorithm. +#[expect(clippy::exhaustive_structs)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct HpkeSymmetricCipherSuite { + /// The KDF to use for this cipher suite. + pub kdf_id: HpkeKdf, + /// The AEAD to use for this cipher suite. + pub aead_id: HpkeAead, +} + +impl Codec<'_> for HpkeSymmetricCipherSuite { + fn encode(&self, bytes: &mut Vec) { + self.kdf_id.encode(bytes); + self.aead_id.encode(bytes); + } + + fn read(r: &mut Reader<'_>) -> Result { + Ok(Self { + kdf_id: HpkeKdf::read(r)?, + aead_id: HpkeAead::read(r)?, + }) + } +} + +/// RFC 9849: `HpkeSymmetricCipherSuite cipher_suites<4..2^16-4>;` +impl TlsListElement for HpkeSymmetricCipherSuite { + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("HpkeSymmetricCipherSuites"), + }; +} + +enum_builder! { + /// The Key Encapsulation Mechanism (`Kem`) type for HPKE operations. + /// Listed by IANA, as specified in [RFC 9180 Section 7.1] + /// + /// [RFC 9180 Section 7.1]: + pub struct HpkeKem(pub u16); + + enum HpkeKemName { + DHKEM_P256_HKDF_SHA256 => 0x0010, + DHKEM_P384_HKDF_SHA384 => 0x0011, + DHKEM_P521_HKDF_SHA512 => 0x0012, + DHKEM_X25519_HKDF_SHA256 => 0x0020, + DHKEM_X448_HKDF_SHA512 => 0x0021, + } +} + +enum_builder! { + /// The Key Derivation Function (`Kdf`) type for HPKE operations. + /// Listed by IANA, as specified in [RFC 9180 Section 7.2] + /// + /// [RFC 9180 Section 7.2]: + pub struct HpkeKdf(pub u16); + + enum HpkeKdfName { + HKDF_SHA256 => 0x0001, + HKDF_SHA384 => 0x0002, + HKDF_SHA512 => 0x0003, + } +} + +impl Default for HpkeKdf { + fn default() -> Self { + // TODO(XXX): revisit the default configuration. This is just what Cloudflare ships right now. + Self::HKDF_SHA256 + } +} + +enum_builder! { + /// The Authenticated Encryption with Associated Data (`Aead`) type for HPKE operations. + /// Listed by IANA, as specified in [RFC 9180 Section 7.3] + /// + /// [RFC 9180 Section 7.3]: + pub struct HpkeAead(pub u16); + + enum HpkeAeadName { + AES_128_GCM => 0x0001, + AES_256_GCM => 0x0002, + CHACHA20_POLY_1305 => 0x0003, + EXPORT_ONLY => 0xFFFF, + } +} + +impl Default for HpkeAead { + fn default() -> Self { + // TODO(XXX): revisit the default configuration. This is just what Cloudflare ships right now. + Self::AES_128_GCM + } +} + +impl HpkeAead { + /// Returns the length of the tag for the AEAD algorithm, or none if the AEAD is EXPORT_ONLY. + pub(crate) fn tag_len(&self) -> Option { + match *self { + // See RFC 9180 Section 7.3, column `Nt`, the length in bytes of the authentication tag + // for the algorithm. + // https://www.rfc-editor.org/rfc/rfc9180.html#section-7.3 + Self::AES_128_GCM | Self::AES_256_GCM | Self::CHACHA20_POLY_1305 => Some(16), + _ => None, + } + } +} + /// An HPKE key pair, made of a matching public and private key. +#[expect(clippy::exhaustive_structs)] pub struct HpkeKeyPair { /// A HPKE public key. pub public_key: HpkePublicKey, @@ -141,5 +249,6 @@ pub struct HpkeKeyPair { } /// An encapsulated secret returned from setting up a sender or receiver context. +#[expect(clippy::exhaustive_structs)] #[derive(Debug)] pub struct EncapsulatedSecret(pub Vec); diff --git a/rustls/src/msgs/ffdhe_groups.rs b/rustls/src/crypto/kx/ffdhe.rs similarity index 92% rename from rustls/src/msgs/ffdhe_groups.rs rename to rustls/src/crypto/kx/ffdhe.rs index ac105b59877..f86756a3100 100644 --- a/rustls/src/msgs/ffdhe_groups.rs +++ b/rustls/src/crypto/kx/ffdhe.rs @@ -1,51 +1,15 @@ //! This module contains parameters for FFDHE named groups as defined //! in [RFC 7919 Appendix A](https://datatracker.ietf.org/doc/html/rfc7919#appendix-A). -use crate::NamedGroup; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] /// Parameters of an FFDHE group, with Big-endian byte order +#[expect(missing_docs, clippy::exhaustive_structs)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct FfdheGroup<'a> { pub p: &'a [u8], pub g: &'a [u8], } -impl FfdheGroup<'static> { - /// Return the `FfdheGroup` corresponding to the provided `NamedGroup` - /// if it is indeed an FFDHE group - #[deprecated( - since = "0.23.13", - note = "This function is linker-unfriendly. Use `SupportedKxGroup::ffdhe_group()` instead" - )] - pub fn from_named_group(named_group: NamedGroup) -> Option { - match named_group { - NamedGroup::FFDHE2048 => Some(FFDHE2048), - NamedGroup::FFDHE3072 => Some(FFDHE3072), - NamedGroup::FFDHE4096 => Some(FFDHE4096), - NamedGroup::FFDHE6144 => Some(FFDHE6144), - NamedGroup::FFDHE8192 => Some(FFDHE8192), - _ => None, - } - } -} - impl<'a> FfdheGroup<'a> { - /// Return the `NamedGroup` for the `FfdheGroup` if it represents one. - #[deprecated( - since = "0.23.13", - note = "This function is linker-unfriendly. Use `SupportedKxGroup::name()` instead" - )] - pub fn named_group(&self) -> Option { - match *self { - FFDHE2048 => Some(NamedGroup::FFDHE2048), - FFDHE3072 => Some(NamedGroup::FFDHE3072), - FFDHE4096 => Some(NamedGroup::FFDHE4096), - FFDHE6144 => Some(NamedGroup::FFDHE6144), - FFDHE8192 => Some(NamedGroup::FFDHE8192), - _ => None, - } - } - /// Construct an `FfdheGroup` from the given `p` and `g`, trimming any potential leading zeros. pub fn from_params_trimming_leading_zeros(p: &'a [u8], g: &'a [u8]) -> Self { fn trim_leading_zeros(buf: &[u8]) -> &[u8] { @@ -308,16 +272,3 @@ pub const FFDHE8192: FfdheGroup<'static> = FfdheGroup { ], g: &[2], }; - -#[test] -fn named_group_ffdhe_group_roundtrip() { - use NamedGroup::*; - let ffdhe_groups = [FFDHE2048, FFDHE3072, FFDHE4096, FFDHE6144, FFDHE8192]; - for g in ffdhe_groups { - #[allow(deprecated)] - let roundtrip = FfdheGroup::from_named_group(g) - .unwrap() - .named_group(); - assert_eq!(roundtrip, Some(g)); - } -} diff --git a/rustls/src/crypto/kx/mod.rs b/rustls/src/crypto/kx/mod.rs new file mode 100644 index 00000000000..0cba771a74d --- /dev/null +++ b/rustls/src/crypto/kx/mod.rs @@ -0,0 +1,689 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::fmt::Debug; +use core::ops::Deref; + +use pki_types::FipsStatus; +use zeroize::Zeroize; + +use crate::enums::ProtocolVersion; +use crate::error::{Error, PeerMisbehaved}; + +pub mod ffdhe; +use ffdhe::FfdheGroup; + +/// A generalization of hybrid key exchange. +#[expect(clippy::exhaustive_structs)] +#[derive(Debug)] +pub struct Hybrid { + /// Classical key exchange component. + pub classical: &'static dyn SupportedKxGroup, + /// Post-quantum key exchange component. + pub post_quantum: &'static dyn SupportedKxGroup, + /// TLS NamedGroup for this hybrid key exchange. + pub name: NamedGroup, + /// Layout of the hybrid key exchange. + pub layout: HybridLayout, +} + +impl SupportedKxGroup for Hybrid { + fn start(&self) -> Result { + let classical = self.classical.start()?.into_single(); + let post_quantum = self.post_quantum.start()?.into_single(); + + let combined_pub_key = self + .layout + .concat(post_quantum.pub_key(), classical.pub_key()); + + Ok(StartedKeyExchange::Hybrid(Box::new(ActiveHybrid { + classical, + post_quantum, + name: self.name, + layout: self.layout, + combined_pub_key, + }))) + } + + fn start_and_complete(&self, client_share: &[u8]) -> Result { + let (post_quantum_share, classical_share) = self + .layout + .split_received_client_share(client_share) + .ok_or(PeerMisbehaved::InvalidKeyShare)?; + + let cl = self + .classical + .start_and_complete(classical_share)?; + let pq = self + .post_quantum + .start_and_complete(post_quantum_share)?; + + let combined_pub_key = self + .layout + .concat(&pq.pub_key, &cl.pub_key); + let secret = self + .layout + .concat(pq.secret.secret_bytes(), cl.secret.secret_bytes()); + + Ok(CompletedKeyExchange { + group: self.name, + pub_key: combined_pub_key, + secret: SharedSecret::from(secret), + }) + } + + fn name(&self) -> NamedGroup { + self.name + } + + fn fips(&self) -> FipsStatus { + // Behold! The Night Mare: SP800-56C rev 2: + // + // "In addition to the currently approved techniques for the generation of the + // shared secret Z as specified in SP 800-56A and SP 800-56B, this Recommendation + // permits the use of a "hybrid" shared secret of the form Z′ = Z || T, a + // concatenation consisting of a "standard" shared secret Z that was generated + // during the execution of a key-establishment scheme (as currently specified in + // [SP 800-56A] or [SP 800-56B])" + // + // NIST plan to adjust this and allow both orders: see + // (Jan 2025) lines 1070-1080. + // + // But, for now, we follow the SP800-56C logic: the element appearing first is the + // one that controls approval. + match self.layout.post_quantum_first { + true => self.post_quantum.fips(), + false => self.classical.fips(), + } + } +} + +struct ActiveHybrid { + classical: Box, + post_quantum: Box, + name: NamedGroup, + layout: HybridLayout, + combined_pub_key: Vec, +} + +impl ActiveKeyExchange for ActiveHybrid { + fn complete(self: Box, peer_pub_key: &[u8]) -> Result { + let (post_quantum_share, classical_share) = self + .layout + .split_received_server_share(peer_pub_key) + .ok_or(PeerMisbehaved::InvalidKeyShare)?; + + let cl = self + .classical + .complete(classical_share)?; + let pq = self + .post_quantum + .complete(post_quantum_share)?; + + let secret = self + .layout + .concat(pq.secret_bytes(), cl.secret_bytes()); + Ok(SharedSecret::from(secret)) + } + + fn pub_key(&self) -> &[u8] { + &self.combined_pub_key + } + + fn group(&self) -> NamedGroup { + self.name + } +} + +impl HybridKeyExchange for ActiveHybrid { + fn component(&self) -> (NamedGroup, &[u8]) { + (self.classical.group(), self.classical.pub_key()) + } + + fn complete_component(self: Box, peer_pub_key: &[u8]) -> Result { + self.classical.complete(peer_pub_key) + } + + fn into_key_exchange(self: Box) -> Box { + self + } + + fn as_key_exchange(&self) -> &(dyn ActiveKeyExchange + 'static) { + self + } +} + +/// Layout of a hybrid key exchange's key shares and secrets. +#[expect(clippy::exhaustive_structs)] +#[derive(Clone, Copy, Debug)] +pub struct HybridLayout { + /// Length of classical key share. + pub classical_share_len: usize, + + /// Length of post-quantum key share sent by client + pub post_quantum_client_share_len: usize, + + /// Length of post-quantum key share sent by server + pub post_quantum_server_share_len: usize, + + /// Whether the post-quantum element comes first in shares and secrets. + /// + /// For dismal and unprincipled reasons, SECP256R1MLKEM768 has the + /// classical element first, while X25519MLKEM768 has it second. + pub post_quantum_first: bool, +} + +impl HybridLayout { + fn split_received_client_share<'a>(&self, share: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> { + self.split(share, self.post_quantum_client_share_len) + } + + fn split_received_server_share<'a>(&self, share: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> { + self.split(share, self.post_quantum_server_share_len) + } + + /// Return the PQ and classical component of a key share. + fn split<'a>( + &self, + share: &'a [u8], + post_quantum_share_len: usize, + ) -> Option<(&'a [u8], &'a [u8])> { + if share.len() != self.classical_share_len + post_quantum_share_len { + return None; + } + + Some(match self.post_quantum_first { + true => { + let (first_share, second_share) = share.split_at(post_quantum_share_len); + (first_share, second_share) + } + false => { + let (first_share, second_share) = share.split_at(self.classical_share_len); + (second_share, first_share) + } + }) + } + + fn concat(&self, post_quantum: &[u8], classical: &[u8]) -> Vec { + match self.post_quantum_first { + true => [post_quantum, classical].concat(), + false => [classical, post_quantum].concat(), + } + } +} + +/// A supported key exchange group. +/// +/// This type carries both configuration and implementation. Specifically, +/// it has a TLS-level name expressed using the [`NamedGroup`] enum, and +/// a function which produces a [`ActiveKeyExchange`]. +/// +/// Compare with [`NamedGroup`], which carries solely a protocol identifier. +pub trait SupportedKxGroup: Send + Sync + Debug { + /// Start a key exchange. + /// + /// This will prepare an ephemeral secret key in the supported group, and a corresponding + /// public key. The key exchange can be completed by calling [`ActiveKeyExchange::complete()`] + /// or discarded. + /// + /// Most implementations will want to return the `StartedKeyExchange::Single(_)` variant. + /// Hybrid key exchange algorithms, which are constructed from two underlying algorithms, + /// may wish to return `StartedKeyExchange::Hybrid(_)` variant which additionally allows + /// one part of the key exchange to be completed separately. See the documentation + /// on [`HybridKeyExchange`] for more detail. + /// + /// # Errors + /// + /// This can fail if the random source fails during ephemeral key generation. + fn start(&self) -> Result; + + /// Start and complete a key exchange, in one operation. + /// + /// The default implementation for this calls `start()` and then calls + /// `complete()` on the result. This is suitable for Diffie-Hellman-like + /// key exchange algorithms, where there is not a data dependency between + /// our key share (named "pub_key" in this API) and the peer's (`peer_pub_key`). + /// + /// If there is such a data dependency (like key encapsulation mechanisms), this + /// function should be implemented. + fn start_and_complete(&self, peer_pub_key: &[u8]) -> Result { + let kx = self.start()?.into_single(); + + Ok(CompletedKeyExchange { + group: kx.group(), + pub_key: kx.pub_key().to_vec(), + secret: kx.complete(peer_pub_key)?, + }) + } + + /// FFDHE group the `SupportedKxGroup` operates in, if any. + /// + /// The default implementation returns `None`, so non-FFDHE groups (the + /// most common) do not need to do anything. + /// + /// FFDHE groups must implement this. [`ffdhe`] contains suitable values to return, for + /// example [`ffdhe::FFDHE2048`]. + fn ffdhe_group(&self) -> Option> { + None + } + + /// Named group the SupportedKxGroup operates in. + /// + /// If the [`NamedGroup`] type does not have a name for the algorithm you are implementing, + /// you can create one locally, eg `NamedGroup(420)`. + fn name(&self) -> NamedGroup; + + /// Return `true` if this is backed by a FIPS-approved implementation. + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated + } +} + +/// Return value from [`SupportedKxGroup::start()`]. +#[non_exhaustive] +pub enum StartedKeyExchange { + /// A single [`ActiveKeyExchange`]. + Single(Box), + /// A [`HybridKeyExchange`] that can potentially be split. + Hybrid(Box), +} + +impl StartedKeyExchange { + /// Collapses this object into its underlying [`ActiveKeyExchange`]. + /// + /// This removes the ability to do the hybrid key exchange optimization, + /// but still allows the key exchange as a whole to be completed. + pub fn into_single(self) -> Box { + match self { + Self::Single(s) => s, + Self::Hybrid(h) => h.into_key_exchange(), + } + } + + /// Accesses the [`HybridKeyExchange`], and checks it was also usable separately. + /// + /// Returns: + /// + /// - the [`HybridKeyExchange`] + /// - the stand-alone `SupportedKxGroup` for the hybrid's component group. + /// + /// This returns `None` for: + /// + /// - non-hybrid groups, + /// - if the hybrid component group is not present in `supported` + /// - if the hybrid component group is not usable with `version` + pub(crate) fn as_hybrid_checked( + &self, + supported: &[&'static dyn SupportedKxGroup], + version: ProtocolVersion, + ) -> Option<(&dyn HybridKeyExchange, &'static dyn SupportedKxGroup)> { + let Self::Hybrid(hybrid) = self else { + return None; + }; + + let component_group = hybrid.component().0; + if !component_group.usable_for_version(version) { + return None; + } + + supported + .iter() + .find(|g| g.name() == component_group) + .copied() + .map(|g| (hybrid.as_ref(), g)) + } +} + +impl Deref for StartedKeyExchange { + type Target = dyn ActiveKeyExchange; + + fn deref(&self) -> &Self::Target { + match self { + Self::Single(s) => s.as_ref(), + Self::Hybrid(h) => h.as_key_exchange(), + } + } +} + +/// An in-progress key exchange originating from a [`SupportedKxGroup`]. +pub trait ActiveKeyExchange: Send + Sync { + /// Completes the key exchange, given the peer's public key. + /// + /// This method must return an error if `peer_pub_key` is invalid: either + /// misencoded, or an invalid public key (such as, but not limited to, being + /// in a small order subgroup). + /// + /// If the key exchange algorithm is FFDHE, the result must be left-padded with zeros, + /// as required by [RFC 8446](https://www.rfc-editor.org/rfc/rfc8446#section-7.4.1) + /// (see [`complete_for_tls_version()`](Self::complete_for_tls_version) for more details). + /// + /// The shared secret is returned as a [`SharedSecret`] which can be constructed + /// from a `&[u8]`. + /// + /// This consumes and so terminates the [`ActiveKeyExchange`]. + fn complete(self: Box, peer_pub_key: &[u8]) -> Result; + + /// Completes the key exchange for the given TLS version, given the peer's public key. + /// + /// Note that finite-field Diffie–Hellman key exchange has different requirements for the derived + /// shared secret in TLS 1.2 and TLS 1.3 (ECDHE key exchange is the same in TLS 1.2 and TLS 1.3): + /// + /// In TLS 1.2, the calculated secret is required to be stripped of leading zeros + /// [(RFC 5246)](https://www.rfc-editor.org/rfc/rfc5246#section-8.1.2). + /// + /// In TLS 1.3, the calculated secret is required to be padded with leading zeros to be the same + /// byte-length as the group modulus [(RFC 8446)](https://www.rfc-editor.org/rfc/rfc8446#section-7.4.1). + /// + /// The default implementation of this method delegates to [`complete()`](Self::complete) assuming it is + /// implemented for TLS 1.3 (i.e., for FFDHE KX, removes padding as needed). Implementers of this trait + /// are encouraged to just implement [`complete()`](Self::complete) assuming TLS 1.3, and let the default + /// implementation of this method handle TLS 1.2-specific requirements. + /// + /// This method must return an error if `peer_pub_key` is invalid: either + /// misencoded, or an invalid public key (such as, but not limited to, being + /// in a small order subgroup). + /// + /// The shared secret is returned as a [`SharedSecret`] which can be constructed + /// from a `&[u8]`. + /// + /// This consumes and so terminates the [`ActiveKeyExchange`]. + fn complete_for_tls_version( + self: Box, + peer_pub_key: &[u8], + tls_version: ProtocolVersion, + ) -> Result { + if tls_version == ProtocolVersion::TLSv1_3 { + return self.complete(peer_pub_key); + } + + let group = self.group(); + let mut complete_res = self.complete(peer_pub_key)?; + if group.key_exchange_algorithm() == KeyExchangeAlgorithm::DHE { + complete_res.strip_leading_zeros(); + } + Ok(complete_res) + } + + /// Return the public key being used. + /// + /// For ECDHE, the encoding required is defined in + /// [RFC8446 section 4.2.8.2](https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.2). + /// + /// For FFDHE, the encoding required is defined in + /// [RFC8446 section 4.2.8.1](https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.1). + fn pub_key(&self) -> &[u8]; + + /// FFDHE group the `ActiveKeyExchange` is operating in. + /// + /// The default implementation returns `None`, so non-FFDHE groups (the + /// most common) do not need to do anything. + /// + /// FFDHE groups must implement this. [`ffdhe`] contains suitable values to return, for + /// example [`ffdhe::FFDHE2048`]. + fn ffdhe_group(&self) -> Option> { + None + } + + /// Return the group being used. + fn group(&self) -> NamedGroup; +} + +/// An in-progress hybrid key exchange originating from a [`SupportedKxGroup`]. +/// +/// "Hybrid" means a key exchange algorithm which is constructed from two +/// (or more) independent component algorithms. Usually one is post-quantum-secure, +/// and the other is "classical". See +/// +/// +/// There is no requirement for a hybrid scheme (or any other!) to implement +/// `HybridKeyExchange` if it is not desirable for it to be "split" like this. +/// It only enables an optimization; described below. +/// +/// # Background +/// Rustls always sends a presumptive key share in its `ClientHello`, using +/// (absent any other information) the first item in +/// [`CryptoProvider::kx_groups`][super::CryptoProvider::kx_groups]. +/// If the server accepts the client's selection, it can complete the handshake +/// using that key share. If not, the server sends a `HelloRetryRequest` instructing +/// the client to send a different key share instead. +/// +/// This request costs an extra round trip, and wastes the key exchange computation +/// (in [`SupportedKxGroup::start()`]) the client already did. We would +/// like to avoid those wastes if possible. +/// +/// It is early days for post-quantum-secure hybrid key exchange deployment. +/// This means (commonly) continuing to offer both the hybrid and classical +/// key exchanges, so the handshake can be completed without a `HelloRetryRequest` +/// for servers that support the offered hybrid or classical schemes. +/// +/// Implementing `HybridKeyExchange` enables two optimizations: +/// +/// 1. Sending both the hybrid and classical key shares in the `ClientHello`. +/// +/// 2. Performing the classical key exchange setup only once. This is important +/// because the classical key exchange setup is relatively expensive. +/// This optimization is permitted and described in +/// +/// +/// Both of these only happen if the classical algorithm appears separately in +/// the client's [`CryptoProvider::kx_groups`][super::CryptoProvider::kx_groups], +/// and if the hybrid algorithm appears first in that list. +/// +/// # How it works +/// This function is only called by rustls for clients. It is called when +/// constructing the initial `ClientHello`. rustls follows these steps: +/// +/// 1. If the return value is `None`, nothing further happens. +/// 2. If the given [`NamedGroup`] does not appear in +/// [`CryptoProvider::kx_groups`][super::CryptoProvider::kx_groups], nothing further happens. +/// 3. The given key share is added to the `ClientHello`, after the hybrid entry. +/// +/// Then, one of three things may happen when the server replies to the `ClientHello`: +/// +/// 1. The server sends a `HelloRetryRequest`. Everything is thrown away and +/// we start again. +/// 2. The server agrees to our hybrid key exchange: rustls calls +/// [`ActiveKeyExchange::complete()`] consuming `self`. +/// 3. The server agrees to our classical key exchange: rustls calls +/// [`HybridKeyExchange::complete_component()`] which +/// discards the hybrid key data, and completes just the classical key exchange. +pub trait HybridKeyExchange: ActiveKeyExchange { + /// Returns the [`NamedGroup`] and public key "share" for the component. + fn component(&self) -> (NamedGroup, &[u8]); + + /// Completes the classical component of the key exchange, given the peer's public key. + /// + /// This method must return an error if `peer_pub_key` is invalid: either + /// misencoded, or an invalid public key (such as, but not limited to, being + /// in a small order subgroup). + /// + /// The shared secret is returned as a [`SharedSecret`] which can be constructed + /// from a `&[u8]`. + /// + /// See the documentation on [`HybridKeyExchange`] for explanation. + fn complete_component(self: Box, peer_pub_key: &[u8]) -> Result; + + /// Obtain the value as a `dyn ActiveKeyExchange` + fn as_key_exchange(&self) -> &(dyn ActiveKeyExchange + 'static); + + /// Remove the ability to do hybrid key exchange on this object. + fn into_key_exchange(self: Box) -> Box; +} + +/// The result from [`SupportedKxGroup::start_and_complete()`]. +#[expect(clippy::exhaustive_structs)] +pub struct CompletedKeyExchange { + /// Which group was used. + pub group: NamedGroup, + + /// Our key share (sometimes a public key). + pub pub_key: Vec, + + /// The computed shared secret. + pub secret: SharedSecret, +} + +enum_builder! { + /// The `NamedGroup` TLS protocol enum. Values in this enum are taken + /// from the various RFCs covering TLS, and are listed by IANA. + /// + /// This enum is used for recognizing key exchange groups advertised + /// by a peer during a TLS handshake. It is **not** a list of groups that + /// Rustls supports. The supported groups are determined via the + /// [`CryptoProvider`][crate::crypto::CryptoProvider] interface. + pub struct NamedGroup(pub u16); + + enum NamedGroupName { + secp256r1 => 0x0017, + secp384r1 => 0x0018, + secp521r1 => 0x0019, + X25519 => 0x001d, + X448 => 0x001e, + /// + brainpoolP256r1tls13 => 0x001f, + /// + brainpoolP384r1tls13 => 0x0020, + /// + brainpoolP512r1tls13 => 0x0021, + /// + curveSM2 => 0x0029, + FFDHE2048 => 0x0100, + FFDHE3072 => 0x0101, + FFDHE4096 => 0x0102, + FFDHE6144 => 0x0103, + FFDHE8192 => 0x0104, + /// + MLKEM512 => 0x0200, + /// + MLKEM768 => 0x0201, + /// + MLKEM1024 => 0x0202, + /// + secp256r1MLKEM768 => 0x11eb, + /// + X25519MLKEM768 => 0x11ec, + /// + secp384r1MLKEM1024 => 0x11ed, + } +} + +impl NamedGroup { + /// Return the key exchange algorithm associated with this `NamedGroup` + pub fn key_exchange_algorithm(self) -> KeyExchangeAlgorithm { + match u16::from(self) { + x if (0x100..0x200).contains(&x) => KeyExchangeAlgorithm::DHE, + _ => KeyExchangeAlgorithm::ECDHE, + } + } + + /// Returns whether this `NamedGroup` is usable for the given protocol version. + pub fn usable_for_version(&self, version: ProtocolVersion) -> bool { + match version { + ProtocolVersion::TLSv1_3 => true, + _ => !matches!( + *self, + Self::MLKEM512 + | Self::MLKEM768 + | Self::MLKEM1024 + | Self::X25519MLKEM768 + | Self::secp256r1MLKEM768 + | Self::secp384r1MLKEM1024 + | Self::brainpoolP256r1tls13 + | Self::brainpoolP384r1tls13 + | Self::brainpoolP512r1tls13 + | Self::curveSM2 + ), + } + } +} + +/// The result from [`ActiveKeyExchange::complete()`] or [`HybridKeyExchange::complete_component()`]. +pub struct SharedSecret { + buf: Vec, + offset: usize, +} + +impl SharedSecret { + /// Returns the shared secret as a slice of bytes. + pub fn secret_bytes(&self) -> &[u8] { + &self.buf[self.offset..] + } + + /// Removes leading zeros from `secret_bytes()` by adjusting the `offset`. + /// + /// This function does not re-allocate. + fn strip_leading_zeros(&mut self) { + let start = self + .secret_bytes() + .iter() + .enumerate() + .find(|(_i, x)| **x != 0) + .map(|(i, _x)| i) + .unwrap_or_else(|| self.secret_bytes().len()); + self.offset += start; + } +} + +impl Drop for SharedSecret { + #[inline(never)] + fn drop(&mut self) { + self.buf.zeroize(); + } +} + +impl From<&[u8]> for SharedSecret { + fn from(source: &[u8]) -> Self { + Self { + buf: source.to_vec(), + offset: 0, + } + } +} + +impl From> for SharedSecret { + fn from(buf: Vec) -> Self { + Self { buf, offset: 0 } + } +} + +/// Describes supported key exchange mechanisms. +#[derive(Clone, Copy, Debug, PartialEq)] +#[non_exhaustive] +pub enum KeyExchangeAlgorithm { + /// Diffie-Hellman Key exchange (with only known parameters as defined in [RFC 7919]). + /// + /// [RFC 7919]: https://datatracker.ietf.org/doc/html/rfc7919 + DHE, + /// Key exchange performed via elliptic curve Diffie-Hellman. + ECDHE, +} + +#[cfg(test)] +mod tests { + use std::vec; + + use super::{NamedGroup, SharedSecret}; + use crate::msgs::test_enum16; + + #[test] + fn test_shared_secret_strip_leading_zeros() { + let test_cases = [ + (vec![0, 1], vec![1]), + (vec![1], vec![1]), + (vec![1, 0, 2], vec![1, 0, 2]), + (vec![0, 0, 1, 2], vec![1, 2]), + (vec![0, 0, 0], vec![]), + (vec![], vec![]), + ]; + for (buf, expected) in test_cases { + let mut secret = SharedSecret::from(&buf[..]); + assert_eq!(secret.secret_bytes(), buf); + secret.strip_leading_zeros(); + assert_eq!(secret.secret_bytes(), expected); + } + } + + #[test] + fn test_enums() { + test_enum16::(NamedGroup::secp256r1, NamedGroup::FFDHE8192); + } +} diff --git a/rustls/src/crypto/mod.rs b/rustls/src/crypto/mod.rs index c0b83995bda..cef1a26a7e9 100644 --- a/rustls/src/crypto/mod.rs +++ b/rustls/src/crypto/mod.rs @@ -1,44 +1,42 @@ +use alloc::borrow::Cow; use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec::Vec; -use core::fmt::Debug; - -use pki_types::PrivateKeyDer; -use zeroize::Zeroize; - -use crate::msgs::ffdhe_groups::FfdheGroup; -use crate::sign::SigningKey; -pub use crate::webpki::{ - verify_tls12_signature, verify_tls13_signature, verify_tls13_signature_with_raw_key, - WebPkiSupportedAlgorithms, -}; -#[cfg(all(doc, feature = "tls12"))] -use crate::Tls12CipherSuite; +use core::borrow::Borrow; +use core::fmt::{self, Debug}; +use core::hash::{Hash, Hasher}; +use core::time::Duration; + +use pki_types::{FipsStatus, PrivateKeyDer, SignatureVerificationAlgorithm}; + +use crate::crypto::kx::KeyExchangeAlgorithm; +use crate::enums::ProtocolVersion; +#[cfg(feature = "webpki")] +use crate::error::PeerMisbehaved; +use crate::error::{ApiMisuse, Error}; +use crate::msgs::ALL_KEY_EXCHANGE_ALGORITHMS; +use crate::sync::Arc; +#[cfg(feature = "webpki")] +pub use crate::webpki::{verify_tls12_signature, verify_tls13_signature}; #[cfg(doc)] -use crate::{ - client, crypto, server, sign, ClientConfig, ConfigBuilder, ServerConfig, SupportedCipherSuite, - Tls13CipherSuite, -}; -use crate::{suites, Error, NamedGroup, ProtocolVersion, SupportedProtocolVersion}; - -/// *ring* based CryptoProvider. -#[cfg(feature = "ring")] -pub mod ring; - -/// aws-lc-rs-based CryptoProvider. -#[cfg(feature = "aws_lc_rs")] -pub mod aws_lc_rs; +use crate::{ClientConfig, ConfigBuilder, ServerConfig, client, crypto, server}; +use crate::{SupportedCipherSuite, Tls12CipherSuite, Tls13CipherSuite}; /// TLS message encryption/decryption interfaces. pub mod cipher; +mod enums; +pub use enums::{CipherSuite, HashAlgorithm, SignatureAlgorithm, SignatureScheme}; + /// Hashing interfaces. pub mod hash; /// HMAC interfaces. pub mod hmac; -#[cfg(feature = "tls12")] +/// Key exchange interfaces. +pub mod kx; +use kx::{NamedGroup, SupportedKxGroup}; + /// Cryptography specific to TLS1.2. pub mod tls12; @@ -48,35 +46,37 @@ pub mod tls13; /// Hybrid public key encryption (RFC 9180). pub mod hpke; -// Message signing interfaces. Re-exported under rustls::sign. Kept crate-internal here to -// avoid having two import paths to the same types. -pub(crate) mod signer; +#[cfg(any(doc, test))] +pub(crate) mod test_provider; +#[cfg(test)] +pub(crate) use test_provider::TEST_PROVIDER; +#[cfg(doc)] +#[doc(hidden)] +pub use test_provider::TEST_PROVIDER; +#[cfg(all(test, any(target_arch = "aarch64", target_arch = "x86_64")))] +pub(crate) use test_provider::TLS13_TEST_SUITE; + +// Message signing interfaces. +mod signer; +pub use signer::{ + CertificateIdentity, Credentials, Identity, InconsistentKeys, SelectedCredential, Signer, + SigningKey, SingleCredential, public_key_to_spki, +}; -pub use crate::msgs::handshake::KeyExchangeAlgorithm; -pub use crate::rand::GetRandomFailed; pub use crate::suites::CipherSuiteCommon; /// Controls core cryptography used by rustls. /// -/// This crate comes with two built-in options, provided as -/// `CryptoProvider` structures: -/// -/// - [`crypto::aws_lc_rs::default_provider`]: (behind the `aws_lc_rs` feature, -/// which is enabled by default). This provider uses the [aws-lc-rs](https://github.com/aws/aws-lc-rs) -/// crate. The `fips` crate feature makes this option use FIPS140-3-approved cryptography. -/// - [`crypto::ring::default_provider`]: (behind the `ring` crate feature, which -/// is optional). This provider uses the [*ring*](https://github.com/briansmith/ring) -/// crate. -/// /// This structure provides defaults. Everything in it can be overridden at /// runtime by replacing field values as needed. /// /// # Using the per-process default `CryptoProvider` /// -/// There is the concept of an implicit default provider, configured at run-time once in -/// a given process. -/// -/// It is used for functions like [`ClientConfig::builder()`] and [`ServerConfig::builder()`]. +/// If it is hard to pass a specific `CryptoProvider` to all callers that need to establish +/// TLS connections, you can store a per-process `CryptoProvider` default via +/// [`CryptoProvider::install_default()`]. When initializing a `ClientConfig` or `ServerConfig` via +/// [`ClientConfig::builder()`] or [`ServerConfig::builder()`], you can obtain the installed +/// provider via [`CryptoProvider::get_default()`]. /// /// The intention is that an application can specify the [`CryptoProvider`] they wish to use /// once, and have that apply to the variety of places where their application does TLS @@ -88,50 +88,44 @@ pub use crate::suites::CipherSuiteCommon; /// - _libraries_ should use [`ClientConfig::builder()`]/[`ServerConfig::builder()`] /// or otherwise rely on the [`CryptoProvider::get_default()`] provider. /// - _applications_ should call [`CryptoProvider::install_default()`] early -/// in their `fn main()`. If _applications_ uses a custom provider based on the one built-in, -/// they can activate the `custom-provider` feature to ensure its usage. +/// in their `fn main()`. /// /// # Using a specific `CryptoProvider` /// /// Supply the provider when constructing your [`ClientConfig`] or [`ServerConfig`]: /// -/// - [`ClientConfig::builder_with_provider()`] -/// - [`ServerConfig::builder_with_provider()`] +/// - [`ClientConfig::builder()`][crate::ClientConfig::builder()] +/// - [`ServerConfig::builder()`][crate::ServerConfig::builder()] /// /// When creating and configuring a webpki-backed client or server certificate verifier, a choice of /// provider is also needed to start the configuration process: /// -/// - [`client::WebPkiServerVerifier::builder_with_provider()`] -/// - [`server::WebPkiClientVerifier::builder_with_provider()`] -/// -/// If you install a custom provider and want to avoid any accidental use of a built-in provider, the feature -/// `custom-provider` can be activated to ensure your custom provider is used everywhere -/// and not a built-in one. This will disable any implicit use of a built-in provider. +/// - [`WebPkiServerVerifier::builder()`][crate::client::WebPkiServerVerifier::builder()] +/// - [`WebPkiClientVerifier::builder()`][crate::server::WebPkiClientVerifier::builder()] /// /// # Making a custom `CryptoProvider` /// -/// Your goal will be to populate a [`crypto::CryptoProvider`] struct instance. +/// Your goal will be to populate an instance of this `CryptoProvider` struct. /// /// ## Which elements are required? /// -/// There is no requirement that the individual elements (`SupportedCipherSuite`, `SupportedKxGroup`, -/// `SigningKey`, etc.) come from the same crate. It is allowed and expected that uninteresting +/// There is no requirement that the individual elements ([`SupportedCipherSuite`], [`SupportedKxGroup`], +/// [`SigningKey`], etc.) come from the same crate. It is allowed and expected that uninteresting /// elements would be delegated back to one of the default providers (statically) or a parent /// provider (dynamically). /// /// For example, if we want to make a provider that just overrides key loading in the config builder -/// API ([`ConfigBuilder::with_single_cert`] etc.), it might look like this: +/// API (with [`ConfigBuilder::with_single_cert`], etc.), it might look like this: /// /// ``` -/// # #[cfg(feature = "aws_lc_rs")] { /// # use std::sync::Arc; -/// # mod fictious_hsm_api { pub fn load_private_key(key_der: pki_types::PrivateKeyDer<'static>) -> ! { unreachable!(); } } -/// use rustls::crypto::aws_lc_rs; +/// # mod fictitious_hsm_api { pub fn load_private_key(key_der: pki_types::PrivateKeyDer<'static>) -> ! { unreachable!(); } } /// /// pub fn provider() -> rustls::crypto::CryptoProvider { -/// rustls::crypto::CryptoProvider{ +/// # let DEFAULT_PROVIDER = panic!(); +/// rustls::crypto::CryptoProvider { /// key_provider: &HsmKeyLoader, -/// ..aws_lc_rs::default_provider() +/// ..DEFAULT_PROVIDER /// } /// } /// @@ -139,57 +133,53 @@ pub use crate::suites::CipherSuiteCommon; /// struct HsmKeyLoader; /// /// impl rustls::crypto::KeyProvider for HsmKeyLoader { -/// fn load_private_key(&self, key_der: pki_types::PrivateKeyDer<'static>) -> Result, rustls::Error> { -/// fictious_hsm_api::load_private_key(key_der) +/// fn load_private_key(&self, key_der: pki_types::PrivateKeyDer<'static>) -> Result, rustls::Error> { +/// fictitious_hsm_api::load_private_key(key_der) /// } /// } -/// # } /// ``` /// /// ## References to the individual elements /// /// The elements are documented separately: /// -/// - **Random** - see [`crypto::SecureRandom::fill()`]. +/// - **Random** - see [`SecureRandom::fill()`]. /// - **Cipher suites** - see [`SupportedCipherSuite`], [`Tls12CipherSuite`], and /// [`Tls13CipherSuite`]. -/// - **Key exchange groups** - see [`crypto::SupportedKxGroup`]. -/// - **Signature verification algorithms** - see [`crypto::WebPkiSupportedAlgorithms`]. -/// - **Authentication key loading** - see [`crypto::KeyProvider::load_private_key()`] and -/// [`sign::SigningKey`]. -/// -/// # Example code -/// -/// See [provider-example/] for a full client and server example that uses -/// cryptography from the [rust-crypto] and [dalek-cryptography] projects. -/// -/// ```shell -/// $ cargo run --example client | head -3 -/// Current ciphersuite: TLS13_CHACHA20_POLY1305_SHA256 -/// HTTP/1.1 200 OK -/// Content-Type: text/html; charset=utf-8 -/// Content-Length: 19899 -/// ``` -/// -/// [provider-example/]: https://github.com/rustls/rustls/tree/main/provider-example/ -/// [rust-crypto]: https://github.com/rustcrypto -/// [dalek-cryptography]: https://github.com/dalek-cryptography +/// - **Key exchange groups** - see [`SupportedKxGroup`]. +/// - **Signature verification algorithms** - see [`WebPkiSupportedAlgorithms`]. +/// - **Authentication key loading** - see [`KeyProvider::load_private_key()`] and +/// [`SigningKey`]. /// /// # FIPS-approved cryptography -/// The `fips` crate feature enables use of the `aws-lc-rs` crate in FIPS mode. +/// +/// Each element of a `CryptoProvider` may be implemented using FIPS-approved cryptography, +/// and the FIPS status of the overall provider is derived from the status of its elements. +/// Call [`CryptoProvider::fips()`] to determine the FIPS status of a given provider. /// /// You can verify the configuration at runtime by checking /// [`ServerConfig::fips()`]/[`ClientConfig::fips()`] return `true`. +#[expect(clippy::exhaustive_structs)] #[derive(Debug, Clone)] pub struct CryptoProvider { - /// List of supported ciphersuites, in preference order -- the first element + /// List of supported TLS1.2 cipher suites, in preference order -- the first element /// is the highest priority. /// - /// The `SupportedCipherSuite` type carries both configuration and implementation. + /// Note that the protocol version is negotiated before the cipher suite. + /// + /// The `Tls12CipherSuite` type carries both configuration and implementation. /// /// A valid `CryptoProvider` must ensure that all cipher suites are accompanied by at least /// one matching key exchange group in [`CryptoProvider::kx_groups`]. - pub cipher_suites: Vec, + pub tls12_cipher_suites: Cow<'static, [&'static Tls12CipherSuite]>, + + /// List of supported TLS1.3 cipher suites, in preference order -- the first element + /// is the highest priority. + /// + /// Note that the protocol version is negotiated before the cipher suite. + /// + /// The `Tls13CipherSuite` type carries both configuration and implementation. + pub tls13_cipher_suites: Cow<'static, [&'static Tls13CipherSuite]>, /// List of supported key exchange groups, in preference order -- the /// first element is the highest priority. @@ -198,22 +188,25 @@ pub struct CryptoProvider { /// and in TLS1.3 a key share for it is sent in the client hello. /// /// The `SupportedKxGroup` type carries both configuration and implementation. - pub kx_groups: Vec<&'static dyn SupportedKxGroup>, + pub kx_groups: Cow<'static, [&'static dyn SupportedKxGroup]>, /// List of signature verification algorithms for use with webpki. /// /// These are used for both certificate chain verification and handshake signature verification. /// /// This is called by [`ConfigBuilder::with_root_certificates()`], - /// [`server::WebPkiClientVerifier::builder_with_provider()`] and - /// [`client::WebPkiServerVerifier::builder_with_provider()`]. + /// [`server::WebPkiClientVerifier::builder()`] and + /// [`client::WebPkiServerVerifier::builder()`]. pub signature_verification_algorithms: WebPkiSupportedAlgorithms, /// Source of cryptographically secure random numbers. pub secure_random: &'static dyn SecureRandom, - /// Provider for loading private [SigningKey]s from [PrivateKeyDer]. + /// Provider for loading private [`SigningKey`]s from [`PrivateKeyDer`]. pub key_provider: &'static dyn KeyProvider, + + /// Provider for creating [`TicketProducer`]s for stateless session resumption. + pub ticketer_factory: &'static dyn TicketerFactory, } impl CryptoProvider { @@ -221,13 +214,14 @@ impl CryptoProvider { /// /// This can be called successfully at most once in any process execution. /// - /// Call this early in your process to configure which provider is used for - /// the provider. The configuration should happen before any use of - /// [`ClientConfig::builder()`] or [`ServerConfig::builder()`]. + /// After calling this, other callers can obtain a reference to the installed + /// default via [`CryptoProvider::get_default()`]. pub fn install_default(self) -> Result<(), Arc> { static_default::install_default(self) } +} +impl CryptoProvider { /// Returns the default `CryptoProvider` for this process. /// /// This will be `None` if no default has been set yet. @@ -235,522 +229,472 @@ impl CryptoProvider { static_default::get_default() } - /// An internal function that: - /// - /// - gets the pre-installed default, or - /// - installs one `from_crate_features()`, or else - /// - panics about the need to call [`CryptoProvider::install_default()`] - pub(crate) fn get_default_or_install_from_crate_features() -> &'static Arc { - if let Some(provider) = Self::get_default() { - return provider; - } - - let provider = Self::from_crate_features() - .expect("no process-level CryptoProvider available -- call CryptoProvider::install_default() before this point"); - // Ignore the error resulting from us losing a race, and accept the outcome. - let _ = provider.install_default(); - Self::get_default().unwrap() - } - - /// Returns a provider named unambiguously by rustls crate features. - /// - /// This function returns `None` if the crate features are ambiguous (ie, specify two - /// providers), or specify no providers, or the feature `custom-provider` is activated. - /// In all cases the application should explicitly specify the provider to use - /// with [`CryptoProvider::install_default`]. - fn from_crate_features() -> Option { - #[cfg(all( - feature = "ring", - not(feature = "aws_lc_rs"), - not(feature = "custom-provider") - ))] - { - return Some(ring::default_provider()); - } - - #[cfg(all( - feature = "aws_lc_rs", - not(feature = "ring"), - not(feature = "custom-provider") - ))] - { - return Some(aws_lc_rs::default_provider()); - } - - #[allow(unreachable_code)] - None - } - - /// Returns `true` if this `CryptoProvider` is operating in FIPS mode. + /// Return the FIPS validation status for this `CryptoProvider`. /// /// This covers only the cryptographic parts of FIPS approval. There are /// also TLS protocol-level recommendations made by NIST. You should /// prefer to call [`ClientConfig::fips()`] or [`ServerConfig::fips()`] /// which take these into account. - pub fn fips(&self) -> bool { + pub fn fips(&self) -> FipsStatus { let Self { - cipher_suites, + tls12_cipher_suites, + tls13_cipher_suites, kx_groups, signature_verification_algorithms, secure_random, key_provider, + ticketer_factory, } = self; - cipher_suites.iter().all(|cs| cs.fips()) - && kx_groups.iter().all(|kx| kx.fips()) - && signature_verification_algorithms.fips() - && secure_random.fips() - && key_provider.fips() - } -} -/// A source of cryptographically secure randomness. -pub trait SecureRandom: Send + Sync + Debug { - /// Fill the given buffer with random bytes. - /// - /// The bytes must be sourced from a cryptographically secure random number - /// generator seeded with good quality, secret entropy. - /// - /// This is used for all randomness required by rustls, but not necessarily - /// randomness required by the underlying cryptography library. For example: - /// [`SupportedKxGroup::start()`] requires random material to generate - /// an ephemeral key exchange key, but this is not included in the interface with - /// rustls: it is assumed that the cryptography library provides for this itself. - fn fill(&self, buf: &mut [u8]) -> Result<(), GetRandomFailed>; + let mut status = Ord::min( + signature_verification_algorithms.fips(), + secure_random.fips(), + ); + status = Ord::min(status, key_provider.fips()); + status = Ord::min(status, ticketer_factory.fips()); + for cs in tls12_cipher_suites.iter() { + status = Ord::min(status, cs.fips()); + } + for cs in tls13_cipher_suites.iter() { + status = Ord::min(status, cs.fips()); + } + for kx in kx_groups.iter() { + status = Ord::min(status, kx.fips()); + } - /// Return `true` if this is backed by a FIPS-approved implementation. - fn fips(&self) -> bool { - false + status } -} -/// A mechanism for loading private [SigningKey]s from [PrivateKeyDer]. -/// -/// This trait is intended to be used with private key material that is sourced from DER, -/// such as a private-key that may be present on-disk. It is not intended to be used with -/// keys held in hardware security modules (HSMs) or physical tokens. For these use-cases -/// see the Rustls manual section on [customizing private key usage]. -/// -/// [customizing private key usage]: -pub trait KeyProvider: Send + Sync + Debug { - /// Decode and validate a private signing key from `key_der`. - /// - /// This is used by [`ConfigBuilder::with_client_auth_cert()`], [`ConfigBuilder::with_single_cert()`], - /// and [`ConfigBuilder::with_single_cert_with_ocsp()`]. The key types and formats supported by this - /// function directly defines the key types and formats supported in those APIs. - /// - /// Return an error if the key type encoding is not supported, or if the key fails validation. - fn load_private_key( - &self, - key_der: PrivateKeyDer<'static>, - ) -> Result, Error>; + pub(crate) fn consistency_check(&self) -> Result<(), Error> { + if self.tls12_cipher_suites.is_empty() && self.tls13_cipher_suites.is_empty() { + return Err(ApiMisuse::NoCipherSuitesConfigured.into()); + } - /// Return `true` if this is backed by a FIPS-approved implementation. - /// - /// If this returns `true`, that must be the case for all possible key types - /// supported by [`KeyProvider::load_private_key()`]. - fn fips(&self) -> bool { - false - } -} + if self.kx_groups.is_empty() { + return Err(ApiMisuse::NoKeyExchangeGroupsConfigured.into()); + } -/// A supported key exchange group. -/// -/// This type carries both configuration and implementation. Specifically, -/// it has a TLS-level name expressed using the [`NamedGroup`] enum, and -/// a function which produces a [`ActiveKeyExchange`]. -/// -/// Compare with [`NamedGroup`], which carries solely a protocol identifier. -pub trait SupportedKxGroup: Send + Sync + Debug { - /// Start a key exchange. - /// - /// This will prepare an ephemeral secret key in the supported group, and a corresponding - /// public key. The key exchange can be completed by calling [ActiveKeyExchange#complete] - /// or discarded. - /// - /// # Errors - /// - /// This can fail if the random source fails during ephemeral key generation. - fn start(&self) -> Result, Error>; + // verifying DHE kx groups return their actual group + for group in self.kx_groups.iter() { + if group.name().key_exchange_algorithm() == KeyExchangeAlgorithm::DHE + && group.ffdhe_group().is_none() + { + return Err(Error::General(alloc::format!( + "SupportedKxGroup {group:?} must return Some() from `ffdhe_group()`" + ))); + } + } - /// Start and complete a key exchange, in one operation. - /// - /// The default implementation for this calls `start()` and then calls - /// `complete()` on the result. This is suitable for Diffie-Hellman-like - /// key exchange algorithms, where there is not a data dependency between - /// our key share (named "pub_key" in this API) and the peer's (`peer_pub_key`). - /// - /// If there is such a data dependency (like key encapsulation mechanisms), this - /// function should be implemented. - fn start_and_complete(&self, peer_pub_key: &[u8]) -> Result { - let kx = self.start()?; - - Ok(CompletedKeyExchange { - group: kx.group(), - pub_key: kx.pub_key().to_vec(), - secret: kx.complete(peer_pub_key)?, - }) + // verifying cipher suites have matching kx groups + let mut supported_kx_algos = Vec::with_capacity(ALL_KEY_EXCHANGE_ALGORITHMS.len()); + for group in self.kx_groups.iter() { + let kx = group.name().key_exchange_algorithm(); + if !supported_kx_algos.contains(&kx) { + supported_kx_algos.push(kx); + } + // Small optimization. We don't need to go over other key exchange groups + // if we already cover all supported key exchange algorithms + if supported_kx_algos.len() == ALL_KEY_EXCHANGE_ALGORITHMS.len() { + break; + } + } + + for cs in self.tls12_cipher_suites.iter() { + if supported_kx_algos.contains(&cs.kx) { + continue; + } + let suite_name = cs.common.suite; + return Err(Error::General(alloc::format!( + "TLS1.2 cipher suite {suite_name:?} requires {0:?} key exchange, but no {0:?}-compatible \ + key exchange groups were present in `CryptoProvider`'s `kx_groups` field", + cs.kx, + ))); + } + + Ok(()) } - /// FFDHE group the `SupportedKxGroup` operates in. - /// - /// Return `None` if this group is not a FFDHE one. - /// - /// The default implementation calls `FfdheGroup::from_named_group`: this function - /// is extremely linker-unfriendly so it is recommended all key exchange implementers - /// provide this function. - /// - /// `rustls::ffdhe_groups` contains suitable values to return from this, - /// for example [`rustls::ffdhe_groups::FFDHE2048`][crate::ffdhe_groups::FFDHE2048]. - fn ffdhe_group(&self) -> Option> { - #[allow(deprecated)] - FfdheGroup::from_named_group(self.name()) + pub(crate) fn iter_cipher_suites(&self) -> impl Iterator + '_ { + self.tls13_cipher_suites + .iter() + .copied() + .map(SupportedCipherSuite::Tls13) + .chain( + self.tls12_cipher_suites + .iter() + .copied() + .map(SupportedCipherSuite::Tls12), + ) } - /// Named group the SupportedKxGroup operates in. - /// - /// If the `NamedGroup` enum does not have a name for the algorithm you are implementing, - /// you can use [`NamedGroup::Unknown`]. - fn name(&self) -> NamedGroup; + /// We support a given TLS version if at least one ciphersuite for the version + /// is available. + pub(crate) fn supports_version(&self, v: ProtocolVersion) -> bool { + match v { + ProtocolVersion::TLSv1_2 => !self.tls12_cipher_suites.is_empty(), + ProtocolVersion::TLSv1_3 => !self.tls13_cipher_suites.is_empty(), + _ => false, + } + } - /// Return `true` if this is backed by a FIPS-approved implementation. - fn fips(&self) -> bool { - false + pub(crate) fn find_kx_group( + &self, + name: NamedGroup, + version: ProtocolVersion, + ) -> Option<&'static dyn SupportedKxGroup> { + if !name.usable_for_version(version) { + return None; + } + self.kx_groups + .iter() + .find(|skxg| skxg.name() == name) + .copied() } +} - /// Return `true` if this should be offered/selected with the given version. - /// - /// The default implementation returns true for all versions. - fn usable_for_version(&self, _version: ProtocolVersion) -> bool { - true +impl Borrow<[&'static Tls12CipherSuite]> for CryptoProvider { + fn borrow(&self) -> &[&'static Tls12CipherSuite] { + &self.tls12_cipher_suites } } -/// An in-progress key exchange originating from a [`SupportedKxGroup`]. -pub trait ActiveKeyExchange: Send + Sync { - /// Completes the key exchange, given the peer's public key. - /// - /// This method must return an error if `peer_pub_key` is invalid: either - /// mis-encoded, or an invalid public key (such as, but not limited to, being - /// in a small order subgroup). - /// - /// If the key exchange algorithm is FFDHE, the result must be left-padded with zeros, - /// as required by [RFC 8446](https://www.rfc-editor.org/rfc/rfc8446#section-7.4.1) - /// (see [`complete_for_tls_version()`](Self::complete_for_tls_version) for more details). +impl Borrow<[&'static Tls13CipherSuite]> for CryptoProvider { + fn borrow(&self) -> &[&'static Tls13CipherSuite] { + &self.tls13_cipher_suites + } +} + +/// Describes which `webpki` signature verification algorithms are supported and +/// how they map to TLS [`SignatureScheme`]s. +/// +/// Create one with [`WebPkiSupportedAlgorithms::new`], which can be done in const-context. +#[derive(Clone, Copy)] +pub struct WebPkiSupportedAlgorithms { + /// A list of all supported signature verification algorithms. /// - /// The shared secret is returned as a [`SharedSecret`] which can be constructed - /// from a `&[u8]`. + /// Used for verifying certificate chains. /// - /// This consumes and so terminates the [`ActiveKeyExchange`]. - fn complete(self: Box, peer_pub_key: &[u8]) -> Result; + /// The order of this list is not significant. It may be empty, but the default + /// certificate verifier will reject all certificates so a custom verifier will be required. + pub(crate) all: &'static [&'static dyn SignatureVerificationAlgorithm], - /// Completes the key exchange for the given TLS version, given the peer's public key. - /// - /// Note that finite-field Diffie–Hellman key exchange has different requirements for the derived - /// shared secret in TLS 1.2 and TLS 1.3 (ECDHE key exchange is the same in TLS 1.2 and TLS 1.3): + /// A mapping from TLS `SignatureScheme`s to matching webpki signature verification algorithms. /// - /// In TLS 1.2, the calculated secret is required to be stripped of leading zeros - /// [(RFC 5246)](https://www.rfc-editor.org/rfc/rfc5246#section-8.1.2). + /// This field has invariants enforced by [`Self::new()`]: /// - /// In TLS 1.3, the calculated secret is required to be padded with leading zeros to be the same - /// byte-length as the group modulus [(RFC 8446)](https://www.rfc-editor.org/rfc/rfc8446#section-7.4.1). + /// - The mappings must be non-empty. + /// - The list of verification algorithms for each mapping must be non-empty. /// - /// The default implementation of this method delegates to [`complete()`](Self::complete) assuming it is - /// implemented for TLS 1.3 (i.e., for FFDHE KX, removes padding as needed). Implementers of this trait - /// are encouraged to just implement [`complete()`](Self::complete) assuming TLS 1.3, and let the default - /// implementation of this method handle TLS 1.2-specific requirements. + /// This is one (`SignatureScheme`) to many ([`SignatureVerificationAlgorithm`]) because + /// (depending on the protocol version) there is not necessary a 1-to-1 mapping. /// - /// This method must return an error if `peer_pub_key` is invalid: either - /// mis-encoded, or an invalid public key (such as, but not limited to, being - /// in a small order subgroup). + /// For TLS1.2, all `SignatureVerificationAlgorithm`s are tried in sequence. /// - /// The shared secret is returned as a [`SharedSecret`] which can be constructed - /// from a `&[u8]`. + /// For TLS1.3, only the first is tried. /// - /// This consumes and so terminates the [`ActiveKeyExchange`]. - fn complete_for_tls_version( - self: Box, - peer_pub_key: &[u8], - tls_version: &SupportedProtocolVersion, - ) -> Result { - if tls_version.version != ProtocolVersion::TLSv1_2 { - return self.complete(peer_pub_key); + /// The supported schemes in this mapping is communicated to the peer and the order is significant. + /// The first mapping is our highest preference. + pub(crate) mapping: &'static [( + SignatureScheme, + &'static [&'static dyn SignatureVerificationAlgorithm], + )], +} + +impl WebPkiSupportedAlgorithms { + /// Creating a `WebPkiSupportedAlgorithms` and checking its consistency. + /// + /// This is intended to only be called in const context, so the panics are + /// compile-time. + pub const fn new( + all: &'static [&'static dyn SignatureVerificationAlgorithm], + mapping: &'static [( + SignatureScheme, + &'static [&'static dyn SignatureVerificationAlgorithm], + )], + ) -> Result { + let s = Self { all, mapping }; + if mapping.is_empty() { + return Err(ApiMisuse::NoSignatureVerificationAlgorithms); } - let group = self.group(); - let mut complete_res = self.complete(peer_pub_key)?; - if group.key_exchange_algorithm() == KeyExchangeAlgorithm::DHE { - complete_res.strip_leading_zeros(); + // TODO: rewrite when feature(const_iter) and feature(const_for) are available + let mut i = 0; + while i < s.mapping.len() { + if s.mapping[i].1.is_empty() { + return Err(ApiMisuse::NoSignatureVerificationAlgorithms); + } + assert!(!s.mapping[i].1.is_empty()); + i += 1; } - Ok(complete_res) + + Ok(s) } - /// For hybrid key exchanges, returns the [`NamedGroup`] and key share - /// for the classical half of this key exchange. - /// - /// There is no requirement for a hybrid scheme (or any other!) to implement - /// `hybrid_component()`. It only enables an optimization; described below. - /// - /// "Hybrid" means a key exchange algorithm which is constructed from two - /// (or more) independent component algorithms. Usually one is post-quantum-secure, - /// and the other is "classical". See - /// - /// - /// # Background - /// Rustls always sends a presumptive key share in its `ClientHello`, using - /// (absent any other information) the first item in [`CryptoProvider::kx_groups`]. - /// If the server accepts the client's selection, it can complete the handshake - /// using that key share. If not, the server sends a `HelloRetryRequest` instructing - /// the client to send a different key share instead. - /// - /// This request costs an extra round trip, and wastes the key exchange computation - /// (in [`SupportedKxGroup::start()`]) the client already did. We would - /// like to avoid those wastes if possible. - /// - /// It is early days for post-quantum-secure hybrid key exchange deployment. - /// This means (commonly) continuing to offer both the hybrid and classical - /// key exchanges, so the handshake can be completed without a `HelloRetryRequest` - /// for servers that support the offered hybrid or classical schemes. - /// - /// Implementing `hybrid_component()` enables two optimizations: - /// - /// 1. Sending both the hybrid and classical key shares in the `ClientHello`. - /// - /// 2. Performing the classical key exchange setup only once. This is important - /// because the classical key exchange setup is relatively expensive. - /// This optimization is permitted and described in - /// - /// - /// Both of these only happen if the classical algorithm appears separately in - /// the client's [`CryptoProvider::kx_groups`], and if the hybrid algorithm appears - /// first in that list. - /// - /// # How it works - /// This function is only called by rustls for clients. It is called when - /// constructing the initial `ClientHello`. rustls follows these steps: - /// - /// 1. If the return value is `None`, nothing further happens. - /// 2. If the given [`NamedGroup`] does not appear in - /// [`CryptoProvider::kx_groups`], nothing further happens. - /// 3. The given key share is added to the `ClientHello`, after the hybrid entry. - /// - /// Then, one of three things may happen when the server replies to the `ClientHello`: - /// - /// 1. The server sends a `HelloRetryRequest`. Everything is thrown away and - /// we start again. - /// 2. The server agrees to our hybrid key exchange: rustls calls - /// [`ActiveKeyExchange::complete()`] consuming `self`. - /// 3. The server agrees to our classical key exchange: rustls calls - /// [`ActiveKeyExchange::complete_hybrid_component()`] which - /// discards the hybrid key data, and completes just the classical key exchange. - fn hybrid_component(&self) -> Option<(NamedGroup, &[u8])> { - None + /// Return all the `scheme` items in `mapping`, maintaining order. + pub fn supported_schemes(&self) -> Vec { + self.mapping + .iter() + .map(|item| item.0) + .collect() } - /// Completes the classical component of the key exchange, given the peer's public key. - /// - /// This is only called if `hybrid_component` returns `Some(_)`. - /// - /// This method must return an error if `peer_pub_key` is invalid: either - /// mis-encoded, or an invalid public key (such as, but not limited to, being - /// in a small order subgroup). - /// - /// The shared secret is returned as a [`SharedSecret`] which can be constructed - /// from a `&[u8]`. - /// - /// See the documentation on [`Self::hybrid_component()`] for explanation. - fn complete_hybrid_component( - self: Box, - _peer_pub_key: &[u8], - ) -> Result { - unreachable!("only called if `hybrid_component()` implemented") + /// Return the FIPS validation status of this implementation. + pub fn fips(&self) -> FipsStatus { + let algs = self + .all + .iter() + .map(|alg| alg.fips_status()) + .min(); + let mapped = self + .mapping + .iter() + .flat_map(|(_, algs)| algs.iter().map(|alg| alg.fips_status())) + .min(); + + match (algs, mapped) { + (Some(algs), Some(mapped)) => Ord::min(algs, mapped), + (Some(status), None) | (None, Some(status)) => status, + (None, None) => FipsStatus::Unvalidated, + } } - /// Return the public key being used. - /// - /// For ECDHE, the encoding required is defined in - /// [RFC8446 section 4.2.8.2](https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.2). - /// - /// For FFDHE, the encoding required is defined in - /// [RFC8446 section 4.2.8.1](https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.1). - fn pub_key(&self) -> &[u8]; + /// Accessor for the `mapping` field. + pub fn mapping( + &self, + ) -> &'static [( + SignatureScheme, + &'static [&'static dyn SignatureVerificationAlgorithm], + )] { + self.mapping + } - /// FFDHE group the `ActiveKeyExchange` is operating in. - /// - /// Return `None` if this group is not a FFDHE one. - /// - /// The default implementation calls `FfdheGroup::from_named_group`: this function - /// is extremely linker-unfriendly so it is recommended all key exchange implementers - /// provide this function. - /// - /// `rustls::ffdhe_groups` contains suitable values to return from this, - /// for example [`rustls::ffdhe_groups::FFDHE2048`][crate::ffdhe_groups::FFDHE2048]. - fn ffdhe_group(&self) -> Option> { - #[allow(deprecated)] - FfdheGroup::from_named_group(self.group()) + /// Return the first item in `mapping` that matches `scheme`. + #[cfg(feature = "webpki")] + pub(crate) fn convert_scheme( + &self, + scheme: SignatureScheme, + ) -> Result<&[&'static dyn SignatureVerificationAlgorithm], Error> { + self.mapping + .iter() + .filter_map(|item| if item.0 == scheme { Some(item.1) } else { None }) + .next() + .ok_or_else(|| PeerMisbehaved::SignedHandshakeWithUnadvertisedSigScheme.into()) } +} - /// Return the group being used. - fn group(&self) -> NamedGroup; +impl Debug for WebPkiSupportedAlgorithms { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "WebPkiSupportedAlgorithms {{ all: [ .. ], mapping: ")?; + f.debug_list() + .entries(self.mapping.iter().map(|item| item.0)) + .finish()?; + write!(f, " }}") + } } -/// The result from [`SupportedKxGroup::start_and_complete()`]. -pub struct CompletedKeyExchange { - /// Which group was used. - pub group: NamedGroup, +impl Hash for WebPkiSupportedAlgorithms { + fn hash(&self, state: &mut H) { + let Self { all, mapping } = self; - /// Our key share (sometimes a public key). - pub pub_key: Vec, + write_algs(state, all); + state.write_usize(mapping.len()); + for (scheme, algs) in *mapping { + state.write_u16(u16::from(*scheme)); + write_algs(state, algs); + } - /// The computed shared secret. - pub secret: SharedSecret, + fn write_algs( + state: &mut H, + algs: &[&'static dyn SignatureVerificationAlgorithm], + ) { + state.write_usize(algs.len()); + for alg in algs { + state.write(alg.public_key_alg_id().as_ref()); + state.write(alg.signature_alg_id().as_ref()); + } + } + } } -/// The result from [`ActiveKeyExchange::complete`] or [`ActiveKeyExchange::complete_hybrid_component`]. -pub struct SharedSecret { - buf: Vec, - offset: usize, -} +pub(crate) mod rand { + use super::{GetRandomFailed, SecureRandom}; -impl SharedSecret { - /// Returns the shared secret as a slice of bytes. - pub fn secret_bytes(&self) -> &[u8] { - &self.buf[self.offset..] + /// Make an array of size `N` containing random material. + pub(crate) fn random_array( + secure_random: &dyn SecureRandom, + ) -> Result<[u8; N], GetRandomFailed> { + let mut v = [0; N]; + secure_random.fill(&mut v)?; + Ok(v) } - /// Removes leading zeros from `secret_bytes()` by adjusting the `offset`. - /// - /// This function does not re-allocate. - fn strip_leading_zeros(&mut self) { - let start = self - .secret_bytes() - .iter() - .enumerate() - .find(|(_i, x)| **x != 0) - .map(|(i, _x)| i) - .unwrap_or(self.secret_bytes().len()); - self.offset += start; + /// Return a uniformly random [`u32`]. + pub(crate) fn random_u32(secure_random: &dyn SecureRandom) -> Result { + Ok(u32::from_be_bytes(random_array(secure_random)?)) } -} -impl Drop for SharedSecret { - fn drop(&mut self) { - self.buf.zeroize(); + /// Return a uniformly random [`u16`]. + pub(crate) fn random_u16(secure_random: &dyn SecureRandom) -> Result { + Ok(u16::from_be_bytes(random_array(secure_random)?)) } } -impl From<&[u8]> for SharedSecret { - fn from(source: &[u8]) -> Self { - Self { - buf: source.to_vec(), - offset: 0, - } - } -} +/// Random material generation failed. +#[expect(clippy::exhaustive_structs)] +#[derive(Debug)] +pub struct GetRandomFailed; + +/// A source of cryptographically secure randomness. +pub trait SecureRandom: Send + Sync + Debug { + /// Fill the given buffer with random bytes. + /// + /// The bytes must be sourced from a cryptographically secure random number + /// generator seeded with good quality, secret entropy. + /// + /// This is used for all randomness required by rustls, but not necessarily + /// randomness required by the underlying cryptography library. For example: + /// [`SupportedKxGroup::start()`] requires random material to generate + /// an ephemeral key exchange key, but this is not included in the interface with + /// rustls: it is assumed that the cryptography library provides for this itself. + fn fill(&self, buf: &mut [u8]) -> Result<(), GetRandomFailed>; -impl From> for SharedSecret { - fn from(buf: Vec) -> Self { - Self { buf, offset: 0 } + /// Return the FIPS validation status of this implementation. + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated } } -/// This function returns a [`CryptoProvider`] that uses -/// FIPS140-3-approved cryptography. -/// -/// Using this function expresses in your code that you require -/// FIPS-approved cryptography, and will not compile if you make -/// a mistake with cargo features. -/// -/// See our [FIPS documentation](crate::manual::_06_fips) for -/// more detail. +/// A mechanism for loading private [`SigningKey`]s from [`PrivateKeyDer`]. /// -/// Install this as the process-default provider, like: +/// This trait is intended to be used with private key material that is sourced from DER, +/// such as a private-key that may be present on-disk. It is not intended to be used with +/// keys held in hardware security modules (HSMs) or physical tokens. For these use-cases +/// see the Rustls manual section on [customizing private key usage]. /// -/// ```rust -/// # #[cfg(feature = "fips")] { -/// rustls::crypto::default_fips_provider().install_default() -/// .expect("default provider already set elsewhere"); -/// # } -/// ``` +/// [customizing private key usage]: +pub trait KeyProvider: Send + Sync + Debug { + /// Decode and validate a private signing key from `key_der`. + /// + /// This is used by [`ConfigBuilder::with_client_auth_cert()`], [`ConfigBuilder::with_single_cert()`], + /// and [`ConfigBuilder::with_single_cert_with_ocsp()`]. The key types and formats supported by this + /// function directly defines the key types and formats supported in those APIs. + /// + /// Return an error if the key type encoding is not supported, or if the key fails validation. + fn load_private_key( + &self, + key_der: PrivateKeyDer<'static>, + ) -> Result, Error>; + + /// Return the FIPS validation status for this key provider. + /// + /// The returned status must cover all possible key types supported by + /// [`KeyProvider::load_private_key()`]. + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated + } +} + +/// A factory that builds [`TicketProducer`]s. /// -/// You can also use this explicitly, like: +/// These can be used in [`ServerConfig::ticketer`] to enable stateless resumption. /// -/// ```rust -/// # #[cfg(feature = "fips")] { -/// # let root_store = rustls::RootCertStore::empty(); -/// let config = rustls::ClientConfig::builder_with_provider( -/// rustls::crypto::default_fips_provider().into() -/// ) -/// .with_safe_default_protocol_versions() -/// .unwrap() -/// .with_root_certificates(root_store) -/// .with_no_client_auth(); -/// # } -/// ``` -#[cfg(all(feature = "aws_lc_rs", any(feature = "fips", docsrs)))] -#[cfg_attr(docsrs, doc(cfg(feature = "fips")))] -pub fn default_fips_provider() -> CryptoProvider { - aws_lc_rs::default_provider() +/// [`ServerConfig::ticketer`]: crate::server::ServerConfig::ticketer +pub trait TicketerFactory: Debug + Send + Sync { + /// Build a new `TicketProducer`. + fn ticketer(&self) -> Result, Error>; + + /// Return the FIPS validation status of ticketers produced from here. + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated + } +} + +/// A trait for the ability to encrypt and decrypt tickets. +pub trait TicketProducer: Debug + Send + Sync { + /// Encrypt and authenticate `plain`, returning the resulting + /// ticket. Return None if `plain` cannot be encrypted for + /// some reason: an empty ticket will be sent and the connection + /// will continue. + fn encrypt(&self, plain: &[u8]) -> Option>; + + /// Decrypt `cipher`, validating its authenticity protection + /// and recovering the plaintext. `cipher` is fully attacker + /// controlled, so this decryption must be side-channel free, + /// panic-proof, and otherwise bullet-proof. If the decryption + /// fails, return None. + fn decrypt(&self, cipher: &[u8]) -> Option>; + + /// Returns the lifetime of tickets produced now. + /// The lifetime is provided as a hint to clients that the + /// ticket will not be useful after the given time. + /// + /// This lifetime must be implemented by key rolling and + /// erasure, *not* by storing a lifetime in the ticket. + /// + /// The objective is to limit damage to forward secrecy caused + /// by tickets, not just limiting their lifetime. + fn lifetime(&self) -> Duration; } mod static_default { - #[cfg(not(feature = "std"))] - use alloc::boxed::Box; - use alloc::sync::Arc; - #[cfg(feature = "std")] use std::sync::OnceLock; - #[cfg(not(feature = "std"))] - use once_cell::race::OnceBox; - use super::CryptoProvider; + use crate::sync::Arc; - #[cfg(feature = "std")] pub(crate) fn install_default( default_provider: CryptoProvider, ) -> Result<(), Arc> { PROCESS_DEFAULT_PROVIDER.set(Arc::new(default_provider)) } - #[cfg(not(feature = "std"))] - pub(crate) fn install_default( - default_provider: CryptoProvider, - ) -> Result<(), Arc> { - PROCESS_DEFAULT_PROVIDER - .set(Box::new(Arc::new(default_provider))) - .map_err(|e| *e) - } - pub(crate) fn get_default() -> Option<&'static Arc> { PROCESS_DEFAULT_PROVIDER.get() } - #[cfg(feature = "std")] static PROCESS_DEFAULT_PROVIDER: OnceLock> = OnceLock::new(); - #[cfg(not(feature = "std"))] - static PROCESS_DEFAULT_PROVIDER: OnceBox> = OnceBox::new(); } #[cfg(test)] -mod tests { - use std::vec; - - use super::SharedSecret; - - #[test] - fn test_shared_secret_strip_leading_zeros() { - let test_cases = [ - (vec![0, 1], vec![1]), - (vec![1], vec![1]), - (vec![1, 0, 2], vec![1, 0, 2]), - (vec![0, 0, 1, 2], vec![1, 2]), - (vec![0, 0, 0], vec![]), - (vec![], vec![]), - ]; - for (buf, expected) in test_cases { - let mut secret = SharedSecret::from(&buf[..]); - assert_eq!(secret.secret_bytes(), buf); - secret.strip_leading_zeros(); - assert_eq!(secret.secret_bytes(), expected); - } +#[track_caller] +pub(crate) fn tls13_suite( + suite: CipherSuite, + provider: &CryptoProvider, +) -> &'static Tls13CipherSuite { + provider + .tls13_cipher_suites + .iter() + .find(|cs| cs.common.suite == suite) + .unwrap() +} + +#[cfg(test)] +#[track_caller] +pub(crate) fn tls12_suite( + suite: CipherSuite, + provider: &CryptoProvider, +) -> &'static Tls12CipherSuite { + provider + .tls12_cipher_suites + .iter() + .find(|cs| cs.common.suite == suite) + .unwrap() +} + +#[cfg(test)] +#[track_caller] +pub(crate) fn tls13_only(provider: CryptoProvider) -> CryptoProvider { + CryptoProvider { + tls12_cipher_suites: Cow::default(), + ..provider + } +} + +#[cfg(test)] +#[track_caller] +pub(crate) fn tls12_only(provider: CryptoProvider) -> CryptoProvider { + CryptoProvider { + tls13_cipher_suites: Cow::default(), + ..provider } } diff --git a/rustls/src/crypto/ring/mod.rs b/rustls/src/crypto/ring/mod.rs deleted file mode 100644 index 8f8ffc0053d..00000000000 --- a/rustls/src/crypto/ring/mod.rs +++ /dev/null @@ -1,195 +0,0 @@ -use alloc::sync::Arc; - -use pki_types::PrivateKeyDer; -pub(crate) use ring as ring_like; -use webpki::ring as webpki_algs; - -use crate::crypto::{CryptoProvider, KeyProvider, SecureRandom}; -use crate::enums::SignatureScheme; -use crate::rand::GetRandomFailed; -use crate::sign::SigningKey; -use crate::suites::SupportedCipherSuite; -use crate::webpki::WebPkiSupportedAlgorithms; -use crate::Error; - -/// Using software keys for authentication. -pub mod sign; - -pub(crate) mod hash; -#[cfg(any(test, feature = "tls12"))] -pub(crate) mod hmac; -pub(crate) mod kx; -pub(crate) mod quic; -#[cfg(any(feature = "std", feature = "hashbrown"))] -pub(crate) mod ticketer; -#[cfg(feature = "tls12")] -pub(crate) mod tls12; -pub(crate) mod tls13; - -/// A `CryptoProvider` backed by the [*ring*] crate. -/// -/// [*ring*]: https://github.com/briansmith/ring -pub fn default_provider() -> CryptoProvider { - CryptoProvider { - cipher_suites: DEFAULT_CIPHER_SUITES.to_vec(), - kx_groups: ALL_KX_GROUPS.to_vec(), - signature_verification_algorithms: SUPPORTED_SIG_ALGS, - secure_random: &Ring, - key_provider: &Ring, - } -} - -/// Default crypto provider. -#[derive(Debug)] -struct Ring; - -impl SecureRandom for Ring { - fn fill(&self, buf: &mut [u8]) -> Result<(), GetRandomFailed> { - use ring_like::rand::SecureRandom; - - ring_like::rand::SystemRandom::new() - .fill(buf) - .map_err(|_| GetRandomFailed) - } -} - -impl KeyProvider for Ring { - fn load_private_key( - &self, - key_der: PrivateKeyDer<'static>, - ) -> Result, Error> { - sign::any_supported_type(&key_der) - } -} - -/// The cipher suite configuration that an application should use by default. -/// -/// This will be [`ALL_CIPHER_SUITES`] sans any supported cipher suites that -/// shouldn't be enabled by most applications. -pub static DEFAULT_CIPHER_SUITES: &[SupportedCipherSuite] = ALL_CIPHER_SUITES; - -/// A list of all the cipher suites supported by the rustls *ring* provider. -pub static ALL_CIPHER_SUITES: &[SupportedCipherSuite] = &[ - // TLS1.3 suites - tls13::TLS13_AES_256_GCM_SHA384, - tls13::TLS13_AES_128_GCM_SHA256, - tls13::TLS13_CHACHA20_POLY1305_SHA256, - // TLS1.2 suites - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - #[cfg(feature = "tls12")] - tls12::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, -]; - -/// All defined cipher suites supported by *ring* appear in this module. -pub mod cipher_suite { - #[cfg(feature = "tls12")] - pub use super::tls12::{ - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - }; - pub use super::tls13::{ - TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256, - }; -} - -/// A `WebPkiSupportedAlgorithms` value that reflects webpki's capabilities when -/// compiled against *ring*. -static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms { - all: &[ - webpki_algs::ECDSA_P256_SHA256, - webpki_algs::ECDSA_P256_SHA384, - webpki_algs::ECDSA_P384_SHA256, - webpki_algs::ECDSA_P384_SHA384, - webpki_algs::ED25519, - webpki_algs::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, - webpki_algs::RSA_PSS_2048_8192_SHA384_LEGACY_KEY, - webpki_algs::RSA_PSS_2048_8192_SHA512_LEGACY_KEY, - webpki_algs::RSA_PKCS1_2048_8192_SHA256, - webpki_algs::RSA_PKCS1_2048_8192_SHA384, - webpki_algs::RSA_PKCS1_2048_8192_SHA512, - webpki_algs::RSA_PKCS1_3072_8192_SHA384, - ], - mapping: &[ - // Note: for TLS1.2 the curve is not fixed by SignatureScheme. For TLS1.3 it is. - ( - SignatureScheme::ECDSA_NISTP384_SHA384, - &[ - webpki_algs::ECDSA_P384_SHA384, - webpki_algs::ECDSA_P256_SHA384, - ], - ), - ( - SignatureScheme::ECDSA_NISTP256_SHA256, - &[ - webpki_algs::ECDSA_P256_SHA256, - webpki_algs::ECDSA_P384_SHA256, - ], - ), - (SignatureScheme::ED25519, &[webpki_algs::ED25519]), - ( - SignatureScheme::RSA_PSS_SHA512, - &[webpki_algs::RSA_PSS_2048_8192_SHA512_LEGACY_KEY], - ), - ( - SignatureScheme::RSA_PSS_SHA384, - &[webpki_algs::RSA_PSS_2048_8192_SHA384_LEGACY_KEY], - ), - ( - SignatureScheme::RSA_PSS_SHA256, - &[webpki_algs::RSA_PSS_2048_8192_SHA256_LEGACY_KEY], - ), - ( - SignatureScheme::RSA_PKCS1_SHA512, - &[webpki_algs::RSA_PKCS1_2048_8192_SHA512], - ), - ( - SignatureScheme::RSA_PKCS1_SHA384, - &[webpki_algs::RSA_PKCS1_2048_8192_SHA384], - ), - ( - SignatureScheme::RSA_PKCS1_SHA256, - &[webpki_algs::RSA_PKCS1_2048_8192_SHA256], - ), - ], -}; - -/// All defined key exchange groups supported by *ring* appear in this module. -/// -/// [`ALL_KX_GROUPS`] is provided as an array of all of these values. -pub mod kx_group { - pub use super::kx::{SECP256R1, SECP384R1, X25519}; -} - -pub use kx::ALL_KX_GROUPS; -#[cfg(any(feature = "std", feature = "hashbrown"))] -pub use ticketer::Ticketer; - -/// Compatibility shims between ring 0.16.x and 0.17.x API -mod ring_shim { - use super::ring_like; - use crate::crypto::SharedSecret; - - pub(super) fn agree_ephemeral( - priv_key: ring_like::agreement::EphemeralPrivateKey, - peer_key: &ring_like::agreement::UnparsedPublicKey<&[u8]>, - ) -> Result { - ring_like::agreement::agree_ephemeral(priv_key, peer_key, |secret| { - SharedSecret::from(secret) - }) - .map_err(|_| ()) - } -} - -pub(super) fn fips() -> bool { - false -} diff --git a/rustls/src/crypto/ring/ticketer.rs b/rustls/src/crypto/ring/ticketer.rs deleted file mode 100644 index 556293cad34..00000000000 --- a/rustls/src/crypto/ring/ticketer.rs +++ /dev/null @@ -1,381 +0,0 @@ -use alloc::boxed::Box; -use alloc::sync::Arc; -use alloc::vec::Vec; -use core::fmt; -use core::fmt::{Debug, Formatter}; -use core::sync::atomic::{AtomicUsize, Ordering}; - -use subtle::ConstantTimeEq; - -use super::ring_like::aead; -use super::ring_like::rand::{SecureRandom, SystemRandom}; -use crate::error::Error; -#[cfg(debug_assertions)] -use crate::log::debug; -use crate::polyfill::try_split_at; -use crate::rand::GetRandomFailed; -use crate::server::ProducesTickets; - -/// A concrete, safe ticket creation mechanism. -pub struct Ticketer {} - -impl Ticketer { - /// Make the recommended `Ticketer`. This produces tickets - /// with a 12 hour life and randomly generated keys. - /// - /// The encryption mechanism used is Chacha20Poly1305. - #[cfg(feature = "std")] - pub fn new() -> Result, Error> { - Ok(Arc::new(crate::ticketer::TicketRotator::new( - 6 * 60 * 60, - make_ticket_generator, - )?)) - } - - /// Make the recommended `Ticketer`. This produces tickets - /// with a 12 hour life and randomly generated keys. - /// - /// The encryption mechanism used is Chacha20Poly1305. - #[cfg(not(feature = "std"))] - pub fn new( - time_provider: &'static dyn TimeProvider, - ) -> Result, Error> { - Ok(Arc::new(crate::ticketer::TicketSwitcher::new::( - 6 * 60 * 60, - make_ticket_generator, - time_provider, - )?)) - } -} - -fn make_ticket_generator() -> Result, GetRandomFailed> { - Ok(Box::new(AeadTicketer::new()?)) -} - -/// This is a `ProducesTickets` implementation which uses -/// any *ring* `aead::Algorithm` to encrypt and authentication -/// the ticket payload. It does not enforce any lifetime -/// constraint. -struct AeadTicketer { - alg: &'static aead::Algorithm, - key: aead::LessSafeKey, - key_name: [u8; 16], - lifetime: u32, - - /// Tracks the largest ciphertext produced by `encrypt`, and - /// uses it to early-reject `decrypt` queries that are too long. - /// - /// Accepting excessively long ciphertexts means a "Partitioning - /// Oracle Attack" (see ) - /// can be more efficient, though also note that these are thought - /// to be cryptographically hard if the key is full-entropy (as it - /// is here). - maximum_ciphertext_len: AtomicUsize, -} - -impl AeadTicketer { - fn new() -> Result { - let mut key = [0u8; 32]; - SystemRandom::new() - .fill(&mut key) - .map_err(|_| GetRandomFailed)?; - - let key = aead::UnboundKey::new(TICKETER_AEAD, &key).unwrap(); - - let mut key_name = [0u8; 16]; - SystemRandom::new() - .fill(&mut key_name) - .map_err(|_| GetRandomFailed)?; - - Ok(Self { - alg: TICKETER_AEAD, - key: aead::LessSafeKey::new(key), - key_name, - lifetime: 60 * 60 * 12, - maximum_ciphertext_len: AtomicUsize::new(0), - }) - } -} - -impl ProducesTickets for AeadTicketer { - fn enabled(&self) -> bool { - true - } - - fn lifetime(&self) -> u32 { - self.lifetime - } - - /// Encrypt `message` and return the ciphertext. - fn encrypt(&self, message: &[u8]) -> Option> { - // Random nonce, because a counter is a privacy leak. - let mut nonce_buf = [0u8; 12]; - SystemRandom::new() - .fill(&mut nonce_buf) - .ok()?; - let nonce = aead::Nonce::assume_unique_for_key(nonce_buf); - let aad = aead::Aad::from(self.key_name); - - // ciphertext structure is: - // key_name: [u8; 16] - // nonce: [u8; 12] - // message: [u8, _] - // tag: [u8; 16] - - let mut ciphertext = Vec::with_capacity( - self.key_name.len() + nonce_buf.len() + message.len() + self.key.algorithm().tag_len(), - ); - ciphertext.extend(self.key_name); - ciphertext.extend(nonce_buf); - ciphertext.extend(message); - let ciphertext = self - .key - .seal_in_place_separate_tag( - nonce, - aad, - &mut ciphertext[self.key_name.len() + nonce_buf.len()..], - ) - .map(|tag| { - ciphertext.extend(tag.as_ref()); - ciphertext - }) - .ok()?; - - self.maximum_ciphertext_len - .fetch_max(ciphertext.len(), Ordering::SeqCst); - Some(ciphertext) - } - - /// Decrypt `ciphertext` and recover the original message. - fn decrypt(&self, ciphertext: &[u8]) -> Option> { - if ciphertext.len() - > self - .maximum_ciphertext_len - .load(Ordering::SeqCst) - { - #[cfg(debug_assertions)] - debug!("rejected over-length ticket"); - return None; - } - - let (alleged_key_name, ciphertext) = try_split_at(ciphertext, self.key_name.len())?; - - let (nonce, ciphertext) = try_split_at(ciphertext, self.alg.nonce_len())?; - - // checking the key_name is the expected one, *and* then putting it into the - // additionally authenticated data is duplicative. this check quickly rejects - // tickets for a different ticketer (see `TicketSwitcher`), while including it - // in the AAD ensures it is authenticated independent of that check and that - // any attempted attack on the integrity such as [^1] must happen for each - // `key_label`, not over a population of potential keys. this approach - // is overall similar to [^2]. - // - // [^1]: https://eprint.iacr.org/2020/1491.pdf - // [^2]: "Authenticated Encryption with Key Identification", fig 6 - // - if ConstantTimeEq::ct_ne(&self.key_name[..], alleged_key_name).into() { - #[cfg(debug_assertions)] - debug!("rejected ticket with wrong ticket_name"); - return None; - } - - // This won't fail since `nonce` has the required length. - let nonce = aead::Nonce::try_assume_unique_for_key(nonce).ok()?; - - let mut out = Vec::from(ciphertext); - - let plain_len = self - .key - .open_in_place(nonce, aead::Aad::from(alleged_key_name), &mut out) - .ok()? - .len(); - out.truncate(plain_len); - - Some(out) - } -} - -impl Debug for AeadTicketer { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // Note: we deliberately omit the key from the debug output. - f.debug_struct("AeadTicketer") - .field("alg", &self.alg) - .field("lifetime", &self.lifetime) - .finish() - } -} - -static TICKETER_AEAD: &aead::Algorithm = &aead::CHACHA20_POLY1305; - -#[cfg(test)] -mod tests { - use core::time::Duration; - - use pki_types::UnixTime; - - use super::*; - - #[test] - fn basic_pairwise_test() { - let t = Ticketer::new().unwrap(); - assert!(t.enabled()); - let cipher = t.encrypt(b"hello world").unwrap(); - let plain = t.decrypt(&cipher).unwrap(); - assert_eq!(plain, b"hello world"); - } - - #[test] - fn refuses_decrypt_before_encrypt() { - let t = Ticketer::new().unwrap(); - assert_eq!(t.decrypt(b"hello"), None); - } - - #[test] - fn refuses_decrypt_larger_than_largest_encryption() { - let t = Ticketer::new().unwrap(); - let mut cipher = t.encrypt(b"hello world").unwrap(); - assert_eq!(t.decrypt(&cipher), Some(b"hello world".to_vec())); - - // obviously this would never work anyway, but this - // and `cannot_decrypt_before_encrypt` exercise the - // first branch in `decrypt()` - cipher.push(0); - assert_eq!(t.decrypt(&cipher), None); - } - - #[test] - fn ticketrotator_switching_test() { - let t = Arc::new(crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap()); - let now = UnixTime::now(); - let cipher1 = t.encrypt(b"ticket 1").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - { - // Trigger new ticketer - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 10, - ))); - } - let cipher2 = t.encrypt(b"ticket 2").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - { - // Trigger new ticketer - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 20, - ))); - } - let cipher3 = t.encrypt(b"ticket 3").unwrap(); - assert!(t.decrypt(&cipher1).is_none()); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3"); - } - - #[test] - fn ticketrotator_remains_usable_over_temporary_ticketer_creation_failure() { - let mut t = crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap(); - let now = UnixTime::now(); - let cipher1 = t.encrypt(b"ticket 1").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - t.generator = fail_generator; - { - // Failed new ticketer; this means we still need to - // rotate. - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 10, - ))); - } - - // check post-failure encryption/decryption still works - let cipher2 = t.encrypt(b"ticket 2").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - - // do the rotation for real - t.generator = make_ticket_generator; - { - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 20, - ))); - } - let cipher3 = t.encrypt(b"ticket 3").unwrap(); - assert!(t.decrypt(&cipher1).is_some()); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3"); - } - - #[test] - fn ticketswitcher_switching_test() { - #[expect(deprecated)] - let t = Arc::new(crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap()); - let now = UnixTime::now(); - let cipher1 = t.encrypt(b"ticket 1").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - { - // Trigger new ticketer - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 10, - ))); - } - let cipher2 = t.encrypt(b"ticket 2").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - { - // Trigger new ticketer - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 20, - ))); - } - let cipher3 = t.encrypt(b"ticket 3").unwrap(); - assert!(t.decrypt(&cipher1).is_none()); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3"); - } - - #[test] - fn ticketswitcher_recover_test() { - #[expect(deprecated)] - let mut t = crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap(); - let now = UnixTime::now(); - let cipher1 = t.encrypt(b"ticket 1").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - t.generator = fail_generator; - { - // Failed new ticketer - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 10, - ))); - } - t.generator = make_ticket_generator; - let cipher2 = t.encrypt(b"ticket 2").unwrap(); - assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - { - // recover - t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs( - now.as_secs() + 20, - ))); - } - let cipher3 = t.encrypt(b"ticket 3").unwrap(); - assert!(t.decrypt(&cipher1).is_none()); - assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); - assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3"); - } - - #[test] - fn aeadticketer_is_debug_and_producestickets() { - use alloc::format; - - use super::*; - - let t = make_ticket_generator().unwrap(); - - let expect = format!("AeadTicketer {{ alg: {TICKETER_AEAD:?}, lifetime: 43200 }}"); - assert_eq!(format!("{:?}", t), expect); - assert!(t.enabled()); - assert_eq!(t.lifetime(), 43200); - } - - fn fail_generator() -> Result, GetRandomFailed> { - Err(GetRandomFailed) - } -} diff --git a/rustls/src/crypto/signer.rs b/rustls/src/crypto/signer.rs index 1ee932d8c40..6accf5dd76c 100644 --- a/rustls/src/crypto/signer.rs +++ b/rustls/src/crypto/signer.rs @@ -1,14 +1,378 @@ use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt::Debug; +use core::hash::{Hash, Hasher}; +use core::iter; +#[cfg(feature = "webpki")] +use pki_types::PrivateKeyDer; use pki_types::{AlgorithmIdentifier, CertificateDer, SubjectPublicKeyInfoDer}; -use crate::enums::{SignatureAlgorithm, SignatureScheme}; -use crate::error::{Error, InconsistentKeys}; -use crate::server::ParsedCertificate; -use crate::x509; +#[cfg(feature = "webpki")] +use super::CryptoProvider; +use crate::client::{ClientCredentialResolver, CredentialRequest}; +use crate::crypto::SignatureScheme; +use crate::enums::CertificateType; +use crate::error::{ApiMisuse, Error, InvalidMessage, PeerIncompatible}; +use crate::msgs::{Codec, Reader}; +use crate::server::{ClientHello, ServerCredentialResolver}; +use crate::sync::Arc; +#[cfg(feature = "webpki")] +use crate::webpki::ParsedCertificate; +use crate::{DynHasher, SignerPublicKey, x509}; + +/// Server certificate resolver which always resolves to the same identity and key. +/// +/// For use with [`ConfigBuilder::with_server_credential_resolver()`] or +/// [`ConfigBuilder::with_client_credential_resolver()`]. +/// +/// [`ConfigBuilder::with_server_credential_resolver()`]: crate::ConfigBuilder::with_server_credential_resolver +/// [`ConfigBuilder::with_client_credential_resolver()`]: crate::ConfigBuilder::with_client_credential_resolver +#[derive(Debug, Hash)] +pub struct SingleCredential { + credentials: Credentials, + types: &'static [CertificateType], +} + +impl From for SingleCredential { + fn from(credentials: Credentials) -> Self { + match &*credentials.identity { + Identity::X509(_) => Self { + credentials, + types: &[CertificateType::X509], + }, + Identity::RawPublicKey(_) => Self { + credentials, + types: &[CertificateType::RawPublicKey], + }, + } + } +} + +impl ClientCredentialResolver for SingleCredential { + fn resolve(&self, request: &CredentialRequest<'_>) -> Option { + match (&*self.credentials.identity, request.negotiated_type()) { + (Identity::X509(_), CertificateType::X509) + | (Identity::RawPublicKey(_), CertificateType::RawPublicKey) => self + .credentials + .signer(request.signature_schemes()), + _ => None, + } + } + + fn supported_certificate_types(&self) -> &'static [CertificateType] { + self.types + } + + fn hash_config(&self, h: &mut dyn Hasher) { + self.hash(&mut DynHasher(h)); + } +} + +impl ServerCredentialResolver for SingleCredential { + fn resolve(&self, client_hello: &ClientHello<'_>) -> Result { + self.credentials + .signer(client_hello.signature_schemes()) + .ok_or(Error::PeerIncompatible( + PeerIncompatible::NoSignatureSchemesInCommon, + )) + } + + fn supported_certificate_types(&self) -> &'static [CertificateType] { + self.types + } +} + +/// A packaged-together certificate chain, matching `SigningKey` and +/// optional stapled OCSP response. +/// +/// Note: this struct is also used to represent an [RFC 7250] raw public key, +/// when the client/server is configured to use raw public keys instead of +/// certificates. +/// +/// [RFC 7250]: https://tools.ietf.org/html/rfc7250 +#[non_exhaustive] +#[derive(Debug)] +pub struct Credentials { + /// The certificate chain or raw public key. + pub identity: Arc>, + /// The signing key matching the `identity`. + pub key: Box, + /// An optional OCSP response from the certificate issuer, + /// attesting to its continued validity. + pub ocsp: Option>, +} + +impl Credentials { + /// Create a new [`Credentials`] from a certificate chain and DER-encoded private key. + /// + /// Attempt to parse the private key with the given [`CryptoProvider`]'s [`KeyProvider`] and + /// verify that it matches the public key in the first certificate of the `identity` + /// if possible (if it is an `X509` identity). + /// + /// [`KeyProvider`]: crate::crypto::KeyProvider + #[cfg(feature = "webpki")] + pub fn from_der( + identity: Arc>, + key: PrivateKeyDer<'static>, + provider: &CryptoProvider, + ) -> Result { + Self::new( + identity, + provider + .key_provider + .load_private_key(key)?, + ) + } + + /// Make a new [`Credentials`], with the given identity and key. + /// + /// Yields [`Error::InconsistentKeys`] if the `identity` is `X509` and the end-entity certificate's subject + /// public key info does not match that of the `key`'s public key, or if the `key` does not + /// have a public key. + /// + /// This constructor should be used with all [`SigningKey`] implementations + /// that can provide a public key, including those provided by rustls itself. + #[cfg(feature = "webpki")] + pub fn new(identity: Arc>, key: Box) -> Result { + if let Identity::X509(CertificateIdentity { end_entity, .. }) = &*identity { + let parsed = ParsedCertificate::try_from(end_entity)?; + match (key.public_key(), parsed.subject_public_key_info()) { + (None, _) => return Err(Error::InconsistentKeys(InconsistentKeys::Unknown)), + (Some(key_spki), cert_spki) if key_spki != cert_spki => { + return Err(Error::InconsistentKeys(InconsistentKeys::KeyMismatch)); + } + _ => {} + } + }; + + Ok(Self { + identity, + key, + ocsp: None, + }) + } + + /// Make a new `Credentials` from a raw private key. + /// + /// Unlike [`Credentials::new()`], this does not check that the end-entity certificate's + /// subject key matches `key`'s public key. + /// + /// This avoids parsing the end-entity certificate, which is useful when using client + /// certificates that are not fully standards compliant, but known to usable by the peer. + pub fn new_unchecked(identity: Arc>, key: Box) -> Self { + Self { + identity, + key, + ocsp: None, + } + } + + /// Attempt to produce a `SelectedCredential` using one of the given signature schemes. + /// + /// Calls [`SigningKey::choose_scheme()`] and propagates `cert_chain` and `ocsp`. + pub fn signer(&self, sig_schemes: &[SignatureScheme]) -> Option { + Some(SelectedCredential { + identity: self.identity.clone(), + signer: self.key.choose_scheme(sig_schemes)?, + ocsp: self.ocsp.clone(), + }) + } +} + +impl Hash for Credentials { + fn hash(&self, state: &mut H) { + self.identity.hash(state); + self.ocsp.hash(state); + } +} + +/// A packaged-together certificate chain and one-time-use signer. +/// +/// This is used in the [`ClientCredentialResolver`] and [`ServerCredentialResolver`] traits +/// as the return value of their `resolve()` methods. +#[non_exhaustive] +#[derive(Debug)] +pub struct SelectedCredential { + /// The certificate chain or raw public key. + pub identity: Arc>, + /// The signing key matching the `identity`. + pub signer: Box, + /// An optional OCSP response from the certificate issuer, + /// attesting to its continued validity. + pub ocsp: Option>, +} + +/// A peer's identity, depending on the negotiated certificate type. +#[non_exhaustive] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum Identity<'a> { + /// A standard X.509 certificate chain. + /// + /// This is the most common case. + X509(CertificateIdentity<'a>), + /// A raw public key, as defined in [RFC 7250](https://tools.ietf.org/html/rfc7250). + RawPublicKey(SubjectPublicKeyInfoDer<'a>), +} + +impl<'a> Identity<'a> { + /// Create a `PeerIdentity::X509` from a certificate chain. + /// + /// Returns `None` if `cert_chain` is empty. + pub fn from_cert_chain(mut cert_chain: Vec>) -> Result { + let mut iter = cert_chain.drain(..); + let Some(first) = iter.next() else { + return Err(ApiMisuse::EmptyCertificateChain); + }; + + Ok(Self::X509(CertificateIdentity { + end_entity: first, + intermediates: iter.collect(), + })) + } + + pub(crate) fn from_peer( + mut cert_chain: Vec>, + expected: CertificateType, + ) -> Result, Error> { + let mut iter = cert_chain.drain(..); + let Some(first) = iter.next() else { + return Ok(None); + }; + + match expected { + CertificateType::X509 => Ok(Some(Self::X509(CertificateIdentity { + end_entity: first, + intermediates: iter.collect(), + }))), + CertificateType::RawPublicKey => match iter.count() { + 0 => Ok(Some(Self::RawPublicKey( + SubjectPublicKeyInfoDer::from(first.as_ref()).into_owned(), + ))), + _ => Err(PeerIncompatible::MultipleRawKeys.into()), + }, + CertificateType(ty) => Err(PeerIncompatible::UnknownCertificateType(ty).into()), + } + } + + /// Convert this `PeerIdentity` into an owned version. + pub fn into_owned(self) -> Identity<'static> { + match self { + Self::X509(id) => Identity::X509(id.into_owned()), + Self::RawPublicKey(spki) => Identity::RawPublicKey(spki.into_owned()), + } + } + + pub(crate) fn as_certificates(&'a self) -> impl Iterator> + 'a { + match self { + Self::X509(cert) => IdentityCertificateIterator::X509( + iter::once(CertificateDer::from(cert.end_entity.as_ref())).chain( + cert.intermediates + .iter() + .map(|c| CertificateDer::from(c.as_ref())), + ), + ), + Self::RawPublicKey(spki) => IdentityCertificateIterator::RawPublicKey(iter::once( + CertificateDer::from(spki.as_ref()), + )), + } + } + + /// Get the public key of this identity as a `SignerPublicKey`. + pub fn as_signer(&self) -> SignerPublicKey<'_> { + match self { + Self::X509(cert) => SignerPublicKey::X509(&cert.end_entity), + Self::RawPublicKey(spki) => SignerPublicKey::RawPublicKey(spki), + } + } +} + +impl<'a> Codec<'a> for Identity<'a> { + fn encode(&self, bytes: &mut Vec) { + match self { + Self::X509(certificates) => { + 0u8.encode(bytes); + certificates.end_entity.encode(bytes); + certificates.intermediates.encode(bytes); + } + Self::RawPublicKey(spki) => { + 1u8.encode(bytes); + spki.encode(bytes); + } + } + } + + fn read(reader: &mut Reader<'a>) -> Result { + match u8::read(reader)? { + 0 => Ok(Self::X509(CertificateIdentity { + end_entity: CertificateDer::read(reader)?.into_owned(), + intermediates: Vec::>::read(reader)? + .into_iter() + .collect(), + })), + 1 => Ok(Self::RawPublicKey( + SubjectPublicKeyInfoDer::read(reader)?.into_owned(), + )), + _ => Err(InvalidMessage::UnexpectedMessage( + "invalid PeerIdentity discriminant", + )), + } + } +} + +enum IdentityCertificateIterator { + X509(C), + RawPublicKey(R), +} + +impl<'a, C, R> Iterator for IdentityCertificateIterator +where + C: Iterator>, + R: Iterator>, +{ + type Item = CertificateDer<'a>; + + fn next(&mut self) -> Option { + match self { + Self::X509(iter) => iter.next(), + Self::RawPublicKey(iter) => iter.next(), + } + } +} + +/// Data required to verify the peer's identity. +#[non_exhaustive] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct CertificateIdentity<'a> { + /// Certificate for the entity being verified. + pub end_entity: CertificateDer<'a>, + /// All certificates other than `end_entity` received in the peer's `Certificate` message. + /// + /// It is in the same order that the peer sent them and may be empty. + pub intermediates: Vec>, +} + +impl<'a> CertificateIdentity<'a> { + /// Create a new `CertificateIdentity` from an end-entity certificate and intermediates. + pub fn new(end_entity: CertificateDer<'a>, intermediates: Vec>) -> Self { + Self { + end_entity, + intermediates, + } + } + + /// Convert this `CertificateIdentity` into an owned version. + pub fn into_owned(self) -> CertificateIdentity<'static> { + CertificateIdentity { + end_entity: self.end_entity.into_owned(), + intermediates: self + .intermediates + .into_iter() + .map(|cert| cert.into_owned()) + .collect(), + } + } +} /// An abstract signing key. /// @@ -19,17 +383,8 @@ use crate::x509; /// `Arc`. There are no concrete public structs in Rustls /// that implement this trait. /// -/// There are two main ways to get a signing key: -/// -/// - [`KeyProvider::load_private_key()`], or -/// - some other method outside of the `KeyProvider` extension trait, -/// for instance: -/// - [`crypto::ring::sign::any_ecdsa_type()`] -/// - [`crypto::ring::sign::any_eddsa_type()`] -/// - [`crypto::ring::sign::any_supported_type()`] -/// - [`crypto::aws_lc_rs::sign::any_ecdsa_type()`] -/// - [`crypto::aws_lc_rs::sign::any_eddsa_type()`] -/// - [`crypto::aws_lc_rs::sign::any_supported_type()`] +/// You can obtain a `SigningKey` by calling the [`KeyProvider::load_private_key()`] +/// method, which is usually referenced via [`CryptoProvider::key_provider`]. /// /// The `KeyProvider` method `load_private_key()` is called under the hood by /// [`ConfigBuilder::with_single_cert()`], @@ -37,23 +392,17 @@ use crate::x509; /// [`ConfigBuilder::with_single_cert_with_ocsp()`]. /// /// A signing key created outside of the `KeyProvider` extension trait can be used -/// to create a [`CertifiedKey`], which in turn can be used to create a -/// [`ResolvesServerCertUsingSni`]. Alternately, a `CertifiedKey` can be returned from a -/// custom implementation of the [`ResolvesServerCert`] or [`ResolvesClientCert`] traits. +/// to create a [`Credentials`], which in turn can be used to create a +/// [`ServerNameResolver`]. Alternately, a `Credentials` can be returned from a +/// custom implementation of the [`ServerCredentialResolver`] or [`ClientCredentialResolver`] traits. /// /// [`KeyProvider::load_private_key()`]: crate::crypto::KeyProvider::load_private_key /// [`ConfigBuilder::with_single_cert()`]: crate::ConfigBuilder::with_single_cert /// [`ConfigBuilder::with_single_cert_with_ocsp()`]: crate::ConfigBuilder::with_single_cert_with_ocsp /// [`ConfigBuilder::with_client_auth_cert()`]: crate::ConfigBuilder::with_client_auth_cert -/// [`crypto::ring::sign::any_ecdsa_type()`]: crate::crypto::ring::sign::any_ecdsa_type -/// [`crypto::ring::sign::any_eddsa_type()`]: crate::crypto::ring::sign::any_eddsa_type -/// [`crypto::ring::sign::any_supported_type()`]: crate::crypto::ring::sign::any_supported_type -/// [`crypto::aws_lc_rs::sign::any_ecdsa_type()`]: crate::crypto::aws_lc_rs::sign::any_ecdsa_type -/// [`crypto::aws_lc_rs::sign::any_eddsa_type()`]: crate::crypto::aws_lc_rs::sign::any_eddsa_type -/// [`crypto::aws_lc_rs::sign::any_supported_type()`]: crate::crypto::aws_lc_rs::sign::any_supported_type -/// [`ResolvesServerCertUsingSni`]: crate::server::ResolvesServerCertUsingSni -/// [`ResolvesServerCert`]: crate::server::ResolvesServerCert -/// [`ResolvesClientCert`]: crate::client::ResolvesClientCert +/// [`ServerNameResolver`]: crate::server::ServerNameResolver +/// [`ServerCredentialResolver`]: crate::server::ServerCredentialResolver +/// [`ClientCredentialResolver`]: crate::client::ClientCredentialResolver pub trait SigningKey: Debug + Send + Sync { /// Choose a `SignatureScheme` from those offered. /// @@ -61,14 +410,11 @@ pub trait SigningKey: Debug + Send + Sync { /// using the chosen scheme. fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option>; - /// Get the RFC 5280-compliant SubjectPublicKeyInfo (SPKI) of this [`SigningKey`] if available. - fn public_key(&self) -> Option> { - // Opt-out by default - None - } - - /// What kind of key we have. - fn algorithm(&self) -> SignatureAlgorithm; + /// Get the RFC 5280-compliant SubjectPublicKeyInfo (SPKI) of this [`SigningKey`]. + /// + /// If an implementation does not have the ability to derive this, + /// it can return `None`. + fn public_key(&self) -> Option>; } /// A thing that can sign a message. @@ -79,70 +425,17 @@ pub trait Signer: Debug + Send + Sync { /// implicit in [`Self::scheme()`]. /// /// The returned signature format is also defined by [`Self::scheme()`]. - fn sign(&self, message: &[u8]) -> Result, Error>; + fn sign(self: Box, message: &[u8]) -> Result, Error>; /// Reveals which scheme will be used when you call [`Self::sign()`]. fn scheme(&self) -> SignatureScheme; } -/// A packaged-together certificate chain, matching `SigningKey` and -/// optional stapled OCSP response. +/// Convert a public key and algorithm identifier into [`SubjectPublicKeyInfoDer`]. /// -/// Note: this struct is also used to represent an [RFC 7250] raw public key, -/// when the client/server is configured to use raw public keys instead of -/// certificates. -/// -/// [RFC 7250]: https://tools.ietf.org/html/rfc7250 -#[derive(Clone, Debug)] -pub struct CertifiedKey { - /// The certificate chain or raw public key. - pub cert: Vec>, - - /// The certified key. - pub key: Arc, - - /// An optional OCSP response from the certificate issuer, - /// attesting to its continued validity. - pub ocsp: Option>, -} - -impl CertifiedKey { - /// Make a new CertifiedKey, with the given chain and key. - /// - /// The cert chain must not be empty. The first certificate in the chain - /// must be the end-entity certificate. - pub fn new(cert: Vec>, key: Arc) -> Self { - Self { - cert, - key, - ocsp: None, - } - } - - /// Verify the consistency of this [`CertifiedKey`]'s public and private keys. - /// This is done by performing a comparison of SubjectPublicKeyInfo bytes. - pub fn keys_match(&self) -> Result<(), Error> { - let Some(key_spki) = self.key.public_key() else { - return Err(InconsistentKeys::Unknown.into()); - }; - - let cert = ParsedCertificate::try_from(self.end_entity_cert()?)?; - match key_spki == cert.subject_public_key_info() { - true => Ok(()), - false => Err(InconsistentKeys::KeyMismatch.into()), - } - } - - /// The end-entity certificate. - pub fn end_entity_cert(&self) -> Result<&CertificateDer<'_>, Error> { - self.cert - .first() - .ok_or(Error::NoCertificatesPresented) - } -} - -#[cfg_attr(not(any(feature = "aws_lc_rs", feature = "ring")), allow(dead_code))] -pub(crate) fn public_key_to_spki( +/// In the returned encoding, `alg_id` is used as the `algorithm` field, and `public_key` is +/// wrapped inside an ASN.1 `BIT STRING` and then used as the `subjectPublicKey` field. +pub fn public_key_to_spki( alg_id: &AlgorithmIdentifier, public_key: impl AsRef<[u8]>, ) -> SubjectPublicKeyInfoDer<'static> { @@ -165,3 +458,20 @@ pub(crate) fn public_key_to_spki( SubjectPublicKeyInfoDer::from(spki) } + +/// Specific failure cases from [`Credentials::new()`] or a [`crate::crypto::SigningKey`] that cannot produce a corresponding public key. +/// +/// [`Credentials::new()`]: crate::crypto::Credentials::new() +#[non_exhaustive] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum InconsistentKeys { + /// The public key returned by the [`SigningKey`] does not match the public key information in the certificate. + /// + /// [`SigningKey`]: crate::crypto::SigningKey + KeyMismatch, + + /// The [`SigningKey`] cannot produce its corresponding public key. + /// + /// [`SigningKey`]: crate::crypto::SigningKey + Unknown, +} diff --git a/rustls/src/crypto/test_provider.rs b/rustls/src/crypto/test_provider.rs new file mode 100644 index 00000000000..fbc44f46047 --- /dev/null +++ b/rustls/src/crypto/test_provider.rs @@ -0,0 +1,579 @@ +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; +use core::time::Duration; +use std::borrow::Cow; + +use crate::crypto::cipher::{ + AeadKey, EncodedMessage, InboundOpaque, Iv, KeyBlockShape, MessageDecrypter, MessageEncrypter, + OutboundOpaque, OutboundPlain, Tls12AeadAlgorithm, Tls13AeadAlgorithm, + UnsupportedOperationError, +}; +use crate::crypto::kx::{ + KeyExchangeAlgorithm, NamedGroup, SharedSecret, StartedKeyExchange, SupportedKxGroup, +}; +use crate::crypto::{ + self, CipherSuite, CipherSuiteCommon, GetRandomFailed, HashAlgorithm, SignatureScheme, + TicketProducer, WebPkiSupportedAlgorithms, hash, hmac, tls12, tls13, +}; +use crate::enums::{ContentType, ProtocolVersion}; +use crate::error::PeerMisbehaved; +use crate::pki_types::{ + AlgorithmIdentifier, InvalidSignature, PrivateKeyDer, SignatureVerificationAlgorithm, + SubjectPublicKeyInfoDer, alg_id, +}; +use crate::sync::Arc; +use crate::{ConnectionTrafficSecrets, Error, Tls12CipherSuite, Tls13CipherSuite}; + +/// This is a `CryptoProvider` that provides NO SECURITY and is for testing only. +#[cfg_attr(not(doc), expect(unreachable_pub))] +pub const TEST_PROVIDER: crypto::CryptoProvider = crypto::CryptoProvider { + tls12_cipher_suites: Cow::Borrowed(&[TLS_TEST_SUITE]), + tls13_cipher_suites: Cow::Borrowed(&[TLS13_TEST_SUITE]), + kx_groups: Cow::Borrowed(&[KEY_EXCHANGE_GROUP]), + signature_verification_algorithms: VERIFY_ALGORITHMS, + secure_random: &Provider, + key_provider: &Provider, + ticketer_factory: &Provider, +}; + +#[derive(Debug)] +struct Provider; + +impl crypto::SecureRandom for Provider { + fn fill(&self, bytes: &mut [u8]) -> Result<(), GetRandomFailed> { + for (out, value) in bytes + .iter_mut() + .zip(RAND.iter().cycle()) + { + *out = *value; + } + Ok(()) + } +} + +const RAND: &[u8] = b"Rand"; + +impl crypto::KeyProvider for Provider { + fn load_private_key( + &self, + _key_der: PrivateKeyDer<'static>, + ) -> Result, Error> { + Ok(Box::new(SigningKey)) + } +} + +impl crypto::TicketerFactory for Provider { + fn ticketer(&self) -> Result, Error> { + Ok(Arc::new(Ticketer)) + } +} + +pub(crate) const TLS13_TEST_SUITE: &Tls13CipherSuite = &Tls13CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite(0xff13), + hash_provider: FAKE_HASH, + confidentiality_limit: u64::MAX, + }, + protocol_version: crate::version::TLS13_VERSION, + hkdf_provider: &tls13::HkdfUsingHmac(FAKE_HMAC), + aead_alg: &Aead, + quic: None, +}; + +const TLS_TEST_SUITE: &Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite(0xff12), + hash_provider: FAKE_HASH, + confidentiality_limit: u64::MAX, + }, + protocol_version: crate::version::TLS12_VERSION, + kx: KeyExchangeAlgorithm::ECDHE, + sign: &[SIGNATURE_SCHEME], + prf_provider: &tls12::PrfUsingHmac(FAKE_HMAC), + aead_alg: &Aead, +}; + +#[derive(Debug, Default)] +struct Ticketer; + +impl TicketProducer for Ticketer { + fn encrypt(&self, plain: &[u8]) -> Option> { + Some(plain.to_vec()) + } + + fn decrypt(&self, cipher: &[u8]) -> Option> { + Some(cipher.to_vec()) + } + + fn lifetime(&self) -> Duration { + Duration::from_secs(60 * 60 * 6) + } +} + +#[cfg(all(not(doc), any(target_arch = "aarch64", target_arch = "x86_64")))] +pub(crate) use hash_impl::SHA256; +pub(crate) use hash_impl::{FAKE_HASH, FAKE_HMAC}; + +#[cfg(all(not(doc), any(target_arch = "aarch64", target_arch = "x86_64")))] +mod hash_impl { + use graviola::hashing::sha2::Sha256Context; + use graviola::hashing::{Hash as _, HashContext as _}; + + use super::*; + + pub(crate) const FAKE_HASH: &dyn hash::Hash = SHA256; + pub(crate) const SHA256: &'static dyn hash::Hash = &graviola::hashing::Sha256; + + impl hash::Hash for graviola::hashing::Sha256 { + fn start(&self) -> Box { + Box::new(Sha256Context::new()) + } + + fn hash(&self, data: &[u8]) -> hash::Output { + let mut cx = Self::new(); + cx.update(data); + hash::Output::new(cx.finish().as_ref()) + } + + fn output_len(&self) -> usize { + Sha256Context::OUTPUT_SZ + } + + fn algorithm(&self) -> HashAlgorithm { + HashAlgorithm::SHA256 + } + } + + impl hash::Context for Sha256Context { + fn fork_finish(&self) -> hash::Output { + hash::Output::new(self.clone().finish().as_ref()) + } + + fn fork(&self) -> Box { + Box::new(self.clone()) + } + + fn finish(self: Box) -> hash::Output { + hash::Output::new((*self).finish().as_ref()) + } + + fn update(&mut self, data: &[u8]) { + self.update(data); + } + } + + pub(crate) const FAKE_HMAC: &dyn hmac::Hmac = &Sha256Hmac; + + pub(super) struct Sha256Hmac; + + impl hmac::Hmac for Sha256Hmac { + fn with_key(&self, key: &[u8]) -> Box { + Box::new(Sha256HmacKey(graviola::hashing::hmac::Hmac::< + graviola::hashing::Sha256, + >::new(key))) + } + + fn hash_output_len(&self) -> usize { + Sha256Context::OUTPUT_SZ + } + } + + struct Sha256HmacKey(graviola::hashing::hmac::Hmac); + + impl hmac::Key for Sha256HmacKey { + fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> hmac::Tag { + let mut ctx = self.0.clone(); + ctx.update(first); + for m in middle { + ctx.update(m); + } + ctx.update(last); + hmac::Tag::new(ctx.finish().as_ref()) + } + + fn tag_len(&self) -> usize { + Sha256Context::OUTPUT_SZ + } + } +} + +#[cfg(any(doc, not(any(target_arch = "aarch64", target_arch = "x86_64"))))] +mod hash_impl { + use super::*; + + pub(crate) const FAKE_HASH: &dyn hash::Hash = &Hash; + + pub(super) struct Hash; + + impl hash::Hash for Hash { + fn start(&self) -> Box { + Box::new(HashContext) + } + + fn hash(&self, _data: &[u8]) -> hash::Output { + hash::Output::new(HASH_OUTPUT) + } + + fn algorithm(&self) -> HashAlgorithm { + HashAlgorithm::from(0xff) + } + + fn output_len(&self) -> usize { + 32 + } + } + + struct HashContext; + + impl hash::Context for HashContext { + fn fork_finish(&self) -> hash::Output { + self.fork().finish() + } + + fn fork(&self) -> Box { + Box::new(Self) + } + + fn finish(self: Box) -> hash::Output { + hash::Output::new(HASH_OUTPUT) + } + + fn update(&mut self, _data: &[u8]) {} + } + + const HASH_OUTPUT: &[u8] = b"HashHashHashHashHashHashHashHash"; + + pub(crate) const FAKE_HMAC: &dyn hmac::Hmac = &Hmac; + + pub(super) struct Hmac; + + impl hmac::Hmac for Hmac { + fn with_key(&self, _key: &[u8]) -> Box { + Box::new(HmacKey) + } + + fn hash_output_len(&self) -> usize { + HASH_OUTPUT.len() + } + } + + struct HmacKey; + + impl hmac::Key for HmacKey { + fn sign_concat(&self, _first: &[u8], _middle: &[&[u8]], _last: &[u8]) -> hmac::Tag { + hmac::Tag::new(HMAC_OUTPUT) + } + + fn tag_len(&self) -> usize { + HMAC_OUTPUT.len() + } + } + + const HMAC_OUTPUT: &[u8] = b"HmacHmacHmacHmacHmacHmacHmacHmac"; +} + +struct ActiveKeyExchange(NamedGroup); + +impl crypto::kx::ActiveKeyExchange for ActiveKeyExchange { + fn complete(self: Box, peer: &[u8]) -> Result { + match peer { + KX_PEER_SHARE => Ok(SharedSecret::from(KX_SHARED_SECRET)), + _ => Err(Error::from(PeerMisbehaved::InvalidKeyShare)), + } + } + + fn pub_key(&self) -> &[u8] { + KX_PEER_SHARE + } + + fn group(&self) -> NamedGroup { + self.0 + } +} + +const KEY_EXCHANGE_GROUP: &dyn SupportedKxGroup = &FakeKeyExchangeGroup(NamedGroup(0xfe00)); + +#[derive(Debug)] +pub(crate) struct FakeKeyExchangeGroup(pub(crate) NamedGroup); + +impl SupportedKxGroup for FakeKeyExchangeGroup { + fn start(&self) -> Result { + Ok(StartedKeyExchange::Single(Box::new(ActiveKeyExchange( + self.0, + )))) + } + + fn name(&self) -> NamedGroup { + self.0 + } +} + +const KX_PEER_SHARE: &[u8] = b"KxPeerShareKxPeerShareKxPeerShare"; +const KX_SHARED_SECRET: &[u8] = b"KxSharedSecretKxSharedSecret"; + +struct Aead; + +impl Tls13AeadAlgorithm for Aead { + fn encrypter(&self, _key: AeadKey, _iv: Iv) -> Box { + Box::new(Tls13Cipher) + } + + fn decrypter(&self, _key: AeadKey, _iv: Iv) -> Box { + Box::new(Tls13Cipher) + } + + fn key_len(&self) -> usize { + 16 + } + + fn extract_keys( + &self, + _key: AeadKey, + _iv: Iv, + ) -> Result { + Err(UnsupportedOperationError) + } +} + +impl Tls12AeadAlgorithm for Aead { + fn encrypter(&self, _key: AeadKey, _iv: &[u8], _: &[u8]) -> Box { + Box::new(Tls12Cipher) + } + + fn decrypter(&self, _key: AeadKey, _iv: &[u8]) -> Box { + Box::new(Tls12Cipher) + } + + fn key_block_shape(&self) -> KeyBlockShape { + KeyBlockShape { + enc_key_len: 32, + fixed_iv_len: 12, + explicit_nonce_len: 0, + } + } + + fn extract_keys( + &self, + _key: AeadKey, + _iv: &[u8], + _explicit: &[u8], + ) -> Result { + Err(UnsupportedOperationError) + } +} + +struct Tls13Cipher; + +impl MessageEncrypter for Tls13Cipher { + fn encrypt( + &mut self, + m: EncodedMessage>, + seq: u64, + ) -> Result, Error> { + let total_len = self.encrypted_payload_len(m.payload.len()); + let mut payload = OutboundOpaque::with_capacity(total_len); + + payload.extend_from_chunks(&m.payload); + payload.extend_from_slice(&m.typ.to_array()); + + for (p, mask) in payload + .as_mut() + .iter_mut() + .zip(AEAD_MASK.iter().cycle()) + { + *p ^= *mask; + } + + payload.extend_from_slice(&seq.to_be_bytes()); + payload.extend_from_slice(AEAD_TAG); + + Ok(EncodedMessage { + typ: ContentType::ApplicationData, + version: ProtocolVersion::TLSv1_2, + payload, + }) + } + + fn encrypted_payload_len(&self, payload_len: usize) -> usize { + payload_len + 1 + AEAD_OVERHEAD + } +} + +impl MessageDecrypter for Tls13Cipher { + fn decrypt<'a>( + &mut self, + mut m: EncodedMessage>, + seq: u64, + ) -> Result, Error> { + let payload = &mut m.payload; + + let mut expected_tag = vec![]; + expected_tag.extend_from_slice(&seq.to_be_bytes()); + expected_tag.extend_from_slice(AEAD_TAG); + + if payload.len() < AEAD_OVERHEAD + || payload.as_ref()[payload.len() - AEAD_OVERHEAD..] != expected_tag + { + return Err(Error::DecryptError); + } + + payload.truncate(payload.len() - AEAD_OVERHEAD); + + for (p, mask) in payload + .as_mut() + .iter_mut() + .zip(AEAD_MASK.iter().cycle()) + { + *p ^= *mask; + } + + m.into_tls13_unpadded_message() + } +} + +struct Tls12Cipher; + +impl MessageEncrypter for Tls12Cipher { + fn encrypt( + &mut self, + m: EncodedMessage>, + seq: u64, + ) -> Result, Error> { + let total_len = self.encrypted_payload_len(m.payload.len()); + let mut payload = OutboundOpaque::with_capacity(total_len); + payload.extend_from_chunks(&m.payload); + + for (p, mask) in payload + .as_mut() + .iter_mut() + .zip(AEAD_MASK.iter().cycle()) + { + *p ^= *mask; + } + + payload.extend_from_slice(&seq.to_be_bytes()); + payload.extend_from_slice(AEAD_TAG); + + Ok(EncodedMessage { + typ: m.typ, + version: m.version, + payload, + }) + } + + fn encrypted_payload_len(&self, payload_len: usize) -> usize { + payload_len + AEAD_OVERHEAD + } +} + +impl MessageDecrypter for Tls12Cipher { + fn decrypt<'a>( + &mut self, + mut m: EncodedMessage>, + seq: u64, + ) -> Result, Error> { + let payload = &mut m.payload; + + let mut expected_tag = vec![]; + expected_tag.extend_from_slice(&seq.to_be_bytes()); + expected_tag.extend_from_slice(AEAD_TAG); + + if payload.len() < AEAD_OVERHEAD + || payload.as_ref()[payload.len() - AEAD_OVERHEAD..] != expected_tag + { + return Err(Error::DecryptError); + } + + payload.truncate(payload.len() - AEAD_OVERHEAD); + + for (p, mask) in payload + .as_mut() + .iter_mut() + .zip(AEAD_MASK.iter().cycle()) + { + *p ^= *mask; + } + + Ok(m.into_plain_message()) + } +} + +const AEAD_MASK: &[u8] = b"AeadMaskPattern"; +const AEAD_TAG: &[u8] = b"AeadTagA"; +const AEAD_OVERHEAD: usize = 16; + +static VERIFY_ALGORITHMS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms { + all: &[VERIFY_ALGORITHM], + mapping: &[(SIGNATURE_SCHEME, &[VERIFY_ALGORITHM])], +}; + +static VERIFY_ALGORITHM: &dyn SignatureVerificationAlgorithm = &VerifyAlgorithm; + +#[derive(Debug)] +struct VerifyAlgorithm; + +impl SignatureVerificationAlgorithm for VerifyAlgorithm { + fn public_key_alg_id(&self) -> AlgorithmIdentifier { + alg_id::ECDSA_P256 + } + + fn signature_alg_id(&self) -> AlgorithmIdentifier { + alg_id::ECDSA_SHA256 + } + + fn verify_signature( + &self, + _public_key: &[u8], + _message: &[u8], + signature: &[u8], + ) -> Result<(), InvalidSignature> { + match signature { + SIGNATURE => Ok(()), + _ => Err(InvalidSignature), + } + } +} + +#[derive(Debug)] +struct SigningKey; + +impl crypto::SigningKey for SigningKey { + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { + match offered.contains(&SIGNATURE_SCHEME) { + true => Some(Box::new(Self)), + false => None, + } + } + + fn public_key(&self) -> Option> { + Some(SubjectPublicKeyInfoDer::from(SUBJECT_PUBLIC_KEY_INFO_DER)) + } +} + +const SUBJECT_PUBLIC_KEY_INFO_DER: &[u8] = &[ + 48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 66, + 0, 4, 179, 82, 178, 210, 233, 245, 65, 87, 222, 175, 222, 238, 202, 123, 3, 223, 151, 55, 26, + 185, 76, 2, 57, 106, 210, 52, 118, 214, 156, 243, 103, 157, 241, 100, 226, 121, 64, 29, 33, + 156, 232, 118, 42, 168, 148, 123, 58, 120, 149, 133, 68, 119, 106, 127, 181, 109, 58, 72, 39, + 17, 103, 222, 144, 46, +]; + +impl crypto::Signer for SigningKey { + fn sign(self: Box, _message: &[u8]) -> Result, Error> { + Ok(SIGNATURE.to_vec()) + } + + fn scheme(&self) -> SignatureScheme { + SIGNATURE_SCHEME + } +} + +const SIGNATURE_SCHEME: SignatureScheme = SignatureScheme::ECDSA_NISTP256_SHA256; +// extracted from cert +const SIGNATURE: &[u8] = &[ + 48u8, 69, 2, 32, 11, 49, 87, 201, 222, 105, 47, 52, 194, 171, 246, 150, 240, 199, 213, 121, 77, + 195, 71, 91, 166, 33, 223, 173, 55, 134, 249, 113, 185, 139, 161, 151, 2, 33, 0, 162, 53, 205, + 227, 18, 175, 158, 210, 251, 138, 30, 135, 109, 203, 124, 52, 208, 103, 221, 35, 80, 88, 101, + 202, 111, 191, 169, 142, 119, 76, 116, 221, +]; diff --git a/rustls/src/crypto/tls12.rs b/rustls/src/crypto/tls12.rs index fce7cb132c9..3336577e891 100644 --- a/rustls/src/crypto/tls12.rs +++ b/rustls/src/crypto/tls12.rs @@ -1,10 +1,14 @@ use alloc::boxed::Box; -use super::{hmac, ActiveKeyExchange}; +use pki_types::FipsStatus; + +use super::hmac; +use super::kx::ActiveKeyExchange; +use crate::enums::ProtocolVersion; use crate::error::Error; -use crate::version::TLS12; /// Implements [`Prf`] using a [`hmac::Hmac`]. +#[expect(clippy::exhaustive_structs)] pub struct PrfUsingHmac<'a>(pub &'a dyn hmac::Hmac); impl Prf for PrfUsingHmac<'_> { @@ -20,7 +24,7 @@ impl Prf for PrfUsingHmac<'_> { output, self.0 .with_key( - kx.complete_for_tls_version(peer_pub_key, &TLS12)? + kx.complete_for_tls_version(peer_pub_key, ProtocolVersion::TLSv1_2)? .secret_bytes(), ) .as_ref(), @@ -30,8 +34,16 @@ impl Prf for PrfUsingHmac<'_> { Ok(()) } - fn for_secret(&self, output: &mut [u8], secret: &[u8], label: &[u8], seed: &[u8]) { - prf(output, self.0.with_key(secret).as_ref(), label, seed); + fn new_secret(&self, secret: &[u8; 48]) -> Box { + Box::new(PrfSecretUsingHmac(self.0.with_key(secret))) + } +} + +struct PrfSecretUsingHmac(Box); + +impl PrfSecret for PrfSecretUsingHmac { + fn prf(&self, output: &mut [u8], label: &[u8], seed: &[u8]) { + prf(output, &*self.0, label, seed) } } @@ -59,111 +71,46 @@ pub trait Prf: Send + Sync { seed: &[u8], ) -> Result<(), Error>; - /// Computes `PRF(secret, label, seed)`, writing the result into `output`. + /// Returns an object that can compute `PRF(secret, label, seed)` with + /// the same `master_secret`. /// - /// The caller guarantees that `secret`, `label`, and `seed` are non-empty. - fn for_secret(&self, output: &mut [u8], secret: &[u8], label: &[u8], seed: &[u8]); + /// This object can amortize any preprocessing needed on `master_secret` over + /// several `PRF(...)` calls. + fn new_secret(&self, master_secret: &[u8; 48]) -> Box; - /// Return `true` if this is backed by a FIPS-approved implementation. - fn fips(&self) -> bool { - false + /// Return the FIPS validation status of this implementation. + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated } } -pub(crate) fn prf(out: &mut [u8], hmac_key: &dyn hmac::Key, label: &[u8], seed: &[u8]) { - // A(1) - let mut current_a = hmac_key.sign(&[label, seed]); +/// An instantiation of the TLS1.2 PRF with a fixed hash function and master secret. +pub trait PrfSecret: Send + Sync { + /// Computes `PRF(secret, label, seed)`, writing the result into `output`. + /// + /// `secret` is implicit in this object; see [`Prf::new_secret`]. + /// + /// The caller guarantees that `label` and `seed` are non-empty. + fn prf(&self, output: &mut [u8], label: &[u8], seed: &[u8]); +} + +#[doc(hidden)] +pub fn prf(out: &mut [u8], hmac_key: &dyn hmac::Key, label: &[u8], seed: &[u8]) { + let mut previous_a: Option = None; let chunk_size = hmac_key.tag_len(); for chunk in out.chunks_mut(chunk_size) { - // P_hash[i] = HMAC_hash(secret, A(i) + seed) - let p_term = hmac_key.sign(&[current_a.as_ref(), label, seed]); + let a_i = match previous_a { + // A(0) = HMAC_hash(secret, label + seed) + None => hmac_key.sign(&[label, seed]), + // A(i) = HMAC_hash(secret, A(i - 1)) + Some(previous_a) => hmac_key.sign(&[previous_a.as_ref()]), + }; + + // P_hash[i] = HMAC_hash(secret, A(i) + label + seed) + let p_term = hmac_key.sign(&[a_i.as_ref(), label, seed]); chunk.copy_from_slice(&p_term.as_ref()[..chunk.len()]); - // A(i+1) = HMAC_hash(secret, A(i)) - current_a = hmac_key.sign(&[current_a.as_ref()]); - } -} - -#[cfg(all(test, feature = "ring"))] -mod tests { - use crate::crypto::hmac::Hmac; - // nb: crypto::aws_lc_rs provider doesn't provide (or need) hmac, - // so cannot be used for this test. - use crate::crypto::ring::hmac; - - // Below known answer tests come from https://mailarchive.ietf.org/arch/msg/tls/fzVCzk-z3FShgGJ6DOXqM1ydxms/ - - #[test] - fn check_sha256() { - let secret = b"\x9b\xbe\x43\x6b\xa9\x40\xf0\x17\xb1\x76\x52\x84\x9a\x71\xdb\x35"; - let seed = b"\xa0\xba\x9f\x93\x6c\xda\x31\x18\x27\xa6\xf7\x96\xff\xd5\x19\x8c"; - let label = b"test label"; - let expect = include_bytes!("../testdata/prf-result.1.bin"); - let mut output = [0u8; 100]; - - super::prf( - &mut output, - &*hmac::HMAC_SHA256.with_key(secret), - label, - seed, - ); - assert_eq!(expect.len(), output.len()); - assert_eq!(expect.to_vec(), output.to_vec()); - } - - #[test] - fn check_sha512() { - let secret = b"\xb0\x32\x35\x23\xc1\x85\x35\x99\x58\x4d\x88\x56\x8b\xbb\x05\xeb"; - let seed = b"\xd4\x64\x0e\x12\xe4\xbc\xdb\xfb\x43\x7f\x03\xe6\xae\x41\x8e\xe5"; - let label = b"test label"; - let expect = include_bytes!("../testdata/prf-result.2.bin"); - let mut output = [0u8; 196]; - - super::prf( - &mut output, - &*hmac::HMAC_SHA512.with_key(secret), - label, - seed, - ); - assert_eq!(expect.len(), output.len()); - assert_eq!(expect.to_vec(), output.to_vec()); - } - - #[test] - fn check_sha384() { - let secret = b"\xb8\x0b\x73\x3d\x6c\xee\xfc\xdc\x71\x56\x6e\xa4\x8e\x55\x67\xdf"; - let seed = b"\xcd\x66\x5c\xf6\xa8\x44\x7d\xd6\xff\x8b\x27\x55\x5e\xdb\x74\x65"; - let label = b"test label"; - let expect = include_bytes!("../testdata/prf-result.3.bin"); - let mut output = [0u8; 148]; - - super::prf( - &mut output, - &*hmac::HMAC_SHA384.with_key(secret), - label, - seed, - ); - assert_eq!(expect.len(), output.len()); - assert_eq!(expect.to_vec(), output.to_vec()); - } -} - -#[cfg(all(bench, feature = "ring"))] -mod benchmarks { - #[bench] - fn bench_sha256(b: &mut test::Bencher) { - use crate::crypto::hmac::Hmac; - use crate::crypto::ring::hmac; - - let label = &b"extended master secret"[..]; - let seed = [0u8; 32]; - let key = &b"secret"[..]; - - b.iter(|| { - let mut out = [0u8; 48]; - super::prf(&mut out, &*hmac::HMAC_SHA256.with_key(key), &label, &seed); - test::black_box(out); - }); + previous_a = Some(a_i); } } diff --git a/rustls/src/crypto/tls13.rs b/rustls/src/crypto/tls13.rs index a2e8029b31f..5e12a991415 100644 --- a/rustls/src/crypto/tls13.rs +++ b/rustls/src/crypto/tls13.rs @@ -1,11 +1,13 @@ use alloc::boxed::Box; use alloc::vec::Vec; +use pki_types::FipsStatus; use zeroize::Zeroize; -use super::{hmac, ActiveKeyExchange}; +use super::hmac; +use super::kx::ActiveKeyExchange; +use crate::enums::ProtocolVersion; use crate::error::Error; -use crate::version::TLS13; /// Implementation of `HkdfExpander` via `hmac::Key`. pub struct HkdfExpanderUsingHmac(Box); @@ -49,6 +51,7 @@ impl HkdfExpander for HkdfExpanderUsingHmac { } /// Implementation of `Hkdf` (and thence `HkdfExpander`) via `hmac::Hmac`. +#[expect(clippy::exhaustive_structs)] pub struct HkdfUsingHmac<'a>(pub &'a dyn hmac::Hmac); impl Hkdf for HkdfUsingHmac<'_> { @@ -159,7 +162,7 @@ pub trait Hkdf: Send + Sync { ) -> Result, Error> { Ok(self.extract_from_secret( salt, - kx.complete_for_tls_version(peer_pub_key, &TLS13)? + kx.complete_for_tls_version(peer_pub_key, ProtocolVersion::TLSv1_3)? .secret_bytes(), )) } @@ -176,9 +179,9 @@ pub trait Hkdf: Send + Sync { /// definition of HMAC. fn hmac_sign(&self, key: &OkmBlock, message: &[u8]) -> hmac::Tag; - /// Return `true` if this is backed by a FIPS-approved implementation. - fn fips(&self) -> bool { - false + /// Return the FIPS validation status of this implementation. + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated } } @@ -191,7 +194,7 @@ pub trait Hkdf: Send + Sync { /// In other contexts (for example, hybrid public key encryption (HPKE)) it may be necessary /// to use the extracted PRK directly for purposes other than an immediate expansion. /// This trait can be implemented to offer this functionality when it is required. -pub(crate) trait HkdfPrkExtract: Hkdf { +pub trait HkdfPrkExtract: Hkdf { /// `HKDF-Extract(salt, secret)` /// /// A `salt` of `None` should be treated as a sequence of `HashLen` zero bytes. @@ -246,6 +249,7 @@ impl OkmBlock { } impl Drop for OkmBlock { + #[inline(never)] fn drop(&mut self) { self.buf.zeroize(); } @@ -259,147 +263,6 @@ impl AsRef<[u8]> for OkmBlock { /// An error type used for `HkdfExpander::expand_slice` when /// the slice exceeds the maximum HKDF output length. +#[expect(clippy::exhaustive_structs)] #[derive(Debug)] pub struct OutputLengthError; - -#[cfg(all(test, feature = "ring"))] -mod tests { - use std::prelude::v1::*; - - use super::{expand, Hkdf, HkdfUsingHmac}; - // nb: crypto::aws_lc_rs provider doesn't provide (or need) hmac, - // so cannot be used for this test. - use crate::crypto::ring::hmac; - - struct ByteArray([u8; N]); - - impl From<[u8; N]> for ByteArray { - fn from(array: [u8; N]) -> Self { - Self(array) - } - } - - /// Test cases from appendix A in the RFC, minus cases requiring SHA1. - - #[test] - fn test_case_1() { - let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256); - let ikm = &[0x0b; 22]; - let salt = &[ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, - ]; - let info: &[&[u8]] = &[ - &[0xf0, 0xf1, 0xf2], - &[0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9], - ]; - - let output: ByteArray<42> = expand( - hkdf.extract_from_secret(Some(salt), ikm) - .as_ref(), - info, - ); - - assert_eq!( - &output.0, - &[ - 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, - 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, - 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65 - ] - ); - } - - #[test] - fn test_case_2() { - let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256); - let ikm: Vec = (0x00u8..=0x4f).collect(); - let salt: Vec = (0x60u8..=0xaf).collect(); - let info: Vec = (0xb0u8..=0xff).collect(); - - let output: ByteArray<82> = expand( - hkdf.extract_from_secret(Some(&salt), &ikm) - .as_ref(), - &[&info], - ); - - assert_eq!( - &output.0, - &[ - 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a, - 0x49, 0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c, - 0x19, 0xaf, 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb, - 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8, - 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, - 0x3e, 0x87, 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87 - ] - ); - } - - #[test] - fn test_case_3() { - let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256); - let ikm = &[0x0b; 22]; - let salt = &[]; - let info = &[]; - - let output: ByteArray<42> = expand( - hkdf.extract_from_secret(Some(salt), ikm) - .as_ref(), - info, - ); - - assert_eq!( - &output.0, - &[ - 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, - 0x5a, 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f, - 0x3c, 0x73, 0x8d, 0x2d, 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8 - ] - ); - } - - #[test] - fn test_salt_not_provided() { - // can't use test case 7, because we don't have (or want) SHA1. - // - // this output is generated with cryptography.io: - // - // >>> hkdf.HKDF(algorithm=hashes.SHA384(), length=96, salt=None, info=b"hello").derive(b"\x0b" * 40) - - let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA384); - let ikm = &[0x0b; 40]; - let info = &[&b"hel"[..], &b"lo"[..]]; - - let output: ByteArray<96> = expand( - hkdf.extract_from_secret(None, ikm) - .as_ref(), - info, - ); - - assert_eq!( - &output.0, - &[ - 0xd5, 0x45, 0xdd, 0x3a, 0xff, 0x5b, 0x19, 0x46, 0xd4, 0x86, 0xfd, 0xb8, 0xd8, 0x88, - 0x2e, 0xe0, 0x1c, 0xc1, 0xa5, 0x48, 0xb6, 0x05, 0x75, 0xe4, 0xd7, 0x5d, 0x0f, 0x5f, - 0x23, 0x40, 0xee, 0x6c, 0x9e, 0x7c, 0x65, 0xd0, 0xee, 0x79, 0xdb, 0xb2, 0x07, 0x1d, - 0x66, 0xa5, 0x50, 0xc4, 0x8a, 0xa3, 0x93, 0x86, 0x8b, 0x7c, 0x69, 0x41, 0x6b, 0x3e, - 0x61, 0x44, 0x98, 0xb8, 0xc2, 0xfc, 0x82, 0x82, 0xae, 0xcd, 0x46, 0xcf, 0xb1, 0x47, - 0xdc, 0xd0, 0x69, 0x0d, 0x19, 0xad, 0xe6, 0x6c, 0x70, 0xfe, 0x87, 0x92, 0x04, 0xb6, - 0x82, 0x2d, 0x97, 0x7e, 0x46, 0x80, 0x4c, 0xe5, 0x76, 0x72, 0xb4, 0xb8 - ] - ); - } - - #[test] - fn test_output_length_bounds() { - let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256); - let ikm = &[]; - let info = &[]; - - let mut output = [0u8; 32 * 255 + 1]; - assert!(hkdf - .extract_from_secret(None, ikm) - .expand_slice(info, &mut output) - .is_err()); - } -} diff --git a/rustls/src/enums.rs b/rustls/src/enums.rs index 4b48a03bec9..e11e005103e 100644 --- a/rustls/src/enums.rs +++ b/rustls/src/enums.rs @@ -1,57 +1,140 @@ -#![allow(non_camel_case_types)] -#![allow(missing_docs)] -use crate::msgs::codec::{Codec, Reader}; +#![expect(missing_docs)] -enum_builder! { - /// The `AlertDescription` TLS protocol enum. Values in this enum are taken - /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub enum AlertDescription { - CloseNotify => 0x00, - UnexpectedMessage => 0x0a, - BadRecordMac => 0x14, - DecryptionFailed => 0x15, - RecordOverflow => 0x16, - DecompressionFailure => 0x1e, - HandshakeFailure => 0x28, - NoCertificate => 0x29, - BadCertificate => 0x2a, - UnsupportedCertificate => 0x2b, - CertificateRevoked => 0x2c, - CertificateExpired => 0x2d, - CertificateUnknown => 0x2e, - IllegalParameter => 0x2f, - UnknownCA => 0x30, - AccessDenied => 0x31, - DecodeError => 0x32, - DecryptError => 0x33, - ExportRestriction => 0x3c, - ProtocolVersion => 0x46, - InsufficientSecurity => 0x47, - InternalError => 0x50, - InappropriateFallback => 0x56, - UserCanceled => 0x5a, - NoRenegotiation => 0x64, - MissingExtension => 0x6d, - UnsupportedExtension => 0x6e, - CertificateUnobtainable => 0x6f, - UnrecognisedName => 0x70, - BadCertificateStatusResponse => 0x71, - BadCertificateHashValue => 0x72, - UnknownPSKIdentity => 0x73, - CertificateRequired => 0x74, - NoApplicationProtocol => 0x78, - EncryptedClientHelloRequired => 0x79, // https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#section-11.2 +use alloc::borrow::Cow; +use alloc::vec::Vec; + +use crate::crypto::cipher::Payload; +use crate::error::InvalidMessage; +use crate::msgs::{Codec, ListLength, NonEmpty, Reader, SizedPayload, TlsListElement}; + +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ApplicationProtocol<'a> { + AcmeTls1, + DoT, + DoQ, + Ftp, + Http09, + Http10, + Http11, + Http2, + Http3, + Imap, + Mqtt, + Pop3, + Postgresql, + WebRtc, + Other(Cow<'a, [u8]>), +} + +impl<'a> ApplicationProtocol<'a> { + fn new(data: Cow<'a, [u8]>) -> Self { + match data.as_ref() { + b"acme-tls/1" => Self::AcmeTls1, + b"dot" => Self::DoT, + b"doq" => Self::DoQ, + b"ftp" => Self::Ftp, + b"http/0.9" => Self::Http09, + b"http/1.0" => Self::Http10, + b"http/1.1" => Self::Http11, + b"h2" => Self::Http2, + b"h3" => Self::Http3, + b"imap" => Self::Imap, + b"mqtt" => Self::Mqtt, + b"pop3" => Self::Pop3, + b"postgresql" => Self::Postgresql, + b"webrtc" => Self::WebRtc, + _r => Self::Other(data), + } + } + + pub fn to_owned(&self) -> ApplicationProtocol<'static> { + match self { + Self::AcmeTls1 => ApplicationProtocol::AcmeTls1, + Self::DoT => ApplicationProtocol::DoT, + Self::DoQ => ApplicationProtocol::DoQ, + Self::Ftp => ApplicationProtocol::Ftp, + Self::Http09 => ApplicationProtocol::Http09, + Self::Http10 => ApplicationProtocol::Http10, + Self::Http11 => ApplicationProtocol::Http11, + Self::Http2 => ApplicationProtocol::Http2, + Self::Http3 => ApplicationProtocol::Http3, + Self::Imap => ApplicationProtocol::Imap, + Self::Mqtt => ApplicationProtocol::Mqtt, + Self::Pop3 => ApplicationProtocol::Pop3, + Self::Postgresql => ApplicationProtocol::Postgresql, + Self::WebRtc => ApplicationProtocol::WebRtc, + Self::Other(data) => ApplicationProtocol::Other(Cow::Owned(data.to_vec())), + } + } +} + +impl<'a> Codec<'a> for ApplicationProtocol<'a> { + fn encode(&self, bytes: &mut Vec) { + SizedPayload::::from(Payload::Borrowed(self.as_ref())).encode(bytes); + } + + fn read(r: &mut Reader<'a>) -> Result { + match SizedPayload::::read(r)?.inner { + Payload::Borrowed(data) => Ok(Self::new(Cow::Borrowed(data))), + Payload::Owned(data) => Ok(Self::new(Cow::Owned(data))), + } + } +} + +/// RFC7301: `ProtocolName protocol_name_list<2..2^16-1>` +impl TlsListElement for ApplicationProtocol<'_> { + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("ProtocolNames"), + }; +} + +impl From> for ApplicationProtocol<'static> { + fn from(data: Vec) -> Self { + Self::new(Cow::Owned(data)) + } +} + +impl<'a, const N: usize> From<&'a [u8; N]> for ApplicationProtocol<'a> { + fn from(data: &'a [u8; N]) -> Self { + ApplicationProtocol::from(&data[..]) + } +} + +impl<'a> From<&'a [u8]> for ApplicationProtocol<'a> { + fn from(data: &'a [u8]) -> Self { + Self::new(Cow::Borrowed(data)) + } +} + +impl AsRef<[u8]> for ApplicationProtocol<'_> { + fn as_ref(&self) -> &[u8] { + match self { + Self::AcmeTls1 => b"acme-tls/1", + Self::DoT => b"dot", + Self::DoQ => b"doq", + Self::Ftp => b"ftp", + Self::Http09 => b"http/0.9", + Self::Http10 => b"http/1.0", + Self::Http11 => b"http/1.1", + Self::Http2 => b"h2", + Self::Http3 => b"h3", + Self::Imap => b"imap", + Self::Mqtt => b"mqtt", + Self::Pop3 => b"pop3", + Self::Postgresql => b"postgresql", + Self::WebRtc => b"webrtc", + Self::Other(data) => data.as_ref(), + } } } enum_builder! { /// The `HandshakeType` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub enum HandshakeType { + pub struct HandshakeType(pub u8); + + enum HandshakeTypeName { HelloRequest => 0x00, ClientHello => 0x01, ServerHello => 0x02, @@ -78,9 +161,9 @@ enum_builder! { enum_builder! { /// The `ContentType` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub enum ContentType { + pub struct ContentType(pub u8); + + pub(crate) enum ContentTypeName { ChangeCipherSpec => 0x14, Alert => 0x15, Handshake => 0x16, @@ -92,10 +175,10 @@ enum_builder! { enum_builder! { /// The `ProtocolVersion` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u16)] - pub enum ProtocolVersion { - SSLv2 => 0x0200, + pub struct ProtocolVersion(pub u16); + + enum ProtocolVersionName { + SSLv2 => 0x0002, SSLv3 => 0x0300, TLSv1_0 => 0x0301, TLSv1_1 => 0x0302, @@ -108,491 +191,48 @@ enum_builder! { } enum_builder! { - /// The `CipherSuite` TLS protocol enum. Values in this enum are taken - /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u16)] - pub enum CipherSuite { - TLS_NULL_WITH_NULL_NULL => 0x0000, - TLS_PSK_WITH_AES_128_GCM_SHA256 => 0x00a8, - TLS_PSK_WITH_AES_256_GCM_SHA384 => 0x00a9, - TLS_EMPTY_RENEGOTIATION_INFO_SCSV => 0x00ff, - TLS13_AES_128_GCM_SHA256 => 0x1301, - TLS13_AES_256_GCM_SHA384 => 0x1302, - TLS13_CHACHA20_POLY1305_SHA256 => 0x1303, - TLS13_AES_128_CCM_SHA256 => 0x1304, - TLS13_AES_128_CCM_8_SHA256 => 0x1305, - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA => 0xc009, - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA => 0xc00a, - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA => 0xc013, - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA => 0xc014, - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 => 0xc023, - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 => 0xc024, - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 => 0xc027, - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 => 0xc028, - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => 0xc02b, - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => 0xc02c, - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => 0xc02f, - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => 0xc030, - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => 0xcca8, - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => 0xcca9, + /// The "TLS Certificate Compression Algorithm IDs" TLS protocol enum. + /// Values in this enum are taken from [RFC8879]. + /// + /// [RFC8879]: https://www.rfc-editor.org/rfc/rfc8879.html#section-7.3 + pub struct CertificateCompressionAlgorithm(pub u16); - !Debug: - TLS_RSA_WITH_NULL_MD5 => 0x0001, - TLS_RSA_WITH_NULL_SHA => 0x0002, - TLS_RSA_EXPORT_WITH_RC4_40_MD5 => 0x0003, - TLS_RSA_WITH_RC4_128_MD5 => 0x0004, - TLS_RSA_WITH_RC4_128_SHA => 0x0005, - TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 => 0x0006, - TLS_RSA_WITH_IDEA_CBC_SHA => 0x0007, - TLS_RSA_EXPORT_WITH_DES40_CBC_SHA => 0x0008, - TLS_RSA_WITH_DES_CBC_SHA => 0x0009, - TLS_RSA_WITH_3DES_EDE_CBC_SHA => 0x000a, - TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA => 0x000b, - TLS_DH_DSS_WITH_DES_CBC_SHA => 0x000c, - TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA => 0x000d, - TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA => 0x000e, - TLS_DH_RSA_WITH_DES_CBC_SHA => 0x000f, - TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA => 0x0010, - TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA => 0x0011, - TLS_DHE_DSS_WITH_DES_CBC_SHA => 0x0012, - TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA => 0x0013, - TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA => 0x0014, - TLS_DHE_RSA_WITH_DES_CBC_SHA => 0x0015, - TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA => 0x0016, - TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 => 0x0017, - TLS_DH_anon_WITH_RC4_128_MD5 => 0x0018, - TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA => 0x0019, - TLS_DH_anon_WITH_DES_CBC_SHA => 0x001a, - TLS_DH_anon_WITH_3DES_EDE_CBC_SHA => 0x001b, - SSL_FORTEZZA_KEA_WITH_NULL_SHA => 0x001c, - SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA => 0x001d, - TLS_KRB5_WITH_DES_CBC_SHA_or_SSL_FORTEZZA_KEA_WITH_RC4_128_SHA => 0x001e, - TLS_KRB5_WITH_3DES_EDE_CBC_SHA => 0x001f, - TLS_KRB5_WITH_RC4_128_SHA => 0x0020, - TLS_KRB5_WITH_IDEA_CBC_SHA => 0x0021, - TLS_KRB5_WITH_DES_CBC_MD5 => 0x0022, - TLS_KRB5_WITH_3DES_EDE_CBC_MD5 => 0x0023, - TLS_KRB5_WITH_RC4_128_MD5 => 0x0024, - TLS_KRB5_WITH_IDEA_CBC_MD5 => 0x0025, - TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA => 0x0026, - TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA => 0x0027, - TLS_KRB5_EXPORT_WITH_RC4_40_SHA => 0x0028, - TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 => 0x0029, - TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 => 0x002a, - TLS_KRB5_EXPORT_WITH_RC4_40_MD5 => 0x002b, - TLS_PSK_WITH_NULL_SHA => 0x002c, - TLS_DHE_PSK_WITH_NULL_SHA => 0x002d, - TLS_RSA_PSK_WITH_NULL_SHA => 0x002e, - TLS_RSA_WITH_AES_128_CBC_SHA => 0x002f, - TLS_DH_DSS_WITH_AES_128_CBC_SHA => 0x0030, - TLS_DH_RSA_WITH_AES_128_CBC_SHA => 0x0031, - TLS_DHE_DSS_WITH_AES_128_CBC_SHA => 0x0032, - TLS_DHE_RSA_WITH_AES_128_CBC_SHA => 0x0033, - TLS_DH_anon_WITH_AES_128_CBC_SHA => 0x0034, - TLS_RSA_WITH_AES_256_CBC_SHA => 0x0035, - TLS_DH_DSS_WITH_AES_256_CBC_SHA => 0x0036, - TLS_DH_RSA_WITH_AES_256_CBC_SHA => 0x0037, - TLS_DHE_DSS_WITH_AES_256_CBC_SHA => 0x0038, - TLS_DHE_RSA_WITH_AES_256_CBC_SHA => 0x0039, - TLS_DH_anon_WITH_AES_256_CBC_SHA => 0x003a, - TLS_RSA_WITH_NULL_SHA256 => 0x003b, - TLS_RSA_WITH_AES_128_CBC_SHA256 => 0x003c, - TLS_RSA_WITH_AES_256_CBC_SHA256 => 0x003d, - TLS_DH_DSS_WITH_AES_128_CBC_SHA256 => 0x003e, - TLS_DH_RSA_WITH_AES_128_CBC_SHA256 => 0x003f, - TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 => 0x0040, - TLS_RSA_WITH_CAMELLIA_128_CBC_SHA => 0x0041, - TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA => 0x0042, - TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA => 0x0043, - TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA => 0x0044, - TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA => 0x0045, - TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA => 0x0046, - TLS_ECDH_ECDSA_WITH_NULL_SHA_draft => 0x0047, - TLS_ECDH_ECDSA_WITH_RC4_128_SHA_draft => 0x0048, - TLS_ECDH_ECDSA_WITH_DES_CBC_SHA_draft => 0x0049, - TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA_draft => 0x004a, - TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA_draft => 0x004b, - TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA_draft => 0x004c, - TLS_ECDH_ECNRA_WITH_DES_CBC_SHA_draft => 0x004d, - TLS_ECDH_ECNRA_WITH_3DES_EDE_CBC_SHA_draft => 0x004e, - TLS_ECMQV_ECDSA_NULL_SHA_draft => 0x004f, - TLS_ECMQV_ECDSA_WITH_RC4_128_SHA_draft => 0x0050, - TLS_ECMQV_ECDSA_WITH_DES_CBC_SHA_draft => 0x0051, - TLS_ECMQV_ECDSA_WITH_3DES_EDE_CBC_SHA_draft => 0x0052, - TLS_ECMQV_ECNRA_NULL_SHA_draft => 0x0053, - TLS_ECMQV_ECNRA_WITH_RC4_128_SHA_draft => 0x0054, - TLS_ECMQV_ECNRA_WITH_DES_CBC_SHA_draft => 0x0055, - TLS_ECMQV_ECNRA_WITH_3DES_EDE_CBC_SHA_draft => 0x0056, - TLS_ECDH_anon_NULL_WITH_SHA_draft => 0x0057, - TLS_ECDH_anon_WITH_RC4_128_SHA_draft => 0x0058, - TLS_ECDH_anon_WITH_DES_CBC_SHA_draft => 0x0059, - TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA_draft => 0x005a, - TLS_ECDH_anon_EXPORT_WITH_DES40_CBC_SHA_draft => 0x005b, - TLS_ECDH_anon_EXPORT_WITH_RC4_40_SHA_draft => 0x005c, - TLS_RSA_EXPORT1024_WITH_RC4_56_MD5 => 0x0060, - TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5 => 0x0061, - TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA => 0x0062, - TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA => 0x0063, - TLS_RSA_EXPORT1024_WITH_RC4_56_SHA => 0x0064, - TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA => 0x0065, - TLS_DHE_DSS_WITH_RC4_128_SHA => 0x0066, - TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 => 0x0067, - TLS_DH_DSS_WITH_AES_256_CBC_SHA256 => 0x0068, - TLS_DH_RSA_WITH_AES_256_CBC_SHA256 => 0x0069, - TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 => 0x006a, - TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 => 0x006b, - TLS_DH_anon_WITH_AES_128_CBC_SHA256 => 0x006c, - TLS_DH_anon_WITH_AES_256_CBC_SHA256 => 0x006d, - TLS_DHE_DSS_WITH_3DES_EDE_CBC_RMD => 0x0072, - TLS_DHE_DSS_WITH_AES_128_CBC_RMD => 0x0073, - TLS_DHE_DSS_WITH_AES_256_CBC_RMD => 0x0074, - TLS_DHE_RSA_WITH_3DES_EDE_CBC_RMD => 0x0077, - TLS_DHE_RSA_WITH_AES_128_CBC_RMD => 0x0078, - TLS_DHE_RSA_WITH_AES_256_CBC_RMD => 0x0079, - TLS_RSA_WITH_3DES_EDE_CBC_RMD => 0x007c, - TLS_RSA_WITH_AES_128_CBC_RMD => 0x007d, - TLS_RSA_WITH_AES_256_CBC_RMD => 0x007e, - TLS_GOSTR341094_WITH_28147_CNT_IMIT => 0x0080, - TLS_GOSTR341001_WITH_28147_CNT_IMIT => 0x0081, - TLS_GOSTR341094_WITH_NULL_GOSTR3411 => 0x0082, - TLS_GOSTR341001_WITH_NULL_GOSTR3411 => 0x0083, - TLS_RSA_WITH_CAMELLIA_256_CBC_SHA => 0x0084, - TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA => 0x0085, - TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA => 0x0086, - TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA => 0x0087, - TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA => 0x0088, - TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA => 0x0089, - TLS_PSK_WITH_RC4_128_SHA => 0x008a, - TLS_PSK_WITH_3DES_EDE_CBC_SHA => 0x008b, - TLS_PSK_WITH_AES_128_CBC_SHA => 0x008c, - TLS_PSK_WITH_AES_256_CBC_SHA => 0x008d, - TLS_DHE_PSK_WITH_RC4_128_SHA => 0x008e, - TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA => 0x008f, - TLS_DHE_PSK_WITH_AES_128_CBC_SHA => 0x0090, - TLS_DHE_PSK_WITH_AES_256_CBC_SHA => 0x0091, - TLS_RSA_PSK_WITH_RC4_128_SHA => 0x0092, - TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA => 0x0093, - TLS_RSA_PSK_WITH_AES_128_CBC_SHA => 0x0094, - TLS_RSA_PSK_WITH_AES_256_CBC_SHA => 0x0095, - TLS_RSA_WITH_SEED_CBC_SHA => 0x0096, - TLS_DH_DSS_WITH_SEED_CBC_SHA => 0x0097, - TLS_DH_RSA_WITH_SEED_CBC_SHA => 0x0098, - TLS_DHE_DSS_WITH_SEED_CBC_SHA => 0x0099, - TLS_DHE_RSA_WITH_SEED_CBC_SHA => 0x009a, - TLS_DH_anon_WITH_SEED_CBC_SHA => 0x009b, - TLS_RSA_WITH_AES_128_GCM_SHA256 => 0x009c, - TLS_RSA_WITH_AES_256_GCM_SHA384 => 0x009d, - TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 => 0x009e, - TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 => 0x009f, - TLS_DH_RSA_WITH_AES_128_GCM_SHA256 => 0x00a0, - TLS_DH_RSA_WITH_AES_256_GCM_SHA384 => 0x00a1, - TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 => 0x00a2, - TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 => 0x00a3, - TLS_DH_DSS_WITH_AES_128_GCM_SHA256 => 0x00a4, - TLS_DH_DSS_WITH_AES_256_GCM_SHA384 => 0x00a5, - TLS_DH_anon_WITH_AES_128_GCM_SHA256 => 0x00a6, - TLS_DH_anon_WITH_AES_256_GCM_SHA384 => 0x00a7, - TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 => 0x00aa, - TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 => 0x00ab, - TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 => 0x00ac, - TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 => 0x00ad, - TLS_PSK_WITH_AES_128_CBC_SHA256 => 0x00ae, - TLS_PSK_WITH_AES_256_CBC_SHA384 => 0x00af, - TLS_PSK_WITH_NULL_SHA256 => 0x00b0, - TLS_PSK_WITH_NULL_SHA384 => 0x00b1, - TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 => 0x00b2, - TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 => 0x00b3, - TLS_DHE_PSK_WITH_NULL_SHA256 => 0x00b4, - TLS_DHE_PSK_WITH_NULL_SHA384 => 0x00b5, - TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 => 0x00b6, - TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 => 0x00b7, - TLS_RSA_PSK_WITH_NULL_SHA256 => 0x00b8, - TLS_RSA_PSK_WITH_NULL_SHA384 => 0x00b9, - TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 => 0x00ba, - TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 => 0x00bb, - TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 => 0x00bc, - TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 => 0x00bd, - TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 => 0x00be, - TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 => 0x00bf, - TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 => 0x00c0, - TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 => 0x00c1, - TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 => 0x00c2, - TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 => 0x00c3, - TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 => 0x00c4, - TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 => 0x00c5, - TLS_ECDH_ECDSA_WITH_NULL_SHA => 0xc001, - TLS_ECDH_ECDSA_WITH_RC4_128_SHA => 0xc002, - TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA => 0xc003, - TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA => 0xc004, - TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA => 0xc005, - TLS_ECDHE_ECDSA_WITH_NULL_SHA => 0xc006, - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA => 0xc007, - TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA => 0xc008, - TLS_ECDH_RSA_WITH_NULL_SHA => 0xc00b, - TLS_ECDH_RSA_WITH_RC4_128_SHA => 0xc00c, - TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA => 0xc00d, - TLS_ECDH_RSA_WITH_AES_128_CBC_SHA => 0xc00e, - TLS_ECDH_RSA_WITH_AES_256_CBC_SHA => 0xc00f, - TLS_ECDHE_RSA_WITH_NULL_SHA => 0xc010, - TLS_ECDHE_RSA_WITH_RC4_128_SHA => 0xc011, - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA => 0xc012, - TLS_ECDH_anon_WITH_NULL_SHA => 0xc015, - TLS_ECDH_anon_WITH_RC4_128_SHA => 0xc016, - TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA => 0xc017, - TLS_ECDH_anon_WITH_AES_128_CBC_SHA => 0xc018, - TLS_ECDH_anon_WITH_AES_256_CBC_SHA => 0xc019, - TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA => 0xc01a, - TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA => 0xc01b, - TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA => 0xc01c, - TLS_SRP_SHA_WITH_AES_128_CBC_SHA => 0xc01d, - TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA => 0xc01e, - TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA => 0xc01f, - TLS_SRP_SHA_WITH_AES_256_CBC_SHA => 0xc020, - TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA => 0xc021, - TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA => 0xc022, - TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 => 0xc025, - TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 => 0xc026, - TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 => 0xc029, - TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 => 0xc02a, - TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 => 0xc02d, - TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 => 0xc02e, - TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 => 0xc031, - TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 => 0xc032, - TLS_ECDHE_PSK_WITH_RC4_128_SHA => 0xc033, - TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA => 0xc034, - TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA => 0xc035, - TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA => 0xc036, - TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 => 0xc037, - TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 => 0xc038, - TLS_ECDHE_PSK_WITH_NULL_SHA => 0xc039, - TLS_ECDHE_PSK_WITH_NULL_SHA256 => 0xc03a, - TLS_ECDHE_PSK_WITH_NULL_SHA384 => 0xc03b, - TLS_RSA_WITH_ARIA_128_CBC_SHA256 => 0xc03c, - TLS_RSA_WITH_ARIA_256_CBC_SHA384 => 0xc03d, - TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 => 0xc03e, - TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 => 0xc03f, - TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 => 0xc040, - TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 => 0xc041, - TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 => 0xc042, - TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 => 0xc043, - TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 => 0xc044, - TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 => 0xc045, - TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 => 0xc046, - TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 => 0xc047, - TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 => 0xc048, - TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 => 0xc049, - TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 => 0xc04a, - TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 => 0xc04b, - TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 => 0xc04c, - TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 => 0xc04d, - TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 => 0xc04e, - TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 => 0xc04f, - TLS_RSA_WITH_ARIA_128_GCM_SHA256 => 0xc050, - TLS_RSA_WITH_ARIA_256_GCM_SHA384 => 0xc051, - TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 => 0xc052, - TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 => 0xc053, - TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 => 0xc054, - TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 => 0xc055, - TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256 => 0xc056, - TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384 => 0xc057, - TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 => 0xc058, - TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 => 0xc059, - TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 => 0xc05a, - TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 => 0xc05b, - TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 => 0xc05c, - TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 => 0xc05d, - TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 => 0xc05e, - TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 => 0xc05f, - TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 => 0xc060, - TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 => 0xc061, - TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 => 0xc062, - TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 => 0xc063, - TLS_PSK_WITH_ARIA_128_CBC_SHA256 => 0xc064, - TLS_PSK_WITH_ARIA_256_CBC_SHA384 => 0xc065, - TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 => 0xc066, - TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 => 0xc067, - TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 => 0xc068, - TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 => 0xc069, - TLS_PSK_WITH_ARIA_128_GCM_SHA256 => 0xc06a, - TLS_PSK_WITH_ARIA_256_GCM_SHA384 => 0xc06b, - TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256 => 0xc06c, - TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384 => 0xc06d, - TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 => 0xc06e, - TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 => 0xc06f, - TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 => 0xc070, - TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 => 0xc071, - TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 => 0xc072, - TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 => 0xc073, - TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 => 0xc074, - TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 => 0xc075, - TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 => 0xc076, - TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 => 0xc077, - TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 => 0xc078, - TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 => 0xc079, - TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc07a, - TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc07b, - TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc07c, - TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc07d, - TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc07e, - TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc07f, - TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256 => 0xc080, - TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384 => 0xc081, - TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 => 0xc082, - TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 => 0xc083, - TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 => 0xc084, - TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 => 0xc085, - TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc086, - TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc087, - TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc088, - TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc089, - TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc08a, - TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc08b, - TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc08c, - TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc08d, - TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 => 0xc08e, - TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 => 0xc08f, - TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 => 0xc090, - TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 => 0xc091, - TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 => 0xc092, - TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 => 0xc093, - TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 => 0xc094, - TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 => 0xc095, - TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 => 0xc096, - TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 => 0xc097, - TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 => 0xc098, - TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 => 0xc099, - TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 => 0xc09a, - TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 => 0xc09b, - TLS_RSA_WITH_AES_128_CCM => 0xc09c, - TLS_RSA_WITH_AES_256_CCM => 0xc09d, - TLS_DHE_RSA_WITH_AES_128_CCM => 0xc09e, - TLS_DHE_RSA_WITH_AES_256_CCM => 0xc09f, - TLS_RSA_WITH_AES_128_CCM_8 => 0xc0a0, - TLS_RSA_WITH_AES_256_CCM_8 => 0xc0a1, - TLS_DHE_RSA_WITH_AES_128_CCM_8 => 0xc0a2, - TLS_DHE_RSA_WITH_AES_256_CCM_8 => 0xc0a3, - TLS_PSK_WITH_AES_128_CCM => 0xc0a4, - TLS_PSK_WITH_AES_256_CCM => 0xc0a5, - TLS_DHE_PSK_WITH_AES_128_CCM => 0xc0a6, - TLS_DHE_PSK_WITH_AES_256_CCM => 0xc0a7, - TLS_PSK_WITH_AES_128_CCM_8 => 0xc0a8, - TLS_PSK_WITH_AES_256_CCM_8 => 0xc0a9, - TLS_PSK_DHE_WITH_AES_128_CCM_8 => 0xc0aa, - TLS_PSK_DHE_WITH_AES_256_CCM_8 => 0xc0ab, - TLS_ECDHE_ECDSA_WITH_AES_128_CCM => 0xc0ac, - TLS_ECDHE_ECDSA_WITH_AES_256_CCM => 0xc0ad, - TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 => 0xc0ae, - TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 => 0xc0af, - TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => 0xccaa, - TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 => 0xccab, - TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 => 0xccac, - TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 => 0xccad, - TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 => 0xccae, - SSL_RSA_FIPS_WITH_DES_CBC_SHA => 0xfefe, - SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA => 0xfeff, + enum CertificateCompressionAlgorithmName { + Zlib => 1, + Brotli => 2, + Zstd => 3, } } enum_builder! { - /// The `SignatureScheme` TLS protocol enum. Values in this enum are taken - /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u16)] - pub enum SignatureScheme { - RSA_PKCS1_SHA1 => 0x0201, - ECDSA_SHA1_Legacy => 0x0203, - RSA_PKCS1_SHA256 => 0x0401, - ECDSA_NISTP256_SHA256 => 0x0403, - RSA_PKCS1_SHA384 => 0x0501, - ECDSA_NISTP384_SHA384 => 0x0503, - RSA_PKCS1_SHA512 => 0x0601, - ECDSA_NISTP521_SHA512 => 0x0603, - RSA_PSS_SHA256 => 0x0804, - RSA_PSS_SHA384 => 0x0805, - RSA_PSS_SHA512 => 0x0806, - ED25519 => 0x0807, - ED448 => 0x0808, - } -} - -impl SignatureScheme { - pub(crate) fn algorithm(&self) -> SignatureAlgorithm { - match *self { - Self::RSA_PKCS1_SHA1 - | Self::RSA_PKCS1_SHA256 - | Self::RSA_PKCS1_SHA384 - | Self::RSA_PKCS1_SHA512 - | Self::RSA_PSS_SHA256 - | Self::RSA_PSS_SHA384 - | Self::RSA_PSS_SHA512 => SignatureAlgorithm::RSA, - Self::ECDSA_SHA1_Legacy - | Self::ECDSA_NISTP256_SHA256 - | Self::ECDSA_NISTP384_SHA384 - | Self::ECDSA_NISTP521_SHA512 => SignatureAlgorithm::ECDSA, - Self::ED25519 => SignatureAlgorithm::ED25519, - Self::ED448 => SignatureAlgorithm::ED448, - _ => SignatureAlgorithm::Unknown(0), - } - } - - /// Whether a particular `SignatureScheme` is allowed for TLS protocol signatures - /// in TLS1.3. - /// - /// This prevents (eg) RSA_PKCS1_SHA256 being offered or accepted, even if our - /// verifier supports it for other protocol versions. + /// The `CertificateType` enum sent in the cert_type extensions. + /// Values in this enum are taken from the various RFCs covering TLS, and are listed by IANA. /// - /// See RFC8446 s4.2.3. - pub(crate) fn supported_in_tls13(&self) -> bool { - matches!( - *self, - Self::ECDSA_NISTP521_SHA512 - | Self::ECDSA_NISTP384_SHA384 - | Self::ECDSA_NISTP256_SHA256 - | Self::RSA_PSS_SHA512 - | Self::RSA_PSS_SHA384 - | Self::RSA_PSS_SHA256 - | Self::ED25519 - ) - } -} + /// [RFC 6091 Section 5]: + /// [RFC 7250 Section 7]: + pub struct CertificateType(pub u8); -enum_builder! { - /// The `SignatureAlgorithm` TLS protocol enum. Values in this enum are taken - /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub enum SignatureAlgorithm { - Anonymous => 0x00, - RSA => 0x01, - DSA => 0x02, - ECDSA => 0x03, - ED25519 => 0x07, - ED448 => 0x08, + enum CertificateTypeName { + X509 => 0x00, + RawPublicKey => 0x02, } } -enum_builder! { - /// The "TLS Certificate Compression Algorithm IDs" TLS protocol enum. - /// Values in this enum are taken from [RFC8879]. - /// - /// [RFC8879]: https://www.rfc-editor.org/rfc/rfc8879.html#section-7.3 - #[repr(u16)] - pub enum CertificateCompressionAlgorithm { - Zlib => 1, - Brotli => 2, - Zstd => 3, +impl Default for CertificateType { + fn default() -> Self { + Self::X509 } } enum_builder! { /// The type of Encrypted Client Hello (`EchClientHelloType`). /// - /// Specified in [draft-ietf-tls-esni Section 5]. + /// Specified in [RFC 9849 Section 5]. /// - /// [draft-ietf-tls-esni Section 5]: - #[repr(u8)] - pub enum EchClientHelloType { + /// [RFC 9849 Section 5]: + pub struct EchClientHelloType(pub u8); + + enum EchClientHelloTypeName { ClientHelloOuter => 0, ClientHelloInner => 1 } @@ -601,20 +241,16 @@ enum_builder! { #[cfg(test)] mod tests { use super::*; - use crate::msgs::enums::tests::{test_enum16, test_enum8}; + use crate::msgs::{test_enum8, test_enum16}; #[test] fn test_enums() { - test_enum8::(SignatureAlgorithm::Anonymous, SignatureAlgorithm::ECDSA); test_enum8::(ContentType::ChangeCipherSpec, ContentType::Heartbeat); test_enum8::(HandshakeType::HelloRequest, HandshakeType::MessageHash); - test_enum8::( - AlertDescription::CloseNotify, - AlertDescription::NoApplicationProtocol, - ); test_enum16::( CertificateCompressionAlgorithm::Zlib, CertificateCompressionAlgorithm::Zstd, ); + test_enum8::(CertificateType::X509, CertificateType::RawPublicKey); } } diff --git a/rustls/src/error.rs b/rustls/src/error.rs deleted file mode 100644 index d7dc6549f43..00000000000 --- a/rustls/src/error.rs +++ /dev/null @@ -1,800 +0,0 @@ -use alloc::format; -use alloc::string::String; -use alloc::vec::Vec; -use core::fmt; -#[cfg(feature = "std")] -use std::time::SystemTimeError; - -use crate::enums::{AlertDescription, ContentType, HandshakeType}; -use crate::msgs::handshake::{EchConfigPayload, KeyExchangeAlgorithm}; -use crate::rand; - -/// rustls reports protocol errors using this type. -#[non_exhaustive] -#[derive(Debug, PartialEq, Clone)] -pub enum Error { - /// We received a TLS message that isn't valid right now. - /// `expect_types` lists the message types we can expect right now. - /// `got_type` is the type we found. This error is typically - /// caused by a buggy TLS stack (the peer or this one), a broken - /// network, or an attack. - InappropriateMessage { - /// Which types we expected - expect_types: Vec, - /// What type we received - got_type: ContentType, - }, - - /// We received a TLS handshake message that isn't valid right now. - /// `expect_types` lists the handshake message types we can expect - /// right now. `got_type` is the type we found. - InappropriateHandshakeMessage { - /// Which handshake type we expected - expect_types: Vec, - /// What handshake type we received - got_type: HandshakeType, - }, - - /// An error occurred while handling Encrypted Client Hello (ECH). - InvalidEncryptedClientHello(EncryptedClientHelloError), - - /// The peer sent us a TLS message with invalid contents. - InvalidMessage(InvalidMessage), - - /// The peer didn't give us any certificates. - NoCertificatesPresented, - - /// The certificate verifier doesn't support the given type of name. - UnsupportedNameType, - - /// We couldn't decrypt a message. This is invariably fatal. - DecryptError, - - /// We couldn't encrypt a message because it was larger than the allowed message size. - /// This should never happen if the application is using valid record sizes. - EncryptError, - - /// The peer doesn't support a protocol version/feature we require. - /// The parameter gives a hint as to what version/feature it is. - PeerIncompatible(PeerIncompatible), - - /// The peer deviated from the standard TLS protocol. - /// The parameter gives a hint where. - PeerMisbehaved(PeerMisbehaved), - - /// We received a fatal alert. This means the peer is unhappy. - AlertReceived(AlertDescription), - - /// We saw an invalid certificate. - /// - /// The contained error is from the certificate validation trait - /// implementation. - InvalidCertificate(CertificateError), - - /// A provided certificate revocation list (CRL) was invalid. - InvalidCertRevocationList(CertRevocationListError), - - /// A catch-all error for unlikely errors. - General(String), - - /// We failed to figure out what time it currently is. - FailedToGetCurrentTime, - - /// We failed to acquire random bytes from the system. - FailedToGetRandomBytes, - - /// This function doesn't work until the TLS handshake - /// is complete. - HandshakeNotComplete, - - /// The peer sent an oversized record/fragment. - PeerSentOversizedRecord, - - /// An incoming connection did not support any known application protocol. - NoApplicationProtocol, - - /// The `max_fragment_size` value supplied in configuration was too small, - /// or too large. - BadMaxFragmentSize, - - /// Specific failure cases from [`keys_match`] or a [`crate::crypto::signer::SigningKey`] that cannot produce a corresponding public key. - /// - /// [`keys_match`]: crate::crypto::signer::CertifiedKey::keys_match - InconsistentKeys(InconsistentKeys), - - /// Any other error. - /// - /// This variant should only be used when the error is not better described by a more - /// specific variant. For example, if a custom crypto provider returns a - /// provider specific error. - /// - /// Enums holding this variant will never compare equal to each other. - Other(OtherError), -} - -/// Specific failure cases from [`keys_match`] or a [`crate::crypto::signer::SigningKey`] that cannot produce a corresponding public key. -/// -/// [`keys_match`]: crate::crypto::signer::CertifiedKey::keys_match -#[non_exhaustive] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum InconsistentKeys { - /// The public key returned by the [`SigningKey`] does not match the public key information in the certificate. - /// - /// [`SigningKey`]: crate::crypto::signer::SigningKey - KeyMismatch, - - /// The [`SigningKey`] cannot produce its corresponding public key. - /// - /// [`SigningKey`]: crate::crypto::signer::SigningKey - Unknown, -} - -impl From for Error { - #[inline] - fn from(e: InconsistentKeys) -> Self { - Self::InconsistentKeys(e) - } -} - -/// A corrupt TLS message payload that resulted in an error. -#[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq)] - -pub enum InvalidMessage { - /// A certificate payload exceeded rustls's 64KB limit - CertificatePayloadTooLarge, - /// An advertised message was larger then expected. - HandshakePayloadTooLarge, - /// The peer sent us a syntactically incorrect ChangeCipherSpec payload. - InvalidCcs, - /// An unknown content type was encountered during message decoding. - InvalidContentType, - /// A peer sent an invalid certificate status type - InvalidCertificateStatusType, - /// Context was incorrectly attached to a certificate request during a handshake. - InvalidCertRequest, - /// A peer's DH params could not be decoded - InvalidDhParams, - /// A message was zero-length when its record kind forbids it. - InvalidEmptyPayload, - /// A peer sent an unexpected key update request. - InvalidKeyUpdate, - /// A peer's server name could not be decoded - InvalidServerName, - /// A TLS message payload was larger then allowed by the specification. - MessageTooLarge, - /// Message is shorter than the expected length - MessageTooShort, - /// Missing data for the named handshake payload value - MissingData(&'static str), - /// A peer did not advertise its supported key exchange groups. - MissingKeyExchange, - /// A peer sent an empty list of signature schemes - NoSignatureSchemes, - /// Trailing data found for the named handshake payload value - TrailingData(&'static str), - /// A peer sent an unexpected message type. - UnexpectedMessage(&'static str), - /// An unknown TLS protocol was encountered during message decoding. - UnknownProtocolVersion, - /// A peer sent a non-null compression method. - UnsupportedCompression, - /// A peer sent an unknown elliptic curve type. - UnsupportedCurveType, - /// A peer sent an unsupported key exchange algorithm. - UnsupportedKeyExchangeAlgorithm(KeyExchangeAlgorithm), -} - -impl From for Error { - #[inline] - fn from(e: InvalidMessage) -> Self { - Self::InvalidMessage(e) - } -} - -#[non_exhaustive] -#[allow(missing_docs)] -#[derive(Debug, PartialEq, Clone)] -/// The set of cases where we failed to make a connection because we thought -/// the peer was misbehaving. -/// -/// This is `non_exhaustive`: we might add or stop using items here in minor -/// versions. We also don't document what they mean. Generally a user of -/// rustls shouldn't vary its behaviour on these error codes, and there is -/// nothing it can do to improve matters. -/// -/// Please file a bug against rustls if you see `Error::PeerMisbehaved` in -/// the wild. -pub enum PeerMisbehaved { - AttemptedDowngradeToTls12WhenTls13IsSupported, - BadCertChainExtensions, - DisallowedEncryptedExtension, - DuplicateClientHelloExtensions, - DuplicateEncryptedExtensions, - DuplicateHelloRetryRequestExtensions, - DuplicateNewSessionTicketExtensions, - DuplicateServerHelloExtensions, - DuplicateServerNameTypes, - EarlyDataAttemptedInSecondClientHello, - EarlyDataExtensionWithoutResumption, - EarlyDataOfferedWithVariedCipherSuite, - HandshakeHashVariedAfterRetry, - IllegalHelloRetryRequestWithEmptyCookie, - IllegalHelloRetryRequestWithNoChanges, - IllegalHelloRetryRequestWithOfferedGroup, - IllegalHelloRetryRequestWithUnofferedCipherSuite, - IllegalHelloRetryRequestWithUnofferedNamedGroup, - IllegalHelloRetryRequestWithUnsupportedVersion, - IllegalHelloRetryRequestWithWrongSessionId, - IllegalHelloRetryRequestWithInvalidEch, - IllegalMiddleboxChangeCipherSpec, - IllegalTlsInnerPlaintext, - IncorrectBinder, - InvalidCertCompression, - InvalidMaxEarlyDataSize, - InvalidKeyShare, - KeyEpochWithPendingFragment, - KeyUpdateReceivedInQuicConnection, - MessageInterleavedWithHandshakeMessage, - MissingBinderInPskExtension, - MissingKeyShare, - MissingPskModesExtension, - MissingQuicTransportParameters, - OfferedDuplicateCertificateCompressions, - OfferedDuplicateKeyShares, - OfferedEarlyDataWithOldProtocolVersion, - OfferedEmptyApplicationProtocol, - OfferedIncorrectCompressions, - PskExtensionMustBeLast, - PskExtensionWithMismatchedIdsAndBinders, - RefusedToFollowHelloRetryRequest, - RejectedEarlyDataInterleavedWithHandshakeMessage, - ResumptionAttemptedWithVariedEms, - ResumptionOfferedWithVariedCipherSuite, - ResumptionOfferedWithVariedEms, - ResumptionOfferedWithIncompatibleCipherSuite, - SelectedDifferentCipherSuiteAfterRetry, - SelectedInvalidPsk, - SelectedTls12UsingTls13VersionExtension, - SelectedUnofferedApplicationProtocol, - SelectedUnofferedCertCompression, - SelectedUnofferedCipherSuite, - SelectedUnofferedCompression, - SelectedUnofferedKxGroup, - SelectedUnofferedPsk, - SelectedUnusableCipherSuiteForVersion, - ServerHelloMustOfferUncompressedEcPoints, - ServerNameDifferedOnRetry, - ServerNameMustContainOneHostName, - SignedKxWithWrongAlgorithm, - SignedHandshakeWithUnadvertisedSigScheme, - TooManyEmptyFragments, - TooManyKeyUpdateRequests, - TooManyRenegotiationRequests, - TooManyWarningAlertsReceived, - TooMuchEarlyDataReceived, - UnexpectedCleartextExtension, - UnsolicitedCertExtension, - UnsolicitedEncryptedExtension, - UnsolicitedSctList, - UnsolicitedServerHelloExtension, - WrongGroupForKeyShare, - UnsolicitedEchExtension, -} - -impl From for Error { - #[inline] - fn from(e: PeerMisbehaved) -> Self { - Self::PeerMisbehaved(e) - } -} - -#[non_exhaustive] -#[allow(missing_docs)] -#[derive(Debug, PartialEq, Clone)] -/// The set of cases where we failed to make a connection because a peer -/// doesn't support a TLS version/feature we require. -/// -/// This is `non_exhaustive`: we might add or stop using items here in minor -/// versions. -pub enum PeerIncompatible { - EcPointsExtensionRequired, - ExtendedMasterSecretExtensionRequired, - IncorrectCertificateTypeExtension, - KeyShareExtensionRequired, - NamedGroupsExtensionRequired, - NoCertificateRequestSignatureSchemesInCommon, - NoCipherSuitesInCommon, - NoEcPointFormatsInCommon, - NoKxGroupsInCommon, - NoSignatureSchemesInCommon, - NullCompressionRequired, - ServerDoesNotSupportTls12Or13, - ServerSentHelloRetryRequestWithUnknownExtension, - ServerTlsVersionIsDisabledByOurConfig, - SignatureAlgorithmsExtensionRequired, - SupportedVersionsExtensionRequired, - Tls12NotOffered, - Tls12NotOfferedOrEnabled, - Tls13RequiredForQuic, - UncompressedEcPointsRequired, - UnsolicitedCertificateTypeExtension, - ServerRejectedEncryptedClientHello(Option>), -} - -impl From for Error { - #[inline] - fn from(e: PeerIncompatible) -> Self { - Self::PeerIncompatible(e) - } -} - -#[non_exhaustive] -#[derive(Debug, Clone)] -/// The ways in which certificate validators can express errors. -/// -/// Note that the rustls TLS protocol code interprets specifically these -/// error codes to send specific TLS alerts. Therefore, if a -/// custom certificate validator uses incorrect errors the library as -/// a whole will send alerts that do not match the standard (this is usually -/// a minor issue, but could be misleading). -pub enum CertificateError { - /// The certificate is not correctly encoded. - BadEncoding, - - /// The current time is after the `notAfter` time in the certificate. - Expired, - - /// The current time is before the `notBefore` time in the certificate. - NotValidYet, - - /// The certificate has been revoked. - Revoked, - - /// The certificate contains an extension marked critical, but it was - /// not processed by the certificate validator. - UnhandledCriticalExtension, - - /// The certificate chain is not issued by a known root certificate. - UnknownIssuer, - - /// The certificate's revocation status could not be determined. - UnknownRevocationStatus, - - /// The certificate's revocation status could not be determined, because the CRL is expired. - ExpiredRevocationList, - - /// A certificate is not correctly signed by the key of its alleged - /// issuer. - BadSignature, - - /// The subject names in an end-entity certificate do not include - /// the expected name. - NotValidForName, - - /// The certificate is being used for a different purpose than allowed. - InvalidPurpose, - - /// The certificate is valid, but the handshake is rejected for other - /// reasons. - ApplicationVerificationFailure, - - /// Any other error. - /// - /// This can be used by custom verifiers to expose the underlying error - /// (where they are not better described by the more specific errors - /// above). - /// - /// It is also used by the default verifier in case its error is - /// not covered by the above common cases. - /// - /// Enums holding this variant will never compare equal to each other. - Other(OtherError), -} - -impl PartialEq for CertificateError { - fn eq(&self, other: &Self) -> bool { - use CertificateError::*; - #[allow(clippy::match_like_matches_macro)] - match (self, other) { - (BadEncoding, BadEncoding) => true, - (Expired, Expired) => true, - (NotValidYet, NotValidYet) => true, - (Revoked, Revoked) => true, - (UnhandledCriticalExtension, UnhandledCriticalExtension) => true, - (UnknownIssuer, UnknownIssuer) => true, - (BadSignature, BadSignature) => true, - (NotValidForName, NotValidForName) => true, - (InvalidPurpose, InvalidPurpose) => true, - (ApplicationVerificationFailure, ApplicationVerificationFailure) => true, - (ExpiredRevocationList, ExpiredRevocationList) => true, - _ => false, - } - } -} - -// The following mapping are heavily referenced in: -// * [OpenSSL Implementation](https://github.com/openssl/openssl/blob/45bb98bfa223efd3258f445ad443f878011450f0/ssl/statem/statem_lib.c#L1434) -// * [BoringSSL Implementation](https://github.com/google/boringssl/blob/583c60bd4bf76d61b2634a58bcda99a92de106cb/ssl/ssl_x509.cc#L1323) -impl From for AlertDescription { - fn from(e: CertificateError) -> Self { - use CertificateError::*; - match e { - BadEncoding | UnhandledCriticalExtension | NotValidForName => Self::BadCertificate, - // RFC 5246/RFC 8446 - // certificate_expired - // A certificate has expired or **is not currently valid**. - Expired | NotValidYet => Self::CertificateExpired, - Revoked => Self::CertificateRevoked, - // OpenSSL, BoringSSL and AWS-LC all generate an Unknown CA alert for - // the case where revocation status can not be determined, so we do the same here. - UnknownIssuer | UnknownRevocationStatus | ExpiredRevocationList => Self::UnknownCA, - BadSignature => Self::DecryptError, - InvalidPurpose => Self::UnsupportedCertificate, - ApplicationVerificationFailure => Self::AccessDenied, - // RFC 5246/RFC 8446 - // certificate_unknown - // Some other (unspecified) issue arose in processing the - // certificate, rendering it unacceptable. - Other(..) => Self::CertificateUnknown, - } - } -} - -impl From for Error { - #[inline] - fn from(e: CertificateError) -> Self { - Self::InvalidCertificate(e) - } -} - -#[non_exhaustive] -#[derive(Debug, Clone)] -/// The ways in which a certificate revocation list (CRL) can be invalid. -pub enum CertRevocationListError { - /// The CRL had a bad, or unsupported signature from its issuer. - BadSignature, - - /// The CRL contained an invalid CRL number. - InvalidCrlNumber, - - /// The CRL contained a revoked certificate with an invalid serial number. - InvalidRevokedCertSerialNumber, - - /// The CRL issuer does not specify the cRLSign key usage. - IssuerInvalidForCrl, - - /// The CRL is invalid for some other reason. - /// - /// Enums holding this variant will never compare equal to each other. - Other(OtherError), - - /// The CRL is not correctly encoded. - ParseError, - - /// The CRL is not a v2 X.509 CRL. - UnsupportedCrlVersion, - - /// The CRL, or a revoked certificate in the CRL, contained an unsupported critical extension. - UnsupportedCriticalExtension, - - /// The CRL is an unsupported delta CRL, containing only changes relative to another CRL. - UnsupportedDeltaCrl, - - /// The CRL is an unsupported indirect CRL, containing revoked certificates issued by a CA - /// other than the issuer of the CRL. - UnsupportedIndirectCrl, - - /// The CRL contained a revoked certificate with an unsupported revocation reason. - /// See RFC 5280 Section 5.3.1[^1] for a list of supported revocation reasons. - /// - /// [^1]: - UnsupportedRevocationReason, -} - -impl PartialEq for CertRevocationListError { - fn eq(&self, other: &Self) -> bool { - use CertRevocationListError::*; - #[allow(clippy::match_like_matches_macro)] - match (self, other) { - (BadSignature, BadSignature) => true, - (InvalidCrlNumber, InvalidCrlNumber) => true, - (InvalidRevokedCertSerialNumber, InvalidRevokedCertSerialNumber) => true, - (IssuerInvalidForCrl, IssuerInvalidForCrl) => true, - (ParseError, ParseError) => true, - (UnsupportedCrlVersion, UnsupportedCrlVersion) => true, - (UnsupportedCriticalExtension, UnsupportedCriticalExtension) => true, - (UnsupportedDeltaCrl, UnsupportedDeltaCrl) => true, - (UnsupportedIndirectCrl, UnsupportedIndirectCrl) => true, - (UnsupportedRevocationReason, UnsupportedRevocationReason) => true, - _ => false, - } - } -} - -impl From for Error { - #[inline] - fn from(e: CertRevocationListError) -> Self { - Self::InvalidCertRevocationList(e) - } -} - -#[non_exhaustive] -#[derive(Debug, Clone, Eq, PartialEq)] -/// An error that occurred while handling Encrypted Client Hello (ECH). -pub enum EncryptedClientHelloError { - /// The provided ECH configuration list was invalid. - InvalidConfigList, - /// No compatible ECH configuration. - NoCompatibleConfig, - /// The client configuration has server name indication (SNI) disabled. - SniRequired, -} - -impl From for Error { - #[inline] - fn from(e: EncryptedClientHelloError) -> Self { - Self::InvalidEncryptedClientHello(e) - } -} - -fn join(items: &[T]) -> String { - items - .iter() - .map(|x| format!("{:?}", x)) - .collect::>() - .join(" or ") -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Self::InappropriateMessage { - ref expect_types, - ref got_type, - } => write!( - f, - "received unexpected message: got {:?} when expecting {}", - got_type, - join::(expect_types) - ), - Self::InappropriateHandshakeMessage { - ref expect_types, - ref got_type, - } => write!( - f, - "received unexpected handshake message: got {:?} when expecting {}", - got_type, - join::(expect_types) - ), - Self::InvalidMessage(ref typ) => { - write!(f, "received corrupt message of type {:?}", typ) - } - Self::PeerIncompatible(ref why) => write!(f, "peer is incompatible: {:?}", why), - Self::PeerMisbehaved(ref why) => write!(f, "peer misbehaved: {:?}", why), - Self::AlertReceived(ref alert) => write!(f, "received fatal alert: {:?}", alert), - Self::InvalidCertificate(ref err) => { - write!(f, "invalid peer certificate: {:?}", err) - } - Self::InvalidCertRevocationList(ref err) => { - write!(f, "invalid certificate revocation list: {:?}", err) - } - Self::NoCertificatesPresented => write!(f, "peer sent no certificates"), - Self::UnsupportedNameType => write!(f, "presented server name type wasn't supported"), - Self::DecryptError => write!(f, "cannot decrypt peer's message"), - Self::InvalidEncryptedClientHello(ref err) => { - write!(f, "encrypted client hello failure: {:?}", err) - } - Self::EncryptError => write!(f, "cannot encrypt message"), - Self::PeerSentOversizedRecord => write!(f, "peer sent excess record size"), - Self::HandshakeNotComplete => write!(f, "handshake not complete"), - Self::NoApplicationProtocol => write!(f, "peer doesn't support any known protocol"), - Self::FailedToGetCurrentTime => write!(f, "failed to get current time"), - Self::FailedToGetRandomBytes => write!(f, "failed to get random bytes"), - Self::BadMaxFragmentSize => { - write!(f, "the supplied max_fragment_size was too small or large") - } - Self::InconsistentKeys(ref why) => { - write!(f, "keys may not be consistent: {:?}", why) - } - Self::General(ref err) => write!(f, "unexpected error: {}", err), - Self::Other(ref err) => write!(f, "other error: {}", err), - } - } -} - -#[cfg(feature = "std")] -impl From for Error { - #[inline] - fn from(_: SystemTimeError) -> Self { - Self::FailedToGetCurrentTime - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Error {} - -impl From for Error { - fn from(_: rand::GetRandomFailed) -> Self { - Self::FailedToGetRandomBytes - } -} - -mod other_error { - #[cfg(feature = "std")] - use alloc::sync::Arc; - use core::fmt; - #[cfg(feature = "std")] - use std::error::Error as StdError; - - use super::Error; - - /// Any other error that cannot be expressed by a more specific [`Error`] variant. - /// - /// For example, an `OtherError` could be produced by a custom crypto provider - /// exposing a provider specific error. - /// - /// Enums holding this type will never compare equal to each other. - #[derive(Debug, Clone)] - pub struct OtherError(#[cfg(feature = "std")] pub Arc); - - impl PartialEq for OtherError { - fn eq(&self, _other: &Self) -> bool { - false - } - } - - impl From for Error { - fn from(value: OtherError) -> Self { - Self::Other(value) - } - } - - impl fmt::Display for OtherError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[cfg(feature = "std")] - { - write!(f, "{}", self.0) - } - #[cfg(not(feature = "std"))] - { - f.write_str("no further information available") - } - } - } - - #[cfg(feature = "std")] - impl StdError for OtherError { - fn source(&self) -> Option<&(dyn StdError + 'static)> { - Some(self.0.as_ref()) - } - } -} - -pub use other_error::OtherError; - -#[cfg(test)] -mod tests { - use std::prelude::v1::*; - use std::{println, vec}; - - use super::{CertRevocationListError, Error, InconsistentKeys, InvalidMessage, OtherError}; - - #[test] - fn certificate_error_equality() { - use super::CertificateError::*; - assert_eq!(BadEncoding, BadEncoding); - assert_eq!(Expired, Expired); - assert_eq!(NotValidYet, NotValidYet); - assert_eq!(Revoked, Revoked); - assert_eq!(UnhandledCriticalExtension, UnhandledCriticalExtension); - assert_eq!(UnknownIssuer, UnknownIssuer); - assert_eq!(BadSignature, BadSignature); - assert_eq!(NotValidForName, NotValidForName); - assert_eq!(InvalidPurpose, InvalidPurpose); - assert_eq!( - ApplicationVerificationFailure, - ApplicationVerificationFailure - ); - let other = Other(OtherError( - #[cfg(feature = "std")] - alloc::sync::Arc::from(Box::from("")), - )); - assert_ne!(other, other); - assert_ne!(BadEncoding, Expired); - } - - #[test] - fn crl_error_equality() { - use super::CertRevocationListError::*; - assert_eq!(BadSignature, BadSignature); - assert_eq!(InvalidCrlNumber, InvalidCrlNumber); - assert_eq!( - InvalidRevokedCertSerialNumber, - InvalidRevokedCertSerialNumber - ); - assert_eq!(IssuerInvalidForCrl, IssuerInvalidForCrl); - assert_eq!(ParseError, ParseError); - assert_eq!(UnsupportedCriticalExtension, UnsupportedCriticalExtension); - assert_eq!(UnsupportedCrlVersion, UnsupportedCrlVersion); - assert_eq!(UnsupportedDeltaCrl, UnsupportedDeltaCrl); - assert_eq!(UnsupportedIndirectCrl, UnsupportedIndirectCrl); - assert_eq!(UnsupportedRevocationReason, UnsupportedRevocationReason); - let other = Other(OtherError( - #[cfg(feature = "std")] - alloc::sync::Arc::from(Box::from("")), - )); - assert_ne!(other, other); - assert_ne!(BadSignature, InvalidCrlNumber); - } - - #[test] - #[cfg(feature = "std")] - fn other_error_equality() { - let other_error = OtherError(alloc::sync::Arc::from(Box::from(""))); - assert_ne!(other_error, other_error); - let other: Error = other_error.into(); - assert_ne!(other, other); - } - - #[test] - fn smoke() { - use crate::enums::{AlertDescription, ContentType, HandshakeType}; - - let all = vec![ - Error::InappropriateMessage { - expect_types: vec![ContentType::Alert], - got_type: ContentType::Handshake, - }, - Error::InappropriateHandshakeMessage { - expect_types: vec![HandshakeType::ClientHello, HandshakeType::Finished], - got_type: HandshakeType::ServerHello, - }, - Error::InvalidMessage(InvalidMessage::InvalidCcs), - Error::NoCertificatesPresented, - Error::DecryptError, - super::PeerIncompatible::Tls12NotOffered.into(), - super::PeerMisbehaved::UnsolicitedCertExtension.into(), - Error::AlertReceived(AlertDescription::ExportRestriction), - super::CertificateError::Expired.into(), - Error::General("undocumented error".to_string()), - Error::FailedToGetCurrentTime, - Error::FailedToGetRandomBytes, - Error::HandshakeNotComplete, - Error::PeerSentOversizedRecord, - Error::NoApplicationProtocol, - Error::BadMaxFragmentSize, - Error::InconsistentKeys(InconsistentKeys::KeyMismatch), - Error::InconsistentKeys(InconsistentKeys::Unknown), - Error::InvalidCertRevocationList(CertRevocationListError::BadSignature), - Error::Other(OtherError( - #[cfg(feature = "std")] - alloc::sync::Arc::from(Box::from("")), - )), - ]; - - for err in all { - println!("{:?}:", err); - println!(" fmt '{}'", err); - } - } - - #[test] - fn rand_error_mapping() { - use super::rand; - let err: Error = rand::GetRandomFailed.into(); - assert_eq!(err, Error::FailedToGetRandomBytes); - } - - #[cfg(feature = "std")] - #[test] - fn time_error_mapping() { - use std::time::SystemTime; - - let time_error = SystemTime::UNIX_EPOCH - .duration_since(SystemTime::now()) - .unwrap_err(); - let err: Error = time_error.into(); - assert_eq!(err, Error::FailedToGetCurrentTime); - } -} diff --git a/rustls/src/error/mod.rs b/rustls/src/error/mod.rs new file mode 100644 index 00000000000..d09ff8a647c --- /dev/null +++ b/rustls/src/error/mod.rs @@ -0,0 +1,1616 @@ +//! Error types used throughout rustls. + +use alloc::format; +use alloc::string::String; +use alloc::vec::Vec; +use core::ops::Deref; +use core::{fmt, mem}; +use std::time::SystemTimeError; + +use pki_types::{AlgorithmIdentifier, EchConfigListBytes, ServerName, UnixTime}; +#[cfg(feature = "webpki")] +use webpki::ExtendedKeyUsage; + +use crate::common_state::maybe_send_fatal_alert; +use crate::conn::SendPath; +use crate::crypto::kx::KeyExchangeAlgorithm; +use crate::crypto::{CipherSuite, GetRandomFailed, InconsistentKeys}; +use crate::enums::{ContentType, HandshakeType}; +use crate::msgs::{Codec, EchConfigPayload}; + +#[cfg(test)] +mod tests; + +/// rustls reports protocol errors using this type. +#[non_exhaustive] +#[derive(Debug, PartialEq, Clone)] +pub enum Error { + /// We received a TLS message that isn't valid right now. + /// `expect_types` lists the message types we can expect right now. + /// `got_type` is the type we found. This error is typically + /// caused by a buggy TLS stack (the peer or this one), a broken + /// network, or an attack. + InappropriateMessage { + /// Which types we expected + expect_types: Vec, + /// What type we received + got_type: ContentType, + }, + + /// We received a TLS handshake message that isn't valid right now. + /// `expect_types` lists the handshake message types we can expect + /// right now. `got_type` is the type we found. + InappropriateHandshakeMessage { + /// Which handshake type we expected + expect_types: Vec, + /// What handshake type we received + got_type: HandshakeType, + }, + + /// An error occurred while handling Encrypted Client Hello (ECH). + InvalidEncryptedClientHello(EncryptedClientHelloError), + + /// The peer sent us a TLS message with invalid contents. + InvalidMessage(InvalidMessage), + + /// The certificate verifier doesn't support the given type of name. + UnsupportedNameType, + + /// We couldn't decrypt a message. This is invariably fatal. + DecryptError, + + /// We couldn't encrypt a message because it was larger than the allowed message size. + /// This should never happen if the application is using valid record sizes. + EncryptError, + + /// The peer doesn't support a protocol version/feature we require. + /// The parameter gives a hint as to what version/feature it is. + PeerIncompatible(PeerIncompatible), + + /// The peer deviated from the standard TLS protocol. + /// The parameter gives a hint where. + PeerMisbehaved(PeerMisbehaved), + + /// We received a fatal alert. This means the peer is unhappy. + AlertReceived(AlertDescription), + + /// We saw an invalid certificate. + /// + /// The contained error is from the certificate validation trait + /// implementation. + InvalidCertificate(CertificateError), + + /// A provided certificate revocation list (CRL) was invalid. + InvalidCertRevocationList(CertRevocationListError), + + /// A catch-all error for unlikely errors. + General(String), + + /// We failed to figure out what time it currently is. + FailedToGetCurrentTime, + + /// We failed to acquire random bytes from the system. + FailedToGetRandomBytes, + + /// This function doesn't work until the TLS handshake + /// is complete. + HandshakeNotComplete, + + /// The peer sent an oversized record/fragment. + PeerSentOversizedRecord, + + /// An incoming connection did not support any known application protocol. + NoApplicationProtocol, + + /// The server certificate resolver didn't find an appropriate certificate. + NoSuitableCertificate, + + /// The `max_fragment_size` value supplied in configuration was too small, + /// or too large. + BadMaxFragmentSize, + + /// Specific failure cases from [`Credentials::new()`] or a + /// [`crate::crypto::SigningKey`] that cannot produce a corresponding public key. + /// + /// If encountered while building a [`Credentials`], consider if + /// [`Credentials::new_unchecked()`] might be appropriate for your use case. + /// + /// [`Credentials::new()`]: crate::crypto::Credentials::new() + /// [`Credentials`]: crate::crypto::Credentials + /// [`Credentials::new_unchecked()`]: crate::crypto::Credentials::new_unchecked() + InconsistentKeys(InconsistentKeys), + + /// The server rejected encrypted client hello (ECH) negotiation + /// + /// It may have returned new ECH configurations that could be used to retry negotiation + /// with a fresh connection. + /// + /// See [`RejectedEch::can_retry()`] and [`crate::client::EchConfig::for_retry()`]. + RejectedEch(RejectedEch), + + /// Errors of this variant should never be produced by the library. + /// + /// Please file a bug if you see one. + Unreachable(&'static str), + + /// The caller misused the API + /// + /// Generally we try to make error cases like this unnecessary by embedding + /// the constraints in the type system, so misuses simply do not compile. But, + /// for cases where that is not possible or exceptionally costly, we return errors + /// of this variant. + /// + /// This only results from the ordering, dependencies or parameter values of calls, + /// so (assuming parameter values are fixed) these can be determined and fixed by + /// reading the code. They are never caused by the values of untrusted data, or + /// other non-determinism. + ApiMisuse(ApiMisuse), + + /// Any other error. + /// + /// This variant should only be used when the error is not better described by a more + /// specific variant. For example, if a custom crypto provider returns a + /// provider specific error. + /// + /// Enums holding this variant will never compare equal to each other. + Other(OtherError), +} + +/// Determine which alert should be sent for a given error. +/// +/// If this mapping fails, no alert is sent. +impl TryFrom<&Error> for AlertDescription { + type Error = (); + + fn try_from(error: &Error) -> Result { + Ok(match error { + Error::DecryptError => Self::BadRecordMac, + Error::InappropriateMessage { .. } | Error::InappropriateHandshakeMessage { .. } => { + Self::UnexpectedMessage + } + Error::InvalidCertificate(e) => Self::from(e), + Error::InvalidMessage(e) => Self::from(*e), + Error::NoApplicationProtocol => Self::NoApplicationProtocol, + Error::PeerMisbehaved(e) => Self::from(*e), + Error::PeerIncompatible(e) => Self::from(*e), + Error::PeerSentOversizedRecord => Self::RecordOverflow, + Error::RejectedEch(_) => Self::EncryptedClientHelloRequired, + + _ => return Err(()), + }) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InappropriateMessage { + expect_types, + got_type, + } => write!( + f, + "received unexpected message: got {:?} when expecting {}", + got_type, + join::(expect_types) + ), + Self::InappropriateHandshakeMessage { + expect_types, + got_type, + } => write!( + f, + "received unexpected handshake message: got {:?} when expecting {}", + got_type, + join::(expect_types) + ), + Self::InvalidMessage(typ) => { + write!(f, "received corrupt message of type {typ:?}") + } + Self::PeerIncompatible(why) => write!(f, "peer is incompatible: {why:?}"), + Self::PeerMisbehaved(why) => write!(f, "peer misbehaved: {why:?}"), + Self::AlertReceived(alert) => write!(f, "received fatal alert: the peer {alert}"), + Self::InvalidCertificate(err) => { + write!(f, "invalid peer certificate: {err}") + } + Self::InvalidCertRevocationList(err) => { + write!(f, "invalid certificate revocation list: {err:?}") + } + Self::UnsupportedNameType => write!(f, "presented server name type wasn't supported"), + Self::DecryptError => write!(f, "cannot decrypt peer's message"), + Self::InvalidEncryptedClientHello(err) => { + write!(f, "encrypted client hello failure: {err:?}") + } + Self::EncryptError => write!(f, "cannot encrypt message"), + Self::PeerSentOversizedRecord => write!(f, "peer sent excess record size"), + Self::HandshakeNotComplete => write!(f, "handshake not complete"), + Self::NoApplicationProtocol => write!(f, "peer doesn't support any known protocol"), + Self::NoSuitableCertificate => write!(f, "no suitable certificate found"), + Self::FailedToGetCurrentTime => write!(f, "failed to get current time"), + Self::FailedToGetRandomBytes => write!(f, "failed to get random bytes"), + Self::BadMaxFragmentSize => { + write!(f, "the supplied max_fragment_size was too small or large") + } + Self::InconsistentKeys(why) => { + write!(f, "keys may not be consistent: {why:?}") + } + Self::RejectedEch(why) => { + write!( + f, + "server rejected encrypted client hello (ECH) {} retry configs", + if why.can_retry() { "with" } else { "without" } + ) + } + Self::General(err) => write!(f, "unexpected error: {err}"), + Self::Unreachable(err) => write!( + f, + "unreachable condition: {err} (please file a bug in rustls)" + ), + Self::ApiMisuse(why) => write!(f, "API misuse: {why:?}"), + Self::Other(err) => write!(f, "other error: {err}"), + } + } +} + +impl From for Error { + #[inline] + fn from(e: CertificateError) -> Self { + Self::InvalidCertificate(e) + } +} + +impl From for Error { + #[inline] + fn from(e: InvalidMessage) -> Self { + Self::InvalidMessage(e) + } +} + +impl From for Error { + #[inline] + fn from(e: PeerMisbehaved) -> Self { + Self::PeerMisbehaved(e) + } +} + +impl From for Error { + #[inline] + fn from(e: PeerIncompatible) -> Self { + Self::PeerIncompatible(e) + } +} + +impl From for Error { + #[inline] + fn from(e: CertRevocationListError) -> Self { + Self::InvalidCertRevocationList(e) + } +} + +impl From for Error { + #[inline] + fn from(e: EncryptedClientHelloError) -> Self { + Self::InvalidEncryptedClientHello(e) + } +} + +impl From for Error { + fn from(rejected_error: RejectedEch) -> Self { + Self::RejectedEch(rejected_error) + } +} + +impl From for Error { + fn from(e: ApiMisuse) -> Self { + Self::ApiMisuse(e) + } +} + +impl From for Error { + fn from(value: OtherError) -> Self { + Self::Other(value) + } +} + +impl From for Error { + #[inline] + fn from(e: InconsistentKeys) -> Self { + Self::InconsistentKeys(e) + } +} + +impl From for Error { + #[inline] + fn from(_: SystemTimeError) -> Self { + Self::FailedToGetCurrentTime + } +} + +impl From for Error { + fn from(_: GetRandomFailed) -> Self { + Self::FailedToGetRandomBytes + } +} + +impl core::error::Error for Error {} + +/// The ways in which certificate validators can express errors. +/// +/// Note that the rustls TLS protocol code interprets specifically these +/// error codes to send specific TLS alerts. Therefore, if a +/// custom certificate validator uses incorrect errors the library as +/// a whole will send alerts that do not match the standard (this is usually +/// a minor issue, but could be misleading). +#[non_exhaustive] +#[derive(Debug, Clone)] +pub enum CertificateError { + /// The certificate is not correctly encoded. + BadEncoding, + + /// The current time is after the `notAfter` time in the certificate. + Expired, + + /// The current time is after the `notAfter` time in the certificate. + /// + /// This variant is semantically the same as `Expired`, but includes + /// extra data to improve error reports. + ExpiredContext { + /// The validation time. + time: UnixTime, + /// The `notAfter` time of the certificate. + not_after: UnixTime, + }, + + /// The current time is before the `notBefore` time in the certificate. + NotValidYet, + + /// The current time is before the `notBefore` time in the certificate. + /// + /// This variant is semantically the same as `NotValidYet`, but includes + /// extra data to improve error reports. + NotValidYetContext { + /// The validation time. + time: UnixTime, + /// The `notBefore` time of the certificate. + not_before: UnixTime, + }, + + /// The certificate has been revoked. + Revoked, + + /// The certificate contains an extension marked critical, but it was + /// not processed by the certificate validator. + UnhandledCriticalExtension, + + /// The certificate chain is not issued by a known root certificate. + UnknownIssuer, + + /// The certificate's revocation status could not be determined. + UnknownRevocationStatus, + + /// The certificate's revocation status could not be determined, because the CRL is expired. + ExpiredRevocationList, + + /// The certificate's revocation status could not be determined, because the CRL is expired. + /// + /// This variant is semantically the same as `ExpiredRevocationList`, but includes + /// extra data to improve error reports. + ExpiredRevocationListContext { + /// The validation time. + time: UnixTime, + /// The nextUpdate time of the CRL. + next_update: UnixTime, + }, + + /// A certificate is not correctly signed by the key of its alleged + /// issuer. + BadSignature, + + /// A signature inside a certificate or on a handshake was made with an unsupported algorithm. + UnsupportedSignatureAlgorithm { + /// The signature algorithm OID that was unsupported. + signature_algorithm_id: Vec, + /// Supported algorithms that were available for signature verification. + supported_algorithms: Vec, + }, + + /// A signature was made with an algorithm that doesn't match the relevant public key. + UnsupportedSignatureAlgorithmForPublicKey { + /// The signature algorithm OID. + signature_algorithm_id: Vec, + /// The public key algorithm OID. + public_key_algorithm_id: Vec, + }, + + /// The subject names in an end-entity certificate do not include + /// the expected name. + NotValidForName, + + /// The subject names in an end-entity certificate do not include + /// the expected name. + /// + /// This variant is semantically the same as `NotValidForName`, but includes + /// extra data to improve error reports. + NotValidForNameContext { + /// Expected server name. + expected: ServerName<'static>, + + /// The names presented in the end entity certificate. + /// + /// These are the subject names as present in the leaf certificate and may contain DNS names + /// with or without a wildcard label as well as IP address names. + presented: Vec, + }, + + /// The certificate is being used for a different purpose than allowed. + InvalidPurpose, + + /// The certificate is being used for a different purpose than allowed. + /// + /// This variant is semantically the same as `InvalidPurpose`, but includes + /// extra data to improve error reports. + InvalidPurposeContext { + /// Extended key purpose that was required by the application. + required: ExtendedKeyPurpose, + /// Extended key purposes that were presented in the peer's certificate. + presented: Vec, + }, + + /// The OCSP response provided to the verifier was invalid. + /// + /// This should be returned from [`ServerVerifier::verify_identity()`] + /// when a verifier checks its `ocsp_response` parameter and finds it invalid. + /// + /// This maps to [`AlertDescription::BadCertificateStatusResponse`]. + /// + /// [`ServerVerifier::verify_identity()`]: crate::client::danger::ServerVerifier::verify_identity + InvalidOcspResponse, + + /// The certificate is valid, but the handshake is rejected for other + /// reasons. + ApplicationVerificationFailure, + + /// Any other error. + /// + /// This can be used by custom verifiers to expose the underlying error + /// (where they are not better described by the more specific errors + /// above). + /// + /// It is also used by the default verifier in case its error is + /// not covered by the above common cases. + /// + /// Enums holding this variant will never compare equal to each other. + Other(OtherError), +} + +impl PartialEq for CertificateError { + fn eq(&self, other: &Self) -> bool { + use CertificateError::*; + match (self, other) { + (BadEncoding, BadEncoding) => true, + (Expired, Expired) => true, + ( + ExpiredContext { + time: left_time, + not_after: left_not_after, + }, + ExpiredContext { + time: right_time, + not_after: right_not_after, + }, + ) => (left_time, left_not_after) == (right_time, right_not_after), + (NotValidYet, NotValidYet) => true, + ( + NotValidYetContext { + time: left_time, + not_before: left_not_before, + }, + NotValidYetContext { + time: right_time, + not_before: right_not_before, + }, + ) => (left_time, left_not_before) == (right_time, right_not_before), + (Revoked, Revoked) => true, + (UnhandledCriticalExtension, UnhandledCriticalExtension) => true, + (UnknownIssuer, UnknownIssuer) => true, + (BadSignature, BadSignature) => true, + ( + UnsupportedSignatureAlgorithm { + signature_algorithm_id: left_signature_algorithm_id, + supported_algorithms: left_supported_algorithms, + }, + UnsupportedSignatureAlgorithm { + signature_algorithm_id: right_signature_algorithm_id, + supported_algorithms: right_supported_algorithms, + }, + ) => { + (left_signature_algorithm_id, left_supported_algorithms) + == (right_signature_algorithm_id, right_supported_algorithms) + } + ( + UnsupportedSignatureAlgorithmForPublicKey { + signature_algorithm_id: left_signature_algorithm_id, + public_key_algorithm_id: left_public_key_algorithm_id, + }, + UnsupportedSignatureAlgorithmForPublicKey { + signature_algorithm_id: right_signature_algorithm_id, + public_key_algorithm_id: right_public_key_algorithm_id, + }, + ) => { + (left_signature_algorithm_id, left_public_key_algorithm_id) + == (right_signature_algorithm_id, right_public_key_algorithm_id) + } + (NotValidForName, NotValidForName) => true, + ( + NotValidForNameContext { + expected: left_expected, + presented: left_presented, + }, + NotValidForNameContext { + expected: right_expected, + presented: right_presented, + }, + ) => (left_expected, left_presented) == (right_expected, right_presented), + (InvalidPurpose, InvalidPurpose) => true, + ( + InvalidPurposeContext { + required: left_required, + presented: left_presented, + }, + InvalidPurposeContext { + required: right_required, + presented: right_presented, + }, + ) => (left_required, left_presented) == (right_required, right_presented), + (InvalidOcspResponse, InvalidOcspResponse) => true, + (ApplicationVerificationFailure, ApplicationVerificationFailure) => true, + (UnknownRevocationStatus, UnknownRevocationStatus) => true, + (ExpiredRevocationList, ExpiredRevocationList) => true, + ( + ExpiredRevocationListContext { + time: left_time, + next_update: left_next_update, + }, + ExpiredRevocationListContext { + time: right_time, + next_update: right_next_update, + }, + ) => (left_time, left_next_update) == (right_time, right_next_update), + _ => false, + } + } +} + +// The following mapping are heavily referenced in: +// * [OpenSSL Implementation](https://github.com/openssl/openssl/blob/45bb98bfa223efd3258f445ad443f878011450f0/ssl/statem/statem_lib.c#L1434) +// * [BoringSSL Implementation](https://github.com/google/boringssl/blob/583c60bd4bf76d61b2634a58bcda99a92de106cb/ssl/ssl_x509.cc#L1323) +impl From<&CertificateError> for AlertDescription { + fn from(e: &CertificateError) -> Self { + use CertificateError::*; + match e { + BadEncoding + | UnhandledCriticalExtension + | NotValidForName + | NotValidForNameContext { .. } => Self::BadCertificate, + // RFC 5246/RFC 8446 + // certificate_expired + // A certificate has expired or **is not currently valid**. + Expired | ExpiredContext { .. } | NotValidYet | NotValidYetContext { .. } => { + Self::CertificateExpired + } + Revoked => Self::CertificateRevoked, + // OpenSSL, BoringSSL and AWS-LC all generate an Unknown CA alert for + // the case where revocation status can not be determined, so we do the same here. + UnknownIssuer + | UnknownRevocationStatus + | ExpiredRevocationList + | ExpiredRevocationListContext { .. } => Self::UnknownCa, + InvalidOcspResponse => Self::BadCertificateStatusResponse, + BadSignature + | UnsupportedSignatureAlgorithm { .. } + | UnsupportedSignatureAlgorithmForPublicKey { .. } => Self::DecryptError, + InvalidPurpose | InvalidPurposeContext { .. } => Self::UnsupportedCertificate, + ApplicationVerificationFailure => Self::AccessDenied, + // RFC 5246/RFC 8446 + // certificate_unknown + // Some other (unspecified) issue arose in processing the + // certificate, rendering it unacceptable. + Other(..) => Self::CertificateUnknown, + } + } +} + +impl fmt::Display for CertificateError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NotValidForNameContext { + expected, + presented, + } => { + write!( + f, + "certificate not valid for name {:?}; certificate ", + expected.to_str() + )?; + + match presented.as_slice() { + &[] => write!( + f, + "is not valid for any names (according to its subjectAltName extension)" + ), + [one] => write!(f, "is only valid for {one}"), + many => { + write!(f, "is only valid for ")?; + + let n = many.len(); + let all_but_last = &many[..n - 1]; + let last = &many[n - 1]; + + for (i, name) in all_but_last.iter().enumerate() { + write!(f, "{name}")?; + if i < n - 2 { + write!(f, ", ")?; + } + } + write!(f, " or {last}") + } + } + } + + Self::ExpiredContext { time, not_after } => write!( + f, + "certificate expired: verification time {} (UNIX), \ + but certificate is not valid after {} \ + ({} seconds ago)", + time.as_secs(), + not_after.as_secs(), + time.as_secs() + .saturating_sub(not_after.as_secs()) + ), + + Self::NotValidYetContext { time, not_before } => write!( + f, + "certificate not valid yet: verification time {} (UNIX), \ + but certificate is not valid before {} \ + ({} seconds in future)", + time.as_secs(), + not_before.as_secs(), + not_before + .as_secs() + .saturating_sub(time.as_secs()) + ), + + Self::ExpiredRevocationListContext { time, next_update } => write!( + f, + "certificate revocation list expired: \ + verification time {} (UNIX), \ + but CRL is not valid after {} \ + ({} seconds ago)", + time.as_secs(), + next_update.as_secs(), + time.as_secs() + .saturating_sub(next_update.as_secs()) + ), + + Self::InvalidPurposeContext { + required, + presented, + } => { + write!( + f, + "certificate does not allow extended key usage for {required}, allows " + )?; + for (i, eku) in presented.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{eku}")?; + } + Ok(()) + } + + Self::Other(other) => write!(f, "{other}"), + + other => write!(f, "{other:?}"), + } + } +} + +enum_builder! { + /// The `AlertDescription` TLS protocol enum. Values in this enum are taken + /// from the various RFCs covering TLS, and are listed by IANA. + pub struct AlertDescription(pub u8); + + enum AlertDescriptionName { + CloseNotify => 0x00, + UnexpectedMessage => 0x0a, + BadRecordMac => 0x14, + DecryptionFailed => 0x15, + RecordOverflow => 0x16, + DecompressionFailure => 0x1e, + HandshakeFailure => 0x28, + NoCertificate => 0x29, + BadCertificate => 0x2a, + UnsupportedCertificate => 0x2b, + CertificateRevoked => 0x2c, + CertificateExpired => 0x2d, + CertificateUnknown => 0x2e, + IllegalParameter => 0x2f, + UnknownCa => 0x30, + AccessDenied => 0x31, + DecodeError => 0x32, + DecryptError => 0x33, + ExportRestriction => 0x3c, + ProtocolVersion => 0x46, + InsufficientSecurity => 0x47, + InternalError => 0x50, + InappropriateFallback => 0x56, + UserCanceled => 0x5a, + NoRenegotiation => 0x64, + MissingExtension => 0x6d, + UnsupportedExtension => 0x6e, + CertificateUnobtainable => 0x6f, + UnrecognizedName => 0x70, + BadCertificateStatusResponse => 0x71, + BadCertificateHashValue => 0x72, + UnknownPskIdentity => 0x73, + CertificateRequired => 0x74, + NoApplicationProtocol => 0x78, + EncryptedClientHelloRequired => 0x79, // https://datatracker.ietf.org/doc/html/rfc9849#section-11.2 + } +} + +impl fmt::Display for AlertDescription { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Ok(known) = AlertDescriptionName::try_from(*self) else { + return write!(f, "sent an unknown alert (0x{:02x?})", self.0); + }; + + // these should be: + // - in past tense + // - be syntactically correct if prefaced with 'the peer' to describe + // received alerts + match known { + // this is normal. + AlertDescriptionName::CloseNotify => write!(f, "cleanly closed the connection"), + + // these are abnormal. they are usually symptomatic of an interop failure. + // please file a bug report. + AlertDescriptionName::UnexpectedMessage => write!(f, "received an unexpected message"), + AlertDescriptionName::BadRecordMac => write!(f, "failed to verify a message"), + AlertDescriptionName::RecordOverflow => write!(f, "rejected an over-length message"), + AlertDescriptionName::IllegalParameter => write!( + f, + "rejected a message because a field was incorrect or inconsistent" + ), + AlertDescriptionName::DecodeError => write!(f, "failed to decode a message"), + AlertDescriptionName::DecryptError => { + write!(f, "failed to perform a handshake cryptographic operation") + } + AlertDescriptionName::InappropriateFallback => { + write!(f, "detected an attempted version downgrade") + } + AlertDescriptionName::MissingExtension => { + write!(f, "required a specific extension that was not provided") + } + AlertDescriptionName::UnsupportedExtension => { + write!(f, "rejected an unsolicited extension") + } + + // these are deprecated by TLS1.3 and should be very rare (but possible + // with TLS1.2 or earlier peers) + AlertDescriptionName::DecryptionFailed => write!(f, "failed to decrypt a message"), + AlertDescriptionName::DecompressionFailure => { + write!(f, "failed to decompress a message") + } + AlertDescriptionName::NoCertificate => write!(f, "found no certificate"), + AlertDescriptionName::ExportRestriction => { + write!(f, "refused due to export restrictions") + } + AlertDescriptionName::NoRenegotiation => { + write!(f, "rejected an attempt at renegotiation") + } + AlertDescriptionName::CertificateUnobtainable => { + write!(f, "failed to retrieve its certificate") + } + AlertDescriptionName::BadCertificateHashValue => { + write!(f, "rejected the `certificate_hash` extension") + } + + // this is fairly normal. it means a server cannot choose compatible parameters + // given our offer. please use ssllabs.com or similar to investigate what parameters + // the server supports. + AlertDescriptionName::HandshakeFailure => write!( + f, + "failed to negotiate an acceptable set of security parameters" + ), + AlertDescriptionName::ProtocolVersion => { + write!(f, "did not support a suitable TLS version") + } + AlertDescriptionName::InsufficientSecurity => { + write!(f, "required a higher security level than was offered") + } + + // these usually indicate a local misconfiguration, either in certificate selection + // or issuance. + AlertDescriptionName::BadCertificate => { + write!( + f, + "rejected the certificate as corrupt or incorrectly signed" + ) + } + AlertDescriptionName::UnsupportedCertificate => { + write!(f, "did not support the certificate") + } + AlertDescriptionName::CertificateRevoked => { + write!(f, "found the certificate to be revoked") + } + AlertDescriptionName::CertificateExpired => { + write!(f, "found the certificate to be expired") + } + AlertDescriptionName::CertificateUnknown => { + write!(f, "rejected the certificate for an unspecified reason") + } + AlertDescriptionName::UnknownCa => { + write!(f, "found the certificate was not issued by a trusted CA") + } + AlertDescriptionName::BadCertificateStatusResponse => { + write!(f, "rejected the certificate status response") + } + // typically this means client authentication is required, in TLS1.2... + AlertDescriptionName::AccessDenied => write!(f, "denied access"), + // and in TLS1.3... + AlertDescriptionName::CertificateRequired => { + write!(f, "required a client certificate") + } + + AlertDescriptionName::InternalError => write!(f, "encountered an internal error"), + AlertDescriptionName::UserCanceled => write!(f, "canceled the handshake"), + + // rejection of SNI (uncommon; usually servers behave as if it was not sent) + AlertDescriptionName::UnrecognizedName => { + write!(f, "did not recognize a name in the `server_name` extension") + } + + // rejection of PSK connections (NYI in this library); indicates a local + // misconfiguration. + AlertDescriptionName::UnknownPskIdentity => { + write!(f, "did not recognize any offered PSK identity") + } + + // rejection of ALPN (varying levels of support, but missing support is + // often dangerous if the peers fail to agree on the same protocol) + AlertDescriptionName::NoApplicationProtocol => write!( + f, + "did not support any of the offered application protocols" + ), + + // ECH requirement by clients, see + // + AlertDescriptionName::EncryptedClientHelloRequired => { + write!(f, "required use of encrypted client hello") + } + } + } +} + +/// A corrupt TLS message payload that resulted in an error. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum InvalidMessage { + /// A certificate payload exceeded rustls's 64KB limit + CertificatePayloadTooLarge, + /// An advertised message was larger then expected. + HandshakePayloadTooLarge, + /// The peer sent us a syntactically incorrect ChangeCipherSpec payload. + InvalidCcs, + /// An unknown content type was encountered during message decoding. + InvalidContentType, + /// A peer sent an invalid certificate status type + InvalidCertificateStatusType, + /// Context was incorrectly attached to a certificate request during a handshake. + InvalidCertRequest, + /// A peer's DH params could not be decoded + InvalidDhParams, + /// A message was zero-length when its record kind forbids it. + InvalidEmptyPayload, + /// A peer sent an unexpected key update request. + InvalidKeyUpdate, + /// A peer's server name could not be decoded + InvalidServerName, + /// A TLS message payload was larger then allowed by the specification. + MessageTooLarge, + /// Message is shorter than the expected length + MessageTooShort, + /// Missing data for the named handshake payload value + MissingData(&'static str), + /// A peer did not advertise its supported key exchange groups. + MissingKeyExchange, + /// A peer sent an empty list of signature schemes + NoSignatureSchemes, + /// Trailing data found for the named handshake payload value + TrailingData(&'static str), + /// A peer sent an unexpected message type. + UnexpectedMessage(&'static str), + /// An unknown TLS protocol was encountered during message decoding. + UnknownProtocolVersion, + /// A peer sent a non-null compression method. + UnsupportedCompression, + /// A peer sent an unknown elliptic curve type. + UnsupportedCurveType, + /// A peer sent an unsupported key exchange algorithm. + UnsupportedKeyExchangeAlgorithm(KeyExchangeAlgorithm), + /// A server sent an empty ticket + EmptyTicketValue, + /// A peer sent an empty list of items, but a non-empty list is required. + /// + /// The argument names the context. + IllegalEmptyList(&'static str), + /// A peer sent a message where a given extension type was repeated + DuplicateExtension(u16), + /// A peer sent a message with a PSK offer extension in wrong position + PreSharedKeyIsNotFinalExtension, + /// A server sent a HelloRetryRequest with an unknown extension + UnknownHelloRetryRequestExtension, + /// The peer sent a TLS1.3 Certificate with an unknown extension + UnknownCertificateExtension, +} + +impl From for AlertDescription { + fn from(e: InvalidMessage) -> Self { + match e { + InvalidMessage::PreSharedKeyIsNotFinalExtension => Self::IllegalParameter, + InvalidMessage::DuplicateExtension(_) => Self::IllegalParameter, + InvalidMessage::UnknownHelloRetryRequestExtension => Self::UnsupportedExtension, + InvalidMessage::CertificatePayloadTooLarge => Self::BadCertificate, + _ => Self::DecodeError, + } + } +} + +/// The set of cases where we failed to make a connection because we thought +/// the peer was misbehaving. +/// +/// This is `non_exhaustive`: we might add or stop using items here in minor +/// versions. We also don't document what they mean. Generally a user of +/// rustls shouldn't vary its behaviour on these error codes, and there is +/// nothing it can do to improve matters. +/// +/// Please file a bug against rustls if you see `Error::PeerMisbehaved` in +/// the wild. +#[expect(missing_docs)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PeerMisbehaved { + AttemptedDowngradeToTls12WhenTls13IsSupported, + BadCertChainExtensions, + DisallowedEncryptedExtension, + DuplicateClientHelloExtensions, + DuplicateEncryptedExtensions, + DuplicateHelloRetryRequestExtensions, + DuplicateNewSessionTicketExtensions, + DuplicateServerHelloExtensions, + DuplicateServerNameTypes, + EarlyDataAttemptedInSecondClientHello, + EarlyDataExtensionWithoutResumption, + EarlyDataOfferedWithVariedCipherSuite, + HandshakeHashVariedAfterRetry, + /// Received an alert with an undefined level and the given [`AlertDescription`] + IllegalAlertLevel(u8, AlertDescription), + IllegalHelloRetryRequestWithEmptyCookie, + IllegalHelloRetryRequestWithNoChanges, + IllegalHelloRetryRequestWithOfferedGroup, + IllegalHelloRetryRequestWithUnofferedCipherSuite, + IllegalHelloRetryRequestWithUnofferedNamedGroup, + IllegalHelloRetryRequestWithUnsupportedVersion, + IllegalHelloRetryRequestWithWrongSessionId, + IllegalHelloRetryRequestWithInvalidEch, + IllegalMiddleboxChangeCipherSpec, + IllegalTlsInnerPlaintext, + /// Received a warning alert with the given [`AlertDescription`] + IllegalWarningAlert(AlertDescription), + IncorrectBinder, + IncorrectFinished, + InvalidCertCompression, + InvalidMaxEarlyDataSize, + InvalidKeyShare, + KeyEpochWithPendingFragment, + KeyUpdateReceivedInQuicConnection, + MessageInterleavedWithHandshakeMessage, + MissingBinderInPskExtension, + MissingKeyShare, + MissingPskModesExtension, + MissingQuicTransportParameters, + NoCertificatesPresented, + OfferedDuplicateCertificateCompressions, + OfferedDuplicateKeyShares, + OfferedEarlyDataWithOldProtocolVersion, + OfferedEmptyApplicationProtocol, + OfferedIncorrectCompressions, + PskExtensionMustBeLast, + PskExtensionWithMismatchedIdsAndBinders, + RefusedToFollowHelloRetryRequest, + RejectedEarlyDataInterleavedWithHandshakeMessage, + ResumptionAttemptedWithVariedEms, + ResumptionOfferedWithVariedCipherSuite, + ResumptionOfferedWithVariedEms, + ResumptionOfferedWithIncompatibleCipherSuite, + SelectedDifferentCipherSuiteAfterRetry, + SelectedInvalidPsk, + SelectedTls12UsingTls13VersionExtension, + SelectedUnofferedApplicationProtocol, + SelectedUnofferedCertCompression, + SelectedUnofferedCipherSuite, + SelectedUnofferedCompression, + SelectedUnofferedKxGroup, + SelectedUnofferedPsk, + ServerEchoedCompatibilitySessionId, + ServerHelloMustOfferUncompressedEcPoints, + ServerNameDifferedOnRetry, + ServerNameMustContainOneHostName, + SignedKxWithWrongAlgorithm, + SignedHandshakeWithUnadvertisedSigScheme, + TooManyEmptyFragments, + TooManyConsecutiveHandshakeMessagesAfterHandshake, + TooManyRenegotiationRequests, + TooManyWarningAlertsReceived, + TooMuchEarlyDataReceived, + UnexpectedCleartextExtension, + UnsolicitedCertExtension, + UnsolicitedEncryptedExtension, + UnsolicitedSctList, + UnsolicitedServerHelloExtension, + WrongGroupForKeyShare, + UnsolicitedEchExtension, +} + +impl From for AlertDescription { + fn from(e: PeerMisbehaved) -> Self { + match e { + PeerMisbehaved::DisallowedEncryptedExtension + | PeerMisbehaved::IllegalHelloRetryRequestWithInvalidEch + | PeerMisbehaved::UnexpectedCleartextExtension + | PeerMisbehaved::UnsolicitedEchExtension + | PeerMisbehaved::UnsolicitedEncryptedExtension + | PeerMisbehaved::UnsolicitedServerHelloExtension => Self::UnsupportedExtension, + + PeerMisbehaved::IllegalMiddleboxChangeCipherSpec + | PeerMisbehaved::KeyEpochWithPendingFragment + | PeerMisbehaved::KeyUpdateReceivedInQuicConnection => Self::UnexpectedMessage, + + PeerMisbehaved::IllegalWarningAlert(_) => Self::DecodeError, + + PeerMisbehaved::IncorrectBinder | PeerMisbehaved::IncorrectFinished => { + Self::DecryptError + } + + PeerMisbehaved::InvalidCertCompression + | PeerMisbehaved::SelectedUnofferedCertCompression => Self::BadCertificate, + + PeerMisbehaved::MissingKeyShare + | PeerMisbehaved::MissingPskModesExtension + | PeerMisbehaved::MissingQuicTransportParameters => Self::MissingExtension, + + PeerMisbehaved::NoCertificatesPresented => Self::CertificateRequired, + + _ => Self::IllegalParameter, + } + } +} + +/// The set of cases where we failed to make a connection because a peer +/// doesn't support a TLS version/feature we require. +/// +/// This is `non_exhaustive`: we might add or stop using items here in minor +/// versions. +#[expect(missing_docs)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PeerIncompatible { + EcPointsExtensionRequired, + ExtendedMasterSecretExtensionRequired, + IncorrectCertificateTypeExtension, + KeyShareExtensionRequired, + MultipleRawKeys, + NamedGroupsExtensionRequired, + NoCertificateRequestSignatureSchemesInCommon, + NoCipherSuitesInCommon, + NoEcPointFormatsInCommon, + NoKxGroupsInCommon, + NoSignatureSchemesInCommon, + NoServerNameProvided, + NullCompressionRequired, + ServerDoesNotSupportTls12Or13, + ServerSentHelloRetryRequestWithUnknownExtension, + ServerTlsVersionIsDisabledByOurConfig, + SignatureAlgorithmsExtensionRequired, + SupportedVersionsExtensionRequired, + Tls12NotOffered, + Tls12NotOfferedOrEnabled, + Tls13RequiredForQuic, + UncompressedEcPointsRequired, + UnknownCertificateType(u8), + UnsolicitedCertificateTypeExtension, +} + +impl From for AlertDescription { + fn from(e: PeerIncompatible) -> Self { + match e { + PeerIncompatible::NullCompressionRequired => Self::IllegalParameter, + + PeerIncompatible::ServerTlsVersionIsDisabledByOurConfig + | PeerIncompatible::SupportedVersionsExtensionRequired + | PeerIncompatible::Tls12NotOffered + | PeerIncompatible::Tls12NotOfferedOrEnabled + | PeerIncompatible::Tls13RequiredForQuic => Self::ProtocolVersion, + + PeerIncompatible::UnknownCertificateType(_) => Self::UnsupportedCertificate, + + _ => Self::HandshakeFailure, + } + } +} + +/// Extended Key Usage (EKU) purpose values. +/// +/// These are usually represented as OID values in the certificate's extension (if present), but +/// we represent the values that are most relevant to rustls as named enum variants. +#[non_exhaustive] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ExtendedKeyPurpose { + /// Client authentication + ClientAuth, + /// Server authentication + ServerAuth, + /// Other EKU values + /// + /// Represented here as a `Vec` for human readability. + Other(Vec), +} + +impl ExtendedKeyPurpose { + #[cfg(feature = "webpki")] + pub(crate) fn for_values(values: impl Iterator) -> Self { + let values = values.collect::>(); + match &*values { + ExtendedKeyUsage::CLIENT_AUTH_REPR => Self::ClientAuth, + ExtendedKeyUsage::SERVER_AUTH_REPR => Self::ServerAuth, + _ => Self::Other(values), + } + } +} + +impl fmt::Display for ExtendedKeyPurpose { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::ClientAuth => write!(f, "client authentication"), + Self::ServerAuth => write!(f, "server authentication"), + Self::Other(values) => { + for (i, value) in values.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{value}")?; + } + Ok(()) + } + } + } +} + +/// The ways in which a certificate revocation list (CRL) can be invalid. +#[non_exhaustive] +#[derive(Debug, Clone)] +pub enum CertRevocationListError { + /// The CRL had a bad signature from its issuer. + BadSignature, + + /// A signature inside a certificate or on a handshake was made with an unsupported algorithm. + UnsupportedSignatureAlgorithm { + /// The signature algorithm OID that was unsupported. + signature_algorithm_id: Vec, + /// Supported algorithms that were available for signature verification. + supported_algorithms: Vec, + }, + + /// A signature was made with an algorithm that doesn't match the relevant public key. + UnsupportedSignatureAlgorithmForPublicKey { + /// The signature algorithm OID. + signature_algorithm_id: Vec, + /// The public key algorithm OID. + public_key_algorithm_id: Vec, + }, + + /// The CRL contained an invalid CRL number. + InvalidCrlNumber, + + /// The CRL contained a revoked certificate with an invalid serial number. + InvalidRevokedCertSerialNumber, + + /// The CRL issuer does not specify the cRLSign key usage. + IssuerInvalidForCrl, + + /// The CRL is invalid for some other reason. + /// + /// Enums holding this variant will never compare equal to each other. + Other(OtherError), + + /// The CRL is not correctly encoded. + ParseError, + + /// The CRL is not a v2 X.509 CRL. + UnsupportedCrlVersion, + + /// The CRL, or a revoked certificate in the CRL, contained an unsupported critical extension. + UnsupportedCriticalExtension, + + /// The CRL is an unsupported delta CRL, containing only changes relative to another CRL. + UnsupportedDeltaCrl, + + /// The CRL is an unsupported indirect CRL, containing revoked certificates issued by a CA + /// other than the issuer of the CRL. + UnsupportedIndirectCrl, + + /// The CRL contained a revoked certificate with an unsupported revocation reason. + /// See RFC 5280 Section 5.3.1[^1] for a list of supported revocation reasons. + /// + /// [^1]: + UnsupportedRevocationReason, +} + +impl PartialEq for CertRevocationListError { + fn eq(&self, other: &Self) -> bool { + use CertRevocationListError::*; + match (self, other) { + (BadSignature, BadSignature) => true, + ( + UnsupportedSignatureAlgorithm { + signature_algorithm_id: left_signature_algorithm_id, + supported_algorithms: left_supported_algorithms, + }, + UnsupportedSignatureAlgorithm { + signature_algorithm_id: right_signature_algorithm_id, + supported_algorithms: right_supported_algorithms, + }, + ) => { + (left_signature_algorithm_id, left_supported_algorithms) + == (right_signature_algorithm_id, right_supported_algorithms) + } + ( + UnsupportedSignatureAlgorithmForPublicKey { + signature_algorithm_id: left_signature_algorithm_id, + public_key_algorithm_id: left_public_key_algorithm_id, + }, + UnsupportedSignatureAlgorithmForPublicKey { + signature_algorithm_id: right_signature_algorithm_id, + public_key_algorithm_id: right_public_key_algorithm_id, + }, + ) => { + (left_signature_algorithm_id, left_public_key_algorithm_id) + == (right_signature_algorithm_id, right_public_key_algorithm_id) + } + (InvalidCrlNumber, InvalidCrlNumber) => true, + (InvalidRevokedCertSerialNumber, InvalidRevokedCertSerialNumber) => true, + (IssuerInvalidForCrl, IssuerInvalidForCrl) => true, + (ParseError, ParseError) => true, + (UnsupportedCrlVersion, UnsupportedCrlVersion) => true, + (UnsupportedCriticalExtension, UnsupportedCriticalExtension) => true, + (UnsupportedDeltaCrl, UnsupportedDeltaCrl) => true, + (UnsupportedIndirectCrl, UnsupportedIndirectCrl) => true, + (UnsupportedRevocationReason, UnsupportedRevocationReason) => true, + _ => false, + } + } +} + +/// An error that occurred while handling Encrypted Client Hello (ECH). +#[non_exhaustive] +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum EncryptedClientHelloError { + /// The provided ECH configuration list was invalid. + InvalidConfigList, + /// No compatible ECH configuration. + NoCompatibleConfig, + /// The client configuration has server name indication (SNI) disabled. + SniRequired, +} + +/// The server rejected the request to enable Encrypted Client Hello (ECH) +/// +/// If [`RejectedEch::can_retry()`] is true, then you may use this with +/// [`crate::client::EchConfig::for_retry()`] to build a new `EchConfig` for a fresh client +/// connection that will use a compatible ECH configuration provided by the server for a retry. +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq)] +pub struct RejectedEch { + pub(crate) retry_configs: Option>, +} + +impl RejectedEch { + /// Returns true if the server provided new ECH configurations to use for a fresh retry connection + /// + /// The `RejectedEch` error can be provided to [`crate::client::EchConfig::for_retry()`] + /// to build a new `EchConfig` for a fresh client connection that will use a compatible ECH + /// configuration provided by the server for a retry. + pub fn can_retry(&self) -> bool { + self.retry_configs.is_some() + } + + /// Returns an `EchConfigListBytes` with the server's provided retry configurations (if any) + pub fn retry_configs(&self) -> Option> { + let Some(retry_configs) = &self.retry_configs else { + return None; + }; + + let mut tls_encoded_list = Vec::new(); + retry_configs.encode(&mut tls_encoded_list); + + Some(EchConfigListBytes::from(tls_encoded_list)) + } +} + +fn join(items: &[T]) -> String { + items + .iter() + .map(|x| format!("{x:?}")) + .collect::>() + .join(" or ") +} + +/// Describes cases of API misuse +/// +/// Variants here should be sufficiently detailed that the action needed is clear. +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq)] +pub enum ApiMisuse { + /// Trying to resume a session with an unknown cipher suite. + ResumingFromUnknownCipherSuite(CipherSuite), + + /// The [`KeyingMaterialExporter`][] was already consumed. + /// + /// Methods that obtain an exporter (eg, [`Connection::exporter()`][]) can only + /// be used once. This error is returned on subsequent calls. + /// + /// [`KeyingMaterialExporter`]: crate::KeyingMaterialExporter + /// [`Connection::exporter()`]: crate::Connection::exporter() + ExporterAlreadyUsed, + + /// The `context` parameter to [`KeyingMaterialExporter::derive()`][] was too long. + /// + /// For TLS1.2 connections (only) this parameter is limited to 64KB. + /// + /// [`KeyingMaterialExporter::derive()`]: crate::KeyingMaterialExporter::derive() + ExporterContextTooLong, + + /// The `output` object for [`KeyingMaterialExporter::derive()`][] was too long. + /// + /// For TLS1.3 connections this is limited to 255 times the hash output length. + /// + /// [`KeyingMaterialExporter::derive()`]: crate::KeyingMaterialExporter::derive() + ExporterOutputTooLong, + + /// The `output` object to [`KeyingMaterialExporter::derive()`][] was zero length. + /// + /// This doesn't make sense, so we explicitly return an error (rather than simply + /// producing no output as requested.) + /// + /// [`KeyingMaterialExporter::derive()`]: crate::KeyingMaterialExporter::derive() + ExporterOutputZeroLength, + + /// [`Acceptor::accept()`][] called after it yielded a connection. + /// + /// [`Acceptor::accept()`]: crate::server::Acceptor::accept() + AcceptorPolledAfterCompletion, + + /// Incorrect sample length provided to [`quic::HeaderProtectionKey::encrypt_in_place()`][] + /// + /// [`quic::HeaderProtectionKey::encrypt_in_place()`]: crate::quic::HeaderProtectionKey::encrypt_in_place() + InvalidQuicHeaderProtectionSampleLength, + + /// Incorrect relation between sample length and header number length provided to + /// [`quic::HeaderProtectionKey::encrypt_in_place()`][] + /// + /// [`quic::HeaderProtectionKey::encrypt_in_place()`]: crate::quic::HeaderProtectionKey::encrypt_in_place() + InvalidQuicHeaderProtectionPacketNumberLength, + + /// Raw keys cannot be used with TLS 1.2. + InvalidSignerForProtocolVersion, + + /// QUIC attempted with a configuration that does not support TLS1.3. + QuicRequiresTls13Support, + + /// QUIC attempted with a configuration that does not support a ciphersuite that supports QUIC. + NoQuicCompatibleCipherSuites, + + /// An empty certificate chain was provided. + EmptyCertificateChain, + + /// QUIC attempted with unsupported [`ServerConfig::max_early_data_size`][] + /// + /// This field must be either zero or [`u32::MAX`] for QUIC. + /// + /// [`ServerConfig::max_early_data_size`]: crate::server::ServerConfig::max_early_data_size + QuicRestrictsMaxEarlyDataSize, + + /// A `CryptoProvider` must have at least one cipher suite. + NoCipherSuitesConfigured, + + /// A `CryptoProvider` must have at least one key exchange group. + NoKeyExchangeGroupsConfigured, + + /// An empty list of signature verification algorithms was provided. + NoSignatureVerificationAlgorithms, + + /// ECH attempted with a configuration that does not support TLS1.3. + EchRequiresTls13Support, + + /// ECH attempted with a configuration that also supports TLS1.2. + EchForbidsTls12Support, + + /// Secret extraction operation attempted without opting-in to secret extraction. + /// + /// This is possible from [`Connection::dangerous_extract_secrets()`][crate::Connection::dangerous_extract_secrets]. + /// + /// You must set [`ServerConfig::enable_secret_extraction`][crate::server::ServerConfig::enable_secret_extraction] or + /// [`ClientConfig::enable_secret_extraction`][crate::client::ClientConfig::enable_secret_extraction] to true before this + /// is available. + SecretExtractionRequiresPriorOptIn, + + /// Secret extraction operation attempted without first extracting all pending + /// TLS data. + /// + /// See [`Self::SecretExtractionRequiresPriorOptIn`] for a list of the affected + /// functions. You must ensure any prior generated TLS records are extracted + /// from the library before using one of these functions. + SecretExtractionWithPendingSendableData, + + /// Attempt to verify a certificate with an unsupported type. + /// + /// A verifier indicated support for a certificate type but then failed to verify the peer's + /// identity of that type. + UnverifiableCertificateType, + + /// A verifier or resolver implementation signalled that it does not support any certificate types. + NoSupportedCertificateTypes, + + /// [`Nonce::to_array()`][] called with incorrect array size. + /// + /// The nonce length does not match the requested array size `N`. + /// + /// [`Nonce::to_array()`]: crate::crypto::cipher::Nonce::to_array() + NonceArraySizeMismatch { + /// The expected array size (type parameter N) + expected: usize, + /// The actual nonce length + actual: usize, + }, + + /// [`Iv::new()`][] called with a value that exceeds the maximum IV length. + /// + /// The IV length must not exceed [`Iv::MAX_LEN`][]. + /// + /// [`Iv::new()`]: crate::crypto::cipher::Iv::new() + /// [`Iv::MAX_LEN`]: crate::crypto::cipher::Iv::MAX_LEN + IvLengthExceedsMaximum { + /// The actual IV length provided + actual: usize, + /// The maximum allowed IV length + maximum: usize, + }, + + /// Calling [`ServerConnection::set_resumption_data()`] must be done before + /// any resumption is offered. + /// + /// [`ServerConnection::set_resumption_data()`]: crate::server::ServerConnection::set_resumption_data() + ResumptionDataProvidedTooLate, + + /// [`KernelConnection::update_tx_secret()`] and associated are not available for TLS1.2 connections. + /// + /// [`KernelConnection::update_tx_secret()`]: crate::conn::kernel::KernelConnection::update_tx_secret() + KeyUpdateNotAvailableForTls12, +} + +impl fmt::Display for ApiMisuse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:?}") + } +} + +impl core::error::Error for ApiMisuse {} + +mod other_error { + use core::error::Error as StdError; + use core::fmt; + + use crate::sync::Arc; + + /// Any other error that cannot be expressed by a more specific [`Error`][super::Error] + /// variant. + /// + /// For example, an `OtherError` could be produced by a custom crypto provider + /// exposing a provider specific error. + /// + /// Enums holding this type will never compare equal to each other. + #[derive(Debug, Clone)] + pub struct OtherError(Arc); + + impl OtherError { + /// Create a new `OtherError` from any error type. + pub fn new(err: impl StdError + Send + Sync + 'static) -> Self { + Self(Arc::new(err)) + } + } + + impl PartialEq for OtherError { + fn eq(&self, _other: &Self) -> bool { + false + } + } + + impl fmt::Display for OtherError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } + } + + impl StdError for OtherError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + Some(self.0.as_ref()) + } + } +} + +pub use other_error::OtherError; + +/// An [`Error`] along with the (possibly encrypted) alert to send to +/// the peer. +pub struct ErrorWithAlert { + /// The error + pub error: Error, + pub(crate) data: Vec, +} + +impl ErrorWithAlert { + pub(crate) fn new(error: Error, send_path: &mut SendPath) -> Self { + maybe_send_fatal_alert(send_path, &error); + Self { + error, + data: send_path.sendable_tls.take_one_vec(), + } + } + + /// Consume any pending TLS data. + /// + /// The returned buffer will contain the alert, if one is to be sent. + pub fn take_tls_data(&mut self) -> Option> { + match self.data.is_empty() { + true => None, + false => Some(mem::take(&mut self.data)), + } + } +} + +impl Deref for ErrorWithAlert { + type Target = Error; + + fn deref(&self) -> &Self::Target { + &self.error + } +} + +/// Direct conversion with no alert. +impl From for ErrorWithAlert { + fn from(error: Error) -> Self { + Self { + error, + data: Vec::new(), + } + } +} + +impl fmt::Debug for ErrorWithAlert { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ErrorWithAlert") + .field("error", &self.error) + .field("data", &self.data.len()) + .finish_non_exhaustive() + } +} diff --git a/rustls/src/error/tests.rs b/rustls/src/error/tests.rs new file mode 100644 index 00000000000..fa8547bae34 --- /dev/null +++ b/rustls/src/error/tests.rs @@ -0,0 +1,333 @@ +use alloc::string::ToString; +use core::time::Duration; +use std::{println, vec}; + +use pki_types::ServerName; + +use super::{ + AlertDescription, CertRevocationListError, Error, ErrorWithAlert, InconsistentKeys, + InvalidMessage, OtherError, UnixTime, +}; +use crate::conn::SendPath; +use crate::crypto::GetRandomFailed; +use crate::msgs::test_enum8_display; + +#[test] +fn certificate_error_equality() { + use super::CertificateError::*; + assert_eq!(BadEncoding, BadEncoding); + assert_eq!(Expired, Expired); + let context = ExpiredContext { + time: UnixTime::since_unix_epoch(Duration::from_secs(1234)), + not_after: UnixTime::since_unix_epoch(Duration::from_secs(123)), + }; + assert_eq!(context, context); + assert_ne!( + context, + ExpiredContext { + time: UnixTime::since_unix_epoch(Duration::from_secs(12345)), + not_after: UnixTime::since_unix_epoch(Duration::from_secs(123)), + } + ); + assert_ne!( + context, + ExpiredContext { + time: UnixTime::since_unix_epoch(Duration::from_secs(1234)), + not_after: UnixTime::since_unix_epoch(Duration::from_secs(1234)), + } + ); + assert_eq!(NotValidYet, NotValidYet); + let context = NotValidYetContext { + time: UnixTime::since_unix_epoch(Duration::from_secs(123)), + not_before: UnixTime::since_unix_epoch(Duration::from_secs(1234)), + }; + assert_eq!(context, context); + assert_ne!( + context, + NotValidYetContext { + time: UnixTime::since_unix_epoch(Duration::from_secs(1234)), + not_before: UnixTime::since_unix_epoch(Duration::from_secs(1234)), + } + ); + assert_ne!( + context, + NotValidYetContext { + time: UnixTime::since_unix_epoch(Duration::from_secs(123)), + not_before: UnixTime::since_unix_epoch(Duration::from_secs(12345)), + } + ); + assert_eq!(Revoked, Revoked); + assert_eq!(UnhandledCriticalExtension, UnhandledCriticalExtension); + assert_eq!(UnknownIssuer, UnknownIssuer); + assert_eq!(ExpiredRevocationList, ExpiredRevocationList); + assert_eq!(UnknownRevocationStatus, UnknownRevocationStatus); + let context = ExpiredRevocationListContext { + time: UnixTime::since_unix_epoch(Duration::from_secs(1234)), + next_update: UnixTime::since_unix_epoch(Duration::from_secs(123)), + }; + assert_eq!(context, context); + assert_ne!( + context, + ExpiredRevocationListContext { + time: UnixTime::since_unix_epoch(Duration::from_secs(12345)), + next_update: UnixTime::since_unix_epoch(Duration::from_secs(123)), + } + ); + assert_ne!( + context, + ExpiredRevocationListContext { + time: UnixTime::since_unix_epoch(Duration::from_secs(1234)), + next_update: UnixTime::since_unix_epoch(Duration::from_secs(1234)), + } + ); + assert_eq!(BadSignature, BadSignature); + assert_eq!( + UnsupportedSignatureAlgorithm { + signature_algorithm_id: vec![1, 2, 3], + supported_algorithms: vec![] + }, + UnsupportedSignatureAlgorithm { + signature_algorithm_id: vec![1, 2, 3], + supported_algorithms: vec![] + } + ); + assert_eq!( + UnsupportedSignatureAlgorithmForPublicKey { + signature_algorithm_id: vec![1, 2, 3], + public_key_algorithm_id: vec![4, 5, 6] + }, + UnsupportedSignatureAlgorithmForPublicKey { + signature_algorithm_id: vec![1, 2, 3], + public_key_algorithm_id: vec![4, 5, 6] + } + ); + assert_eq!(NotValidForName, NotValidForName); + let context = NotValidForNameContext { + expected: ServerName::try_from("example.com") + .unwrap() + .to_owned(), + presented: vec!["other.com".into()], + }; + assert_eq!(context, context); + assert_ne!( + context, + NotValidForNameContext { + expected: ServerName::try_from("example.com") + .unwrap() + .to_owned(), + presented: vec![] + } + ); + assert_ne!( + context, + NotValidForNameContext { + expected: ServerName::try_from("huh.com") + .unwrap() + .to_owned(), + presented: vec!["other.com".into()], + } + ); + assert_eq!(InvalidPurpose, InvalidPurpose); + assert_eq!( + ApplicationVerificationFailure, + ApplicationVerificationFailure + ); + assert_eq!(InvalidOcspResponse, InvalidOcspResponse); + let other = Other(OtherError::new(TestError)); + assert_ne!(other, other); + assert_ne!(BadEncoding, Expired); +} + +#[test] +fn crl_error_equality() { + use super::CertRevocationListError::*; + assert_eq!(BadSignature, BadSignature); + assert_eq!( + UnsupportedSignatureAlgorithm { + signature_algorithm_id: vec![1, 2, 3], + supported_algorithms: vec![] + }, + UnsupportedSignatureAlgorithm { + signature_algorithm_id: vec![1, 2, 3], + supported_algorithms: vec![] + } + ); + assert_eq!( + UnsupportedSignatureAlgorithmForPublicKey { + signature_algorithm_id: vec![1, 2, 3], + public_key_algorithm_id: vec![4, 5, 6] + }, + UnsupportedSignatureAlgorithmForPublicKey { + signature_algorithm_id: vec![1, 2, 3], + public_key_algorithm_id: vec![4, 5, 6] + } + ); + assert_eq!(InvalidCrlNumber, InvalidCrlNumber); + assert_eq!( + InvalidRevokedCertSerialNumber, + InvalidRevokedCertSerialNumber + ); + assert_eq!(IssuerInvalidForCrl, IssuerInvalidForCrl); + assert_eq!(ParseError, ParseError); + assert_eq!(UnsupportedCriticalExtension, UnsupportedCriticalExtension); + assert_eq!(UnsupportedCrlVersion, UnsupportedCrlVersion); + assert_eq!(UnsupportedDeltaCrl, UnsupportedDeltaCrl); + assert_eq!(UnsupportedIndirectCrl, UnsupportedIndirectCrl); + assert_eq!(UnsupportedRevocationReason, UnsupportedRevocationReason); + let other = Other(OtherError::new(TestError)); + assert_ne!(other, other); + assert_ne!(BadSignature, InvalidCrlNumber); +} + +#[test] +fn other_error_equality() { + let other_error = OtherError::new(TestError); + assert_ne!(other_error, other_error); + let other: Error = other_error.into(); + assert_ne!(other, other); +} + +#[test] +fn smoke() { + use crate::enums::{ContentType, HandshakeType}; + + let all = vec![ + Error::InappropriateMessage { + expect_types: vec![ContentType::Alert], + got_type: ContentType::Handshake, + }, + Error::InappropriateHandshakeMessage { + expect_types: vec![HandshakeType::ClientHello, HandshakeType::Finished], + got_type: HandshakeType::ServerHello, + }, + Error::InvalidMessage(InvalidMessage::InvalidCcs), + Error::DecryptError, + super::PeerIncompatible::Tls12NotOffered.into(), + super::PeerMisbehaved::UnsolicitedCertExtension.into(), + Error::AlertReceived(AlertDescription::ExportRestriction), + super::CertificateError::Expired.into(), + super::CertificateError::NotValidForNameContext { + expected: ServerName::try_from("example.com") + .unwrap() + .to_owned(), + presented: vec![], + } + .into(), + super::CertificateError::NotValidForNameContext { + expected: ServerName::try_from("example.com") + .unwrap() + .to_owned(), + presented: vec!["DnsName(\"hello.com\")".into()], + } + .into(), + super::CertificateError::NotValidForNameContext { + expected: ServerName::try_from("example.com") + .unwrap() + .to_owned(), + presented: vec![ + "DnsName(\"hello.com\")".into(), + "DnsName(\"goodbye.com\")".into(), + ], + } + .into(), + super::CertificateError::NotValidYetContext { + time: UnixTime::since_unix_epoch(Duration::from_secs(300)), + not_before: UnixTime::since_unix_epoch(Duration::from_secs(320)), + } + .into(), + super::CertificateError::ExpiredContext { + time: UnixTime::since_unix_epoch(Duration::from_secs(320)), + not_after: UnixTime::since_unix_epoch(Duration::from_secs(300)), + } + .into(), + super::CertificateError::ExpiredRevocationListContext { + time: UnixTime::since_unix_epoch(Duration::from_secs(320)), + next_update: UnixTime::since_unix_epoch(Duration::from_secs(300)), + } + .into(), + super::CertificateError::InvalidOcspResponse.into(), + Error::General("undocumented error".to_string()), + Error::FailedToGetCurrentTime, + Error::FailedToGetRandomBytes, + Error::HandshakeNotComplete, + Error::PeerSentOversizedRecord, + Error::NoApplicationProtocol, + Error::BadMaxFragmentSize, + Error::InconsistentKeys(InconsistentKeys::KeyMismatch), + Error::InconsistentKeys(InconsistentKeys::Unknown), + Error::InvalidCertRevocationList(CertRevocationListError::BadSignature), + Error::Unreachable("smoke"), + super::ApiMisuse::ExporterAlreadyUsed.into(), + Error::Other(OtherError::new(TestError)), + ]; + + for err in all { + println!("{err:?}:"); + println!(" fmt '{err}'"); + } +} + +#[derive(Debug)] +struct TestError; + +impl core::fmt::Display for TestError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "test error") + } +} + +impl core::error::Error for TestError {} + +#[test] +fn alert_description_traits() { + test_enum8_display::( + AlertDescription::CloseNotify, + AlertDescription::EncryptedClientHelloRequired, + ); +} + +#[test] +fn alert_display() { + println!("Review the following error messages for syntax and grammar errors:"); + for u in 0..=u8::MAX { + let err = Error::AlertReceived(AlertDescription::from(u)); + println!(" - {err}"); + } + + // pipe the output of this test to `llm` for a quick check of these... +} + +#[test] +fn rand_error_mapping() { + let err: Error = GetRandomFailed.into(); + assert_eq!(err, Error::FailedToGetRandomBytes); +} + +#[test] +fn time_error_mapping() { + use std::time::SystemTime; + + let time_error = SystemTime::UNIX_EPOCH + .duration_since(SystemTime::now()) + .unwrap_err(); + let err: Error = time_error.into(); + assert_eq!(err, Error::FailedToGetCurrentTime); +} + +#[test] +fn error_with_alert() { + let mut e = ErrorWithAlert::new(Error::NoApplicationProtocol, &mut SendPath::default()); + assert_eq!( + std::format!("{e:?}"), + "ErrorWithAlert { error: NoApplicationProtocol, data: 7, .. }" + ); + assert_eq!(e.take_tls_data(), Some(vec![21, 3, 3, 0, 2, 2, 120])); + assert_eq!(e.take_tls_data(), None); + + let mut e = ErrorWithAlert::from(Error::NoApplicationProtocol); + assert_eq!(e.take_tls_data(), None); + assert_eq!( + std::format!("{e:?}"), + "ErrorWithAlert { error: NoApplicationProtocol, data: 0, .. }" + ); +} diff --git a/rustls/src/hash_hs.rs b/rustls/src/hash_hs.rs index 4f664002d7b..be1b31e456a 100644 --- a/rustls/src/hash_hs.rs +++ b/rustls/src/hash_hs.rs @@ -2,11 +2,8 @@ use alloc::boxed::Box; use alloc::vec::Vec; use core::mem; -use crate::crypto::hash; -use crate::msgs::codec::Codec; -use crate::msgs::enums::HashAlgorithm; -use crate::msgs::handshake::HandshakeMessagePayload; -use crate::msgs::message::{Message, MessagePayload}; +use crate::crypto::{HashAlgorithm, hash}; +use crate::msgs::{Codec, HandshakeAlignedProof, HandshakeMessagePayload, Message, MessagePayload}; /// Early stage buffering of handshake payloads. /// @@ -129,14 +126,14 @@ impl HandshakeHash { ctx.finish() } - pub(crate) fn into_hrr_buffer(self) -> HandshakeHashBuffer { + pub(crate) fn into_hrr_buffer(self, _proof: &HandshakeAlignedProof) -> HandshakeHashBuffer { let old_hash = self.ctx.finish(); let old_handshake_hash_msg = HandshakeMessagePayload::build_handshake_hash(old_hash.as_ref()); HandshakeHashBuffer { - client_auth_enabled: self.client_auth.is_some(), buffer: old_handshake_hash_msg.get_encoding(), + client_auth_enabled: self.client_auth.is_some(), } } @@ -162,7 +159,6 @@ impl HandshakeHash { /// Takes this object's buffer containing all handshake messages /// so far. This method only works once; it resets the buffer /// to empty. - #[cfg(feature = "tls12")] pub(crate) fn take_handshake_buf(&mut self) -> Option> { self.client_auth.take() } @@ -183,22 +179,20 @@ impl Clone for HandshakeHash { } } -#[cfg(test)] -#[macro_rules_attribute::apply(test_for_each_provider)] +#[cfg(all(test, any(target_arch = "aarch64", target_arch = "x86_64")))] mod tests { - use super::provider::hash::SHA256; use super::*; - use crate::crypto::hash::Hash; - use crate::enums::{HandshakeType, ProtocolVersion}; - use crate::msgs::base::Payload; - use crate::msgs::handshake::{HandshakeMessagePayload, HandshakePayload}; + use crate::crypto::cipher::Payload; + use crate::crypto::test_provider::SHA256; + use crate::enums::ProtocolVersion; + use crate::msgs::{HandshakeMessagePayload, HandshakePayload}; #[test] fn hashes_correctly() { let mut hhb = HandshakeHashBuffer::new(); hhb.add_raw(b"hello"); assert_eq!(hhb.buffer.len(), 5); - let mut hh = hhb.start_hash(&SHA256); + let mut hh = hhb.start_hash(SHA256); assert!(hh.client_auth.is_none()); hh.add_raw(b"world"); let h = hh.current_hash(); @@ -214,10 +208,9 @@ mod tests { // handshake protocol encoding of 0x0e 00 00 00 let server_hello_done_message = Message { version: ProtocolVersion::TLSv1_2, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::ServerHelloDone, - payload: HandshakePayload::ServerHelloDone, - }), + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ServerHelloDone, + )), }; let app_data_ignored = Message { @@ -235,8 +228,9 @@ mod tests { hhb.add_message(&server_hello_done_message); hhb.add_message(&app_data_ignored); hhb.add_message(&end_of_early_data_flight); + assert_eq!( - hhb.start_hash(&SHA256) + hhb.start_hash(SHA256) .current_hash() .as_ref(), SHA256 @@ -245,7 +239,7 @@ mod tests { ); // non-buffered mode - let mut hh = HandshakeHashBuffer::new().start_hash(&SHA256); + let mut hh = HandshakeHashBuffer::new().start_hash(SHA256); hh.add_message(&server_hello_done_message); hh.add_message(&app_data_ignored); hh.add_message(&end_of_early_data_flight); @@ -257,20 +251,21 @@ mod tests { ); } - #[cfg(feature = "tls12")] #[test] fn buffers_correctly() { let mut hhb = HandshakeHashBuffer::new(); hhb.set_client_auth_enabled(); hhb.add_raw(b"hello"); assert_eq!(hhb.buffer.len(), 5); - let mut hh = hhb.start_hash(&SHA256); + + let mut hh = hhb.start_hash(SHA256); assert_eq!( hh.client_auth .as_ref() .map(|buf| buf.len()), Some(5) ); + hh.add_raw(b"world"); assert_eq!( hh.client_auth @@ -278,6 +273,7 @@ mod tests { .map(|buf| buf.len()), Some(10) ); + let h = hh.current_hash(); let h = h.as_ref(); assert_eq!(h[0], 0x93); @@ -294,17 +290,20 @@ mod tests { hhb.set_client_auth_enabled(); hhb.add_raw(b"hello"); assert_eq!(hhb.buffer.len(), 5); - let mut hh = hhb.start_hash(&SHA256); + + let mut hh = hhb.start_hash(SHA256); assert_eq!( hh.client_auth .as_ref() .map(|buf| buf.len()), Some(5) ); + hh.abandon_client_auth(); assert_eq!(hh.client_auth, None); hh.add_raw(b"world"); assert_eq!(hh.client_auth, None); + let h = hh.current_hash(); let h = h.as_ref(); assert_eq!(h[0], 0x93); @@ -330,7 +329,7 @@ mod tests { assert_eq!(hhb_prime.buffer.len(), 10); assert_ne!(hhb.buffer, hhb_prime.buffer); - let hh = hhb.start_hash(&SHA256); + let hh = hhb.start_hash(SHA256); let hh_hash = hh.current_hash(); let hh_hash = hh_hash.as_ref(); diff --git a/rustls/src/key_log.rs b/rustls/src/key_log.rs index 0ffcccfea04..2f16599f5e3 100644 --- a/rustls/src/key_log.rs +++ b/rustls/src/key_log.rs @@ -1,8 +1,5 @@ use core::fmt::Debug; -#[cfg(all(doc, feature = "std"))] -use crate::KeyLogFile; - /// This trait represents the ability to do something useful /// with key material, such as logging it to a file for debugging. /// @@ -13,8 +10,8 @@ use crate::KeyLogFile; /// You'll likely want some interior mutability in your /// implementation to make this useful. /// -/// See [`KeyLogFile`] that implements the standard -/// `SSLKEYLOGFILE` environment variable behaviour. +/// For the standard `SSLKEYLOGFILE` environment variable behavior, +/// see the `KeyLogFile` implementation provided in the rustls-util crate. pub trait KeyLog: Debug + Send + Sync { /// Log the given `secret`. `client_random` is provided for /// session identification. `label` describes precisely what @@ -49,6 +46,7 @@ pub trait KeyLog: Debug + Send + Sync { } /// KeyLog that does exactly nothing. +#[expect(clippy::exhaustive_structs)] #[derive(Debug)] pub struct NoKeyLog; diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index d8562932ba3..65b40ba8b30 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -9,64 +9,72 @@ //! //! ### Platform support //! -//! While Rustls itself is platform independent, by default it uses [`aws-lc-rs`] for implementing -//! the cryptography in TLS. See [the aws-lc-rs FAQ][aws-lc-rs-platforms-faq] for more details of the -//! platform/architecture support constraints in aws-lc-rs. -//! -//! [`ring`] is also available via the `ring` crate feature: see -//! [the supported `ring` target platforms][ring-target-platforms]. +//! While Rustls itself is platform independent, it requires the use of cryptography primitives +//! for implementing the cryptography algorithms used in TLS. In Rustls, a +//! [`crypto::CryptoProvider`] represents a collection of crypto primitive implementations. //! //! By providing a custom instance of the [`crypto::CryptoProvider`] struct, you //! can replace all cryptography dependencies of rustls. This is a route to being portable //! to a wider set of architectures and environments, or compliance requirements. See the //! [`crypto::CryptoProvider`] documentation for more details. //! -//! Specifying `default-features = false` when depending on rustls will remove the -//! dependency on aws-lc-rs. -//! -//! Rustls requires Rust 1.63 or later. It has an optional dependency on zlib-rs which requires 1.75 or later. -//! -//! [ring-target-platforms]: https://github.com/briansmith/ring/blob/2e8363b433fa3b3962c877d9ed2e9145612f3160/include/ring-core/target.h#L18-L64 //! [`crypto::CryptoProvider`]: crate::crypto::CryptoProvider -//! [`ring`]: https://crates.io/crates/ring -//! [aws-lc-rs-platforms-faq]: https://aws.github.io/aws-lc-rs/faq.html#can-i-run-aws-lc-rs-on-x-platform-or-architecture -//! [`aws-lc-rs`]: https://crates.io/crates/aws-lc-rs //! //! ### Cryptography providers //! //! Since Rustls 0.22 it has been possible to choose the provider of the cryptographic primitives //! that Rustls uses. This may be appealing if you have specific platform, compliance or feature -//! requirements that aren't met by the default provider, [`aws-lc-rs`]. +//! requirements. +//! +//! From 0.24, users must explicitly provide a crypto provider when constructing `ClientConfig` or +//! `ServerConfig` instances. See the [`crypto::CryptoProvider`] documentation for more details. //! -//! Users that wish to customize the provider in use can do so when constructing `ClientConfig` -//! and `ServerConfig` instances using the `with_crypto_provider` method on the respective config -//! builder types. See the [`crypto::CryptoProvider`] documentation for more details. +//! #### First-party providers //! -//! #### Built-in providers +//! The Rustls project currently maintains two cryptography providers: //! -//! Rustls ships with two built-in providers controlled with associated feature flags: +//! * [`rustls-aws-lc-rs`] - a provider that uses the [`aws-lc-rs`] crate for cryptography. +//! While this provider can be harder to build on [some platforms][aws-lc-rs-platforms-faq], it provides excellent +//! performance and a complete feature set (including post-quantum algorithms). +//! * [`rustls-ring`] - a provider that uses the [`ring`] crate for cryptography. This +//! provider is easier to build on a variety of platforms, but has a more limited feature set +//! (for example, it does not support post-quantum algorithms). //! -//! * [`aws-lc-rs`] - enabled by default, available with the `aws_lc_rs` feature flag enabled. -//! * [`ring`] - available with the `ring` feature flag enabled. +//! The Rustls team recommends using the [`rustls-aws-lc-rs`] crate for its complete feature set +//! and performance. See [the aws-lc-rs FAQ][aws-lc-rs-platforms-faq] for more details of the +//! platform/architecture support constraints in aws-lc-rs. //! //! See the documentation for [`crypto::CryptoProvider`] for details on how providers are //! selected. //! +//! (For rustls versions prior to 0.24, both of these providers were shipped as part of the rustls +//! crate, and Cargo features were used to select the preferred provider. The `aws-lc-rs` feature +//! was enabled by default.) +//! +//! [`rustls-aws-lc-rs`]: https://crates.io/crates/rustls-aws-lc-rs +//! [`aws-lc-rs`]: https://crates.io/crates/aws-lc-rs +//! [aws-lc-rs-platforms-faq]: https://aws.github.io/aws-lc-rs/faq.html#can-i-run-aws-lc-rs-on-x-platform-or-architecture +//! [`rustls-ring`]: https://crates.io/crates/rustls-ring +//! [`ring`]: https://crates.io/crates/ring +//! //! #### Third-party providers //! //! The community has also started developing third-party providers for Rustls: //! -//! * [`rustls-mbedtls-provider`] - a provider that uses [`mbedtls`] for cryptography. -//! * [`rustls-openssl`] - a provider that uses [OpenSSL] for cryptography. -//! * [`rustls-post-quantum`]: an experimental provider that adds support for post-quantum -//! key exchange to the default aws-lc-rs provider. //! * [`boring-rustls-provider`] - a work-in-progress provider that uses [`boringssl`] for //! cryptography. +//! * [`rustls-ccm`] - adds AES-CCM cipher suites (TLS 1.2 and 1.3) using [`RustCrypto`], for IoT/constrained-device protocols (IEEE 2030.5, Matter, RFC 7925). +//! * [`rustls-graviola`] - a provider that uses [`graviola`] for cryptography. +//! * [`rustls-mbedtls-provider`] - a provider that uses [`mbedtls`] for cryptography. +//! * [`rustls-openssl`] - a provider that uses [OpenSSL] for cryptography. //! * [`rustls-rustcrypto`] - an experimental provider that uses the crypto primitives //! from [`RustCrypto`] for cryptography. //! * [`rustls-symcrypt`] - a provider that uses Microsoft's [SymCrypt] library. //! * [`rustls-wolfcrypt-provider`] - a work-in-progress provider that uses [`wolfCrypt`] for cryptography. //! +//! [`rustls-ccm`]: https://github.com/jsulmont/rustls-ccm +//! [`rustls-graviola`]: https://crates.io/crates/rustls-graviola +//! [`graviola`]: https://github.com/ctz/graviola //! [`rustls-mbedtls-provider`]: https://github.com/fortanix/rustls-mbedtls-provider //! [`mbedtls`]: https://github.com/Mbed-TLS/mbedtls //! [`rustls-openssl`]: https://github.com/tofay/rustls-openssl @@ -77,21 +85,12 @@ //! [`boringssl`]: https://github.com/google/boringssl //! [`rustls-rustcrypto`]: https://github.com/RustCrypto/rustls-rustcrypto //! [`RustCrypto`]: https://github.com/RustCrypto -//! [`rustls-post-quantum`]: https://crates.io/crates/rustls-post-quantum //! [`rustls-wolfcrypt-provider`]: https://github.com/wolfSSL/rustls-wolfcrypt-provider //! [`wolfCrypt`]: https://www.wolfssl.com/products/wolfcrypt //! -//! #### Custom provider -//! -//! We also provide a simple example of writing your own provider in the [`custom-provider`] -//! example. This example implements a minimal provider using parts of the [`RustCrypto`] -//! ecosystem. -//! //! See the [Making a custom CryptoProvider] section of the documentation for more information //! on this topic. //! -//! [`custom-provider`]: https://github.com/rustls/rustls/tree/main/provider-example/ -//! [`RustCrypto`]: https://github.com/RustCrypto //! [Making a custom CryptoProvider]: https://docs.rs/rustls/latest/rustls/crypto/struct.CryptoProvider.html#making-a-custom-cryptoprovider //! //! ## Design overview @@ -106,7 +105,7 @@ //! It doesn't make or accept TCP connections, or do DNS, or read or write files. //! //! Our [examples] directory contains demos that show how to handle I/O using the -//! [`stream::Stream`] helper, as well as more complex asynchronous I/O using [`mio`]. +//! `rustls_util::Stream` helper, as well as more complex asynchronous I/O using [`mio`]. //! If you're already using Tokio for an async runtime you may prefer to use [`tokio-rustls`] instead //! of interacting with rustls directly. //! @@ -119,7 +118,7 @@ //! plaintext on the right: //! //! [`read_tls()`]: Connection::read_tls -//! [`write_tls()`]: Connection::read_tls +//! [`write_tls()`]: Connection::write_tls //! //! ```text //! TLS Plaintext @@ -145,13 +144,11 @@ //! the Mozilla set of root certificates. //! //! ```rust,no_run -//! # #[cfg(feature = "aws-lc-rs")] { //! let root_store = rustls::RootCertStore::from_iter( //! webpki_roots::TLS_SERVER_ROOTS //! .iter() //! .cloned(), //! ); -//! # } //! ``` //! //! [`webpki_roots`]: https://crates.io/crates/webpki-roots @@ -160,35 +157,36 @@ //! and use it for all connections made by that process. //! //! ```rust,no_run -//! # #[cfg(feature = "aws_lc_rs")] { +//! # let DEFAULT_PROVIDER = rustls::crypto::CryptoProvider::get_default().unwrap().clone(); //! # let root_store: rustls::RootCertStore = panic!(); -//! let config = rustls::ClientConfig::builder() +//! let config = rustls::ClientConfig::builder(DEFAULT_PROVIDER) //! .with_root_certificates(root_store) -//! .with_no_client_auth(); -//! # } +//! .with_no_client_auth() +//! .unwrap(); //! ``` //! //! Now we can make a connection. You need to provide the server's hostname so we //! know what to expect to find in the server's certificate. //! -//! ```rust -//! # #[cfg(feature = "aws_lc_rs")] { +//! ```rust,no_run //! # use rustls; //! # use webpki; //! # use std::sync::Arc; -//! # rustls::crypto::aws_lc_rs::default_provider().install_default(); +//! # let DEFAULT_PROVIDER = rustls::crypto::CryptoProvider::get_default().unwrap().clone(); //! # let root_store = rustls::RootCertStore::from_iter( //! # webpki_roots::TLS_SERVER_ROOTS //! # .iter() //! # .cloned(), //! # ); -//! # let config = rustls::ClientConfig::builder() +//! # let client_config = Arc::new(rustls::ClientConfig::builder(DEFAULT_PROVIDER) //! # .with_root_certificates(root_store) -//! # .with_no_client_auth(); -//! let rc_config = Arc::new(config); +//! # .with_no_client_auth() +//! # .unwrap()); +//! //! let example_com = "example.com".try_into().unwrap(); -//! let mut client = rustls::ClientConnection::new(rc_config, example_com); -//! # } +//! let mut client = client_config.connect(example_com) +//! .build() +//! .unwrap(); //! ``` //! //! Now you should do appropriate IO for the `client` object. If `client.wants_read()` yields @@ -215,8 +213,7 @@ //! errors. //! //! ```rust,no_run -//! # #[cfg(feature = "aws_lc_rs")] { -//! # let mut client = rustls::ClientConnection::new(panic!(), panic!()).unwrap(); +//! # let mut client: rustls::ClientConnection = panic!(); //! # struct Socket { } //! # impl Socket { //! # fn ready_for_write(&self) -> bool { false } @@ -257,7 +254,6 @@ //! //! socket.wait_for_something_to_happen(); //! } -//! # } //! ``` //! //! # Examples @@ -269,101 +265,42 @@ //! //! [`mio`]: https://docs.rs/mio/latest/mio/ //! +//! # Manual +//! +//! The [rustls manual](crate::manual) explains design decisions and includes how-to guidance. +//! //! # Crate features //! Here's a list of what features are exposed by the rustls crate and what //! they mean. //! -//! - `aws_lc_rs` (enabled by default): makes the rustls crate depend on the [`aws-lc-rs`] crate. -//! Use `rustls::crypto::aws_lc_rs::default_provider().install_default()` to -//! use it as the default `CryptoProvider`, or provide it explicitly -//! when making a `ClientConfig` or `ServerConfig`. -//! -//! Note that aws-lc-rs has additional build-time dependencies like cmake. -//! See [the documentation](https://aws.github.io/aws-lc-rs/requirements/index.html) for details. -//! -//! - `ring`: makes the rustls crate depend on the *ring* crate for cryptography. -//! Use `rustls::crypto::ring::default_provider().install_default()` to -//! use it as the default `CryptoProvider`, or provide it explicitly -//! when making a `ClientConfig` or `ServerConfig`. -//! -//! - `fips`: enable support for FIPS140-3-approved cryptography, via the aws-lc-rs crate. -//! This feature enables the `aws_lc_rs` feature, which makes the rustls crate depend -//! on [aws-lc-rs](https://github.com/aws/aws-lc-rs). It also changes the default -//! for [`ServerConfig::require_ems`] and [`ClientConfig::require_ems`]. -//! -//! See [manual::_06_fips] for more details. -//! -//! - `custom-provider`: disables implicit use of built-in providers (`aws-lc-rs` or `ring`). This forces -//! applications to manually install one, for instance, when using a custom `CryptoProvider`. -//! -//! - `tls12` (enabled by default): enable support for TLS version 1.2. Note that, due to the -//! additive nature of Cargo features and because it is enabled by default, other crates -//! in your dependency graph could re-enable it for your application. If you want to disable -//! TLS 1.2 for security reasons, consider explicitly enabling TLS 1.3 only in the config -//! builder API. +//! - `std` (enabled by default): enable the high-level (buffered) Connection API and other functionality +//! which relies on the `std` library. //! -//! - `logging` (enabled by default): make the rustls crate depend on the `log` crate. +//! - `log` (enabled by default): make the rustls crate depend on the `log` crate. //! rustls outputs interesting protocol-level messages at `trace!` and `debug!` level, //! and protocol-level errors at `warn!` and `error!` level. The log messages do not //! contain secret key data, and so are safe to archive without affecting session security. //! -//! - `read_buf`: when building with Rust Nightly, adds support for the unstable -//! `std::io::ReadBuf` and related APIs. This reduces costs from initializing -//! buffers. Will do nothing on non-Nightly releases. +//! - `webpki` (enabled by default): make the rustls crate depend on the `rustls-wepbki` crate, which +//! is used by default to provide built-in certificate verification. Without this feature, users must +//! provide certificate verification themselves. //! //! - `brotli`: uses the `brotli` crate for RFC8879 certificate compression support. //! //! - `zlib`: uses the `zlib-rs` crate for RFC8879 certificate compression support. //! +//! [x25519mlkem768-manual]: manual::_05_defaults#about-the-post-quantum-secure-key-exchange-x25519mlkem768 // Require docs for public APIs, deny unsafe code, etc. +#![warn(missing_docs, clippy::exhaustive_enums, clippy::exhaustive_structs)] #![forbid(unsafe_code, unused_must_use)] -#![cfg_attr(not(any(read_buf, bench)), forbid(unstable_features))] -#![warn( - clippy::alloc_instead_of_core, - clippy::clone_on_ref_ptr, - clippy::manual_let_else, - clippy::std_instead_of_core, - clippy::use_self, - clippy::upper_case_acronyms, - elided_lifetimes_in_paths, - missing_docs, - trivial_casts, - trivial_numeric_casts, - unreachable_pub, - unused_import_braces, - unused_extern_crates, - unused_qualifications -)] -// Relax these clippy lints: -// - ptr_arg: this triggers on references to type aliases that are Vec -// underneath. -// - too_many_arguments: some things just need a lot of state, wrapping it -// doesn't necessarily make it easier to follow what's going on -// - new_ret_no_self: we sometimes return `Arc`, which seems fine -// - single_component_path_imports: our top-level `use log` import causes -// a false positive, https://github.com/rust-lang/rust-clippy/issues/5210 -// - new_without_default: for internal constructors, the indirection is not -// helpful -#![allow( - clippy::too_many_arguments, - clippy::new_ret_no_self, - clippy::ptr_arg, - clippy::single_component_path_imports, - clippy::new_without_default -)] +#![cfg_attr(not(any(bench, coverage_nightly)), forbid(unstable_features))] // Enable documentation for all features on docs.rs -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -// XXX: Because of https://github.com/rust-lang/rust/issues/54726, we cannot -// write `#![rustversion::attr(nightly, feature(read_buf))]` here. Instead, -// build.rs set `read_buf` for (only) Rust Nightly to get the same effect. -// -// All the other conditional logic in the crate could use -// `#[rustversion::nightly]` instead of `#[cfg(read_buf)]`; `#[cfg(read_buf)]` -// is used to avoid needing `rustversion` to be compiled twice during -// cross-compiling. -#![cfg_attr(read_buf, feature(read_buf))] -#![cfg_attr(read_buf, feature(core_io_borrowed_buf))] +#![cfg_attr(rustls_docsrs, feature(doc_cfg))] +// Enable coverage() attr for nightly coverage builds, see +// +// (`coverage_nightly` is a cfg set by `cargo-llvm-cov`) +#![cfg_attr(coverage_nightly, feature(coverage_attribute))] #![cfg_attr(bench, feature(test))] #![no_std] @@ -373,7 +310,6 @@ extern crate alloc; // is in `std::prelude` but not in `core::prelude`. This helps maintain no-std support as even // developers that are not interested in, or aware of, no-std support and / or that never run // `cargo build --no-default-features` locally will get errors when they rely on `std::prelude` API. -#[cfg(any(feature = "std", test))] extern crate std; #[cfg(doc)] @@ -385,22 +321,33 @@ use crate::crypto::CryptoProvider; extern crate test; // log for logging (optional). -#[cfg(feature = "logging")] +#[cfg(feature = "log")] +#[expect(clippy::single_component_path_imports)] use log; -#[cfg(not(feature = "logging"))] +#[cfg(not(feature = "log"))] mod log { - macro_rules! trace ( ($($tt:tt)*) => {{}} ); - macro_rules! debug ( ($($tt:tt)*) => {{}} ); - macro_rules! error ( ($($tt:tt)*) => {{}} ); - macro_rules! _warn ( ($($tt:tt)*) => {{}} ); - pub(crate) use {_warn as warn, debug, error, trace}; + macro_rules! trace ( ($($tt:tt)*) => { crate::log::_used!($($tt)*) } ); + macro_rules! debug ( ($($tt:tt)*) => { crate::log::_used!($($tt)*) } ); + macro_rules! error ( ($($tt:tt)*) => { crate::log::_used!($($tt)*) } ); + macro_rules! _warn ( ($($tt:tt)*) => { crate::log::_used!($($tt)*) } ); + macro_rules! _used ( ($($tt:tt)*) => { { let _ = format_args!($($tt)*); } } ); + pub(crate) use _used; + pub(crate) use _warn as warn; + pub(crate) use debug; + pub(crate) use error; + pub(crate) use trace; } -#[cfg(test)] -#[macro_use] -mod test_macros; +/// This internal `sync` module aliases the `Arc` implementation to allow downstream forks +/// of rustls targeting architectures without atomic pointers to replace the implementation +/// with another implementation such as `portable_atomic_util::Arc` in one central location. +mod sync { + #[expect(clippy::disallowed_types)] + pub(crate) type Arc = alloc::sync::Arc; +} +#[expect(unnameable_types)] #[macro_use] mod msgs; mod common_state; @@ -408,241 +355,66 @@ pub mod compress; mod conn; /// Crypto provider interface. pub mod crypto; -mod error; +pub mod error; mod hash_hs; -#[cfg(any(feature = "std", feature = "hashbrown"))] mod limited_cache; -mod rand; -mod record_layer; -#[cfg(feature = "std")] -mod stream; -#[cfg(feature = "tls12")] mod tls12; mod tls13; mod vecbuf; mod verify; -#[cfg(test)] -mod verifybench; mod x509; #[macro_use] mod check; -#[cfg(feature = "logging")] mod bs_debug; mod builder; -mod enums; +pub mod enums; mod key_log; -#[cfg(feature = "std")] -mod key_log_file; mod suites; mod versions; +#[cfg(feature = "webpki")] mod webpki; /// Internal classes that are used in integration tests. /// The contents of this section DO NOT form part of the stable interface. -#[allow(missing_docs)] #[doc(hidden)] pub mod internal { - /// Low-level TLS message parsing and encoding functions. - pub mod msgs { - pub mod base { - pub use crate::msgs::base::{Payload, PayloadU16}; - } - pub mod codec { - pub use crate::msgs::codec::{Codec, Reader}; - } - pub mod enums { - pub use crate::msgs::enums::{ - AlertLevel, CertificateType, Compression, EchVersion, HpkeAead, HpkeKdf, HpkeKem, - NamedGroup, - }; - } - pub mod fragmenter { - pub use crate::msgs::fragmenter::MessageFragmenter; - } - pub mod handshake { - pub use crate::msgs::handshake::{ - CertificateChain, ClientExtension, ClientHelloPayload, DistinguishedName, - EchConfigContents, EchConfigPayload, HandshakeMessagePayload, HandshakePayload, - HpkeKeyConfig, HpkeSymmetricCipherSuite, KeyShareEntry, Random, ServerExtension, - ServerName, SessionId, - }; - } - pub mod message { - pub use crate::msgs::message::{ - Message, MessagePayload, OutboundOpaqueMessage, PlainMessage, - }; - } - pub mod persist { - pub use crate::msgs::persist::ServerSessionValue; - } - } - - pub use crate::tls13::key_schedule::{derive_traffic_iv, derive_traffic_key}; - - pub mod fuzzing { - pub use crate::msgs::deframer::fuzz_deframer; - } -} - -/// Unbuffered connection API -/// -/// This is an alternative to the [`crate::ConnectionCommon`] API that does not internally buffer -/// TLS nor plaintext data. Instead those buffers are managed by the API user so they have -/// control over when and how to allocate, resize and dispose of them. -/// -/// This API is lower level than the `ConnectionCommon` API and is built around a state machine -/// interface where the API user must handle each state to advance and complete the -/// handshake process. -/// -/// Like the `ConnectionCommon` API, no IO happens internally so all IO must be handled by the API -/// user. Unlike the `ConnectionCommon` API, this API does not make use of the [`std::io::Read`] and -/// [`std::io::Write`] traits so it's usable in no-std context. -/// -/// The entry points into this API are [`crate::client::UnbufferedClientConnection::new`], -/// [`crate::server::UnbufferedServerConnection::new`] and -/// [`unbuffered::UnbufferedConnectionCommon::process_tls_records`]. The state machine API is -/// documented in [`unbuffered::ConnectionState`]. -/// -/// # Examples -/// -/// [`unbuffered-client`] and [`unbuffered-server`] are examples that fully exercise the API in -/// std, non-async context. -/// -/// [`unbuffered-client`]: https://github.com/rustls/rustls/blob/main/examples/src/bin/unbuffered-client.rs -/// [`unbuffered-server`]: https://github.com/rustls/rustls/blob/main/examples/src/bin/unbuffered-server.rs -pub mod unbuffered { - pub use crate::conn::unbuffered::{ - AppDataRecord, ConnectionState, EncodeError, EncodeTlsData, EncryptError, - InsufficientSizeError, ReadEarlyData, ReadTraffic, TransmitTlsData, UnbufferedStatus, - WriteTraffic, - }; - pub use crate::conn::UnbufferedConnectionCommon; + pub use crate::msgs::fuzzing; } // The public interface is: -pub use crate::builder::{ConfigBuilder, ConfigSide, WantsVerifier, WantsVersions}; -pub use crate::common_state::{CommonState, HandshakeKind, IoState, Side}; -#[cfg(feature = "std")] -pub use crate::conn::{Connection, Reader, Writer}; -pub use crate::conn::{ConnectionCommon, SideData}; -pub use crate::enums::{ - AlertDescription, CertificateCompressionAlgorithm, CipherSuite, ContentType, HandshakeType, - ProtocolVersion, SignatureAlgorithm, SignatureScheme, -}; -pub use crate::error::{ - CertRevocationListError, CertificateError, EncryptedClientHelloError, Error, InconsistentKeys, - InvalidMessage, OtherError, PeerIncompatible, PeerMisbehaved, +pub use crate::builder::{ConfigBuilder, ConfigSide, WantsVerifier}; +pub use crate::common_state::{CommonState, ConnectionOutputs, HandshakeKind}; +pub use crate::conn::{ + Connection, IoState, KeyingMaterialExporter, Reader, SideData, Writer, kernel, }; +pub use crate::error::Error; pub use crate::key_log::{KeyLog, NoKeyLog}; -#[cfg(feature = "std")] -pub use crate::key_log_file::KeyLogFile; -pub use crate::msgs::enums::NamedGroup; -pub use crate::msgs::ffdhe_groups; -pub use crate::msgs::handshake::DistinguishedName; -#[cfg(feature = "std")] -pub use crate::stream::{Stream, StreamOwned}; pub use crate::suites::{ CipherSuiteCommon, ConnectionTrafficSecrets, ExtractedSecrets, SupportedCipherSuite, }; -#[cfg(feature = "std")] pub use crate::ticketer::TicketRotator; -#[cfg(any(feature = "std", feature = "hashbrown"))] // < XXX: incorrect feature gate -pub use crate::ticketer::TicketSwitcher; -#[cfg(feature = "tls12")] pub use crate::tls12::Tls12CipherSuite; pub use crate::tls13::Tls13CipherSuite; -pub use crate::verify::DigitallySignedStruct; -pub use crate::versions::{SupportedProtocolVersion, ALL_VERSIONS, DEFAULT_VERSIONS}; +pub use crate::verify::{DigitallySignedStruct, DistinguishedName, SignerPublicKey}; +pub use crate::versions::{ALL_VERSIONS, DEFAULT_VERSIONS, SupportedProtocolVersion}; +#[cfg(feature = "webpki")] pub use crate::webpki::RootCertStore; /// Items for use in a client. -pub mod client { - pub(super) mod builder; - mod client_conn; - mod common; - mod ech; - pub(super) mod handy; - mod hs; - #[cfg(feature = "tls12")] - mod tls12; - mod tls13; - - pub use builder::WantsClientCert; - pub use client_conn::{ - ClientConfig, ClientConnectionData, ClientSessionStore, EarlyDataError, ResolvesClientCert, - Resumption, Tls12Resumption, UnbufferedClientConnection, - }; - #[cfg(feature = "std")] - pub use client_conn::{ClientConnection, WriteEarlyData}; - pub use ech::{EchConfig, EchGreaseConfig, EchMode, EchStatus}; - pub use handy::AlwaysResolvesClientRawPublicKeys; - #[cfg(any(feature = "std", feature = "hashbrown"))] - pub use handy::ClientSessionMemoryCache; - - /// Dangerous configuration that should be audited and used with extreme care. - pub mod danger { - pub use super::builder::danger::DangerousClientConfigBuilder; - pub use super::client_conn::danger::DangerousClientConfig; - pub use crate::verify::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; - } - - pub use crate::msgs::persist::{Tls12ClientSessionValue, Tls13ClientSessionValue}; - pub use crate::webpki::{ - verify_server_cert_signed_by_trust_anchor, verify_server_name, ServerCertVerifierBuilder, - VerifierBuilderError, WebPkiServerVerifier, - }; -} - -pub use client::ClientConfig; -#[cfg(feature = "std")] -pub use client::ClientConnection; +pub mod client; +pub use client::{ClientConfig, ClientConnection}; /// Items for use in a server. -pub mod server { - pub(crate) mod builder; - mod common; - pub(crate) mod handy; - mod hs; - mod server_conn; - #[cfg(feature = "tls12")] - mod tls12; - mod tls13; - - pub use builder::WantsServerCert; - #[cfg(any(feature = "std", feature = "hashbrown"))] - pub use handy::ResolvesServerCertUsingSni; - #[cfg(any(feature = "std", feature = "hashbrown"))] - pub use handy::ServerSessionMemoryCache; - pub use handy::{AlwaysResolvesServerRawPublicKeys, NoServerSessionStorage}; - pub use server_conn::{ - Accepted, ClientHello, ProducesTickets, ResolvesServerCert, ServerConfig, - ServerConnectionData, StoresServerSessions, UnbufferedServerConnection, - }; - #[cfg(feature = "std")] - pub use server_conn::{AcceptedAlert, Acceptor, ReadEarlyData, ServerConnection}; - - pub use crate::verify::NoClientAuth; - pub use crate::webpki::{ - ClientCertVerifierBuilder, ParsedCertificate, VerifierBuilderError, WebPkiClientVerifier, - }; - - /// Dangerous configuration that should be audited and used with extreme care. - pub mod danger { - pub use crate::verify::{ClientCertVerified, ClientCertVerifier}; - } -} - -pub use server::ServerConfig; -#[cfg(feature = "std")] -pub use server::ServerConnection; +pub mod server; +pub use server::{ServerConfig, ServerConnection}; /// All defined protocol versions appear in this module. /// -/// ALL_VERSIONS is a provided as an array of all of these values. +/// ALL_VERSIONS is provided as an array of all of these values. pub mod version { - #[cfg(feature = "tls12")] - pub use crate::versions::TLS12; - pub use crate::versions::TLS13; + pub use crate::versions::{ + TLS12, TLS12_VERSION, TLS13, TLS13_VERSION, Tls12Version, Tls13Version, + }; } /// Re-exports the contents of the [rustls-pki-types](https://docs.rs/rustls-pki-types) crate for easy access @@ -651,15 +423,9 @@ pub mod pki_types { pub use pki_types::*; } -/// Message signing interfaces. -pub mod sign { - pub use crate::crypto::signer::{CertifiedKey, Signer, SigningKey}; -} - /// APIs for implementing QUIC TLS pub mod quic; -#[cfg(any(feature = "std", feature = "hashbrown"))] // < XXX: incorrect feature gate /// APIs for implementing TLS tickets pub mod ticketer; @@ -671,18 +437,31 @@ pub mod time_provider; /// APIs abstracting over locking primitives. pub mod lock; -/// Polyfills for features that are not yet stabilized or available with current MSRV. -pub(crate) mod polyfill; - -#[cfg(any(feature = "std", feature = "hashbrown"))] mod hash_map { - #[cfg(feature = "std")] - pub(crate) use std::collections::hash_map::Entry; - #[cfg(feature = "std")] pub(crate) use std::collections::HashMap; + pub(crate) use std::collections::hash_map::Entry; +} - #[cfg(all(not(feature = "std"), feature = "hashbrown"))] - pub(crate) use hashbrown::hash_map::Entry; - #[cfg(all(not(feature = "std"), feature = "hashbrown"))] - pub(crate) use hashbrown::HashMap; +mod sealed { + #[expect(unnameable_types)] + pub trait Sealed {} } + +mod core_hash_polyfill { + use core::hash::Hasher; + + /// Working around `core::hash::Hasher` not being dyn-compatible + pub(super) struct DynHasher<'a>(pub(crate) &'a mut dyn Hasher); + + impl Hasher for DynHasher<'_> { + fn finish(&self) -> u64 { + self.0.finish() + } + + fn write(&mut self, bytes: &[u8]) { + self.0.write(bytes) + } + } +} + +pub(crate) use core_hash_polyfill::DynHasher; diff --git a/rustls/src/limited_cache.rs b/rustls/src/limited_cache.rs index 4252a337f97..10e1af42d5b 100644 --- a/rustls/src/limited_cache.rs +++ b/rustls/src/limited_cache.rs @@ -1,5 +1,6 @@ use alloc::collections::VecDeque; use core::borrow::Borrow; +use core::fmt::Debug; use core::hash::Hash; use crate::hash_map::{Entry, HashMap}; @@ -12,53 +13,14 @@ use crate::hash_map::{Entry, HashMap}; /// storage. /// /// This is inefficient: it stores keys twice. -pub(crate) struct LimitedCache { +pub(crate) struct LimitedCache { map: HashMap, // first item is the oldest key oldest: VecDeque, } -impl LimitedCache -where - K: Eq + Hash + Clone + core::fmt::Debug, - V: Default, -{ - pub(crate) fn get_or_insert_default_and_edit(&mut self, k: K, edit: impl FnOnce(&mut V)) { - let inserted_new_item = match self.map.entry(k) { - Entry::Occupied(value) => { - edit(value.into_mut()); - false - } - entry @ Entry::Vacant(_) => { - self.oldest - .push_back(entry.key().clone()); - edit(entry.or_insert_with(V::default)); - true - } - }; - - // ensure next insertion does not require a realloc - if inserted_new_item && self.oldest.capacity() == self.oldest.len() { - if let Some(oldest_key) = self.oldest.pop_front() { - self.map.remove(&oldest_key); - } - } - } - - pub(crate) fn get_mut(&mut self, k: &Q) -> Option<&mut V> - where - K: Borrow, - { - self.map.get_mut(k) - } -} - -impl LimitedCache -where - K: Eq + Hash + Clone + core::fmt::Debug, - V: Default, -{ +impl LimitedCache { /// Create a new LimitedCache with the given rough capacity. pub(crate) fn new(capacity_order_of_magnitude: usize) -> Self { Self { @@ -91,6 +53,13 @@ where } } + pub(crate) fn get_mut(&mut self, k: &Q) -> Option<&mut V> + where + K: Borrow, + { + self.map.get_mut(k) + } + pub(crate) fn get(&self, k: &Q) -> Option<&V> where K: Borrow, @@ -102,25 +71,48 @@ where where K: Borrow, { - if let Some(value) = self.map.remove(k) { - // O(N) search, followed by O(N) removal - if let Some(index) = self - .oldest - .iter() - .position(|item| item.borrow() == k) - { - self.oldest.remove(index); + let value = self.map.remove(k)?; + + // O(N) search, followed by O(N) removal + if let Some(index) = self + .oldest + .iter() + .position(|item| item.borrow() == k) + { + self.oldest.remove(index); + } + + Some(value) + } +} + +impl LimitedCache { + pub(crate) fn get_or_insert_default_and_edit(&mut self, k: K, edit: impl FnOnce(&mut V)) { + let inserted_new_item = match self.map.entry(k) { + Entry::Occupied(value) => { + edit(value.into_mut()); + false + } + entry @ Entry::Vacant(_) => { + self.oldest + .push_back(entry.key().clone()); + edit(entry.or_insert_with(V::default)); + true + } + }; + + // ensure next insertion does not require a realloc + if inserted_new_item && self.oldest.capacity() == self.oldest.len() { + if let Some(oldest_key) = self.oldest.pop_front() { + self.map.remove(&oldest_key); } - Some(value) - } else { - None } } } #[cfg(test)] mod tests { - use std::prelude::v1::*; + use alloc::string::String; type Test = super::LimitedCache; diff --git a/rustls/src/lock.rs b/rustls/src/lock.rs index 4a261b2eb75..26c5bbb0269 100644 --- a/rustls/src/lock.rs +++ b/rustls/src/lock.rs @@ -1,9 +1,5 @@ -#[cfg(not(feature = "std"))] -pub use no_std_lock::*; -#[cfg(feature = "std")] pub use std_lock::*; -#[cfg(feature = "std")] mod std_lock { use std::sync::Mutex as StdMutex; pub use std::sync::MutexGuard; @@ -31,58 +27,3 @@ mod std_lock { } } } - -#[cfg(not(feature = "std"))] -mod no_std_lock { - use alloc::boxed::Box; - use alloc::sync::Arc; - use core::fmt::Debug; - use core::ops::DerefMut; - - /// A no-std compatible wrapper around [`Lock`]. - #[derive(Debug)] - pub struct Mutex { - inner: Arc>, - } - - impl Mutex { - /// Creates a new mutex in an unlocked state ready for use. - pub fn new(val: T) -> Self - where - M: MakeMutex, - T: Send + 'static, - { - Self { - inner: M::make_mutex(val), - } - } - - /// Acquires the mutex, blocking the current thread until it is able to do so. - /// - /// This will return `None` in the case the mutex is poisoned. - #[inline] - pub fn lock(&self) -> Option> { - self.inner.lock().ok() - } - } - - /// A lock protecting shared data. - pub trait Lock: Debug + Send + Sync { - /// Acquire the lock. - fn lock(&self) -> Result, Poisoned>; - } - - /// A lock builder. - pub trait MakeMutex { - /// Create a new mutex. - fn make_mutex(value: T) -> Arc> - where - T: Send + 'static; - } - - /// A no-std compatible mutex guard. - pub type MutexGuard<'a, T> = Box + 'a>; - - /// A marker type used to indicate `Lock::lock` failed due to a poisoned lock. - pub struct Poisoned; -} diff --git a/rustls/src/manual/defaults.rs b/rustls/src/manual/defaults.rs index aa15863c163..f2a08378018 100644 --- a/rustls/src/manual/defaults.rs +++ b/rustls/src/manual/defaults.rs @@ -2,16 +2,6 @@ ## Rationale for defaults -### Why is AES-256 preferred over AES-128? - -This is a trade-off between: - -1. classical security level: searching a 2^128 key space is as implausible as 2^256. -2. post-quantum security level: the difference is more meaningful, and AES-256 seems like the conservative choice. -3. performance: AES-256 is around 40% slower than AES-128, though hardware acceleration typically narrows this gap. - -The choice is frankly quite marginal. - ### Why is AES-GCM preferred over chacha20-poly1305? Hardware support for accelerating AES-GCM is widespread, and hardware-accelerated AES-GCM @@ -26,4 +16,41 @@ the implementation security will be improved. We think this is an uncommon case Both provide roughly the same classical security level, but x25519 has better performance and it's _much_ more likely that both peers will have good quality implementations. +### About the post-quantum-secure key exchange `X25519MLKEM768` + +[X25519MLKEM768] -- a hybrid[^1], post-quantum-secure[^2] key exchange +algorithm -- is available when using the aws-lc-rs provider, set as the highest-priority key +exchange algorithm by default. + +[X25519MLKEM768] is pre-standardization, but is now widely deployed, +for example, by [Chrome] and [Cloudflare]. + +You may see unexpected connection failures (such as [tldr.fail]) +-- [please report these to us][interop-bug]. + +The two components of this key exchange are well regarded: +X25519 alone is already used by default by rustls, and tends to have +higher quality implementations than other elliptic curves. +ML-KEM-768 was standardized by NIST in [FIPS203]. + +MLKEM768 is available separately, but is not currently enabled +by default out of conservatism. + +[^1]: meaning: a construction that runs a classical and post-quantum + key exchange, and uses the output of both together. This is a hedge + against the post-quantum half being broken. + +[^2]: a "post-quantum-secure" algorithm is one posited to be invulnerable + to attack using a cryptographically-relevant quantum computer. In contrast, + classical algorithms would be broken by such a computer. Note that such computers + do not currently exist, and may never exist, but current traffic could be captured + now and attacked later. + +[X25519MLKEM768]: +[FIPS203]: +[Chrome]: +[Cloudflare]: +[interop-bug]: +[tldr.fail]: + */ diff --git a/rustls/src/manual/features.rs b/rustls/src/manual/features.rs index 9896ac27408..03c58f858c2 100644 --- a/rustls/src/manual/features.rs +++ b/rustls/src/manual/features.rs @@ -1,10 +1,14 @@ /*! -The below list reflects the support provided with the default crate features. -Items marked with an asterisk `*` can be extended or altered via public -APIs ([`CryptoProvider`] for example). +The below list reflects the support provided from the first-party provider crates +[`rustls-aws-lc-rs`] or [`rustls-ring`]. + +Items marked with an asterisk `*` can be extended or altered via public APIs ([`CryptoProvider`] for example) +and are dependent on the provider used. [`CryptoProvider`]: crate::crypto::CryptoProvider +[`rustls-aws-lc-rs`]: https://crates.io/crates/rustls-aws-lc-rs +[`rustls-ring`]: https://crates.io/crates/rustls-ring ## Current features @@ -12,6 +16,7 @@ APIs ([`CryptoProvider`] for example). * ECDSA, Ed25519 or RSA server authentication by clients `*` * ECDSA, Ed25519[^1] or RSA server authentication by servers `*` * Forward secrecy using ECDHE; with curve25519, nistp256 or nistp384 curves `*` +* Post-quantum hybrid key exchange with [X25519MLKEM768](https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/) [^2] `*` * AES128-GCM and AES256-GCM bulk encryption, with safe nonces `*` * ChaCha20-Poly1305 bulk encryption ([RFC7905](https://tools.ietf.org/html/rfc7905)) `*` * ALPN support @@ -30,12 +35,13 @@ APIs ([`CryptoProvider`] for example). * [RFC8879](https://tools.ietf.org/html/rfc8879) certificate compression by clients and servers `*` * Client-side Encrypted client hello (ECH) - ([draft-ietf-tls-esni](https://datatracker.ietf.org/doc/draft-ietf-tls-esni/)). + ([RFC 9849](https://datatracker.ietf.org/doc/html/rfc9849)). [^1]: Note that, at the time of writing, Ed25519 does not have wide support in browsers. It is also not supported by the WebPKI, because the CA/Browser Forum Baseline Requirements do not support it for publicly trusted certificates. +[^2]: See [the documentation][crate::manual::_05_defaults#about-the-post-quantum-secure-key-exchange-x25519mlkem768] ## Non-features @@ -57,7 +63,7 @@ and will not support: certificates"). _Rustls' default certificate verifier does not support using a trust anchor as both a CA certificate and an end-entity certificate in order to limit complexity and risk in path building. While dangerous, all authentication can be turned off if required -- - see the [example code](https://github.com/rustls/rustls/blob/992e2364a006b2e84a8cf6a7c3eaf0bdb773c9de/examples/src/bin/tlsclient-mio.rs#L318)_ `*` + see the [example code](https://github.com/rustls/rustls/blob/v/0.23.23/examples/src/bin/tlsclient-mio.rs#L338)_ `*` ### About "custom extensions" @@ -77,7 +83,7 @@ cause security or interop failures. Instead, we suggest that potential users of that API consider: - whether their use can fit in standard extensions such as ALPN, - or [ALPS][alps][^2]. + or [ALPS][alps][^3]. - if not, whether they can fit in a more general extension, and define and standardize that in the [IETF TLSWG][tlswg]. @@ -93,6 +99,6 @@ See also: [Go's position on such an API][golang]. [alps]: https://datatracker.ietf.org/doc/html/draft-vvv-tls-alps [golang]: https://github.com/golang/go/issues/51497 [tlswg]: https://datatracker.ietf.org/wg/tls/charter/ -[^2]: rustls does not currently implement ALPS, but it is something we +[^3]: rustls does not currently implement ALPS, but it is something we would consider once standardised and deployed. */ diff --git a/rustls/src/manual/fips.rs b/rustls/src/manual/fips.rs index 203a759e415..bce74fbd86e 100644 --- a/rustls/src/manual/fips.rs +++ b/rustls/src/manual/fips.rs @@ -1,30 +1,20 @@ /*! # Using rustls with FIPS-approved cryptography -To use FIPS-approved cryptography with rustls, you should take -these actions: +To use FIPS-approved cryptography with rustls, you should use a FIPS-approved `CryptoProvider`. +The easiest way to do this is to use the the `rustls-aws-lc-rs` crate with the `fips` feature enabled. -## 1. Enable the `fips` crate feature for rustls. +## 1. Enable the `fips` crate feature for rustls-aws-lc-rs: Use: ```toml -rustls = { version = "0.23", features = [ "fips" ] } +rustls = { version = "0.24" } +rustls-aws-lc-rs = { version = "0.1", features = ["fips"] } ``` ## 2. Use the FIPS `CryptoProvider` -This is [`default_fips_provider()`]: - -```rust,ignore -rustls::crypto::default_fips_provider() - .install_default() - .expect("default provider already set elsewhere"); -``` - -This snippet makes use of the process-default provider, -and that assumes all your uses of rustls use that. -See [`CryptoProvider`] documentation for other ways to -specify which `CryptoProvider` to use. +Instantiate your `ClientConfig` or `ServerConfig` using the FIPS `CryptoProvider`. ## 3. Validate the FIPS status of your `ClientConfig`/`ServerConfig` at run-time @@ -37,26 +27,9 @@ You could, for example: assert!(client_config.fips()); ``` -But maybe your application has an error handling -or health-check strategy better than panicking. - -# aws-lc-rs FIPS approval status - -This is covered by [FIPS 140-3 certificate #4816][cert-4816]. -See [the security policy][policy-4816] for precisely which -environments and functions this certificate covers. - -Later releases of aws-lc-rs may be covered by later certificates, -or be pending certification. - -For the most up-to-date details see the latest documentation -for the [`aws-lc-fips-sys`] crate. +But maybe your application has an error handling or health-check strategy better than panicking. -[`aws-lc-fips-sys`]: https://crates.io/crates/aws-lc-fips-sys -[`default_fips_provider()`]: crate::crypto::default_fips_provider [`CryptoProvider`]: crate::crypto::CryptoProvider [`ClientConfig::fips()`]: crate::client::ClientConfig::fips [`ServerConfig::fips()`]: crate::server::ServerConfig::fips -[cert-4816]: https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/4816 -[policy-4816]: https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp4816.pdf */ diff --git a/rustls/src/manual/howto.rs b/rustls/src/manual/howto.rs index f3a8219cdf0..d1b31be7c40 100644 --- a/rustls/src/manual/howto.rs +++ b/rustls/src/manual/howto.rs @@ -5,10 +5,10 @@ By default rustls supports PKCS#8-format[^1] RSA or ECDSA keys, plus PKCS#1-form However, if your private key resides in a HSM, or in another process, or perhaps another machine, rustls has some extension points to support this: -The main trait you must implement is [`sign::SigningKey`][signing_key]. The primary method here -is [`choose_scheme`][choose_scheme] where you are given a set of [`SignatureScheme`s][sig_scheme] the client says +The main trait you must implement is [`SigningKey`][signing_key]. The primary method here +is [`choose_scheme()`][choose_scheme] where you are given a set of [`SignatureScheme`s][sig_scheme] the client says it supports: you must choose one (or return `None` -- this aborts the handshake). Having -done that, you return an implementation of the [`sign::Signer`][signer] trait. +done that, you return an implementation of the [`Signer`][signer] trait. The [`sign()`][sign_method] performs the signature and returns it. (Unfortunately this is currently designed for keys with low latency access, like in a @@ -17,22 +17,22 @@ It's a TODO to make these and other extension points async.) Once you have these two pieces, configuring a server to use them involves, briefly: -- packaging your [`sign::SigningKey`][signing_key] with the matching certificate chain into a [`sign::CertifiedKey`][certified_key] -- making a [`ResolvesServerCertUsingSni`][cert_using_sni] and feeding in your [`sign::CertifiedKey`][certified_key] for all SNI hostnames you want to use it for, +- packaging your [`SigningKey`][signing_key] with the matching certificate chain into a [`Credentials`][credentials] +- making a [`ServerNameResolver`][cert_using_sni] and feeding in your [`Credentials`][credentials] for all SNI hostnames you want to use it for, - setting that as your `ServerConfig`'s [`cert_resolver`][cert_resolver] -For a complete example of implementing a custom [`sign::SigningKey`][signing_key] -and [`sign::Signer`][signer] see the [rustls-cng] crate. +For a complete example of implementing a custom [`SigningKey`][signing_key] and +[`Signer`][signer] see the [`signer` module in the `rustls-cng` crate][rustls-cng-signer]. -[signing_key]: crate::crypto::signer::SigningKey -[choose_scheme]: crate::crypto::signer::SigningKey.choose_scheme() -[sig_scheme]: crate::SignatureScheme -[signer]: crate::crypto::signer::Signer -[sign_method]: crate::crypto::Signer.sign() -[certified_key]: crate::crypto::signer::CertifiedKey -[cert_using_sni]: crate::server::ResolvesServerCertUsingSni +[signing_key]: crate::crypto::SigningKey +[choose_scheme]: crate::crypto::SigningKey::choose_scheme +[sig_scheme]: crate::crypto::SignatureScheme +[signer]: crate::crypto::Signer +[sign_method]: crate::crypto::Signer::sign +[credentials]: crate::crypto::Credentials +[cert_using_sni]: crate::server::ServerNameResolver [cert_resolver]: crate::ServerConfig::cert_resolver -[rustls-cng]: https://github.com/rustls/rustls-cng/blob/dev/src/signer.rs +[rustls-cng-signer]: https://github.com/rustls/rustls-cng/blob/dev/src/signer.rs [^1]: For PKCS#8 it does not support password encryption -- there's not a meaningful threat model addressed by this, and the encryption supported is typically extremely poor. @@ -54,4 +54,71 @@ messages are never delimited by the close of the TCP connection), it can uncondi ignore `UnexpectedEof` errors from rustls. [^2]: + +# Debugging + +If you encounter a bug with Rustls it can be helpful to collect up as much diagnostic +information as possible. + +## Collecting logs + +If your bug reproduces with one of the [Rustls examples] you can use the +[`RUST_LOG`] environment variable to increase the log verbosity. If you're using +your own application, you may need to configure it with a logging backend +like `env_logger`. + +Consider reproducing your bug with `RUST_LOG=rustls=trace` and sharing the result +in a [GitHub gist]. + +[Rustls examples]: https://github.com/rustls/rustls/tree/main/examples +[`RUST_LOG`]: https://docs.rs/env_logger/latest/env_logger/#enabling-logging +[`env_logger`]: https://docs.rs/env_logger/ +[GitHub gist]: https://docs.github.com/en/get-started/writing-on-github/editing-and-sharing-content-with-gists/creating-gists + +## Taking a packet capture + +When logs aren't enough taking a packet capture ("pcap") is another helpful tool. +The details of how to accomplish this vary by operating system/context. + +### tcpdump + +As one example, on Linux using [`tcpdump`] is often easiest. + +If you know the IP address of the remote server your bug demonstrates with you +could take a short packet capture with this command (after replacing +`XX.XX.XX.XX` with the correct IP address): + +```bash +sudo tcpdump -i any tcp and dst host XX.XX.XX.XX -C5 -w rustls.pcap +``` + +The `-i any` captures on any network interface. The `tcp and dst host XX.XX.XX.XX` +portion target the capture to TCP traffic to the specified IP address. The `-C5` +argument limits the capture to at most 5MB. Lastly the `-w` argument writes the +capture to `rustls.pcap`. + +Another approach is to use `tcp and port XXXX` instead of `tcp and dst host XX.XX.XX.XX` +to capture all traffic to a specific port instead of a specific host server. + +[`tcpdump`]: https://web.archive.org/web/www.redhat.com/en/blog/introduction-using-tcpdump-linux-command-line + +### SSLKEYLOGFILE + +If the bug you are reporting happens after data is encrypted you may also wish to +share the secret keys required to decrypt the post-handshake traffic. + +If you're using one of the [Rustls examples] you can set the `SSLKEYLOGFILE` environment +variable to a path where secrets will be written. E.g. `SSLKEYLOGFILE=rustls.pcap.keys`. + +If you're using your own application you may need to customize the Rustls `ClientConfig` +or `ServerConfig`'s `key_log` setting like the example applications do. + +With the file from `SSLKEYLOGFILE` it is possible to use [Wireshark] or another tool to +decrypt the post-handshake messages, following [these instructions][curl-sslkeylogfile]. + +Remember this allows plaintext decryption and should only be done in testing contexts +where no sensitive data (API keys, etc) are being shared. + +[Wireshark]: https://www.wireshark.org/download.html +[curl-sslkeylogfile]: https://everything.curl.dev/usingcurl/tls/sslkeylogfile.html */ diff --git a/rustls/src/manual/implvulns.rs b/rustls/src/manual/implvulns.rs index a073350c4e6..8c76c41c333 100644 --- a/rustls/src/manual/implvulns.rs +++ b/rustls/src/manual/implvulns.rs @@ -53,7 +53,7 @@ states that look like: ```ignore struct ExpectTraffic { (...) - _cert_verified: verify::ServerCertVerified, + _cert_verified: verify::ServerVerified, _sig_verified: verify::HandshakeSignatureValid, _fin_verified: verify::FinishedMessageVerified, } @@ -72,7 +72,7 @@ to data being encrypted with the wrong keys (specifically, keys which were not s from OpenSSL taking a *reactive* strategy to incoming messages ("when I get a message X, I should do Y") which allows it to diverge from the proper state machine under attacker control. -[SMACK](https://mitls.org/pages/attacks/SMACK) is a similar suite of vulnerabilities found in JSSE, +[SMACK](https://web.archive.org/web/mitls.org/pages/attacks/SMACK) is a similar suite of vulnerabilities found in JSSE, CyaSSL, OpenSSL, Mono and axTLS. "SKIP-TLS" demonstrated that some implementations allowed handshake messages (and in one case, the entire handshake!) to be skipped leading to breaks in security. "FREAK" found that some implementations incorrectly allowed export-only state transitions (i.e., transitions that diff --git a/rustls/src/manual/mod.rs b/rustls/src/manual/mod.rs index 0e4fddce1cb..eacd26e73ab 100644 --- a/rustls/src/manual/mod.rs +++ b/rustls/src/manual/mod.rs @@ -6,7 +6,6 @@ It does this from a few aspects: how rustls attempts to avoid construction error that occurred in other TLS libraries, how rustls attempts to avoid past TLS protocol vulnerabilities, and assorted advice for achieving common tasks with rustls. */ -#![allow(non_snake_case)] /// This section discusses vulnerabilities in other TLS implementations, theorising their /// root cause and how we aim to avoid them in rustls. diff --git a/rustls/src/manual/tlsvulns.rs b/rustls/src/manual/tlsvulns.rs index 6d2220e3789..107d675b20e 100644 --- a/rustls/src/manual/tlsvulns.rs +++ b/rustls/src/manual/tlsvulns.rs @@ -17,7 +17,7 @@ in favour of the remaining proven-secure option. But that didn't happen, not in both RFCs included incorrect advice on countermeasures for implementers, suggesting that the flaw was "not believed to be large enough to be exploitable". -[Lucky 13](http://www.isg.rhul.ac.uk/tls/Lucky13.html) (2013) exploited this flaw and affected all implementations, including +[Lucky 13](https://web.archive.org/web/20190830051732/www.isg.rhul.ac.uk/tls/Lucky13.html) (2013) exploited this flaw and affected all implementations, including those written [after discovery](https://aws.amazon.com/blogs/security/s2n-and-lucky-13/). OpenSSL even had a [memory safety vulnerability in the fix for Lucky 13](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-2107), which gives a flavour of the kind of complexity required to remove the side channel. @@ -36,21 +36,21 @@ public key. This has two overall problems: 1. It provides no _forward secrecy_: later compromise of the server's private key breaks confidentiality of *all* past sessions using that key. This is a crucial property in the presence of software that is often - [poor at keeping a secret](http://heartbleed.com/). + [poor at keeping a secret](https://web.archive.org/web/heartbleed.com/). 2. The padding used in practice in TLS ("PKCS#1", or fully "RSAES-PKCS1-v1_5") has been known to be broken since [1998](http://archiv.infsec.ethz.ch/education/fs08/secsem/bleichenbacher98.pdf). In a similar pattern to the MAC-then-encrypt problem discussed above, TLSv1.0 (1999), TLSv1.1 (2006) and TLSv1.2 (2008) continued to specify use of PKCS#1 encryption, again with incrementally more complex and incorrect advice on countermeasures. -[ROBOT](https://robotattack.org/) (2018) showed that implementations were still vulnerable to these attacks twenty years later. -[The Marvin Attack](https://people.redhat.com/~hkario/marvin/) (2023) demonstrated the same a further five years later. +[ROBOT](https://web.archive.org/web/robotattack.org/) (2018) showed that implementations were still vulnerable to these attacks twenty years later. +[The Marvin Attack](https://web.archive.org/web/people.redhat.com/~hkario/marvin/) (2023) demonstrated the same a further five years later. rustls does not support RSA key exchange. TLSv1.3 also removed support. ## BEAST -[BEAST](https://vnhacker.blogspot.com/2011/09/beast.html) ([CVE-2011-3389](https://nvd.nist.gov/vuln/detail/CVE-2011-3389)) +[BEAST](https://web.archive.org/web/vnhacker.blogspot.com/2011/09/beast.html) ([CVE-2011-3389](https://nvd.nist.gov/vuln/detail/CVE-2011-3389)) was demonstrated in 2011 by Thai Duong and Juliano Rizzo, and was another vulnerability in CBC-based ciphersuites in SSLv3.0 and TLSv1.0. CBC mode is vulnerable to adaptive chosen-plaintext attacks if the IV is predictable. In the case of these protocol versions, the IV was the previous @@ -66,7 +66,7 @@ rustls does not support these ciphersuites. ## CRIME -In 2002 [John Kelsey](https://www.iacr.org/cryptodb/archive/2002/FSE/3091/3091.pdf) discussed the length side channel +In 2002 [John Kelsey](https://web.archive.org/web/20190701134313/www.iacr.org/cryptodb/archive/2002/FSE/3091/3091.pdf) discussed the length side channel as applied to compression of combined secret and attacker-chosen strings. Compression continued to be an option in TLSv1.1 (2006) and in TLSv1.2 (2008). Support in libraries was widespread. @@ -86,8 +86,8 @@ to export control. These controls were dropped in 2000. Since the "export-grade" ciphersuites no longer fulfilled any purpose, and because they were actively harmful to users, one may have expected software support to disappear quickly. This did not happen. -In 2015 [the FREAK attack](https://mitls.org/pages/attacks/SMACK#freak) ([CVE-2015-0204](https://nvd.nist.gov/vuln/detail/CVE-2015-0204)) -and [the Logjam attack](https://weakdh.org/) ([CVE-2015-4000](https://nvd.nist.gov/vuln/detail/CVE-2015-4000)) both +In 2015 [the FREAK attack](https://web.archive.org/web/mitls.org/pages/attacks/SMACK#freak) ([CVE-2015-0204](https://nvd.nist.gov/vuln/detail/CVE-2015-0204)) +and [the Logjam attack](https://web.archive.org/web/weakdh.org/) ([CVE-2015-4000](https://nvd.nist.gov/vuln/detail/CVE-2015-4000)) both demonstrated total breaks of security in the presence of servers that accepted export ciphersuites. FREAK factored 512-bit RSA keys, while Logjam optimised solving discrete logs in the 512-bit group used by many different servers. @@ -99,7 +99,7 @@ Block ciphers are vulnerable to birthday attacks, where the probability of repea once a particular key has been used for many blocks. For block ciphers with 64-bit blocks, this becomes probable once a given key encrypts the order of 32GB of data. -[Sweet32](https://sweet32.info/) ([CVE-2016-2183](https://nvd.nist.gov/vuln/detail/CVE-2016-2183)) attacked this fact +[Sweet32](https://web.archive.org/web/sweet32.info/) ([CVE-2016-2183](https://nvd.nist.gov/vuln/detail/CVE-2016-2183)) attacked this fact in the context of TLS support for 3DES, breaking confidentiality by analysing a large amount of attacker-induced traffic in one session. @@ -107,7 +107,7 @@ rustls does not support any 64-bit block ciphers. ## DROWN -[DROWN](https://drownattack.com/) ([CVE-2016-0800](https://nvd.nist.gov/vuln/detail/CVE-2016-0800)) is a cross-protocol +[DROWN](https://web.archive.org/web/drownattack.com/) ([CVE-2016-0800](https://nvd.nist.gov/vuln/detail/CVE-2016-0800)) is a cross-protocol attack that breaks the security of TLSv1.2 and earlier (when used with RSA key exchange) by using SSLv2. It is required that the server uses the same key for both protocol versions. @@ -115,7 +115,7 @@ rustls naturally does not support SSLv2, but most importantly does not support R ## Poodle -[POODLE](https://cdn1.vox-cdn.com/uploads/chorus_asset/file/2354994/ssl-poodle.0.pdf) ([CVE-2014-3566](https://nvd.nist.gov/vuln/detail/CVE-2014-3566)) +[POODLE](https://web.archive.org/web/cdn1.vox-cdn.com/uploads/chorus_asset/file/2354994/ssl-poodle.0.pdf) ([CVE-2014-3566](https://nvd.nist.gov/vuln/detail/CVE-2014-3566)) is an attack against CBC mode ciphersuites in SSLv3. This was possible in most cases because some clients willingly downgraded to SSLv3 after failed handshakes for later versions. @@ -146,7 +146,7 @@ standardise this method. ## Renegotiation -In 2009 Marsh Ray and Steve Dispensa [discovered](https://kryptera.se/Renegotiating%20TLS.pdf) that the renegotiation +In 2009 Marsh Ray and Steve Dispensa [discovered](https://web.archive.org/web/kryptera.se/Renegotiating%20TLS.pdf) that the renegotiation feature of all versions of TLS allows a MitM to splice a request of their choice onto the front of the client's real HTTP request. A countermeasure was proposed and widely implemented to bind renegotiations to their previous negotiations; unfortunately this was insufficient. @@ -155,7 +155,7 @@ rustls does not support renegotiation in TLSv1.2. TLSv1.3 also no longer suppor ## 3SHAKE -[3SHAKE](https://www.mitls.org/pages/attacks/3SHAKE) (2014) described a complex attack that broke the "Secure Renegotiation" extension +[3SHAKE](https://web.archive.org/web/www.mitls.org/pages/attacks/3SHAKE) (2014) described a complex attack that broke the "Secure Renegotiation" extension introduced as a countermeasure to the previous protocol flaw. rustls does not support renegotiation for TLSv1.2 connections, or RSA key exchange, and both are required for this attack @@ -165,7 +165,7 @@ TLSv1.3 no longer supports renegotiation and RSA key exchange. It also effectiv ## KCI -[This vulnerability](https://kcitls.org/) makes use of TLS ciphersuites (those offering static DH) which were standardised +[This vulnerability](https://web.archive.org/web/kcitls.org/) makes use of TLS ciphersuites (those offering static DH) which were standardised yet not widely used. However, they were implemented by libraries, and as a result enabled for various clients. It coupled this with misconfigured certificates (on services including facebook.com) which allowed their misuse to MitM connections. diff --git a/rustls/src/msgs/alert.rs b/rustls/src/msgs/alert.rs deleted file mode 100644 index e66f64cf961..00000000000 --- a/rustls/src/msgs/alert.rs +++ /dev/null @@ -1,26 +0,0 @@ -use alloc::vec::Vec; - -use crate::enums::AlertDescription; -use crate::error::InvalidMessage; -use crate::msgs::codec::{Codec, Reader}; -use crate::msgs::enums::AlertLevel; - -#[derive(Debug)] -pub struct AlertMessagePayload { - pub level: AlertLevel, - pub description: AlertDescription, -} - -impl Codec<'_> for AlertMessagePayload { - fn encode(&self, bytes: &mut Vec) { - self.level.encode(bytes); - self.description.encode(bytes); - } - - fn read(r: &mut Reader<'_>) -> Result { - let level = AlertLevel::read(r)?; - let description = AlertDescription::read(r)?; - r.expect_empty("AlertMessagePayload") - .map(|_| Self { level, description }) - } -} diff --git a/rustls/src/msgs/base.rs b/rustls/src/msgs/base.rs deleted file mode 100644 index cbdad0a7b67..00000000000 --- a/rustls/src/msgs/base.rs +++ /dev/null @@ -1,204 +0,0 @@ -use alloc::vec::Vec; -use core::fmt; - -use pki_types::CertificateDer; -use zeroize::Zeroize; - -use crate::error::InvalidMessage; -use crate::msgs::codec; -use crate::msgs::codec::{Codec, Reader}; - -/// An externally length'd payload -#[derive(Clone, Eq, PartialEq)] -pub enum Payload<'a> { - Borrowed(&'a [u8]), - Owned(Vec), -} - -impl<'a> Codec<'a> for Payload<'a> { - fn encode(&self, bytes: &mut Vec) { - bytes.extend_from_slice(self.bytes()); - } - - fn read(r: &mut Reader<'a>) -> Result { - Ok(Self::read(r)) - } -} - -impl<'a> Payload<'a> { - pub fn bytes(&self) -> &[u8] { - match self { - Self::Borrowed(bytes) => bytes, - Self::Owned(bytes) => bytes, - } - } - - pub fn into_owned(self) -> Payload<'static> { - Payload::Owned(self.into_vec()) - } - - pub fn into_vec(self) -> Vec { - match self { - Self::Borrowed(bytes) => bytes.to_vec(), - Self::Owned(bytes) => bytes, - } - } - - pub fn read(r: &mut Reader<'a>) -> Self { - Self::Borrowed(r.rest()) - } -} - -impl Payload<'static> { - pub fn new(bytes: impl Into>) -> Self { - Self::Owned(bytes.into()) - } - - pub fn empty() -> Self { - Self::Borrowed(&[]) - } -} - -impl<'a> Codec<'a> for CertificateDer<'a> { - fn encode(&self, bytes: &mut Vec) { - codec::u24(self.as_ref().len() as u32).encode(bytes); - bytes.extend(self.as_ref()); - } - - fn read(r: &mut Reader<'a>) -> Result { - let len = codec::u24::read(r)?.0 as usize; - let mut sub = r.sub(len)?; - let body = sub.rest(); - Ok(Self::from(body)) - } -} - -impl fmt::Debug for Payload<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - hex(f, self.bytes()) - } -} - -/// An arbitrary, unknown-content, u24-length-prefixed payload -#[derive(Clone, Eq, PartialEq)] -pub(crate) struct PayloadU24<'a>(pub(crate) Payload<'a>); - -impl PayloadU24<'_> { - pub(crate) fn into_owned(self) -> PayloadU24<'static> { - PayloadU24(self.0.into_owned()) - } -} - -impl<'a> Codec<'a> for PayloadU24<'a> { - fn encode(&self, bytes: &mut Vec) { - let inner = self.0.bytes(); - codec::u24(inner.len() as u32).encode(bytes); - bytes.extend_from_slice(inner); - } - - fn read(r: &mut Reader<'a>) -> Result { - let len = codec::u24::read(r)?.0 as usize; - let mut sub = r.sub(len)?; - Ok(Self(Payload::read(&mut sub))) - } -} - -impl fmt::Debug for PayloadU24<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -/// An arbitrary, unknown-content, u16-length-prefixed payload -#[derive(Clone, Eq, PartialEq)] -pub struct PayloadU16(pub Vec); - -impl PayloadU16 { - pub fn new(bytes: Vec) -> Self { - Self(bytes) - } - - pub fn empty() -> Self { - Self::new(Vec::new()) - } - - pub fn encode_slice(slice: &[u8], bytes: &mut Vec) { - (slice.len() as u16).encode(bytes); - bytes.extend_from_slice(slice); - } -} - -impl Codec<'_> for PayloadU16 { - fn encode(&self, bytes: &mut Vec) { - Self::encode_slice(&self.0, bytes); - } - - fn read(r: &mut Reader<'_>) -> Result { - let len = u16::read(r)? as usize; - let mut sub = r.sub(len)?; - let body = sub.rest().to_vec(); - Ok(Self(body)) - } -} - -impl fmt::Debug for PayloadU16 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - hex(f, &self.0) - } -} - -/// An arbitrary, unknown-content, u8-length-prefixed payload -#[derive(Clone, Eq, PartialEq)] -pub struct PayloadU8(pub(crate) Vec); - -impl PayloadU8 { - pub(crate) fn encode_slice(slice: &[u8], bytes: &mut Vec) { - (slice.len() as u8).encode(bytes); - bytes.extend_from_slice(slice); - } - - pub(crate) fn new(bytes: Vec) -> Self { - Self(bytes) - } - - pub(crate) fn empty() -> Self { - Self(Vec::new()) - } -} - -impl Codec<'_> for PayloadU8 { - fn encode(&self, bytes: &mut Vec) { - (self.0.len() as u8).encode(bytes); - bytes.extend_from_slice(&self.0); - } - - fn read(r: &mut Reader<'_>) -> Result { - let len = u8::read(r)? as usize; - let mut sub = r.sub(len)?; - let body = sub.rest().to_vec(); - Ok(Self(body)) - } -} - -impl Zeroize for PayloadU8 { - fn zeroize(&mut self) { - self.0.zeroize(); - } -} - -impl fmt::Debug for PayloadU8 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - hex(f, &self.0) - } -} - -// Format an iterator of u8 into a hex string -pub(super) fn hex<'a>( - f: &mut fmt::Formatter<'_>, - payload: impl IntoIterator, -) -> fmt::Result { - for b in payload { - write!(f, "{:02x}", b)?; - } - Ok(()) -} diff --git a/rustls/src/msgs/ccs.rs b/rustls/src/msgs/ccs.rs deleted file mode 100644 index ac2c9e6a865..00000000000 --- a/rustls/src/msgs/ccs.rs +++ /dev/null @@ -1,23 +0,0 @@ -use alloc::vec::Vec; - -use crate::error::InvalidMessage; -use crate::msgs::codec::{Codec, Reader}; - -#[derive(Debug)] -pub struct ChangeCipherSpecPayload; - -impl Codec<'_> for ChangeCipherSpecPayload { - fn encode(&self, bytes: &mut Vec) { - 1u8.encode(bytes); - } - - fn read(r: &mut Reader<'_>) -> Result { - let typ = u8::read(r)?; - if typ != 1 { - return Err(InvalidMessage::InvalidCcs); - } - - r.expect_empty("ChangeCipherSpecPayload") - .map(|_| Self {}) - } -} diff --git a/rustls/src/msgs/client_hello.rs b/rustls/src/msgs/client_hello.rs new file mode 100644 index 00000000000..7195c4bd5a3 --- /dev/null +++ b/rustls/src/msgs/client_hello.rs @@ -0,0 +1,855 @@ +use alloc::boxed::Box; +use alloc::string::String; +use alloc::vec; +use alloc::vec::Vec; +use core::ops::{Deref, DerefMut}; + +use pki_types::DnsName; + +use super::codec::{ + Codec, LengthPrefixedBuffer, ListLength, MaybeEmpty, NonEmpty, Reader, SizedPayload, + TlsListElement, TlsListIter, +}; +use super::enums::{CertificateStatusType, Compression, ExtensionType, PskKeyExchangeMode}; +use super::handshake::{ + DuplicateExtensionChecker, Encoding, KeyShareEntry, Random, SessionId, SupportedEcPointFormats, + SupportedProtocolVersions, has_duplicates, +}; +use crate::crypto::cipher::Payload; +use crate::crypto::hpke::HpkeSymmetricCipherSuite; +use crate::crypto::kx::NamedGroup; +use crate::crypto::{CipherSuite, SignatureScheme}; +use crate::enums::{ + ApplicationProtocol, CertificateCompressionAlgorithm, CertificateType, EchClientHelloType, + ProtocolVersion, +}; +use crate::error::InvalidMessage; +use crate::log::warn; +use crate::msgs::enums::ServerNameType; +use crate::verify::DistinguishedName; + +#[derive(Clone, Debug)] +pub(crate) struct ClientHelloPayload { + pub(crate) client_version: ProtocolVersion, + pub(crate) random: Random, + pub(crate) session_id: SessionId, + pub(crate) cipher_suites: Vec, + pub(crate) compression_methods: Vec, + pub(crate) extensions: Box>, +} + +impl ClientHelloPayload { + pub(crate) fn ech_inner_encoding(&self, to_compress: Vec) -> Vec { + let mut bytes = Vec::new(); + self.payload_encode(&mut bytes, Encoding::EchInnerHello { to_compress }); + bytes + } + + pub(crate) fn payload_encode(&self, bytes: &mut Vec, purpose: Encoding) { + self.client_version.encode(bytes); + self.random.encode(bytes); + + match purpose { + // SessionID is required to be empty in the encoded inner client hello. + Encoding::EchInnerHello { .. } => SessionId::empty().encode(bytes), + _ => self.session_id.encode(bytes), + } + + self.cipher_suites.encode(bytes); + self.compression_methods.encode(bytes); + + let to_compress = match purpose { + Encoding::EchInnerHello { to_compress } if !to_compress.is_empty() => to_compress, + _ => { + self.extensions.encode(bytes); + return; + } + }; + + let mut compressed = self.extensions.clone(); + + // First, eliminate the full-fat versions of the extensions + for e in &to_compress { + compressed.clear(*e); + } + + // Replace with the marker noting which extensions were elided. + compressed.encrypted_client_hello_outer = Some(to_compress); + + // And encode as normal. + compressed.encode(bytes); + } + + pub(crate) fn has_keyshare_extension_with_duplicates(&self) -> bool { + self.key_shares + .as_ref() + .map(|entries| { + has_duplicates::<_, _, u16>( + entries + .iter() + .map(|kse| u16::from(kse.group)), + ) + }) + .unwrap_or_default() + } + + pub(crate) fn has_certificate_compression_extension_with_duplicates(&self) -> bool { + if let Some(algs) = &self.certificate_compression_algorithms { + has_duplicates::<_, _, u16>(algs.iter().copied()) + } else { + false + } + } +} + +impl Codec<'_> for ClientHelloPayload { + fn encode(&self, bytes: &mut Vec) { + self.payload_encode(bytes, Encoding::Standard) + } + + fn read(r: &mut Reader<'_>) -> Result { + let ret = Self { + client_version: ProtocolVersion::read(r)?, + random: Random::read(r)?, + session_id: SessionId::read(r)?, + cipher_suites: Vec::read(r)?, + compression_methods: Vec::read(r)?, + extensions: Box::new(ClientExtensions::read(r)?.into_owned()), + }; + + match r.any_left() { + true => Err(InvalidMessage::TrailingData("ClientHelloPayload")), + false => Ok(ret), + } + } +} + +impl Deref for ClientHelloPayload { + type Target = ClientExtensions<'static>; + fn deref(&self) -> &Self::Target { + &self.extensions + } +} + +impl DerefMut for ClientHelloPayload { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.extensions + } +} + +extension_struct! { + /// A representation of extensions present in a `ClientHello` message + /// + /// All extensions are optional (by definition) so are represented with `Option`. + /// + /// Some extensions have an empty value and are represented with Option<()>. + /// + /// Unknown extensions are dropped during parsing. + pub(crate) struct ClientExtensions<'a> { + /// Requested server name indication (RFC6066) + ExtensionType::ServerName => + pub(crate) server_name: Option>, + + /// Certificate status is requested (RFC6066) + ExtensionType::StatusRequest => + pub(crate) certificate_status_request: Option, + + /// Supported groups (RFC4492/RFC8446) + ExtensionType::EllipticCurves => + pub(crate) named_groups: Option>, + + /// Supported EC point formats (RFC4492) + ExtensionType::ECPointFormats => + pub(crate) ec_point_formats: Option, + + /// Supported signature schemes (RFC5246/RFC8446) + ExtensionType::SignatureAlgorithms => + pub(crate) signature_schemes: Option>, + + /// Offered ALPN protocols (RFC6066) + ExtensionType::ALProtocolNegotiation => + pub(crate) protocols: Option>>, + + /// Available client certificate types (RFC7250) + ExtensionType::ClientCertificateType => + pub(crate) client_certificate_types: Option>, + + /// Acceptable server certificate types (RFC7250) + ExtensionType::ServerCertificateType => + pub(crate) server_certificate_types: Option>, + + /// Extended master secret is requested (RFC7627) + ExtensionType::ExtendedMasterSecret => + pub(crate) extended_master_secret_request: Option<()>, + + /// Offered certificate compression methods (RFC8879) + ExtensionType::CompressCertificate => + pub(crate) certificate_compression_algorithms: Option>, + + /// Session ticket offer or request (RFC5077/RFC8446) + ExtensionType::SessionTicket => + pub(crate) session_ticket: Option, + + /// Offered preshared keys (RFC8446) + ExtensionType::PreSharedKey => + pub(crate) preshared_key_offer: Option, + + /// Early data is requested (RFC8446) + ExtensionType::EarlyData => + pub(crate) early_data_request: Option<()>, + + /// Supported TLS versions (RFC8446) + ExtensionType::SupportedVersions => + pub(crate) supported_versions: Option, + + /// Stateless HelloRetryRequest cookie (RFC8446) + ExtensionType::Cookie => + pub(crate) cookie: Option>, + + /// Offered preshared key modes (RFC8446) + ExtensionType::PSKKeyExchangeModes => + pub(crate) preshared_key_modes: Option, + + /// Certificate authority names (RFC8446) + ExtensionType::CertificateAuthorities => + pub(crate) certificate_authority_names: Option>, + + /// Offered key exchange shares (RFC8446) + ExtensionType::KeyShare => + pub(crate) key_shares: Option>, + + /// QUIC transport parameters (RFC9001) + ExtensionType::TransportParameters => + pub(crate) transport_parameters: Option>, + + /// Secure renegotiation (RFC5746) + ExtensionType::RenegotiationInfo => + pub(crate) renegotiation_info: Option>, + + /// Encrypted inner client hello (RFC 9849) + ExtensionType::EncryptedClientHello => + pub(crate) encrypted_client_hello: Option, + + /// Encrypted client hello outer extensions (RFC 9849) + ExtensionType::EncryptedClientHelloOuterExtensions => + pub(crate) encrypted_client_hello_outer: Option>, + } + { + /// Order randomization seed. + pub(crate) order_seed: u16, + + /// Extensions that must appear contiguously. + pub(crate) contiguous_extensions: Vec, + } +} + +impl ClientExtensions<'_> { + pub(crate) fn into_owned(self) -> ClientExtensions<'static> { + let Self { + server_name, + certificate_status_request, + named_groups, + ec_point_formats, + signature_schemes, + protocols, + client_certificate_types, + server_certificate_types, + extended_master_secret_request, + certificate_compression_algorithms, + session_ticket, + preshared_key_offer, + early_data_request, + supported_versions, + cookie, + preshared_key_modes, + certificate_authority_names, + key_shares, + transport_parameters, + renegotiation_info, + encrypted_client_hello, + encrypted_client_hello_outer, + order_seed, + contiguous_extensions, + } = self; + ClientExtensions { + server_name: server_name.map(|x| x.into_owned()), + certificate_status_request, + named_groups, + ec_point_formats, + signature_schemes, + protocols: protocols.map(|ps| { + ps.into_iter() + .map(|p| p.to_owned()) + .collect::>() + }), + client_certificate_types, + server_certificate_types, + extended_master_secret_request, + certificate_compression_algorithms, + session_ticket, + preshared_key_offer, + early_data_request, + supported_versions, + cookie: cookie.map(|x| x.into_owned()), + preshared_key_modes, + certificate_authority_names, + key_shares, + transport_parameters: transport_parameters.map(|x| x.into_owned()), + renegotiation_info: renegotiation_info.map(|x| x.into_owned()), + encrypted_client_hello, + encrypted_client_hello_outer, + order_seed, + contiguous_extensions, + } + } + + pub(crate) fn used_extensions_in_encoding_order(&self) -> Vec { + let mut exts = self.order_insensitive_extensions_in_random_order(); + exts.extend(&self.contiguous_extensions); + + if self + .encrypted_client_hello_outer + .is_some() + { + exts.push(ExtensionType::EncryptedClientHelloOuterExtensions); + } + if self.encrypted_client_hello.is_some() { + exts.push(ExtensionType::EncryptedClientHello); + } + if self.preshared_key_offer.is_some() { + exts.push(ExtensionType::PreSharedKey); + } + exts + } + + /// Returns extensions which don't need a specific order, in randomized order. + /// + /// Extensions are encoded in three portions: + /// + /// - First, extensions not otherwise dealt with by other cases. + /// These are encoded in random order, controlled by `self.order_seed`, + /// and this is the set of extensions returned by this function. + /// + /// - Second, extensions named in `self.contiguous_extensions`, in the order + /// given by that field. + /// + /// - Lastly, any ECH and PSK extensions (in that order). These + /// are required to be last by the standard. + fn order_insensitive_extensions_in_random_order(&self) -> Vec { + let mut order = self.collect_used(); + + // Remove extensions which have specific order requirements. + order.retain(|ext| { + !(matches!( + *ext, + ExtensionType::PreSharedKey + | ExtensionType::EncryptedClientHello + | ExtensionType::EncryptedClientHelloOuterExtensions + ) || self.contiguous_extensions.contains(ext)) + }); + + order.sort_by_cached_key(|new_ext| { + let seed = ((self.order_seed as u32) << 16) | (u16::from(*new_ext) as u32); + low_quality_integer_hash(seed) + }); + + order + } +} + +impl<'a> Codec<'a> for ClientExtensions<'a> { + fn encode(&self, bytes: &mut Vec) { + let order = self.used_extensions_in_encoding_order(); + + if order.is_empty() { + return; + } + + let body = LengthPrefixedBuffer::new(ListLength::U16, bytes); + for item in order { + self.encode_one(item, body.buf); + } + } + + fn read(r: &mut Reader<'a>) -> Result { + let mut out = Self::default(); + + // extensions length can be absent if no extensions + if !r.any_left() { + return Ok(out); + } + + let mut checker = DuplicateExtensionChecker::new(); + + let len = usize::from(u16::read(r)?); + let mut sub = r.sub(len)?; + + while sub.any_left() { + let typ = out.read_one(&mut sub, |unknown| checker.check(unknown))?; + + // PreSharedKey offer must come last + if typ == ExtensionType::PreSharedKey && sub.any_left() { + return Err(InvalidMessage::PreSharedKeyIsNotFinalExtension); + } + } + + Ok(out) + } +} + +/// Representation of the `ECHClientHello` client extension specified in +/// [RFC 9849 Section 5]. +/// +/// [RFC 9849 Section 5]: +#[derive(Clone, Debug)] +pub(crate) enum EncryptedClientHello { + /// A `ECHClientHello` with type [EchClientHelloType::ClientHelloOuter]. + Outer(EncryptedClientHelloOuter), + /// An empty `ECHClientHello` with type [EchClientHelloType::ClientHelloInner]. + /// + /// This variant has no payload. + Inner, +} + +impl Codec<'_> for EncryptedClientHello { + fn encode(&self, bytes: &mut Vec) { + match self { + Self::Outer(payload) => { + EchClientHelloType::ClientHelloOuter.encode(bytes); + payload.encode(bytes); + } + Self::Inner => { + EchClientHelloType::ClientHelloInner.encode(bytes); + // Empty payload. + } + } + } + + fn read(r: &mut Reader<'_>) -> Result { + match EchClientHelloType::read(r)? { + EchClientHelloType::ClientHelloOuter => { + Ok(Self::Outer(EncryptedClientHelloOuter::read(r)?)) + } + EchClientHelloType::ClientHelloInner => Ok(Self::Inner), + _ => Err(InvalidMessage::InvalidContentType), + } + } +} + +/// Representation of the ECHClientHello extension with type outer specified in +/// [RFC 9849 Section 5]. +/// +/// [RFC 9849 Section 5]: +#[derive(Clone, Debug)] +pub(crate) struct EncryptedClientHelloOuter { + /// The cipher suite used to encrypt ClientHelloInner. Must match a value from + /// ECHConfigContents.cipher_suites list. + pub cipher_suite: HpkeSymmetricCipherSuite, + /// The ECHConfigContents.key_config.config_id for the chosen ECHConfig. + pub config_id: u8, + /// The HPKE encapsulated key, used by servers to decrypt the corresponding payload field. + /// This field is empty in a ClientHelloOuter sent in response to a HelloRetryRequest. + pub enc: SizedPayload<'static, u16, MaybeEmpty>, + /// The serialized and encrypted ClientHelloInner structure, encrypted using HPKE. + pub payload: SizedPayload<'static, u16, NonEmpty>, +} + +impl Codec<'_> for EncryptedClientHelloOuter { + fn encode(&self, bytes: &mut Vec) { + self.cipher_suite.encode(bytes); + self.config_id.encode(bytes); + self.enc.encode(bytes); + self.payload.encode(bytes); + } + + fn read(r: &mut Reader<'_>) -> Result { + Ok(Self { + cipher_suite: HpkeSymmetricCipherSuite::read(r)?, + config_id: u8::read(r)?, + enc: SizedPayload::read(r)?.into_owned(), + payload: SizedPayload::read(r)?.into_owned(), + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) enum ServerNamePayload<'a> { + /// A successfully decoded value: + SingleDnsName(DnsName<'a>), + + /// A DNS name which was actually an IP address + IpAddress, + + /// A successfully decoded, but syntactically-invalid value. + Invalid, +} + +impl ServerNamePayload<'_> { + pub(super) fn into_owned(self) -> ServerNamePayload<'static> { + match self { + Self::SingleDnsName(d) => ServerNamePayload::SingleDnsName(d.to_owned()), + Self::IpAddress => ServerNamePayload::IpAddress, + Self::Invalid => ServerNamePayload::Invalid, + } + } + + /// RFC6066: `ServerName server_name_list<1..2^16-1>` + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("ServerNames"), + }; + + /// Get the `DnsName` out of this `ServerNamePayload` if it contains one. + /// The returned `DnsName` will be normalized (converted to lowercase). + pub(crate) fn to_dns_name_normalized(&self) -> Option> { + match self { + Self::SingleDnsName(dns_name) => Some(dns_name.to_lowercase_owned()), + Self::IpAddress => None, + Self::Invalid => None, + } + } +} + +/// Simplified encoding/decoding for a `ServerName` extension payload to/from `DnsName` +/// +/// This is possible because: +/// +/// - the spec (RFC6066) disallows multiple names for a given name type +/// - name types other than ServerNameType::HostName are not defined, and they and +/// any data that follows them cannot be skipped over. +impl<'a> Codec<'a> for ServerNamePayload<'a> { + fn encode(&self, bytes: &mut Vec) { + let server_name_list = LengthPrefixedBuffer::new(Self::SIZE_LEN, bytes); + + let ServerNamePayload::SingleDnsName(dns_name) = self else { + return; + }; + + ServerNameType::HostName.encode(server_name_list.buf); + let name_slice = dns_name.as_ref().as_bytes(); + (name_slice.len() as u16).encode(server_name_list.buf); + server_name_list + .buf + .extend_from_slice(name_slice); + } + + fn read(r: &mut Reader<'a>) -> Result { + let mut found = None; + + let len = Self::SIZE_LEN.read(r)?; + let mut sub = r.sub(len)?; + + while sub.any_left() { + let typ = ServerNameType::read(&mut sub)?; + + let payload = match typ { + ServerNameType::HostName => HostNamePayload::read(&mut sub)?, + _ => { + // Consume remainder of extension bytes. Since the length of the item + // is an unknown encoding, we cannot continue. + sub.rest(); + break; + } + }; + + // "The ServerNameList MUST NOT contain more than one name of + // the same name_type." - RFC6066 + if found.is_some() { + warn!("Illegal SNI extension: duplicate host_name received"); + return Err(InvalidMessage::InvalidServerName); + } + + found = match payload { + HostNamePayload::HostName(dns_name) => { + Some(Self::SingleDnsName(dns_name.to_owned())) + } + + HostNamePayload::IpAddress(_invalid) => { + warn!("Illegal SNI extension: IP address presented as hostname ({_invalid:?})"); + Some(Self::IpAddress) + } + + HostNamePayload::Invalid(_invalid) => { + warn!( + "Illegal SNI hostname received {:?}", + String::from_utf8_lossy(_invalid.bytes()) + ); + Some(Self::Invalid) + } + }; + } + + Ok(found.unwrap_or(Self::Invalid)) + } +} + +impl<'a> From<&DnsName<'a>> for ServerNamePayload<'static> { + fn from(value: &DnsName<'a>) -> Self { + Self::SingleDnsName(trim_hostname_trailing_dot_for_sni(value)) + } +} + +fn trim_hostname_trailing_dot_for_sni(dns_name: &DnsName<'_>) -> DnsName<'static> { + let dns_name_str = dns_name.as_ref(); + + // RFC6066: "The hostname is represented as a byte string using + // ASCII encoding without a trailing dot" + if dns_name_str.ends_with('.') { + let trimmed = &dns_name_str[0..dns_name_str.len() - 1]; + DnsName::try_from(trimmed) + .unwrap() + .to_owned() + } else { + dns_name.to_owned() + } +} + +#[derive(Clone, Debug)] +pub(crate) enum HostNamePayload { + HostName(DnsName<'static>), + IpAddress(SizedPayload<'static, u16, NonEmpty>), + Invalid(SizedPayload<'static, u16, NonEmpty>), +} + +impl HostNamePayload { + fn read(r: &mut Reader<'_>) -> Result { + use pki_types::ServerName; + let raw = SizedPayload::::read(r)?; + + match ServerName::try_from(raw.bytes()) { + Ok(ServerName::DnsName(d)) => Ok(Self::HostName(d.to_owned())), + Ok(ServerName::IpAddress(_)) => Ok(Self::IpAddress(raw.into_owned())), + Ok(_) | Err(_) => Ok(Self::Invalid(raw.into_owned())), + } + } +} + +// --- RFC6066 certificate status request --- + +#[derive(Clone, Debug)] +pub(crate) enum CertificateStatusRequest { + Ocsp(OcspCertificateStatusRequest), + Unknown((CertificateStatusType, Payload<'static>)), +} + +impl Codec<'_> for CertificateStatusRequest { + fn encode(&self, bytes: &mut Vec) { + match self { + Self::Ocsp(r) => r.encode(bytes), + Self::Unknown((typ, payload)) => { + typ.encode(bytes); + payload.encode(bytes); + } + } + } + + fn read(r: &mut Reader<'_>) -> Result { + let typ = CertificateStatusType::read(r)?; + + match typ { + CertificateStatusType::OCSP => { + let ocsp_req = OcspCertificateStatusRequest::read(r)?; + Ok(Self::Ocsp(ocsp_req)) + } + _ => { + let data = Payload::read(r).into_owned(); + Ok(Self::Unknown((typ, data))) + } + } + } +} + +impl CertificateStatusRequest { + pub(crate) fn build_ocsp() -> Self { + let ocsp = OcspCertificateStatusRequest { + responder_ids: Vec::new(), + extensions: SizedPayload::from(Payload::new(Vec::new())), + }; + Self::Ocsp(ocsp) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct OcspCertificateStatusRequest { + pub(crate) responder_ids: Vec, + pub(crate) extensions: SizedPayload<'static, u16, MaybeEmpty>, +} + +impl Codec<'_> for OcspCertificateStatusRequest { + fn encode(&self, bytes: &mut Vec) { + CertificateStatusType::OCSP.encode(bytes); + self.responder_ids.encode(bytes); + self.extensions.encode(bytes); + } + + fn read(r: &mut Reader<'_>) -> Result { + Ok(Self { + responder_ids: Vec::read(r)?, + extensions: SizedPayload::read(r)?.into_owned(), + }) + } +} + +wrapped_payload!(pub(crate) struct ResponderId, SizedPayload,); + +/// RFC6066: `ResponderID responder_id_list<0..2^16-1>;` +impl TlsListElement for ResponderId { + const SIZE_LEN: ListLength = ListLength::U16; +} + +// --- TLS 1.3 PresharedKey offers --- + +#[derive(Clone, Debug)] +pub(crate) struct PresharedKeyOffer { + pub(crate) identities: Vec, + pub(crate) binders: Vec, +} + +impl PresharedKeyOffer { + /// Make a new one with one entry. + pub(crate) fn new(id: PresharedKeyIdentity, binder: Vec) -> Self { + Self { + identities: vec![id], + binders: vec![PresharedKeyBinder::from(binder)], + } + } +} + +impl Codec<'_> for PresharedKeyOffer { + fn encode(&self, bytes: &mut Vec) { + self.identities.encode(bytes); + self.binders.encode(bytes); + } + + fn read(r: &mut Reader<'_>) -> Result { + Ok(Self { + identities: Vec::read(r)?, + binders: Vec::read(r)?, + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct PresharedKeyIdentity { + /// RFC8446: `opaque identity<1..2^16-1>;` + pub(crate) identity: SizedPayload<'static, u16, NonEmpty>, + pub(crate) obfuscated_ticket_age: u32, +} + +impl PresharedKeyIdentity { + pub(crate) fn new(id: Vec, age: u32) -> Self { + Self { + identity: SizedPayload::from(Payload::new(id)), + obfuscated_ticket_age: age, + } + } +} + +impl Codec<'_> for PresharedKeyIdentity { + fn encode(&self, bytes: &mut Vec) { + self.identity.encode(bytes); + self.obfuscated_ticket_age.encode(bytes); + } + + fn read(r: &mut Reader<'_>) -> Result { + Ok(Self { + identity: SizedPayload::read(r)?.into_owned(), + obfuscated_ticket_age: u32::read(r)?, + }) + } +} + +/// RFC8446: `PskIdentity identities<7..2^16-1>;` +impl TlsListElement for PresharedKeyIdentity { + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("PskIdentities"), + }; +} + +wrapped_payload!( + /// RFC8446: `opaque PskBinderEntry<32..255>;` + pub(crate) struct PresharedKeyBinder, SizedPayload, +); + +/// RFC8446: `PskBinderEntry binders<33..2^16-1>;` +impl TlsListElement for PresharedKeyBinder { + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("PskBinders"), + }; +} + +/// RFC8446: `PskKeyExchangeMode ke_modes<1..255>;` +#[derive(Clone, Copy, Debug, Default)] +pub(crate) struct PskKeyExchangeModes { + pub(crate) psk_dhe: bool, + pub(crate) psk: bool, +} + +impl Codec<'_> for PskKeyExchangeModes { + fn encode(&self, bytes: &mut Vec) { + let inner = LengthPrefixedBuffer::new(PskKeyExchangeMode::SIZE_LEN, bytes); + if self.psk_dhe { + PskKeyExchangeMode::PSK_DHE_KE.encode(inner.buf); + } + if self.psk { + PskKeyExchangeMode::PSK_KE.encode(inner.buf); + } + } + + fn read(reader: &mut Reader<'_>) -> Result { + let mut psk_dhe = false; + let mut psk = false; + + for ke in TlsListIter::::new(reader)? { + match ke? { + PskKeyExchangeMode::PSK_DHE_KE => psk_dhe = true, + PskKeyExchangeMode::PSK_KE => psk = true, + _ => continue, + }; + } + + Ok(Self { psk_dhe, psk }) + } +} + +impl TlsListElement for PskKeyExchangeMode { + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("PskKeyExchangeModes"), + }; +} + +#[derive(Clone, Debug)] +pub(crate) enum ClientSessionTicket { + Request, + Offer(Payload<'static>), +} + +impl<'a> Codec<'a> for ClientSessionTicket { + fn encode(&self, bytes: &mut Vec) { + match self { + Self::Request => (), + Self::Offer(p) => p.encode(bytes), + } + } + + fn read(r: &mut Reader<'a>) -> Result { + Ok(match r.left() { + 0 => Self::Request, + _ => Self::Offer(Payload::read(r).into_owned()), + }) + } +} + +fn low_quality_integer_hash(mut x: u32) -> u32 { + x = x + .wrapping_add(0x7ed55d16) + .wrapping_add(x << 12); + x = (x ^ 0xc761c23c) ^ (x >> 19); + x = x + .wrapping_add(0x165667b1) + .wrapping_add(x << 5); + x = x.wrapping_add(0xd3a2646c) ^ (x << 9); + x = x + .wrapping_add(0xfd7046c5) + .wrapping_add(x << 3); + x = (x ^ 0xb55a4f09) ^ (x >> 16); + x +} diff --git a/rustls/src/msgs/codec.rs b/rustls/src/msgs/codec.rs index 0a7ae21507e..efa42cb5a4d 100644 --- a/rustls/src/msgs/codec.rs +++ b/rustls/src/msgs/codec.rs @@ -1,213 +1,242 @@ use alloc::vec::Vec; -use core::fmt::Debug; +use core::fmt::{self, Debug}; +use core::marker::PhantomData; +use core::mem; +use pki_types::{CertificateDer, SubjectPublicKeyInfoDer}; +use zeroize::Zeroize; + +use crate::crypto::cipher::Payload; use crate::error::InvalidMessage; -/// Wrapper over a slice of bytes that allows reading chunks from -/// with the current position state held using a cursor. -/// -/// A new reader for a sub section of the buffer can be created -/// using the `sub` function or a section of a certain length can -/// be obtained using the `take` function -pub struct Reader<'a> { - /// The underlying buffer storing the readers content - buffer: &'a [u8], - /// Stores the current reading position for the buffer - cursor: usize, +/// An arbitrary, unknown-content, u24-length-prefixed payload +#[derive(Clone, Eq, PartialEq)] +pub(crate) struct SizedPayload<'a, L, C: Cardinality = MaybeEmpty> { + pub(crate) inner: Payload<'a>, + pub(crate) _marker: PhantomData<(L, C)>, } -impl<'a> Reader<'a> { - /// Creates a new Reader of the provided `bytes` slice with - /// the initial cursor position of zero. - pub fn init(bytes: &'a [u8]) -> Self { - Reader { - buffer: bytes, - cursor: 0, +impl<'a, L, C: Cardinality> SizedPayload<'a, L, C> { + pub(crate) fn into_owned(self) -> SizedPayload<'static, L, C> { + SizedPayload { + inner: self.inner.into_owned(), + _marker: PhantomData, } } - /// Attempts to create a new Reader on a sub section of this - /// readers bytes by taking a slice of the provided `length` - /// will return None if there is not enough bytes - pub fn sub(&mut self, length: usize) -> Result { - match self.take(length) { - Some(bytes) => Ok(Reader::init(bytes)), - None => Err(InvalidMessage::MessageTooShort), - } + pub(crate) fn into_vec(self) -> Vec { + self.inner.into_owned().into_vec() } - /// Borrows a slice of all the remaining bytes - /// that appear after the cursor position. - /// - /// Moves the cursor to the end of the buffer length. - pub fn rest(&mut self) -> &'a [u8] { - let rest = &self.buffer[self.cursor..]; - self.cursor = self.buffer.len(); - rest - } - - /// Attempts to borrow a slice of bytes from the current - /// cursor position of `length` if there is not enough - /// bytes remaining after the cursor to take the length - /// then None is returned instead. - pub fn take(&mut self, length: usize) -> Option<&'a [u8]> { - if self.left() < length { - return None; + pub(crate) fn as_mut(&mut self) -> Option<&mut [u8]> { + match &mut self.inner { + Payload::Owned(vec) => Some(vec.as_mut_slice()), + Payload::Borrowed(_) => None, } - let current = self.cursor; - self.cursor += length; - Some(&self.buffer[current..current + length]) } - /// Used to check whether the reader has any content left - /// after the cursor (cursor has not reached end of buffer) - pub fn any_left(&self) -> bool { - self.cursor < self.buffer.len() + pub(crate) fn to_vec(&self) -> Vec { + self.inner.bytes().to_vec() } - pub fn expect_empty(&self, name: &'static str) -> Result<(), InvalidMessage> { - match self.any_left() { - true => Err(InvalidMessage::TrailingData(name)), - false => Ok(()), - } + pub(crate) fn bytes(&'a self) -> &'a [u8] { + self.inner.bytes() } - /// Returns the cursor position which is also the number - /// of bytes that have been read from the buffer. - pub fn used(&self) -> usize { - self.cursor + pub(crate) fn is_empty(&self) -> bool { + self.inner.bytes().is_empty() } +} - /// Returns the number of bytes that are still able to be - /// read (The number of remaining takes) - pub fn left(&self) -> usize { - self.buffer.len() - self.cursor +impl<'a, L: PayloadSize<'a>> SizedPayload<'a, L, MaybeEmpty> { + #[cfg(test)] + pub(crate) fn empty() -> Self { + Self { + inner: Payload::Borrowed(&[]), + _marker: PhantomData, + } } } -/// Trait for implementing encoding and decoding functionality -/// on something. -pub trait Codec<'a>: Debug + Sized { - /// Function for encoding itself by appending itself to - /// the provided vec of bytes. - fn encode(&self, bytes: &mut Vec); - - /// Function for decoding itself from the provided reader - /// will return Some if the decoding was successful or - /// None if it was not. - fn read(_: &mut Reader<'a>) -> Result; - - /// Convenience function for encoding the implementation - /// into a vec and returning it - fn get_encoding(&self) -> Vec { - let mut bytes = Vec::new(); - self.encode(&mut bytes); - bytes +impl<'a, L: PayloadSize<'a>, C: Cardinality> Codec<'a> for SizedPayload<'a, L, C> { + fn encode(&self, bytes: &mut Vec) { + let inner = self.inner.bytes(); + debug_assert!(inner.len() >= C::MIN); + debug_assert!(inner.len() <= L::MAX); + L::length(inner).encode(bytes); + bytes.extend_from_slice(inner); } - /// Function for wrapping a call to the read function in - /// a Reader for the slice of bytes provided - /// - /// Returns `Err(InvalidMessage::ExcessData(_))` if - /// `Self::read` does not read the entirety of `bytes`. - fn read_bytes(bytes: &'a [u8]) -> Result { - let mut reader = Reader::init(bytes); - Self::read(&mut reader).and_then(|r| { - reader.expect_empty("read_bytes")?; - Ok(r) + fn read(r: &mut Reader<'a>) -> Result { + let len = L::read(r)?.into(); + if len < C::MIN { + return Err(InvalidMessage::IllegalEmptyList("SizedPayload")); + } + let mut sub = r.sub(len)?; + Ok(Self { + inner: Payload::read(&mut sub), + _marker: PhantomData, }) } } -impl Codec<'_> for u8 { - fn encode(&self, bytes: &mut Vec) { - bytes.push(*self); +impl Zeroize for SizedPayload<'_, u8, C> { + #[inline(never)] + fn zeroize(&mut self) { + if let Payload::Owned(buf) = &mut self.inner { + buf.zeroize(); + } } +} - fn read(r: &mut Reader<'_>) -> Result { - match r.take(1) { - Some(&[byte]) => Ok(byte), - _ => Err(InvalidMessage::MissingData("u8")), +impl<'a, L: PayloadSize<'a>, C: Cardinality> From> for SizedPayload<'a, L, C> { + fn from(inner: Payload<'a>) -> Self { + debug_assert!(inner.bytes().len() >= C::MIN); + debug_assert!(inner.bytes().len() <= L::MAX); + Self { + inner, + _marker: PhantomData, } } } -pub(crate) fn put_u16(v: u16, out: &mut [u8]) { - let out: &mut [u8; 2] = (&mut out[..2]).try_into().unwrap(); - *out = u16::to_be_bytes(v); +impl<'a, L: PayloadSize<'a>, C: Cardinality> From> for SizedPayload<'a, L, C> { + fn from(inner: Vec) -> Self { + debug_assert!(inner.len() >= C::MIN); + debug_assert!(inner.len() <= L::MAX); + Self { + inner: Payload::Owned(inner), + _marker: PhantomData, + } + } } -impl Codec<'_> for u16 { - fn encode(&self, bytes: &mut Vec) { - let mut b16 = [0u8; 2]; - put_u16(*self, &mut b16); - bytes.extend_from_slice(&b16); +impl<'a, L: PayloadSize<'a>, C: Cardinality> Debug for SizedPayload<'a, L, C> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) } +} - fn read(r: &mut Reader<'_>) -> Result { - match r.take(2) { - Some(&[b1, b2]) => Ok(Self::from_be_bytes([b1, b2])), - _ => Err(InvalidMessage::MissingData("u16")), - } +impl<'a> PayloadSize<'a> for U24 { + fn length(bytes: &[u8]) -> Self { + Self(bytes.len() as u32) } + + const MAX: usize = 0xFFFFFF; } -// Make a distinct type for u24, even though it's a u32 underneath -#[allow(non_camel_case_types)] -#[derive(Debug, Copy, Clone)] -pub struct u24(pub u32); +impl<'a> PayloadSize<'a> for u16 { + fn length(bytes: &[u8]) -> Self { + bytes.len() as Self + } -#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] -impl From for usize { - #[inline] - fn from(v: u24) -> Self { - v.0 as Self + const MAX: usize = 0xFFFF; +} + +impl<'a> PayloadSize<'a> for u8 { + fn length(bytes: &[u8]) -> Self { + bytes.len() as Self } + + const MAX: usize = 0xFF; +} + +pub(crate) trait PayloadSize<'a>: Codec<'a> + Into { + fn length(bytes: &[u8]) -> Self; + + const MAX: usize; +} + +pub(crate) trait Cardinality: Clone + Eq + PartialEq { + const MIN: usize; +} + +#[derive(Clone, Eq, PartialEq)] +pub(crate) struct MaybeEmpty; + +impl Cardinality for MaybeEmpty { + const MIN: usize = 0; +} + +#[derive(Clone, Eq, PartialEq)] +pub(crate) struct NonEmpty; + +impl Cardinality for NonEmpty { + const MIN: usize = 1; } -impl Codec<'_> for u24 { +impl<'a> Codec<'a> for CertificateDer<'a> { fn encode(&self, bytes: &mut Vec) { - let be_bytes = u32::to_be_bytes(self.0); - bytes.extend_from_slice(&be_bytes[1..]); + let nest = LengthPrefixedBuffer::new(Self::SIZE_LEN, bytes); + nest.buf.extend(self.as_ref()); } - fn read(r: &mut Reader<'_>) -> Result { - match r.take(3) { - Some(&[a, b, c]) => Ok(Self(u32::from_be_bytes([0, a, b, c]))), - _ => Err(InvalidMessage::MissingData("u24")), + fn read(r: &mut Reader<'a>) -> Result { + let len = ListLength::NonZeroU24 { + max: CERTIFICATE_MAX_SIZE_LIMIT, + empty_error: InvalidMessage::IllegalEmptyList("CertificateDer"), + too_many_error: InvalidMessage::CertificatePayloadTooLarge, } + .read(r)?; + + let mut sub = r.sub(len)?; + let body = sub.rest(); + Ok(Self::from(body)) } } -impl Codec<'_> for u32 { +impl TlsListElement for CertificateDer<'_> { + const SIZE_LEN: ListLength = ListLength::U24 { + max: CERTIFICATE_MAX_SIZE_LIMIT, + error: InvalidMessage::CertificatePayloadTooLarge, + }; +} + +impl<'a> Codec<'a> for SubjectPublicKeyInfoDer<'a> { fn encode(&self, bytes: &mut Vec) { - bytes.extend(Self::to_be_bytes(*self)); + let nest = LengthPrefixedBuffer::new(Self::SIZE_LEN, bytes); + nest.buf.extend(self.as_ref()); } - fn read(r: &mut Reader<'_>) -> Result { - match r.take(4) { - Some(&[a, b, c, d]) => Ok(Self::from_be_bytes([a, b, c, d])), - _ => Err(InvalidMessage::MissingData("u32")), - } + fn read(r: &mut Reader<'a>) -> Result { + let len = Self::SIZE_LEN.read(r)?; + let mut sub = r.sub(len)?; + let body = sub.rest(); + Ok(Self::from(body)) } } -pub(crate) fn put_u64(v: u64, bytes: &mut [u8]) { - let bytes: &mut [u8; 8] = (&mut bytes[..8]).try_into().unwrap(); - *bytes = u64::to_be_bytes(v); +impl TlsListElement for SubjectPublicKeyInfoDer<'_> { + const SIZE_LEN: ListLength = CertificateDer::SIZE_LEN; } -impl Codec<'_> for u64 { - fn encode(&self, bytes: &mut Vec) { - let mut b64 = [0u8; 8]; - put_u64(*self, &mut b64); - bytes.extend_from_slice(&b64); +/// An iterator over a vector of `TlsListElements`. +/// +/// All uses _MUST_ exhaust the iterator, as errors may be delayed +/// until the last element. +pub(crate) struct TlsListIter<'a, T: Codec<'a> + TlsListElement + Debug> { + sub: Reader<'a>, + _t: PhantomData, +} + +impl<'a, T: Codec<'a> + TlsListElement + Debug> TlsListIter<'a, T> { + pub(crate) fn new(r: &mut Reader<'a>) -> Result { + let len = T::SIZE_LEN.read(r)?; + let sub = r.sub(len)?; + Ok(Self { + sub, + _t: PhantomData, + }) } +} - fn read(r: &mut Reader<'_>) -> Result { - match r.take(8) { - Some(&[a, b, c, d, e, f, g, h]) => Ok(Self::from_be_bytes([a, b, c, d, e, f, g, h])), - _ => Err(InvalidMessage::MissingData("u64")), +impl<'a, T: Codec<'a> + TlsListElement + Debug> Iterator for TlsListIter<'a, T> { + type Item = Result; + + fn next(&mut self) -> Option { + match self.sub.any_left() { + true => Some(T::read(&mut self.sub)), + false => None, } } } @@ -225,46 +254,15 @@ impl<'a, T: Codec<'a> + TlsListElement + Debug> Codec<'a> for Vec { } fn read(r: &mut Reader<'a>) -> Result { - let len = match T::SIZE_LEN { - ListLength::U8 => usize::from(u8::read(r)?), - ListLength::U16 => usize::from(u16::read(r)?), - ListLength::U24 { max, error } => match usize::from(u24::read(r)?) { - len if len > max => return Err(error), - len => len, - }, - }; - - let mut sub = r.sub(len)?; let mut ret = Self::new(); - while sub.any_left() { - ret.push(T::read(&mut sub)?); + for item in TlsListIter::::new(r)? { + ret.push(item?); } Ok(ret) } } -/// A trait for types that can be encoded and decoded in a list. -/// -/// This trait is used to implement `Codec` for `Vec`. Lists in the TLS wire format are -/// prefixed with a length, the size of which depends on the type of the list elements. -/// As such, the `Codec` implementation for `Vec` requires an implementation of this trait -/// for its element type `T`. -pub(crate) trait TlsListElement { - const SIZE_LEN: ListLength; -} - -/// The length of the length prefix for a list. -/// -/// The types that appear in lists are limited to three kinds of length prefixes: -/// 1, 2, and 3 bytes. For the latter kind, we require a `TlsListElement` implementer -/// to specify a maximum length and error if the actual length is larger. -pub(crate) enum ListLength { - U8, - U16, - U24 { max: usize, error: InvalidMessage }, -} - /// Tracks encoding a length-delimited structure in a single pass. pub(crate) struct LengthPrefixedBuffer<'a> { pub(crate) buf: &'a mut Vec, @@ -280,9 +278,9 @@ impl<'a> LengthPrefixedBuffer<'a> { pub(crate) fn new(size_len: ListLength, buf: &'a mut Vec) -> Self { let len_offset = buf.len(); buf.extend(match size_len { - ListLength::U8 => &[0xff][..], - ListLength::U16 => &[0xff, 0xff], - ListLength::U24 { .. } => &[0xff, 0xff, 0xff], + ListLength::NonZeroU8 { .. } => &[0xff][..], + ListLength::U16 | ListLength::NonZeroU16 { .. } => &[0xff, 0xff], + ListLength::U24 { .. } | ListLength::NonZeroU24 { .. } => &[0xff, 0xff, 0xff], }); Self { @@ -297,12 +295,12 @@ impl Drop for LengthPrefixedBuffer<'_> { /// Goes back and corrects the length previously inserted at the start of the structure. fn drop(&mut self) { match self.size_len { - ListLength::U8 => { + ListLength::NonZeroU8 { .. } => { let len = self.buf.len() - self.len_offset - 1; debug_assert!(len <= 0xff); self.buf[self.len_offset] = len as u8; } - ListLength::U16 => { + ListLength::U16 | ListLength::NonZeroU16 { .. } => { let len = self.buf.len() - self.len_offset - 2; debug_assert!(len <= 0xffff); let out: &mut [u8; 2] = (&mut self.buf[self.len_offset..self.len_offset + 2]) @@ -310,7 +308,7 @@ impl Drop for LengthPrefixedBuffer<'_> { .unwrap(); *out = u16::to_be_bytes(len as u16); } - ListLength::U24 { .. } => { + ListLength::U24 { .. } | ListLength::NonZeroU24 { .. } => { let len = self.buf.len() - self.len_offset - 3; debug_assert!(len <= 0xff_ffff); let len_bytes = u32::to_be_bytes(len as u32); @@ -323,9 +321,291 @@ impl Drop for LengthPrefixedBuffer<'_> { } } +impl Codec<'_> for u8 { + fn encode(&self, bytes: &mut Vec) { + bytes.push(*self); + } + + fn read(r: &mut Reader<'_>) -> Result { + r.take_array("u8").map(|&[byte]| byte) + } +} + +pub(crate) fn put_u16(v: u16, out: &mut [u8]) { + let out: &mut [u8; 2] = (&mut out[..2]).try_into().unwrap(); + *out = u16::to_be_bytes(v); +} + +impl Codec<'_> for u16 { + fn encode(&self, bytes: &mut Vec) { + let mut b16 = [0u8; 2]; + put_u16(*self, &mut b16); + bytes.extend_from_slice(&b16); + } + + fn read(r: &mut Reader<'_>) -> Result { + r.take_array("u16") + .map(|&[b1, b2]| Self::from_be_bytes([b1, b2])) + } +} + +// Make a distinct type for u24, even though it's a u32 underneath +#[derive(Debug, Copy, Clone)] +pub struct U24(pub u32); + +#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] +impl From for usize { + #[inline] + fn from(v: U24) -> Self { + v.0 as Self + } +} + +impl Codec<'_> for U24 { + fn encode(&self, bytes: &mut Vec) { + let be_bytes = u32::to_be_bytes(self.0); + bytes.extend_from_slice(&be_bytes[1..]); + } + + fn read(r: &mut Reader<'_>) -> Result { + r.take_array("u24") + .map(|&[a, b, c]| Self(u32::from_be_bytes([0, a, b, c]))) + } +} + +impl Codec<'_> for u32 { + fn encode(&self, bytes: &mut Vec) { + bytes.extend(Self::to_be_bytes(*self)); + } + + fn read(r: &mut Reader<'_>) -> Result { + r.take_array("u32") + .map(|&[a, b, c, d]| Self::from_be_bytes([a, b, c, d])) + } +} + +pub(crate) fn put_u64(v: u64, bytes: &mut [u8]) { + let bytes: &mut [u8; 8] = (&mut bytes[..8]).try_into().unwrap(); + *bytes = u64::to_be_bytes(v); +} + +impl Codec<'_> for u64 { + fn encode(&self, bytes: &mut Vec) { + let mut b64 = [0u8; 8]; + put_u64(*self, &mut b64); + bytes.extend_from_slice(&b64); + } + + fn read(r: &mut Reader<'_>) -> Result { + r.take_array("u64") + .map(|&[a, b, c, d, e, f, g, h]| Self::from_be_bytes([a, b, c, d, e, f, g, h])) + } +} + +impl Codec<'_> for () { + fn encode(&self, _: &mut Vec) {} + + fn read(r: &mut Reader<'_>) -> Result { + r.expect_empty("Empty") + } +} + +/// Trait for implementing encoding and decoding functionality +/// on something. +pub(crate) trait Codec<'a>: Debug + Sized { + /// Function for encoding itself by appending itself to + /// the provided vec of bytes. + fn encode(&self, bytes: &mut Vec); + + /// Function for decoding itself from the provided reader + /// will return Some if the decoding was successful or + /// None if it was not. + fn read(_: &mut Reader<'a>) -> Result; + + /// Convenience function for encoding the implementation + /// into a vec and returning it + fn get_encoding(&self) -> Vec { + let mut bytes = Vec::new(); + self.encode(&mut bytes); + bytes + } + + /// Function for wrapping a call to the read function in + /// a Reader for the slice of bytes provided + /// + /// Returns `Err(InvalidMessage::ExcessData(_))` if + /// `Self::read` does not read the entirety of `bytes`. + fn read_bytes(bytes: &'a [u8]) -> Result { + let mut reader = Reader::new(bytes); + Self::read(&mut reader).and_then(|r| { + reader.expect_empty("read_bytes")?; + Ok(r) + }) + } +} + +/// Wrapper over a slice of bytes that allows reading chunks from +/// with the current position state held using a cursor. +/// +/// A new reader for a sub section of the buffer can be created +/// using the `sub` function or a section of a certain length can +/// be obtained using the `take` function +pub(crate) struct Reader<'a> { + /// The underlying buffer storing the readers content + buffer: &'a [u8], +} + +impl<'a> Reader<'a> { + /// Creates a new Reader of the provided `bytes` slice. + pub(crate) fn new(buffer: &'a [u8]) -> Self { + Self { buffer } + } + + /// Attempts to create a new Reader on a sub section of this + /// readers bytes by taking a slice of the provided `length` + /// will return None if there is not enough bytes + pub(crate) fn sub(&mut self, length: usize) -> Result { + match self.take(length) { + Some(bytes) => Ok(Reader::new(bytes)), + None => Err(InvalidMessage::MessageTooShort), + } + } + + /// Borrow an array of `N` bytes from the buffer. + /// + /// If there are not enough bytes remaining to take the length `None` is returned instead + pub(crate) fn take_array( + &mut self, + ty: &'static str, + ) -> Result<&'a [u8; N], InvalidMessage> { + match self.buffer.split_first_chunk() { + Some((chunk, rest)) => { + self.buffer = rest; + Ok(chunk) + } + _ => Err(InvalidMessage::MissingData(ty)), + } + } + + /// Borrow a slice of `length` bytes from the buffer. + /// + /// If there are not enough bytes remaining to take the length `None` is returned instead. + pub(crate) fn take(&mut self, length: usize) -> Option<&'a [u8]> { + let (out, rest) = self.buffer.split_at_checked(length)?; + self.buffer = rest; + Some(out) + } + + /// Borrows a slice of all the remaining bytes. + /// + /// Moves the cursor to the end of the buffer length. + pub(crate) fn rest(&mut self) -> &'a [u8] { + mem::take(&mut self.buffer) + } + + pub(crate) fn expect_empty(&self, name: &'static str) -> Result<(), InvalidMessage> { + match self.any_left() { + true => Err(InvalidMessage::TrailingData(name)), + false => Ok(()), + } + } + + /// Whether the reader has any content left. + pub(crate) fn any_left(&self) -> bool { + !self.buffer.is_empty() + } + + /// Number of bytes that are still able to be read. + pub(crate) fn left(&self) -> usize { + self.buffer.len() + } +} + +/// A trait for types that can be encoded and decoded in a list. +/// +/// This trait is used to implement `Codec` for `Vec`. Lists in the TLS wire format are +/// prefixed with a length, the size of which depends on the type of the list elements. +/// As such, the `Codec` implementation for `Vec` requires an implementation of this trait +/// for its element type `T`. +pub(crate) trait TlsListElement { + const SIZE_LEN: ListLength; +} + +/// The length of the length prefix for a list. +/// +/// The types that appear in lists are limited to three kinds of length prefixes: +/// 1, 2, and 3 bytes. For the latter kind, we require a `TlsListElement` implementer +/// to specify a maximum length and error if the actual length is larger. +pub(crate) enum ListLength { + /// U8 but non-empty + NonZeroU8 { empty_error: InvalidMessage }, + + /// U16, perhaps empty + U16, + + /// U16 but non-empty + NonZeroU16 { empty_error: InvalidMessage }, + + /// U24 with imposed upper bound + U24 { max: usize, error: InvalidMessage }, + + /// U24 but non-empty, with imposed upper bound + NonZeroU24 { + max: usize, + empty_error: InvalidMessage, + too_many_error: InvalidMessage, + }, +} + +impl ListLength { + pub(crate) fn read(&self, r: &mut Reader<'_>) -> Result { + Ok(match self { + Self::NonZeroU8 { empty_error } => match usize::from(u8::read(r)?) { + 0 => return Err(*empty_error), + len => len, + }, + Self::U16 => usize::from(u16::read(r)?), + Self::NonZeroU16 { empty_error } => match usize::from(u16::read(r)?) { + 0 => return Err(*empty_error), + len => len, + }, + Self::U24 { max, error } => match usize::from(U24::read(r)?) { + len if len > *max => return Err(*error), + len => len, + }, + Self::NonZeroU24 { + max, + empty_error, + too_many_error, + } => match usize::from(U24::read(r)?) { + 0 => return Err(*empty_error), + len if len > *max => return Err(*too_many_error), + len => len, + }, + }) + } +} + +// Format an iterator of u8 into a hex string +pub(crate) fn hex<'a>( + f: &mut fmt::Formatter<'_>, + payload: impl IntoIterator, +) -> fmt::Result { + for b in payload { + write!(f, "{b:02x}")?; + } + Ok(()) +} + +/// TLS has a 16MB size limit on any handshake message, +/// plus a 16MB limit on any given certificate. +/// +/// We contract that to 64KB to limit the amount of memory allocation +/// that is directly controllable by the peer. +pub(crate) const CERTIFICATE_MAX_SIZE_LIMIT: usize = 0x1_0000; + #[cfg(test)] mod tests { - use std::prelude::v1::*; use std::vec; use super::*; diff --git a/rustls/src/msgs/deframer/buffers.rs b/rustls/src/msgs/deframer/buffers.rs index bf098740700..67d5afb7582 100644 --- a/rustls/src/msgs/deframer/buffers.rs +++ b/rustls/src/msgs/deframer/buffers.rs @@ -1,167 +1,22 @@ use alloc::vec::Vec; -use core::mem; use core::ops::Range; -#[cfg(feature = "std")] use std::io; -#[cfg(feature = "std")] -use crate::msgs::message::MAX_WIRE_SIZE; - -/// Conversion from a slice within a larger buffer into -/// a `Range` offset within. -#[derive(Debug)] -pub(crate) struct Locator { - bounds: Range<*const u8>, -} - -impl Locator { - #[inline] - pub(crate) fn new(slice: &[u8]) -> Self { - Self { - bounds: slice.as_ptr_range(), - } - } - - #[inline] - pub(crate) fn locate(&self, slice: &[u8]) -> Range { - let bounds = slice.as_ptr_range(); - debug_assert!(self.fully_contains(slice)); - let start = bounds.start as usize - self.bounds.start as usize; - let len = bounds.end as usize - bounds.start as usize; - Range { - start, - end: start + len, - } - } - - #[inline] - pub(crate) fn fully_contains(&self, slice: &[u8]) -> bool { - let bounds = slice.as_ptr_range(); - bounds.start >= self.bounds.start && bounds.end <= self.bounds.end - } -} - -/// Conversion from a `Range` offset to the original slice. -pub(crate) struct Delocator<'b> { - slice: &'b [u8], -} - -impl<'b> Delocator<'b> { - #[inline] - pub(crate) fn new(slice: &'b [u8]) -> Self { - Self { slice } - } - - #[inline] - pub(crate) fn slice_from_range(&'_ self, range: &Range) -> &'b [u8] { - // safety: this unwrap is safe so long as `range` came from `locate()` - // for the same buffer - self.slice.get(range.clone()).unwrap() - } - - #[inline] - pub(crate) fn locator(self) -> Locator { - Locator::new(self.slice) - } -} - -/// Reordering the underlying buffer based on ranges. -pub(crate) struct Coalescer<'b> { - slice: &'b mut [u8], -} - -impl<'b> Coalescer<'b> { - #[inline] - pub(crate) fn new(slice: &'b mut [u8]) -> Self { - Self { slice } - } - - #[inline] - pub(crate) fn copy_within(&mut self, from: Range, to: Range) { - debug_assert!(from.len() == to.len()); - debug_assert!(self.slice.get(from.clone()).is_some()); - debug_assert!(self.slice.get(to.clone()).is_some()); - self.slice.copy_within(from, to.start); - } - - #[inline] - pub(crate) fn delocator(self) -> Delocator<'b> { - Delocator::new(self.slice) - } -} - -/// Accounting structure tracking progress in parsing a single buffer. -#[derive(Clone, Debug)] -pub(crate) struct BufferProgress { - /// Prefix of the buffer that has been processed so far. - /// - /// `processed` may exceed `discard`, that means we have parsed - /// some buffer, but are still using it. This happens due to - /// in-place decryption of incoming records, and in-place - /// reassembly of handshake messages. - /// - /// 0 <= processed <= len - processed: usize, - - /// Prefix of the buffer that can be removed. - /// - /// If `discard` exceeds `processed`, that means we are ignoring - /// data without processing it. - /// - /// 0 <= discard <= len - discard: usize, -} - -impl BufferProgress { - pub(super) fn new(processed: usize) -> Self { - Self { - processed, - discard: 0, - } - } - - #[inline] - pub(crate) fn add_discard(&mut self, discard: usize) { - self.discard += discard; - } - - #[inline] - pub(crate) fn add_processed(&mut self, processed: usize) { - self.processed += processed; - } - - #[inline] - pub(crate) fn take_discard(&mut self) -> usize { - // the caller is about to discard `discard` bytes - // from the front of the buffer. adjust `processed` - // down by the same amount. - self.processed = self - .processed - .saturating_sub(self.discard); - mem::take(&mut self.discard) - } - - #[inline] - pub(crate) fn processed(&self) -> usize { - self.processed - } -} - #[derive(Default, Debug)] -pub(crate) struct DeframerVecBuffer { +#[expect(unreachable_pub)] +pub struct VecInput { /// Buffer of data read from the socket, in the process of being parsed into messages. /// - /// For buffer size management, checkout out the [`DeframerVecBuffer::prepare_read()`] method. + /// For buffer size management, checkout out the [`VecInput::prepare_read()`] method. buf: Vec, /// What size prefix of `buf` is used. used: usize, } -impl DeframerVecBuffer { +impl VecInput { /// Discard `taken` bytes from the start of our buffer. pub(crate) fn discard(&mut self, taken: usize) { - #[allow(clippy::comparison_chain)] if taken < self.used { /* Before: * +----------+----------+----------+ @@ -188,16 +43,9 @@ impl DeframerVecBuffer { &mut self.buf[..self.used] } - pub(crate) fn filled(&self) -> &[u8] { - &self.buf[..self.used] - } -} - -#[cfg(feature = "std")] -impl DeframerVecBuffer { /// Read some bytes from `rd`, and add them to the buffer. - pub(crate) fn read(&mut self, rd: &mut dyn io::Read, in_handshake: bool) -> io::Result { - if let Err(err) = self.prepare_read(in_handshake) { + pub(crate) fn read(&mut self, rd: &mut dyn io::Read) -> io::Result { + if let Err(err) = self.prepare_read() { return Err(io::Error::new(io::ErrorKind::InvalidData, err)); } @@ -211,39 +59,32 @@ impl DeframerVecBuffer { } /// Resize the internal `buf` if necessary for reading more bytes. - fn prepare_read(&mut self, is_joining_hs: bool) -> Result<(), &'static str> { + fn prepare_read(&mut self) -> Result<(), &'static str> { /// TLS allows for handshake messages of up to 16MB. We /// restrict that to 64KB to limit potential for denial-of- /// service. - const MAX_HANDSHAKE_SIZE: u32 = 0xffff; + const MAX_HANDSHAKE_SIZE: usize = 0xffff; const READ_SIZE: usize = 4096; - // We allow a maximum of 64k of buffered data for handshake messages only. Enforce this - // by varying the maximum allowed buffer size here based on whether a prefix of a - // handshake payload is currently being buffered. Given that the first read of such a + // We allow a maximum of 64k of buffered data. Given that the first read of such a // payload will only ever be 4k bytes, the next time we come around here we allow a // larger buffer size. Once the large message and any following handshake messages in // the same flight have been consumed, `pop()` will call `discard()` to reset `used`. // At this point, the buffer resizing logic below should reduce the buffer size. - let allow_max = match is_joining_hs { - true => MAX_HANDSHAKE_SIZE as usize, - false => MAX_WIRE_SIZE, - }; - - if self.used >= allow_max { + if self.used >= MAX_HANDSHAKE_SIZE { return Err("message buffer full"); } // If we can and need to increase the buffer size to allow a 4k read, do so. After - // dealing with a large handshake message (exceeding `OutboundOpaqueMessage::MAX_WIRE_SIZE`), + // dealing with a large handshake message (exceeding `MAX_WIRE_SIZE`), // make sure to reduce the buffer size again (large messages should be rare). // Also, reduce the buffer size if there are neither full nor partial messages in it, // which usually means that the other side suspended sending data. - let need_capacity = Ord::min(allow_max, self.used + READ_SIZE); + let need_capacity = Ord::min(MAX_HANDSHAKE_SIZE, self.used + READ_SIZE); if need_capacity > self.buf.len() { self.buf.resize(need_capacity, 0); - } else if self.used == 0 || self.buf.len() > allow_max { + } else if self.used == 0 || self.buf.len() > MAX_HANDSHAKE_SIZE { self.buf.resize(need_capacity, 0); self.buf.shrink_to(need_capacity); } @@ -265,32 +106,157 @@ impl DeframerVecBuffer { self.used += len; Range { start, end } } + + pub(crate) fn filled(&self) -> &[u8] { + &self.buf[..self.used] + } } -/// A borrowed version of [`DeframerVecBuffer`] that tracks discard operations +impl TlsInputBuffer for VecInput { + fn slice_mut(&mut self) -> &mut [u8] { + self.filled_mut() + } + + fn discard(&mut self, num_bytes: usize) { + self.discard(num_bytes) + } +} + +/// A borrowed version of [`VecInput`] that tracks discard operations #[derive(Debug)] -pub(crate) struct DeframerSliceBuffer<'a> { +#[expect(unreachable_pub)] +pub struct SliceInput<'a> { // a fully initialized buffer that will be deframed buf: &'a mut [u8], // number of bytes to discard from the front of `buf` at a later time discard: usize, } -impl<'a> DeframerSliceBuffer<'a> { - pub(crate) fn new(buf: &'a mut [u8]) -> Self { +#[expect(dead_code, unreachable_pub)] +impl<'a> SliceInput<'a> { + pub fn new(buf: &'a mut [u8]) -> Self { Self { buf, discard: 0 } } - /// Tracks a pending discard operation of `num_bytes` - pub(crate) fn queue_discard(&mut self, num_bytes: usize) { + /// Returns how many bytes were consumed at the start of the original buffer. + pub fn into_used(self) -> usize { + self.discard + } +} + +impl TlsInputBuffer for SliceInput<'_> { + fn slice_mut(&mut self) -> &mut [u8] { + &mut self.buf[self.discard..] + } + + fn discard(&mut self, num_bytes: usize) { self.discard += num_bytes; } +} - pub(crate) fn pending_discard(&self) -> usize { - self.discard +/// An abstraction over received data buffers (either owned or borrowed) +#[expect(unreachable_pub)] +pub trait TlsInputBuffer { + /// Return the buffer which contains the received data. + /// + /// If no data is available, return the empty slice. + /// + /// This is mutable, because the buffer is used for in-place decryption + /// and coalescing of TLS records. Coalescing of TLS records can happen + /// incrementally over multiple calls into rustls. As a result the + /// contents of this buffer must not be altered except to add new bytes + /// at the end. + fn slice_mut(&mut self) -> &mut [u8]; + + /// Discard `num_bytes` from the front of the buffer returned by `slice_mut()`. + /// + /// Multiple calls to `discard()` are cumulative, rather than "last wins". In + /// other words, `discard(1)` followed by `discard(1)` gives the same result + /// as `discard(2)`. + /// + /// The next call to `slice_mut()` must reflect all previous `discard()`s. In + /// other words, if `slice_mut()` returns slice `[p..q]`, it should then + /// return `[p+n..q]` after `discard(n)`. + /// + /// Rustls guarantees it will not `discard()` more bytes than are returned + /// from `slice_mut()`. + fn discard(&mut self, num_bytes: usize); +} + +/// Reordering the underlying buffer based on ranges. +pub(crate) struct Coalescer<'b> { + slice: &'b mut [u8], +} + +impl<'b> Coalescer<'b> { + #[inline] + pub(crate) fn new(slice: &'b mut [u8]) -> Self { + Self { slice } } - pub(crate) fn filled_mut(&mut self) -> &mut [u8] { - &mut self.buf[self.discard..] + #[inline] + pub(crate) fn copy_within(&mut self, from: Range, to: Range) { + debug_assert!(from.len() == to.len()); + debug_assert!(self.slice.get(from.clone()).is_some()); + debug_assert!(self.slice.get(to.clone()).is_some()); + self.slice.copy_within(from, to.start); + } + + #[inline] + pub(crate) fn delocator(self) -> Delocator<'b> { + Delocator::new(self.slice) + } +} + +/// Conversion from a `Range` offset to the original slice. +pub(crate) struct Delocator<'b> { + slice: &'b [u8], +} + +impl<'b> Delocator<'b> { + #[inline] + pub(crate) fn new(slice: &'b [u8]) -> Self { + Self { slice } + } + + #[inline] + pub(crate) fn slice_from_range(&'_ self, range: &Range) -> &'b [u8] { + // safety: this unwrap is safe so long as `range` came from `locate()` + // for the same buffer + self.slice.get(range.clone()).unwrap() + } +} + +/// Conversion from a slice within a larger buffer into +/// a `Range` offset within. +#[derive(Debug)] +pub(crate) struct Locator { + bounds: Range<*const u8>, +} + +impl Locator { + #[inline] + pub(crate) fn new(slice: &[u8]) -> Self { + Self { + bounds: slice.as_ptr_range(), + } + } + + #[inline] + pub(crate) fn locate(&self, slice: &[u8]) -> Range { + let bounds = slice.as_ptr_range(); + debug_assert!(self.fully_contains(slice)); + let start = bounds.start as usize - self.bounds.start as usize; + let len = bounds.end as usize - bounds.start as usize; + Range { + start, + end: start + len, + } + } + + #[inline] + pub(crate) fn fully_contains(&self, slice: &[u8]) -> bool { + let bounds = slice.as_ptr_range(); + bounds.start >= self.bounds.start && bounds.end <= self.bounds.end } } diff --git a/rustls/src/msgs/deframer/handshake.rs b/rustls/src/msgs/deframer/handshake.rs deleted file mode 100644 index c781e9309cd..00000000000 --- a/rustls/src/msgs/deframer/handshake.rs +++ /dev/null @@ -1,525 +0,0 @@ -use alloc::vec::Vec; -use core::mem; -use core::ops::Range; - -use super::buffers::{BufferProgress, Coalescer, Delocator, Locator}; -use crate::error::InvalidMessage; -use crate::msgs::codec::{u24, Codec}; -use crate::msgs::message::InboundPlainMessage; -use crate::{ContentType, ProtocolVersion}; - -#[derive(Debug)] -pub(crate) struct HandshakeDeframer { - /// Spans covering individual handshake payloads, in order of receipt. - spans: Vec, - - /// Discard value, tracking the rightmost extent of the last message - /// in `spans`. - outer_discard: usize, -} - -impl HandshakeDeframer { - /// Accepts a message into the deframer. - /// - /// `containing_buffer` allows mapping the message payload to its position - /// in the input buffer, and thereby avoid retaining a borrow on the input - /// buffer. - /// - /// That is required because our processing of handshake messages requires - /// them to be contiguous (and avoiding that would mean supporting gather-based - /// parsing in a large number of places, including `core`, `webpki`, and the - /// `CryptoProvider` interface). `coalesce()` arranges for that to happen, but - /// to do so it needs to move the fragments together in the original buffer. - /// This would not be possible if the messages were borrowing from that buffer. - /// - /// `outer_discard` is the rightmost extent of the original message. - pub(crate) fn input_message( - &mut self, - msg: InboundPlainMessage<'_>, - containing_buffer: &Locator, - outer_discard: usize, - ) { - debug_assert_eq!(msg.typ, ContentType::Handshake); - debug_assert!(containing_buffer.fully_contains(msg.payload)); - debug_assert!(self.outer_discard <= outer_discard); - - self.outer_discard = outer_discard; - - // if our last span is incomplete, we can blindly add this as a new span -- - // no need to attempt parsing it with `DissectHandshakeIter`. - // - // `coalesce()` will later move this new message to be contiguous with - // `_last_incomplete`, and reparse the result. - // - // we cannot merge these processes, because `coalesce` mutates the underlying - // buffer, and `msg` borrows it. - if let Some(_last_incomplete) = self - .spans - .last() - .filter(|span| !span.is_complete()) - { - self.spans.push(FragmentSpan { - version: msg.version, - size: None, - bounds: containing_buffer.locate(msg.payload), - }); - return; - } - - // otherwise, we can expect `msg` to contain a handshake header introducing - // a new message (and perhaps several of them.) - for span in DissectHandshakeIter::new(msg, containing_buffer) { - self.spans.push(span); - } - } - - /// Returns a `BufferProgress` that skips over unprocessed handshake data. - pub(crate) fn progress(&self) -> BufferProgress { - BufferProgress::new(self.outer_discard) - } - - /// Do we have a message ready? ie, would `iter().next()` return `Some`? - pub(crate) fn has_message_ready(&self) -> bool { - match self.spans.first() { - Some(span) => span.is_complete(), - None => false, - } - } - - /// Do we have any message data, partial or otherwise? - pub(crate) fn is_active(&self) -> bool { - !self.spans.is_empty() - } - - /// We are "aligned" if there is no partial fragment of a handshake - /// message. - pub(crate) fn is_aligned(&self) -> bool { - self.spans - .iter() - .all(|span| span.is_complete()) - } - - /// Iterate over the complete messages. - pub(crate) fn iter<'a, 'b>(&'a mut self, containing_buffer: &'b [u8]) -> HandshakeIter<'a, 'b> { - HandshakeIter { - deframer: self, - containing_buffer: Delocator::new(containing_buffer), - index: 0, - } - } - - /// Coalesce the handshake portions of the given buffer, - /// if needed. - /// - /// This does nothing if there is nothing to do. - /// - /// In a normal TLS stream, handshake messages need not be contiguous. - /// For example, each handshake message could be delivered in its own - /// outer TLS message. This would mean the handshake messages are - /// separated by the outer TLS message headers, and likely also - /// separated by encryption overhead (any explicit nonce in front, - /// any padding and authentication tag afterwards). - /// - /// For a toy example of one handshake message in two fragments, and: - /// - /// - the letter `h` for handshake header octets - /// - the letter `H` for handshake payload octets - /// - the letter `x` for octets in the buffer ignored by this code, - /// - /// the buffer and `spans` data structure could look like: - /// - /// ```text - /// 0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 - /// x x x x x h h h h H H H x x x x x H H H H H H x x x - /// '------------' '----------' - /// | | - /// spans = [ { bounds = (5, 12), | - /// size = Some(9), .. }, | - /// { bounds = (17, 23), .. } ] - /// ``` - /// - /// In this case, `requires_coalesce` returns `Some(0)`. Then - /// `coalesce_one` moves the second range leftwards: - /// - /// ```text - /// 0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 - /// x x x x x h h h h H H H x x x x x H H H H H H x x x - /// '----------' - /// ^ '----------' - /// | v - /// '--<---<--' - /// copy_within(from = (17, 23), - /// to = (12, 18)) - /// ``` - /// - /// Leaving the buffer and spans: - /// - /// ```text - /// 0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 - /// x x x x x h h h h H H H H H H H H H x x x x x x x x - /// '------------------------' - /// | - /// spans = [ { bounds = (5, 18), size = Some(9), .. } ] - /// ``` - pub(crate) fn coalesce(&mut self, containing_buffer: &mut [u8]) -> Result<(), InvalidMessage> { - // Strategy: while there is work to do, scan `spans` - // for a pair where the first is not complete. move - // the second down towards the first, then reparse the contents. - while let Some(i) = self.requires_coalesce() { - self.coalesce_one(i, Coalescer::new(containing_buffer)); - } - - // check resulting spans pass our imposed length limit - match self - .spans - .iter() - .any(|span| span.size.unwrap_or_default() > MAX_HANDSHAKE_SIZE) - { - true => Err(InvalidMessage::HandshakePayloadTooLarge), - false => Ok(()), - } - } - - /// Within `containing_buffer`, move `span[index+1]` to be contiguous - /// with `span[index]`. - fn coalesce_one(&mut self, index: usize, mut containing_buffer: Coalescer<'_>) { - let second = self.spans.remove(index + 1); - let mut first = self.spans.remove(index); - - // move the entirety of `second` to be contiguous with `first` - let len = second.bounds.len(); - let target = Range { - start: first.bounds.end, - end: first.bounds.end + len, - }; - - containing_buffer.copy_within(second.bounds, target); - let delocator = containing_buffer.delocator(); - - // now adjust `first` to cover both - first.bounds.end += len; - - // finally, attempt to re-dissect `first` - let msg = InboundPlainMessage { - typ: ContentType::Handshake, - version: first.version, - payload: delocator.slice_from_range(&first.bounds), - }; - - for (i, span) in DissectHandshakeIter::new(msg, &delocator.locator()).enumerate() { - self.spans.insert(index + i, span); - } - } - - /// We require coalescing if any span except the last is not complete. - /// - /// Returns an index into `spans` for the first non-complete span: - /// this will never be the last item. - fn requires_coalesce(&self) -> Option { - self.spans - .split_last() - .and_then(|(_last, elements)| { - elements - .iter() - .enumerate() - .find_map(|(i, span)| (!span.is_complete()).then_some(i)) - }) - } -} - -impl Default for HandshakeDeframer { - fn default() -> Self { - Self { - // capacity: a typical upper limit on handshake messages in - // a single flight - spans: Vec::with_capacity(16), - outer_discard: 0, - } - } -} - -struct DissectHandshakeIter<'a, 'b> { - version: ProtocolVersion, - payload: &'b [u8], - containing_buffer: &'a Locator, -} - -impl<'a, 'b> DissectHandshakeIter<'a, 'b> { - fn new(msg: InboundPlainMessage<'b>, containing_buffer: &'a Locator) -> Self { - Self { - version: msg.version, - payload: msg.payload, - containing_buffer, - } - } -} - -impl Iterator for DissectHandshakeIter<'_, '_> { - type Item = FragmentSpan; - - fn next(&mut self) -> Option { - if self.payload.is_empty() { - return None; - } - - // If there is not enough data to have a header the length is unknown - if self.payload.len() < HANDSHAKE_HEADER_LEN { - let buf = mem::take(&mut self.payload); - let bounds = self.containing_buffer.locate(buf); - return Some(FragmentSpan { - version: self.version, - size: None, - bounds: bounds.clone(), - }); - } - - let (header, rest) = mem::take(&mut self.payload).split_at(HANDSHAKE_HEADER_LEN); - - // safety: header[1..] is exactly 3 bytes, so `u24::read_bytes` cannot fail - let size = u24::read_bytes(&header[1..]) - .unwrap() - .into(); - - let available = if size < rest.len() { - self.payload = &rest[size..]; - size - } else { - rest.len() - }; - - let mut bounds = self.containing_buffer.locate(header); - bounds.end += available; - Some(FragmentSpan { - version: self.version, - size: Some(size), - bounds: bounds.clone(), - }) - } -} - -pub(crate) struct HandshakeIter<'a, 'b> { - deframer: &'a mut HandshakeDeframer, - containing_buffer: Delocator<'b>, - index: usize, -} - -impl<'b> Iterator for HandshakeIter<'_, 'b> { - type Item = (InboundPlainMessage<'b>, usize); - - fn next(&mut self) -> Option { - let next_span = self.deframer.spans.get(self.index)?; - - if !next_span.is_complete() { - return None; - } - - // if this is the last handshake message, then we'll end - // up with an empty `spans` and can discard the remainder - // of the input buffer. - let discard = if self.deframer.spans.len() - 1 == self.index { - mem::take(&mut self.deframer.outer_discard) - } else { - 0 - }; - - self.index += 1; - Some(( - InboundPlainMessage { - typ: ContentType::Handshake, - version: next_span.version, - payload: self - .containing_buffer - .slice_from_range(&next_span.bounds), - }, - discard, - )) - } -} - -impl Drop for HandshakeIter<'_, '_> { - fn drop(&mut self) { - self.deframer.spans.drain(..self.index); - } -} - -#[derive(Debug)] -struct FragmentSpan { - /// version taken from containing message. - version: ProtocolVersion, - - /// size of the handshake message body (excluding header) - /// - /// `None` means the size is unknown, because `bounds` is not - /// large enough to encompass a whole header. - size: Option, - - /// bounds of the handshake message, including header - bounds: Range, -} - -impl FragmentSpan { - /// A `FragmentSpan` is "complete" if its size is known, and its - /// bounds exactly encompasses one handshake message. - fn is_complete(&self) -> bool { - match self.size { - Some(sz) => sz + HANDSHAKE_HEADER_LEN == self.bounds.len(), - None => false, - } - } -} - -const HANDSHAKE_HEADER_LEN: usize = 1 + 3; - -/// TLS allows for handshake messages of up to 16MB. We -/// restrict that to 64KB to limit potential for denial-of- -/// service. -const MAX_HANDSHAKE_SIZE: usize = 0xffff; - -#[cfg(test)] -mod tests { - use std::vec; - - use super::*; - use crate::msgs::deframer::DeframerIter; - - fn add_bytes(hs: &mut HandshakeDeframer, slice: &[u8], within: &[u8]) { - let msg = InboundPlainMessage { - typ: ContentType::Handshake, - version: ProtocolVersion::TLSv1_3, - payload: slice, - }; - let locator = Locator::new(within); - let discard = locator.locate(slice).end; - hs.input_message(msg, &locator, discard); - } - - #[test] - fn coalesce() { - let mut input = vec![0, 0, 0, 0x21, 0, 0, 0, 0, 0x01, 0xff, 0x00, 0x01]; - let mut hs = HandshakeDeframer::default(); - - add_bytes(&mut hs, &input[3..4], &input); - assert_eq!(hs.requires_coalesce(), None); - add_bytes(&mut hs, &input[4..6], &input); - assert_eq!(hs.requires_coalesce(), Some(0)); - add_bytes(&mut hs, &input[8..10], &input); - assert_eq!(hs.requires_coalesce(), Some(0)); - - std::println!("before: {hs:?}"); - hs.coalesce(&mut input).unwrap(); - std::println!("after: {hs:?}"); - - let (msg, discard) = hs.iter(&input).next().unwrap(); - std::println!("msg {msg:?} discard {discard:?}"); - assert_eq!(msg.typ, ContentType::Handshake); - assert_eq!(msg.version, ProtocolVersion::TLSv1_3); - assert_eq!(msg.payload, &[0x21, 0x00, 0x00, 0x01, 0xff]); - - input.drain(..discard); - assert_eq!(input, &[0, 1]); - } - - #[test] - fn append() { - let mut input = vec![0, 0, 0, 0x21, 0, 0, 5, 0, 0, 1, 2, 3, 4, 5, 0]; - let mut hs = HandshakeDeframer::default(); - - add_bytes(&mut hs, &input[3..7], &input); - add_bytes(&mut hs, &input[9..14], &input); - assert_eq!(hs.spans.len(), 2); - - hs.coalesce(&mut input).unwrap(); - assert_eq!(hs.spans.len(), 1); - - let (msg, discard) = std::dbg!(hs.iter(&input).next().unwrap()); - assert_eq!(msg.typ, ContentType::Handshake); - assert_eq!(msg.version, ProtocolVersion::TLSv1_3); - assert_eq!(msg.payload, &[0x21, 0x00, 0x00, 0x05, 1, 2, 3, 4, 5]); - - input.drain(..discard); - assert_eq!(input, &[0]); - } - - #[test] - fn coalesce_rejects_excess_size_message() { - const X: u8 = 0xff; - let mut input = vec![0x21, 0x01, 0x00, X, 0x00, 0xab, X]; - let mut hs = HandshakeDeframer::default(); - - // split header over multiple messages, which motivates doing - // this check in `coalesce()` - add_bytes(&mut hs, &input[0..3], &input); - add_bytes(&mut hs, &input[4..6], &input); - - assert_eq!( - hs.coalesce(&mut input), - Err(InvalidMessage::HandshakePayloadTooLarge) - ); - } - - #[test] - fn iter_only_returns_full_messages() { - let input = [0, 0, 0, 0x21, 0, 0, 1, 0xab, 0x21, 0, 0, 1]; - - let mut hs = HandshakeDeframer::default(); - - add_bytes(&mut hs, &input[3..8], &input); - add_bytes(&mut hs, &input[8..12], &input); - - let mut iter = hs.iter(&input); - let (msg, discard) = iter.next().unwrap(); - assert!(iter.next().is_none()); - - assert_eq!(msg.typ, ContentType::Handshake); - assert_eq!(msg.version, ProtocolVersion::TLSv1_3); - assert_eq!(msg.payload, &[0x21, 0x00, 0x00, 0x01, 0xab]); - assert_eq!(discard, 0); - } - - #[test] - fn handshake_flight() { - // intended to be a realistic example - let mut input = include_bytes!("../../testdata/handshake-test.1.bin").to_vec(); - let locator = Locator::new(&input); - - let mut hs = HandshakeDeframer::default(); - - let mut iter = DeframerIter::new(&mut input[..]); - - while let Some(message) = iter.next() { - let plain = message.unwrap().into_plain_message(); - std::println!("message {plain:?}"); - - hs.input_message(plain, &locator, iter.bytes_consumed()); - } - - hs.coalesce(&mut input[..]).unwrap(); - - let mut iter = hs.iter(&input[..]); - for _ in 0..4 { - let (msg, discard) = iter.next().unwrap(); - assert!(matches!( - msg, - InboundPlainMessage { - typ: ContentType::Handshake, - .. - } - )); - assert_eq!(discard, 0); - } - - let (msg, discard) = iter.next().unwrap(); - assert!(matches!( - msg, - InboundPlainMessage { - typ: ContentType::Handshake, - .. - } - )); - assert_eq!(discard, 4280); - drop(iter); - - input.drain(0..discard); - assert!(input.is_empty()); - } -} diff --git a/rustls/src/msgs/deframer/mod.rs b/rustls/src/msgs/deframer/mod.rs index 2f44962c779..a36b5c06593 100644 --- a/rustls/src/msgs/deframer/mod.rs +++ b/rustls/src/msgs/deframer/mod.rs @@ -1,52 +1,56 @@ use core::mem; +use core::ops::Range; +use std::collections::VecDeque; +use crate::crypto::cipher::{EncodedMessage, InboundOpaque, MessageError}; +use crate::enums::{ContentType, ProtocolVersion}; use crate::error::{Error, InvalidMessage}; -use crate::msgs::codec::Reader; -use crate::msgs::message::{ - read_opaque_message_header, InboundOpaqueMessage, MessageError, HEADER_SIZE, -}; +use crate::msgs::codec::{Codec, Reader, U24}; +use crate::msgs::{HEADER_SIZE, read_opaque_message_header}; -pub(crate) mod buffers; -pub(crate) mod handshake; +mod buffers; +use buffers::Coalescer; +pub(crate) use buffers::{Delocator, Locator, TlsInputBuffer, VecInput}; -/// A deframer of TLS wire messages. -/// -/// Returns `Some(Ok(_))` containing each `InboundOpaqueMessage` deframed -/// from the buffer. -/// -/// Returns `None` if no further messages can be deframed from the -/// buffer. More data is required for further progress. -/// -/// Returns `Some(Err(_))` if the peer is not talking TLS, but some -/// other protocol. The caller should abort the connection, because -/// the deframer cannot recover. -/// -/// Call `bytes_consumed()` to learn how many bytes the iterator has -/// processed from the front of the original buffer. This is only updated -/// when a message is successfully deframed (ie. `Some(Ok(_))` is returned). -pub(crate) struct DeframerIter<'a> { - buf: &'a mut [u8], - consumed: usize, -} - -impl<'a> DeframerIter<'a> { - /// Make a new `DeframerIter` - pub(crate) fn new(buf: &'a mut [u8]) -> Self { - Self { buf, consumed: 0 } +pub fn fuzz_deframer(data: &[u8]) { + let mut buf = data.to_vec(); + let mut deframer = Deframer::default(); + while let Some(result) = deframer.deframe(&mut buf) { + if result.is_err() { + break; + } } - /// How many bytes were processed successfully from the front - /// of the buffer passed to `new()`? - pub(crate) fn bytes_consumed(&self) -> usize { - self.consumed - } + assert!(deframer.processed() <= buf.len()); } -impl<'a> Iterator for DeframerIter<'a> { - type Item = Result, Error>; +#[derive(Debug)] +pub(crate) struct Deframer { + /// Spans covering individual handshake payloads, in order of receipt. + spans: VecDeque, - fn next(&mut self) -> Option { - let mut reader = Reader::init(self.buf); + /// Prefix of the buffer that has been processed so far. + /// + /// `processed` may exceed `discard`, that means we have parsed + /// some buffer, but are still using it. This happens due to + /// in-place decryption of incoming records, and in-place + /// reassembly of handshake messages. + /// + /// 0 <= processed <= len + processed: usize, + + /// Prefix of the buffer that can be removed. + /// + /// If `discard` exceeds `processed`, that means we are ignoring + /// data without processing it. + /// + /// 0 <= discard <= len + discard: usize, +} + +impl Deframer { + pub(crate) fn deframe<'a>(&mut self, buf: &'a mut [u8]) -> Option, Error>> { + let mut reader = Reader::new(buf.get(self.processed..)?); let (typ, version, len) = match read_opaque_message_header(&mut reader) { Ok(header) => header, @@ -64,78 +68,569 @@ impl<'a> Iterator for DeframerIter<'a> { } }; - let end = HEADER_SIZE + len as usize; - - self.buf.get(HEADER_SIZE..end)?; - // we now have a TLS header and body on the front of `self.buf`. remove // it from the front. - let (consumed, remainder) = mem::take(&mut self.buf).split_at_mut(end); - self.buf = remainder; - self.consumed += end; + let end = self.processed + HEADER_SIZE + len as usize; + let head = buf.get_mut(..end)?; + let bounds = self.processed..end; + self.processed = end; + + Some(Ok(Deframed { + message: EncodedMessage { + typ, + version, + payload: InboundOpaque(&mut head[bounds.start + HEADER_SIZE..]), + }, + bounds, + })) + } + + /// Accepts a message into the deframer. + /// + /// `containing_buffer` allows mapping the message payload to its position + /// in the input buffer, and thereby avoid retaining a borrow on the input + /// buffer. + /// + /// That is required because our processing of handshake messages requires + /// them to be contiguous (and avoiding that would mean supporting gather-based + /// parsing in a large number of places, including `core`, `webpki`, and the + /// `CryptoProvider` interface). `coalesce()` arranges for that to happen, but + /// to do so it needs to move the fragments together in the original buffer. + /// This would not be possible if the messages were borrowing from that buffer. + pub(crate) fn input_message(&mut self, msg: EncodedMessage<&'_ [u8]>, bounds: Range) { + debug_assert_eq!(msg.typ, ContentType::Handshake); + + // if our last span is incomplete, we can blindly add this as a new span -- + // no need to attempt parsing it with `DissectHandshakeIter`. + // + // `coalesce()` will later move this new message to be contiguous with + // `_last_incomplete`, and reparse the result. + // + // we cannot merge these processes, because `coalesce` mutates the underlying + // buffer, and `msg` borrows it. + if let Some(_last_incomplete) = self + .spans + .back() + .filter(|span| !span.is_complete()) + { + self.spans.push_back(FragmentSpan { + version: msg.version, + size: None, + bounds, + }); + return; + } + + // otherwise, we can expect `msg` to contain a handshake header introducing + // a new message (and perhaps several of them.) + for span in DissectHandshakeIter::new(msg, bounds) { + self.spans.push_back(span); + } + } + + /// Coalesce the handshake portions of the given buffer, + /// if needed. + /// + /// This does nothing if there is nothing to do. + /// + /// In a normal TLS stream, handshake messages need not be contiguous. + /// For example, each handshake message could be delivered in its own + /// outer TLS message. This would mean the handshake messages are + /// separated by the outer TLS message headers, and likely also + /// separated by encryption overhead (any explicit nonce in front, + /// any padding and authentication tag afterwards). + /// + /// For a toy example of one handshake message in two fragments, and: + /// + /// - the letter `h` for handshake header octets + /// - the letter `H` for handshake payload octets + /// - the letter `x` for octets in the buffer ignored by this code, + /// + /// the buffer and `spans` data structure could look like: + /// + /// ```text + /// 0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 + /// x x x x x h h h h H H H x x x x x H H H H H H x x x + /// '------------' '----------' + /// | | + /// spans = [ { bounds = (5, 12), | + /// size = Some(9), .. }, | + /// { bounds = (17, 23), .. } ] + /// ``` + /// + /// In this case, `requires_coalesce` returns `Some(0)`. Then + /// `coalesce_one` moves the second range leftwards: + /// + /// ```text + /// 0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 + /// x x x x x h h h h H H H x x x x x H H H H H H x x x + /// '----------' + /// ^ '----------' + /// | v + /// '--<---<--' + /// copy_within(from = (17, 23), + /// to = (12, 18)) + /// ``` + /// + /// Leaving the buffer and spans: + /// + /// ```text + /// 0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 + /// x x x x x h h h h H H H H H H H H H x x x x x x x x + /// '------------------------' + /// | + /// spans = [ { bounds = (5, 18), size = Some(9), .. } ] + /// ``` + pub(crate) fn coalesce(&mut self, containing_buffer: &mut [u8]) -> Result<(), InvalidMessage> { + // Strategy: while there is work to do, scan `spans` + // for a pair where the first is not complete. move + // the second down towards the first, then reparse the contents. + loop { + let limit = self.spans.len().saturating_sub(1); + let iter = self.spans.iter(); + let Some(index) = iter + .enumerate() + .take(limit) + .find_map(|(i, span)| (!span.is_complete()).then_some(i)) + else { + return Ok(()); + }; + + let Some(second) = self.spans.remove(index + 1) else { + return Ok(()); + }; + + let Some(mut first) = self.spans.remove(index) else { + self.spans.insert(index + 1, second); + return Ok(()); + }; + + // move the entirety of `second` to be contiguous with `first` + let len = second.bounds.len(); + let target = Range { + start: first.bounds.end, + end: first.bounds.end + len, + }; + + let mut coalescer = Coalescer::new(containing_buffer); + coalescer.copy_within(second.bounds, target); + let delocator = coalescer.delocator(); + + // now adjust `first` to cover both + first.bounds.end += len; + + // finally, attempt to re-dissect `first` + let msg = EncodedMessage { + typ: ContentType::Handshake, + version: first.version, + payload: delocator.slice_from_range(&first.bounds), + }; + + let mut too_large = false; + for (i, span) in DissectHandshakeIter::new(msg, first.bounds).enumerate() { + if span.size.unwrap_or_default() > MAX_HANDSHAKE_SIZE { + too_large = true; + } + self.spans.insert(index + i, span); + } + + if too_large { + return Err(InvalidMessage::HandshakePayloadTooLarge); + } + } + } + + /// Yield the next complete handshake message from `containing_buffer`. + /// + /// If this was the last pending handshake message, marks the processed + /// buffer region for discard. + pub(crate) fn message<'b>( + &mut self, + next_span: FragmentSpan, + containing_buffer: &'b [u8], + ) -> EncodedMessage<&'b [u8]> { + // if this is the last handshake message, then we'll end + // up with an empty `spans` and can discard the remainder + // of the input buffer. + if self.spans.is_empty() { + self.discard += self.processed; + } + + EncodedMessage { + typ: ContentType::Handshake, + version: next_span.version, + payload: Delocator::new(containing_buffer).slice_from_range(&next_span.bounds), + } + } + + /// Yield the first complete [`FragmentSpan`] if any. + pub(crate) fn complete_span(&mut self) -> Option { + match self.spans.front() { + Some(span) if span.is_complete() => self.spans.pop_front(), + _ => None, + } + } + + #[inline] + pub(crate) fn take_discard(&mut self) -> usize { + // the caller is about to discard `discard` bytes + // from the front of the buffer. adjust `processed` + // down by the same amount. + self.processed = self + .processed + .saturating_sub(self.discard); + mem::take(&mut self.discard) + } - Some(Ok(InboundOpaqueMessage::new( - typ, - version, - &mut consumed[HEADER_SIZE..], - ))) + #[inline] + pub(crate) fn discard_processed(&mut self) { + self.discard = self.processed; + } + + #[inline] + pub(crate) fn add_processed(&mut self, processed: usize) { + self.processed += processed; + } + + /// We are "aligned" if there is no partial fragments of a handshake message. + pub(crate) fn aligned(&self) -> Option { + self.spans + .iter() + .all(|span| span.is_complete()) + .then_some(HandshakeAlignedProof(())) + } + + /// Do we have any message data, partial or otherwise? + pub(crate) fn is_active(&self) -> bool { + !self.spans.is_empty() + } + + #[inline] + pub(crate) fn processed(&self) -> usize { + self.processed } } -pub fn fuzz_deframer(data: &[u8]) { - let mut buf = data.to_vec(); - let mut iter = DeframerIter::new(&mut buf); +impl Default for Deframer { + fn default() -> Self { + Self { + // capacity: a typical upper limit on handshake messages in + // a single flight + spans: VecDeque::with_capacity(16), + processed: 0, + discard: 0, + } + } +} - for message in iter.by_ref() { - if message.is_err() { - break; +struct DissectHandshakeIter<'b> { + version: ProtocolVersion, + payload: &'b [u8], + bounds: Range, +} + +impl<'b> DissectHandshakeIter<'b> { + fn new(msg: EncodedMessage<&'b [u8]>, bounds: Range) -> Self { + Self { + version: msg.version, + payload: msg.payload, + bounds, + } + } +} + +impl Iterator for DissectHandshakeIter<'_> { + type Item = FragmentSpan; + + fn next(&mut self) -> Option { + if self.payload.is_empty() { + return None; + } + + // If there is not enough data to have a header the length is unknown + let all = mem::take(&mut self.payload); + let Some((header, rest)) = all.split_at_checked(HANDSHAKE_HEADER_LEN) else { + return Some(FragmentSpan { + version: self.version, + size: None, + bounds: mem::take(&mut self.bounds), + }); + }; + + // safety: header[1..] is exactly 3 bytes, so `u24::read_bytes` cannot fail + let size = U24::read_bytes(&header[1..]) + .unwrap() + .into(); + + let payload = match rest.split_at_checked(size) { + Some((payload, rest)) => { + self.payload = rest; + payload + } + None => rest, + }; + + let span_len = header.len() + payload.len(); + let bounds = self.bounds.start..self.bounds.start + span_len; + self.bounds = self.bounds.start + span_len..self.bounds.end; + Some(FragmentSpan { + version: self.version, + size: Some(size), + bounds, + }) + } +} + +#[derive(Debug)] +pub(crate) struct FragmentSpan { + /// version taken from containing message. + version: ProtocolVersion, + + /// size of the handshake message body (excluding header) + /// + /// `None` means the size is unknown, because `bounds` is not + /// large enough to encompass a whole header. + size: Option, + + /// bounds of the handshake message, including header + bounds: Range, +} + +impl FragmentSpan { + /// A `FragmentSpan` is "complete" if its size is known, and its + /// bounds exactly encompasses one handshake message. + fn is_complete(&self) -> bool { + match self.size { + Some(sz) => sz + HANDSHAKE_HEADER_LEN == self.bounds.len(), + None => false, } } +} - assert!(iter.bytes_consumed() <= buf.len()); +pub(crate) struct Deframed<'a> { + pub(crate) message: EncodedMessage>, + pub(crate) bounds: Range, } -#[cfg(feature = "std")] +/// Proof type that the handshake deframer is aligned. +/// +/// See [`Deframer::aligned()`] for more details. +#[must_use] +#[derive(Clone, Copy)] +pub(crate) struct HandshakeAlignedProof(()); + +const HANDSHAKE_HEADER_LEN: usize = 1 + 3; + +/// TLS allows for handshake messages of up to 16MB. We +/// restrict that to 64KB to limit potential for denial-of- +/// service. +const MAX_HANDSHAKE_SIZE: usize = 0xffff; + #[cfg(test)] mod tests { + use alloc::vec; use alloc::vec::Vec; - use std::prelude::v1::*; use super::*; - use crate::ContentType; + use crate::msgs::HEADER_SIZE; + + #[test] + fn exercise_fuzz_deframer() { + fuzz_deframer(&[0xff, 0xff, 0xff, 0xff, 0xff]); + for prefix in 0..7 { + fuzz_deframer(&[0x16, 0x03, 0x03, 0x00, 0x01, 0xff][..prefix]); + } + } + + fn add_bytes(deframer: &mut Deframer, range: Range, within: &[u8]) { + let msg = EncodedMessage { + typ: ContentType::Handshake, + version: ProtocolVersion::TLSv1_3, + payload: &within[range.start..range.end], + }; + + deframer.processed = range.end; + deframer.input_message(msg, range); + } + + #[test] + fn coalesce() { + let mut input = vec![0, 0, 0, 0x21, 0, 0, 0, 0, 0x01, 0xff, 0x00, 0x01]; + let mut deframer = Deframer::default(); + + add_bytes(&mut deframer, 3..4, &input); + add_bytes(&mut deframer, 4..6, &input); + add_bytes(&mut deframer, 8..10, &input); + + std::println!("before: {deframer:?}"); + deframer.coalesce(&mut input).unwrap(); + std::println!("after: {deframer:?}"); + + let span = deframer.complete_span().unwrap(); + let msg = deframer.message(span, &input); + std::println!("msg {msg:?}"); + assert_eq!(msg.typ, ContentType::Handshake); + assert_eq!(msg.version, ProtocolVersion::TLSv1_3); + assert_eq!(msg.payload, &[0x21, 0x00, 0x00, 0x01, 0xff]); + + input.drain(..deframer.take_discard()); + + assert_eq!(input, &[0, 1]); + } + + #[test] + fn append() { + let mut input = vec![0, 0, 0, 0x21, 0, 0, 5, 0, 0, 1, 2, 3, 4, 5, 0]; + let mut deframer = Deframer::default(); + + add_bytes(&mut deframer, 3..7, &input); + add_bytes(&mut deframer, 9..14, &input); + assert_eq!(deframer.spans.len(), 2); + + deframer.coalesce(&mut input).unwrap(); + let span = deframer.complete_span().unwrap(); + + let msg = std::dbg!(deframer.message(span, &input)); + assert_eq!(msg.typ, ContentType::Handshake); + assert_eq!(msg.version, ProtocolVersion::TLSv1_3); + assert_eq!(msg.payload, &[0x21, 0x00, 0x00, 0x05, 1, 2, 3, 4, 5]); + + input.drain(..deframer.take_discard()); + + assert_eq!(input, &[0]); + } + + #[test] + fn coalesce_rejects_excess_size_message() { + const X: u8 = 0xff; + let mut input = vec![0x21, 0x01, 0x00, X, 0x00, 0xab, X]; + let mut deframer = Deframer::default(); + + // split header over multiple messages, which motivates doing + // this check in `coalesce()` + add_bytes(&mut deframer, 0..3, &input); + add_bytes(&mut deframer, 4..6, &input); + + assert_eq!( + deframer.coalesce(&mut input), + Err(InvalidMessage::HandshakePayloadTooLarge) + ); + } + + #[test] + fn iter_only_returns_full_messages() { + let input = [0, 0, 0, 0x21, 0, 0, 1, 0xab, 0x21, 0, 0, 1]; + + let mut deframer = Deframer::default(); + + add_bytes(&mut deframer, 3..8, &input); + add_bytes(&mut deframer, 8..12, &input); + + let span = deframer.complete_span().unwrap(); + let msg = deframer.message(span, &input); + assert!(deframer.complete_span().is_none()); + + assert_eq!(msg.typ, ContentType::Handshake); + assert_eq!(msg.version, ProtocolVersion::TLSv1_3); + assert_eq!(msg.payload, &[0x21, 0x00, 0x00, 0x01, 0xab]); + // second span is incomplete, so no discard yet + assert_eq!(deframer.discard, 0); + } + + #[test] + fn handshake_flight() { + // intended to be a realistic example + let mut input = include_bytes!("../../testdata/handshake-test.1.bin").to_vec(); + + let mut deframer = Deframer::default(); + while let Some(result) = deframer.deframe(&mut input) { + let Deframed { message, bounds } = result.unwrap(); + let plain = message.into_plain_message(); + std::println!("message {plain:?}"); + + deframer.input_message(plain, bounds.start + HEADER_SIZE..bounds.end); + } + + deframer + .coalesce(&mut input[..]) + .unwrap(); + + for _ in 0..4 { + let span = deframer.complete_span().unwrap(); + let msg = deframer.message(span, &input[..]); + assert!(matches!( + msg, + EncodedMessage { + typ: ContentType::Handshake, + .. + } + )); + assert_eq!(deframer.discard, 0); + } + + let span = deframer.complete_span().unwrap(); + let msg = deframer.message(span, &input[..]); + assert!(matches!( + msg, + EncodedMessage { + typ: ContentType::Handshake, + .. + } + )); + + let discard = deframer.take_discard(); + assert_eq!(discard, 4280); + input.drain(0..discard); + assert!(input.is_empty()); + } #[test] fn iterator_empty_before_header_received() { - assert!(DeframerIter::new(&mut []) - .next() - .is_none()); - assert!(DeframerIter::new(&mut [0x16]) - .next() - .is_none()); - assert!(DeframerIter::new(&mut [0x16, 0x03]) - .next() - .is_none()); - assert!(DeframerIter::new(&mut [0x16, 0x03, 0x03]) - .next() - .is_none()); - assert!(DeframerIter::new(&mut [0x16, 0x03, 0x03, 0x00]) - .next() - .is_none()); - assert!(DeframerIter::new(&mut [0x16, 0x03, 0x03, 0x00, 0x01]) - .next() - .is_none()); + assert!( + Deframer::default() + .deframe(&mut []) + .is_none() + ); + assert!( + Deframer::default() + .deframe(&mut [0x16]) + .is_none() + ); + assert!( + Deframer::default() + .deframe(&mut [0x16, 0x03]) + .is_none() + ); + assert!( + Deframer::default() + .deframe(&mut [0x16, 0x03, 0x03]) + .is_none() + ); + assert!( + Deframer::default() + .deframe(&mut [0x16, 0x03, 0x03, 0x00]) + .is_none() + ); + assert!( + Deframer::default() + .deframe(&mut [0x16, 0x03, 0x03, 0x00, 0x01]) + .is_none() + ); } #[test] fn iterate_one_message() { let mut buffer = [0x17, 0x03, 0x03, 0x00, 0x01, 0x00]; - let mut iter = DeframerIter::new(&mut buffer); - assert_eq!( - iter.next().unwrap().unwrap().typ, - ContentType::ApplicationData - ); - assert_eq!(iter.bytes_consumed(), 6); - assert!(iter.next().is_none()); + let mut deframer = Deframer::default(); + + let Deframed { message, bounds } = deframer + .deframe(&mut buffer) + .unwrap() + .unwrap(); + + assert_eq!(message.typ, ContentType::ApplicationData); + assert_eq!(bounds.end, 6); + assert!(deframer.deframe(&mut buffer).is_none()); } #[test] @@ -143,23 +638,33 @@ mod tests { let mut buffer = [ 0x16, 0x03, 0x03, 0x00, 0x01, 0x00, 0x17, 0x03, 0x03, 0x00, 0x01, 0x00, ]; - let mut iter = DeframerIter::new(&mut buffer); - assert_eq!(iter.next().unwrap().unwrap().typ, ContentType::Handshake); - assert_eq!(iter.bytes_consumed(), 6); - assert_eq!( - iter.next().unwrap().unwrap().typ, - ContentType::ApplicationData - ); - assert_eq!(iter.bytes_consumed(), 12); - assert!(iter.next().is_none()); + let mut deframer = Deframer::default(); + + let Deframed { message, bounds } = deframer + .deframe(&mut buffer) + .unwrap() + .unwrap(); + + assert_eq!(message.typ, ContentType::Handshake); + assert_eq!(bounds.end, 6); + + let Deframed { message, bounds } = deframer + .deframe(&mut buffer) + .unwrap() + .unwrap(); + + assert_eq!(message.typ, ContentType::ApplicationData); + assert_eq!(bounds.end, 12); + assert!(deframer.deframe(&mut buffer).is_none()); } #[test] fn iterator_invalid_protocol_version_rejected() { let mut buffer = include_bytes!("../../testdata/deframer-invalid-version.bin").to_vec(); - let mut iter = DeframerIter::new(&mut buffer); + let mut deframer = Deframer::default(); + let result = deframer.deframe(&mut buffer).unwrap(); assert_eq!( - iter.next().unwrap().err(), + result.err(), Some(Error::InvalidMessage( InvalidMessage::UnknownProtocolVersion )) @@ -169,9 +674,10 @@ mod tests { #[test] fn iterator_invalid_content_type_rejected() { let mut buffer = include_bytes!("../../testdata/deframer-invalid-contenttype.bin").to_vec(); - let mut iter = DeframerIter::new(&mut buffer); + let mut deframer = Deframer::default(); + let result = deframer.deframe(&mut buffer).unwrap(); assert_eq!( - iter.next().unwrap().err(), + result.err(), Some(Error::InvalidMessage(InvalidMessage::InvalidContentType)) ); } @@ -179,9 +685,10 @@ mod tests { #[test] fn iterator_excess_message_length_rejected() { let mut buffer = include_bytes!("../../testdata/deframer-invalid-length.bin").to_vec(); - let mut iter = DeframerIter::new(&mut buffer); + let mut deframer = Deframer::default(); + let result = deframer.deframe(&mut buffer).unwrap(); assert_eq!( - iter.next().unwrap().err(), + result.err(), Some(Error::InvalidMessage(InvalidMessage::MessageTooLarge)) ); } @@ -189,9 +696,10 @@ mod tests { #[test] fn iterator_zero_message_length_rejected() { let mut buffer = include_bytes!("../../testdata/deframer-invalid-empty.bin").to_vec(); - let mut iter = DeframerIter::new(&mut buffer); + let mut deframer = Deframer::default(); + let result = deframer.deframe(&mut buffer).unwrap(); assert_eq!( - iter.next().unwrap().err(), + result.err(), Some(Error::InvalidMessage(InvalidMessage::InvalidEmptyPayload)) ); } @@ -203,24 +711,18 @@ mod tests { buffer.extend(client_hello); buffer.extend(client_hello); buffer.extend(client_hello); - let mut iter = DeframerIter::new(&mut buffer); + let mut deframer = Deframer::default(); let mut count = 0; + let mut end = 0; - for message in iter.by_ref() { - let message = message.unwrap(); + while let Some(result) = deframer.deframe(&mut buffer) { + let Deframed { message, bounds } = result.unwrap(); assert_eq!(ContentType::Handshake, message.typ); count += 1; + end = bounds.end; } assert_eq!(count, 3); - assert_eq!(client_hello.len() * 3, iter.bytes_consumed()); - } - - #[test] - fn exercise_fuzz_deframer() { - fuzz_deframer(&[0xff, 0xff, 0xff, 0xff, 0xff]); - for prefix in 0..7 { - fuzz_deframer(&[0x16, 0x03, 0x03, 0x00, 0x01, 0xff][..prefix]); - } + assert_eq!(client_hello.len() * 3, end); } } diff --git a/rustls/src/msgs/enums.rs b/rustls/src/msgs/enums.rs index 2e03e85593e..ec7e34ccf82 100644 --- a/rustls/src/msgs/enums.rs +++ b/rustls/src/msgs/enums.rs @@ -1,83 +1,44 @@ #![allow(clippy::upper_case_acronyms)] #![allow(non_camel_case_types)] -use crate::crypto::KeyExchangeAlgorithm; -use crate::msgs::codec::{Codec, Reader}; - -enum_builder! { - /// The `HashAlgorithm` TLS protocol enum. Values in this enum are taken - /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub enum HashAlgorithm { - NONE => 0x00, - MD5 => 0x01, - SHA1 => 0x02, - SHA224 => 0x03, - SHA256 => 0x04, - SHA384 => 0x05, - SHA512 => 0x06, - } -} enum_builder! { /// The `ClientCertificateType` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub(crate) enum ClientCertificateType { + pub(crate) struct ClientCertificateType(pub u8); + + enum ClientCertificateTypeName { RSASign => 0x01, - DSSSign => 0x02, - RSAFixedDH => 0x03, - DSSFixedDH => 0x04, - RSAEphemeralDH => 0x05, - DSSEphemeralDH => 0x06, - FortezzaDMS => 0x14, ECDSASign => 0x40, - RSAFixedECDH => 0x41, - ECDSAFixedECDH => 0x42, } } enum_builder! { /// The `Compression` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub enum Compression { + pub(crate) struct Compression(pub(crate) u8); + + pub(crate) enum CompressionName { Null => 0x00, - Deflate => 0x01, - LSZ => 0x40, } } enum_builder! { /// The `AlertLevel` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub enum AlertLevel { + pub struct AlertLevel(pub u8); + + pub(crate) enum AlertLevelName { Warning => 0x01, Fatal => 0x02, } } -enum_builder! { - /// The `HeartbeatMessageType` TLS protocol enum. Values in this enum are taken - /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub(crate) enum HeartbeatMessageType { - Request => 0x01, - Response => 0x02, - } -} - enum_builder! { /// The `ExtensionType` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u16)] - pub enum ExtensionType { + pub struct ExtensionType(pub u16); + + enum ExtensionTypeName { ServerName => 0x0000, MaxFragmentLength => 0x0001, ClientCertificateUrl => 0x0002, @@ -117,9 +78,8 @@ enum_builder! { NextProtocolNegotiation => 0x3374, ChannelId => 0x754f, RenegotiationInfo => 0xff01, - TransportParametersDraft => 0xffa5, - EncryptedClientHello => 0xfe0d, // https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#section-11.1 - EncryptedClientHelloOuterExtensions => 0xfd00, // https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#section-5.1 + EncryptedClientHello => 0xfe0d, // https://datatracker.ietf.org/doc/html/rfc9849#section-11.1 + EncryptedClientHelloOuterExtensions => 0xfd00, // https://datatracker.ietf.org/doc/html/rfc9849#section-5.1 } } @@ -131,13 +91,13 @@ impl ExtensionType { /// sometimes (e.g. server name, cert compression methods), but not always, SHOULD NOT be /// compressed. /// - /// See [draft-ietf-esni-18 §5](https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#section-5) - /// and [draft-ietf-esni-18 §10.5](https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#section-10.5) + /// See [RFC 9849 §5](https://datatracker.ietf.org/doc/html/rfc9849#section-5) + /// and [RFC 9849 §10.5](https://datatracker.ietf.org/doc/html/rfc9849#section-10.5) /// for more information. pub(crate) fn ech_compress(&self) -> bool { // We match which extensions we will compress with BoringSSL and Go's stdlib. matches!( - self, + *self, Self::StatusRequest | Self::EllipticCurves | Self::SignatureAlgorithms @@ -154,138 +114,39 @@ impl ExtensionType { enum_builder! { /// The `ServerNameType` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub(crate) enum ServerNameType { - HostName => 0x00, - } -} - -enum_builder! { - /// The `NamedCurve` TLS protocol enum. Values in this enum are taken - /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - /// - /// This enum is used for recognizing elliptic curve parameters advertised - /// by a peer during a TLS handshake. It is **not** a list of curves that - /// Rustls supports. See [`crate::crypto::ring::kx_group`] for the list of supported - /// elliptic curve groups. - #[repr(u16)] - pub(crate) enum NamedCurve { - sect163k1 => 0x0001, - sect163r1 => 0x0002, - sect163r2 => 0x0003, - sect193r1 => 0x0004, - sect193r2 => 0x0005, - sect233k1 => 0x0006, - sect233r1 => 0x0007, - sect239k1 => 0x0008, - sect283k1 => 0x0009, - sect283r1 => 0x000a, - sect409k1 => 0x000b, - sect409r1 => 0x000c, - sect571k1 => 0x000d, - sect571r1 => 0x000e, - secp160k1 => 0x000f, - secp160r1 => 0x0010, - secp160r2 => 0x0011, - secp192k1 => 0x0012, - secp192r1 => 0x0013, - secp224k1 => 0x0014, - secp224r1 => 0x0015, - secp256k1 => 0x0016, - secp256r1 => 0x0017, - secp384r1 => 0x0018, - secp521r1 => 0x0019, - brainpoolp256r1 => 0x001a, - brainpoolp384r1 => 0x001b, - brainpoolp512r1 => 0x001c, - X25519 => 0x001d, - X448 => 0x001e, - arbitrary_explicit_prime_curves => 0xff01, - arbitrary_explicit_char2_curves => 0xff02, - } -} - -enum_builder! { - /// The `NamedGroup` TLS protocol enum. Values in this enum are taken - /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u16)] - pub enum NamedGroup { - secp256r1 => 0x0017, - secp384r1 => 0x0018, - secp521r1 => 0x0019, - X25519 => 0x001d, - X448 => 0x001e, - FFDHE2048 => 0x0100, - FFDHE3072 => 0x0101, - FFDHE4096 => 0x0102, - FFDHE6144 => 0x0103, - FFDHE8192 => 0x0104, - MLKEM512 => 0x0200, - MLKEM768 => 0x0201, - MLKEM1024 => 0x0202, - secp256r1MLKEM768 => 0x11eb, - X25519MLKEM768 => 0x11ec, - } -} + pub(crate) struct ServerNameType(pub u8); -impl NamedGroup { - /// Return the key exchange algorithm associated with this `NamedGroup` - pub fn key_exchange_algorithm(self) -> KeyExchangeAlgorithm { - match u16::from(self) { - x if (0x100..0x200).contains(&x) => KeyExchangeAlgorithm::DHE, - _ => KeyExchangeAlgorithm::ECDHE, - } + enum ServerNameTypeName { + HostName => 0x00, } } enum_builder! { /// The `ECPointFormat` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub enum ECPointFormat { - Uncompressed => 0x00, - ANSIX962CompressedPrime => 0x01, - ANSIX962CompressedChar2 => 0x02, - } -} - -impl ECPointFormat { - pub(crate) const SUPPORTED: [Self; 1] = [Self::Uncompressed]; -} + pub struct ECPointFormat(pub u8); -enum_builder! { - /// The `HeartbeatMode` TLS protocol enum. Values in this enum are taken - /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub(crate) enum HeartbeatMode { - PeerAllowedToSend => 0x01, - PeerNotAllowedToSend => 0x02, + enum ECPointFormatName { + Uncompressed => 0x00, } } enum_builder! { /// The `ECCurveType` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub(crate) enum ECCurveType { - ExplicitPrime => 0x01, - ExplicitChar2 => 0x02, + pub(crate) struct ECCurveType(pub(crate) u8); + + enum ECCurveTypeName { NamedCurve => 0x03, } } enum_builder! { - /// The `PSKKeyExchangeMode` TLS protocol enum. Values in this enum are taken + /// The `PskKeyExchangeMode` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub enum PSKKeyExchangeMode { + pub struct PskKeyExchangeMode(pub u8); + + enum PskKeyExchangeModeName { PSK_KE => 0x00, PSK_DHE_KE => 0x01, } @@ -294,9 +155,9 @@ enum_builder! { enum_builder! { /// The `KeyUpdateRequest` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub enum KeyUpdateRequest { + pub struct KeyUpdateRequest(pub u8); + + enum KeyUpdateRequestName { UpdateNotRequested => 0x00, UpdateRequested => 0x01, } @@ -305,104 +166,22 @@ enum_builder! { enum_builder! { /// The `CertificateStatusType` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. - /// The `Unknown` item is used when processing unrecognised ordinals. - #[repr(u8)] - pub enum CertificateStatusType { - OCSP => 0x01, - } -} - -enum_builder! { - /// The `CertificateType` enum sent in the cert_type extensions. - /// Values in this enum are taken from the various RFCs covering TLS, and are listed by IANA. - /// - /// [RFC 6091 Section 5]: - /// [RFC 7250 Section 7]: - #[repr(u8)] - pub enum CertificateType { - X509 => 0x00, - RawPublicKey => 0x02, - } -} - -enum_builder! { - /// The Key Encapsulation Mechanism (`Kem`) type for HPKE operations. - /// Listed by IANA, as specified in [RFC 9180 Section 7.1] - /// - /// [RFC 9180 Section 7.1]: - #[repr(u16)] - pub enum HpkeKem { - DHKEM_P256_HKDF_SHA256 => 0x0010, - DHKEM_P384_HKDF_SHA384 => 0x0011, - DHKEM_P521_HKDF_SHA512 => 0x0012, - DHKEM_X25519_HKDF_SHA256 => 0x0020, - DHKEM_X448_HKDF_SHA512 => 0x0021, - } -} - -enum_builder! { - /// The Key Derivation Function (`Kdf`) type for HPKE operations. - /// Listed by IANA, as specified in [RFC 9180 Section 7.2] - /// - /// [RFC 9180 Section 7.2]: - #[repr(u16)] - pub enum HpkeKdf { - HKDF_SHA256 => 0x0001, - HKDF_SHA384 => 0x0002, - HKDF_SHA512 => 0x0003, - } -} - -impl Default for HpkeKdf { - // TODO(XXX): revisit the default configuration. This is just what Cloudflare ships right now. - fn default() -> Self { - Self::HKDF_SHA256 - } -} - -enum_builder! { - /// The Authenticated Encryption with Associated Data (`Aead`) type for HPKE operations. - /// Listed by IANA, as specified in [RFC 9180 Section 7.3] - /// - /// [RFC 9180 Section 7.3]: - #[repr(u16)] - pub enum HpkeAead { - AES_128_GCM => 0x0001, - AES_256_GCM => 0x0002, - CHACHA20_POLY_1305 => 0x0003, - EXPORT_ONLY => 0xFFFF, - } -} - -impl HpkeAead { - /// Returns the length of the tag for the AEAD algorithm, or none if the AEAD is EXPORT_ONLY. - pub(crate) fn tag_len(&self) -> Option { - match self { - // See RFC 9180 Section 7.3, column `Nt`, the length in bytes of the authentication tag - // for the algorithm. - // https://www.rfc-editor.org/rfc/rfc9180.html#section-7.3 - Self::AES_128_GCM | Self::AES_256_GCM | Self::CHACHA20_POLY_1305 => Some(16), - _ => None, - } - } -} + pub struct CertificateStatusType(pub u8); -impl Default for HpkeAead { - // TODO(XXX): revisit the default configuration. This is just what Cloudflare ships right now. - fn default() -> Self { - Self::AES_128_GCM + enum CertificateStatusTypeName { + OCSP => 0x01, } } enum_builder! { /// The Encrypted Client Hello protocol version (`EchVersion`). /// - /// Specified in [draft-ietf-tls-esni Section 4]. - /// TODO(XXX): Update reference once RFC is published. + /// Specified in [RFC 9849 Section 4]. /// - /// [draft-ietf-tls-esni Section 4]: - #[repr(u16)] - pub enum EchVersion { + /// [RFC 9849 Section 4]: + pub struct EchVersion(pub u16); + + enum EchVersionName { V18 => 0xfe0d, } } @@ -412,42 +191,25 @@ pub(crate) mod tests { // These tests are intended to provide coverage and // check panic-safety of relatively unused values. - use std::prelude::v1::*; + use alloc::vec::Vec; use super::*; + use crate::msgs::codec::Codec; #[test] fn test_enums() { - test_enum8::(HashAlgorithm::NONE, HashAlgorithm::SHA512); test_enum8::( ClientCertificateType::RSASign, - ClientCertificateType::ECDSAFixedECDH, + ClientCertificateType::ECDSASign, ); - test_enum8::(Compression::Null, Compression::LSZ); + test_enum8::(Compression::Null, Compression::Null); test_enum8::(AlertLevel::Warning, AlertLevel::Fatal); - test_enum8::( - HeartbeatMessageType::Request, - HeartbeatMessageType::Response, - ); test_enum16::(ExtensionType::ServerName, ExtensionType::RenegotiationInfo); test_enum8::(ServerNameType::HostName, ServerNameType::HostName); - test_enum16::( - NamedCurve::sect163k1, - NamedCurve::arbitrary_explicit_char2_curves, - ); - test_enum16::(NamedGroup::secp256r1, NamedGroup::FFDHE8192); - test_enum8::( - ECPointFormat::Uncompressed, - ECPointFormat::ANSIX962CompressedChar2, - ); - test_enum8::( - HeartbeatMode::PeerAllowedToSend, - HeartbeatMode::PeerNotAllowedToSend, - ); - test_enum8::(ECCurveType::ExplicitPrime, ECCurveType::NamedCurve); - test_enum8::( - PSKKeyExchangeMode::PSK_KE, - PSKKeyExchangeMode::PSK_DHE_KE, + test_enum8::(ECPointFormat::Uncompressed, ECPointFormat::Uncompressed); + test_enum8::( + PskKeyExchangeMode::PSK_KE, + PskKeyExchangeMode::PSK_DHE_KE, ); test_enum8::( KeyUpdateRequest::UpdateNotRequested, @@ -457,7 +219,6 @@ pub(crate) mod tests { CertificateStatusType::OCSP, CertificateStatusType::OCSP, ); - test_enum8::(CertificateType::X509, CertificateType::RawPublicKey); } pub(crate) fn test_enum8 Codec<'a>>(first: T, last: T) { @@ -471,6 +232,21 @@ pub(crate) mod tests { let t = T::read_bytes(&buf).unwrap(); assert_eq!(val, get8(&t)); + std::println!("{val:?}"); + } + } + + pub(crate) fn test_enum8_display Codec<'a> + core::fmt::Display + Copy + From>( + first: T, + last: T, + ) where + u8: From, + { + test_enum8(first, last); + + for val in u8::from(first)..u8::from(last) + 1 { + let t = T::from(val); + std::println!("0x{val:02x} => {t}"); } } @@ -485,18 +261,17 @@ pub(crate) mod tests { let t = T::read_bytes(&buf).unwrap(); assert_eq!(val, get16(&t)); + std::println!("{val:?}"); } } fn get8 Codec<'a>>(enum_value: &T) -> u8 { let enc = enum_value.get_encoding(); - assert_eq!(enc.len(), 1); - enc[0] + u8::from_be_bytes(enc.try_into().unwrap()) } fn get16 Codec<'a>>(enum_value: &T) -> u16 { let enc = enum_value.get_encoding(); - assert_eq!(enc.len(), 2); - (enc[0] as u16 >> 8) | (enc[1] as u16) + u16::from_be_bytes(enc.try_into().unwrap()) } } diff --git a/rustls/src/msgs/fragmenter.rs b/rustls/src/msgs/fragmenter.rs index bad02853287..93ed9ba33c4 100644 --- a/rustls/src/msgs/fragmenter.rs +++ b/rustls/src/msgs/fragmenter.rs @@ -1,11 +1,12 @@ -use crate::enums::{ContentType, ProtocolVersion}; -use crate::msgs::message::{OutboundChunks, OutboundPlainMessage, PlainMessage}; use crate::Error; +use crate::crypto::cipher::{EncodedMessage, OutboundPlain, Payload}; +use crate::enums::{ContentType, ProtocolVersion}; + pub(crate) const MAX_FRAGMENT_LEN: usize = 16384; pub(crate) const PACKET_OVERHEAD: usize = 1 + 2 + 2; pub(crate) const MAX_FRAGMENT_SIZE: usize = MAX_FRAGMENT_LEN + PACKET_OVERHEAD; -pub struct MessageFragmenter { +pub(crate) struct MessageFragmenter { max_frag: usize, } @@ -25,10 +26,10 @@ impl MessageFragmenter { /// Return an iterator across those messages. /// /// Payloads are borrowed from `msg`. - pub fn fragment_message<'a>( + pub(crate) fn fragment_message<'a>( &self, - msg: &'a PlainMessage, - ) -> impl Iterator> + 'a { + msg: &'a EncodedMessage>, + ) -> impl ExactSizeIterator>> + 'a { self.fragment_payload(msg.typ, msg.version, msg.payload.bytes().into()) } @@ -43,9 +44,9 @@ impl MessageFragmenter { &self, typ: ContentType, version: ProtocolVersion, - payload: OutboundChunks<'a>, - ) -> impl ExactSizeIterator> { - Chunker::new(payload, self.max_frag).map(move |payload| OutboundPlainMessage { + payload: OutboundPlain<'a>, + ) -> impl ExactSizeIterator>> { + Chunker::new(payload, self.max_frag).map(move |payload| EncodedMessage { typ, version, payload, @@ -60,7 +61,10 @@ impl MessageFragmenter { /// A `max_fragment_size` of `None` sets the highest allowable fragment size. /// /// Returns BadMaxFragmentSize if the size is smaller than 32 or larger than 16389. - pub fn set_max_fragment_size(&mut self, max_fragment_size: Option) -> Result<(), Error> { + pub(crate) fn set_max_fragment_size( + &mut self, + max_fragment_size: Option, + ) -> Result<(), Error> { self.max_frag = match max_fragment_size { Some(sz @ 32..=MAX_FRAGMENT_SIZE) => sz - PACKET_OVERHEAD, None => MAX_FRAGMENT_LEN, @@ -72,18 +76,18 @@ impl MessageFragmenter { /// An iterator over borrowed fragments of a payload struct Chunker<'a> { - payload: OutboundChunks<'a>, + payload: OutboundPlain<'a>, limit: usize, } impl<'a> Chunker<'a> { - fn new(payload: OutboundChunks<'a>, limit: usize) -> Self { + fn new(payload: OutboundPlain<'a>, limit: usize) -> Self { Self { payload, limit } } } impl<'a> Iterator for Chunker<'a> { - type Item = OutboundChunks<'a>; + type Item = OutboundPlain<'a>; fn next(&mut self) -> Option { if self.payload.is_empty() { @@ -98,22 +102,21 @@ impl<'a> Iterator for Chunker<'a> { impl ExactSizeIterator for Chunker<'_> { fn len(&self) -> usize { - (self.payload.len() + self.limit - 1) / self.limit + self.payload.len().div_ceil(self.limit) } } #[cfg(test)] mod tests { - use std::prelude::v1::*; + use alloc::vec::Vec; use std::vec; use super::{MessageFragmenter, PACKET_OVERHEAD}; + use crate::crypto::cipher::{EncodedMessage, OutboundPlain, Payload}; use crate::enums::{ContentType, ProtocolVersion}; - use crate::msgs::base::Payload; - use crate::msgs::message::{OutboundChunks, OutboundPlainMessage, PlainMessage}; fn msg_eq( - m: &OutboundPlainMessage<'_>, + m: &EncodedMessage>, total_len: usize, typ: &ContentType, version: &ProtocolVersion, @@ -133,7 +136,7 @@ mod tests { let typ = ContentType::Handshake; let version = ProtocolVersion::TLSv1_2; let data: Vec = (1..70u8).collect(); - let m = PlainMessage { + let m = EncodedMessage { typ, version, payload: Payload::new(data), @@ -177,7 +180,7 @@ mod tests { #[test] fn non_fragment() { - let m = PlainMessage { + let m = EncodedMessage { typ: ContentType::Handshake, version: ProtocolVersion::TLSv1_2, payload: Payload::new(b"\x01\x02\x03\x04\x05\x06\x07\x08".to_vec()), @@ -204,7 +207,7 @@ mod tests { let typ = ContentType::Handshake; let version = ProtocolVersion::TLSv1_2; let payload_owner: Vec<&[u8]> = vec![&[b'a'; 8], &[b'b'; 12], &[b'c'; 32], &[b'd'; 20]]; - let borrowed_payload = OutboundChunks::new(&payload_owner); + let borrowed_payload = OutboundPlain::new(&payload_owner); let mut frag = MessageFragmenter::default(); frag.set_max_fragment_size(Some(37)) // 32 + packet overhead .unwrap(); diff --git a/rustls/src/msgs/handshake.rs b/rustls/src/msgs/handshake.rs index 38e211016ec..420b2b07982 100644 --- a/rustls/src/msgs/handshake.rs +++ b/rustls/src/msgs/handshake.rs @@ -1,105 +1,61 @@ use alloc::collections::BTreeSet; -#[cfg(feature = "logging")] -use alloc::string::String; -use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; -use core::ops::Deref; +use core::ops::{Deref, DerefMut}; +use core::time::Duration; use core::{fmt, iter}; -use pki_types::{CertificateDer, DnsName}; +use pki_types::CertificateDer; -#[cfg(feature = "tls12")] -use crate::crypto::ActiveKeyExchange; -use crate::crypto::SecureRandom; +use crate::crypto::cipher::Payload; +use crate::crypto::kx::ffdhe::FfdheGroup; +use crate::crypto::kx::{ActiveKeyExchange, KeyExchangeAlgorithm, NamedGroup}; +use crate::crypto::{ + CipherSuite, GetRandomFailed, SecureRandom, SelectedCredential, SignatureScheme, +}; use crate::enums::{ - CertificateCompressionAlgorithm, CipherSuite, EchClientHelloType, HandshakeType, - ProtocolVersion, SignatureScheme, + ApplicationProtocol, CertificateCompressionAlgorithm, CertificateType, ProtocolVersion, }; use crate::error::InvalidMessage; -#[cfg(feature = "tls12")] -use crate::ffdhe_groups::FfdheGroup; use crate::log::warn; -use crate::msgs::base::{Payload, PayloadU16, PayloadU24, PayloadU8}; -use crate::msgs::codec::{self, Codec, LengthPrefixedBuffer, ListLength, Reader, TlsListElement}; +use crate::msgs::codec::{ + CERTIFICATE_MAX_SIZE_LIMIT, Codec, LengthPrefixedBuffer, ListLength, MaybeEmpty, NonEmpty, + Reader, SizedPayload, TlsListElement, TlsListIter, U24, hex, +}; use crate::msgs::enums::{ - CertificateStatusType, CertificateType, ClientCertificateType, Compression, ECCurveType, - ECPointFormat, EchVersion, ExtensionType, HpkeAead, HpkeKdf, HpkeKem, KeyUpdateRequest, - NamedGroup, PSKKeyExchangeMode, ServerNameType, + CertificateStatusType, ClientCertificateType, Compression, ECCurveType, ECPointFormat, + ExtensionType, }; -use crate::rand; -use crate::verify::DigitallySignedStruct; -use crate::x509::wrap_in_sequence; - -/// Create a newtype wrapper around a given type. -/// -/// This is used to create newtypes for the various TLS message types which is used to wrap -/// the `PayloadU8` or `PayloadU16` types. This is typically used for types where we don't need -/// anything other than access to the underlying bytes. -macro_rules! wrapped_payload( - ($(#[$comment:meta])* $vis:vis struct $name:ident, $inner:ident,) => { - $(#[$comment])* - #[derive(Clone, Debug)] - $vis struct $name($inner); - - impl From> for $name { - fn from(v: Vec) -> Self { - Self($inner::new(v)) - } - } - - impl AsRef<[u8]> for $name { - fn as_ref(&self) -> &[u8] { - self.0.0.as_slice() - } - } - - impl Codec<'_> for $name { - fn encode(&self, bytes: &mut Vec) { - self.0.encode(bytes); - } - - fn read(r: &mut Reader<'_>) -> Result { - Ok(Self($inner::read(r)?)) - } - } - } -); +use crate::sync::Arc; +use crate::verify::{DigitallySignedStruct, DistinguishedName}; #[derive(Clone, Copy, Eq, PartialEq)] -pub struct Random(pub(crate) [u8; 32]); +pub(crate) struct Random(pub(crate) [u8; 32]); impl fmt::Debug for Random { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - super::base::hex(f, &self.0) + hex(f, &self.0) } } -static HELLO_RETRY_REQUEST_RANDOM: Random = Random([ +pub(super) const HELLO_RETRY_REQUEST_RANDOM: Random = Random([ 0xcf, 0x21, 0xad, 0x74, 0xe5, 0x9a, 0x61, 0x11, 0xbe, 0x1d, 0x8c, 0x02, 0x1e, 0x65, 0xb8, 0x91, 0xc2, 0xa2, 0x11, 0x16, 0x7a, 0xbb, 0x8c, 0x5e, 0x07, 0x9e, 0x09, 0xe2, 0xc8, 0xa8, 0x33, 0x9c, ]); -static ZERO_RANDOM: Random = Random([0u8; 32]); - impl Codec<'_> for Random { fn encode(&self, bytes: &mut Vec) { bytes.extend_from_slice(&self.0); } fn read(r: &mut Reader<'_>) -> Result { - let Some(bytes) = r.take(32) else { - return Err(InvalidMessage::MissingData("Random")); - }; - - let mut opaque = [0; 32]; - opaque.clone_from_slice(bytes); - Ok(Self(opaque)) + r.take_array("Random") + .map(|&bytes| Self(bytes)) } } impl Random { - pub(crate) fn new(secure_random: &dyn SecureRandom) -> Result { + pub(crate) fn new(secure_random: &dyn SecureRandom) -> Result { let mut data = [0u8; 32]; secure_random.fill(&mut data)?; Ok(Self(data)) @@ -114,29 +70,27 @@ impl From<[u8; 32]> for Random { } #[derive(Copy, Clone)] -pub struct SessionId { - len: usize, +pub(crate) struct SessionId { data: [u8; 32], + len: usize, } -impl fmt::Debug for SessionId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - super::base::hex(f, &self.data[..self.len]) +impl SessionId { + pub(crate) fn random(secure_random: &dyn SecureRandom) -> Result { + let mut data = [0u8; 32]; + secure_random.fill(&mut data)?; + Ok(Self { data, len: 32 }) } -} - -impl PartialEq for SessionId { - fn eq(&self, other: &Self) -> bool { - if self.len != other.len { - return false; - } - let mut diff = 0u8; - for i in 0..self.len { - diff |= self.data[i] ^ other.data[i]; + pub(crate) fn empty() -> Self { + Self { + data: [0u8; 32], + len: 0, } + } - diff == 0u8 + pub(crate) fn is_empty(&self) -> bool { + self.len == 0 } } @@ -163,23 +117,18 @@ impl Codec<'_> for SessionId { } } -impl SessionId { - pub fn random(secure_random: &dyn SecureRandom) -> Result { - let mut data = [0u8; 32]; - secure_random.fill(&mut data)?; - Ok(Self { data, len: 32 }) - } +impl PartialEq for SessionId { + fn eq(&self, other: &Self) -> bool { + if self.len != other.len { + return false; + } - pub(crate) fn empty() -> Self { - Self { - data: [0u8; 32], - len: 0, + let mut diff = 0u8; + for i in 0..self.len { + diff |= self.data[i] ^ other.data[i]; } - } - #[cfg(feature = "tls12")] - pub(crate) fn is_empty(&self) -> bool { - self.len == 0 + diff == 0u8 } } @@ -189,186 +138,121 @@ impl AsRef<[u8]> for SessionId { } } -#[derive(Clone, Debug, PartialEq)] -pub struct UnknownExtension { - pub(crate) typ: ExtensionType, - pub(crate) payload: Payload<'static>, -} - -impl UnknownExtension { - fn encode(&self, bytes: &mut Vec) { - self.payload.encode(bytes); - } - - fn read(typ: ExtensionType, r: &mut Reader<'_>) -> Self { - let payload = Payload::read(r).into_owned(); - Self { typ, payload } +impl fmt::Debug for SessionId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + hex(f, &self.data[..self.len]) } } -impl TlsListElement for ECPointFormat { - const SIZE_LEN: ListLength = ListLength::U8; -} - -impl TlsListElement for NamedGroup { - const SIZE_LEN: ListLength = ListLength::U16; -} - -impl TlsListElement for SignatureScheme { - const SIZE_LEN: ListLength = ListLength::U16; +#[derive(Clone, Copy, Debug)] +pub(crate) struct SupportedEcPointFormats { + pub(crate) uncompressed: bool, } -#[derive(Clone, Debug)] -pub(crate) enum ServerNamePayload { - HostName(DnsName<'static>), - IpAddress(PayloadU16), - Unknown(Payload<'static>), -} +impl Codec<'_> for SupportedEcPointFormats { + fn encode(&self, bytes: &mut Vec) { + let inner = LengthPrefixedBuffer::new(ECPointFormat::SIZE_LEN, bytes); -impl ServerNamePayload { - pub(crate) fn new_hostname(hostname: DnsName<'static>) -> Self { - Self::HostName(hostname) + if self.uncompressed { + ECPointFormat::Uncompressed.encode(inner.buf); + } } - fn read_hostname(r: &mut Reader<'_>) -> Result { - use pki_types::ServerName; - let raw = PayloadU16::read(r)?; + fn read(r: &mut Reader<'_>) -> Result { + let mut uncompressed = false; - match ServerName::try_from(raw.0.as_slice()) { - Ok(ServerName::DnsName(d)) => Ok(Self::HostName(d.to_owned())), - Ok(ServerName::IpAddress(_)) => Ok(Self::IpAddress(raw)), - Ok(_) | Err(_) => { - warn!( - "Illegal SNI hostname received {:?}", - String::from_utf8_lossy(&raw.0) - ); - Err(InvalidMessage::InvalidServerName) + for pf in TlsListIter::::new(r)? { + if let ECPointFormat::Uncompressed = pf? { + uncompressed = true; } } - } - fn encode(&self, bytes: &mut Vec) { - match *self { - Self::HostName(ref name) => { - (name.as_ref().len() as u16).encode(bytes); - bytes.extend_from_slice(name.as_ref().as_bytes()); - } - Self::IpAddress(ref r) => r.encode(bytes), - Self::Unknown(ref r) => r.encode(bytes), - } + Ok(Self { uncompressed }) } } -#[derive(Clone, Debug)] -pub struct ServerName { - pub(crate) typ: ServerNameType, - pub(crate) payload: ServerNamePayload, -} - -impl Codec<'_> for ServerName { - fn encode(&self, bytes: &mut Vec) { - self.typ.encode(bytes); - self.payload.encode(bytes); - } - - fn read(r: &mut Reader<'_>) -> Result { - let typ = ServerNameType::read(r)?; - - let payload = match typ { - ServerNameType::HostName => ServerNamePayload::read_hostname(r)?, - _ => ServerNamePayload::Unknown(Payload::read(r).into_owned()), - }; - - Ok(Self { typ, payload }) +impl Default for SupportedEcPointFormats { + fn default() -> Self { + Self { uncompressed: true } } } -impl TlsListElement for ServerName { - const SIZE_LEN: ListLength = ListLength::U16; +/// RFC8422: `ECPointFormat ec_point_format_list<1..2^8-1>` +impl TlsListElement for ECPointFormat { + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("ECPointFormats"), + }; } -pub(crate) trait ConvertServerNameList { - fn has_duplicate_names_for_type(&self) -> bool; - fn single_hostname(&self) -> Option>; +/// RFC8422: `NamedCurve named_curve_list<2..2^16-1>` +impl TlsListElement for NamedGroup { + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("NamedGroups"), + }; } -impl ConvertServerNameList for [ServerName] { - /// RFC6066: "The ServerNameList MUST NOT contain more than one name of the same name_type." - fn has_duplicate_names_for_type(&self) -> bool { - has_duplicates::<_, _, u8>(self.iter().map(|name| name.typ)) - } - - fn single_hostname(&self) -> Option> { - fn only_dns_hostnames(name: &ServerName) -> Option> { - if let ServerNamePayload::HostName(ref dns) = name.payload { - Some(dns.borrow()) - } else { - None - } - } - - self.iter() - .filter_map(only_dns_hostnames) - .next() - } +/// RFC8446: `SignatureScheme supported_signature_algorithms<2..2^16-2>;` +impl TlsListElement for SignatureScheme { + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::NoSignatureSchemes, + }; } -wrapped_payload!(pub struct ProtocolName, PayloadU8,); +/// RFC7301 encodes a single protocol name as `Vec` +#[derive(Clone, Debug)] +pub(crate) struct SingleProtocolName(ApplicationProtocol<'static>); -impl TlsListElement for ProtocolName { - const SIZE_LEN: ListLength = ListLength::U16; -} +impl SingleProtocolName { + pub(crate) fn new(single: ApplicationProtocol<'static>) -> Self { + Self(single) + } -pub(crate) trait ConvertProtocolNameList { - fn from_slices(names: &[&[u8]]) -> Self; - fn to_slices(&self) -> Vec<&[u8]>; - fn as_single_slice(&self) -> Option<&[u8]>; + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("ProtocolNames"), + }; } -impl ConvertProtocolNameList for Vec { - fn from_slices(names: &[&[u8]]) -> Self { - let mut ret = Self::new(); - - for name in names { - ret.push(ProtocolName::from(name.to_vec())); - } - - ret +impl Codec<'_> for SingleProtocolName { + fn encode(&self, bytes: &mut Vec) { + let body = LengthPrefixedBuffer::new(Self::SIZE_LEN, bytes); + self.0.encode(body.buf); } - fn to_slices(&self) -> Vec<&[u8]> { - self.iter() - .map(|proto| proto.as_ref()) - .collect::>() - } + fn read(reader: &mut Reader<'_>) -> Result { + let len = Self::SIZE_LEN.read(reader)?; + let mut sub = reader.sub(len)?; + + let item = ApplicationProtocol::read(&mut sub)?; - fn as_single_slice(&self) -> Option<&[u8]> { - if self.len() == 1 { - Some(self[0].as_ref()) + if sub.any_left() { + Err(InvalidMessage::TrailingData("SingleProtocolName")) } else { - None + Ok(Self(item.to_owned())) } } } +impl AsRef> for SingleProtocolName { + fn as_ref(&self) -> &ApplicationProtocol<'static> { + &self.0 + } +} + // --- TLS 1.3 Key shares --- #[derive(Clone, Debug)] -pub struct KeyShareEntry { +pub(crate) struct KeyShareEntry { pub(crate) group: NamedGroup, - pub(crate) payload: PayloadU16, + /// RFC8446: `opaque key_exchange<1..2^16-1>;` + pub(crate) payload: SizedPayload<'static, u16, NonEmpty>, } impl KeyShareEntry { - pub fn new(group: NamedGroup, payload: impl Into>) -> Self { + pub(crate) fn new(group: NamedGroup, payload: impl Into>) -> Self { Self { group, - payload: PayloadU16::new(payload.into()), + payload: SizedPayload::from(Payload::new(payload.into())), } } - - pub fn group(&self) -> NamedGroup { - self.group - } } impl Codec<'_> for KeyShareEntry { @@ -377,873 +261,246 @@ impl Codec<'_> for KeyShareEntry { self.payload.encode(bytes); } - fn read(r: &mut Reader<'_>) -> Result { - let group = NamedGroup::read(r)?; - let payload = PayloadU16::read(r)?; - - Ok(Self { group, payload }) - } -} - -// --- TLS 1.3 PresharedKey offers --- -#[derive(Clone, Debug)] -pub(crate) struct PresharedKeyIdentity { - pub(crate) identity: PayloadU16, - pub(crate) obfuscated_ticket_age: u32, -} - -impl PresharedKeyIdentity { - pub(crate) fn new(id: Vec, age: u32) -> Self { - Self { - identity: PayloadU16::new(id), - obfuscated_ticket_age: age, - } - } -} - -impl Codec<'_> for PresharedKeyIdentity { - fn encode(&self, bytes: &mut Vec) { - self.identity.encode(bytes); - self.obfuscated_ticket_age.encode(bytes); - } - fn read(r: &mut Reader<'_>) -> Result { Ok(Self { - identity: PayloadU16::read(r)?, - obfuscated_ticket_age: u32::read(r)?, + group: NamedGroup::read(r)?, + payload: SizedPayload::read(r)?.into_owned(), }) } } -impl TlsListElement for PresharedKeyIdentity { - const SIZE_LEN: ListLength = ListLength::U16; -} - -wrapped_payload!(pub(crate) struct PresharedKeyBinder, PayloadU8,); +// --- -impl TlsListElement for PresharedKeyBinder { +/// RFC8446: `KeyShareEntry client_shares<0..2^16-1>;` +impl TlsListElement for KeyShareEntry { const SIZE_LEN: ListLength = ListLength::U16; } -#[derive(Clone, Debug)] -pub struct PresharedKeyOffer { - pub(crate) identities: Vec, - pub(crate) binders: Vec, +/// The body of the `SupportedVersions` extension when it appears in a +/// `ClientHello` +/// +/// This is documented as a preference-order vector, but we (as a server) +/// ignore the preference of the client. +/// +/// RFC8446: `ProtocolVersion versions<2..254>;` +#[derive(Clone, Copy, Debug, Default)] +pub(crate) struct SupportedProtocolVersions { + pub(crate) tls13: bool, + pub(crate) tls12: bool, } -impl PresharedKeyOffer { - /// Make a new one with one entry. - pub(crate) fn new(id: PresharedKeyIdentity, binder: Vec) -> Self { - Self { - identities: vec![id], - binders: vec![PresharedKeyBinder::from(binder)], +impl SupportedProtocolVersions { + /// Return true if `filter` returns true for any enabled version. + pub(crate) fn any(&self, filter: impl Fn(ProtocolVersion) -> bool) -> bool { + if self.tls13 && filter(ProtocolVersion::TLSv1_3) { + return true; + } + if self.tls12 && filter(ProtocolVersion::TLSv1_2) { + return true; } + false } + + const LIST_LENGTH: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("ProtocolVersions"), + }; } -impl Codec<'_> for PresharedKeyOffer { +impl Codec<'_> for SupportedProtocolVersions { fn encode(&self, bytes: &mut Vec) { - self.identities.encode(bytes); - self.binders.encode(bytes); + let inner = LengthPrefixedBuffer::new(Self::LIST_LENGTH, bytes); + if self.tls13 { + ProtocolVersion::TLSv1_3.encode(inner.buf); + } + if self.tls12 { + ProtocolVersion::TLSv1_2.encode(inner.buf); + } } - fn read(r: &mut Reader<'_>) -> Result { - Ok(Self { - identities: Vec::read(r)?, - binders: Vec::read(r)?, - }) - } -} + fn read(reader: &mut Reader<'_>) -> Result { + let mut tls12 = false; + let mut tls13 = false; -// --- RFC6066 certificate status request --- -wrapped_payload!(pub(crate) struct ResponderId, PayloadU16,); + for pv in TlsListIter::::new(reader)? { + match pv? { + ProtocolVersion::TLSv1_3 => tls13 = true, + ProtocolVersion::TLSv1_2 => tls12 = true, + _ => continue, + }; + } -impl TlsListElement for ResponderId { - const SIZE_LEN: ListLength = ListLength::U16; + Ok(Self { tls13, tls12 }) + } } -#[derive(Clone, Debug)] -pub struct OcspCertificateStatusRequest { - pub(crate) responder_ids: Vec, - pub(crate) extensions: PayloadU16, +impl TlsListElement for ProtocolVersion { + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("ProtocolVersions"), + }; } -impl Codec<'_> for OcspCertificateStatusRequest { - fn encode(&self, bytes: &mut Vec) { - CertificateStatusType::OCSP.encode(bytes); - self.responder_ids.encode(bytes); - self.extensions.encode(bytes); - } - - fn read(r: &mut Reader<'_>) -> Result { - Ok(Self { - responder_ids: Vec::read(r)?, - extensions: PayloadU16::read(r)?, - }) - } +/// RFC7250: `CertificateType client_certificate_types<1..2^8-1>;` +/// +/// Ditto `CertificateType server_certificate_types<1..2^8-1>;` +impl TlsListElement for CertificateType { + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("CertificateTypes"), + }; } -#[derive(Clone, Debug)] -pub enum CertificateStatusRequest { - Ocsp(OcspCertificateStatusRequest), - Unknown((CertificateStatusType, Payload<'static>)), +/// RFC8879: `CertificateCompressionAlgorithm algorithms<2..2^8-2>;` +impl TlsListElement for CertificateCompressionAlgorithm { + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("CertificateCompressionAlgorithms"), + }; } -impl Codec<'_> for CertificateStatusRequest { - fn encode(&self, bytes: &mut Vec) { - match self { - Self::Ocsp(ref r) => r.encode(bytes), - Self::Unknown((typ, payload)) => { - typ.encode(bytes); - payload.encode(bytes); - } - } - } - - fn read(r: &mut Reader<'_>) -> Result { - let typ = CertificateStatusType::read(r)?; +/// A precursor to `ClientExtensions`, allowing customisation. +/// +/// This is smaller than `ClientExtensions`, as it only contains the extensions +/// we need to vary between different protocols (eg, TCP-TLS versus QUIC). +#[derive(Clone, Default)] +pub(crate) struct ClientExtensionsInput { + /// QUIC transport parameters + pub(crate) transport_parameters: Option, + + /// ALPN protocols + pub(crate) protocols: Option>>, +} + +impl ClientExtensionsInput { + pub(crate) fn from_alpn(alpn_protocols: Vec>) -> Self { + let protocols = match alpn_protocols.is_empty() { + true => None, + false => Some(alpn_protocols), + }; - match typ { - CertificateStatusType::OCSP => { - let ocsp_req = OcspCertificateStatusRequest::read(r)?; - Ok(Self::Ocsp(ocsp_req)) - } - _ => { - let data = Payload::read(r).into_owned(); - Ok(Self::Unknown((typ, data))) - } + Self { + transport_parameters: None, + protocols, } } } -impl CertificateStatusRequest { - pub(crate) fn build_ocsp() -> Self { - let ocsp = OcspCertificateStatusRequest { - responder_ids: Vec::new(), - extensions: PayloadU16::empty(), - }; - Self::Ocsp(ocsp) - } +#[derive(Clone)] +pub(crate) enum TransportParameters { + /// QUIC transport parameters (RFC9001) + Quic(Payload<'static>), } -// --- - -impl TlsListElement for PSKKeyExchangeMode { - const SIZE_LEN: ListLength = ListLength::U8; +#[derive(Default)] +pub(crate) struct ServerExtensionsInput { + /// QUIC transport parameters + pub(crate) transport_parameters: Option, } -impl TlsListElement for KeyShareEntry { - const SIZE_LEN: ListLength = ListLength::U16; +/// RFC8446: `CipherSuite cipher_suites<2..2^16-2>;` +impl TlsListElement for CipherSuite { + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("CipherSuites"), + }; } -impl TlsListElement for ProtocolVersion { - const SIZE_LEN: ListLength = ListLength::U8; +/// RFC5246: `CompressionMethod compression_methods<1..2^8-1>;` +impl TlsListElement for Compression { + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("Compressions"), + }; } -impl TlsListElement for CertificateType { - const SIZE_LEN: ListLength = ListLength::U8; +/// RFC 9849: `ExtensionType OuterExtensions<2..254>;` +impl TlsListElement for ExtensionType { + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("ExtensionTypes"), + }; } -impl TlsListElement for CertificateCompressionAlgorithm { - const SIZE_LEN: ListLength = ListLength::U8; +extension_struct! { + /// A representation of extensions present in a `HelloRetryRequest` message + pub(crate) struct HelloRetryRequestExtensions<'a> { + ExtensionType::KeyShare => + pub(crate) key_share: Option, + + ExtensionType::Cookie => + pub(crate) cookie: Option>, + + ExtensionType::SupportedVersions => + pub(crate) supported_versions: Option, + + ExtensionType::EncryptedClientHello => + pub(crate) encrypted_client_hello: Option>, + } + { + /// Records decoding order of records, and controls encoding order. + pub(crate) order: Option>, + } } -#[derive(Clone, Debug)] -pub enum ClientExtension { - EcPointFormats(Vec), - NamedGroups(Vec), - SignatureAlgorithms(Vec), - ServerName(Vec), - SessionTicket(ClientSessionTicket), - Protocols(Vec), - SupportedVersions(Vec), - KeyShare(Vec), - PresharedKeyModes(Vec), - PresharedKey(PresharedKeyOffer), - Cookie(PayloadU16), - ExtendedMasterSecretRequest, - CertificateStatusRequest(CertificateStatusRequest), - ServerCertTypes(Vec), - ClientCertTypes(Vec), - TransportParameters(Vec), - TransportParametersDraft(Vec), - EarlyData, - CertificateCompressionAlgorithms(Vec), - EncryptedClientHello(EncryptedClientHello), - EncryptedClientHelloOuterExtensions(Vec), - AuthorityNames(Vec), - Unknown(UnknownExtension), -} - -impl ClientExtension { - pub(crate) fn ext_type(&self) -> ExtensionType { - match *self { - Self::EcPointFormats(_) => ExtensionType::ECPointFormats, - Self::NamedGroups(_) => ExtensionType::EllipticCurves, - Self::SignatureAlgorithms(_) => ExtensionType::SignatureAlgorithms, - Self::ServerName(_) => ExtensionType::ServerName, - Self::SessionTicket(_) => ExtensionType::SessionTicket, - Self::Protocols(_) => ExtensionType::ALProtocolNegotiation, - Self::SupportedVersions(_) => ExtensionType::SupportedVersions, - Self::KeyShare(_) => ExtensionType::KeyShare, - Self::PresharedKeyModes(_) => ExtensionType::PSKKeyExchangeModes, - Self::PresharedKey(_) => ExtensionType::PreSharedKey, - Self::Cookie(_) => ExtensionType::Cookie, - Self::ExtendedMasterSecretRequest => ExtensionType::ExtendedMasterSecret, - Self::CertificateStatusRequest(_) => ExtensionType::StatusRequest, - Self::ClientCertTypes(_) => ExtensionType::ClientCertificateType, - Self::ServerCertTypes(_) => ExtensionType::ServerCertificateType, - Self::TransportParameters(_) => ExtensionType::TransportParameters, - Self::TransportParametersDraft(_) => ExtensionType::TransportParametersDraft, - Self::EarlyData => ExtensionType::EarlyData, - Self::CertificateCompressionAlgorithms(_) => ExtensionType::CompressCertificate, - Self::EncryptedClientHello(_) => ExtensionType::EncryptedClientHello, - Self::EncryptedClientHelloOuterExtensions(_) => { - ExtensionType::EncryptedClientHelloOuterExtensions - } - Self::AuthorityNames(_) => ExtensionType::CertificateAuthorities, - Self::Unknown(ref r) => r.typ, +impl HelloRetryRequestExtensions<'_> { + fn into_owned(self) -> HelloRetryRequestExtensions<'static> { + let Self { + key_share, + cookie, + supported_versions, + encrypted_client_hello, + order, + } = self; + HelloRetryRequestExtensions { + key_share, + cookie: cookie.map(|x| x.into_owned()), + supported_versions, + encrypted_client_hello: encrypted_client_hello.map(|x| x.into_owned()), + order, } } } -impl Codec<'_> for ClientExtension { +impl<'a> Codec<'a> for HelloRetryRequestExtensions<'a> { fn encode(&self, bytes: &mut Vec) { - self.ext_type().encode(bytes); - - let nested = LengthPrefixedBuffer::new(ListLength::U16, bytes); - match *self { - Self::EcPointFormats(ref r) => r.encode(nested.buf), - Self::NamedGroups(ref r) => r.encode(nested.buf), - Self::SignatureAlgorithms(ref r) => r.encode(nested.buf), - Self::ServerName(ref r) => r.encode(nested.buf), - Self::SessionTicket(ClientSessionTicket::Request) - | Self::ExtendedMasterSecretRequest - | Self::EarlyData => {} - Self::SessionTicket(ClientSessionTicket::Offer(ref r)) => r.encode(nested.buf), - Self::Protocols(ref r) => r.encode(nested.buf), - Self::SupportedVersions(ref r) => r.encode(nested.buf), - Self::KeyShare(ref r) => r.encode(nested.buf), - Self::PresharedKeyModes(ref r) => r.encode(nested.buf), - Self::PresharedKey(ref r) => r.encode(nested.buf), - Self::Cookie(ref r) => r.encode(nested.buf), - Self::CertificateStatusRequest(ref r) => r.encode(nested.buf), - Self::ClientCertTypes(ref r) => r.encode(nested.buf), - Self::ServerCertTypes(ref r) => r.encode(nested.buf), - Self::TransportParameters(ref r) | Self::TransportParametersDraft(ref r) => { - nested.buf.extend_from_slice(r); - } - Self::CertificateCompressionAlgorithms(ref r) => r.encode(nested.buf), - Self::EncryptedClientHello(ref r) => r.encode(nested.buf), - Self::EncryptedClientHelloOuterExtensions(ref r) => r.encode(nested.buf), - Self::AuthorityNames(ref r) => r.encode(nested.buf), - Self::Unknown(ref r) => r.encode(nested.buf), + let extensions = LengthPrefixedBuffer::new(ListLength::U16, bytes); + + for ext in self + .order + .as_deref() + .unwrap_or(Self::ALL_EXTENSIONS) + { + self.encode_one(*ext, extensions.buf); } } - fn read(r: &mut Reader<'_>) -> Result { - let typ = ExtensionType::read(r)?; - let len = u16::read(r)? as usize; - let mut sub = r.sub(len)?; - - let ext = match typ { - ExtensionType::ECPointFormats => Self::EcPointFormats(Vec::read(&mut sub)?), - ExtensionType::EllipticCurves => Self::NamedGroups(Vec::read(&mut sub)?), - ExtensionType::SignatureAlgorithms => Self::SignatureAlgorithms(Vec::read(&mut sub)?), - ExtensionType::ServerName => Self::ServerName(Vec::read(&mut sub)?), - ExtensionType::SessionTicket => { - if sub.any_left() { - let contents = Payload::read(&mut sub).into_owned(); - Self::SessionTicket(ClientSessionTicket::Offer(contents)) - } else { - Self::SessionTicket(ClientSessionTicket::Request) - } - } - ExtensionType::ALProtocolNegotiation => Self::Protocols(Vec::read(&mut sub)?), - ExtensionType::SupportedVersions => Self::SupportedVersions(Vec::read(&mut sub)?), - ExtensionType::KeyShare => Self::KeyShare(Vec::read(&mut sub)?), - ExtensionType::PSKKeyExchangeModes => Self::PresharedKeyModes(Vec::read(&mut sub)?), - ExtensionType::PreSharedKey => Self::PresharedKey(PresharedKeyOffer::read(&mut sub)?), - ExtensionType::Cookie => Self::Cookie(PayloadU16::read(&mut sub)?), - ExtensionType::ExtendedMasterSecret if !sub.any_left() => { - Self::ExtendedMasterSecretRequest - } - ExtensionType::ClientCertificateType => Self::ClientCertTypes(Vec::read(&mut sub)?), - ExtensionType::ServerCertificateType => Self::ServerCertTypes(Vec::read(&mut sub)?), - ExtensionType::StatusRequest => { - let csr = CertificateStatusRequest::read(&mut sub)?; - Self::CertificateStatusRequest(csr) - } - ExtensionType::TransportParameters => Self::TransportParameters(sub.rest().to_vec()), - ExtensionType::TransportParametersDraft => { - Self::TransportParametersDraft(sub.rest().to_vec()) - } - ExtensionType::EarlyData if !sub.any_left() => Self::EarlyData, - ExtensionType::CompressCertificate => { - Self::CertificateCompressionAlgorithms(Vec::read(&mut sub)?) - } - ExtensionType::EncryptedClientHelloOuterExtensions => { - Self::EncryptedClientHelloOuterExtensions(Vec::read(&mut sub)?) - } - ExtensionType::CertificateAuthorities => Self::AuthorityNames(Vec::read(&mut sub)?), - _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), - }; + fn read(r: &mut Reader<'a>) -> Result { + let mut out = Self::default(); - sub.expect_empty("ClientExtension") - .map(|_| ext) - } -} + // we must record order, so re-encoding round trips. this is needed, + // unfortunately, for ECH HRR confirmation + let mut order = vec![]; -fn trim_hostname_trailing_dot_for_sni(dns_name: &DnsName<'_>) -> DnsName<'static> { - let dns_name_str = dns_name.as_ref(); + let len = usize::from(u16::read(r)?); + let mut sub = r.sub(len)?; - // RFC6066: "The hostname is represented as a byte string using - // ASCII encoding without a trailing dot" - if dns_name_str.ends_with('.') { - let trimmed = &dns_name_str[0..dns_name_str.len() - 1]; - DnsName::try_from(trimmed) - .unwrap() - .to_owned() - } else { - dns_name.to_owned() - } -} + while sub.any_left() { + let typ = out.read_one(&mut sub, |_unk| { + Err(InvalidMessage::UnknownHelloRetryRequestExtension) + })?; -impl ClientExtension { - /// Make a basic SNI ServerNameRequest quoting `hostname`. - pub(crate) fn make_sni(dns_name: &DnsName<'_>) -> Self { - let name = ServerName { - typ: ServerNameType::HostName, - payload: ServerNamePayload::new_hostname(trim_hostname_trailing_dot_for_sni(dns_name)), - }; + order.push(typ); + } - Self::ServerName(vec![name]) + out.order = Some(order); + Ok(out) } } #[derive(Clone, Debug)] -pub enum ClientSessionTicket { - Request, - Offer(Payload<'static>), +pub(crate) struct HelloRetryRequest { + pub(crate) legacy_version: ProtocolVersion, + pub(crate) session_id: SessionId, + pub(crate) cipher_suite: CipherSuite, + pub(crate) extensions: HelloRetryRequestExtensions<'static>, } -#[derive(Clone, Debug)] -pub enum ServerExtension { - EcPointFormats(Vec), - ServerNameAck, - SessionTicketAck, - RenegotiationInfo(PayloadU8), - Protocols(Vec), - KeyShare(KeyShareEntry), - PresharedKey(u16), - ExtendedMasterSecretAck, - CertificateStatusAck, - ServerCertType(CertificateType), - ClientCertType(CertificateType), - SupportedVersions(ProtocolVersion), - TransportParameters(Vec), - TransportParametersDraft(Vec), - EarlyData, - EncryptedClientHello(ServerEncryptedClientHello), - Unknown(UnknownExtension), -} - -impl ServerExtension { - pub(crate) fn ext_type(&self) -> ExtensionType { - match *self { - Self::EcPointFormats(_) => ExtensionType::ECPointFormats, - Self::ServerNameAck => ExtensionType::ServerName, - Self::SessionTicketAck => ExtensionType::SessionTicket, - Self::RenegotiationInfo(_) => ExtensionType::RenegotiationInfo, - Self::Protocols(_) => ExtensionType::ALProtocolNegotiation, - Self::KeyShare(_) => ExtensionType::KeyShare, - Self::PresharedKey(_) => ExtensionType::PreSharedKey, - Self::ClientCertType(_) => ExtensionType::ClientCertificateType, - Self::ServerCertType(_) => ExtensionType::ServerCertificateType, - Self::ExtendedMasterSecretAck => ExtensionType::ExtendedMasterSecret, - Self::CertificateStatusAck => ExtensionType::StatusRequest, - Self::SupportedVersions(_) => ExtensionType::SupportedVersions, - Self::TransportParameters(_) => ExtensionType::TransportParameters, - Self::TransportParametersDraft(_) => ExtensionType::TransportParametersDraft, - Self::EarlyData => ExtensionType::EarlyData, - Self::EncryptedClientHello(_) => ExtensionType::EncryptedClientHello, - Self::Unknown(ref r) => r.typ, - } - } -} - -impl Codec<'_> for ServerExtension { - fn encode(&self, bytes: &mut Vec) { - self.ext_type().encode(bytes); - - let nested = LengthPrefixedBuffer::new(ListLength::U16, bytes); - match *self { - Self::EcPointFormats(ref r) => r.encode(nested.buf), - Self::ServerNameAck - | Self::SessionTicketAck - | Self::ExtendedMasterSecretAck - | Self::CertificateStatusAck - | Self::EarlyData => {} - Self::RenegotiationInfo(ref r) => r.encode(nested.buf), - Self::Protocols(ref r) => r.encode(nested.buf), - Self::KeyShare(ref r) => r.encode(nested.buf), - Self::PresharedKey(r) => r.encode(nested.buf), - Self::ClientCertType(r) => r.encode(nested.buf), - Self::ServerCertType(r) => r.encode(nested.buf), - Self::SupportedVersions(ref r) => r.encode(nested.buf), - Self::TransportParameters(ref r) | Self::TransportParametersDraft(ref r) => { - nested.buf.extend_from_slice(r); - } - Self::EncryptedClientHello(ref r) => r.encode(nested.buf), - Self::Unknown(ref r) => r.encode(nested.buf), - } - } - - fn read(r: &mut Reader<'_>) -> Result { - let typ = ExtensionType::read(r)?; - let len = u16::read(r)? as usize; - let mut sub = r.sub(len)?; - - let ext = match typ { - ExtensionType::ECPointFormats => Self::EcPointFormats(Vec::read(&mut sub)?), - ExtensionType::ServerName => Self::ServerNameAck, - ExtensionType::SessionTicket => Self::SessionTicketAck, - ExtensionType::StatusRequest => Self::CertificateStatusAck, - ExtensionType::RenegotiationInfo => Self::RenegotiationInfo(PayloadU8::read(&mut sub)?), - ExtensionType::ALProtocolNegotiation => Self::Protocols(Vec::read(&mut sub)?), - ExtensionType::ClientCertificateType => { - Self::ClientCertType(CertificateType::read(&mut sub)?) - } - ExtensionType::ServerCertificateType => { - Self::ServerCertType(CertificateType::read(&mut sub)?) - } - ExtensionType::KeyShare => Self::KeyShare(KeyShareEntry::read(&mut sub)?), - ExtensionType::PreSharedKey => Self::PresharedKey(u16::read(&mut sub)?), - ExtensionType::ExtendedMasterSecret => Self::ExtendedMasterSecretAck, - ExtensionType::SupportedVersions => { - Self::SupportedVersions(ProtocolVersion::read(&mut sub)?) - } - ExtensionType::TransportParameters => Self::TransportParameters(sub.rest().to_vec()), - ExtensionType::TransportParametersDraft => { - Self::TransportParametersDraft(sub.rest().to_vec()) - } - ExtensionType::EarlyData => Self::EarlyData, - ExtensionType::EncryptedClientHello => { - Self::EncryptedClientHello(ServerEncryptedClientHello::read(&mut sub)?) - } - _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), - }; - - sub.expect_empty("ServerExtension") - .map(|_| ext) - } -} - -impl ServerExtension { - pub(crate) fn make_alpn(proto: &[&[u8]]) -> Self { - Self::Protocols(Vec::from_slices(proto)) - } - - #[cfg(feature = "tls12")] - pub(crate) fn make_empty_renegotiation_info() -> Self { - let empty = Vec::new(); - Self::RenegotiationInfo(PayloadU8::new(empty)) - } -} - -#[derive(Clone, Debug)] -pub struct ClientHelloPayload { - pub client_version: ProtocolVersion, - pub random: Random, - pub session_id: SessionId, - pub cipher_suites: Vec, - pub compression_methods: Vec, - pub extensions: Vec, -} - -impl Codec<'_> for ClientHelloPayload { - fn encode(&self, bytes: &mut Vec) { - self.payload_encode(bytes, Encoding::Standard) - } - - fn read(r: &mut Reader<'_>) -> Result { - let mut ret = Self { - client_version: ProtocolVersion::read(r)?, - random: Random::read(r)?, - session_id: SessionId::read(r)?, - cipher_suites: Vec::read(r)?, - compression_methods: Vec::read(r)?, - extensions: Vec::new(), - }; - - if r.any_left() { - ret.extensions = Vec::read(r)?; - } - - match (r.any_left(), ret.extensions.is_empty()) { - (true, _) => Err(InvalidMessage::TrailingData("ClientHelloPayload")), - (_, true) => Err(InvalidMessage::MissingData("ClientHelloPayload")), - _ => Ok(ret), - } - } -} - -impl TlsListElement for CipherSuite { - const SIZE_LEN: ListLength = ListLength::U16; -} - -impl TlsListElement for Compression { - const SIZE_LEN: ListLength = ListLength::U8; -} - -impl TlsListElement for ClientExtension { - const SIZE_LEN: ListLength = ListLength::U16; -} - -impl TlsListElement for ExtensionType { - const SIZE_LEN: ListLength = ListLength::U8; -} - -impl ClientHelloPayload { - pub(crate) fn ech_inner_encoding(&self, to_compress: Vec) -> Vec { - let mut bytes = Vec::new(); - self.payload_encode(&mut bytes, Encoding::EchInnerHello { to_compress }); - bytes - } - - pub(crate) fn payload_encode(&self, bytes: &mut Vec, purpose: Encoding) { - self.client_version.encode(bytes); - self.random.encode(bytes); - - match purpose { - // SessionID is required to be empty in the encoded inner client hello. - Encoding::EchInnerHello { .. } => SessionId::empty().encode(bytes), - _ => self.session_id.encode(bytes), - } - - self.cipher_suites.encode(bytes); - self.compression_methods.encode(bytes); - - let to_compress = match purpose { - // Compressed extensions must be replaced in the encoded inner client hello. - Encoding::EchInnerHello { to_compress } if !to_compress.is_empty() => to_compress, - _ => { - if !self.extensions.is_empty() { - self.extensions.encode(bytes); - } - return; - } - }; - - // Safety: not empty check in match guard. - let first_compressed_type = *to_compress.first().unwrap(); - - // Compressed extensions are in a contiguous range and must be replaced - // with a marker extension. - let compressed_start_idx = self - .extensions - .iter() - .position(|ext| ext.ext_type() == first_compressed_type); - let compressed_end_idx = compressed_start_idx.map(|start| start + to_compress.len()); - let marker_ext = ClientExtension::EncryptedClientHelloOuterExtensions(to_compress); - - let exts = self - .extensions - .iter() - .enumerate() - .filter_map(|(i, ext)| { - if Some(i) == compressed_start_idx { - Some(&marker_ext) - } else if Some(i) > compressed_start_idx && Some(i) < compressed_end_idx { - None - } else { - Some(ext) - } - }); - - let nested = LengthPrefixedBuffer::new(ListLength::U16, bytes); - for ext in exts { - ext.encode(nested.buf); - } - } - - /// Returns true if there is more than one extension of a given - /// type. - pub(crate) fn has_duplicate_extension(&self) -> bool { - has_duplicates::<_, _, u16>( - self.extensions - .iter() - .map(|ext| ext.ext_type()), - ) - } - - pub(crate) fn find_extension(&self, ext: ExtensionType) -> Option<&ClientExtension> { - self.extensions - .iter() - .find(|x| x.ext_type() == ext) - } - - pub(crate) fn sni_extension(&self) -> Option<&[ServerName]> { - let ext = self.find_extension(ExtensionType::ServerName)?; - match *ext { - // Does this comply with RFC6066? - // - // [RFC6066][] specifies that literal IP addresses are illegal in - // `ServerName`s with a `name_type` of `host_name`. - // - // Some clients incorrectly send such extensions: we choose to - // successfully parse these (into `ServerNamePayload::IpAddress`) - // but then act like the client sent no `server_name` extension. - // - // [RFC6066]: https://datatracker.ietf.org/doc/html/rfc6066#section-3 - ClientExtension::ServerName(ref req) - if !req - .iter() - .any(|name| matches!(name.payload, ServerNamePayload::IpAddress(_))) => - { - Some(req) - } - _ => None, - } - } - - pub fn sigalgs_extension(&self) -> Option<&[SignatureScheme]> { - let ext = self.find_extension(ExtensionType::SignatureAlgorithms)?; - match *ext { - ClientExtension::SignatureAlgorithms(ref req) => Some(req), - _ => None, - } - } - - pub(crate) fn namedgroups_extension(&self) -> Option<&[NamedGroup]> { - let ext = self.find_extension(ExtensionType::EllipticCurves)?; - match *ext { - ClientExtension::NamedGroups(ref req) => Some(req), - _ => None, - } - } - - #[cfg(feature = "tls12")] - pub(crate) fn ecpoints_extension(&self) -> Option<&[ECPointFormat]> { - let ext = self.find_extension(ExtensionType::ECPointFormats)?; - match *ext { - ClientExtension::EcPointFormats(ref req) => Some(req), - _ => None, - } - } - - pub(crate) fn server_certificate_extension(&self) -> Option<&[CertificateType]> { - let ext = self.find_extension(ExtensionType::ServerCertificateType)?; - match ext { - ClientExtension::ServerCertTypes(req) => Some(req), - _ => None, - } - } - - pub(crate) fn client_certificate_extension(&self) -> Option<&[CertificateType]> { - let ext = self.find_extension(ExtensionType::ClientCertificateType)?; - match ext { - ClientExtension::ClientCertTypes(req) => Some(req), - _ => None, - } - } - - pub(crate) fn alpn_extension(&self) -> Option<&Vec> { - let ext = self.find_extension(ExtensionType::ALProtocolNegotiation)?; - match *ext { - ClientExtension::Protocols(ref req) => Some(req), - _ => None, - } - } - - pub(crate) fn quic_params_extension(&self) -> Option> { - let ext = self - .find_extension(ExtensionType::TransportParameters) - .or_else(|| self.find_extension(ExtensionType::TransportParametersDraft))?; - match *ext { - ClientExtension::TransportParameters(ref bytes) - | ClientExtension::TransportParametersDraft(ref bytes) => Some(bytes.to_vec()), - _ => None, - } - } - - #[cfg(feature = "tls12")] - pub(crate) fn ticket_extension(&self) -> Option<&ClientExtension> { - self.find_extension(ExtensionType::SessionTicket) - } - - pub(crate) fn versions_extension(&self) -> Option<&[ProtocolVersion]> { - let ext = self.find_extension(ExtensionType::SupportedVersions)?; - match *ext { - ClientExtension::SupportedVersions(ref vers) => Some(vers), - _ => None, - } - } - - pub fn keyshare_extension(&self) -> Option<&[KeyShareEntry]> { - let ext = self.find_extension(ExtensionType::KeyShare)?; - match *ext { - ClientExtension::KeyShare(ref shares) => Some(shares), - _ => None, - } - } - - pub(crate) fn has_keyshare_extension_with_duplicates(&self) -> bool { - self.keyshare_extension() - .map(|entries| { - has_duplicates::<_, _, u16>( - entries - .iter() - .map(|kse| u16::from(kse.group)), - ) - }) - .unwrap_or_default() - } - - pub(crate) fn psk(&self) -> Option<&PresharedKeyOffer> { - let ext = self.find_extension(ExtensionType::PreSharedKey)?; - match *ext { - ClientExtension::PresharedKey(ref psk) => Some(psk), - _ => None, - } - } - - pub(crate) fn check_psk_ext_is_last(&self) -> bool { - self.extensions - .last() - .is_some_and(|ext| ext.ext_type() == ExtensionType::PreSharedKey) - } - - pub(crate) fn psk_modes(&self) -> Option<&[PSKKeyExchangeMode]> { - let ext = self.find_extension(ExtensionType::PSKKeyExchangeModes)?; - match *ext { - ClientExtension::PresharedKeyModes(ref psk_modes) => Some(psk_modes), - _ => None, - } - } - - pub(crate) fn psk_mode_offered(&self, mode: PSKKeyExchangeMode) -> bool { - self.psk_modes() - .map(|modes| modes.contains(&mode)) - .unwrap_or(false) - } - - pub(crate) fn set_psk_binder(&mut self, binder: impl Into>) { - let last_extension = self.extensions.last_mut(); - if let Some(ClientExtension::PresharedKey(ref mut offer)) = last_extension { - offer.binders[0] = PresharedKeyBinder::from(binder.into()); - } - } - - #[cfg(feature = "tls12")] - pub(crate) fn ems_support_offered(&self) -> bool { - self.find_extension(ExtensionType::ExtendedMasterSecret) - .is_some() - } - - pub(crate) fn early_data_extension_offered(&self) -> bool { - self.find_extension(ExtensionType::EarlyData) - .is_some() - } - - pub(crate) fn certificate_compression_extension( - &self, - ) -> Option<&[CertificateCompressionAlgorithm]> { - let ext = self.find_extension(ExtensionType::CompressCertificate)?; - match *ext { - ClientExtension::CertificateCompressionAlgorithms(ref algs) => Some(algs), - _ => None, - } - } - - pub(crate) fn has_certificate_compression_extension_with_duplicates(&self) -> bool { - if let Some(algs) = self.certificate_compression_extension() { - has_duplicates::<_, _, u16>(algs.iter().cloned()) - } else { - false - } - } - - pub(crate) fn certificate_authorities_extension(&self) -> Option<&[DistinguishedName]> { - match self.find_extension(ExtensionType::CertificateAuthorities)? { - ClientExtension::AuthorityNames(ext) => Some(ext), - _ => unreachable!("extension type checked"), - } - } -} - -#[derive(Clone, Debug)] -pub(crate) enum HelloRetryExtension { - KeyShare(NamedGroup), - Cookie(PayloadU16), - SupportedVersions(ProtocolVersion), - EchHelloRetryRequest(Vec), - Unknown(UnknownExtension), -} - -impl HelloRetryExtension { - pub(crate) fn ext_type(&self) -> ExtensionType { - match *self { - Self::KeyShare(_) => ExtensionType::KeyShare, - Self::Cookie(_) => ExtensionType::Cookie, - Self::SupportedVersions(_) => ExtensionType::SupportedVersions, - Self::EchHelloRetryRequest(_) => ExtensionType::EncryptedClientHello, - Self::Unknown(ref r) => r.typ, - } - } -} - -impl Codec<'_> for HelloRetryExtension { - fn encode(&self, bytes: &mut Vec) { - self.ext_type().encode(bytes); - - let nested = LengthPrefixedBuffer::new(ListLength::U16, bytes); - match *self { - Self::KeyShare(ref r) => r.encode(nested.buf), - Self::Cookie(ref r) => r.encode(nested.buf), - Self::SupportedVersions(ref r) => r.encode(nested.buf), - Self::EchHelloRetryRequest(ref r) => { - nested.buf.extend_from_slice(r); - } - Self::Unknown(ref r) => r.encode(nested.buf), - } - } - - fn read(r: &mut Reader<'_>) -> Result { - let typ = ExtensionType::read(r)?; - let len = u16::read(r)? as usize; - let mut sub = r.sub(len)?; - - let ext = match typ { - ExtensionType::KeyShare => Self::KeyShare(NamedGroup::read(&mut sub)?), - ExtensionType::Cookie => Self::Cookie(PayloadU16::read(&mut sub)?), - ExtensionType::SupportedVersions => { - Self::SupportedVersions(ProtocolVersion::read(&mut sub)?) - } - ExtensionType::EncryptedClientHello => Self::EchHelloRetryRequest(sub.rest().to_vec()), - _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), - }; - - sub.expect_empty("HelloRetryExtension") - .map(|_| ext) - } -} - -impl TlsListElement for HelloRetryExtension { - const SIZE_LEN: ListLength = ListLength::U16; -} - -#[derive(Clone, Debug)] -pub struct HelloRetryRequest { - pub(crate) legacy_version: ProtocolVersion, - pub session_id: SessionId, - pub(crate) cipher_suite: CipherSuite, - pub(crate) extensions: Vec, -} - -impl Codec<'_> for HelloRetryRequest { - fn encode(&self, bytes: &mut Vec) { - self.payload_encode(bytes, Encoding::Standard) +impl Codec<'_> for HelloRetryRequest { + fn encode(&self, bytes: &mut Vec) { + self.payload_encode(bytes, Encoding::Standard) } fn read(r: &mut Reader<'_>) -> Result { @@ -1256,73 +513,16 @@ impl Codec<'_> for HelloRetryRequest { } Ok(Self { - legacy_version: ProtocolVersion::Unknown(0), + legacy_version: ProtocolVersion(0), session_id, cipher_suite, - extensions: Vec::read(r)?, + extensions: HelloRetryRequestExtensions::read(r)?.into_owned(), }) } } impl HelloRetryRequest { - /// Returns true if there is more than one extension of a given - /// type. - pub(crate) fn has_duplicate_extension(&self) -> bool { - has_duplicates::<_, _, u16>( - self.extensions - .iter() - .map(|ext| ext.ext_type()), - ) - } - - pub(crate) fn has_unknown_extension(&self) -> bool { - self.extensions.iter().any(|ext| { - ext.ext_type() != ExtensionType::KeyShare - && ext.ext_type() != ExtensionType::SupportedVersions - && ext.ext_type() != ExtensionType::Cookie - && ext.ext_type() != ExtensionType::EncryptedClientHello - }) - } - - fn find_extension(&self, ext: ExtensionType) -> Option<&HelloRetryExtension> { - self.extensions - .iter() - .find(|x| x.ext_type() == ext) - } - - pub fn requested_key_share_group(&self) -> Option { - let ext = self.find_extension(ExtensionType::KeyShare)?; - match *ext { - HelloRetryExtension::KeyShare(grp) => Some(grp), - _ => None, - } - } - - pub(crate) fn cookie(&self) -> Option<&PayloadU16> { - let ext = self.find_extension(ExtensionType::Cookie)?; - match *ext { - HelloRetryExtension::Cookie(ref ck) => Some(ck), - _ => None, - } - } - - pub(crate) fn supported_versions(&self) -> Option { - let ext = self.find_extension(ExtensionType::SupportedVersions)?; - match *ext { - HelloRetryExtension::SupportedVersions(ver) => Some(ver), - _ => None, - } - } - - pub(crate) fn ech(&self) -> Option<&Vec> { - let ext = self.find_extension(ExtensionType::EncryptedClientHello)?; - match *ext { - HelloRetryExtension::EchHelloRetryRequest(ref ech) => Some(ech), - _ => None, - } - } - - fn payload_encode(&self, bytes: &mut Vec, purpose: Encoding) { + pub(super) fn payload_encode(&self, bytes: &mut Vec, purpose: Encoding) { self.legacy_version.encode(bytes); HELLO_RETRY_REQUEST_RANDOM.encode(bytes); self.session_id.encode(bytes); @@ -1333,149 +533,57 @@ impl HelloRetryRequest { // For the purpose of ECH confirmation, the Encrypted Client Hello extension // must have its payload replaced by 8 zero bytes. // - // See draft-ietf-tls-esni-18 7.2.1: - // - Encoding::EchConfirmation => { - let extensions = LengthPrefixedBuffer::new(ListLength::U16, bytes); - for ext in &self.extensions { - match ext.ext_type() { - ExtensionType::EncryptedClientHello => { - HelloRetryExtension::EchHelloRetryRequest(vec![0u8; 8]) - .encode(extensions.buf); - } - _ => { - ext.encode(extensions.buf); - } - } + // See RFC 9849 7.2.1: + // + Encoding::EchConfirmation + if self + .extensions + .encrypted_client_hello + .is_some() => + { + let hrr_confirmation = [0u8; 8]; + HelloRetryRequestExtensions { + encrypted_client_hello: Some(Payload::Borrowed(&hrr_confirmation)), + ..self.extensions.clone() } + .encode(bytes); } - _ => { - self.extensions.encode(bytes); - } + _ => self.extensions.encode(bytes), } } } -#[derive(Clone, Debug)] -pub struct ServerHelloPayload { - pub extensions: Vec, - pub(crate) legacy_version: ProtocolVersion, - pub(crate) random: Random, - pub(crate) session_id: SessionId, - pub(crate) cipher_suite: CipherSuite, - pub(crate) compression_method: Compression, -} - -impl Codec<'_> for ServerHelloPayload { - fn encode(&self, bytes: &mut Vec) { - self.payload_encode(bytes, Encoding::Standard) - } - - // minus version and random, which have already been read. - fn read(r: &mut Reader<'_>) -> Result { - let session_id = SessionId::read(r)?; - let suite = CipherSuite::read(r)?; - let compression = Compression::read(r)?; - - // RFC5246: - // "The presence of extensions can be detected by determining whether - // there are bytes following the compression_method field at the end of - // the ServerHello." - let extensions = if r.any_left() { Vec::read(r)? } else { vec![] }; - - let ret = Self { - legacy_version: ProtocolVersion::Unknown(0), - random: ZERO_RANDOM, - session_id, - cipher_suite: suite, - compression_method: compression, - extensions, - }; - - r.expect_empty("ServerHelloPayload") - .map(|_| ret) - } -} - -impl HasServerExtensions for ServerHelloPayload { - fn extensions(&self) -> &[ServerExtension] { +impl Deref for HelloRetryRequest { + type Target = HelloRetryRequestExtensions<'static>; + fn deref(&self) -> &Self::Target { &self.extensions } } -impl ServerHelloPayload { - pub(crate) fn key_share(&self) -> Option<&KeyShareEntry> { - let ext = self.find_extension(ExtensionType::KeyShare)?; - match *ext { - ServerExtension::KeyShare(ref share) => Some(share), - _ => None, - } - } - - pub(crate) fn psk_index(&self) -> Option { - let ext = self.find_extension(ExtensionType::PreSharedKey)?; - match *ext { - ServerExtension::PresharedKey(ref index) => Some(*index), - _ => None, - } - } - - pub(crate) fn ecpoints_extension(&self) -> Option<&[ECPointFormat]> { - let ext = self.find_extension(ExtensionType::ECPointFormats)?; - match *ext { - ServerExtension::EcPointFormats(ref fmts) => Some(fmts), - _ => None, - } - } - - #[cfg(feature = "tls12")] - pub(crate) fn ems_support_acked(&self) -> bool { - self.find_extension(ExtensionType::ExtendedMasterSecret) - .is_some() - } - - pub(crate) fn supported_versions(&self) -> Option { - let ext = self.find_extension(ExtensionType::SupportedVersions)?; - match *ext { - ServerExtension::SupportedVersions(vers) => Some(vers), - _ => None, - } - } - - fn payload_encode(&self, bytes: &mut Vec, encoding: Encoding) { - self.legacy_version.encode(bytes); - - match encoding { - // When encoding a ServerHello for ECH confirmation, the random value - // has the last 8 bytes zeroed out. - Encoding::EchConfirmation => { - // Indexing safety: self.random is 32 bytes long by definition. - let rand_vec = self.random.get_encoding(); - bytes.extend_from_slice(&rand_vec.as_slice()[..24]); - bytes.extend_from_slice(&[0u8; 8]); - } - _ => self.random.encode(bytes), - } - - self.session_id.encode(bytes); - self.cipher_suite.encode(bytes); - self.compression_method.encode(bytes); - - if !self.extensions.is_empty() { - self.extensions.encode(bytes); - } +impl DerefMut for HelloRetryRequest { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.extensions } } #[derive(Clone, Default, Debug)] -pub struct CertificateChain<'a>(pub Vec>); +pub(crate) struct CertificateChain<'a>(pub(crate) Vec>); + +impl<'a> CertificateChain<'a> { + pub(crate) fn from_signer(signer: &'a SelectedCredential) -> Self { + Self( + signer + .identity + .as_certificates() + .collect(), + ) + } -impl CertificateChain<'_> { pub(crate) fn into_owned(self) -> CertificateChain<'static> { CertificateChain( self.0 .into_iter() - .map(|c| c.into_owned()) + .map(CertificateDer::into_owned) .collect(), ) } @@ -1487,7 +595,12 @@ impl<'a> Codec<'a> for CertificateChain<'a> { } fn read(r: &mut Reader<'a>) -> Result { - Vec::read(r).map(Self) + let mut ret = Vec::new(); + for item in TlsListIter::>::new(r)? { + ret.push(item?); + } + + Ok(Self(ret)) } } @@ -1499,98 +612,62 @@ impl<'a> Deref for CertificateChain<'a> { } } -impl TlsListElement for CertificateDer<'_> { - const SIZE_LEN: ListLength = ListLength::U24 { - max: CERTIFICATE_MAX_SIZE_LIMIT, - error: InvalidMessage::CertificatePayloadTooLarge, - }; -} - -/// TLS has a 16MB size limit on any handshake message, -/// plus a 16MB limit on any given certificate. -/// -/// We contract that to 64KB to limit the amount of memory allocation -/// that is directly controllable by the peer. -pub(crate) const CERTIFICATE_MAX_SIZE_LIMIT: usize = 0x1_0000; - -#[derive(Debug)] -pub(crate) enum CertificateExtension<'a> { - CertificateStatus(CertificateStatus<'a>), - Unknown(UnknownExtension), -} - -impl CertificateExtension<'_> { - pub(crate) fn ext_type(&self) -> ExtensionType { - match *self { - Self::CertificateStatus(_) => ExtensionType::StatusRequest, - Self::Unknown(ref r) => r.typ, - } - } - - pub(crate) fn cert_status(&self) -> Option<&[u8]> { - match *self { - Self::CertificateStatus(ref cs) => Some(cs.ocsp_response.0.bytes()), - _ => None, - } +extension_struct! { + pub(crate) struct CertificateExtensions<'a> { + ExtensionType::StatusRequest => + pub(crate) status: Option>, } +} - pub(crate) fn into_owned(self) -> CertificateExtension<'static> { - match self { - Self::CertificateStatus(st) => CertificateExtension::CertificateStatus(st.into_owned()), - Self::Unknown(unk) => CertificateExtension::Unknown(unk), +impl CertificateExtensions<'_> { + fn into_owned(self) -> CertificateExtensions<'static> { + CertificateExtensions { + status: self.status.map(|s| s.into_owned()), } } } -impl<'a> Codec<'a> for CertificateExtension<'a> { +impl<'a> Codec<'a> for CertificateExtensions<'a> { fn encode(&self, bytes: &mut Vec) { - self.ext_type().encode(bytes); + let extensions = LengthPrefixedBuffer::new(ListLength::U16, bytes); - let nested = LengthPrefixedBuffer::new(ListLength::U16, bytes); - match *self { - Self::CertificateStatus(ref r) => r.encode(nested.buf), - Self::Unknown(ref r) => r.encode(nested.buf), + for ext in Self::ALL_EXTENSIONS { + self.encode_one(*ext, extensions.buf); } } fn read(r: &mut Reader<'a>) -> Result { - let typ = ExtensionType::read(r)?; - let len = u16::read(r)? as usize; + let mut out = Self::default(); + + let len = usize::from(u16::read(r)?); let mut sub = r.sub(len)?; - let ext = match typ { - ExtensionType::StatusRequest => { - let st = CertificateStatus::read(&mut sub)?; - Self::CertificateStatus(st) - } - _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), - }; + while sub.any_left() { + out.read_one(&mut sub, |_unk| { + Err(InvalidMessage::UnknownCertificateExtension) + })?; + } - sub.expect_empty("CertificateExtension") - .map(|_| ext) + Ok(out) } } -impl TlsListElement for CertificateExtension<'_> { - const SIZE_LEN: ListLength = ListLength::U16; -} - #[derive(Debug)] pub(crate) struct CertificateEntry<'a> { pub(crate) cert: CertificateDer<'a>, - pub(crate) exts: Vec>, + pub(crate) extensions: CertificateExtensions<'a>, } impl<'a> Codec<'a> for CertificateEntry<'a> { fn encode(&self, bytes: &mut Vec) { self.cert.encode(bytes); - self.exts.encode(bytes); + self.extensions.encode(bytes); } fn read(r: &mut Reader<'a>) -> Result { Ok(Self { cert: CertificateDer::read(r)?, - exts: Vec::read(r)?, + extensions: CertificateExtensions::read(r)?.into_owned(), }) } } @@ -1599,41 +676,16 @@ impl<'a> CertificateEntry<'a> { pub(crate) fn new(cert: CertificateDer<'a>) -> Self { Self { cert, - exts: Vec::new(), + extensions: CertificateExtensions::default(), } } - pub(crate) fn into_owned(self) -> CertificateEntry<'static> { + fn into_owned(self) -> CertificateEntry<'static> { CertificateEntry { cert: self.cert.into_owned(), - exts: self - .exts - .into_iter() - .map(CertificateExtension::into_owned) - .collect(), + extensions: self.extensions.into_owned(), } } - - pub(crate) fn has_duplicate_extension(&self) -> bool { - has_duplicates::<_, _, u16>( - self.exts - .iter() - .map(|ext| ext.ext_type()), - ) - } - - pub(crate) fn has_unknown_extension(&self) -> bool { - self.exts - .iter() - .any(|ext| ext.ext_type() != ExtensionType::StatusRequest) - } - - pub(crate) fn ocsp_response(&self) -> Option<&[u8]> { - self.exts - .iter() - .find(|ext| ext.ext_type() == ExtensionType::StatusRequest) - .and_then(CertificateExtension::cert_status) - } } impl TlsListElement for CertificateEntry<'_> { @@ -1644,32 +696,23 @@ impl TlsListElement for CertificateEntry<'_> { } #[derive(Debug)] -pub struct CertificatePayloadTls13<'a> { - pub(crate) context: PayloadU8, +pub(crate) struct CertificatePayloadTls13<'a> { + pub(crate) context: SizedPayload<'a, u8>, pub(crate) entries: Vec>, } -impl<'a> Codec<'a> for CertificatePayloadTls13<'a> { - fn encode(&self, bytes: &mut Vec) { - self.context.encode(bytes); - self.entries.encode(bytes); - } - - fn read(r: &mut Reader<'a>) -> Result { - Ok(Self { - context: PayloadU8::read(r)?, - entries: Vec::read(r)?, - }) - } -} - impl<'a> CertificatePayloadTls13<'a> { pub(crate) fn new( - certs: impl Iterator>, + certs: impl Iterator>, ocsp_response: Option<&'a [u8]>, ) -> Self { + let ocsp_response = match ocsp_response { + Some([]) | None => None, + Some(bytes) => Some(bytes), + }; + Self { - context: PayloadU8::empty(), + context: SizedPayload::from(Payload::Borrowed(&[])), entries: certs // zip certificate iterator with `ocsp_response` followed by // an infinite-length iterator of `None`. @@ -1682,10 +725,7 @@ impl<'a> CertificatePayloadTls13<'a> { .map(|(cert, ocsp)| { let mut e = CertificateEntry::new(cert.clone()); if let Some(ocsp) = ocsp { - e.exts - .push(CertificateExtension::CertificateStatus( - CertificateStatus::new(ocsp), - )); + e.extensions.status = Some(CertificateStatus::new(ocsp)); } e }) @@ -1693,9 +733,9 @@ impl<'a> CertificatePayloadTls13<'a> { } } - pub(crate) fn into_owned(self) -> CertificatePayloadTls13<'static> { + pub(super) fn into_owned(self) -> CertificatePayloadTls13<'static> { CertificatePayloadTls13 { - context: self.context, + context: self.context.into_owned(), entries: self .entries .into_iter() @@ -1704,41 +744,15 @@ impl<'a> CertificatePayloadTls13<'a> { } } - pub(crate) fn any_entry_has_duplicate_extension(&self) -> bool { - for entry in &self.entries { - if entry.has_duplicate_extension() { - return true; - } - } - - false - } - - pub(crate) fn any_entry_has_unknown_extension(&self) -> bool { - for entry in &self.entries { - if entry.has_unknown_extension() { - return true; - } - } - - false - } - - pub(crate) fn any_entry_has_extension(&self) -> bool { - for entry in &self.entries { - if !entry.exts.is_empty() { - return true; - } - } - - false - } - pub(crate) fn end_entity_ocsp(&self) -> Vec { - self.entries - .first() - .and_then(CertificateEntry::ocsp_response) - .map(|resp| resp.to_vec()) + let Some(entry) = self.entries.first() else { + return vec![]; + }; + entry + .extensions + .status + .as_ref() + .map(|status| status.ocsp_response.to_vec()) .unwrap_or_default() } @@ -1752,16 +766,18 @@ impl<'a> CertificatePayloadTls13<'a> { } } -/// Describes supported key exchange mechanisms. -#[derive(Clone, Copy, Debug, PartialEq)] -#[non_exhaustive] -pub enum KeyExchangeAlgorithm { - /// Diffie-Hellman Key exchange (with only known parameters as defined in [RFC 7919]). - /// - /// [RFC 7919]: https://datatracker.ietf.org/doc/html/rfc7919 - DHE, - /// Key exchange performed via elliptic curve Diffie-Hellman. - ECDHE, +impl<'a> Codec<'a> for CertificatePayloadTls13<'a> { + fn encode(&self, bytes: &mut Vec) { + self.context.encode(bytes); + self.entries.encode(bytes); + } + + fn read(r: &mut Reader<'a>) -> Result { + Ok(Self { + context: SizedPayload::read(r)?.into_owned(), + entries: Vec::read(r)?, + }) + } } pub(crate) static ALL_KEY_EXCHANGE_ALGORITHMS: &[KeyExchangeAlgorithm] = @@ -1797,25 +813,22 @@ impl Codec<'_> for EcParameters { } } -#[cfg(feature = "tls12")] pub(crate) trait KxDecode<'a>: fmt::Debug + Sized { /// Decode a key exchange message given the key_exchange `algo` fn decode(r: &mut Reader<'a>, algo: KeyExchangeAlgorithm) -> Result; } -#[cfg(feature = "tls12")] #[derive(Debug)] pub(crate) enum ClientKeyExchangeParams { Ecdh(ClientEcdhParams), Dh(ClientDhParams), } -#[cfg(feature = "tls12")] impl ClientKeyExchangeParams { pub(crate) fn pub_key(&self) -> &[u8] { match self { - Self::Ecdh(ecdh) => &ecdh.public.0, - Self::Dh(dh) => &dh.public.0, + Self::Ecdh(ecdh) => ecdh.public.bytes(), + Self::Dh(dh) => dh.public.bytes(), } } @@ -1827,7 +840,6 @@ impl ClientKeyExchangeParams { } } -#[cfg(feature = "tls12")] impl KxDecode<'_> for ClientKeyExchangeParams { fn decode(r: &mut Reader<'_>, algo: KeyExchangeAlgorithm) -> Result { use KeyExchangeAlgorithm::*; @@ -1838,31 +850,29 @@ impl KxDecode<'_> for ClientKeyExchangeParams { } } -#[cfg(feature = "tls12")] #[derive(Debug)] pub(crate) struct ClientEcdhParams { - pub(crate) public: PayloadU8, + /// RFC4492: `opaque point <1..2^8-1>;` + pub(crate) public: SizedPayload<'static, u8, NonEmpty>, } -#[cfg(feature = "tls12")] impl Codec<'_> for ClientEcdhParams { fn encode(&self, bytes: &mut Vec) { self.public.encode(bytes); } fn read(r: &mut Reader<'_>) -> Result { - let pb = PayloadU8::read(r)?; + let pb = SizedPayload::read(r)?.into_owned(); Ok(Self { public: pb }) } } -#[cfg(feature = "tls12")] #[derive(Debug)] pub(crate) struct ClientDhParams { - pub(crate) public: PayloadU16, + /// RFC5246: `opaque dh_Yc<1..2^16-1>;` + pub(crate) public: SizedPayload<'static, u16, NonEmpty>, } -#[cfg(feature = "tls12")] impl Codec<'_> for ClientDhParams { fn encode(&self, bytes: &mut Vec) { self.public.encode(bytes); @@ -1870,7 +880,7 @@ impl Codec<'_> for ClientDhParams { fn read(r: &mut Reader<'_>) -> Result { Ok(Self { - public: PayloadU16::read(r)?, + public: SizedPayload::read(r)?.into_owned(), }) } } @@ -1878,18 +888,18 @@ impl Codec<'_> for ClientDhParams { #[derive(Debug)] pub(crate) struct ServerEcdhParams { pub(crate) curve_params: EcParameters, - pub(crate) public: PayloadU8, + /// RFC4492: `opaque point <1..2^8-1>;` + pub(crate) public: SizedPayload<'static, u8, NonEmpty>, } impl ServerEcdhParams { - #[cfg(feature = "tls12")] pub(crate) fn new(kx: &dyn ActiveKeyExchange) -> Self { Self { curve_params: EcParameters { curve_type: ECCurveType::NamedCurve, named_group: kx.group(), }, - public: PayloadU8::new(kx.pub_key().to_vec()), + public: kx.pub_key().to_vec().into(), } } } @@ -1902,7 +912,7 @@ impl Codec<'_> for ServerEcdhParams { fn read(r: &mut Reader<'_>) -> Result { let cp = EcParameters::read(r)?; - let pb = PayloadU8::read(r)?; + let pb = SizedPayload::read(r)?.into_owned(); Ok(Self { curve_params: cp, @@ -1912,30 +922,30 @@ impl Codec<'_> for ServerEcdhParams { } #[derive(Debug)] -#[allow(non_snake_case)] pub(crate) struct ServerDhParams { - pub(crate) dh_p: PayloadU16, - pub(crate) dh_g: PayloadU16, - pub(crate) dh_Ys: PayloadU16, + /// RFC5246: `opaque dh_p<1..2^16-1>;` + pub(crate) dh_p: SizedPayload<'static, u16, NonEmpty>, + /// RFC5246: `opaque dh_g<1..2^16-1>;` + pub(crate) dh_g: SizedPayload<'static, u16, NonEmpty>, + /// RFC5246: `opaque dh_Ys<1..2^16-1>;` + pub(crate) dh_ys: SizedPayload<'static, u16, NonEmpty>, } impl ServerDhParams { - #[cfg(feature = "tls12")] pub(crate) fn new(kx: &dyn ActiveKeyExchange) -> Self { let Some(params) = kx.ffdhe_group() else { panic!("invalid NamedGroup for DHE key exchange: {:?}", kx.group()); }; Self { - dh_p: PayloadU16::new(params.p.to_vec()), - dh_g: PayloadU16::new(params.g.to_vec()), - dh_Ys: PayloadU16::new(kx.pub_key().to_vec()), + dh_p: SizedPayload::from(Payload::new(params.p.to_vec())), + dh_g: SizedPayload::from(Payload::new(params.g.to_vec())), + dh_ys: SizedPayload::from(Payload::new(kx.pub_key().to_vec())), } } - #[cfg(feature = "tls12")] pub(crate) fn as_ffdhe_group(&self) -> FfdheGroup<'_> { - FfdheGroup::from_params_trimming_leading_zeros(&self.dh_p.0, &self.dh_g.0) + FfdheGroup::from_params_trimming_leading_zeros(self.dh_p.bytes(), self.dh_g.bytes()) } } @@ -1943,19 +953,18 @@ impl Codec<'_> for ServerDhParams { fn encode(&self, bytes: &mut Vec) { self.dh_p.encode(bytes); self.dh_g.encode(bytes); - self.dh_Ys.encode(bytes); + self.dh_ys.encode(bytes); } fn read(r: &mut Reader<'_>) -> Result { Ok(Self { - dh_p: PayloadU16::read(r)?, - dh_g: PayloadU16::read(r)?, - dh_Ys: PayloadU16::read(r)?, + dh_p: SizedPayload::read(r)?.into_owned(), + dh_g: SizedPayload::read(r)?.into_owned(), + dh_ys: SizedPayload::read(r)?.into_owned(), }) } } -#[allow(dead_code)] #[derive(Debug)] pub(crate) enum ServerKeyExchangeParams { Ecdh(ServerEcdhParams), @@ -1963,7 +972,6 @@ pub(crate) enum ServerKeyExchangeParams { } impl ServerKeyExchangeParams { - #[cfg(feature = "tls12")] pub(crate) fn new(kx: &dyn ActiveKeyExchange) -> Self { match kx.group().key_exchange_algorithm() { KeyExchangeAlgorithm::DHE => Self::Dh(ServerDhParams::new(kx)), @@ -1971,11 +979,10 @@ impl ServerKeyExchangeParams { } } - #[cfg(feature = "tls12")] pub(crate) fn pub_key(&self) -> &[u8] { match self { - Self::Ecdh(ecdh) => &ecdh.public.0, - Self::Dh(dh) => &dh.dh_Ys.0, + Self::Ecdh(ecdh) => ecdh.public.bytes(), + Self::Dh(dh) => dh.dh_ys.bytes(), } } @@ -1987,7 +994,6 @@ impl ServerKeyExchangeParams { } } -#[cfg(feature = "tls12")] impl KxDecode<'_> for ServerKeyExchangeParams { fn decode(r: &mut Reader<'_>, algo: KeyExchangeAlgorithm) -> Result { use KeyExchangeAlgorithm::*; @@ -1999,20 +1005,20 @@ impl KxDecode<'_> for ServerKeyExchangeParams { } #[derive(Debug)] -pub struct ServerKeyExchange { +pub(crate) struct ServerKeyExchange { pub(crate) params: ServerKeyExchangeParams, pub(crate) dss: DigitallySignedStruct, } impl ServerKeyExchange { - pub fn encode(&self, buf: &mut Vec) { + pub(crate) fn encode(&self, buf: &mut Vec) { self.params.encode(buf); self.dss.encode(buf); } } #[derive(Debug)] -pub enum ServerKeyExchangePayload { +pub(crate) enum ServerKeyExchangePayload { Known(ServerKeyExchange), Unknown(Payload<'static>), } @@ -2025,9 +1031,9 @@ impl From for ServerKeyExchangePayload { impl Codec<'_> for ServerKeyExchangePayload { fn encode(&self, bytes: &mut Vec) { - match *self { - Self::Known(ref x) => x.encode(bytes), - Self::Unknown(ref x) => x.encode(bytes), + match self { + Self::Known(x) => x.encode(bytes), + Self::Unknown(x) => x.encode(bytes), } } @@ -2039,10 +1045,9 @@ impl Codec<'_> for ServerKeyExchangePayload { } impl ServerKeyExchangePayload { - #[cfg(feature = "tls12")] pub(crate) fn unwrap_given_kxa(&self, kxa: KeyExchangeAlgorithm) -> Option { - if let Self::Unknown(ref unk) = *self { - let mut rd = Reader::init(unk.bytes()); + if let Self::Unknown(unk) = self { + let mut rd = Reader::new(unk.bytes()); let result = ServerKeyExchange { params: ServerKeyExchangeParams::decode(&mut rd, kxa).ok()?, @@ -2058,127 +1063,15 @@ impl ServerKeyExchangePayload { } } -// -- EncryptedExtensions (TLS1.3 only) -- - -impl TlsListElement for ServerExtension { - const SIZE_LEN: ListLength = ListLength::U16; -} - -pub(crate) trait HasServerExtensions { - fn extensions(&self) -> &[ServerExtension]; - - /// Returns true if there is more than one extension of a given - /// type. - fn has_duplicate_extension(&self) -> bool { - has_duplicates::<_, _, u16>( - self.extensions() - .iter() - .map(|ext| ext.ext_type()), - ) - } - - fn find_extension(&self, ext: ExtensionType) -> Option<&ServerExtension> { - self.extensions() - .iter() - .find(|x| x.ext_type() == ext) - } - - fn alpn_protocol(&self) -> Option<&[u8]> { - let ext = self.find_extension(ExtensionType::ALProtocolNegotiation)?; - match *ext { - ServerExtension::Protocols(ref protos) => protos.as_single_slice(), - _ => None, - } - } - - fn server_cert_type(&self) -> Option<&CertificateType> { - let ext = self.find_extension(ExtensionType::ServerCertificateType)?; - match ext { - ServerExtension::ServerCertType(req) => Some(req), - _ => None, - } - } - - fn client_cert_type(&self) -> Option<&CertificateType> { - let ext = self.find_extension(ExtensionType::ClientCertificateType)?; - match ext { - ServerExtension::ClientCertType(req) => Some(req), - _ => None, - } - } - - fn quic_params_extension(&self) -> Option> { - let ext = self - .find_extension(ExtensionType::TransportParameters) - .or_else(|| self.find_extension(ExtensionType::TransportParametersDraft))?; - match *ext { - ServerExtension::TransportParameters(ref bytes) - | ServerExtension::TransportParametersDraft(ref bytes) => Some(bytes.to_vec()), - _ => None, - } - } - - fn server_ech_extension(&self) -> Option { - let ext = self.find_extension(ExtensionType::EncryptedClientHello)?; - match ext { - ServerExtension::EncryptedClientHello(ech) => Some(ech.clone()), - _ => None, - } - } - - fn early_data_extension_offered(&self) -> bool { - self.find_extension(ExtensionType::EarlyData) - .is_some() - } -} - -impl HasServerExtensions for Vec { - fn extensions(&self) -> &[ServerExtension] { - self - } -} - +/// RFC5246: `ClientCertificateType certificate_types<1..2^8-1>;` impl TlsListElement for ClientCertificateType { - const SIZE_LEN: ListLength = ListLength::U8; -} - -wrapped_payload!( - /// A `DistinguishedName` is a `Vec` wrapped in internal types. - /// - /// It contains the DER or BER encoded [`Subject` field from RFC 5280](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6) - /// for a single certificate. The Subject field is [encoded as an RFC 5280 `Name`](https://datatracker.ietf.org/doc/html/rfc5280#page-116). - /// It can be decoded using [x509-parser's FromDer trait](https://docs.rs/x509-parser/latest/x509_parser/prelude/trait.FromDer.html). - /// - /// ```ignore - /// for name in distinguished_names { - /// use x509_parser::prelude::FromDer; - /// println!("{}", x509_parser::x509::X509Name::from_der(&name.0)?.1); - /// } - /// ``` - pub struct DistinguishedName, - PayloadU16, -); - -impl DistinguishedName { - /// Create a [`DistinguishedName`] after prepending its outer SEQUENCE encoding. - /// - /// This can be decoded using [x509-parser's FromDer trait](https://docs.rs/x509-parser/latest/x509_parser/prelude/trait.FromDer.html). - /// - /// ```ignore - /// use x509_parser::prelude::FromDer; - /// println!("{}", x509_parser::x509::X509Name::from_der(dn.as_ref())?.1); - /// ``` - pub fn in_sequence(bytes: &[u8]) -> Self { - Self(PayloadU16::new(wrap_in_sequence(bytes))) - } -} - -impl TlsListElement for DistinguishedName { - const SIZE_LEN: ListLength = ListLength::U16; + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("ClientCertificateTypes"), + }; } #[derive(Debug)] -pub struct CertificateRequestPayload { +pub(crate) struct CertificateRequestPayload { pub(crate) certtypes: Vec, pub(crate) sigschemes: Vec, pub(crate) canames: Vec, @@ -2209,74 +1102,57 @@ impl Codec<'_> for CertificateRequestPayload { } } -#[derive(Debug)] -pub(crate) enum CertReqExtension { - SignatureAlgorithms(Vec), - AuthorityNames(Vec), - CertificateCompressionAlgorithms(Vec), - Unknown(UnknownExtension), -} - -impl CertReqExtension { - pub(crate) fn ext_type(&self) -> ExtensionType { - match *self { - Self::SignatureAlgorithms(_) => ExtensionType::SignatureAlgorithms, - Self::AuthorityNames(_) => ExtensionType::CertificateAuthorities, - Self::CertificateCompressionAlgorithms(_) => ExtensionType::CompressCertificate, - Self::Unknown(ref r) => r.typ, - } +extension_struct! { + pub(crate) struct CertificateRequestExtensions { + ExtensionType::SignatureAlgorithms => + pub(crate) signature_algorithms: Option>, + + ExtensionType::CertificateAuthorities => + pub(crate) authority_names: Option>, + + ExtensionType::CompressCertificate => + pub(crate) certificate_compression_algorithms: Option>, } } -impl Codec<'_> for CertReqExtension { +impl Codec<'_> for CertificateRequestExtensions { fn encode(&self, bytes: &mut Vec) { - self.ext_type().encode(bytes); - - let nested = LengthPrefixedBuffer::new(ListLength::U16, bytes); - match *self { - Self::SignatureAlgorithms(ref r) => r.encode(nested.buf), - Self::AuthorityNames(ref r) => r.encode(nested.buf), - Self::CertificateCompressionAlgorithms(ref r) => r.encode(nested.buf), - Self::Unknown(ref r) => r.encode(nested.buf), + let extensions = LengthPrefixedBuffer::new(ListLength::U16, bytes); + + for ext in Self::ALL_EXTENSIONS { + self.encode_one(*ext, extensions.buf); } } fn read(r: &mut Reader<'_>) -> Result { - let typ = ExtensionType::read(r)?; - let len = u16::read(r)? as usize; + let mut out = Self::default(); + + let mut checker = DuplicateExtensionChecker::new(); + + let len = usize::from(u16::read(r)?); let mut sub = r.sub(len)?; - let ext = match typ { - ExtensionType::SignatureAlgorithms => { - let schemes = Vec::read(&mut sub)?; - if schemes.is_empty() { - return Err(InvalidMessage::NoSignatureSchemes); - } - Self::SignatureAlgorithms(schemes) - } - ExtensionType::CertificateAuthorities => { - let cas = Vec::read(&mut sub)?; - Self::AuthorityNames(cas) - } - ExtensionType::CompressCertificate => { - Self::CertificateCompressionAlgorithms(Vec::read(&mut sub)?) - } - _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), - }; + while sub.any_left() { + out.read_one(&mut sub, |unknown| checker.check(unknown))?; + } - sub.expect_empty("CertReqExtension") - .map(|_| ext) - } -} + if out + .signature_algorithms + .as_ref() + .map(|algs| algs.is_empty()) + .unwrap_or_default() + { + return Err(InvalidMessage::NoSignatureSchemes); + } -impl TlsListElement for CertReqExtension { - const SIZE_LEN: ListLength = ListLength::U16; + Ok(out) + } } #[derive(Debug)] -pub struct CertificateRequestPayloadTls13 { - pub(crate) context: PayloadU8, - pub(crate) extensions: Vec, +pub(crate) struct CertificateRequestPayloadTls13 { + pub(crate) context: SizedPayload<'static, u8>, + pub(crate) extensions: CertificateRequestExtensions, } impl Codec<'_> for CertificateRequestPayloadTls13 { @@ -2286,8 +1162,8 @@ impl Codec<'_> for CertificateRequestPayloadTls13 { } fn read(r: &mut Reader<'_>) -> Result { - let context = PayloadU8::read(r)?; - let extensions = Vec::read(r)?; + let context = SizedPayload::read(r)?.into_owned(); + let extensions = CertificateRequestExtensions::read(r)?; Ok(Self { context, @@ -2296,188 +1172,120 @@ impl Codec<'_> for CertificateRequestPayloadTls13 { } } -impl CertificateRequestPayloadTls13 { - pub(crate) fn find_extension(&self, ext: ExtensionType) -> Option<&CertReqExtension> { - self.extensions - .iter() - .find(|x| x.ext_type() == ext) - } - - pub(crate) fn sigalgs_extension(&self) -> Option<&[SignatureScheme]> { - let ext = self.find_extension(ExtensionType::SignatureAlgorithms)?; - match *ext { - CertReqExtension::SignatureAlgorithms(ref sa) => Some(sa), - _ => None, - } - } - - pub(crate) fn authorities_extension(&self) -> Option<&[DistinguishedName]> { - let ext = self.find_extension(ExtensionType::CertificateAuthorities)?; - match *ext { - CertReqExtension::AuthorityNames(ref an) => Some(an), - _ => None, - } - } - - pub(crate) fn certificate_compression_extension( - &self, - ) -> Option<&[CertificateCompressionAlgorithm]> { - let ext = self.find_extension(ExtensionType::CompressCertificate)?; - match *ext { - CertReqExtension::CertificateCompressionAlgorithms(ref comps) => Some(comps), - _ => None, - } - } -} - // -- NewSessionTicket -- #[derive(Debug)] -pub struct NewSessionTicketPayload { - pub(crate) lifetime_hint: u32, +pub(crate) struct NewSessionTicketPayload { + pub(crate) lifetime_hint: Duration, // Tickets can be large (KB), so we deserialise this straight // into an Arc, so it can be passed directly into the client's // session object without copying. - pub(crate) ticket: Arc, + pub(crate) ticket: Arc>, } impl NewSessionTicketPayload { - #[cfg(feature = "tls12")] - pub(crate) fn new(lifetime_hint: u32, ticket: Vec) -> Self { + pub(crate) fn new(lifetime_hint: Duration, ticket: Vec) -> Self { Self { lifetime_hint, - ticket: Arc::new(PayloadU16::new(ticket)), + ticket: Arc::new(SizedPayload::from(Payload::new(ticket))), } } } impl Codec<'_> for NewSessionTicketPayload { fn encode(&self, bytes: &mut Vec) { - self.lifetime_hint.encode(bytes); + (self.lifetime_hint.as_secs() as u32).encode(bytes); self.ticket.encode(bytes); } fn read(r: &mut Reader<'_>) -> Result { - let lifetime = u32::read(r)?; - let ticket = Arc::new(PayloadU16::read(r)?); - Ok(Self { - lifetime_hint: lifetime, - ticket, + lifetime_hint: Duration::from_secs(u32::read(r)? as u64), + ticket: Arc::new(SizedPayload::read(r)?.into_owned()), }) } } // -- NewSessionTicket electric boogaloo -- -#[derive(Debug)] -pub(crate) enum NewSessionTicketExtension { - EarlyData(u32), - Unknown(UnknownExtension), -} - -impl NewSessionTicketExtension { - pub(crate) fn ext_type(&self) -> ExtensionType { - match *self { - Self::EarlyData(_) => ExtensionType::EarlyData, - Self::Unknown(ref r) => r.typ, - } +extension_struct! { + pub(crate) struct NewSessionTicketExtensions { + ExtensionType::EarlyData => + pub(crate) max_early_data_size: Option, } } -impl Codec<'_> for NewSessionTicketExtension { +impl Codec<'_> for NewSessionTicketExtensions { fn encode(&self, bytes: &mut Vec) { - self.ext_type().encode(bytes); + let extensions = LengthPrefixedBuffer::new(ListLength::U16, bytes); - let nested = LengthPrefixedBuffer::new(ListLength::U16, bytes); - match *self { - Self::EarlyData(r) => r.encode(nested.buf), - Self::Unknown(ref r) => r.encode(nested.buf), + for ext in Self::ALL_EXTENSIONS { + self.encode_one(*ext, extensions.buf); } } fn read(r: &mut Reader<'_>) -> Result { - let typ = ExtensionType::read(r)?; - let len = u16::read(r)? as usize; + let mut out = Self::default(); + + let mut checker = DuplicateExtensionChecker::new(); + + let len = usize::from(u16::read(r)?); let mut sub = r.sub(len)?; - let ext = match typ { - ExtensionType::EarlyData => Self::EarlyData(u32::read(&mut sub)?), - _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), - }; + while sub.any_left() { + out.read_one(&mut sub, |unknown| checker.check(unknown))?; + } - sub.expect_empty("NewSessionTicketExtension") - .map(|_| ext) + Ok(out) } } -impl TlsListElement for NewSessionTicketExtension { - const SIZE_LEN: ListLength = ListLength::U16; -} - #[derive(Debug)] -pub struct NewSessionTicketPayloadTls13 { - pub(crate) lifetime: u32, +pub(crate) struct NewSessionTicketPayloadTls13 { + pub(crate) lifetime: Duration, pub(crate) age_add: u32, - pub(crate) nonce: PayloadU8, - pub(crate) ticket: Arc, - pub(crate) exts: Vec, + pub(crate) nonce: SizedPayload<'static, u8>, + pub(crate) ticket: Arc>, + pub(crate) extensions: NewSessionTicketExtensions, } impl NewSessionTicketPayloadTls13 { - pub(crate) fn new(lifetime: u32, age_add: u32, nonce: Vec, ticket: Vec) -> Self { + pub(crate) fn new(lifetime: Duration, age_add: u32, nonce: [u8; 32], ticket: Vec) -> Self { Self { lifetime, age_add, - nonce: PayloadU8::new(nonce), - ticket: Arc::new(PayloadU16::new(ticket)), - exts: vec![], - } - } - - pub(crate) fn has_duplicate_extension(&self) -> bool { - has_duplicates::<_, _, u16>( - self.exts - .iter() - .map(|ext| ext.ext_type()), - ) - } - - pub(crate) fn find_extension(&self, ext: ExtensionType) -> Option<&NewSessionTicketExtension> { - self.exts - .iter() - .find(|x| x.ext_type() == ext) - } - - pub(crate) fn max_early_data_size(&self) -> Option { - let ext = self.find_extension(ExtensionType::EarlyData)?; - match *ext { - NewSessionTicketExtension::EarlyData(ref sz) => Some(*sz), - _ => None, + nonce: nonce.to_vec().into(), + ticket: Arc::new(SizedPayload::from(Payload::new(ticket))), + extensions: NewSessionTicketExtensions::default(), } } } impl Codec<'_> for NewSessionTicketPayloadTls13 { fn encode(&self, bytes: &mut Vec) { - self.lifetime.encode(bytes); + (self.lifetime.as_secs() as u32).encode(bytes); self.age_add.encode(bytes); self.nonce.encode(bytes); self.ticket.encode(bytes); - self.exts.encode(bytes); + self.extensions.encode(bytes); } fn read(r: &mut Reader<'_>) -> Result { - let lifetime = u32::read(r)?; + let lifetime = Duration::from_secs(u32::read(r)? as u64); let age_add = u32::read(r)?; - let nonce = PayloadU8::read(r)?; - let ticket = Arc::new(PayloadU16::read(r)?); - let exts = Vec::read(r)?; + let nonce = SizedPayload::read(r)?.into_owned(); + // nb. RFC8446: `opaque ticket<1..2^16-1>;` + let ticket = Arc::new(match SizedPayload::::read(r) { + Err(InvalidMessage::IllegalEmptyList(_)) => Err(InvalidMessage::EmptyTicketValue), + Err(err) => Err(err), + Ok(pl) => Ok(SizedPayload::from(Payload::new(pl.into_vec()))), + }?); + let extensions = NewSessionTicketExtensions::read(r)?; Ok(Self { lifetime, age_add, nonce, ticket, - exts, + extensions, }) } } @@ -2485,9 +1293,10 @@ impl Codec<'_> for NewSessionTicketPayloadTls13 { // -- RFC6066 certificate status types /// Only supports OCSP -#[derive(Debug)] -pub struct CertificateStatus<'a> { - pub(crate) ocsp_response: PayloadU24<'a>, +#[derive(Clone, Debug)] +pub(crate) struct CertificateStatus<'a> { + /// `opaque OCSPResponse<1..2^24-1>;` + pub(crate) ocsp_response: SizedPayload<'a, U24, NonEmpty>, } impl<'a> Codec<'a> for CertificateStatus<'a> { @@ -2501,7 +1310,7 @@ impl<'a> Codec<'a> for CertificateStatus<'a> { match typ { CertificateStatusType::OCSP => Ok(Self { - ocsp_response: PayloadU24::read(r)?, + ocsp_response: SizedPayload::read(r)?, }), _ => Err(InvalidMessage::InvalidCertificateStatusType), } @@ -2511,13 +1320,12 @@ impl<'a> Codec<'a> for CertificateStatus<'a> { impl<'a> CertificateStatus<'a> { pub(crate) fn new(ocsp: &'a [u8]) -> Self { CertificateStatus { - ocsp_response: PayloadU24(Payload::Borrowed(ocsp)), + ocsp_response: SizedPayload::from(Payload::Borrowed(ocsp)), } } - #[cfg(feature = "tls12")] pub(crate) fn into_inner(self) -> Vec { - self.ocsp_response.0.into_vec() + self.ocsp_response.into_vec() } pub(crate) fn into_owned(self) -> CertificateStatus<'static> { @@ -2530,30 +1338,31 @@ impl<'a> CertificateStatus<'a> { // -- RFC8879 compressed certificates #[derive(Debug)] -pub struct CompressedCertificatePayload<'a> { +pub(crate) struct CompressedCertificatePayload<'a> { pub(crate) alg: CertificateCompressionAlgorithm, pub(crate) uncompressed_len: u32, - pub(crate) compressed: PayloadU24<'a>, + /// `opaque compressed_certificate_message<1..2^24-1>;` + pub(crate) compressed: SizedPayload<'a, U24, NonEmpty>, } impl<'a> Codec<'a> for CompressedCertificatePayload<'a> { fn encode(&self, bytes: &mut Vec) { self.alg.encode(bytes); - codec::u24(self.uncompressed_len).encode(bytes); + U24(self.uncompressed_len).encode(bytes); self.compressed.encode(bytes); } fn read(r: &mut Reader<'a>) -> Result { Ok(Self { alg: CertificateCompressionAlgorithm::read(r)?, - uncompressed_len: codec::u24::read(r)?.0, - compressed: PayloadU24::read(r)?, + uncompressed_len: U24::read(r)?.0, + compressed: SizedPayload::read(r)?, }) } } impl CompressedCertificatePayload<'_> { - fn into_owned(self) -> CompressedCertificatePayload<'static> { + pub(super) fn into_owned(self) -> CompressedCertificatePayload<'static> { CompressedCertificatePayload { compressed: self.compressed.into_owned(), ..self @@ -2564,586 +1373,25 @@ impl CompressedCertificatePayload<'_> { CompressedCertificatePayload { alg: self.alg, uncompressed_len: self.uncompressed_len, - compressed: PayloadU24(Payload::Borrowed(self.compressed.0.bytes())), - } - } -} - -#[derive(Debug)] -pub enum HandshakePayload<'a> { - HelloRequest, - ClientHello(ClientHelloPayload), - ServerHello(ServerHelloPayload), - HelloRetryRequest(HelloRetryRequest), - Certificate(CertificateChain<'a>), - CertificateTls13(CertificatePayloadTls13<'a>), - CompressedCertificate(CompressedCertificatePayload<'a>), - ServerKeyExchange(ServerKeyExchangePayload), - CertificateRequest(CertificateRequestPayload), - CertificateRequestTls13(CertificateRequestPayloadTls13), - CertificateVerify(DigitallySignedStruct), - ServerHelloDone, - EndOfEarlyData, - ClientKeyExchange(Payload<'a>), - NewSessionTicket(NewSessionTicketPayload), - NewSessionTicketTls13(NewSessionTicketPayloadTls13), - EncryptedExtensions(Vec), - KeyUpdate(KeyUpdateRequest), - Finished(Payload<'a>), - CertificateStatus(CertificateStatus<'a>), - MessageHash(Payload<'a>), - Unknown(Payload<'a>), -} - -impl HandshakePayload<'_> { - fn encode(&self, bytes: &mut Vec) { - use self::HandshakePayload::*; - match *self { - HelloRequest | ServerHelloDone | EndOfEarlyData => {} - ClientHello(ref x) => x.encode(bytes), - ServerHello(ref x) => x.encode(bytes), - HelloRetryRequest(ref x) => x.encode(bytes), - Certificate(ref x) => x.encode(bytes), - CertificateTls13(ref x) => x.encode(bytes), - CompressedCertificate(ref x) => x.encode(bytes), - ServerKeyExchange(ref x) => x.encode(bytes), - ClientKeyExchange(ref x) => x.encode(bytes), - CertificateRequest(ref x) => x.encode(bytes), - CertificateRequestTls13(ref x) => x.encode(bytes), - CertificateVerify(ref x) => x.encode(bytes), - NewSessionTicket(ref x) => x.encode(bytes), - NewSessionTicketTls13(ref x) => x.encode(bytes), - EncryptedExtensions(ref x) => x.encode(bytes), - KeyUpdate(ref x) => x.encode(bytes), - Finished(ref x) => x.encode(bytes), - CertificateStatus(ref x) => x.encode(bytes), - MessageHash(ref x) => x.encode(bytes), - Unknown(ref x) => x.encode(bytes), - } - } - - fn into_owned(self) -> HandshakePayload<'static> { - use HandshakePayload::*; - - match self { - HelloRequest => HelloRequest, - ClientHello(x) => ClientHello(x), - ServerHello(x) => ServerHello(x), - HelloRetryRequest(x) => HelloRetryRequest(x), - Certificate(x) => Certificate(x.into_owned()), - CertificateTls13(x) => CertificateTls13(x.into_owned()), - CompressedCertificate(x) => CompressedCertificate(x.into_owned()), - ServerKeyExchange(x) => ServerKeyExchange(x), - CertificateRequest(x) => CertificateRequest(x), - CertificateRequestTls13(x) => CertificateRequestTls13(x), - CertificateVerify(x) => CertificateVerify(x), - ServerHelloDone => ServerHelloDone, - EndOfEarlyData => EndOfEarlyData, - ClientKeyExchange(x) => ClientKeyExchange(x.into_owned()), - NewSessionTicket(x) => NewSessionTicket(x), - NewSessionTicketTls13(x) => NewSessionTicketTls13(x), - EncryptedExtensions(x) => EncryptedExtensions(x), - KeyUpdate(x) => KeyUpdate(x), - Finished(x) => Finished(x.into_owned()), - CertificateStatus(x) => CertificateStatus(x.into_owned()), - MessageHash(x) => MessageHash(x.into_owned()), - Unknown(x) => Unknown(x.into_owned()), - } - } -} - -#[derive(Debug)] -pub struct HandshakeMessagePayload<'a> { - pub typ: HandshakeType, - pub payload: HandshakePayload<'a>, -} - -impl<'a> Codec<'a> for HandshakeMessagePayload<'a> { - fn encode(&self, bytes: &mut Vec) { - self.payload_encode(bytes, Encoding::Standard); - } - - fn read(r: &mut Reader<'a>) -> Result { - Self::read_version(r, ProtocolVersion::TLSv1_2) - } -} - -impl<'a> HandshakeMessagePayload<'a> { - pub(crate) fn read_version( - r: &mut Reader<'a>, - vers: ProtocolVersion, - ) -> Result { - let mut typ = HandshakeType::read(r)?; - let len = codec::u24::read(r)?.0 as usize; - let mut sub = r.sub(len)?; - - let payload = match typ { - HandshakeType::HelloRequest if sub.left() == 0 => HandshakePayload::HelloRequest, - HandshakeType::ClientHello => { - HandshakePayload::ClientHello(ClientHelloPayload::read(&mut sub)?) - } - HandshakeType::ServerHello => { - let version = ProtocolVersion::read(&mut sub)?; - let random = Random::read(&mut sub)?; - - if random == HELLO_RETRY_REQUEST_RANDOM { - let mut hrr = HelloRetryRequest::read(&mut sub)?; - hrr.legacy_version = version; - typ = HandshakeType::HelloRetryRequest; - HandshakePayload::HelloRetryRequest(hrr) - } else { - let mut shp = ServerHelloPayload::read(&mut sub)?; - shp.legacy_version = version; - shp.random = random; - HandshakePayload::ServerHello(shp) - } - } - HandshakeType::Certificate if vers == ProtocolVersion::TLSv1_3 => { - let p = CertificatePayloadTls13::read(&mut sub)?; - HandshakePayload::CertificateTls13(p) - } - HandshakeType::Certificate => { - HandshakePayload::Certificate(CertificateChain::read(&mut sub)?) - } - HandshakeType::ServerKeyExchange => { - let p = ServerKeyExchangePayload::read(&mut sub)?; - HandshakePayload::ServerKeyExchange(p) - } - HandshakeType::ServerHelloDone => { - sub.expect_empty("ServerHelloDone")?; - HandshakePayload::ServerHelloDone - } - HandshakeType::ClientKeyExchange => { - HandshakePayload::ClientKeyExchange(Payload::read(&mut sub)) - } - HandshakeType::CertificateRequest if vers == ProtocolVersion::TLSv1_3 => { - let p = CertificateRequestPayloadTls13::read(&mut sub)?; - HandshakePayload::CertificateRequestTls13(p) - } - HandshakeType::CertificateRequest => { - let p = CertificateRequestPayload::read(&mut sub)?; - HandshakePayload::CertificateRequest(p) - } - HandshakeType::CompressedCertificate => HandshakePayload::CompressedCertificate( - CompressedCertificatePayload::read(&mut sub)?, - ), - HandshakeType::CertificateVerify => { - HandshakePayload::CertificateVerify(DigitallySignedStruct::read(&mut sub)?) - } - HandshakeType::NewSessionTicket if vers == ProtocolVersion::TLSv1_3 => { - let p = NewSessionTicketPayloadTls13::read(&mut sub)?; - HandshakePayload::NewSessionTicketTls13(p) - } - HandshakeType::NewSessionTicket => { - let p = NewSessionTicketPayload::read(&mut sub)?; - HandshakePayload::NewSessionTicket(p) - } - HandshakeType::EncryptedExtensions => { - HandshakePayload::EncryptedExtensions(Vec::read(&mut sub)?) - } - HandshakeType::KeyUpdate => { - HandshakePayload::KeyUpdate(KeyUpdateRequest::read(&mut sub)?) - } - HandshakeType::EndOfEarlyData => { - sub.expect_empty("EndOfEarlyData")?; - HandshakePayload::EndOfEarlyData - } - HandshakeType::Finished => HandshakePayload::Finished(Payload::read(&mut sub)), - HandshakeType::CertificateStatus => { - HandshakePayload::CertificateStatus(CertificateStatus::read(&mut sub)?) - } - HandshakeType::MessageHash => { - // does not appear on the wire - return Err(InvalidMessage::UnexpectedMessage("MessageHash")); - } - HandshakeType::HelloRetryRequest => { - // not legal on wire - return Err(InvalidMessage::UnexpectedMessage("HelloRetryRequest")); - } - _ => HandshakePayload::Unknown(Payload::read(&mut sub)), - }; - - sub.expect_empty("HandshakeMessagePayload") - .map(|_| Self { typ, payload }) - } - - pub(crate) fn encoding_for_binder_signing(&self) -> Vec { - let mut ret = self.get_encoding(); - - let binder_len = match self.payload { - HandshakePayload::ClientHello(ref ch) => match ch.extensions.last() { - Some(ClientExtension::PresharedKey(ref offer)) => { - let mut binders_encoding = Vec::new(); - offer - .binders - .encode(&mut binders_encoding); - binders_encoding.len() - } - _ => 0, - }, - _ => 0, - }; - - let ret_len = ret.len() - binder_len; - ret.truncate(ret_len); - ret - } - - pub(crate) fn payload_encode(&self, bytes: &mut Vec, encoding: Encoding) { - // output type, length, and encoded payload - match self.typ { - HandshakeType::HelloRetryRequest => HandshakeType::ServerHello, - _ => self.typ, - } - .encode(bytes); - - let nested = LengthPrefixedBuffer::new( - ListLength::U24 { - max: usize::MAX, - error: InvalidMessage::MessageTooLarge, - }, - bytes, - ); - - match &self.payload { - // for Server Hello and HelloRetryRequest payloads we need to encode the payload - // differently based on the purpose of the encoding. - HandshakePayload::ServerHello(payload) => payload.payload_encode(nested.buf, encoding), - HandshakePayload::HelloRetryRequest(payload) => { - payload.payload_encode(nested.buf, encoding) - } - - // All other payload types are encoded the same regardless of purpose. - _ => self.payload.encode(nested.buf), - } - } - - pub(crate) fn build_handshake_hash(hash: &[u8]) -> Self { - Self { - typ: HandshakeType::MessageHash, - payload: HandshakePayload::MessageHash(Payload::new(hash.to_vec())), - } - } - - pub(crate) fn into_owned(self) -> HandshakeMessagePayload<'static> { - let Self { typ, payload } = self; - HandshakeMessagePayload { - typ, - payload: payload.into_owned(), - } - } -} - -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] -pub struct HpkeSymmetricCipherSuite { - pub kdf_id: HpkeKdf, - pub aead_id: HpkeAead, -} - -impl Codec<'_> for HpkeSymmetricCipherSuite { - fn encode(&self, bytes: &mut Vec) { - self.kdf_id.encode(bytes); - self.aead_id.encode(bytes); - } - - fn read(r: &mut Reader<'_>) -> Result { - Ok(Self { - kdf_id: HpkeKdf::read(r)?, - aead_id: HpkeAead::read(r)?, - }) - } -} - -impl TlsListElement for HpkeSymmetricCipherSuite { - const SIZE_LEN: ListLength = ListLength::U16; -} - -#[derive(Clone, Debug, PartialEq)] -pub struct HpkeKeyConfig { - pub config_id: u8, - pub kem_id: HpkeKem, - pub public_key: PayloadU16, - pub symmetric_cipher_suites: Vec, -} - -impl Codec<'_> for HpkeKeyConfig { - fn encode(&self, bytes: &mut Vec) { - self.config_id.encode(bytes); - self.kem_id.encode(bytes); - self.public_key.encode(bytes); - self.symmetric_cipher_suites - .encode(bytes); - } - - fn read(r: &mut Reader<'_>) -> Result { - Ok(Self { - config_id: u8::read(r)?, - kem_id: HpkeKem::read(r)?, - public_key: PayloadU16::read(r)?, - symmetric_cipher_suites: Vec::::read(r)?, - }) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct EchConfigContents { - pub key_config: HpkeKeyConfig, - pub maximum_name_length: u8, - pub public_name: DnsName<'static>, - pub extensions: Vec, -} - -impl EchConfigContents { - /// Returns true if there is more than one extension of a given - /// type. - pub(crate) fn has_duplicate_extension(&self) -> bool { - has_duplicates::<_, _, u16>( - self.extensions - .iter() - .map(|ext| ext.ext_type()), - ) - } - - /// Returns true if there is at least one mandatory unsupported extension. - pub(crate) fn has_unknown_mandatory_extension(&self) -> bool { - self.extensions - .iter() - // An extension is considered mandatory if the high bit of its type is set. - .any(|ext| { - matches!(ext.ext_type(), ExtensionType::Unknown(_)) - && u16::from(ext.ext_type()) & 0x8000 != 0 - }) - } -} - -impl Codec<'_> for EchConfigContents { - fn encode(&self, bytes: &mut Vec) { - self.key_config.encode(bytes); - self.maximum_name_length.encode(bytes); - let dns_name = &self.public_name.borrow(); - PayloadU8::encode_slice(dns_name.as_ref().as_ref(), bytes); - self.extensions.encode(bytes); - } - - fn read(r: &mut Reader<'_>) -> Result { - Ok(Self { - key_config: HpkeKeyConfig::read(r)?, - maximum_name_length: u8::read(r)?, - public_name: { - DnsName::try_from(PayloadU8::read(r)?.0.as_slice()) - .map_err(|_| InvalidMessage::InvalidServerName)? - .to_owned() - }, - extensions: Vec::read(r)?, - }) - } -} - -/// An encrypted client hello (ECH) config. -#[derive(Clone, Debug, PartialEq)] -pub enum EchConfigPayload { - /// A recognized V18 ECH configuration. - V18(EchConfigContents), - /// An unknown version ECH configuration. - Unknown { - version: EchVersion, - contents: PayloadU16, - }, -} - -impl TlsListElement for EchConfigPayload { - const SIZE_LEN: ListLength = ListLength::U16; -} - -impl Codec<'_> for EchConfigPayload { - fn encode(&self, bytes: &mut Vec) { - match self { - Self::V18(c) => { - // Write the version, the length, and the contents. - EchVersion::V18.encode(bytes); - let inner = LengthPrefixedBuffer::new(ListLength::U16, bytes); - c.encode(inner.buf); - } - Self::Unknown { version, contents } => { - // Unknown configuration versions are opaque. - version.encode(bytes); - contents.encode(bytes); - } - } - } - - fn read(r: &mut Reader<'_>) -> Result { - let version = EchVersion::read(r)?; - let length = u16::read(r)?; - let mut contents = r.sub(length as usize)?; - - Ok(match version { - EchVersion::V18 => Self::V18(EchConfigContents::read(&mut contents)?), - _ => { - // Note: we don't PayloadU16::read() here because we've already read the length prefix. - let data = PayloadU16::new(contents.rest().into()); - Self::Unknown { - version, - contents: data, - } - } - }) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum EchConfigExtension { - Unknown(UnknownExtension), -} - -impl EchConfigExtension { - pub(crate) fn ext_type(&self) -> ExtensionType { - match *self { - Self::Unknown(ref r) => r.typ, - } - } -} - -impl Codec<'_> for EchConfigExtension { - fn encode(&self, bytes: &mut Vec) { - self.ext_type().encode(bytes); - - let nested = LengthPrefixedBuffer::new(ListLength::U16, bytes); - match *self { - Self::Unknown(ref r) => r.encode(nested.buf), - } - } - - fn read(r: &mut Reader<'_>) -> Result { - let typ = ExtensionType::read(r)?; - let len = u16::read(r)? as usize; - let mut sub = r.sub(len)?; - - #[allow(clippy::match_single_binding)] // Future-proofing. - let ext = match typ { - _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), - }; - - sub.expect_empty("EchConfigExtension") - .map(|_| ext) - } -} - -impl TlsListElement for EchConfigExtension { - const SIZE_LEN: ListLength = ListLength::U16; -} - -/// Representation of the `ECHClientHello` client extension specified in -/// [draft-ietf-tls-esni Section 5]. -/// -/// [draft-ietf-tls-esni Section 5]: -#[derive(Clone, Debug)] -pub enum EncryptedClientHello { - /// A `ECHClientHello` with type [EchClientHelloType::ClientHelloOuter]. - Outer(EncryptedClientHelloOuter), - /// An empty `ECHClientHello` with type [EchClientHelloType::ClientHelloInner]. - /// - /// This variant has no payload. - Inner, -} - -impl Codec<'_> for EncryptedClientHello { - fn encode(&self, bytes: &mut Vec) { - match self { - Self::Outer(payload) => { - EchClientHelloType::ClientHelloOuter.encode(bytes); - payload.encode(bytes); - } - Self::Inner => { - EchClientHelloType::ClientHelloInner.encode(bytes); - // Empty payload. - } - } - } - - fn read(r: &mut Reader<'_>) -> Result { - match EchClientHelloType::read(r)? { - EchClientHelloType::ClientHelloOuter => { - Ok(Self::Outer(EncryptedClientHelloOuter::read(r)?)) - } - EchClientHelloType::ClientHelloInner => Ok(Self::Inner), - _ => Err(InvalidMessage::InvalidContentType), + compressed: SizedPayload::from(Payload::Borrowed(self.compressed.bytes())), } } } -/// Representation of the ECHClientHello extension with type outer specified in -/// [draft-ietf-tls-esni Section 5]. -/// -/// [draft-ietf-tls-esni Section 5]: -#[derive(Clone, Debug)] -pub struct EncryptedClientHelloOuter { - /// The cipher suite used to encrypt ClientHelloInner. Must match a value from - /// ECHConfigContents.cipher_suites list. - pub cipher_suite: HpkeSymmetricCipherSuite, - /// The ECHConfigContents.key_config.config_id for the chosen ECHConfig. - pub config_id: u8, - /// The HPKE encapsulated key, used by servers to decrypt the corresponding payload field. - /// This field is empty in a ClientHelloOuter sent in response to a HelloRetryRequest. - pub enc: PayloadU16, - /// The serialized and encrypted ClientHelloInner structure, encrypted using HPKE. - pub payload: PayloadU16, -} - -impl Codec<'_> for EncryptedClientHelloOuter { - fn encode(&self, bytes: &mut Vec) { - self.cipher_suite.encode(bytes); - self.config_id.encode(bytes); - self.enc.encode(bytes); - self.payload.encode(bytes); - } - - fn read(r: &mut Reader<'_>) -> Result { - Ok(Self { - cipher_suite: HpkeSymmetricCipherSuite::read(r)?, - config_id: u8::read(r)?, - enc: PayloadU16::read(r)?, - payload: PayloadU16::read(r)?, - }) - } -} - -/// Representation of the ECHEncryptedExtensions extension specified in -/// [draft-ietf-tls-esni Section 5]. -/// -/// [draft-ietf-tls-esni Section 5]: -#[derive(Clone, Debug)] -pub struct ServerEncryptedClientHello { - pub(crate) retry_configs: Vec, -} - -impl Codec<'_> for ServerEncryptedClientHello { - fn encode(&self, bytes: &mut Vec) { - self.retry_configs.encode(bytes); - } - - fn read(r: &mut Reader<'_>) -> Result { - Ok(Self { - retry_configs: Vec::::read(r)?, - }) - } -} - /// The method of encoding to use for a handshake message. /// /// In some cases a handshake message may be encoded differently depending on the purpose -/// the encoded message is being used for. For example, a [ServerHelloPayload] may be encoded -/// with the last 8 bytes of the random zeroed out when being encoded for ECH confirmation. +/// the encoded message is being used for. pub(crate) enum Encoding { /// Standard RFC 8446 encoding. Standard, - /// Encoding for ECH confirmation. + /// Encoding for ECH confirmation for HRR. EchConfirmation, /// Encoding for ECH inner client hello. EchInnerHello { to_compress: Vec }, } -fn has_duplicates, E: Into, T: Eq + Ord>(iter: I) -> bool { +pub(super) fn has_duplicates, E: Into, T: Eq + Ord>(iter: I) -> bool { let mut seen = BTreeSet::new(); for x in iter { @@ -3155,55 +1403,18 @@ fn has_duplicates, E: Into, T: Eq + Ord>(iter: I) - false } -#[cfg(test)] -mod tests { - use super::*; +pub(super) struct DuplicateExtensionChecker(pub(super) BTreeSet); - #[test] - fn test_ech_config_dupe_exts() { - let unknown_ext = EchConfigExtension::Unknown(UnknownExtension { - typ: ExtensionType::Unknown(0x42), - payload: Payload::new(vec![0x42]), - }); - let mut config = config_template(); - config - .extensions - .push(unknown_ext.clone()); - config.extensions.push(unknown_ext); - - assert!(config.has_duplicate_extension()); - assert!(!config.has_unknown_mandatory_extension()); +impl DuplicateExtensionChecker { + pub(super) fn new() -> Self { + Self(BTreeSet::new()) } - #[test] - fn test_ech_config_mandatory_exts() { - let mandatory_unknown_ext = EchConfigExtension::Unknown(UnknownExtension { - typ: ExtensionType::Unknown(0x42 | 0x8000), // Note: high bit set. - payload: Payload::new(vec![0x42]), - }); - let mut config = config_template(); - config - .extensions - .push(mandatory_unknown_ext); - - assert!(!config.has_duplicate_extension()); - assert!(config.has_unknown_mandatory_extension()); - } - - fn config_template() -> EchConfigContents { - EchConfigContents { - key_config: HpkeKeyConfig { - config_id: 0, - kem_id: HpkeKem::DHKEM_P256_HKDF_SHA256, - public_key: PayloadU16(b"xxx".into()), - symmetric_cipher_suites: vec![HpkeSymmetricCipherSuite { - kdf_id: HpkeKdf::HKDF_SHA256, - aead_id: HpkeAead::AES_128_GCM, - }], - }, - maximum_name_length: 0, - public_name: DnsName::try_from("example.com").unwrap(), - extensions: vec![], + pub(super) fn check(&mut self, typ: ExtensionType) -> Result<(), InvalidMessage> { + let u = u16::from(typ); + match self.0.insert(u) { + true => Ok(()), + false => Err(InvalidMessage::DuplicateExtension(u)), } } } diff --git a/rustls/src/msgs/handshake_test.rs b/rustls/src/msgs/handshake_test.rs index c6b2861eb5f..d7208c91c63 100644 --- a/rustls/src/msgs/handshake_test.rs +++ b/rustls/src/msgs/handshake_test.rs @@ -1,46 +1,58 @@ -use alloc::sync::Arc; -use std::prelude::v1::*; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::time::Duration; use std::{format, println, vec}; use pki_types::{CertificateDer, DnsName}; -use super::base::{Payload, PayloadU16, PayloadU24, PayloadU8}; -use super::codec::{put_u16, Codec, Reader}; +use super::client_hello::{ + CertificateStatusRequest, ClientExtensions, ClientHelloPayload, ClientSessionTicket, + EncryptedClientHello, PresharedKeyBinder, PresharedKeyIdentity, PresharedKeyOffer, + PskKeyExchangeModes, ServerNamePayload, +}; +use super::codec::{Codec, Reader, SizedPayload, put_u16}; use super::enums::{ - CertificateType, ClientCertificateType, Compression, ECCurveType, ECPointFormat, ExtensionType, - KeyUpdateRequest, NamedGroup, PSKKeyExchangeMode, ServerNameType, + ClientCertificateType, Compression, ECCurveType, EchVersion, ExtensionType, KeyUpdateRequest, }; use super::handshake::{ - CertReqExtension, CertificateChain, CertificateEntry, CertificateExtension, - CertificatePayloadTls13, CertificateRequestPayload, CertificateRequestPayloadTls13, - CertificateStatus, CertificateStatusRequest, ClientExtension, ClientHelloPayload, - ClientSessionTicket, CompressedCertificatePayload, ConvertProtocolNameList, - ConvertServerNameList, DistinguishedName, EcParameters, HandshakeMessagePayload, - HandshakePayload, HasServerExtensions, HelloRetryExtension, HelloRetryRequest, KeyShareEntry, - NewSessionTicketExtension, NewSessionTicketPayload, NewSessionTicketPayloadTls13, - PresharedKeyBinder, PresharedKeyIdentity, PresharedKeyOffer, ProtocolName, Random, - ServerDhParams, ServerEcdhParams, ServerExtension, ServerHelloPayload, ServerKeyExchange, - ServerKeyExchangeParams, ServerKeyExchangePayload, SessionId, UnknownExtension, + CertificateChain, CertificateEntry, CertificateExtensions, CertificatePayloadTls13, + CertificateRequestExtensions, CertificateRequestPayload, CertificateRequestPayloadTls13, + CertificateStatus, CompressedCertificatePayload, EcParameters, HelloRetryRequest, + HelloRetryRequestExtensions, KeyShareEntry, NewSessionTicketExtensions, + NewSessionTicketPayload, NewSessionTicketPayloadTls13, Random, ServerDhParams, + ServerEcdhParams, ServerKeyExchange, ServerKeyExchangeParams, ServerKeyExchangePayload, + SessionId, SingleProtocolName, SupportedEcPointFormats, SupportedProtocolVersions, +}; +use super::server_hello::{ + EchConfigContents, EchConfigPayload, HpkeKeyConfig, ServerEncryptedClientHello, + ServerExtensions, ServerHelloPayload, }; +use super::{HandshakeMessagePayload, HandshakePayload}; +use crate::crypto::cipher::Payload; +use crate::crypto::hpke::{HpkeAead, HpkeKdf, HpkeKem, HpkeSymmetricCipherSuite}; +use crate::crypto::kx::NamedGroup; +use crate::crypto::{CipherSuite, SignatureScheme}; use crate::enums::{ - CertificateCompressionAlgorithm, CipherSuite, HandshakeType, ProtocolVersion, SignatureScheme, + ApplicationProtocol, CertificateCompressionAlgorithm, CertificateType, HandshakeType, + ProtocolVersion, }; use crate::error::InvalidMessage; -use crate::verify::DigitallySignedStruct; +use crate::sync::Arc; +use crate::verify::{DigitallySignedStruct, DistinguishedName}; #[test] fn rejects_short_random() { let bytes = [0x01; 31]; - let mut rd = Reader::init(&bytes); + let mut rd = Reader::new(&bytes); assert!(Random::read(&mut rd).is_err()); } #[test] fn reads_random() { let bytes = [0x01; 32]; - let mut rd = Reader::init(&bytes); + let mut rd = Reader::new(&bytes); let rnd = Random::read(&mut rd).unwrap(); - println!("{:?}", rnd); + println!("{rnd:?}"); assert!(!rd.any_left()); } @@ -56,32 +68,31 @@ fn debug_random() { #[test] fn rejects_truncated_session_id() { let bytes = [32; 32]; - let mut rd = Reader::init(&bytes); + let mut rd = Reader::new(&bytes); assert!(SessionId::read(&mut rd).is_err()); } #[test] fn rejects_session_id_with_bad_length() { let bytes = [33; 33]; - let mut rd = Reader::init(&bytes); + let mut rd = Reader::new(&bytes); assert!(SessionId::read(&mut rd).is_err()); } #[test] fn session_id_with_different_lengths_are_unequal() { - let a = SessionId::read(&mut Reader::init(&[1u8, 1])).unwrap(); - let b = SessionId::read(&mut Reader::init(&[2u8, 1, 2])).unwrap(); + let a = SessionId::read(&mut Reader::new(&[1u8, 1])).unwrap(); + let b = SessionId::read(&mut Reader::new(&[2u8, 1, 2])).unwrap(); assert_ne!(a, b); } #[test] fn accepts_short_session_id() { let bytes = [1; 2]; - let mut rd = Reader::init(&bytes); + let mut rd = Reader::new(&bytes); let sess = SessionId::read(&mut rd).unwrap(); - println!("{:?}", sess); + println!("{sess:?}"); - #[cfg(feature = "tls12")] assert!(!sess.is_empty()); assert_ne!(sess, SessionId::empty()); assert!(!rd.any_left()); @@ -90,11 +101,10 @@ fn accepts_short_session_id() { #[test] fn accepts_empty_session_id() { let bytes = [0; 1]; - let mut rd = Reader::init(&bytes); + let mut rd = Reader::new(&bytes); let sess = SessionId::read(&mut rd).unwrap(); - println!("{:?}", sess); + println!("{sess:?}"); - #[cfg(feature = "tls12")] assert!(sess.is_empty()); assert_eq!(sess, SessionId::empty()); assert!(!rd.any_left()); @@ -106,177 +116,145 @@ fn debug_session_id() { 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ]; - let mut rd = Reader::init(&bytes); + let mut rd = Reader::new(&bytes); let sess = SessionId::read(&mut rd).unwrap(); assert_eq!( "0101010101010101010101010101010101010101010101010101010101010101", - format!("{:?}", sess) + format!("{sess:?}") ); } #[test] -fn can_round_trip_unknown_client_ext() { - let bytes = [0x12u8, 0x34u8, 0, 3, 1, 2, 3]; - let mut rd = Reader::init(&bytes); - let ext = ClientExtension::read(&mut rd).unwrap(); - - println!("{:?}", ext); - assert_eq!(ext.ext_type(), ExtensionType::Unknown(0x1234)); - assert_eq!(bytes.to_vec(), ext.get_encoding()); -} - -#[test] -fn refuses_client_ext_with_unparsed_bytes() { - let bytes = [0x00u8, 0x0b, 0x00, 0x04, 0x02, 0xf8, 0x01, 0x02]; - let mut rd = Reader::init(&bytes); - assert!(ClientExtension::read(&mut rd).is_err()); +fn refuses_client_exts_with_unparsed_bytes() { + let bytes = [0x00u8, 0x08, 0x00, 0x0b, 0x00, 0x04, 0x02, 0xf8, 0x01, 0x02]; + assert_eq!( + ClientExtensions::read_bytes(&bytes).unwrap_err(), + InvalidMessage::TrailingData("ClientExtensions") + ); } #[test] fn refuses_server_ext_with_unparsed_bytes() { - let bytes = [0x00u8, 0x0b, 0x00, 0x04, 0x02, 0xf8, 0x01, 0x02]; - let mut rd = Reader::init(&bytes); - assert!(ServerExtension::read(&mut rd).is_err()); + let bytes = [0x00u8, 0x08, 0x00, 0x0b, 0x00, 0x04, 0x02, 0xf8, 0x01, 0x02]; + assert_eq!( + ServerExtensions::read_bytes(&bytes).unwrap_err(), + InvalidMessage::TrailingData("ServerExtensions") + ); } #[test] fn refuses_certificate_ext_with_unparsed_bytes() { - let bytes = [0x00u8, 0x05, 0x00, 0x03, 0x00, 0x00, 0x01]; - let mut rd = Reader::init(&bytes); - assert!(CertificateExtension::read(&mut rd).is_err()); + let bytes = [ + 0x00u8, 0x0a, 0x00, 0x05, 0x00, 0x06, 0x01, 0x00, 0x00, 0x01, 0xcc, 0x01, + ]; + assert_eq!( + CertificateExtensions::read_bytes(&bytes).unwrap_err(), + InvalidMessage::TrailingData("CertificateExtensions") + ); } #[test] -fn refuses_certificate_req_ext_with_unparsed_bytes() { - let bytes = [0x00u8, 0x0d, 0x00, 0x05, 0x00, 0x02, 0x01, 0x02, 0xff]; - let mut rd = Reader::init(&bytes); - assert!(CertReqExtension::read(&mut rd).is_err()); +fn refuses_certificate_ext_with_unknown_type() { + let bytes = [0x00u8, 0x08, 0x00, 0x05, 0x00, 0x03, 0x99, 0x00, 0x00, 0x00]; + assert_eq!( + CertificateExtensions::read_bytes(&bytes).unwrap_err(), + InvalidMessage::InvalidCertificateStatusType + ); } #[test] -fn refuses_helloreq_ext_with_unparsed_bytes() { - let bytes = [0x00u8, 0x2b, 0x00, 0x03, 0x00, 0x00, 0x01]; - let mut rd = Reader::init(&bytes); - assert!(HelloRetryExtension::read(&mut rd).is_err()); +fn refuses_certificate_req_ext_with_unparsed_bytes() { + let bytes = [ + 0x00u8, 0x09, 0x00, 0x0d, 0x00, 0x05, 0x00, 0x02, 0x01, 0x02, 0xff, + ]; + assert_eq!( + CertificateRequestExtensions::read_bytes(&bytes).unwrap_err(), + InvalidMessage::TrailingData("CertificateRequestExtensions") + ); } #[test] -fn refuses_new_session_ticket_ext_with_unparsed_bytes() { - let bytes = [0x00u8, 0x2a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01]; - let mut rd = Reader::init(&bytes); - assert!(NewSessionTicketExtension::read(&mut rd).is_err()); +fn refuses_certificate_req_ext_with_duplicate() { + let bytes = [0x00u8, 0x08, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00]; + assert_eq!( + CertificateRequestExtensions::read_bytes(&bytes).unwrap_err(), + InvalidMessage::DuplicateExtension(0x0099) + ); } #[test] -fn can_round_trip_single_sni() { - let bytes = [0, 0, 0, 7, 0, 5, 0, 0, 2, 0x6c, 0x6f]; - let mut rd = Reader::init(&bytes); - let ext = ClientExtension::read(&mut rd).unwrap(); - println!("{:?}", ext); - - assert_eq!(ext.ext_type(), ExtensionType::ServerName); - assert_eq!(bytes.to_vec(), ext.get_encoding()); +fn refuses_new_session_ticket_ext_with_unparsed_bytes() { + let bytes = [ + 0x00u8, 0x09, 0x00, 0x2a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, + ]; + assert_eq!( + NewSessionTicketExtensions::read_bytes(&bytes).unwrap_err(), + InvalidMessage::TrailingData("NewSessionTicketExtensions") + ); } #[test] -fn can_round_trip_mixed_case_sni() { - let bytes = [0, 0, 0, 7, 0, 5, 0, 0, 2, 0x4c, 0x6f]; - let mut rd = Reader::init(&bytes); - let ext = ClientExtension::read(&mut rd).unwrap(); - println!("{:?}", ext); - - assert_eq!(ext.ext_type(), ExtensionType::ServerName); - assert_eq!(bytes.to_vec(), ext.get_encoding()); +fn refuses_new_session_ticket_ext_with_duplicate_extension() { + let bytes = [0x00u8, 0x08, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00]; + assert_eq!( + NewSessionTicketExtensions::read_bytes(&bytes).unwrap_err(), + InvalidMessage::DuplicateExtension(0x0099) + ); } #[test] -fn can_round_trip_other_sni_name_types() { - let bytes = [0, 0, 0, 7, 0, 5, 1, 0, 2, 0x6c, 0x6f]; - let mut rd = Reader::init(&bytes); - let ext = ClientExtension::read(&mut rd).unwrap(); - println!("{:?}", ext); - - assert_eq!(ext.ext_type(), ExtensionType::ServerName); - assert_eq!(bytes.to_vec(), ext.get_encoding()); -} +fn rejects_truncated_sni() { + let bytes = [0, 1, 0]; + assert!(ServerNamePayload::read(&mut Reader::new(&bytes)).is_err()); -#[test] -fn single_hostname_returns_none_for_other_sni_name_types() { - let bytes = [0, 0, 0, 7, 0, 5, 1, 0, 2, 0x6c, 0x6f]; - let mut rd = Reader::init(&bytes); - let ext = ClientExtension::read(&mut rd).unwrap(); - println!("{:?}", ext); - - assert_eq!(ext.ext_type(), ExtensionType::ServerName); - if let ClientExtension::ServerName(snr) = ext { - assert!(!snr.has_duplicate_names_for_type()); - assert!(snr.single_hostname().is_none()); - } else { - unreachable!(); - } -} + let bytes = [0, 2, 0, 1]; + assert!(ServerNamePayload::read(&mut Reader::new(&bytes)).is_err()); -#[test] -fn can_round_trip_multi_name_sni() { - let bytes = [0, 0, 0, 12, 0, 10, 0, 0, 2, 0x68, 0x69, 0, 0, 2, 0x6c, 0x6f]; - let mut rd = Reader::init(&bytes); - let ext = ClientExtension::read(&mut rd).unwrap(); - println!("{:?}", ext); + let bytes = [0, 3, 0, 1, 0]; + assert!(ServerNamePayload::read(&mut Reader::new(&bytes)).is_err()); - assert_eq!(ext.ext_type(), ExtensionType::ServerName); - assert_eq!(bytes.to_vec(), ext.get_encoding()); - match ext { - ClientExtension::ServerName(req) => { - assert_eq!(2, req.len()); + let bytes = [0, 4, 0, 2, 0, 0]; + assert!(ServerNamePayload::read(&mut Reader::new(&bytes)).is_err()); - assert!(req.has_duplicate_names_for_type()); + let bytes = [0, 5, 0, 3, 0, 0, 0]; + assert!(ServerNamePayload::read(&mut Reader::new(&bytes)).is_err()); - let dns_name = req.single_hostname().unwrap(); - assert_eq!(dns_name.as_ref(), "hi"); + let bytes = [0, 5, 0, 3, 0, 0, 1]; + assert!(ServerNamePayload::read(&mut Reader::new(&bytes)).is_err()); - assert_eq!(req[0].typ, ServerNameType::HostName); - assert_eq!(req[1].typ, ServerNameType::HostName); - } - _ => unreachable!(), - } + let bytes = [0, 6, 0, 4, 0, 0, 2, 0x68]; + assert!(ServerNamePayload::read(&mut Reader::new(&bytes)).is_err()); } #[test] -fn rejects_truncated_sni() { - let bytes = [0, 0, 0, 1, 0]; - assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_err()); - - let bytes = [0, 0, 0, 2, 0, 1]; - assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_err()); - - let bytes = [0, 0, 0, 3, 0, 1, 0]; - assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_err()); - - let bytes = [0, 0, 0, 4, 0, 2, 0, 0]; - assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_err()); - - let bytes = [0, 0, 0, 5, 0, 3, 0, 0, 0]; - assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_err()); - - let bytes = [0, 0, 0, 5, 0, 3, 0, 0, 1]; - assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_err()); +fn rejects_empty_sni_extension() { + assert_eq!( + ClientExtensions::read_bytes(&[0, 6, 0, 0, 0, 2, 0, 0]).unwrap_err(), + InvalidMessage::IllegalEmptyList("ServerNames") + ); +} - let bytes = [0, 0, 0, 6, 0, 4, 0, 0, 2, 0x68]; - assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_err()); +#[test] +fn rejects_duplicate_names_in_sni_extension() { + assert_eq!( + ClientExtensions::read_bytes(&[0, 14, 0, 0, 0, 10, 0, 8, 0, 0, 1, b'a', 0, 0, 1, b'b',]) + .unwrap_err(), + InvalidMessage::InvalidServerName + ); } #[test] fn can_round_trip_psk_identity() { - let bytes = [0, 0, 0x11, 0x22, 0x33, 0x44]; - let psk_id = PresharedKeyIdentity::read(&mut Reader::init(&bytes)).unwrap(); - println!("{:?}", psk_id); + let bytes = [0, 1, 0x99, 0x11, 0x22, 0x33, 0x44]; + let psk_id = PresharedKeyIdentity::read(&mut Reader::new(&bytes)).unwrap(); + println!("{psk_id:?}"); assert_eq!(psk_id.obfuscated_ticket_age, 0x11223344); assert_eq!(psk_id.get_encoding(), bytes.to_vec()); let bytes = [0, 5, 0x1, 0x2, 0x3, 0x4, 0x5, 0x11, 0x22, 0x33, 0x44]; - let psk_id = PresharedKeyIdentity::read(&mut Reader::init(&bytes)).unwrap(); - println!("{:?}", psk_id); - assert_eq!(psk_id.identity.0, vec![0x1, 0x2, 0x3, 0x4, 0x5]); + let psk_id = PresharedKeyIdentity::read(&mut Reader::new(&bytes)).unwrap(); + println!("{psk_id:?}"); + assert_eq!(psk_id.identity.bytes(), &[0x1, 0x2, 0x3, 0x4, 0x5]); assert_eq!(psk_id.obfuscated_ticket_age, 0x11223344); assert_eq!(psk_id.get_encoding(), bytes.to_vec()); } @@ -286,11 +264,11 @@ fn can_round_trip_psk_offer() { let bytes = [ 0, 7, 0, 1, 0x99, 0x11, 0x22, 0x33, 0x44, 0, 4, 3, 0x01, 0x02, 0x3, ]; - let psko = PresharedKeyOffer::read(&mut Reader::init(&bytes)).unwrap(); - println!("{:?}", psko); + let psko = PresharedKeyOffer::read(&mut Reader::new(&bytes)).unwrap(); + println!("{psko:?}"); assert_eq!(psko.identities.len(), 1); - assert_eq!(psko.identities[0].identity.0, vec![0x99]); + assert_eq!(psko.identities[0].identity.bytes(), &[0x99]); assert_eq!(psko.identities[0].obfuscated_ticket_age, 0x11223344); assert_eq!(psko.binders.len(), 1); assert_eq!(psko.binders[0].as_ref(), &[1, 2, 3]); @@ -299,71 +277,31 @@ fn can_round_trip_psk_offer() { #[test] fn can_round_trip_cert_status_req_for_ocsp() { - let ext = ClientExtension::CertificateStatusRequest(CertificateStatusRequest::build_ocsp()); - println!("{:?}", ext); + let ext = CertificateStatusRequest::build_ocsp(); + println!("{ext:?}"); let bytes = [ - 0, 5, // CertificateStatusRequest 0, 11, 1, // OCSP 0, 5, 0, 3, 0, 1, 1, 0, 1, 2, ]; - let csr = ClientExtension::read(&mut Reader::init(&bytes)).unwrap(); - println!("{:?}", csr); + let csr = CertificateStatusRequest::read(&mut Reader::new(&bytes)).unwrap(); + println!("{csr:?}"); assert_eq!(csr.get_encoding(), bytes.to_vec()); } #[test] fn can_round_trip_cert_status_req_for_other() { let bytes = [ - 0, 5, // CertificateStatusRequest 0, 5, 2, // !OCSP 1, 2, 3, 4, ]; - let csr = ClientExtension::read(&mut Reader::init(&bytes)).unwrap(); - println!("{:?}", csr); + let csr = CertificateStatusRequest::read(&mut Reader::new(&bytes)).unwrap(); + println!("{csr:?}"); assert_eq!(csr.get_encoding(), bytes.to_vec()); } -#[test] -fn can_round_trip_multi_proto() { - let bytes = [0, 16, 0, 8, 0, 6, 2, 0x68, 0x69, 2, 0x6c, 0x6f]; - let mut rd = Reader::init(&bytes); - let ext = ClientExtension::read(&mut rd).unwrap(); - println!("{:?}", ext); - - assert_eq!(ext.ext_type(), ExtensionType::ALProtocolNegotiation); - assert_eq!(ext.get_encoding(), bytes.to_vec()); - match ext { - ClientExtension::Protocols(prot) => { - assert_eq!(2, prot.len()); - assert_eq!(vec![b"hi", b"lo"], prot.to_slices()); - assert_eq!(prot.as_single_slice(), None); - } - _ => unreachable!(), - } -} - -#[test] -fn can_round_trip_single_proto() { - let bytes = [0, 16, 0, 5, 0, 3, 2, 0x68, 0x69]; - let mut rd = Reader::init(&bytes); - let ext = ClientExtension::read(&mut rd).unwrap(); - println!("{:?}", ext); - - assert_eq!(ext.ext_type(), ExtensionType::ALProtocolNegotiation); - assert_eq!(bytes.to_vec(), ext.get_encoding()); - match ext { - ClientExtension::Protocols(prot) => { - assert_eq!(1, prot.len()); - assert_eq!(vec![b"hi"], prot.to_slices()); - assert_eq!(prot.as_single_slice(), Some(&b"hi"[..])); - } - _ => unreachable!(), - } -} - #[test] fn can_print_all_client_extensions() { println!("client hello {:?}", sample_client_hello_payload()); @@ -377,32 +315,171 @@ fn can_clone_all_client_extensions() { } #[test] -fn client_has_duplicate_extensions_works() { - let mut chp = sample_client_hello_payload(); - assert!(chp.has_duplicate_extension()); // due to SessionTicketRequest/SessionTicketOffer +fn client_extensions_basics() { + let src = ClientExtensions { + early_data_request: Some(()), + ..Default::default() + }; + let mut target = ClientExtensions::default(); - chp.extensions.drain(1..); - assert!(!chp.has_duplicate_extension()); + assert_eq!(src.collect_used(), vec![ExtensionType::EarlyData]); + assert_eq!(target.collect_used(), vec![]); - chp.extensions = vec![]; - assert!(!chp.has_duplicate_extension()); + target.clone_one(&src, ExtensionType::EarlyData); + assert_eq!(target.collect_used(), vec![ExtensionType::EarlyData]); +} + +#[test] +fn client_extensions_empty() { + // both sides of empty-encoding branch + assert_eq!(ClientExtensions::default().get_encoding(), Vec::::new()); + assert_eq!( + ClientExtensions::read_bytes(&[]) + .unwrap() + .collect_used(), + vec![] + ); + + let early_data = b"\x00\x04\x00\x2a\x00\x00"; + assert_eq!( + ClientExtensions { + early_data_request: Some(()), + ..Default::default() + } + .get_encoding(), + early_data + ); + assert_eq!( + ClientExtensions::read_bytes(early_data) + .unwrap() + .collect_used(), + vec![ExtensionType::EarlyData] + ); +} + +#[test] +fn client_extensions_decode_checks_duplicates() { + // base + ClientExtensions::read_bytes(b"\x00\x04\x00\x2a\x00\x00").unwrap(); + + // duplicate known + assert_eq!( + ClientExtensions::read_bytes(b"\x00\x08\x00\x2a\x00\x00\x00\x2a\x00\x00").unwrap_err(), + InvalidMessage::DuplicateExtension(0x002a) + ); + + // duplicate unknown + assert_eq!( + ClientExtensions::read_bytes(b"\x00\x08\xff\xff\x00\x00\xff\xff\x00\x00").unwrap_err(), + InvalidMessage::DuplicateExtension(0xffff) + ); +} + +#[test] +fn client_extensions_ordering() { + // the important thing here is that PSK requests come last, + // ECH requests come second to last, and order of other extensions + // do vary. + + let psk_offer = PresharedKeyOffer { + identities: vec![], + binders: vec![], + }; + + let psk_and_ech = ClientExtensions { + early_data_request: Some(()), + extended_master_secret_request: Some(()), + preshared_key_offer: Some(psk_offer.clone()), + encrypted_client_hello: Some(EncryptedClientHello::Inner), + ..Default::default() + }; + + let psk_and_ech_with_contiguous = ClientExtensions { + contiguous_extensions: vec![ExtensionType::ExtendedMasterSecret], + ..psk_and_ech.clone() + }; + + let ech = ClientExtensions { + early_data_request: Some(()), + extended_master_secret_request: Some(()), + encrypted_client_hello: Some(EncryptedClientHello::Inner), + ..Default::default() + }; + + let psk = ClientExtensions { + early_data_request: Some(()), + extended_master_secret_request: Some(()), + preshared_key_offer: Some(psk_offer), + ..Default::default() + }; + + let neither = ClientExtensions { + early_data_request: Some(()), + extended_master_secret_request: Some(()), + ..Default::default() + }; + + fn encoding_with_order(order_seed: u16, exts: &ClientExtensions<'_>) -> Vec { + let mut e = exts.clone(); + e.order_seed = order_seed; + e.get_encoding() + } + + assert_ne!( + encoding_with_order(0, &psk_and_ech), + encoding_with_order(1, &psk_and_ech) + ); + assert_eq!( + encoding_with_order(0, &psk_and_ech_with_contiguous), + encoding_with_order(1, &psk_and_ech_with_contiguous) + ); + assert_ne!(encoding_with_order(0, &ech), encoding_with_order(1, &ech)); + assert_ne!(encoding_with_order(0, &psk), encoding_with_order(1, &psk)); + assert_ne!( + encoding_with_order(0, &neither), + encoding_with_order(1, &neither) + ); + + // check order invariants hold for all seeds + for seed in 0..=0xffff { + // must end with ECH and then PSK + assert!(encoding_with_order(seed, &psk_and_ech).ends_with( + b"\xfe\x0d\x00\x01\x01\ + \x00\x29\x00\x04\x00\x00\x00\x00" + )); + + // must end with EMS, then ECH and then PSK + assert!( + encoding_with_order(seed, &psk_and_ech_with_contiguous).ends_with( + b"\x00\x17\x00\x00\ + \xfe\x0d\x00\x01\x01\ + \x00\x29\x00\x04\x00\x00\x00\x00" + ) + ); + + // just PSK + assert!(encoding_with_order(seed, &psk).ends_with(b"\x00\x29\x00\x04\x00\x00\x00\x00")); + + // just ECH + assert!(encoding_with_order(seed, &ech).ends_with(b"\xfe\x0d\x00\x01\x01")); + } } #[test] fn test_truncated_psk_offer() { - let ext = ClientExtension::PresharedKey(PresharedKeyOffer { + let ext = PresharedKeyOffer { identities: vec![PresharedKeyIdentity::new(vec![3, 4, 5], 123456)], binders: vec![PresharedKeyBinder::from(vec![1, 2, 3])], - }); + }; let mut enc = ext.get_encoding(); - println!("testing {:?} enc {:?}", ext, enc); + println!("testing {ext:?} enc {enc:?}"); for l in 0..enc.len() { if l == 9 { continue; } - put_u16(l as u16, &mut enc[4..]); - let rc = ClientExtension::read_bytes(&enc); + put_u16(l as u16, &mut enc); + let rc = PresharedKeyOffer::read_bytes(&enc); assert!(rc.is_err()); } } @@ -411,7 +488,7 @@ fn test_truncated_psk_offer() { fn test_truncated_client_hello_is_detected() { let ch = sample_client_hello_payload(); let enc = ch.get_encoding(); - println!("testing {:?} enc {:?}", ch, enc); + println!("testing {ch:?} enc {enc:?}"); for l in 0..enc.len() { println!("len {:?} enc {:?}", l, &enc[..l]); @@ -426,313 +503,58 @@ fn test_truncated_client_hello_is_detected() { fn test_truncated_client_extension_is_detected() { let chp = sample_client_hello_payload(); - for ext in &chp.extensions { - let mut enc = ext.get_encoding(); - println!("testing {:?} enc {:?}", ext, enc); - - // "outer" truncation, i.e., where the extension-level length is longer than - // the input - for l in 0..enc.len() { - assert!(ClientExtension::read_bytes(&enc[..l]).is_err()); - } + let enc = chp.extensions.get_encoding(); + println!("testing enc {enc:?}"); - // these extension types don't have any internal encoding that rustls validates: - match ext.ext_type() { - ExtensionType::TransportParameters | ExtensionType::Unknown(_) => { - continue; - } - _ => {} - }; - - // "inner" truncation, where the extension-level length agrees with the input - // length, but isn't long enough for the type of extension - for l in 0..(enc.len() - 4) { - put_u16(l as u16, &mut enc[2..]); - println!(" encoding {:?} len {:?}", enc, l); - assert!(ClientExtension::read_bytes(&enc).is_err()); - } + // "outer" truncation, i.e., where the extension-level length is longer than + // the input + for l in 1..enc.len() { + assert!(ClientExtensions::read_bytes(&enc[..l]).is_err()); } } -#[test] -fn client_sni_extension() { - test_client_extension_getter(ExtensionType::ServerName, |chp| { - chp.sni_extension().is_some() - }); -} - -#[test] -fn client_sigalgs_extension() { - test_client_extension_getter(ExtensionType::SignatureAlgorithms, |chp| { - chp.sigalgs_extension().is_some() - }); -} - -#[test] -fn client_namedgroups_extension() { - test_client_extension_getter(ExtensionType::EllipticCurves, |chp| { - chp.namedgroups_extension().is_some() - }); -} - -#[cfg(feature = "tls12")] -#[test] -fn client_ecpoints_extension() { - test_client_extension_getter(ExtensionType::ECPointFormats, |chp| { - chp.ecpoints_extension().is_some() - }); -} - -#[test] -fn client_alpn_extension() { - test_client_extension_getter(ExtensionType::ALProtocolNegotiation, |chp| { - chp.alpn_extension().is_some() - }); -} - -#[test] -fn client_client_certificate_extension() { - test_client_extension_getter(ExtensionType::ClientCertificateType, |chp| { - chp.client_certificate_extension() - .is_some() - }); -} - -#[test] -fn client_server_certificate_extension() { - test_client_extension_getter(ExtensionType::ServerCertificateType, |chp| { - chp.server_certificate_extension() - .is_some() - }); -} - -#[test] -fn client_quic_params_extension() { - test_client_extension_getter(ExtensionType::TransportParameters, |chp| { - chp.quic_params_extension().is_some() - }); -} - -#[test] -fn client_versions_extension() { - test_client_extension_getter(ExtensionType::SupportedVersions, |chp| { - chp.versions_extension().is_some() - }); -} - -#[test] -fn client_keyshare_extension() { - test_client_extension_getter(ExtensionType::KeyShare, |chp| { - chp.keyshare_extension().is_some() - }); -} - -#[test] -fn client_psk() { - test_client_extension_getter(ExtensionType::PreSharedKey, |chp| chp.psk().is_some()); -} - -#[test] -fn client_psk_modes() { - test_client_extension_getter(ExtensionType::PSKKeyExchangeModes, |chp| { - chp.psk_modes().is_some() - }); -} - -fn test_client_extension_getter(typ: ExtensionType, getter: fn(&ClientHelloPayload) -> bool) { - let mut chp = sample_client_hello_payload(); - let ext = chp.find_extension(typ).unwrap().clone(); - - chp.extensions = vec![]; - assert!(!getter(&chp)); - - chp.extensions = vec![ext]; - assert!(getter(&chp)); - - chp.extensions = vec![ClientExtension::Unknown(UnknownExtension { - typ, - payload: Payload::Borrowed(&[]), - })]; - assert!(!getter(&chp)); -} - #[test] fn test_truncated_hello_retry_extension_is_detected() { let hrr = sample_hello_retry_request(); - for ext in &hrr.extensions { - let mut enc = ext.get_encoding(); - println!("testing {:?} enc {:?}", ext, enc); - - // "outer" truncation, i.e., where the extension-level length is longer than - // the input - for l in 0..enc.len() { - assert!(HelloRetryExtension::read_bytes(&enc[..l]).is_err()); - } - - // these extension types don't have any internal encoding that rustls validates: - if let ExtensionType::Unknown(_) = ext.ext_type() { - continue; - } + let mut enc = hrr.extensions.get_encoding(); + println!("testing enc {enc:?}"); - // "inner" truncation, where the extension-level length agrees with the input - // length, but isn't long enough for the type of extension - for l in 0..(enc.len() - 4) { - put_u16(l as u16, &mut enc[2..]); - println!(" encoding {:?} len {:?}", enc, l); - assert!(HelloRetryExtension::read_bytes(&enc).is_err()); - } + // "outer" truncation, i.e., where the extension-level length is longer than + // the input + for l in 0..enc.len() { + assert!(HelloRetryRequestExtensions::read_bytes(&enc[..l]).is_err()); } -} - -#[test] -fn hello_retry_requested_key_share_group() { - test_hello_retry_extension_getter(ExtensionType::KeyShare, |hrr| { - hrr.requested_key_share_group() - .is_some() - }); -} -#[test] -fn hello_retry_cookie() { - test_hello_retry_extension_getter(ExtensionType::Cookie, |hrr| hrr.cookie().is_some()); -} - -#[test] -fn hello_retry_supported_versions() { - test_hello_retry_extension_getter(ExtensionType::SupportedVersions, |hrr| { - hrr.supported_versions().is_some() - }); -} - -fn test_hello_retry_extension_getter(typ: ExtensionType, getter: fn(&HelloRetryRequest) -> bool) { - let mut hrr = sample_hello_retry_request(); - let mut exts = core::mem::take(&mut hrr.extensions); - exts.retain(|ext| ext.ext_type() == typ); - - assert!(!getter(&hrr)); - - hrr.extensions = exts; - assert!(getter(&hrr)); - - hrr.extensions = vec![HelloRetryExtension::Unknown(UnknownExtension { - typ, - payload: Payload::Borrowed(&[]), - })]; - assert!(!getter(&hrr)); + // "inner" truncation, where the extension-level length agrees with the input + // length, but isn't long enough for the type of extension + for l in 0..(enc.len() - 4) { + put_u16(l as u16, &mut enc); + println!(" encoding {enc:?} len {l:?}"); + assert!(HelloRetryRequestExtensions::read_bytes(&enc).is_err()); + } } #[test] fn test_truncated_server_extension_is_detected() { let shp = sample_server_hello_payload(); - for ext in &shp.extensions { - let mut enc = ext.get_encoding(); - println!("testing {:?} enc {:?}", ext, enc); + let mut enc = shp.extensions.get_encoding(); + println!("testing enc {enc:?}"); - // "outer" truncation, i.e., where the extension-level length is longer than - // the input - for l in 0..enc.len() { - assert!(ServerExtension::read_bytes(&enc[..l]).is_err()); - } - - // these extension types don't have any internal encoding that rustls validates: - match ext.ext_type() { - ExtensionType::TransportParameters | ExtensionType::Unknown(_) => { - continue; - } - _ => {} - }; - - // "inner" truncation, where the extension-level length agrees with the input - // length, but isn't long enough for the type of extension - for l in 0..(enc.len() - 4) { - put_u16(l as u16, &mut enc[2..]); - println!(" encoding {:?} len {:?}", enc, l); - assert!(ServerExtension::read_bytes(&enc).is_err()); - } + // "outer" truncation, i.e., where the extension-level length is longer than + // the input + for l in 0..enc.len() { + assert!(ServerExtensions::read_bytes(&enc[..l]).is_err()); } -} - -fn test_server_extension_getter(typ: ExtensionType, getter: fn(&ServerHelloPayload) -> bool) { - let mut shp = sample_server_hello_payload(); - let ext = shp.find_extension(typ).unwrap().clone(); - - shp.extensions = vec![]; - assert!(!getter(&shp)); - - shp.extensions = vec![ext]; - assert!(getter(&shp)); - - shp.extensions = vec![ServerExtension::Unknown(UnknownExtension { - typ, - payload: Payload::Borrowed(&[]), - })]; - assert!(!getter(&shp)); -} - -#[test] -fn server_key_share() { - test_server_extension_getter(ExtensionType::KeyShare, |shp| shp.key_share().is_some()); -} - -#[test] -fn server_psk_index() { - test_server_extension_getter(ExtensionType::PreSharedKey, |shp| shp.psk_index().is_some()); -} - -#[test] -fn server_ecpoints_extension() { - test_server_extension_getter(ExtensionType::ECPointFormats, |shp| { - shp.ecpoints_extension().is_some() - }); -} - -#[test] -fn server_supported_versions() { - test_server_extension_getter(ExtensionType::SupportedVersions, |shp| { - shp.supported_versions().is_some() - }); -} - -#[test] -fn server_client_certificate_type_extension() { - test_server_extension_getter(ExtensionType::ClientCertificateType, |shp| { - shp.client_cert_type().is_some() - }); -} - -#[test] -fn server_server_certificate_type_extension() { - test_server_extension_getter(ExtensionType::ServerCertificateType, |shp| { - shp.server_cert_type().is_some() - }); -} - -#[test] -fn cert_entry_ocsp_response() { - test_cert_extension_getter(ExtensionType::StatusRequest, |ce| { - ce.ocsp_response().is_some() - }); -} - -fn test_cert_extension_getter(typ: ExtensionType, getter: fn(&CertificateEntry<'_>) -> bool) { - let mut ce = sample_certificate_payload_tls13() - .entries - .remove(0); - let mut exts = core::mem::take(&mut ce.exts); - exts.retain(|ext| ext.ext_type() == typ); - assert!(!getter(&ce)); - - ce.exts = exts; - assert!(getter(&ce)); - - ce.exts = vec![CertificateExtension::Unknown(UnknownExtension { - typ, - payload: Payload::Borrowed(&[]), - })]; - assert!(!getter(&ce)); + // "inner" truncation, where the extension-level length agrees with the input + // length, but isn't long enough for the type of extension + for l in 0..(enc.len() - 4) { + put_u16(l as u16, &mut enc[..2]); + println!(" encoding {enc:?} len {l:?}"); + assert!(ServerExtensions::read_bytes(&enc).is_err()); + } } #[test] @@ -749,16 +571,16 @@ fn can_clone_all_server_extensions() { #[test] fn can_round_trip_all_tls12_handshake_payloads() { - for ref hm in all_tls12_handshake_payloads().iter() { - println!("{:?}", hm.typ); + for hm in all_tls12_handshake_payloads().iter() { + println!("{:?}", hm.0.handshake_type()); let bytes = hm.get_encoding(); - let mut rd = Reader::init(&bytes); + let mut rd = Reader::new(&bytes); let other = HandshakeMessagePayload::read(&mut rd).unwrap(); assert!(!rd.any_left()); assert_eq!(hm.get_encoding(), other.get_encoding()); - println!("{:?}", hm); - println!("{:?}", other); + println!("{hm:?}"); + println!("{other:?}"); } } @@ -777,7 +599,7 @@ fn can_into_owned_all_tls12_handshake_payloads() { fn can_detect_truncation_of_all_tls12_handshake_payloads() { for hm in all_tls12_handshake_payloads().iter() { let mut enc = hm.get_encoding(); - println!("test {:?} enc {:?}", hm, enc); + println!("test {hm:?} enc {enc:?}"); // outer truncation for l in 0..enc.len() { @@ -787,23 +609,25 @@ fn can_detect_truncation_of_all_tls12_handshake_payloads() { // inner truncation for l in 0..enc.len() - 4 { put_u24(l as u32, &mut enc[1..]); - println!(" check len {:?} enc {:?}", l, enc); + println!(" check len {l:?} enc {enc:?}"); - match (hm.typ, l) { + match (hm.0.handshake_type(), l) { (HandshakeType::ClientHello, 41) | (HandshakeType::ServerHello, 38) | (HandshakeType::ServerKeyExchange, _) | (HandshakeType::ClientKeyExchange, _) | (HandshakeType::Finished, _) - | (HandshakeType::Unknown(_), _) => continue, + | (HandshakeType(99), _) => continue, _ => {} }; - assert!(HandshakeMessagePayload::read_version( - &mut Reader::init(&enc), - ProtocolVersion::TLSv1_2 - ) - .is_err()); + assert!( + HandshakeMessagePayload::read_version( + &mut Reader::new(&enc), + ProtocolVersion::TLSv1_2 + ) + .is_err() + ); assert!(HandshakeMessagePayload::read_bytes(&enc).is_err()); } } @@ -811,18 +635,18 @@ fn can_detect_truncation_of_all_tls12_handshake_payloads() { #[test] fn can_round_trip_all_tls13_handshake_payloads() { - for ref hm in all_tls13_handshake_payloads().iter() { - println!("{:?}", hm.typ); + for hm in all_tls13_handshake_payloads().iter() { + println!("{:?}", hm.0.handshake_type()); let bytes = hm.get_encoding(); - let mut rd = Reader::init(&bytes); + let mut rd = Reader::new(&bytes); let other = HandshakeMessagePayload::read_version(&mut rd, ProtocolVersion::TLSv1_3).unwrap(); assert!(!rd.any_left()); assert_eq!(hm.get_encoding(), other.get_encoding()); - println!("{:?}", hm); - println!("{:?}", other); + println!("{hm:?}"); + println!("{other:?}"); } } @@ -841,7 +665,7 @@ fn can_into_owned_all_tls13_handshake_payloads() { fn can_detect_truncation_of_all_tls13_handshake_payloads() { for hm in all_tls13_handshake_payloads().iter() { let mut enc = hm.get_encoding(); - println!("test {:?} enc {:?}", hm, enc); + println!("test {hm:?} enc {enc:?}"); // outer truncation for l in 0..enc.len() { @@ -851,23 +675,25 @@ fn can_detect_truncation_of_all_tls13_handshake_payloads() { // inner truncation for l in 0..enc.len() - 4 { put_u24(l as u32, &mut enc[1..]); - println!(" check len {:?} enc {:?}", l, enc); + println!(" check len {l:?} enc {enc:?}"); - match (hm.typ, l) { + match (hm.0.handshake_type(), l) { (HandshakeType::ClientHello, 41) | (HandshakeType::ServerHello, 38) | (HandshakeType::ServerKeyExchange, _) | (HandshakeType::ClientKeyExchange, _) | (HandshakeType::Finished, _) - | (HandshakeType::Unknown(_), _) => continue, + | (HandshakeType(99), _) => continue, _ => {} }; - assert!(HandshakeMessagePayload::read_version( - &mut Reader::init(&enc), - ProtocolVersion::TLSv1_3 - ) - .is_err()); + assert!( + HandshakeMessagePayload::read_version( + &mut Reader::new(&enc), + ProtocolVersion::TLSv1_3 + ) + .is_err() + ); } } } @@ -880,11 +706,8 @@ fn put_u24(u: u32, b: &mut [u8]) { #[test] fn cannot_read_message_hash_from_network() { - let mh = HandshakeMessagePayload { - typ: HandshakeType::MessageHash, - payload: HandshakePayload::MessageHash(Payload::new(vec![1, 2, 3])), - }; - println!("mh {:?}", mh); + let mh = HandshakeMessagePayload(HandshakePayload::MessageHash(Payload::new(vec![1, 2, 3]))); + println!("mh {mh:?}"); let enc = mh.get_encoding(); assert!(HandshakeMessagePayload::read_bytes(&enc).is_err()); } @@ -921,9 +744,9 @@ fn cannot_decode_huge_certificate() { #[test] fn can_decode_server_hello_from_api_devicecheck_apple_com() { let data = include_bytes!("../testdata/hello-api.devicecheck.apple.com.bin"); - let mut r = Reader::init(data); + let mut r = Reader::new(data); let hm = HandshakeMessagePayload::read(&mut r).unwrap(); - println!("msg: {:?}", hm); + println!("msg: {hm:?}"); } #[test] @@ -935,20 +758,121 @@ fn wrapped_dn_encoding() { assert_eq!(dn.as_ref(), [expected_prefix, subject.to_vec()].concat()); } +#[test] +fn test_decode_config_list() { + fn assert_config(contents: &EchConfigContents, public_name: impl AsRef<[u8]>, max_len: u8) { + assert_eq!(contents.maximum_name_length, max_len); + assert_eq!( + contents.public_name, + DnsName::try_from(public_name.as_ref()).unwrap() + ); + assert!(contents.extensions.is_empty()); + } + + fn assert_key_config( + config: &HpkeKeyConfig, + id: u8, + kem_id: HpkeKem, + cipher_suites: Vec, + ) { + assert_eq!(config.config_id, id); + assert_eq!(config.kem_id, kem_id); + assert_eq!(config.symmetric_cipher_suites, cipher_suites); + } + + let config_list = get_ech_config(ECHCONFIG_LIST_LOCALHOST); + assert_eq!(config_list.len(), 1); + let EchConfigPayload::V18(contents) = &config_list[0] else { + std::panic!("unexpected ECH config version: {:?}", config_list[0]); + }; + assert_config(contents, "localhost", 128); + assert_key_config( + &contents.key_config, + 0, + HpkeKem::DHKEM_X25519_HKDF_SHA256, + vec![ + HpkeSymmetricCipherSuite { + kdf_id: HpkeKdf::HKDF_SHA256, + aead_id: HpkeAead::AES_128_GCM, + }, + HpkeSymmetricCipherSuite { + kdf_id: HpkeKdf::HKDF_SHA256, + aead_id: HpkeAead::CHACHA20_POLY_1305, + }, + ], + ); + + let config_list = get_ech_config(ECHCONFIG_LIST_CF); + assert_eq!(config_list.len(), 2); + let EchConfigPayload::V18(contents_a) = &config_list[0] else { + std::panic!("unexpected ECH config version: {:?}", config_list[0]); + }; + assert_config(contents_a, "cloudflare-esni.com", 37); + assert_key_config( + &contents_a.key_config, + 195, + HpkeKem::DHKEM_X25519_HKDF_SHA256, + vec![HpkeSymmetricCipherSuite { + kdf_id: HpkeKdf::HKDF_SHA256, + aead_id: HpkeAead::AES_128_GCM, + }], + ); + let EchConfigPayload::V18(contents_b) = &config_list[1] else { + std::panic!("unexpected ECH config version: {:?}", config_list[1]); + }; + assert_config(contents_b, "cloudflare-esni.com", 42); + assert_key_config( + &contents_b.key_config, + 3, + HpkeKem::DHKEM_P256_HKDF_SHA256, + vec![HpkeSymmetricCipherSuite { + kdf_id: HpkeKdf::HKDF_SHA256, + aead_id: HpkeAead::AES_128_GCM, + }], + ); + + let config_list = get_ech_config(ECHCONFIG_LIST_WITH_UNSUPPORTED); + assert_eq!(config_list.len(), 4); + // The first config should be unsupported. + assert!(matches!( + config_list[0], + EchConfigPayload::Unknown { + version: EchVersion(0xBADD), + .. + } + )); + // The other configs should be recognized. + for config in config_list.iter().skip(1) { + assert!(matches!(config, EchConfigPayload::V18(_))); + } +} + +#[test] +fn test_echconfig_serialization() { + fn assert_round_trip_eq(original: &[u8]) { + let configs = get_ech_config(original); + let mut output = Vec::new(); + configs.encode(&mut output); + assert_eq!(original, output); + } + + assert_round_trip_eq(ECHCONFIG_LIST_LOCALHOST); + assert_round_trip_eq(ECHCONFIG_LIST_CF); + assert_round_trip_eq(ECHCONFIG_LIST_WITH_UNSUPPORTED); +} + fn sample_hello_retry_request() -> HelloRetryRequest { HelloRetryRequest { legacy_version: ProtocolVersion::TLSv1_2, session_id: SessionId::empty(), - cipher_suite: CipherSuite::TLS_NULL_WITH_NULL_NULL, - extensions: vec![ - HelloRetryExtension::KeyShare(NamedGroup::X25519), - HelloRetryExtension::Cookie(PayloadU16(vec![0])), - HelloRetryExtension::SupportedVersions(ProtocolVersion::TLSv1_2), - HelloRetryExtension::Unknown(UnknownExtension { - typ: ExtensionType::Unknown(12345), - payload: Payload::Borrowed(&[1, 2, 3]), - }), - ], + cipher_suite: CipherSuite::TLS_PSK_DHE_WITH_AES_128_CCM_8, + extensions: HelloRetryRequestExtensions { + key_share: Some(NamedGroup::X25519), + cookie: Some(SizedPayload::::from(vec![0])), + supported_versions: Some(ProtocolVersion::TLSv1_2), + encrypted_client_hello: Some(Payload::new(vec![1, 2, 3])), + order: None, + }, } } @@ -957,20 +881,28 @@ fn sample_client_hello_payload() -> ClientHelloPayload { client_version: ProtocolVersion::TLSv1_2, random: Random::from([0; 32]), session_id: SessionId::empty(), - cipher_suites: vec![CipherSuite::TLS_NULL_WITH_NULL_NULL], + cipher_suites: vec![CipherSuite::TLS_PSK_WITH_AES_128_CCM], compression_methods: vec![Compression::Null], - extensions: vec![ - ClientExtension::EcPointFormats(ECPointFormat::SUPPORTED.to_vec()), - ClientExtension::NamedGroups(vec![NamedGroup::X25519]), - ClientExtension::SignatureAlgorithms(vec![SignatureScheme::ECDSA_NISTP256_SHA256]), - ClientExtension::make_sni(&DnsName::try_from("hello").unwrap()), - ClientExtension::SessionTicket(ClientSessionTicket::Request), - ClientExtension::SessionTicket(ClientSessionTicket::Offer(Payload::Borrowed(&[]))), - ClientExtension::Protocols(vec![ProtocolName::from(vec![0])]), - ClientExtension::SupportedVersions(vec![ProtocolVersion::TLSv1_3]), - ClientExtension::KeyShare(vec![KeyShareEntry::new(NamedGroup::X25519, &[1, 2, 3][..])]), - ClientExtension::PresharedKeyModes(vec![PSKKeyExchangeMode::PSK_DHE_KE]), - ClientExtension::PresharedKey(PresharedKeyOffer { + extensions: Box::new(ClientExtensions { + server_name: Some(ServerNamePayload::from( + &DnsName::try_from("hello").unwrap(), + )), + cookie: Some(SizedPayload::::from(vec![1, 2, 3])), + signature_schemes: Some(vec![SignatureScheme::ECDSA_NISTP256_SHA256]), + session_ticket: Some(ClientSessionTicket::Request), + ec_point_formats: Some(SupportedEcPointFormats::default()), + named_groups: Some(vec![NamedGroup::X25519]), + protocols: Some(vec![ApplicationProtocol::from(vec![0])]), + supported_versions: Some(SupportedProtocolVersions { + tls13: true, + ..Default::default() + }), + key_shares: Some(vec![KeyShareEntry::new(NamedGroup::X25519, &[1, 2, 3][..])]), + preshared_key_modes: Some(PskKeyExchangeModes { + psk_dhe: true, + psk: false, + }), + preshared_key_offer: Some(PresharedKeyOffer { identities: vec![ PresharedKeyIdentity::new(vec![3, 4, 5], 123456), PresharedKeyIdentity::new(vec![6, 7, 8], 7891011), @@ -980,22 +912,17 @@ fn sample_client_hello_payload() -> ClientHelloPayload { PresharedKeyBinder::from(vec![3, 4, 5]), ], }), - ClientExtension::Cookie(PayloadU16(vec![1, 2, 3])), - ClientExtension::ExtendedMasterSecretRequest, - ClientExtension::CertificateStatusRequest(CertificateStatusRequest::build_ocsp()), - ClientExtension::ServerCertTypes(vec![CertificateType::RawPublicKey]), - ClientExtension::ClientCertTypes(vec![CertificateType::RawPublicKey]), - ClientExtension::TransportParameters(vec![1, 2, 3]), - ClientExtension::EarlyData, - ClientExtension::CertificateCompressionAlgorithms(vec![ - CertificateCompressionAlgorithm::Brotli, - CertificateCompressionAlgorithm::Zlib, - ]), - ClientExtension::Unknown(UnknownExtension { - typ: ExtensionType::Unknown(12345), - payload: Payload::Borrowed(&[1, 2, 3]), - }), - ], + extended_master_secret_request: Some(()), + certificate_status_request: Some(CertificateStatusRequest::build_ocsp()), + server_certificate_types: Some(vec![CertificateType::RawPublicKey]), + client_certificate_types: Some(vec![CertificateType::RawPublicKey]), + transport_parameters: Some(Payload::new(vec![1, 2, 3])), + early_data_request: Some(()), + certificate_compression_algorithms: Some(vec![CertificateCompressionAlgorithm::Brotli]), + encrypted_client_hello: Some(EncryptedClientHello::Inner), + encrypted_client_hello_outer: Some(vec![ExtensionType::SCT]), + ..Default::default() + }), } } @@ -1004,219 +931,147 @@ fn sample_server_hello_payload() -> ServerHelloPayload { legacy_version: ProtocolVersion::TLSv1_2, random: Random::from([0; 32]), session_id: SessionId::empty(), - cipher_suite: CipherSuite::TLS_NULL_WITH_NULL_NULL, + cipher_suite: CipherSuite::TLS_PSK_WITH_AES_128_CCM, compression_method: Compression::Null, - extensions: vec![ - ServerExtension::EcPointFormats(ECPointFormat::SUPPORTED.to_vec()), - ServerExtension::ServerNameAck, - ServerExtension::SessionTicketAck, - ServerExtension::RenegotiationInfo(PayloadU8(vec![0])), - ServerExtension::Protocols(vec![ProtocolName::from(vec![0])]), - ServerExtension::KeyShare(KeyShareEntry::new(NamedGroup::X25519, &[1, 2, 3][..])), - ServerExtension::PresharedKey(3), - ServerExtension::ExtendedMasterSecretAck, - ServerExtension::CertificateStatusAck, - ServerExtension::SupportedVersions(ProtocolVersion::TLSv1_2), - ServerExtension::TransportParameters(vec![1, 2, 3]), - ServerExtension::Unknown(UnknownExtension { - typ: ExtensionType::Unknown(12345), - payload: Payload::Borrowed(&[1, 2, 3]), + extensions: Box::new(ServerExtensions { + ec_point_formats: Some(SupportedEcPointFormats::default()), + server_name_ack: Some(()), + session_ticket_ack: Some(()), + renegotiation_info: Some(SizedPayload::from(vec![0])), + selected_protocol: Some(SingleProtocolName::new(ApplicationProtocol::from(vec![0]))), + key_share: Some(KeyShareEntry::new(NamedGroup::X25519, &[1, 2, 3][..])), + preshared_key: Some(3), + early_data_ack: Some(()), + encrypted_client_hello_ack: Some(ServerEncryptedClientHello { + retry_configs: vec![], }), - ServerExtension::ClientCertType(CertificateType::RawPublicKey), - ServerExtension::ServerCertType(CertificateType::RawPublicKey), - ], + extended_master_secret_ack: Some(()), + certificate_status_request_ack: Some(()), + selected_version: Some(ProtocolVersion::TLSv1_2), + transport_parameters: Some(Payload::new(vec![1, 2, 3])), + client_certificate_type: Some(CertificateType::RawPublicKey), + server_certificate_type: Some(CertificateType::RawPublicKey), + unknown_extensions: Default::default(), + }), } } fn all_tls12_handshake_payloads() -> Vec> { vec![ - HandshakeMessagePayload { - typ: HandshakeType::HelloRequest, - payload: HandshakePayload::HelloRequest, - }, - HandshakeMessagePayload { - typ: HandshakeType::ClientHello, - payload: HandshakePayload::ClientHello(sample_client_hello_payload()), - }, - HandshakeMessagePayload { - typ: HandshakeType::ServerHello, - payload: HandshakePayload::ServerHello(sample_server_hello_payload()), - }, - HandshakeMessagePayload { - typ: HandshakeType::HelloRetryRequest, - payload: HandshakePayload::HelloRetryRequest(sample_hello_retry_request()), - }, - HandshakeMessagePayload { - typ: HandshakeType::Certificate, - payload: HandshakePayload::Certificate(CertificateChain(vec![CertificateDer::from( - vec![1, 2, 3], - )])), - }, - HandshakeMessagePayload { - typ: HandshakeType::ServerKeyExchange, - payload: HandshakePayload::ServerKeyExchange(sample_ecdhe_server_key_exchange_payload()), - }, - HandshakeMessagePayload { - typ: HandshakeType::ServerKeyExchange, - payload: HandshakePayload::ServerKeyExchange(sample_dhe_server_key_exchange_payload()), - }, - HandshakeMessagePayload { - typ: HandshakeType::ServerKeyExchange, - payload: HandshakePayload::ServerKeyExchange( - sample_unknown_server_key_exchange_payload(), - ), - }, - HandshakeMessagePayload { - typ: HandshakeType::CertificateRequest, - payload: HandshakePayload::CertificateRequest(sample_certificate_request_payload()), - }, - HandshakeMessagePayload { - typ: HandshakeType::ServerHelloDone, - payload: HandshakePayload::ServerHelloDone, - }, - HandshakeMessagePayload { - typ: HandshakeType::ClientKeyExchange, - payload: HandshakePayload::ClientKeyExchange(Payload::Borrowed(&[1, 2, 3])), - }, - HandshakeMessagePayload { - typ: HandshakeType::NewSessionTicket, - payload: HandshakePayload::NewSessionTicket(sample_new_session_ticket_payload()), - }, - HandshakeMessagePayload { - typ: HandshakeType::EncryptedExtensions, - payload: HandshakePayload::EncryptedExtensions(sample_encrypted_extensions()), - }, - HandshakeMessagePayload { - typ: HandshakeType::KeyUpdate, - payload: HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateRequested), - }, - HandshakeMessagePayload { - typ: HandshakeType::KeyUpdate, - payload: HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateNotRequested), - }, - HandshakeMessagePayload { - typ: HandshakeType::Finished, - payload: HandshakePayload::Finished(Payload::Borrowed(&[1, 2, 3])), - }, - HandshakeMessagePayload { - typ: HandshakeType::CertificateStatus, - payload: HandshakePayload::CertificateStatus(sample_certificate_status()), - }, - HandshakeMessagePayload { - typ: HandshakeType::Unknown(99), - payload: HandshakePayload::Unknown(Payload::Borrowed(&[1, 2, 3])), - }, + HandshakeMessagePayload(HandshakePayload::HelloRequest), + HandshakeMessagePayload(HandshakePayload::ClientHello(sample_client_hello_payload())), + HandshakeMessagePayload(HandshakePayload::ServerHello(sample_server_hello_payload())), + HandshakeMessagePayload(HandshakePayload::HelloRetryRequest( + sample_hello_retry_request(), + )), + HandshakeMessagePayload(HandshakePayload::Certificate(CertificateChain(vec![ + CertificateDer::from(vec![1, 2, 3]), + ]))), + HandshakeMessagePayload(HandshakePayload::ServerKeyExchange( + sample_ecdhe_server_key_exchange_payload(), + )), + HandshakeMessagePayload(HandshakePayload::ServerKeyExchange( + sample_dhe_server_key_exchange_payload(), + )), + HandshakeMessagePayload(HandshakePayload::ServerKeyExchange( + sample_unknown_server_key_exchange_payload(), + )), + HandshakeMessagePayload(HandshakePayload::CertificateRequest( + sample_certificate_request_payload(), + )), + HandshakeMessagePayload(HandshakePayload::ServerHelloDone), + HandshakeMessagePayload(HandshakePayload::ClientKeyExchange(Payload::Borrowed(&[ + 1, 2, 3, + ]))), + HandshakeMessagePayload(HandshakePayload::NewSessionTicket( + sample_new_session_ticket_payload(), + )), + HandshakeMessagePayload(HandshakePayload::EncryptedExtensions( + sample_encrypted_extensions(), + )), + HandshakeMessagePayload(HandshakePayload::KeyUpdate( + KeyUpdateRequest::UpdateRequested, + )), + HandshakeMessagePayload(HandshakePayload::KeyUpdate( + KeyUpdateRequest::UpdateNotRequested, + )), + HandshakeMessagePayload(HandshakePayload::Finished(Payload::Borrowed(&[1, 2, 3]))), + HandshakeMessagePayload(HandshakePayload::CertificateStatus( + sample_certificate_status(), + )), + HandshakeMessagePayload(HandshakePayload::Unknown(( + HandshakeType(99), + Payload::Borrowed(&[1, 2, 3]), + ))), ] } fn all_tls13_handshake_payloads() -> Vec> { vec![ - HandshakeMessagePayload { - typ: HandshakeType::HelloRequest, - payload: HandshakePayload::HelloRequest, - }, - HandshakeMessagePayload { - typ: HandshakeType::ClientHello, - payload: HandshakePayload::ClientHello(sample_client_hello_payload()), - }, - HandshakeMessagePayload { - typ: HandshakeType::ServerHello, - payload: HandshakePayload::ServerHello(sample_server_hello_payload()), - }, - HandshakeMessagePayload { - typ: HandshakeType::HelloRetryRequest, - payload: HandshakePayload::HelloRetryRequest(sample_hello_retry_request()), - }, - HandshakeMessagePayload { - typ: HandshakeType::Certificate, - payload: HandshakePayload::CertificateTls13(sample_certificate_payload_tls13()), - }, - HandshakeMessagePayload { - typ: HandshakeType::CompressedCertificate, - payload: HandshakePayload::CompressedCertificate(sample_compressed_certificate()), - }, - HandshakeMessagePayload { - typ: HandshakeType::ServerKeyExchange, - payload: HandshakePayload::ServerKeyExchange(sample_ecdhe_server_key_exchange_payload()), - }, - HandshakeMessagePayload { - typ: HandshakeType::ServerKeyExchange, - payload: HandshakePayload::ServerKeyExchange(sample_dhe_server_key_exchange_payload()), - }, - HandshakeMessagePayload { - typ: HandshakeType::ServerKeyExchange, - payload: HandshakePayload::ServerKeyExchange( - sample_unknown_server_key_exchange_payload(), - ), - }, - HandshakeMessagePayload { - typ: HandshakeType::CertificateRequest, - payload: HandshakePayload::CertificateRequestTls13( - sample_certificate_request_payload_tls13(), - ), - }, - HandshakeMessagePayload { - typ: HandshakeType::CertificateVerify, - payload: HandshakePayload::CertificateVerify(DigitallySignedStruct::new( - SignatureScheme::ECDSA_NISTP256_SHA256, - vec![1, 2, 3], - )), - }, - HandshakeMessagePayload { - typ: HandshakeType::ServerHelloDone, - payload: HandshakePayload::ServerHelloDone, - }, - HandshakeMessagePayload { - typ: HandshakeType::ClientKeyExchange, - payload: HandshakePayload::ClientKeyExchange(Payload::Borrowed(&[1, 2, 3])), - }, - HandshakeMessagePayload { - typ: HandshakeType::NewSessionTicket, - payload: HandshakePayload::NewSessionTicketTls13( - sample_new_session_ticket_payload_tls13(), - ), - }, - HandshakeMessagePayload { - typ: HandshakeType::EncryptedExtensions, - payload: HandshakePayload::EncryptedExtensions(sample_encrypted_extensions()), - }, - HandshakeMessagePayload { - typ: HandshakeType::KeyUpdate, - payload: HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateRequested), - }, - HandshakeMessagePayload { - typ: HandshakeType::KeyUpdate, - payload: HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateNotRequested), - }, - HandshakeMessagePayload { - typ: HandshakeType::Finished, - payload: HandshakePayload::Finished(Payload::Borrowed(&[1, 2, 3])), - }, - HandshakeMessagePayload { - typ: HandshakeType::CertificateStatus, - payload: HandshakePayload::CertificateStatus(sample_certificate_status()), - }, - HandshakeMessagePayload { - typ: HandshakeType::Unknown(99), - payload: HandshakePayload::Unknown(Payload::Borrowed(&[1, 2, 3])), - }, + HandshakeMessagePayload(HandshakePayload::HelloRequest), + HandshakeMessagePayload(HandshakePayload::ClientHello(sample_client_hello_payload())), + HandshakeMessagePayload(HandshakePayload::ServerHello(sample_server_hello_payload())), + HandshakeMessagePayload(HandshakePayload::HelloRetryRequest( + sample_hello_retry_request(), + )), + HandshakeMessagePayload(HandshakePayload::CertificateTls13( + sample_certificate_payload_tls13(), + )), + HandshakeMessagePayload(HandshakePayload::CompressedCertificate( + sample_compressed_certificate(), + )), + HandshakeMessagePayload(HandshakePayload::ServerKeyExchange( + sample_ecdhe_server_key_exchange_payload(), + )), + HandshakeMessagePayload(HandshakePayload::ServerKeyExchange( + sample_dhe_server_key_exchange_payload(), + )), + HandshakeMessagePayload(HandshakePayload::ServerKeyExchange( + sample_unknown_server_key_exchange_payload(), + )), + HandshakeMessagePayload(HandshakePayload::CertificateRequestTls13( + sample_certificate_request_payload_tls13(), + )), + HandshakeMessagePayload(HandshakePayload::CertificateVerify( + DigitallySignedStruct::new(SignatureScheme::ECDSA_NISTP256_SHA256, vec![1, 2, 3]), + )), + HandshakeMessagePayload(HandshakePayload::ServerHelloDone), + HandshakeMessagePayload(HandshakePayload::ClientKeyExchange(Payload::Borrowed(&[ + 1, 2, 3, + ]))), + HandshakeMessagePayload(HandshakePayload::NewSessionTicketTls13( + sample_new_session_ticket_payload_tls13(), + )), + HandshakeMessagePayload(HandshakePayload::EncryptedExtensions( + sample_encrypted_extensions(), + )), + HandshakeMessagePayload(HandshakePayload::KeyUpdate( + KeyUpdateRequest::UpdateRequested, + )), + HandshakeMessagePayload(HandshakePayload::KeyUpdate( + KeyUpdateRequest::UpdateNotRequested, + )), + HandshakeMessagePayload(HandshakePayload::Finished(Payload::Borrowed(&[1, 2, 3]))), + HandshakeMessagePayload(HandshakePayload::CertificateStatus( + sample_certificate_status(), + )), + HandshakeMessagePayload(HandshakePayload::Unknown(( + HandshakeType(99), + Payload::Borrowed(&[1, 2, 3]), + ))), ] } fn sample_certificate_payload_tls13() -> CertificatePayloadTls13<'static> { CertificatePayloadTls13 { - context: PayloadU8(vec![1, 2, 3]), + context: SizedPayload::from(vec![1, 2, 3]), entries: vec![CertificateEntry { cert: CertificateDer::from(vec![3, 4, 5]), - exts: vec![ - CertificateExtension::CertificateStatus(CertificateStatus { - ocsp_response: PayloadU24(Payload::new(vec![1, 2, 3])), + extensions: CertificateExtensions { + status: Some(CertificateStatus { + ocsp_response: SizedPayload::from(Payload::new(vec![1, 2, 3])), }), - CertificateExtension::Unknown(UnknownExtension { - typ: ExtensionType::Unknown(12345), - payload: Payload::Borrowed(&[1, 2, 3]), - }), - ], + }, }], } } @@ -1225,7 +1080,7 @@ fn sample_compressed_certificate() -> CompressedCertificatePayload<'static> { CompressedCertificatePayload { alg: CertificateCompressionAlgorithm::Brotli, uncompressed_len: 123, - compressed: PayloadU24(Payload::new(vec![1, 2, 3])), + compressed: SizedPayload::from(Payload::new(vec![1, 2, 3])), } } @@ -1236,7 +1091,7 @@ fn sample_ecdhe_server_key_exchange_payload() -> ServerKeyExchangePayload { curve_type: ECCurveType::NamedCurve, named_group: NamedGroup::X25519, }, - public: PayloadU8(vec![1, 2, 3]), + public: SizedPayload::from(vec![1, 2, 3]), }), dss: DigitallySignedStruct::new(SignatureScheme::RSA_PSS_SHA256, vec![1, 2, 3]), }) @@ -1245,9 +1100,9 @@ fn sample_ecdhe_server_key_exchange_payload() -> ServerKeyExchangePayload { fn sample_dhe_server_key_exchange_payload() -> ServerKeyExchangePayload { ServerKeyExchangePayload::Known(ServerKeyExchange { params: ServerKeyExchangeParams::Dh(ServerDhParams { - dh_p: PayloadU16(vec![1, 2, 3]), - dh_g: PayloadU16(vec![2]), - dh_Ys: PayloadU16(vec![1, 2]), + dh_p: SizedPayload::::from(vec![1, 2, 3]), + dh_g: SizedPayload::::from(vec![2]), + dh_ys: SizedPayload::::from(vec![1, 2]), }), dss: DigitallySignedStruct::new(SignatureScheme::RSA_PSS_SHA256, vec![1, 2, 3]), }) @@ -1267,44 +1122,55 @@ fn sample_certificate_request_payload() -> CertificateRequestPayload { fn sample_certificate_request_payload_tls13() -> CertificateRequestPayloadTls13 { CertificateRequestPayloadTls13 { - context: PayloadU8(vec![1, 2, 3]), - extensions: vec![ - CertReqExtension::SignatureAlgorithms(vec![SignatureScheme::ECDSA_NISTP256_SHA256]), - CertReqExtension::AuthorityNames(vec![DistinguishedName::from(vec![1, 2, 3])]), - CertReqExtension::Unknown(UnknownExtension { - typ: ExtensionType::Unknown(12345), - payload: Payload::Borrowed(&[1, 2, 3]), - }), - ], + context: SizedPayload::from(vec![1, 2, 3]), + extensions: CertificateRequestExtensions { + signature_algorithms: Some(vec![SignatureScheme::ECDSA_NISTP256_SHA256]), + authority_names: Some(vec![DistinguishedName::from(vec![1, 2, 3])]), + certificate_compression_algorithms: Some(vec![CertificateCompressionAlgorithm::Zlib]), + }, } } fn sample_new_session_ticket_payload() -> NewSessionTicketPayload { NewSessionTicketPayload { - lifetime_hint: 1234, - ticket: Arc::new(PayloadU16(vec![1, 2, 3])), + lifetime_hint: Duration::from_secs(1234), + ticket: Arc::new(SizedPayload::::from(vec![1, 2, 3])), } } fn sample_new_session_ticket_payload_tls13() -> NewSessionTicketPayloadTls13 { NewSessionTicketPayloadTls13 { - lifetime: 123, + lifetime: Duration::from_secs(123), age_add: 1234, - nonce: PayloadU8(vec![1, 2, 3]), - ticket: Arc::new(PayloadU16(vec![4, 5, 6])), - exts: vec![NewSessionTicketExtension::Unknown(UnknownExtension { - typ: ExtensionType::Unknown(12345), - payload: Payload::Borrowed(&[1, 2, 3]), - })], + nonce: SizedPayload::from(vec![1, 2, 3]), + ticket: Arc::new(SizedPayload::::from(vec![4, 5, 6])), + extensions: NewSessionTicketExtensions { + max_early_data_size: Some(1234), + }, } } -fn sample_encrypted_extensions() -> Vec { +fn sample_encrypted_extensions() -> Box> { sample_server_hello_payload().extensions } fn sample_certificate_status() -> CertificateStatus<'static> { CertificateStatus { - ocsp_response: PayloadU24(Payload::new(vec![1, 2, 3])), + ocsp_response: SizedPayload::from(Payload::new(vec![1, 2, 3])), } } + +fn get_ech_config(encoded: &[u8]) -> Vec { + Vec::<_>::read(&mut Reader::new(encoded)).unwrap() +} + +// One EchConfig, with server-name "localhost". +static ECHCONFIG_LIST_LOCALHOST: &[u8] = + include_bytes!("../../tests/data/localhost-echconfigs.bin"); + +// Two EchConfigs, both with server-name "cloudflare-esni.com". +static ECHCONFIG_LIST_CF: &[u8] = include_bytes!("../../tests/data/cloudflare-esni-echconfigs.bin"); + +// Three EchConfigs, the first one with an unsupported version. +static ECHCONFIG_LIST_WITH_UNSUPPORTED: &[u8] = + include_bytes!("../../tests/data/unsupported-then-valid-echconfigs.bin"); diff --git a/rustls/src/msgs/macros.rs b/rustls/src/msgs/macros.rs index 5fe49ae58eb..0405eac6c31 100644 --- a/rustls/src/msgs/macros.rs +++ b/rustls/src/msgs/macros.rs @@ -2,84 +2,367 @@ macro_rules! enum_builder { ( $(#[doc = $comment:literal])* - #[repr($uint:ty)] - $enum_vis:vis enum $enum_name:ident - { - $( $enum_var:ident => $enum_val:literal),* $(,)? - $( !Debug: - $( $enum_var_nd:ident => $enum_val_nd:literal),* $(,)? - )? + $struct_vis:vis struct $struct_name:ident($inner_vis:vis $uint:ty); + + $enum_vis:vis enum $enum_name:ident { + $( + $(#[$enum_metas:meta])* + $enum_var:ident => $enum_val:literal + ),* + $(,)? } ) => { $(#[doc = $comment])* - #[non_exhaustive] - #[derive(PartialEq, Eq, Clone, Copy)] - $enum_vis enum $enum_name { - $( $enum_var),* - $(, $($enum_var_nd),* )? - ,Unknown($uint) - } + /// + /// Protocol enumerations in rustls are represented with a struct, which contains + /// the numeric value used on the wire + #[doc = concat!("(in this case, ", stringify!($uint), ").")] + /// Each known value has a named const item on this type -- this can be used in a + /// match arm or to access the numeric value. + /// + /// If a known value does not exist for a value you need, you can simply create it locally: + /// + /// ```no_compile + #[doc = concat!("pub const MyValue: ", stringify!($struct_name), " = ", stringify!($struct_name), "(123);")] + /// ``` + /// + /// The [`Debug`][core::fmt::Debug] impl for this type also looks up and pretty-prints + /// known named items. Unknown values are formatted in hexadecimal. + #[allow(missing_docs, clippy::exhaustive_structs)] + #[derive(PartialEq, Eq, Clone, Copy, Hash)] + $struct_vis struct $struct_name($inner_vis $uint); - impl $enum_name { + #[allow(missing_docs, non_upper_case_globals, clippy::upper_case_acronyms)] + impl $struct_name { + /// Encode the value as a big-endian byte array. // NOTE(allow) generated irrespective if there are callers #[allow(dead_code)] - $enum_vis fn to_array(self) -> [u8; core::mem::size_of::<$uint>()] { - <$uint>::from(self).to_be_bytes() + $struct_vis fn to_array(self) -> [u8; core::mem::size_of::<$uint>()] { + self.0.to_be_bytes() } - // NOTE(allow) generated irrespective if there are callers - #[allow(dead_code)] - $enum_vis fn as_str(&self) -> Option<&'static str> { - match self { - $( $enum_name::$enum_var => Some(stringify!($enum_var))),* - $(, $( $enum_name::$enum_var_nd => Some(stringify!($enum_var_nd))),* )? - ,$enum_name::Unknown(_) => None, - } - } + $( + $(#[$enum_metas])* + $struct_vis const $enum_var: Self = Self($enum_val); + )* } - impl Codec<'_> for $enum_name { - // NOTE(allow) fully qualified Vec is only needed in no-std mode - #[allow(unused_qualifications)] + impl crate::msgs::Codec<'_> for $struct_name { fn encode(&self, bytes: &mut alloc::vec::Vec) { - <$uint>::from(*self).encode(bytes); + self.0.encode(bytes); } - fn read(r: &mut Reader<'_>) -> Result { + fn read(r: &mut crate::msgs::Reader<'_>) -> Result { match <$uint>::read(r) { - Ok(x) => Ok($enum_name::from(x)), - Err(_) => Err(crate::error::InvalidMessage::MissingData(stringify!($enum_name))), + Ok(x) => Ok(Self(x)), + Err(_) => Err(crate::error::InvalidMessage::MissingData(stringify!($struct_name))), + } + } + } + + impl core::fmt::Debug for $struct_name { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match $enum_name::try_from(*self) { + Ok(known) => known.fmt(f), + Err(unknown) => write!(f, "0x{:x?}", unknown.0), } } } - impl From<$uint> for $enum_name { + impl From<$uint> for $struct_name { fn from(x: $uint) -> Self { - match x { - $($enum_val => $enum_name::$enum_var),* - $(, $($enum_val_nd => $enum_name::$enum_var_nd),* )? - , x => $enum_name::Unknown(x), + Self(x) + } + } + + impl From<$struct_name> for $uint { + fn from(x: $struct_name) -> Self { + x.0 + } + } + + $(#[doc = $comment])* + #[allow(missing_docs, non_camel_case_types, clippy::upper_case_acronyms)] + #[non_exhaustive] + #[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] + $enum_vis enum $enum_name { + $( + $(#[$enum_metas])* + $enum_var = $enum_val + ),* + } + + impl TryFrom<$struct_name> for $enum_name { + type Error = $struct_name; + fn try_from(value: $struct_name) -> Result { + match value.0 { + $($enum_val => Ok(Self::$enum_var), )* + _ => Err(value), } } } + }; +} - impl From<$enum_name> for $uint { - fn from(value: $enum_name) -> Self { - match value { - $( $enum_name::$enum_var => $enum_val),* - $(, $( $enum_name::$enum_var_nd => $enum_val_nd),* )? - ,$enum_name::Unknown(x) => x +/// A macro which defines a structure containing TLS extensions +/// +/// The contents are defined by two blocks, which are merged to +/// give the struct's items. The second block is optional. +/// +/// The first block defines the items read-into by decoding, +/// and used for encoding. +/// +/// The type of each item in the first block _must_ be an `Option`. +/// This records the presence of that extension. +/// +/// Each item in the first block is prefixed with a match arm, +/// which must match an `ExtensionType` variant. This maps +/// the item to its extension type. +/// +/// Items in the second block are not encoded or decoded-to. +/// They therefore must have a reasonable `Default` value. +/// +/// All items must have a `Default`, `Debug` and `Clone`. +macro_rules! extension_struct { + ( + $(#[doc = $comment:literal])* + $struct_vis:vis struct $struct_name:ident$(<$struct_lt:lifetime>)* + { + $( + $(#[$item_attr:meta])* + $item_id:path => $item_vis:vis $item_slot:ident : Option<$item_ty:ty>, + )+ + } $( + { + $( + $(#[$meta_attr:meta])* + $meta_vis:vis $meta_slot:ident : $meta_ty:ty, + )+ + })* + ) => { + $(#[doc = $comment])* + #[non_exhaustive] + #[derive(Clone, Default)] + $struct_vis struct $struct_name$(<$struct_lt>)* { + $( + $(#[$item_attr])* + $item_vis $item_slot: Option<$item_ty>, + )+ + $($( + $(#[$meta_attr])* + $meta_vis $meta_slot: $meta_ty, + )+)* + } + + impl<'a> $struct_name$(<$struct_lt>)* { + /// Reads one extension typ, length and body from `r`. + /// + /// Unhandled extensions (according to `read_extension_body()` are inserted into `unknown_extensions`) + fn read_one( + &mut self, + r: &mut Reader<'a>, + mut unknown: impl FnMut(ExtensionType) -> Result<(), InvalidMessage>, + ) -> Result { + let typ = ExtensionType::read(r)?; + let len = usize::from(u16::read(r)?); + let mut ext_body = r.sub(len)?; + match self.read_extension_body(typ, &mut ext_body)? { + true => ext_body.expect_empty(stringify!($struct_name))?, + false => unknown(typ)?, + + }; + Ok(typ) + } + + /// Reads one extension body for an extension named by `typ`. + /// + /// Returns `true` if handled, `false` otherwise. + /// + /// `r` is fully consumed if `typ` is unhandled. + fn read_extension_body( + &mut self, + typ: ExtensionType, + r: &mut Reader<'a>, + ) -> Result { + match typ { + $( + $item_id => Self::read_once(r, $item_id, &mut self.$item_slot)?, + )* + + // read and ignore unhandled extensions + _ => { + r.rest(); + return Ok(false); + } } + + Ok(true) } + + /// Decode `r` as `T` into `out`, only if `out` is `None`. + fn read_once(r: &mut Reader<'a>, id: ExtensionType, out: &mut Option) -> Result<(), InvalidMessage> + where T: Codec<'a>, + { + if let Some(_) = out { + return Err(InvalidMessage::DuplicateExtension(u16::from(id))); + } + + *out = Some(T::read(r)?); + Ok(()) + } + + /// Encode one extension body for `typ` into `output`. + /// + /// Adds nothing to `output` if `typ` is absent from this + /// struct, either because it is `None` or unhandled by + /// this struct. + fn encode_one( + &self, + typ: ExtensionType, + output: &mut Vec, + ) { + match typ { + $( + $item_id => if let Some(item) = &self.$item_slot { + typ.encode(output); + item.encode(LengthPrefixedBuffer::new(ListLength::U16, output).buf); + }, + + )* + _ => {}, + } + } + + /// Return a list of extensions whose items are `Some` + #[allow(dead_code)] + pub(crate) fn collect_used(&self) -> Vec { + let mut r = Vec::with_capacity(Self::ALL_EXTENSIONS.len()); + + $( + if let Some(_) = &self.$item_slot { + r.push($item_id); + } + )* + + r + } + + /// Clone the value of the extension identified by `typ` from `source` to `self`. + /// + /// Does nothing if `typ` is not an extension handled by this object. + #[allow(dead_code)] + pub(crate) fn clone_one( + &mut self, + source: &Self, + typ: ExtensionType, + ) { + match typ { + $( + $item_id => self.$item_slot = source.$item_slot.clone(), + )* + _ => {}, + } + } + + /// Remove the extension identified by `typ` from `self`. + #[allow(dead_code)] + pub(crate) fn clear(&mut self, typ: ExtensionType) { + match typ { + $( + $item_id => self.$item_slot = None, + )* + _ => {}, + } + } + + /// Return true if all present extensions are named in `allowed` + #[allow(dead_code)] + pub(crate) fn only_contains(&self, allowed: &[ExtensionType]) -> bool { + $( + if let Some(_) = &self.$item_slot { + if !allowed.contains(&$item_id) { + return false; + } + } + )* + + true + } + + /// Return true if any extension named in `exts` is present. + #[allow(dead_code)] + pub(crate) fn contains_any(&self, exts: &[ExtensionType]) -> bool { + for e in exts { + if self.contains(*e) { + return true; + } + } + false + } + + fn contains(&self, e: ExtensionType) -> bool { + match e { + $( + + $item_id => self.$item_slot.is_some(), + )* + _ => false, + } + } + + /// Every `ExtensionType` this structure may encode/decode. + const ALL_EXTENSIONS: &'static [ExtensionType] = &[ + $($item_id,)* + ]; } - impl core::fmt::Debug for $enum_name { + impl<'a> core::fmt::Debug for $struct_name$(<$struct_lt>)* { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - $( $enum_name::$enum_var => f.write_str(stringify!($enum_var)), )* - _ => write!(f, "{}(0x{:x?})", stringify!($enum_name), <$uint>::from(*self)), - } + let mut ds = f.debug_struct(stringify!($struct_name)); + $( + if let Some(ext) = &self.$item_slot { + ds.field(stringify!($item_slot), ext); + } + )* + $($( + ds.field(stringify!($meta_slot), &self.$meta_slot); + )+)* + ds.finish_non_exhaustive() } } - }; + } } + +/// Create a newtype wrapper around a given type. +/// +/// This is used to create newtypes for the various TLS message types which is used to wrap +/// the `PayloadU8` or `SizedPayload` types. This is typically used for types where we don't need +/// anything other than access to the underlying bytes. +macro_rules! wrapped_payload( + ($(#[$comment:meta])* $vis:vis struct $name:ident, $inner:ident$(<$len:ty, $cardinality:ty>)?,) => { + $(#[$comment])* + #[derive(Clone, Debug)] + $vis struct $name($inner$(<'static, $len, $cardinality>)?); + + impl From> for $name { + fn from(v: Vec) -> Self { + Self($inner::from(v)) + } + } + + impl AsRef<[u8]> for $name { + fn as_ref(&self) -> &[u8] { + self.0.bytes() + } + } + + impl Codec<'_> for $name { + fn encode(&self, bytes: &mut Vec) { + self.0.encode(bytes); + } + + fn read(r: &mut Reader<'_>) -> Result { + Ok(Self($inner::read(r)?.into_owned())) + } + } + } +); diff --git a/rustls/src/msgs/message/inbound.rs b/rustls/src/msgs/message/inbound.rs deleted file mode 100644 index f8b2181c22d..00000000000 --- a/rustls/src/msgs/message/inbound.rs +++ /dev/null @@ -1,161 +0,0 @@ -use core::ops::{Deref, DerefMut, Range}; - -use crate::enums::{ContentType, ProtocolVersion}; -use crate::error::{Error, PeerMisbehaved}; -use crate::msgs::fragmenter::MAX_FRAGMENT_LEN; - -/// A TLS frame, named TLSPlaintext in the standard. -/// -/// This inbound type borrows its encrypted payload from a buffer elsewhere. -/// It is used for joining and is consumed by decryption. -pub struct InboundOpaqueMessage<'a> { - pub typ: ContentType, - pub version: ProtocolVersion, - pub payload: BorrowedPayload<'a>, -} - -impl<'a> InboundOpaqueMessage<'a> { - /// Construct a new `InboundOpaqueMessage` from constituent fields. - /// - /// `payload` is borrowed. - pub fn new(typ: ContentType, version: ProtocolVersion, payload: &'a mut [u8]) -> Self { - Self { - typ, - version, - payload: BorrowedPayload(payload), - } - } - - /// Force conversion into a plaintext message. - /// - /// This should only be used for messages that are known to be in plaintext. Otherwise, the - /// `InboundOpaqueMessage` should be decrypted into a `PlainMessage` using a `MessageDecrypter`. - pub fn into_plain_message(self) -> InboundPlainMessage<'a> { - InboundPlainMessage { - typ: self.typ, - version: self.version, - payload: self.payload.into_inner(), - } - } - - /// Force conversion into a plaintext message. - /// - /// `range` restricts the resulting message: this function panics if it is out of range for - /// the underlying message payload. - /// - /// This should only be used for messages that are known to be in plaintext. Otherwise, the - /// `InboundOpaqueMessage` should be decrypted into a `PlainMessage` using a `MessageDecrypter`. - pub fn into_plain_message_range(self, range: Range) -> InboundPlainMessage<'a> { - InboundPlainMessage { - typ: self.typ, - version: self.version, - payload: &self.payload.into_inner()[range], - } - } - - /// For TLS1.3 (only), checks the length msg.payload is valid and removes the padding. - /// - /// Returns an error if the message (pre-unpadding) is too long, or the padding is invalid, - /// or the message (post-unpadding) is too long. - pub fn into_tls13_unpadded_message(mut self) -> Result, Error> { - let payload = &mut self.payload; - - if payload.len() > MAX_FRAGMENT_LEN + 1 { - return Err(Error::PeerSentOversizedRecord); - } - - self.typ = unpad_tls13_payload(payload); - if self.typ == ContentType::Unknown(0) { - return Err(PeerMisbehaved::IllegalTlsInnerPlaintext.into()); - } - - if payload.len() > MAX_FRAGMENT_LEN { - return Err(Error::PeerSentOversizedRecord); - } - - self.version = ProtocolVersion::TLSv1_3; - Ok(self.into_plain_message()) - } -} - -pub struct BorrowedPayload<'a>(&'a mut [u8]); - -impl Deref for BorrowedPayload<'_> { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - self.0 - } -} - -impl DerefMut for BorrowedPayload<'_> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.0 - } -} - -impl<'a> BorrowedPayload<'a> { - pub fn truncate(&mut self, len: usize) { - if len >= self.len() { - return; - } - - self.0 = core::mem::take(&mut self.0) - .split_at_mut(len) - .0; - } - - pub(crate) fn into_inner(self) -> &'a mut [u8] { - self.0 - } - - pub(crate) fn pop(&mut self) -> Option { - if self.is_empty() { - return None; - } - - let len = self.len(); - let last = self[len - 1]; - self.truncate(len - 1); - Some(last) - } -} - -/// A TLS frame, named `TLSPlaintext` in the standard. -/// -/// This inbound type borrows its decrypted payload from the original buffer. -/// It results from decryption. -#[derive(Debug)] -pub struct InboundPlainMessage<'a> { - pub typ: ContentType, - pub version: ProtocolVersion, - pub payload: &'a [u8], -} - -impl InboundPlainMessage<'_> { - /// Returns true if the payload is a CCS message. - /// - /// We passthrough ChangeCipherSpec messages in the deframer without decrypting them. - /// Note: this is prior to the record layer, so is unencrypted. See - /// third paragraph of section 5 in RFC8446. - pub(crate) fn is_valid_ccs(&self) -> bool { - self.typ == ContentType::ChangeCipherSpec && self.payload == [0x01] - } -} - -/// Decode a TLS1.3 `TLSInnerPlaintext` encoding. -/// -/// `p` is a message payload, immediately post-decryption. This function -/// removes zero padding bytes, until a non-zero byte is encountered which is -/// the content type, which is returned. See RFC8446 s5.2. -/// -/// ContentType(0) is returned if the message payload is empty or all zeroes. -fn unpad_tls13_payload(p: &mut BorrowedPayload<'_>) -> ContentType { - loop { - match p.pop() { - Some(0) => {} - Some(content_type) => return ContentType::from(content_type), - None => return ContentType::Unknown(0), - } - } -} diff --git a/rustls/src/msgs/message/mod.rs b/rustls/src/msgs/message/mod.rs deleted file mode 100644 index aeb35c850c1..00000000000 --- a/rustls/src/msgs/message/mod.rs +++ /dev/null @@ -1,257 +0,0 @@ -use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion}; -use crate::error::{Error, InvalidMessage}; -use crate::msgs::alert::AlertMessagePayload; -use crate::msgs::base::Payload; -use crate::msgs::ccs::ChangeCipherSpecPayload; -use crate::msgs::codec::{Codec, Reader}; -use crate::msgs::enums::{AlertLevel, KeyUpdateRequest}; -use crate::msgs::handshake::{HandshakeMessagePayload, HandshakePayload}; - -mod inbound; -pub use inbound::{BorrowedPayload, InboundOpaqueMessage, InboundPlainMessage}; - -mod outbound; -use alloc::vec::Vec; - -pub(crate) use outbound::read_opaque_message_header; -pub use outbound::{OutboundChunks, OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload}; - -#[derive(Debug)] -pub enum MessagePayload<'a> { - Alert(AlertMessagePayload), - // one handshake message, parsed - Handshake { - parsed: HandshakeMessagePayload<'a>, - encoded: Payload<'a>, - }, - // (potentially) multiple handshake messages, unparsed - HandshakeFlight(Payload<'a>), - ChangeCipherSpec(ChangeCipherSpecPayload), - ApplicationData(Payload<'a>), -} - -impl<'a> MessagePayload<'a> { - pub fn encode(&self, bytes: &mut Vec) { - match self { - Self::Alert(x) => x.encode(bytes), - Self::Handshake { encoded, .. } => bytes.extend(encoded.bytes()), - Self::HandshakeFlight(x) => bytes.extend(x.bytes()), - Self::ChangeCipherSpec(x) => x.encode(bytes), - Self::ApplicationData(x) => x.encode(bytes), - } - } - - pub fn handshake(parsed: HandshakeMessagePayload<'a>) -> Self { - Self::Handshake { - encoded: Payload::new(parsed.get_encoding()), - parsed, - } - } - - pub fn new( - typ: ContentType, - vers: ProtocolVersion, - payload: &'a [u8], - ) -> Result { - let mut r = Reader::init(payload); - match typ { - ContentType::ApplicationData => Ok(Self::ApplicationData(Payload::Borrowed(payload))), - ContentType::Alert => AlertMessagePayload::read(&mut r).map(MessagePayload::Alert), - ContentType::Handshake => { - HandshakeMessagePayload::read_version(&mut r, vers).map(|parsed| Self::Handshake { - parsed, - encoded: Payload::Borrowed(payload), - }) - } - ContentType::ChangeCipherSpec => { - ChangeCipherSpecPayload::read(&mut r).map(MessagePayload::ChangeCipherSpec) - } - _ => Err(InvalidMessage::InvalidContentType), - } - } - - pub fn content_type(&self) -> ContentType { - match self { - Self::Alert(_) => ContentType::Alert, - Self::Handshake { .. } | Self::HandshakeFlight(_) => ContentType::Handshake, - Self::ChangeCipherSpec(_) => ContentType::ChangeCipherSpec, - Self::ApplicationData(_) => ContentType::ApplicationData, - } - } - - pub(crate) fn into_owned(self) -> MessagePayload<'static> { - use MessagePayload::*; - match self { - Alert(x) => Alert(x), - Handshake { parsed, encoded } => Handshake { - parsed: parsed.into_owned(), - encoded: encoded.into_owned(), - }, - HandshakeFlight(x) => HandshakeFlight(x.into_owned()), - ChangeCipherSpec(x) => ChangeCipherSpec(x), - ApplicationData(x) => ApplicationData(x.into_owned()), - } - } -} - -impl From> for PlainMessage { - fn from(msg: Message<'_>) -> Self { - let typ = msg.payload.content_type(); - let payload = match msg.payload { - MessagePayload::ApplicationData(payload) => payload.into_owned(), - _ => { - let mut buf = Vec::new(); - msg.payload.encode(&mut buf); - Payload::Owned(buf) - } - }; - - Self { - typ, - version: msg.version, - payload, - } - } -} - -/// A decrypted TLS frame -/// -/// This type owns all memory for its interior parts. It can be decrypted from an OpaqueMessage -/// or encrypted into an OpaqueMessage, and it is also used for joining and fragmenting. -#[derive(Clone, Debug)] -pub struct PlainMessage { - pub typ: ContentType, - pub version: ProtocolVersion, - pub payload: Payload<'static>, -} - -impl PlainMessage { - pub fn into_unencrypted_opaque(self) -> OutboundOpaqueMessage { - OutboundOpaqueMessage { - version: self.version, - typ: self.typ, - payload: PrefixedPayload::from(self.payload.bytes()), - } - } - - pub fn borrow_inbound(&self) -> InboundPlainMessage<'_> { - InboundPlainMessage { - version: self.version, - typ: self.typ, - payload: self.payload.bytes(), - } - } - - pub fn borrow_outbound(&self) -> OutboundPlainMessage<'_> { - OutboundPlainMessage { - version: self.version, - typ: self.typ, - payload: self.payload.bytes().into(), - } - } -} - -/// A message with decoded payload -#[derive(Debug)] -pub struct Message<'a> { - pub version: ProtocolVersion, - pub payload: MessagePayload<'a>, -} - -impl Message<'_> { - pub fn is_handshake_type(&self, hstyp: HandshakeType) -> bool { - // Bit of a layering violation, but OK. - if let MessagePayload::Handshake { parsed, .. } = &self.payload { - parsed.typ == hstyp - } else { - false - } - } - - pub fn build_alert(level: AlertLevel, desc: AlertDescription) -> Self { - Self { - version: ProtocolVersion::TLSv1_2, - payload: MessagePayload::Alert(AlertMessagePayload { - level, - description: desc, - }), - } - } - - pub fn build_key_update_notify() -> Self { - Self { - version: ProtocolVersion::TLSv1_3, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::KeyUpdate, - payload: HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateNotRequested), - }), - } - } - - pub fn build_key_update_request() -> Self { - Self { - version: ProtocolVersion::TLSv1_3, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::KeyUpdate, - payload: HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateRequested), - }), - } - } - - #[cfg(feature = "std")] - pub(crate) fn into_owned(self) -> Message<'static> { - let Self { version, payload } = self; - Message { - version, - payload: payload.into_owned(), - } - } -} - -impl TryFrom for Message<'static> { - type Error = Error; - - fn try_from(plain: PlainMessage) -> Result { - Ok(Self { - version: plain.version, - payload: MessagePayload::new(plain.typ, plain.version, plain.payload.bytes())? - .into_owned(), - }) - } -} - -/// Parses a plaintext message into a well-typed [`Message`]. -/// -/// A [`PlainMessage`] must contain plaintext content. Encrypted content should be stored in an -/// [`InboundOpaqueMessage`] and decrypted before being stored into a [`PlainMessage`]. -impl<'a> TryFrom> for Message<'a> { - type Error = Error; - - fn try_from(plain: InboundPlainMessage<'a>) -> Result { - Ok(Self { - version: plain.version, - payload: MessagePayload::new(plain.typ, plain.version, plain.payload)?, - }) - } -} - -#[derive(Debug)] -pub enum MessageError { - TooShortForHeader, - TooShortForLength, - InvalidEmptyPayload, - MessageTooLarge, - InvalidContentType, - UnknownProtocolVersion, -} - -/// Content type, version and size. -pub(crate) const HEADER_SIZE: usize = 1 + 2 + 2; - -/// Maximum message payload size. -/// That's 2^14 payload bytes and a 2KB allowance for ciphertext overheads. -const MAX_PAYLOAD: u16 = 16_384 + 2048; - -/// Maximum on-the-wire message size. -#[cfg(feature = "std")] -pub(crate) const MAX_WIRE_SIZE: usize = MAX_PAYLOAD as usize + HEADER_SIZE; diff --git a/rustls/src/msgs/message/outbound.rs b/rustls/src/msgs/message/outbound.rs deleted file mode 100644 index f6fdeb86235..00000000000 --- a/rustls/src/msgs/message/outbound.rs +++ /dev/null @@ -1,422 +0,0 @@ -use alloc::vec::Vec; - -use super::{MessageError, PlainMessage, HEADER_SIZE, MAX_PAYLOAD}; -use crate::enums::{ContentType, ProtocolVersion}; -use crate::msgs::base::Payload; -use crate::msgs::codec::{Codec, Reader}; -use crate::record_layer::RecordLayer; - -/// A TLS frame, named `TLSPlaintext` in the standard. -/// -/// This outbound type borrows its "to be encrypted" payload from the "user". -/// It is used for fragmenting and is consumed by encryption. -#[derive(Debug)] -pub struct OutboundPlainMessage<'a> { - pub typ: ContentType, - pub version: ProtocolVersion, - pub payload: OutboundChunks<'a>, -} - -impl OutboundPlainMessage<'_> { - pub(crate) fn encoded_len(&self, record_layer: &RecordLayer) -> usize { - HEADER_SIZE + record_layer.encrypted_len(self.payload.len()) - } - - pub(crate) fn to_unencrypted_opaque(&self) -> OutboundOpaqueMessage { - let mut payload = PrefixedPayload::with_capacity(self.payload.len()); - payload.extend_from_chunks(&self.payload); - OutboundOpaqueMessage { - version: self.version, - typ: self.typ, - payload, - } - } -} - -/// A collection of borrowed plaintext slices. -/// -/// Warning: OutboundChunks does not guarantee that the simplest variant is used. -/// Multiple can hold non fragmented or empty payloads. -#[derive(Debug, Clone)] -pub enum OutboundChunks<'a> { - /// A single byte slice. Contrary to `Multiple`, this uses a single pointer indirection - Single(&'a [u8]), - /// A collection of chunks (byte slices) - /// and cursors to single out a fragmented range of bytes. - /// OutboundChunks assumes that start <= end - Multiple { - chunks: &'a [&'a [u8]], - start: usize, - end: usize, - }, -} - -impl<'a> OutboundChunks<'a> { - /// Create a payload from a slice of byte slices. - /// If fragmented the cursors are added by default: start = 0, end = length - pub fn new(chunks: &'a [&'a [u8]]) -> Self { - if chunks.len() == 1 { - Self::Single(chunks[0]) - } else { - Self::Multiple { - chunks, - start: 0, - end: chunks - .iter() - .map(|chunk| chunk.len()) - .sum(), - } - } - } - - /// Create a payload with a single empty slice - pub fn new_empty() -> Self { - Self::Single(&[]) - } - - /// Flatten the slice of byte slices to an owned vector of bytes - pub fn to_vec(&self) -> Vec { - let mut vec = Vec::with_capacity(self.len()); - self.copy_to_vec(&mut vec); - vec - } - - /// Append all bytes to a vector - pub fn copy_to_vec(&self, vec: &mut Vec) { - match *self { - Self::Single(chunk) => vec.extend_from_slice(chunk), - Self::Multiple { chunks, start, end } => { - let mut size = 0; - for chunk in chunks.iter() { - let psize = size; - let len = chunk.len(); - size += len; - if size <= start || psize >= end { - continue; - } - let start = start.saturating_sub(psize); - let end = if end - psize < len { end - psize } else { len }; - vec.extend_from_slice(&chunk[start..end]); - } - } - } - } - - /// Split self in two, around an index - /// Works similarly to `split_at` in the core library, except it doesn't panic if out of bound - pub fn split_at(&self, mid: usize) -> (Self, Self) { - match *self { - Self::Single(chunk) => { - let mid = Ord::min(mid, chunk.len()); - (Self::Single(&chunk[..mid]), Self::Single(&chunk[mid..])) - } - Self::Multiple { chunks, start, end } => { - let mid = Ord::min(start + mid, end); - ( - Self::Multiple { - chunks, - start, - end: mid, - }, - Self::Multiple { - chunks, - start: mid, - end, - }, - ) - } - } - } - - /// Returns true if the payload is empty - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Returns the cumulative length of all chunks - pub fn len(&self) -> usize { - match self { - Self::Single(chunk) => chunk.len(), - Self::Multiple { start, end, .. } => end - start, - } - } -} - -impl<'a> From<&'a [u8]> for OutboundChunks<'a> { - fn from(payload: &'a [u8]) -> Self { - Self::Single(payload) - } -} - -/// A TLS frame, named `TLSPlaintext` in the standard. -/// -/// This outbound type owns all memory for its interior parts. -/// It results from encryption and is used for io write. -#[derive(Clone, Debug)] -pub struct OutboundOpaqueMessage { - pub typ: ContentType, - pub version: ProtocolVersion, - pub payload: PrefixedPayload, -} - -impl OutboundOpaqueMessage { - /// Construct a new `OpaqueMessage` from constituent fields. - /// - /// `body` is moved into the `payload` field. - pub fn new(typ: ContentType, version: ProtocolVersion, payload: PrefixedPayload) -> Self { - Self { - typ, - version, - payload, - } - } - - /// Construct by decoding from a [`Reader`]. - /// - /// `MessageError` allows callers to distinguish between valid prefixes (might - /// become valid if we read more data) and invalid data. - pub fn read(r: &mut Reader<'_>) -> Result { - let (typ, version, len) = read_opaque_message_header(r)?; - - let content = r - .take(len as usize) - .ok_or(MessageError::TooShortForLength)?; - - Ok(Self { - typ, - version, - payload: PrefixedPayload::from(content), - }) - } - - pub fn encode(self) -> Vec { - let length = self.payload.len() as u16; - let mut encoded_payload = self.payload.0; - encoded_payload[0] = self.typ.into(); - encoded_payload[1..3].copy_from_slice(&self.version.to_array()); - encoded_payload[3..5].copy_from_slice(&(length).to_be_bytes()); - encoded_payload - } - - /// Force conversion into a plaintext message. - /// - /// This should only be used for messages that are known to be in plaintext. Otherwise, the - /// `OutboundOpaqueMessage` should be decrypted into a `PlainMessage` using a `MessageDecrypter`. - pub fn into_plain_message(self) -> PlainMessage { - PlainMessage { - version: self.version, - typ: self.typ, - payload: Payload::Owned(self.payload.as_ref().to_vec()), - } - } -} - -#[derive(Clone, Debug)] -pub struct PrefixedPayload(Vec); - -impl PrefixedPayload { - pub fn with_capacity(capacity: usize) -> Self { - let mut prefixed_payload = Vec::with_capacity(HEADER_SIZE + capacity); - prefixed_payload.resize(HEADER_SIZE, 0); - Self(prefixed_payload) - } - - pub fn extend_from_slice(&mut self, slice: &[u8]) { - self.0.extend_from_slice(slice) - } - - pub fn extend_from_chunks(&mut self, chunks: &OutboundChunks<'_>) { - chunks.copy_to_vec(&mut self.0) - } - - pub fn truncate(&mut self, len: usize) { - self.0.truncate(len + HEADER_SIZE) - } - - fn len(&self) -> usize { - self.0.len() - HEADER_SIZE - } -} - -impl AsRef<[u8]> for PrefixedPayload { - fn as_ref(&self) -> &[u8] { - &self.0[HEADER_SIZE..] - } -} - -impl AsMut<[u8]> for PrefixedPayload { - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0[HEADER_SIZE..] - } -} - -impl<'a> Extend<&'a u8> for PrefixedPayload { - fn extend>(&mut self, iter: T) { - self.0.extend(iter) - } -} - -impl From<&[u8]> for PrefixedPayload { - fn from(content: &[u8]) -> Self { - let mut payload = Vec::with_capacity(HEADER_SIZE + content.len()); - payload.extend(&[0u8; HEADER_SIZE]); - payload.extend(content); - Self(payload) - } -} - -impl From<&[u8; N]> for PrefixedPayload { - fn from(content: &[u8; N]) -> Self { - Self::from(&content[..]) - } -} - -pub(crate) fn read_opaque_message_header( - r: &mut Reader<'_>, -) -> Result<(ContentType, ProtocolVersion, u16), MessageError> { - let typ = ContentType::read(r).map_err(|_| MessageError::TooShortForHeader)?; - // Don't accept any new content-types. - if let ContentType::Unknown(_) = typ { - return Err(MessageError::InvalidContentType); - } - - let version = ProtocolVersion::read(r).map_err(|_| MessageError::TooShortForHeader)?; - // Accept only versions 0x03XX for any XX. - match version { - ProtocolVersion::Unknown(ref v) if (v & 0xff00) != 0x0300 => { - return Err(MessageError::UnknownProtocolVersion); - } - _ => {} - }; - - let len = u16::read(r).map_err(|_| MessageError::TooShortForHeader)?; - - // Reject undersize messages - // implemented per section 5.1 of RFC8446 (TLSv1.3) - // per section 6.2.1 of RFC5246 (TLSv1.2) - if typ != ContentType::ApplicationData && len == 0 { - return Err(MessageError::InvalidEmptyPayload); - } - - // Reject oversize messages - if len >= MAX_PAYLOAD { - return Err(MessageError::MessageTooLarge); - } - - Ok((typ, version, len)) -} - -#[cfg(test)] -mod tests { - use std::{println, vec}; - - use super::*; - - #[test] - fn split_at_with_single_slice() { - let owner: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7]; - let borrowed_payload = OutboundChunks::Single(owner); - - let (before, after) = borrowed_payload.split_at(6); - println!("before:{:?}\nafter:{:?}", before, after); - assert_eq!(before.to_vec(), &[0, 1, 2, 3, 4, 5]); - assert_eq!(after.to_vec(), &[6, 7]); - } - - #[test] - fn split_at_with_multiple_slices() { - let owner: Vec<&[u8]> = vec![&[0, 1, 2, 3], &[4, 5], &[6, 7, 8], &[9, 10, 11, 12]]; - let borrowed_payload = OutboundChunks::new(&owner); - - let (before, after) = borrowed_payload.split_at(3); - println!("before:{:?}\nafter:{:?}", before, after); - assert_eq!(before.to_vec(), &[0, 1, 2]); - assert_eq!(after.to_vec(), &[3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); - - let (before, after) = borrowed_payload.split_at(8); - println!("before:{:?}\nafter:{:?}", before, after); - assert_eq!(before.to_vec(), &[0, 1, 2, 3, 4, 5, 6, 7]); - assert_eq!(after.to_vec(), &[8, 9, 10, 11, 12]); - - let (before, after) = borrowed_payload.split_at(11); - println!("before:{:?}\nafter:{:?}", before, after); - assert_eq!(before.to_vec(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - assert_eq!(after.to_vec(), &[11, 12]); - } - - #[test] - fn split_out_of_bounds() { - let owner: Vec<&[u8]> = vec![&[0, 1, 2, 3], &[4, 5], &[6, 7, 8], &[9, 10, 11, 12]]; - - let single_payload = OutboundChunks::Single(owner[0]); - let (before, after) = single_payload.split_at(17); - println!("before:{:?}\nafter:{:?}", before, after); - assert_eq!(before.to_vec(), &[0, 1, 2, 3]); - assert!(after.is_empty()); - - let multiple_payload = OutboundChunks::new(&owner); - let (before, after) = multiple_payload.split_at(17); - println!("before:{:?}\nafter:{:?}", before, after); - assert_eq!(before.to_vec(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); - assert!(after.is_empty()); - - let empty_payload = OutboundChunks::new_empty(); - let (before, after) = empty_payload.split_at(17); - println!("before:{:?}\nafter:{:?}", before, after); - assert!(before.is_empty()); - assert!(after.is_empty()); - } - - #[test] - fn empty_slices_mixed() { - let owner: Vec<&[u8]> = vec![&[], &[], &[0], &[], &[1, 2], &[], &[3], &[4], &[], &[]]; - let mut borrowed_payload = OutboundChunks::new(&owner); - let mut fragment_count = 0; - let mut fragment; - let expected_fragments: &[&[u8]] = &[&[0, 1], &[2, 3], &[4]]; - - while !borrowed_payload.is_empty() { - (fragment, borrowed_payload) = borrowed_payload.split_at(2); - println!("{fragment:?}"); - assert_eq!(&expected_fragments[fragment_count], &fragment.to_vec()); - fragment_count += 1; - } - assert_eq!(fragment_count, expected_fragments.len()); - } - - #[test] - fn exhaustive_splitting() { - let owner: Vec = (0..127).collect(); - let slices = (0..7) - .map(|i| &owner[((1 << i) - 1)..((1 << (i + 1)) - 1)]) - .collect::>(); - let payload = OutboundChunks::new(&slices); - - assert_eq!(payload.to_vec(), owner); - println!("{:#?}", payload); - - for start in 0..128 { - for end in start..128 { - for mid in 0..(end - start) { - let witness = owner[start..end].split_at(mid); - let split_payload = payload - .split_at(end) - .0 - .split_at(start) - .1 - .split_at(mid); - assert_eq!( - witness.0, - split_payload.0.to_vec(), - "start: {start}, mid:{mid}, end:{end}" - ); - assert_eq!( - witness.1, - split_payload.1.to_vec(), - "start: {start}, mid:{mid}, end:{end}" - ); - } - } - } - } -} diff --git a/rustls/src/msgs/message_test.rs b/rustls/src/msgs/message_test.rs deleted file mode 100644 index 53fca53e190..00000000000 --- a/rustls/src/msgs/message_test.rs +++ /dev/null @@ -1,109 +0,0 @@ -use std::io::Read; -use std::path::{Path, PathBuf}; -use std::prelude::v1::*; -use std::{format, fs, println, vec}; - -use super::base::Payload; -use super::codec::Reader; -use super::enums::AlertLevel; -use super::message::{Message, OutboundOpaqueMessage, PlainMessage}; -use crate::enums::{AlertDescription, HandshakeType}; -use crate::msgs::base::{PayloadU16, PayloadU24, PayloadU8}; - -#[test] -fn test_read_fuzz_corpus() { - fn corpus_dir() -> PathBuf { - let from_subcrate = Path::new("../fuzz/corpus/message"); - let from_root = Path::new("fuzz/corpus/message"); - - if from_root.is_dir() { - from_root.to_path_buf() - } else { - from_subcrate.to_path_buf() - } - } - - for file in fs::read_dir(corpus_dir()).unwrap() { - let mut f = fs::File::open(file.unwrap().path()).unwrap(); - let mut bytes = Vec::new(); - f.read_to_end(&mut bytes).unwrap(); - - let mut rd = Reader::init(&bytes); - let msg = OutboundOpaqueMessage::read(&mut rd) - .unwrap() - .into_plain_message(); - println!("{:?}", msg); - - let Ok(msg) = Message::try_from(msg) else { - continue; - }; - - let enc = PlainMessage::from(msg) - .into_unencrypted_opaque() - .encode(); - assert_eq!(bytes.to_vec(), enc); - assert_eq!(bytes[..rd.used()].to_vec(), enc); - } -} - -#[test] -fn can_read_safari_client_hello_with_ip_address_in_sni_extension() { - let _ = env_logger::Builder::new() - .filter(None, log::LevelFilter::Trace) - .try_init(); - - let bytes = b"\ - \x16\x03\x01\x00\xeb\x01\x00\x00\xe7\x03\x03\xb6\x1f\xe4\x3a\x55\ - \x90\x3e\xc0\x28\x9c\x12\xe0\x5c\x84\xea\x90\x1b\xfb\x11\xfc\xbd\ - \x25\x55\xda\x9f\x51\x93\x1b\x8d\x92\x66\xfd\x00\x00\x2e\xc0\x2c\ - \xc0\x2b\xc0\x24\xc0\x23\xc0\x0a\xc0\x09\xcc\xa9\xc0\x30\xc0\x2f\ - \xc0\x28\xc0\x27\xc0\x14\xc0\x13\xcc\xa8\x00\x9d\x00\x9c\x00\x3d\ - \x00\x3c\x00\x35\x00\x2f\xc0\x08\xc0\x12\x00\x0a\x01\x00\x00\x90\ - \xff\x01\x00\x01\x00\x00\x00\x00\x0e\x00\x0c\x00\x00\x09\x31\x32\ - \x37\x2e\x30\x2e\x30\x2e\x31\x00\x17\x00\x00\x00\x0d\x00\x18\x00\ - \x16\x04\x03\x08\x04\x04\x01\x05\x03\x02\x03\x08\x05\x08\x05\x05\ - \x01\x08\x06\x06\x01\x02\x01\x00\x05\x00\x05\x01\x00\x00\x00\x00\ - \x33\x74\x00\x00\x00\x12\x00\x00\x00\x10\x00\x30\x00\x2e\x02\x68\ - \x32\x05\x68\x32\x2d\x31\x36\x05\x68\x32\x2d\x31\x35\x05\x68\x32\ - \x2d\x31\x34\x08\x73\x70\x64\x79\x2f\x33\x2e\x31\x06\x73\x70\x64\ - \x79\x2f\x33\x08\x68\x74\x74\x70\x2f\x31\x2e\x31\x00\x0b\x00\x02\ - \x01\x00\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x18\x00\x19"; - let mut rd = Reader::init(bytes); - let m = OutboundOpaqueMessage::read(&mut rd).unwrap(); - println!("m = {:?}", m); - Message::try_from(m.into_plain_message()).unwrap(); -} - -#[test] -fn alert_is_not_handshake() { - let m = Message::build_alert(AlertLevel::Fatal, AlertDescription::DecodeError); - assert!(!m.is_handshake_type(HandshakeType::ClientHello)); -} - -#[test] -fn construct_all_types() { - let samples = [ - &b"\x14\x03\x04\x00\x01\x01"[..], - &b"\x15\x03\x04\x00\x02\x01\x16"[..], - &b"\x16\x03\x04\x00\x05\x18\x00\x00\x01\x00"[..], - &b"\x17\x03\x04\x00\x04\x11\x22\x33\x44"[..], - &b"\x18\x03\x04\x00\x04\x11\x22\x33\x44"[..], - ]; - for &bytes in samples.iter() { - let m = OutboundOpaqueMessage::read(&mut Reader::init(bytes)).unwrap(); - println!("m = {:?}", m); - let m = Message::try_from(m.into_plain_message()); - println!("m' = {:?}", m); - } -} - -#[test] -fn debug_payload() { - assert_eq!("01020304", format!("{:?}", Payload::new(vec![1, 2, 3, 4]))); - assert_eq!("01020304", format!("{:?}", PayloadU8(vec![1, 2, 3, 4]))); - assert_eq!("01020304", format!("{:?}", PayloadU16(vec![1, 2, 3, 4]))); - assert_eq!( - "01020304", - format!("{:?}", PayloadU24(Payload::new(vec![1, 2, 3, 4]))) - ); -} diff --git a/rustls/src/msgs/mod.rs b/rustls/src/msgs/mod.rs index 270c2dbcc72..36a3e0c92f4 100644 --- a/rustls/src/msgs/mod.rs +++ b/rustls/src/msgs/mod.rs @@ -1,4 +1,4 @@ -#![allow(missing_docs)] +#![expect(missing_docs)] //! cat says: //! //! ```text @@ -31,44 +31,813 @@ //! //! +use alloc::boxed::Box; +use alloc::vec::Vec; + +use crate::crypto::cipher::{EncodedMessage, MessageError, Payload}; +use crate::enums::{ContentType, ContentTypeName, HandshakeType, ProtocolVersion}; +use crate::error::{AlertDescription, InvalidMessage}; +use crate::verify::DigitallySignedStruct; + #[macro_use] mod macros; -pub(crate) mod alert; -pub(crate) mod base; -pub(crate) mod ccs; -pub(crate) mod codec; -pub(crate) mod deframer; -pub(crate) mod enums; -pub(crate) mod fragmenter; -pub(crate) mod handshake; -pub(crate) mod message; -pub(crate) mod persist; +mod client_hello; +pub(crate) use client_hello::{ + CertificateStatusRequest, ClientExtensions, ClientHelloPayload, ClientSessionTicket, + EncryptedClientHello, EncryptedClientHelloOuter, PresharedKeyBinder, PresharedKeyIdentity, + PresharedKeyOffer, PskKeyExchangeModes, ServerNamePayload, +}; + +mod codec; +pub(crate) use codec::{ + CERTIFICATE_MAX_SIZE_LIMIT, Codec, ListLength, MaybeEmpty, NonEmpty, Reader, SizedPayload, + TlsListElement, hex, put_u16, put_u64, +}; +use codec::{LengthPrefixedBuffer, U24}; +mod deframer; +pub(crate) use deframer::{ + Deframed, Deframer, Delocator, HandshakeAlignedProof, Locator, TlsInputBuffer, VecInput, +}; + +mod enums; #[cfg(test)] -mod handshake_test; +pub(crate) use enums::ECCurveType; +#[cfg(test)] +pub(crate) use enums::tests::{test_enum8, test_enum8_display, test_enum16}; +pub(crate) use enums::{ + AlertLevel, AlertLevelName, ClientCertificateType, Compression, ExtensionType, KeyUpdateRequest, +}; -pub mod ffdhe_groups; +mod fragmenter; +pub(crate) use fragmenter::{MAX_FRAGMENT_LEN, MessageFragmenter}; + +#[macro_use] +mod handshake; +use handshake::HELLO_RETRY_REQUEST_RANDOM; +pub(crate) use handshake::{ + ALL_KEY_EXCHANGE_ALGORITHMS, CertificateChain, CertificatePayloadTls13, + CertificateRequestExtensions, CertificateRequestPayload, CertificateRequestPayloadTls13, + CertificateStatus, ClientDhParams, ClientEcdhParams, ClientExtensionsInput, + ClientKeyExchangeParams, CompressedCertificatePayload, Encoding, HelloRetryRequest, + HelloRetryRequestExtensions, KeyShareEntry, KxDecode, NewSessionTicketPayload, + NewSessionTicketPayloadTls13, Random, ServerExtensionsInput, ServerKeyExchange, + ServerKeyExchangeParams, ServerKeyExchangePayload, SessionId, SingleProtocolName, + SupportedEcPointFormats, SupportedProtocolVersions, TransportParameters, +}; #[cfg(test)] -mod message_test; +pub(crate) use handshake::{EcParameters, NewSessionTicketExtensions, ServerEcdhParams}; + +mod server_hello; +pub(crate) use server_hello::{ + EchConfigContents, EchConfigPayload, HpkeKeyConfig, ServerExtensions, ServerHelloPayload, +}; + +#[cfg(test)] +mod handshake_test; + +pub mod fuzzing { + pub use super::deframer::fuzz_deframer; + use super::{Codec, EncodedMessage, Message, MessageFragmenter, Payload, Reader}; + use crate::server::ServerSessionValue; + + pub fn fuzz_fragmenter(data: &[u8]) { + let mut rdr = Reader::new(data); + let Ok(msg) = EncodedMessage::>::read(&mut rdr) else { + return; + }; + + let Ok(msg) = Message::try_from(&msg) else { + return; + }; + + let mut frg = MessageFragmenter::default(); + frg.set_max_fragment_size(Some(32)) + .unwrap(); + for msg in frg.fragment_message(&EncodedMessage::>::from(msg)) { + Message::try_from(&EncodedMessage { + typ: msg.typ, + version: msg.version, + payload: Payload::Owned(msg.payload.to_vec()), + }) + .ok(); + } + } + + pub fn fuzz_message(data: &[u8]) { + let mut rdr = Reader::new(data); + let Ok(m) = EncodedMessage::>::read(&mut rdr) else { + return; + }; + + let Ok(msg) = Message::try_from(&m) else { + return; + }; + + //println!("msg = {:#?}", m); + let enc = EncodedMessage::>::from(msg) + .into_unencrypted_opaque() + .encode(); + //println!("data = {:?}", &data[..rdr.used()]); + assert_eq!(enc, data[..data.len() - rdr.left()]); + } + + pub fn fuzz_server_session_value(data: &[u8]) { + let mut rdr = Reader::new(data); + let _ = ServerSessionValue::read(&mut rdr); + } +} + +/// A message with decoded payload +#[derive(Debug)] +pub(crate) struct Message<'a> { + pub version: ProtocolVersion, + pub payload: MessagePayload<'a>, +} + +impl Message<'_> { + pub(crate) fn build_alert(level: AlertLevel, desc: AlertDescription) -> Self { + Self { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::Alert(AlertMessagePayload { + level, + description: desc, + }), + } + } + + pub(crate) fn build_key_update_notify() -> Self { + Self { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateNotRequested), + )), + } + } + + pub(crate) fn build_key_update_request() -> Self { + Self { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateRequested), + )), + } + } + + pub(crate) fn into_owned(self) -> Message<'static> { + let Self { version, payload } = self; + Message { + version, + payload: payload.into_owned(), + } + } + + #[cfg(test)] + pub(crate) fn into_wire_bytes(self) -> Vec { + EncodedMessage::>::from(self) + .into_unencrypted_opaque() + .encode() + } + + pub(crate) fn handshake_type(&self) -> Option { + match &self.payload { + MessagePayload::Handshake { parsed, .. } => Some(parsed.0.handshake_type()), + _ => None, + } + } +} + +impl<'a> TryFrom> for Message<'a> { + type Error = InvalidMessage; + + fn try_from(plain: EncodedMessage<&'a [u8]>) -> Result { + Ok(Self { + version: plain.version, + payload: MessagePayload::new(plain.typ, plain.version, plain.payload)?, + }) + } +} + +impl<'a> TryFrom<&'a EncodedMessage>> for Message<'a> { + type Error = InvalidMessage; + + fn try_from(plain: &'a EncodedMessage>) -> Result { + Ok(Self { + version: plain.version, + payload: MessagePayload::new(plain.typ, plain.version, plain.payload.bytes())?, + }) + } +} + +pub(crate) fn read_opaque_message_header( + r: &mut Reader<'_>, +) -> Result<(ContentType, ProtocolVersion, u16), MessageError> { + let typ = ContentType::read(r).map_err(|_| MessageError::TooShortForHeader)?; + // Don't accept any new content-types. + if ContentTypeName::try_from(typ).is_err() { + return Err(MessageError::InvalidContentType); + } + + let version = ProtocolVersion::read(r).map_err(|_| MessageError::TooShortForHeader)?; + // Accept only versions 0x03XX for any XX. + if version.0 & 0xff00 != 0x0300 { + return Err(MessageError::UnknownProtocolVersion); + } + + let len = u16::read(r).map_err(|_| MessageError::TooShortForHeader)?; + + // Reject undersize messages + // implemented per section 5.1 of RFC8446 (TLSv1.3) + // per section 6.2.1 of RFC5246 (TLSv1.2) + if typ != ContentType::ApplicationData && len == 0 { + return Err(MessageError::InvalidEmptyPayload); + } + + // Reject oversize messages + if len >= MAX_PAYLOAD { + return Err(MessageError::MessageTooLarge); + } + + Ok((typ, version, len)) +} + +#[non_exhaustive] +#[derive(Debug)] +pub(crate) enum MessagePayload<'a> { + Alert(AlertMessagePayload), + // one handshake message, parsed + Handshake { + parsed: HandshakeMessagePayload<'a>, + encoded: Payload<'a>, + }, + // (potentially) multiple handshake messages, unparsed + HandshakeFlight(Payload<'a>), + ChangeCipherSpec(ChangeCipherSpecPayload), + ApplicationData(Payload<'a>), +} + +impl<'a> MessagePayload<'a> { + pub(crate) fn encode(&self, bytes: &mut Vec) { + match self { + Self::Alert(x) => x.encode(bytes), + Self::Handshake { encoded, .. } => bytes.extend(encoded.bytes()), + Self::HandshakeFlight(x) => bytes.extend(x.bytes()), + Self::ChangeCipherSpec(x) => x.encode(bytes), + Self::ApplicationData(x) => x.encode(bytes), + } + } + + pub(crate) fn handshake(parsed: HandshakeMessagePayload<'a>) -> Self { + Self::Handshake { + encoded: Payload::new(parsed.get_encoding()), + parsed, + } + } + + pub(crate) fn new( + typ: ContentType, + vers: ProtocolVersion, + payload: &'a [u8], + ) -> Result { + let mut r = Reader::new(payload); + match typ { + ContentType::ApplicationData => Ok(Self::ApplicationData(Payload::Borrowed(payload))), + ContentType::Alert => AlertMessagePayload::read(&mut r).map(MessagePayload::Alert), + ContentType::Handshake => { + HandshakeMessagePayload::read_version(&mut r, vers).map(|parsed| Self::Handshake { + parsed, + encoded: Payload::Borrowed(payload), + }) + } + ContentType::ChangeCipherSpec => { + ChangeCipherSpecPayload::read(&mut r).map(MessagePayload::ChangeCipherSpec) + } + _ => Err(InvalidMessage::InvalidContentType), + } + } + + pub(crate) fn content_type(&self) -> ContentType { + match self { + Self::Alert(_) => ContentType::Alert, + Self::Handshake { .. } | Self::HandshakeFlight(_) => ContentType::Handshake, + Self::ChangeCipherSpec(_) => ContentType::ChangeCipherSpec, + Self::ApplicationData(_) => ContentType::ApplicationData, + } + } + + pub(crate) fn into_owned(self) -> MessagePayload<'static> { + use MessagePayload::*; + match self { + Alert(x) => Alert(x), + Handshake { parsed, encoded } => Handshake { + parsed: parsed.into_owned(), + encoded: encoded.into_owned(), + }, + HandshakeFlight(x) => HandshakeFlight(x.into_owned()), + ChangeCipherSpec(x) => ChangeCipherSpec(x), + ApplicationData(x) => ApplicationData(x.into_owned()), + } + } +} + +impl From> for EncodedMessage> { + fn from(msg: Message<'_>) -> Self { + let typ = msg.payload.content_type(); + let payload = match msg.payload { + MessagePayload::ApplicationData(payload) => payload.into_owned(), + _ => { + let mut buf = Vec::new(); + msg.payload.encode(&mut buf); + Payload::Owned(buf) + } + }; + + Self { + typ, + version: msg.version, + payload, + } + } +} + +#[derive(Debug)] +pub(crate) struct HandshakeMessagePayload<'a>(pub(crate) HandshakePayload<'a>); + +impl<'a> Codec<'a> for HandshakeMessagePayload<'a> { + fn encode(&self, bytes: &mut Vec) { + self.payload_encode(bytes, Encoding::Standard); + } + + fn read(r: &mut Reader<'a>) -> Result { + Self::read_version(r, ProtocolVersion::TLSv1_2) + } +} + +impl<'a> HandshakeMessagePayload<'a> { + pub(crate) fn read_version( + r: &mut Reader<'a>, + vers: ProtocolVersion, + ) -> Result { + let typ = HandshakeType::read(r)?; + let len = U24::read(r)?.0 as usize; + let mut sub = r.sub(len)?; + + let payload = match typ { + HandshakeType::HelloRequest if sub.left() == 0 => HandshakePayload::HelloRequest, + HandshakeType::ClientHello => { + HandshakePayload::ClientHello(ClientHelloPayload::read(&mut sub)?) + } + HandshakeType::ServerHello => { + let version = ProtocolVersion::read(&mut sub)?; + let random = Random::read(&mut sub)?; + + if random == HELLO_RETRY_REQUEST_RANDOM { + let mut hrr = HelloRetryRequest::read(&mut sub)?; + hrr.legacy_version = version; + HandshakePayload::HelloRetryRequest(hrr) + } else { + let mut shp = ServerHelloPayload::read(&mut sub)?; + shp.legacy_version = version; + shp.random = random; + HandshakePayload::ServerHello(shp) + } + } + HandshakeType::Certificate if vers == ProtocolVersion::TLSv1_3 => { + let p = CertificatePayloadTls13::read(&mut sub)?; + HandshakePayload::CertificateTls13(p) + } + HandshakeType::Certificate => { + HandshakePayload::Certificate(CertificateChain::read(&mut sub)?) + } + HandshakeType::ServerKeyExchange => { + let p = ServerKeyExchangePayload::read(&mut sub)?; + HandshakePayload::ServerKeyExchange(p) + } + HandshakeType::ServerHelloDone => { + sub.expect_empty("ServerHelloDone")?; + HandshakePayload::ServerHelloDone + } + HandshakeType::ClientKeyExchange => { + HandshakePayload::ClientKeyExchange(Payload::read(&mut sub)) + } + HandshakeType::CertificateRequest if vers == ProtocolVersion::TLSv1_3 => { + let p = CertificateRequestPayloadTls13::read(&mut sub)?; + HandshakePayload::CertificateRequestTls13(p) + } + HandshakeType::CertificateRequest => { + let p = CertificateRequestPayload::read(&mut sub)?; + HandshakePayload::CertificateRequest(p) + } + HandshakeType::CompressedCertificate => HandshakePayload::CompressedCertificate( + CompressedCertificatePayload::read(&mut sub)?, + ), + HandshakeType::CertificateVerify => { + HandshakePayload::CertificateVerify(DigitallySignedStruct::read(&mut sub)?) + } + HandshakeType::NewSessionTicket if vers == ProtocolVersion::TLSv1_3 => { + let p = NewSessionTicketPayloadTls13::read(&mut sub)?; + HandshakePayload::NewSessionTicketTls13(p) + } + HandshakeType::NewSessionTicket => { + let p = NewSessionTicketPayload::read(&mut sub)?; + HandshakePayload::NewSessionTicket(p) + } + HandshakeType::EncryptedExtensions => { + HandshakePayload::EncryptedExtensions(Box::new(ServerExtensions::read(&mut sub)?)) + } + HandshakeType::KeyUpdate => { + HandshakePayload::KeyUpdate(KeyUpdateRequest::read(&mut sub)?) + } + HandshakeType::EndOfEarlyData => { + sub.expect_empty("EndOfEarlyData")?; + HandshakePayload::EndOfEarlyData + } + HandshakeType::Finished => HandshakePayload::Finished(Payload::read(&mut sub)), + HandshakeType::CertificateStatus => { + HandshakePayload::CertificateStatus(CertificateStatus::read(&mut sub)?) + } + HandshakeType::MessageHash => { + // does not appear on the wire + return Err(InvalidMessage::UnexpectedMessage("MessageHash")); + } + HandshakeType::HelloRetryRequest => { + // not legal on wire + return Err(InvalidMessage::UnexpectedMessage("HelloRetryRequest")); + } + _ => HandshakePayload::Unknown((typ, Payload::read(&mut sub))), + }; + + sub.expect_empty("HandshakeMessagePayload") + .map(|_| Self(payload)) + } + + pub(crate) fn encoding_for_binder_signing(&self) -> Vec { + let mut ret = self.get_encoding(); + let ret_len = ret.len() - self.total_binder_length(); + ret.truncate(ret_len); + ret + } + + pub(crate) fn total_binder_length(&self) -> usize { + match &self.0 { + HandshakePayload::ClientHello(ch) => match &ch.preshared_key_offer { + Some(offer) => { + let mut binders_encoding = Vec::new(); + offer + .binders + .encode(&mut binders_encoding); + binders_encoding.len() + } + _ => 0, + }, + _ => 0, + } + } + + pub(crate) fn payload_encode(&self, bytes: &mut Vec, encoding: Encoding) { + // output type, length, and encoded payload + self.0 + .wire_handshake_type() + .encode(bytes); + + let nested = LengthPrefixedBuffer::new( + ListLength::U24 { + max: usize::MAX, + error: InvalidMessage::MessageTooLarge, + }, + bytes, + ); + + match &self.0 { + // for Server Hello and HelloRetryRequest payloads we need to encode the payload + // differently based on the purpose of the encoding. + HandshakePayload::ServerHello(payload) => payload.payload_encode(nested.buf, encoding), + HandshakePayload::HelloRetryRequest(payload) => { + payload.payload_encode(nested.buf, encoding) + } + + // All other payload types are encoded the same regardless of purpose. + _ => self.0.encode(nested.buf), + } + } + + pub(crate) fn build_handshake_hash(hash: &[u8]) -> Self { + Self(HandshakePayload::MessageHash(Payload::new(hash.to_vec()))) + } + + pub(crate) fn into_owned(self) -> HandshakeMessagePayload<'static> { + HandshakeMessagePayload(self.0.into_owned()) + } +} + +#[derive(Debug)] +pub(crate) enum HandshakePayload<'a> { + HelloRequest, + ClientHello(ClientHelloPayload), + ServerHello(ServerHelloPayload), + HelloRetryRequest(HelloRetryRequest), + Certificate(CertificateChain<'a>), + CertificateTls13(CertificatePayloadTls13<'a>), + CompressedCertificate(CompressedCertificatePayload<'a>), + ServerKeyExchange(ServerKeyExchangePayload), + CertificateRequest(CertificateRequestPayload), + CertificateRequestTls13(CertificateRequestPayloadTls13), + CertificateVerify(DigitallySignedStruct), + ServerHelloDone, + EndOfEarlyData, + ClientKeyExchange(Payload<'a>), + NewSessionTicket(NewSessionTicketPayload), + NewSessionTicketTls13(NewSessionTicketPayloadTls13), + EncryptedExtensions(Box>), + KeyUpdate(KeyUpdateRequest), + Finished(Payload<'a>), + CertificateStatus(CertificateStatus<'a>), + MessageHash(Payload<'a>), + Unknown((HandshakeType, Payload<'a>)), +} + +impl HandshakePayload<'_> { + fn encode(&self, bytes: &mut Vec) { + use self::HandshakePayload::*; + match self { + HelloRequest | ServerHelloDone | EndOfEarlyData => {} + ClientHello(x) => x.encode(bytes), + ServerHello(x) => x.encode(bytes), + HelloRetryRequest(x) => x.encode(bytes), + Certificate(x) => x.encode(bytes), + CertificateTls13(x) => x.encode(bytes), + CompressedCertificate(x) => x.encode(bytes), + ServerKeyExchange(x) => x.encode(bytes), + ClientKeyExchange(x) => x.encode(bytes), + CertificateRequest(x) => x.encode(bytes), + CertificateRequestTls13(x) => x.encode(bytes), + CertificateVerify(x) => x.encode(bytes), + NewSessionTicket(x) => x.encode(bytes), + NewSessionTicketTls13(x) => x.encode(bytes), + EncryptedExtensions(x) => x.encode(bytes), + KeyUpdate(x) => x.encode(bytes), + Finished(x) => x.encode(bytes), + CertificateStatus(x) => x.encode(bytes), + MessageHash(x) => x.encode(bytes), + Unknown((_, x)) => x.encode(bytes), + } + } + + pub(crate) fn handshake_type(&self) -> HandshakeType { + use self::HandshakePayload::*; + match self { + HelloRequest => HandshakeType::HelloRequest, + ClientHello(_) => HandshakeType::ClientHello, + ServerHello(_) => HandshakeType::ServerHello, + HelloRetryRequest(_) => HandshakeType::HelloRetryRequest, + Certificate(_) | CertificateTls13(_) => HandshakeType::Certificate, + CompressedCertificate(_) => HandshakeType::CompressedCertificate, + ServerKeyExchange(_) => HandshakeType::ServerKeyExchange, + CertificateRequest(_) | CertificateRequestTls13(_) => HandshakeType::CertificateRequest, + CertificateVerify(_) => HandshakeType::CertificateVerify, + ServerHelloDone => HandshakeType::ServerHelloDone, + EndOfEarlyData => HandshakeType::EndOfEarlyData, + ClientKeyExchange(_) => HandshakeType::ClientKeyExchange, + NewSessionTicket(_) | NewSessionTicketTls13(_) => HandshakeType::NewSessionTicket, + EncryptedExtensions(_) => HandshakeType::EncryptedExtensions, + KeyUpdate(_) => HandshakeType::KeyUpdate, + Finished(_) => HandshakeType::Finished, + CertificateStatus(_) => HandshakeType::CertificateStatus, + MessageHash(_) => HandshakeType::MessageHash, + Unknown((t, _)) => *t, + } + } + + fn wire_handshake_type(&self) -> HandshakeType { + match self.handshake_type() { + // A `HelloRetryRequest` appears on the wire as a `ServerHello` with a magic `random` value. + HandshakeType::HelloRetryRequest => HandshakeType::ServerHello, + other => other, + } + } + + fn into_owned(self) -> HandshakePayload<'static> { + use HandshakePayload::*; + + match self { + HelloRequest => HelloRequest, + ClientHello(x) => ClientHello(x), + ServerHello(x) => ServerHello(x), + HelloRetryRequest(x) => HelloRetryRequest(x), + Certificate(x) => Certificate(x.into_owned()), + CertificateTls13(x) => CertificateTls13(x.into_owned()), + CompressedCertificate(x) => CompressedCertificate(x.into_owned()), + ServerKeyExchange(x) => ServerKeyExchange(x), + CertificateRequest(x) => CertificateRequest(x), + CertificateRequestTls13(x) => CertificateRequestTls13(x), + CertificateVerify(x) => CertificateVerify(x), + ServerHelloDone => ServerHelloDone, + EndOfEarlyData => EndOfEarlyData, + ClientKeyExchange(x) => ClientKeyExchange(x.into_owned()), + NewSessionTicket(x) => NewSessionTicket(x), + NewSessionTicketTls13(x) => NewSessionTicketTls13(x), + EncryptedExtensions(x) => EncryptedExtensions(Box::new(x.into_owned())), + KeyUpdate(x) => KeyUpdate(x), + Finished(x) => Finished(x.into_owned()), + CertificateStatus(x) => CertificateStatus(x.into_owned()), + MessageHash(x) => MessageHash(x.into_owned()), + Unknown((t, x)) => Unknown((t, x.into_owned())), + } + } +} + +#[derive(Debug)] +pub(crate) struct AlertMessagePayload { + pub level: AlertLevel, + pub description: AlertDescription, +} + +impl Codec<'_> for AlertMessagePayload { + fn encode(&self, bytes: &mut Vec) { + self.level.encode(bytes); + self.description.encode(bytes); + } + + fn read(r: &mut Reader<'_>) -> Result { + let level = AlertLevel::read(r)?; + let description = AlertDescription::read(r)?; + r.expect_empty("AlertMessagePayload") + .map(|_| Self { level, description }) + } +} + +#[derive(Debug)] +pub(crate) struct ChangeCipherSpecPayload; + +impl Codec<'_> for ChangeCipherSpecPayload { + fn encode(&self, bytes: &mut Vec) { + 1u8.encode(bytes); + } + + fn read(r: &mut Reader<'_>) -> Result { + let typ = u8::read(r)?; + if typ != 1 { + return Err(InvalidMessage::InvalidCcs); + } + + r.expect_empty("ChangeCipherSpecPayload") + .map(|_| Self {}) + } +} + +/// Content type, version and size. +pub(crate) const HEADER_SIZE: usize = 1 + 2 + 2; + +/// Maximum message payload size. +/// That's 2^14 payload bytes and a 2KB allowance for ciphertext overheads. +pub(crate) const MAX_PAYLOAD: u16 = 16_384 + 2048; #[cfg(test)] mod tests { - use super::codec::Reader; - use super::message::{Message, OutboundOpaqueMessage}; + use alloc::vec; + use std::io::Read; + use std::path::{Path, PathBuf}; + use std::{format, fs, println}; + + use super::*; + use crate::crypto::cipher::OutboundOpaque; + use crate::error::AlertDescription; + + #[test] + fn test_read_fuzz_corpus() { + fn corpus_dir() -> PathBuf { + let from_subcrate = Path::new("../fuzz/corpus/message"); + let from_root = Path::new("fuzz/corpus/message"); + + if from_root.is_dir() { + from_root.to_path_buf() + } else { + from_subcrate.to_path_buf() + } + } + + for file in fs::read_dir(corpus_dir()).unwrap() { + let mut f = fs::File::open(file.unwrap().path()).unwrap(); + let mut bytes = Vec::new(); + f.read_to_end(&mut bytes).unwrap(); + + let mut rd = Reader::new(&bytes); + let msg = EncodedMessage::>::read(&mut rd).unwrap(); + println!("{msg:?}"); + + let Ok(msg) = Message::try_from(&msg) else { + continue; + }; + + let enc = EncodedMessage::>::from(msg) + .into_unencrypted_opaque() + .encode(); + assert_eq!(bytes.to_vec(), enc); + assert_eq!(bytes[..bytes.len() - rd.left()].to_vec(), enc); + } + } + + #[test] + fn can_read_safari_client_hello_with_ip_address_in_sni_extension() { + let _ = env_logger::Builder::new() + .filter(None, log::LevelFilter::Trace) + .try_init(); + + let bytes = b"\ + \x16\x03\x01\x00\xeb\x01\x00\x00\xe7\x03\x03\xb6\x1f\xe4\x3a\x55\ + \x90\x3e\xc0\x28\x9c\x12\xe0\x5c\x84\xea\x90\x1b\xfb\x11\xfc\xbd\ + \x25\x55\xda\x9f\x51\x93\x1b\x8d\x92\x66\xfd\x00\x00\x2e\xc0\x2c\ + \xc0\x2b\xc0\x24\xc0\x23\xc0\x0a\xc0\x09\xcc\xa9\xc0\x30\xc0\x2f\ + \xc0\x28\xc0\x27\xc0\x14\xc0\x13\xcc\xa8\x00\x9d\x00\x9c\x00\x3d\ + \x00\x3c\x00\x35\x00\x2f\xc0\x08\xc0\x12\x00\x0a\x01\x00\x00\x90\ + \xff\x01\x00\x01\x00\x00\x00\x00\x0e\x00\x0c\x00\x00\x09\x31\x32\ + \x37\x2e\x30\x2e\x30\x2e\x31\x00\x17\x00\x00\x00\x0d\x00\x18\x00\ + \x16\x04\x03\x08\x04\x04\x01\x05\x03\x02\x03\x08\x05\x08\x05\x05\ + \x01\x08\x06\x06\x01\x02\x01\x00\x05\x00\x05\x01\x00\x00\x00\x00\ + \x33\x74\x00\x00\x00\x12\x00\x00\x00\x10\x00\x30\x00\x2e\x02\x68\ + \x32\x05\x68\x32\x2d\x31\x36\x05\x68\x32\x2d\x31\x35\x05\x68\x32\ + \x2d\x31\x34\x08\x73\x70\x64\x79\x2f\x33\x2e\x31\x06\x73\x70\x64\ + \x79\x2f\x33\x08\x68\x74\x74\x70\x2f\x31\x2e\x31\x00\x0b\x00\x02\ + \x01\x00\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x18\x00\x19"; + let mut rd = Reader::new(bytes); + let m = EncodedMessage::>::read(&mut rd).unwrap(); + println!("m = {m:?}"); + Message::try_from(&m).unwrap(); + } + + #[test] + fn alert_is_not_handshake() { + let m = Message::build_alert(AlertLevel::Fatal, AlertDescription::DecodeError); + assert_ne!(m.handshake_type(), Some(HandshakeType::ClientHello)); + } + + #[test] + fn construct_all_types() { + let samples = [ + &b"\x14\x03\x04\x00\x01\x01"[..], + &b"\x15\x03\x04\x00\x02\x01\x16"[..], + &b"\x16\x03\x04\x00\x05\x18\x00\x00\x01\x00"[..], + &b"\x17\x03\x04\x00\x04\x11\x22\x33\x44"[..], + &b"\x18\x03\x04\x00\x04\x11\x22\x33\x44"[..], + ]; + for &bytes in samples.iter() { + let m = EncodedMessage::>::read(&mut Reader::new(bytes)).unwrap(); + println!("m = {m:?}"); + let m = Message::try_from(&m); + println!("m' = {m:?}"); + } + } + + #[test] + fn debug_payload() { + assert_eq!("01020304", format!("{:?}", Payload::new(vec![1, 2, 3, 4]))); + assert_eq!( + "01020304", + format!("{:?}", SizedPayload::::from(vec![1, 2, 3, 4])) + ); + assert_eq!( + "01020304", + format!( + "{:?}", + SizedPayload::::from(vec![1, 2, 3, 4]) + ) + ); + assert_eq!( + "01020304", + format!( + "{:?}", + SizedPayload::<'static, U24, NonEmpty>::from(Payload::new(vec![1, 2, 3, 4])) + ) + ); + } + + #[test] + fn into_wire_format() { + // Message::into_wire_bytes() include both message-level and handshake-level headers + assert_eq!( + Message::build_key_update_request().into_wire_bytes(), + &[0x16, 0x3, 0x4, 0x0, 0x5, 0x18, 0x0, 0x0, 0x1, 0x1] + ); + } #[test] fn smoketest() { let bytes = include_bytes!("../testdata/handshake-test.1.bin"); - let mut r = Reader::init(bytes); + let mut r = Reader::new(bytes); while r.any_left() { - let m = OutboundOpaqueMessage::read(&mut r).unwrap(); + let m = EncodedMessage::>::read(&mut r).unwrap(); - let out = m.clone().encode(); + let out = EncodedMessage { + typ: m.typ, + version: m.version, + payload: OutboundOpaque::from(m.payload.bytes()), + } + .encode(); assert!(!out.is_empty()); - Message::try_from(m.into_plain_message()).unwrap(); + Message::try_from(&m).unwrap(); } } } diff --git a/rustls/src/msgs/persist.rs b/rustls/src/msgs/persist.rs deleted file mode 100644 index 33e7b66f9cc..00000000000 --- a/rustls/src/msgs/persist.rs +++ /dev/null @@ -1,454 +0,0 @@ -use alloc::sync::Arc; -use alloc::vec::Vec; -use core::cmp; - -use pki_types::{DnsName, UnixTime}; -use zeroize::Zeroizing; - -use crate::enums::{CipherSuite, ProtocolVersion}; -use crate::error::InvalidMessage; -use crate::msgs::base::{PayloadU16, PayloadU8}; -use crate::msgs::codec::{Codec, Reader}; -use crate::msgs::handshake::CertificateChain; -#[cfg(feature = "tls12")] -use crate::msgs::handshake::SessionId; -#[cfg(feature = "tls12")] -use crate::tls12::Tls12CipherSuite; -use crate::tls13::Tls13CipherSuite; - -pub(crate) struct Retrieved { - pub(crate) value: T, - retrieved_at: UnixTime, -} - -impl Retrieved { - pub(crate) fn new(value: T, retrieved_at: UnixTime) -> Self { - Self { - value, - retrieved_at, - } - } - - pub(crate) fn map(&self, f: impl FnOnce(&T) -> Option<&M>) -> Option> { - Some(Retrieved { - value: f(&self.value)?, - retrieved_at: self.retrieved_at, - }) - } -} - -impl Retrieved<&Tls13ClientSessionValue> { - pub(crate) fn obfuscated_ticket_age(&self) -> u32 { - let age_secs = self - .retrieved_at - .as_secs() - .saturating_sub(self.value.common.epoch); - let age_millis = age_secs as u32 * 1000; - age_millis.wrapping_add(self.value.age_add) - } -} - -impl> Retrieved { - pub(crate) fn has_expired(&self) -> bool { - let common = &*self.value; - common.lifetime_secs != 0 - && common - .epoch - .saturating_add(u64::from(common.lifetime_secs)) - < self.retrieved_at.as_secs() - } -} - -impl core::ops::Deref for Retrieved { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.value - } -} - -#[derive(Debug)] -pub struct Tls13ClientSessionValue { - suite: &'static Tls13CipherSuite, - age_add: u32, - max_early_data_size: u32, - pub(crate) common: ClientSessionCommon, - quic_params: PayloadU16, -} - -impl Tls13ClientSessionValue { - pub(crate) fn new( - suite: &'static Tls13CipherSuite, - ticket: Arc, - secret: &[u8], - server_cert_chain: CertificateChain<'static>, - time_now: UnixTime, - lifetime_secs: u32, - age_add: u32, - max_early_data_size: u32, - ) -> Self { - Self { - suite, - age_add, - max_early_data_size, - common: ClientSessionCommon::new( - ticket, - secret, - time_now, - lifetime_secs, - server_cert_chain, - ), - quic_params: PayloadU16(Vec::new()), - } - } - - pub fn max_early_data_size(&self) -> u32 { - self.max_early_data_size - } - - pub fn suite(&self) -> &'static Tls13CipherSuite { - self.suite - } - - #[doc(hidden)] - /// Test only: rewind epoch by `delta` seconds. - pub fn rewind_epoch(&mut self, delta: u32) { - self.common.epoch -= delta as u64; - } - - #[doc(hidden)] - /// Test only: replace `max_early_data_size` with `new` - pub fn _private_set_max_early_data_size(&mut self, new: u32) { - self.max_early_data_size = new; - } - - pub fn set_quic_params(&mut self, quic_params: &[u8]) { - self.quic_params = PayloadU16(quic_params.to_vec()); - } - - pub fn quic_params(&self) -> Vec { - self.quic_params.0.clone() - } -} - -impl core::ops::Deref for Tls13ClientSessionValue { - type Target = ClientSessionCommon; - - fn deref(&self) -> &Self::Target { - &self.common - } -} - -#[derive(Debug, Clone)] -pub struct Tls12ClientSessionValue { - #[cfg(feature = "tls12")] - suite: &'static Tls12CipherSuite, - #[cfg(feature = "tls12")] - pub(crate) session_id: SessionId, - #[cfg(feature = "tls12")] - extended_ms: bool, - #[doc(hidden)] - #[cfg(feature = "tls12")] - pub(crate) common: ClientSessionCommon, -} - -#[cfg(feature = "tls12")] -impl Tls12ClientSessionValue { - pub(crate) fn new( - suite: &'static Tls12CipherSuite, - session_id: SessionId, - ticket: Arc, - master_secret: &[u8], - server_cert_chain: CertificateChain<'static>, - time_now: UnixTime, - lifetime_secs: u32, - extended_ms: bool, - ) -> Self { - Self { - suite, - session_id, - extended_ms, - common: ClientSessionCommon::new( - ticket, - master_secret, - time_now, - lifetime_secs, - server_cert_chain, - ), - } - } - - pub(crate) fn ticket(&mut self) -> Arc { - Arc::clone(&self.common.ticket) - } - - pub(crate) fn extended_ms(&self) -> bool { - self.extended_ms - } - - pub(crate) fn suite(&self) -> &'static Tls12CipherSuite { - self.suite - } - - #[doc(hidden)] - /// Test only: rewind epoch by `delta` seconds. - pub fn rewind_epoch(&mut self, delta: u32) { - self.common.epoch -= delta as u64; - } -} - -#[cfg(feature = "tls12")] -impl core::ops::Deref for Tls12ClientSessionValue { - type Target = ClientSessionCommon; - - fn deref(&self) -> &Self::Target { - &self.common - } -} - -#[derive(Debug, Clone)] -pub struct ClientSessionCommon { - ticket: Arc, - secret: Zeroizing, - epoch: u64, - lifetime_secs: u32, - server_cert_chain: Arc>, -} - -impl ClientSessionCommon { - fn new( - ticket: Arc, - secret: &[u8], - time_now: UnixTime, - lifetime_secs: u32, - server_cert_chain: CertificateChain<'static>, - ) -> Self { - Self { - ticket, - secret: Zeroizing::new(PayloadU8(secret.to_vec())), - epoch: time_now.as_secs(), - lifetime_secs: cmp::min(lifetime_secs, MAX_TICKET_LIFETIME), - server_cert_chain: Arc::new(server_cert_chain), - } - } - - pub(crate) fn server_cert_chain(&self) -> &CertificateChain<'static> { - &self.server_cert_chain - } - - pub(crate) fn secret(&self) -> &[u8] { - self.secret.0.as_ref() - } - - pub(crate) fn ticket(&self) -> &[u8] { - self.ticket.0.as_ref() - } -} - -static MAX_TICKET_LIFETIME: u32 = 7 * 24 * 60 * 60; - -/// This is the maximum allowed skew between server and client clocks, over -/// the maximum ticket lifetime period. This encompasses TCP retransmission -/// times in case packet loss occurs when the client sends the ClientHello -/// or receives the NewSessionTicket, _and_ actual clock skew over this period. -static MAX_FRESHNESS_SKEW_MS: u32 = 60 * 1000; - -// --- Server types --- -#[derive(Debug)] -pub struct ServerSessionValue { - pub(crate) sni: Option>, - pub(crate) version: ProtocolVersion, - pub(crate) cipher_suite: CipherSuite, - pub(crate) master_secret: Zeroizing, - pub(crate) extended_ms: bool, - pub(crate) client_cert_chain: Option>, - pub(crate) alpn: Option, - pub(crate) application_data: PayloadU16, - pub creation_time_sec: u64, - pub(crate) age_obfuscation_offset: u32, - freshness: Option, -} - -impl Codec<'_> for ServerSessionValue { - fn encode(&self, bytes: &mut Vec) { - if let Some(ref sni) = self.sni { - 1u8.encode(bytes); - let sni_bytes: &str = sni.as_ref(); - PayloadU8::new(Vec::from(sni_bytes)).encode(bytes); - } else { - 0u8.encode(bytes); - } - self.version.encode(bytes); - self.cipher_suite.encode(bytes); - self.master_secret.encode(bytes); - (u8::from(self.extended_ms)).encode(bytes); - if let Some(ref chain) = self.client_cert_chain { - 1u8.encode(bytes); - chain.encode(bytes); - } else { - 0u8.encode(bytes); - } - if let Some(ref alpn) = self.alpn { - 1u8.encode(bytes); - alpn.encode(bytes); - } else { - 0u8.encode(bytes); - } - self.application_data.encode(bytes); - self.creation_time_sec.encode(bytes); - self.age_obfuscation_offset - .encode(bytes); - } - - fn read(r: &mut Reader<'_>) -> Result { - let has_sni = u8::read(r)?; - let sni = if has_sni == 1 { - let dns_name = PayloadU8::read(r)?; - let dns_name = match DnsName::try_from(dns_name.0.as_slice()) { - Ok(dns_name) => dns_name.to_owned(), - Err(_) => return Err(InvalidMessage::InvalidServerName), - }; - - Some(dns_name) - } else { - None - }; - - let v = ProtocolVersion::read(r)?; - let cs = CipherSuite::read(r)?; - let ms = Zeroizing::new(PayloadU8::read(r)?); - let ems = u8::read(r)?; - let has_ccert = u8::read(r)? == 1; - let ccert = if has_ccert { - Some(CertificateChain::read(r)?.into_owned()) - } else { - None - }; - let has_alpn = u8::read(r)? == 1; - let alpn = if has_alpn { - Some(PayloadU8::read(r)?) - } else { - None - }; - let application_data = PayloadU16::read(r)?; - let creation_time_sec = u64::read(r)?; - let age_obfuscation_offset = u32::read(r)?; - - Ok(Self { - sni, - version: v, - cipher_suite: cs, - master_secret: ms, - extended_ms: ems == 1u8, - client_cert_chain: ccert, - alpn, - application_data, - creation_time_sec, - age_obfuscation_offset, - freshness: None, - }) - } -} - -impl ServerSessionValue { - pub(crate) fn new( - sni: Option<&DnsName<'_>>, - v: ProtocolVersion, - cs: CipherSuite, - ms: &[u8], - client_cert_chain: Option>, - alpn: Option>, - application_data: Vec, - creation_time: UnixTime, - age_obfuscation_offset: u32, - ) -> Self { - Self { - sni: sni.map(|dns| dns.to_owned()), - version: v, - cipher_suite: cs, - master_secret: Zeroizing::new(PayloadU8::new(ms.to_vec())), - extended_ms: false, - client_cert_chain, - alpn: alpn.map(PayloadU8::new), - application_data: PayloadU16::new(application_data), - creation_time_sec: creation_time.as_secs(), - age_obfuscation_offset, - freshness: None, - } - } - - #[cfg(feature = "tls12")] - pub(crate) fn set_extended_ms_used(&mut self) { - self.extended_ms = true; - } - - pub(crate) fn set_freshness( - mut self, - obfuscated_client_age_ms: u32, - time_now: UnixTime, - ) -> Self { - let client_age_ms = obfuscated_client_age_ms.wrapping_sub(self.age_obfuscation_offset); - let server_age_ms = (time_now - .as_secs() - .saturating_sub(self.creation_time_sec) as u32) - .saturating_mul(1000); - - let age_difference = if client_age_ms < server_age_ms { - server_age_ms - client_age_ms - } else { - client_age_ms - server_age_ms - }; - - self.freshness = Some(age_difference <= MAX_FRESHNESS_SKEW_MS); - self - } - - pub(crate) fn is_fresh(&self) -> bool { - self.freshness.unwrap_or_default() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(feature = "std")] // for UnixTime::now - #[test] - fn serversessionvalue_is_debug() { - use std::{println, vec}; - let ssv = ServerSessionValue::new( - None, - ProtocolVersion::TLSv1_3, - CipherSuite::TLS13_AES_128_GCM_SHA256, - &[1, 2, 3], - None, - None, - vec![4, 5, 6], - UnixTime::now(), - 0x12345678, - ); - println!("{:?}", ssv); - } - - #[test] - fn serversessionvalue_no_sni() { - let bytes = [ - 0x00, 0x03, 0x03, 0xc0, 0x23, 0x03, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0xfe, 0xed, 0xf0, 0x0d, - ]; - let mut rd = Reader::init(&bytes); - let ssv = ServerSessionValue::read(&mut rd).unwrap(); - assert_eq!(ssv.get_encoding(), bytes); - } - - #[test] - fn serversessionvalue_with_cert() { - let bytes = [ - 0x00, 0x03, 0x03, 0xc0, 0x23, 0x03, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0xfe, 0xed, 0xf0, 0x0d, - ]; - let mut rd = Reader::init(&bytes); - let ssv = ServerSessionValue::read(&mut rd).unwrap(); - assert_eq!(ssv.get_encoding(), bytes); - } -} diff --git a/rustls/src/msgs/server_hello.rs b/rustls/src/msgs/server_hello.rs new file mode 100644 index 00000000000..66021fab94a --- /dev/null +++ b/rustls/src/msgs/server_hello.rs @@ -0,0 +1,500 @@ +use alloc::boxed::Box; +use alloc::collections::BTreeSet; +use alloc::vec::Vec; +use core::ops::{Deref, DerefMut}; + +use pki_types::DnsName; + +use super::codec::{ + Codec, LengthPrefixedBuffer, ListLength, MaybeEmpty, NonEmpty, Reader, SizedPayload, + TlsListElement, +}; +use super::enums::{Compression, EchVersion, ExtensionType}; +use super::handshake::{ + DuplicateExtensionChecker, Encoding, KeyShareEntry, Random, SessionId, SingleProtocolName, + SupportedEcPointFormats, has_duplicates, +}; +use crate::crypto::CipherSuite; +use crate::crypto::cipher::Payload; +use crate::crypto::hpke::{HpkeKem, HpkeSymmetricCipherSuite}; +use crate::enums::{CertificateType, ProtocolVersion}; +use crate::error::InvalidMessage; + +#[derive(Clone, Debug)] +pub(crate) struct ServerHelloPayload { + pub(crate) legacy_version: ProtocolVersion, + pub(crate) random: Random, + pub(crate) session_id: SessionId, + pub(crate) cipher_suite: CipherSuite, + pub(crate) compression_method: Compression, + pub(crate) extensions: Box>, +} + +impl Codec<'_> for ServerHelloPayload { + fn encode(&self, bytes: &mut Vec) { + self.payload_encode(bytes, Encoding::Standard) + } + + // minus version and random, which have already been read. + fn read(r: &mut Reader<'_>) -> Result { + let session_id = SessionId::read(r)?; + let suite = CipherSuite::read(r)?; + let compression = Compression::read(r)?; + + // RFC5246: + // "The presence of extensions can be detected by determining whether + // there are bytes following the compression_method field at the end of + // the ServerHello." + let extensions = Box::new( + if r.any_left() { + ServerExtensions::read(r)? + } else { + ServerExtensions::default() + } + .into_owned(), + ); + + let ret = Self { + legacy_version: ProtocolVersion(0), + random: ZERO_RANDOM, + session_id, + cipher_suite: suite, + compression_method: compression, + extensions, + }; + + r.expect_empty("ServerHelloPayload") + .map(|_| ret) + } +} + +impl ServerHelloPayload { + pub(super) fn payload_encode(&self, bytes: &mut Vec, encoding: Encoding) { + debug_assert!( + !matches!(encoding, Encoding::EchConfirmation), + "we cannot compute an ECH confirmation on a received ServerHello" + ); + + self.legacy_version.encode(bytes); + self.random.encode(bytes); + self.session_id.encode(bytes); + self.cipher_suite.encode(bytes); + self.compression_method.encode(bytes); + self.extensions.encode(bytes); + } +} + +impl Deref for ServerHelloPayload { + type Target = ServerExtensions<'static>; + fn deref(&self) -> &Self::Target { + &self.extensions + } +} + +impl DerefMut for ServerHelloPayload { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.extensions + } +} + +extension_struct! { + pub(crate) struct ServerExtensions<'a> { + /// Supported EC point formats (RFC4492) + ExtensionType::ECPointFormats => + pub(crate) ec_point_formats: Option, + + /// Server name indication acknowledgement (RFC6066) + ExtensionType::ServerName => + pub(crate) server_name_ack: Option<()>, + + /// Session ticket acknowledgement (RFC5077) + ExtensionType::SessionTicket => + pub(crate) session_ticket_ack: Option<()>, + + ExtensionType::RenegotiationInfo => + pub(crate) renegotiation_info: Option>, + + /// Selected ALPN protocol (RFC7301) + ExtensionType::ALProtocolNegotiation => + pub(crate) selected_protocol: Option, + + /// Key exchange server share (RFC8446) + ExtensionType::KeyShare => + pub(crate) key_share: Option, + + /// Selected preshared key index (RFC8446) + ExtensionType::PreSharedKey => + pub(crate) preshared_key: Option, + + /// Required client certificate type (RFC7250) + ExtensionType::ClientCertificateType => + pub(crate) client_certificate_type: Option, + + /// Selected server certificate type (RFC7250) + ExtensionType::ServerCertificateType => + pub(crate) server_certificate_type: Option, + + /// Extended master secret is in use (RFC7627) + ExtensionType::ExtendedMasterSecret => + pub(crate) extended_master_secret_ack: Option<()>, + + /// Certificate status acknowledgement (RFC6066) + ExtensionType::StatusRequest => + pub(crate) certificate_status_request_ack: Option<()>, + + /// Selected TLS version (RFC8446) + ExtensionType::SupportedVersions => + pub(crate) selected_version: Option, + + /// QUIC transport parameters (RFC9001) + ExtensionType::TransportParameters => + pub(crate) transport_parameters: Option>, + + /// Early data is accepted (RFC8446) + ExtensionType::EarlyData => + pub(crate) early_data_ack: Option<()>, + + /// Encrypted inner client hello response (RFC 9849) + ExtensionType::EncryptedClientHello => + pub(crate) encrypted_client_hello_ack: Option, + } + { + pub(crate) unknown_extensions: BTreeSet, + } +} + +impl ServerExtensions<'_> { + pub(super) fn into_owned(self) -> ServerExtensions<'static> { + let Self { + ec_point_formats, + server_name_ack, + session_ticket_ack, + renegotiation_info, + selected_protocol, + key_share, + preshared_key, + client_certificate_type, + server_certificate_type, + extended_master_secret_ack, + certificate_status_request_ack, + selected_version, + transport_parameters, + early_data_ack, + encrypted_client_hello_ack, + unknown_extensions, + } = self; + ServerExtensions { + ec_point_formats, + server_name_ack, + session_ticket_ack, + renegotiation_info: renegotiation_info.map(|x| x.into_owned()), + selected_protocol, + key_share, + preshared_key, + client_certificate_type, + server_certificate_type, + extended_master_secret_ack, + certificate_status_request_ack, + selected_version, + transport_parameters: transport_parameters.map(|x| x.into_owned()), + early_data_ack, + encrypted_client_hello_ack, + unknown_extensions, + } + } +} + +impl<'a> Codec<'a> for ServerExtensions<'a> { + fn encode(&self, bytes: &mut Vec) { + let extensions = LengthPrefixedBuffer::new(ListLength::U16, bytes); + + for ext in Self::ALL_EXTENSIONS { + self.encode_one(*ext, extensions.buf); + } + } + + fn read(r: &mut Reader<'a>) -> Result { + let mut out = Self::default(); + let mut checker = DuplicateExtensionChecker::new(); + + let len = usize::from(u16::read(r)?); + let mut sub = r.sub(len)?; + + while sub.any_left() { + out.read_one(&mut sub, |unknown| checker.check(unknown))?; + } + + out.unknown_extensions = checker.0; + Ok(out) + } +} + +/// Representation of the ECHEncryptedExtensions extension specified in +/// [RFC 9849 Section 5]. +/// +/// [RFC 9849 Section 5]: +#[derive(Clone, Debug)] +pub(crate) struct ServerEncryptedClientHello { + pub(crate) retry_configs: Vec, +} + +impl Codec<'_> for ServerEncryptedClientHello { + fn encode(&self, bytes: &mut Vec) { + self.retry_configs.encode(bytes); + } + + fn read(r: &mut Reader<'_>) -> Result { + Ok(Self { + retry_configs: Vec::::read(r)?, + }) + } +} + +/// An encrypted client hello (ECH) config. +#[non_exhaustive] +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum EchConfigPayload { + /// A recognized V18 ECH configuration. + V18(EchConfigContents), + /// An unknown version ECH configuration. + Unknown { + version: EchVersion, + contents: SizedPayload<'static, u16, MaybeEmpty>, + }, +} + +impl TlsListElement for EchConfigPayload { + const SIZE_LEN: ListLength = ListLength::U16; +} + +impl Codec<'_> for EchConfigPayload { + fn encode(&self, bytes: &mut Vec) { + match self { + Self::V18(c) => { + // Write the version, the length, and the contents. + EchVersion::V18.encode(bytes); + let inner = LengthPrefixedBuffer::new(ListLength::U16, bytes); + c.encode(inner.buf); + } + Self::Unknown { version, contents } => { + // Unknown configuration versions are opaque. + version.encode(bytes); + contents.encode(bytes); + } + } + } + + fn read(r: &mut Reader<'_>) -> Result { + let version = EchVersion::read(r)?; + let length = u16::read(r)?; + let mut contents = r.sub(length as usize)?; + + Ok(match version { + EchVersion::V18 => Self::V18(EchConfigContents::read(&mut contents)?), + _ => { + // Note: we don't SizedPayload::read() here because we've already read the length prefix. + let data = SizedPayload::from(Payload::new(contents.rest())); + Self::Unknown { + version, + contents: data, + } + } + }) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct EchConfigContents { + pub key_config: HpkeKeyConfig, + pub maximum_name_length: u8, + pub public_name: DnsName<'static>, + pub extensions: Vec, +} + +impl EchConfigContents { + /// Returns true if there is more than one extension of a given + /// type. + pub(crate) fn has_duplicate_extension(&self) -> bool { + has_duplicates::<_, _, u16>( + self.extensions + .iter() + .map(|ext| ext.ext_type()), + ) + } + + /// Returns true if there is at least one mandatory unsupported extension. + pub(crate) fn has_unknown_mandatory_extension(&self) -> bool { + self.extensions + .iter() + // An extension is considered mandatory if the high bit of its type is set. + .any(|ext| (ext.ext_type().0 & 0x8000) != 0) + } +} + +impl Codec<'_> for EchConfigContents { + fn encode(&self, bytes: &mut Vec) { + self.key_config.encode(bytes); + self.maximum_name_length.encode(bytes); + let dns_name = &self.public_name.borrow(); + SizedPayload::::from(Payload::Borrowed(dns_name.as_ref().as_ref())) + .encode(bytes); + self.extensions.encode(bytes); + } + + fn read(r: &mut Reader<'_>) -> Result { + Ok(Self { + key_config: HpkeKeyConfig::read(r)?, + maximum_name_length: u8::read(r)?, + public_name: { + DnsName::try_from(SizedPayload::::read(r)?.bytes()) + .map_err(|_| InvalidMessage::InvalidServerName)? + .to_owned() + }, + extensions: Vec::read(r)?, + }) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct HpkeKeyConfig { + pub config_id: u8, + pub kem_id: HpkeKem, + /// RFC 9849: `opaque HpkePublicKey<1..2^16-1>;` + pub public_key: SizedPayload<'static, u16, NonEmpty>, + pub symmetric_cipher_suites: Vec, +} + +impl Codec<'_> for HpkeKeyConfig { + fn encode(&self, bytes: &mut Vec) { + self.config_id.encode(bytes); + self.kem_id.encode(bytes); + self.public_key.encode(bytes); + self.symmetric_cipher_suites + .encode(bytes); + } + + fn read(r: &mut Reader<'_>) -> Result { + Ok(Self { + config_id: u8::read(r)?, + kem_id: HpkeKem::read(r)?, + public_key: SizedPayload::read(r)?.into_owned(), + symmetric_cipher_suites: Vec::::read(r)?, + }) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum EchConfigExtension { + Unknown(UnknownExtension), +} + +impl EchConfigExtension { + pub(crate) fn ext_type(&self) -> ExtensionType { + match self { + Self::Unknown(r) => r.typ, + } + } +} + +impl Codec<'_> for EchConfigExtension { + fn encode(&self, bytes: &mut Vec) { + self.ext_type().encode(bytes); + + let nested = LengthPrefixedBuffer::new(ListLength::U16, bytes); + match self { + Self::Unknown(r) => r.encode(nested.buf), + } + } + + fn read(r: &mut Reader<'_>) -> Result { + let typ = ExtensionType::read(r)?; + let len = u16::read(r)? as usize; + let mut sub = r.sub(len)?; + + #[expect(clippy::match_single_binding)] // Future-proofing. + let ext = match typ { + _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), + }; + + sub.expect_empty("EchConfigExtension") + .map(|_| ext) + } +} + +impl TlsListElement for EchConfigExtension { + const SIZE_LEN: ListLength = ListLength::U16; +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct UnknownExtension { + pub(crate) typ: ExtensionType, + pub(crate) payload: Payload<'static>, +} + +impl UnknownExtension { + fn encode(&self, bytes: &mut Vec) { + self.payload.encode(bytes); + } + + fn read(typ: ExtensionType, r: &mut Reader<'_>) -> Self { + let payload = Payload::read(r).into_owned(); + Self { typ, payload } + } +} + +static ZERO_RANDOM: Random = Random([0u8; 32]); + +#[cfg(test)] +mod tests { + use alloc::vec; + + use super::*; + use crate::crypto::hpke::{HpkeAead, HpkeKdf}; + + #[test] + fn test_ech_config_dupe_exts() { + let unknown_ext = EchConfigExtension::Unknown(UnknownExtension { + typ: ExtensionType(0x42), + payload: Payload::new(vec![0x42]), + }); + let mut config = config_template(); + config + .extensions + .push(unknown_ext.clone()); + config.extensions.push(unknown_ext); + + assert!(config.has_duplicate_extension()); + assert!(!config.has_unknown_mandatory_extension()); + } + + #[test] + fn test_ech_config_mandatory_exts() { + let mandatory_unknown_ext = EchConfigExtension::Unknown(UnknownExtension { + typ: ExtensionType(0x42 | 0x8000), // Note: high bit set. + payload: Payload::new(vec![0x42]), + }); + let mut config = config_template(); + config + .extensions + .push(mandatory_unknown_ext); + + assert!(!config.has_duplicate_extension()); + assert!(config.has_unknown_mandatory_extension()); + } + + fn config_template() -> EchConfigContents { + EchConfigContents { + key_config: HpkeKeyConfig { + config_id: 0, + kem_id: HpkeKem::DHKEM_P256_HKDF_SHA256, + public_key: SizedPayload::from(b"xxx".to_vec()), + symmetric_cipher_suites: vec![HpkeSymmetricCipherSuite { + kdf_id: HpkeKdf::HKDF_SHA256, + aead_id: HpkeAead::AES_128_GCM, + }], + }, + maximum_name_length: 0, + public_name: DnsName::try_from("example.com").unwrap(), + extensions: vec![], + } + } +} diff --git a/rustls/src/polyfill.rs b/rustls/src/polyfill.rs deleted file mode 100644 index 82571a2244a..00000000000 --- a/rustls/src/polyfill.rs +++ /dev/null @@ -1,9 +0,0 @@ -/// Non-panicking `let (nonce, ciphertext) = ciphertext.split_at(...)`. -// TODO(XXX): remove once MSRV reaches 1.80 -#[allow(dead_code)] // Complicated conditional compilation guards elided -pub(crate) fn try_split_at(slice: &[u8], mid: usize) -> Option<(&[u8], &[u8])> { - match mid > slice.len() { - true => None, - false => Some(slice.split_at(mid)), - } -} diff --git a/rustls/src/quic.rs b/rustls/src/quic.rs index adfe168669f..fef63e0b913 100644 --- a/rustls/src/quic.rs +++ b/rustls/src/quic.rs @@ -1,462 +1,470 @@ use alloc::boxed::Box; use alloc::collections::VecDeque; use alloc::vec::Vec; -#[cfg(feature = "std")] -use core::fmt::Debug; +use core::fmt::{self, Debug}; +use core::ops::{Deref, DerefMut}; -/// This module contains optional APIs for implementing QUIC TLS. -use crate::common_state::Side; -use crate::crypto::cipher::{AeadKey, Iv}; +use pki_types::{DnsName, FipsStatus, ServerName}; + +use crate::client::{ClientConfig, ClientSide}; +pub use crate::common_state::Side; +use crate::common_state::{CommonState, ConnectionOutputs, Protocol}; +use crate::conn::{ConnectionCore, KeyingMaterialExporter, SideData}; +use crate::crypto::cipher::{AeadKey, EncodedMessage, Iv, Payload}; use crate::crypto::tls13::{Hkdf, HkdfExpander, OkmBlock}; -use crate::enums::AlertDescription; -use crate::error::Error; +use crate::enums::{ApplicationProtocol, ContentType, ProtocolVersion}; +use crate::error::{ApiMisuse, Error}; +use crate::msgs::{ + ClientExtensionsInput, Message, MessagePayload, ServerExtensionsInput, TransportParameters, + VecInput, +}; +use crate::server::{ServerConfig, ServerSide}; +use crate::suites::SupportedCipherSuite; +use crate::sync::Arc; +use crate::tls13::Tls13CipherSuite; use crate::tls13::key_schedule::{ hkdf_expand_label, hkdf_expand_label_aead_key, hkdf_expand_label_block, }; -use crate::tls13::Tls13CipherSuite; -#[cfg(feature = "std")] -mod connection { - use alloc::sync::Arc; - use alloc::vec; - use alloc::vec::Vec; - use core::fmt::{self, Debug}; - use core::ops::{Deref, DerefMut}; - - use pki_types::ServerName; - - use super::{DirectionalKeys, KeyChange, Version}; - use crate::client::{ClientConfig, ClientConnectionData}; - use crate::common_state::{CommonState, Protocol, DEFAULT_BUFFER_LIMIT}; - use crate::conn::{ConnectionCore, SideData}; - use crate::enums::{AlertDescription, ContentType, ProtocolVersion}; - use crate::error::Error; - use crate::msgs::deframer::buffers::{DeframerVecBuffer, Locator}; - use crate::msgs::handshake::{ClientExtension, ServerExtension}; - use crate::msgs::message::InboundPlainMessage; - use crate::server::{ServerConfig, ServerConnectionData}; - use crate::vecbuf::ChunkVecBuffer; - - /// A QUIC client or server connection. - #[derive(Debug)] - pub enum Connection { - /// A client connection - Client(ClientConnection), - /// A server connection - Server(ServerConnection), - } - - impl Connection { - /// Return the TLS-encoded transport parameters for the session's peer. - /// - /// See [`ConnectionCommon::quic_transport_parameters()`] for more details. - pub fn quic_transport_parameters(&self) -> Option<&[u8]> { - match self { - Self::Client(conn) => conn.quic_transport_parameters(), - Self::Server(conn) => conn.quic_transport_parameters(), - } - } +/// A QUIC client or server connection. +pub trait Connection: Debug + Deref { + /// Return the TLS-encoded transport parameters for the session's peer. + /// + /// While the transport parameters are technically available prior to the + /// completion of the handshake, they cannot be fully trusted until the + /// handshake completes, and reliance on them should be minimized. + /// However, any tampering with the parameters will cause the handshake + /// to fail. + fn quic_transport_parameters(&self) -> Option<&[u8]>; - /// Compute the keys for encrypting/decrypting 0-RTT packets, if available - pub fn zero_rtt_keys(&self) -> Option { - match self { - Self::Client(conn) => conn.zero_rtt_keys(), - Self::Server(conn) => conn.zero_rtt_keys(), - } - } + /// Compute the keys for encrypting/decrypting 0-RTT packets, if available + fn zero_rtt_keys(&self) -> Option; - /// Consume unencrypted TLS handshake data. - /// - /// Handshake data obtained from separate encryption levels should be supplied in separate calls. - pub fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> { - match self { - Self::Client(conn) => conn.read_hs(plaintext), - Self::Server(conn) => conn.read_hs(plaintext), - } - } + /// Consume unencrypted TLS handshake data. + /// + /// Handshake data obtained from separate encryption levels should be supplied in separate calls. + fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error>; - /// Emit unencrypted TLS handshake data. - /// - /// When this returns `Some(_)`, the new keys must be used for future handshake data. - pub fn write_hs(&mut self, buf: &mut Vec) -> Option { - match self { - Self::Client(conn) => conn.write_hs(buf), - Self::Server(conn) => conn.write_hs(buf), - } - } + /// Emit unencrypted TLS handshake data. + /// + /// When this returns `Some(_)`, the new keys must be used for future handshake data. + fn write_hs(&mut self, buf: &mut Vec) -> Option; - /// Emit the TLS description code of a fatal alert, if one has arisen. - /// - /// Check after `read_hs` returns `Err(_)`. - pub fn alert(&self) -> Option { - match self { - Self::Client(conn) => conn.alert(), - Self::Server(conn) => conn.alert(), - } - } + /// Returns true if the connection is currently performing the TLS handshake. + fn is_handshaking(&self) -> bool; +} - /// Derives key material from the agreed connection secrets. - /// - /// This function fills in `output` with `output.len()` bytes of key - /// material derived from the master session secret using `label` - /// and `context` for diversification. Ownership of the buffer is taken - /// by the function and returned via the Ok result to ensure no key - /// material leaks if the function fails. - /// - /// See RFC5705 for more details on what this does and is for. - /// - /// For TLS1.3 connections, this function does not use the - /// "early" exporter at any point. - /// - /// This function fails if called prior to the handshake completing; - /// check with [`CommonState::is_handshaking`] first. - #[inline] - pub fn export_keying_material>( - &self, - output: T, - label: &[u8], - context: Option<&[u8]>, - ) -> Result { - match self { - Self::Client(conn) => conn - .core - .export_keying_material(output, label, context), - Self::Server(conn) => conn - .core - .export_keying_material(output, label, context), - } - } - } +/// A QUIC client connection. +pub struct ClientConnection { + inner: ConnectionCommon, +} - impl Deref for Connection { - type Target = CommonState; +impl ClientConnection { + /// Make a new QUIC ClientConnection. + /// + /// This differs from `ClientConnection::new()` in that it takes an extra `params` argument, + /// which contains the TLS-encoded transport parameters to send. + pub fn new( + config: Arc, + quic_version: Version, + name: ServerName<'static>, + params: Vec, + ) -> Result { + Self::new_with_alpn( + config.clone(), + quic_version, + name, + params, + config.alpn_protocols.clone(), + ) + } - fn deref(&self) -> &Self::Target { - match self { - Self::Client(conn) => &conn.core.common_state, - Self::Server(conn) => &conn.core.common_state, - } + /// Make a new QUIC ClientConnection with custom ALPN protocols. + pub fn new_with_alpn( + config: Arc, + version: Version, + name: ServerName<'static>, + params: Vec, + alpn_protocols: Vec>, + ) -> Result { + let suites = &config.provider().tls13_cipher_suites; + if suites.is_empty() { + return Err(ApiMisuse::QuicRequiresTls13Support.into()); } - } - impl DerefMut for Connection { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - Self::Client(conn) => &mut conn.core.common_state, - Self::Server(conn) => &mut conn.core.common_state, - } + if !suites + .iter() + .any(|scs| scs.quic.is_some()) + { + return Err(ApiMisuse::NoQuicCompatibleCipherSuites.into()); } - } - /// A QUIC client connection. - pub struct ClientConnection { - inner: ConnectionCommon, - } - - impl ClientConnection { - /// Make a new QUIC ClientConnection. - /// - /// This differs from `ClientConnection::new()` in that it takes an extra `params` argument, - /// which contains the TLS-encoded transport parameters to send. - pub fn new( - config: Arc, - quic_version: Version, - name: ServerName<'static>, - params: Vec, - ) -> Result { - if !config.supports_version(ProtocolVersion::TLSv1_3) { - return Err(Error::General( - "TLS 1.3 support is required for QUIC".into(), - )); - } + let exts = ClientExtensionsInput { + transport_parameters: Some(match version { + Version::V1 | Version::V2 => TransportParameters::Quic(Payload::new(params)), + }), - if !config.supports_protocol(Protocol::Quic) { - return Err(Error::General( - "at least one ciphersuite must support QUIC".into(), - )); - } + ..ClientExtensionsInput::from_alpn(alpn_protocols) + }; - let ext = match quic_version { - Version::V1Draft => ClientExtension::TransportParametersDraft(params), - Version::V1 | Version::V2 => ClientExtension::TransportParameters(params), - }; + let mut quic = Quic { + version, + ..Quic::default() + }; - let mut inner = ConnectionCore::for_client(config, name, vec![ext], Protocol::Quic)?; - inner.common_state.quic.version = quic_version; - Ok(Self { - inner: inner.into(), - }) - } + let inner = ConnectionCore::for_client( + config, + name, + exts, + Some(&mut quic), + Protocol::Quic(version), + )?; + + Ok(Self { + inner: ConnectionCommon::new(inner, quic), + }) + } - /// Returns True if the server signalled it will process early data. - /// - /// If you sent early data and this returns false at the end of the - /// handshake then the server will not process the data. This - /// is not an error, but you may wish to resend the data. - pub fn is_early_data_accepted(&self) -> bool { - self.inner.core.is_early_data_accepted() - } + /// Returns True if the server signalled it will process early data. + /// + /// If you sent early data and this returns false at the end of the + /// handshake then the server will not process the data. This + /// is not an error, but you may wish to resend the data. + pub fn is_early_data_accepted(&self) -> bool { + self.inner.core.is_early_data_accepted() } - impl Deref for ClientConnection { - type Target = ConnectionCommon; + /// Returns the number of TLS1.3 tickets that have been received. + pub fn tls13_tickets_received(&self) -> u32 { + self.inner + .core + .common + .recv + .tls13_tickets_received + } - fn deref(&self) -> &Self::Target { - &self.inner - } + /// Returns an object that can derive key material from the agreed connection secrets. + /// + /// See [RFC5705][] for more details on what this is for. + /// + /// This function can be called at most once per connection. + /// + /// This function will error: + /// + /// - if called prior to the handshake completing; (check with + /// [`CommonState::is_handshaking`] first). + /// - if called more than once per connection. + /// + /// [RFC5705]: https://datatracker.ietf.org/doc/html/rfc5705 + pub fn exporter(&mut self) -> Result { + self.inner.core.exporter() } +} - impl DerefMut for ClientConnection { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } +impl Connection for ClientConnection { + fn quic_transport_parameters(&self) -> Option<&[u8]> { + self.inner.quic_transport_parameters() } - impl Debug for ClientConnection { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("quic::ClientConnection") - .finish() - } + fn zero_rtt_keys(&self) -> Option { + self.inner.zero_rtt_keys() } - impl From for Connection { - fn from(c: ClientConnection) -> Self { - Self::Client(c) - } + fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> { + self.inner.read_hs(plaintext) } - /// A QUIC server connection. - pub struct ServerConnection { - inner: ConnectionCommon, - } - - impl ServerConnection { - /// Make a new QUIC ServerConnection. - /// - /// This differs from `ServerConnection::new()` in that it takes an extra `params` argument, - /// which contains the TLS-encoded transport parameters to send. - pub fn new( - config: Arc, - quic_version: Version, - params: Vec, - ) -> Result { - if !config.supports_version(ProtocolVersion::TLSv1_3) { - return Err(Error::General( - "TLS 1.3 support is required for QUIC".into(), - )); - } + fn write_hs(&mut self, buf: &mut Vec) -> Option { + self.inner.write_hs(buf) + } - if !config.supports_protocol(Protocol::Quic) { - return Err(Error::General( - "at least one ciphersuite must support QUIC".into(), - )); - } + fn is_handshaking(&self) -> bool { + self.inner.is_handshaking() + } +} - if config.max_early_data_size != 0 && config.max_early_data_size != 0xffff_ffff { - return Err(Error::General( - "QUIC sessions must set a max early data of 0 or 2^32-1".into(), - )); - } +impl Deref for ClientConnection { + type Target = ConnectionOutputs; - let ext = match quic_version { - Version::V1Draft => ServerExtension::TransportParametersDraft(params), - Version::V1 | Version::V2 => ServerExtension::TransportParameters(params), - }; + fn deref(&self) -> &Self::Target { + &self.inner + } +} - let mut core = ConnectionCore::for_server(config, vec![ext])?; - core.common_state.protocol = Protocol::Quic; - core.common_state.quic.version = quic_version; - Ok(Self { inner: core.into() }) +impl Debug for ClientConnection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("quic::ClientConnection") + .finish_non_exhaustive() + } +} + +/// A QUIC server connection. +pub struct ServerConnection { + inner: ConnectionCommon, +} + +impl ServerConnection { + /// Make a new QUIC ServerConnection. + /// + /// This differs from `ServerConnection::new()` in that it takes an extra `params` argument, + /// which contains the TLS-encoded transport parameters to send. + pub fn new( + config: Arc, + version: Version, + params: Vec, + ) -> Result { + let suites = &config.provider.tls13_cipher_suites; + if suites.is_empty() { + return Err(ApiMisuse::QuicRequiresTls13Support.into()); } - /// Explicitly discard early data, notifying the client - /// - /// Useful if invariants encoded in `received_resumption_data()` cannot be respected. - /// - /// Must be called while `is_handshaking` is true. - pub fn reject_early_data(&mut self) { - self.inner.core.reject_early_data() + if !suites + .iter() + .any(|scs| scs.quic.is_some()) + { + return Err(ApiMisuse::NoQuicCompatibleCipherSuites.into()); } - /// Retrieves the server name, if any, used to select the certificate and - /// private key. - /// - /// This returns `None` until some time after the client's server name indication - /// (SNI) extension value is processed during the handshake. It will never be - /// `None` when the connection is ready to send or process application data, - /// unless the client does not support SNI. - /// - /// This is useful for application protocols that need to enforce that the - /// server name matches an application layer protocol hostname. For - /// example, HTTP/1.1 servers commonly expect the `Host:` header field of - /// every request on a connection to match the hostname in the SNI extension - /// when the client provides the SNI extension. - /// - /// The server name is also used to match sessions during session resumption. - pub fn server_name(&self) -> Option<&str> { - self.inner.core.get_sni_str() + if config.max_early_data_size != 0 && config.max_early_data_size != 0xffff_ffff { + return Err(ApiMisuse::QuicRestrictsMaxEarlyDataSize.into()); } + + let exts = ServerExtensionsInput { + transport_parameters: Some(match version { + Version::V1 | Version::V2 => TransportParameters::Quic(Payload::new(params)), + }), + }; + + let core = ConnectionCore::for_server(config, exts, Protocol::Quic(version))?; + let inner = ConnectionCommon::new( + core, + Quic { + version, + ..Quic::default() + }, + ); + Ok(Self { inner }) } - impl Deref for ServerConnection { - type Target = ConnectionCommon; + /// Retrieves the server name, if any, used to select the certificate and + /// private key. + /// + /// This returns `None` until some time after the client's server name indication + /// (SNI) extension value is processed during the handshake. It will never be + /// `None` when the connection is ready to send or process application data, + /// unless the client does not support SNI. + /// + /// This is useful for application protocols that need to enforce that the + /// server name matches an application layer protocol hostname. For + /// example, HTTP/1.1 servers commonly expect the `Host:` header field of + /// every request on a connection to match the hostname in the SNI extension + /// when the client provides the SNI extension. + /// + /// The server name is also used to match sessions during session resumption. + pub fn server_name(&self) -> Option<&DnsName<'_>> { + self.inner.core.side.server_name() + } - fn deref(&self) -> &Self::Target { - &self.inner + /// Set the resumption data to embed in future resumption tickets supplied to the client. + /// + /// Defaults to the empty byte string. Must be less than 2^15 bytes to allow room for other + /// data. Should be called while `is_handshaking` returns true to ensure all transmitted + /// resumption tickets are affected (otherwise an error will be returned). + /// + /// Integrity will be assured by rustls, but the data will be visible to the client. If secrecy + /// from the client is desired, encrypt the data separately. + pub fn set_resumption_data(&mut self, resumption_data: &[u8]) -> Result<(), Error> { + assert!(resumption_data.len() < 2usize.pow(15)); + match &mut self.inner.core.state { + Ok(st) => st.set_resumption_data(resumption_data), + Err(e) => Err(e.clone()), } } - impl DerefMut for ServerConnection { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } + /// Retrieves the resumption data supplied by the client, if any. + /// + /// Returns `Some` if and only if a valid resumption ticket has been received from the client. + pub fn received_resumption_data(&self) -> Option<&[u8]> { + self.inner + .core + .side + .received_resumption_data() } - impl Debug for ServerConnection { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("quic::ServerConnection") - .finish() - } + /// Returns an object that can derive key material from the agreed connection secrets. + /// + /// See [RFC5705][] for more details on what this is for. + /// + /// This function can be called at most once per connection. + /// + /// This function will error: + /// + /// - if called prior to the handshake completing; (check with + /// [`CommonState::is_handshaking`] first). + /// - if called more than once per connection. + /// + /// [RFC5705]: https://datatracker.ietf.org/doc/html/rfc5705 + pub fn exporter(&mut self) -> Result { + self.inner.core.exporter() } +} - impl From for Connection { - fn from(c: ServerConnection) -> Self { - Self::Server(c) - } +impl Connection for ServerConnection { + fn quic_transport_parameters(&self) -> Option<&[u8]> { + self.inner.quic_transport_parameters() } - /// A shared interface for QUIC connections. - pub struct ConnectionCommon { - core: ConnectionCore, - deframer_buffer: DeframerVecBuffer, - sendable_plaintext: ChunkVecBuffer, - } - - impl ConnectionCommon { - /// Return the TLS-encoded transport parameters for the session's peer. - /// - /// While the transport parameters are technically available prior to the - /// completion of the handshake, they cannot be fully trusted until the - /// handshake completes, and reliance on them should be minimized. - /// However, any tampering with the parameters will cause the handshake - /// to fail. - pub fn quic_transport_parameters(&self) -> Option<&[u8]> { - self.core - .common_state - .quic - .params - .as_ref() - .map(|v| v.as_ref()) - } + fn zero_rtt_keys(&self) -> Option { + self.inner.zero_rtt_keys() + } - /// Compute the keys for encrypting/decrypting 0-RTT packets, if available - pub fn zero_rtt_keys(&self) -> Option { - let suite = self - .core - .common_state - .suite - .and_then(|suite| suite.tls13())?; - Some(DirectionalKeys::new( - suite, - suite.quic?, - self.core - .common_state - .quic - .early_secret - .as_ref()?, - self.core.common_state.quic.version, - )) - } + fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> { + self.inner.read_hs(plaintext) + } - /// Consume unencrypted TLS handshake data. - /// - /// Handshake data obtained from separate encryption levels should be supplied in separate calls. - pub fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> { - let range = self.deframer_buffer.extend(plaintext); - - self.core.hs_deframer.input_message( - InboundPlainMessage { - typ: ContentType::Handshake, - version: ProtocolVersion::TLSv1_3, - payload: &self.deframer_buffer.filled()[range.clone()], - }, - &Locator::new(self.deframer_buffer.filled()), - range.end, - ); - - self.core - .hs_deframer - .coalesce(self.deframer_buffer.filled_mut())?; - - self.core - .process_new_packets(&mut self.deframer_buffer, &mut self.sendable_plaintext)?; - - Ok(()) - } + fn write_hs(&mut self, buf: &mut Vec) -> Option { + self.inner.write_hs(buf) + } - /// Emit unencrypted TLS handshake data. - /// - /// When this returns `Some(_)`, the new keys must be used for future handshake data. - pub fn write_hs(&mut self, buf: &mut Vec) -> Option { - self.core - .common_state - .quic - .write_hs(buf) - } + fn is_handshaking(&self) -> bool { + self.inner.is_handshaking() + } +} - /// Emit the TLS description code of a fatal alert, if one has arisen. - /// - /// Check after `read_hs` returns `Err(_)`. - pub fn alert(&self) -> Option { - self.core.common_state.quic.alert - } +impl Deref for ServerConnection { + type Target = ConnectionOutputs; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl Debug for ServerConnection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("quic::ServerConnection") + .finish_non_exhaustive() } +} - impl Deref for ConnectionCommon { - type Target = CommonState; +/// A shared interface for QUIC connections. +struct ConnectionCommon { + core: ConnectionCore, + deframer_buffer: VecInput, + quic: Quic, +} - fn deref(&self) -> &Self::Target { - &self.core.common_state +impl ConnectionCommon { + fn new(core: ConnectionCore, quic: Quic) -> Self { + Self { + core, + deframer_buffer: VecInput::default(), + quic, } } - impl DerefMut for ConnectionCommon { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.core.common_state - } + fn quic_transport_parameters(&self) -> Option<&[u8]> { + self.quic + .params + .as_ref() + .map(|v| v.as_ref()) } - impl From> for ConnectionCommon { - fn from(core: ConnectionCore) -> Self { - Self { - core, - deframer_buffer: DeframerVecBuffer::default(), - sendable_plaintext: ChunkVecBuffer::new(Some(DEFAULT_BUFFER_LIMIT)), - } - } + fn zero_rtt_keys(&self) -> Option { + let suite = self + .core + .common + .negotiated_cipher_suite() + .and_then(|suite| match suite { + SupportedCipherSuite::Tls13(suite) => Some(suite), + _ => None, + })?; + + Some(DirectionalKeys::new( + suite, + suite.quic?, + self.quic.early_secret.as_ref()?, + self.quic.version, + )) + } + + fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> { + let range = self.deframer_buffer.extend(plaintext); + + let deframer = &mut self.core.common.recv.deframer; + deframer.add_processed(range.len()); + deframer.input_message( + EncodedMessage { + typ: ContentType::Handshake, + version: ProtocolVersion::TLSv1_3, + payload: &self.deframer_buffer.filled()[range.start..range.end], + }, + range, + ); + + self.core + .common + .recv + .deframer + .coalesce(self.deframer_buffer.filled_mut())?; + + self.core + .process_new_packets(&mut self.deframer_buffer, Some(&mut self.quic))?; + + Ok(()) + } + + fn write_hs(&mut self, buf: &mut Vec) -> Option { + self.quic.write_hs(buf) } } -#[cfg(feature = "std")] -pub use connection::{ClientConnection, Connection, ConnectionCommon, ServerConnection}; +impl Deref for ConnectionCommon { + type Target = CommonState; + + fn deref(&self) -> &Self::Target { + &self.core.common + } +} + +impl DerefMut for ConnectionCommon { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.core.common + } +} #[derive(Default)] pub(crate) struct Quic { + pub(crate) version: Version, /// QUIC transport parameters received from the peer during the handshake pub(crate) params: Option>, - pub(crate) alert: Option, pub(crate) hs_queue: VecDeque<(bool, Vec)>, pub(crate) early_secret: Option, pub(crate) hs_secrets: Option, pub(crate) traffic_secrets: Option, /// Whether keys derived from traffic_secrets have been passed to the QUIC implementation - #[cfg(feature = "std")] pub(crate) returned_traffic_keys: bool, - pub(crate) version: Version, } -#[cfg(feature = "std")] impl Quic { + pub(crate) fn send_msg(&mut self, m: Message<'_>, must_encrypt: bool) { + if let MessagePayload::Alert(_) = m.payload { + // alerts are sent out-of-band in QUIC mode + return; + } + + debug_assert!( + matches!( + m.payload, + MessagePayload::Handshake { .. } | MessagePayload::HandshakeFlight(_) + ), + "QUIC uses TLS for the cryptographic handshake only" + ); + let mut bytes = Vec::new(); + m.payload.encode(&mut bytes); + self.hs_queue + .push_back((must_encrypt, bytes)); + } + pub(crate) fn write_hs(&mut self, buf: &mut Vec) -> Option { while let Some((_, msg)) = self.hs_queue.pop_front() { buf.extend_from_slice(&msg); @@ -490,6 +498,82 @@ impl Quic { } } +impl QuicOutput for Quic { + fn transport_parameters(&mut self, params: Vec) { + self.params = Some(params); + } + + fn early_secret(&mut self, secret: Option) { + self.early_secret = secret; + } + + fn handshake_secrets( + &mut self, + client_secret: OkmBlock, + server_secret: OkmBlock, + suite: &'static Tls13CipherSuite, + quic: &'static dyn Algorithm, + side: Side, + ) { + self.hs_secrets = Some(Secrets::new( + client_secret, + server_secret, + suite, + quic, + side, + self.version, + )); + } + + fn traffic_secrets( + &mut self, + client_secret: OkmBlock, + server_secret: OkmBlock, + suite: &'static Tls13CipherSuite, + quic: &'static dyn Algorithm, + side: Side, + ) { + self.traffic_secrets = Some(Secrets::new( + client_secret, + server_secret, + suite, + quic, + side, + self.version, + )); + } + + fn send_msg(&mut self, m: Message<'_>, must_encrypt: bool) { + self.send_msg(m, must_encrypt); + } +} + +pub(crate) trait QuicOutput { + fn transport_parameters(&mut self, params: Vec); + + fn early_secret(&mut self, secret: Option); + + fn handshake_secrets( + &mut self, + client_secret: OkmBlock, + server_secret: OkmBlock, + suite: &'static Tls13CipherSuite, + quic: &'static dyn Algorithm, + side: Side, + ); + + fn traffic_secrets( + &mut self, + client_secret: OkmBlock, + server_secret: OkmBlock, + suite: &'static Tls13CipherSuite, + quic: &'static dyn Algorithm, + side: Side, + ); + + fn send_msg(&mut self, m: Message<'_>, must_encrypt: bool); +} + /// Secrets used to encrypt/decrypt traffic #[derive(Clone)] pub struct Secrets { @@ -558,6 +642,7 @@ impl Secrets { } /// Keys used to communicate in a single direction +#[expect(clippy::exhaustive_structs)] pub struct DirectionalKeys { /// Encrypts or decrypts a packet's headers pub header: Box, @@ -619,8 +704,8 @@ pub trait Algorithm: Send + Sync { fn aead_key_len(&self) -> usize; /// Whether this algorithm is FIPS-approved. - fn fips(&self) -> bool { - false + fn fips(&self) -> FipsStatus { + FipsStatus::Unvalidated } } @@ -689,30 +774,38 @@ pub trait HeaderProtectionKey: Send + Sync { pub trait PacketKey: Send + Sync { /// Encrypt a QUIC packet /// - /// Takes a `packet_number`, used to derive the nonce; the packet `header`, which is used as - /// the additional authenticated data; and the `payload`. The authentication tag is returned if - /// encryption succeeds. + /// Takes a `packet_number` and optional `path_id`, used to derive the nonce; the packet + /// `header`, which is used as the additional authenticated data; and the `payload`. The + /// authentication tag is returned if encryption succeeds. /// /// Fails if and only if the payload is longer than allowed by the cipher suite's AEAD algorithm. + /// + /// When provided, the `path_id` is used for multipath encryption as described in + /// . fn encrypt_in_place( &self, packet_number: u64, header: &[u8], payload: &mut [u8], + path_id: Option, ) -> Result; /// Decrypt a QUIC packet /// - /// Takes the packet `header`, which is used as the additional authenticated data, and the - /// `payload`, which includes the authentication tag. + /// Takes a `packet_number` and optional `path_id`, used to derive the nonce; the packet + /// `header`, which is used as the additional authenticated data, and the `payload`, which + /// includes the authentication tag. /// - /// If the return value is `Ok`, the decrypted payload can be found in `payload`, up to the - /// length found in the return value. + /// On success, returns the slice of `payload` containing the decrypted data. + /// + /// When provided, the `path_id` is used for multipath encryption as described in + /// . fn decrypt_in_place<'a>( &self, packet_number: u64, header: &[u8], payload: &'a mut [u8], + path_id: Option, ) -> Result<&'a [u8], Error>; /// Tag length for the underlying AEAD algorithm @@ -742,6 +835,7 @@ pub trait PacketKey: Send + Sync { } /// Packet protection keys for bidirectional 1-RTT communication +#[expect(clippy::exhaustive_structs)] pub struct PacketKeySet { /// Encrypts outgoing packets pub local: Box, @@ -760,14 +854,16 @@ impl PacketKeySet { } } -pub(crate) struct KeyBuilder<'a> { +/// Helper for building QUIC packet and header protection keys +pub struct KeyBuilder<'a> { expander: Box, version: Version, alg: &'a dyn Algorithm, } impl<'a> KeyBuilder<'a> { - pub(crate) fn new( + /// Create a new KeyBuilder + pub fn new( secret: &OkmBlock, version: Version, alg: &'a dyn Algorithm, @@ -781,7 +877,7 @@ impl<'a> KeyBuilder<'a> { } /// Derive packet keys - pub(crate) fn packet_key(&self) -> Box { + pub fn packet_key(&self) -> Box { let aead_key_len = self.alg.aead_key_len(); let packet_key = hkdf_expand_label_aead_key( self.expander.as_ref(), @@ -797,7 +893,7 @@ impl<'a> KeyBuilder<'a> { } /// Derive header protection keys - pub(crate) fn header_protection_key(&self) -> Box { + pub fn header_protection_key(&self) -> Box { let header_key = hkdf_expand_label_aead_key( self.expander.as_ref(), self.alg.aead_key_len(), @@ -810,6 +906,7 @@ impl<'a> KeyBuilder<'a> { } /// Produces QUIC initial keys from a TLS 1.3 ciphersuite and a QUIC key generation algorithm. +#[non_exhaustive] #[derive(Clone, Copy)] pub struct Suite { /// The TLS 1.3 ciphersuite used to derive keys. @@ -832,6 +929,7 @@ impl Suite { } /// Complete set of keys used to communicate with the peer +#[expect(clippy::exhaustive_structs)] pub struct Keys { /// Encrypts outgoing packets pub local: DirectionalKeys, @@ -856,12 +954,12 @@ impl Keys { .extract_from_secret(Some(salt), client_dst_connection_id); let secrets = Secrets { - version, client: hkdf_expand_label_block(hs_secret.as_ref(), CLIENT_LABEL, &[]), server: hkdf_expand_label_block(hs_secret.as_ref(), SERVER_LABEL, &[]), suite, quic, side, + version, }; Self::new(&secrets) } @@ -880,14 +978,15 @@ impl Keys { /// QUIC uses 4 different sets of keys (and progressive key updates for long-running connections): /// /// * Initial: these can be created from [`Keys::initial()`] -/// * 0-RTT keys: can be retrieved from [`ConnectionCommon::zero_rtt_keys()`] -/// * Handshake: these are returned from [`ConnectionCommon::write_hs()`] after `ClientHello` and +/// * 0-RTT keys: can be retrieved from [`Connection::zero_rtt_keys()`] +/// * Handshake: these are returned from [`Connection::write_hs()`] after `ClientHello` and /// `ServerHello` messages have been exchanged -/// * 1-RTT keys: these are returned from [`ConnectionCommon::write_hs()`] after the handshake is done +/// * 1-RTT keys: these are returned from [`Connection::write_hs()`] after the handshake is done /// /// Once the 1-RTT keys have been exchanged, either side may initiate a key update. Progressive /// update keys can be obtained from the [`Secrets`] returned in [`KeyChange::OneRtt`]. Note that /// only packet keys are updated by key updates; header protection keys remain the same. +#[expect(clippy::exhaustive_enums)] pub enum KeyChange { /// Keys for the handshake space Handshake { @@ -907,11 +1006,10 @@ pub enum KeyChange { /// /// Governs version-specific behavior in the TLS layer #[non_exhaustive] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum Version { - /// Draft versions 29, 30, 31 and 32 - V1Draft, /// First stable RFC + #[default] V1, /// Anti-ossification variant of V1 V2, @@ -920,18 +1018,13 @@ pub enum Version { impl Version { fn initial_salt(self) -> &'static [u8; 20] { match self { - Self::V1Draft => &[ - // https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#section-5.2 - 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, - 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99, - ], Self::V1 => &[ // https://www.rfc-editor.org/rfc/rfc9001.html#name-initial-secrets 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a, ], Self::V2 => &[ - // https://www.ietf.org/archive/id/draft-ietf-quic-v2-10.html#name-initial-salt-2 + // https://tools.ietf.org/html/rfc9369.html#name-initial-salt 0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9, ], @@ -941,7 +1034,7 @@ impl Version { /// Key derivation label for packet keys. pub(crate) fn packet_key_label(&self) -> &'static [u8] { match self { - Self::V1Draft | Self::V1 => b"quic key", + Self::V1 => b"quic key", Self::V2 => b"quicv2 key", } } @@ -949,7 +1042,7 @@ impl Version { /// Key derivation label for packet "IV"s. pub(crate) fn packet_iv_label(&self) -> &'static [u8] { match self { - Self::V1Draft | Self::V1 => b"quic iv", + Self::V1 => b"quic iv", Self::V2 => b"quicv2 iv", } } @@ -957,31 +1050,92 @@ impl Version { /// Key derivation for header keys. pub(crate) fn header_key_label(&self) -> &'static [u8] { match self { - Self::V1Draft | Self::V1 => b"quic hp", + Self::V1 => b"quic hp", Self::V2 => b"quicv2 hp", } } fn key_update_label(&self) -> &'static [u8] { match self { - Self::V1Draft | Self::V1 => b"quic ku", + Self::V1 => b"quic ku", Self::V2 => b"quicv2 ku", } } } -impl Default for Version { - fn default() -> Self { - Self::V1 +#[cfg(all(test, any(target_arch = "aarch64", target_arch = "x86_64")))] +mod tests { + use super::*; + use crate::crypto::TLS13_TEST_SUITE; + use crate::crypto::tls13::OkmBlock; + use crate::quic::{HeaderProtectionKey, Secrets, Side, Version}; + + #[test] + fn key_update_test_vector() { + fn equal_okm(x: &OkmBlock, y: &OkmBlock) -> bool { + x.as_ref() == y.as_ref() + } + + let mut secrets = Secrets { + // Constant dummy values for reproducibility + client: OkmBlock::new( + &[ + 0xb8, 0x76, 0x77, 0x08, 0xf8, 0x77, 0x23, 0x58, 0xa6, 0xea, 0x9f, 0xc4, 0x3e, + 0x4a, 0xdd, 0x2c, 0x96, 0x1b, 0x3f, 0x52, 0x87, 0xa6, 0xd1, 0x46, 0x7e, 0xe0, + 0xae, 0xab, 0x33, 0x72, 0x4d, 0xbf, + ][..], + ), + server: OkmBlock::new( + &[ + 0x42, 0xdc, 0x97, 0x21, 0x40, 0xe0, 0xf2, 0xe3, 0x98, 0x45, 0xb7, 0x67, 0x61, + 0x34, 0x39, 0xdc, 0x67, 0x58, 0xca, 0x43, 0x25, 0x9b, 0x87, 0x85, 0x06, 0x82, + 0x4e, 0xb1, 0xe4, 0x38, 0xd8, 0x55, + ][..], + ), + suite: TLS13_TEST_SUITE, + quic: &FakeAlgorithm, + side: Side::Client, + version: Version::V1, + }; + secrets.update(); + + assert!(equal_okm( + &secrets.client, + &OkmBlock::new( + &[ + 0x42, 0xca, 0xc8, 0xc9, 0x1c, 0xd5, 0xeb, 0x40, 0x68, 0x2e, 0x43, 0x2e, 0xdf, + 0x2d, 0x2b, 0xe9, 0xf4, 0x1a, 0x52, 0xca, 0x6b, 0x22, 0xd8, 0xe6, 0xcd, 0xb1, + 0xe8, 0xac, 0xa9, 0x6, 0x1f, 0xce + ][..] + ) + )); + assert!(equal_okm( + &secrets.server, + &OkmBlock::new( + &[ + 0xeb, 0x7f, 0x5e, 0x2a, 0x12, 0x3f, 0x40, 0x7d, 0xb4, 0x99, 0xe3, 0x61, 0xca, + 0xe5, 0x90, 0xd4, 0xd9, 0x92, 0xe1, 0x4b, 0x7a, 0xce, 0x3, 0xc2, 0x44, 0xe0, + 0x42, 0x21, 0x15, 0xb6, 0xd3, 0x8a + ][..] + ) + )); } -} -#[cfg(test)] -mod tests { - use std::prelude::v1::*; + struct FakeAlgorithm; - use super::PacketKey; - use crate::quic::HeaderProtectionKey; + impl Algorithm for FakeAlgorithm { + fn packet_key(&self, _key: AeadKey, _iv: Iv) -> Box { + unimplemented!() + } + + fn header_protection_key(&self, _key: AeadKey) -> Box { + unimplemented!() + } + + fn aead_key_len(&self) -> usize { + 16 + } + } #[test] fn auto_traits() { diff --git a/rustls/src/rand.rs b/rustls/src/rand.rs deleted file mode 100644 index 23593863d38..00000000000 --- a/rustls/src/rand.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! The single place where we generate random material for our own use. - -use alloc::vec; -use alloc::vec::Vec; - -use crate::crypto::SecureRandom; - -/// Make a [`Vec`] of the given size containing random material. -pub(crate) fn random_vec( - secure_random: &dyn SecureRandom, - len: usize, -) -> Result, GetRandomFailed> { - let mut v = vec![0; len]; - secure_random.fill(&mut v)?; - Ok(v) -} - -/// Return a uniformly random [`u32`]. -pub(crate) fn random_u32(secure_random: &dyn SecureRandom) -> Result { - let mut buf = [0u8; 4]; - secure_random.fill(&mut buf)?; - Ok(u32::from_be_bytes(buf)) -} - -/// Return a uniformly random [`u16`]. -pub(crate) fn random_u16(secure_random: &dyn SecureRandom) -> Result { - let mut buf = [0u8; 2]; - secure_random.fill(&mut buf)?; - Ok(u16::from_be_bytes(buf)) -} - -/// Random material generation failed. -#[derive(Debug)] -pub struct GetRandomFailed; diff --git a/rustls/src/server/builder.rs b/rustls/src/server/builder.rs deleted file mode 100644 index 196dd3fcd06..00000000000 --- a/rustls/src/server/builder.rs +++ /dev/null @@ -1,149 +0,0 @@ -use alloc::sync::Arc; -use alloc::vec::Vec; -use core::marker::PhantomData; - -use pki_types::{CertificateDer, PrivateKeyDer}; - -use crate::builder::{ConfigBuilder, WantsVerifier}; -use crate::error::Error; -use crate::server::{handy, ResolvesServerCert, ServerConfig}; -use crate::sign::CertifiedKey; -use crate::verify::{ClientCertVerifier, NoClientAuth}; -use crate::{compress, versions, InconsistentKeys, NoKeyLog}; - -impl ConfigBuilder { - /// Choose how to verify client certificates. - pub fn with_client_cert_verifier( - self, - client_cert_verifier: Arc, - ) -> ConfigBuilder { - ConfigBuilder { - state: WantsServerCert { - versions: self.state.versions, - verifier: client_cert_verifier, - }, - provider: self.provider, - time_provider: self.time_provider, - side: PhantomData, - } - } - - /// Disable client authentication. - pub fn with_no_client_auth(self) -> ConfigBuilder { - self.with_client_cert_verifier(Arc::new(NoClientAuth)) - } -} - -/// A config builder state where the caller must supply how to provide a server certificate to -/// the connecting peer. -/// -/// For more information, see the [`ConfigBuilder`] documentation. -#[derive(Clone, Debug)] -pub struct WantsServerCert { - versions: versions::EnabledVersions, - verifier: Arc, -} - -impl ConfigBuilder { - /// Sets a single certificate chain and matching private key. This - /// certificate and key is used for all subsequent connections, - /// irrespective of things like SNI hostname. - /// - /// Note that the end-entity certificate must have the - /// [Subject Alternative Name](https://tools.ietf.org/html/rfc6125#section-4.1) - /// extension to describe, e.g., the valid DNS name. The `commonName` field is - /// disregarded. - /// - /// `cert_chain` is a vector of DER-encoded certificates. - /// `key_der` is a DER-encoded private key as PKCS#1, PKCS#8, or SEC1. The - /// `aws-lc-rs` and `ring` [`CryptoProvider`][crate::CryptoProvider]s support - /// all three encodings, but other `CryptoProviders` may not. - /// - /// This function fails if `key_der` is invalid, or if the - /// `SubjectPublicKeyInfo` from the private key does not match the public - /// key for the end-entity certificate from the `cert_chain`. - pub fn with_single_cert( - self, - cert_chain: Vec>, - key_der: PrivateKeyDer<'static>, - ) -> Result { - let private_key = self - .provider - .key_provider - .load_private_key(key_der)?; - - let certified_key = CertifiedKey::new(cert_chain, private_key); - match certified_key.keys_match() { - // Don't treat unknown consistency as an error - Ok(()) | Err(Error::InconsistentKeys(InconsistentKeys::Unknown)) => (), - Err(err) => return Err(err), - } - - let resolver = handy::AlwaysResolvesChain::new(certified_key); - Ok(self.with_cert_resolver(Arc::new(resolver))) - } - - /// Sets a single certificate chain, matching private key and optional OCSP - /// response. This certificate and key is used for all - /// subsequent connections, irrespective of things like SNI hostname. - /// - /// `cert_chain` is a vector of DER-encoded certificates. - /// `key_der` is a DER-encoded private key as PKCS#1, PKCS#8, or SEC1. The - /// `aws-lc-rs` and `ring` [`CryptoProvider`][crate::CryptoProvider]s support - /// all three encodings, but other `CryptoProviders` may not. - /// `ocsp` is a DER-encoded OCSP response. Ignored if zero length. - /// - /// This function fails if `key_der` is invalid, or if the - /// `SubjectPublicKeyInfo` from the private key does not match the public - /// key for the end-entity certificate from the `cert_chain`. - pub fn with_single_cert_with_ocsp( - self, - cert_chain: Vec>, - key_der: PrivateKeyDer<'static>, - ocsp: Vec, - ) -> Result { - let private_key = self - .provider - .key_provider - .load_private_key(key_der)?; - - let certified_key = CertifiedKey::new(cert_chain, private_key); - match certified_key.keys_match() { - // Don't treat unknown consistency as an error - Ok(()) | Err(Error::InconsistentKeys(InconsistentKeys::Unknown)) => (), - Err(err) => return Err(err), - } - - let resolver = handy::AlwaysResolvesChain::new_with_extras(certified_key, ocsp); - Ok(self.with_cert_resolver(Arc::new(resolver))) - } - - /// Sets a custom [`ResolvesServerCert`]. - pub fn with_cert_resolver(self, cert_resolver: Arc) -> ServerConfig { - ServerConfig { - provider: self.provider, - verifier: self.state.verifier, - cert_resolver, - ignore_client_order: false, - max_fragment_size: None, - #[cfg(feature = "std")] - session_storage: handy::ServerSessionMemoryCache::new(256), - #[cfg(not(feature = "std"))] - session_storage: Arc::new(handy::NoServerSessionStorage {}), - ticketer: Arc::new(handy::NeverProducesTickets {}), - alpn_protocols: Vec::new(), - versions: self.state.versions, - key_log: Arc::new(NoKeyLog {}), - enable_secret_extraction: false, - max_early_data_size: 0, - send_half_rtt_data: false, - send_tls13_tickets: 2, - #[cfg(feature = "tls12")] - require_ems: cfg!(feature = "fips"), - time_provider: self.time_provider, - cert_compressors: compress::default_cert_compressors().to_vec(), - cert_compression_cache: Arc::new(compress::CompressionCache::default()), - cert_decompressors: compress::default_cert_decompressors().to_vec(), - } - } -} diff --git a/rustls/src/server/common.rs b/rustls/src/server/common.rs deleted file mode 100644 index 8310998a0cf..00000000000 --- a/rustls/src/server/common.rs +++ /dev/null @@ -1,35 +0,0 @@ -use pki_types::CertificateDer; - -use crate::sign; - -/// ActiveCertifiedKey wraps [`sign::CertifiedKey`] and tracks OSCP state in a single handshake. -pub(super) struct ActiveCertifiedKey<'a> { - key: &'a sign::CertifiedKey, - ocsp: Option<&'a [u8]>, -} - -impl ActiveCertifiedKey<'_> { - pub(super) fn from_certified_key(key: &sign::CertifiedKey) -> ActiveCertifiedKey<'_> { - ActiveCertifiedKey { - key, - ocsp: key.ocsp.as_deref(), - } - } - - /// Get the certificate chain - #[inline] - pub(super) fn get_cert(&self) -> &[CertificateDer<'static>] { - &self.key.cert - } - - /// Get the signing key - #[inline] - pub(super) fn get_key(&self) -> &dyn sign::SigningKey { - &*self.key.key - } - - #[inline] - pub(super) fn get_ocsp(&self) -> Option<&[u8]> { - self.ocsp - } -} diff --git a/rustls/src/server/config.rs b/rustls/src/server/config.rs new file mode 100644 index 00000000000..266ed838be3 --- /dev/null +++ b/rustls/src/server/config.rs @@ -0,0 +1,827 @@ +use alloc::borrow::Cow; +use alloc::vec::Vec; +use core::fmt::Debug; +use core::marker::PhantomData; + +#[cfg(feature = "webpki")] +use pki_types::PrivateKeyDer; +use pki_types::{DnsName, FipsStatus, UnixTime}; + +use super::hs::ClientHelloInput; +use super::{ServerSessionKey, handy}; +use crate::builder::{ConfigBuilder, WantsVerifier}; +#[cfg(doc)] +use crate::crypto; +use crate::crypto::kx::NamedGroup; +use crate::crypto::{ + CipherSuite, CryptoProvider, SelectedCredential, SignatureScheme, TicketProducer, +}; +#[cfg(feature = "webpki")] +use crate::crypto::{Credentials, Identity, SingleCredential}; +use crate::enums::{ApplicationProtocol, CertificateType, ProtocolVersion}; +use crate::error::{Error, PeerMisbehaved}; +use crate::msgs::ServerNamePayload; +use crate::suites::Suite; +use crate::sync::Arc; +use crate::time_provider::{DefaultTimeProvider, TimeProvider}; +use crate::verify::{ClientVerifier, DistinguishedName, NoClientAuth}; +use crate::{KeyLog, NoKeyLog, Tls12CipherSuite, Tls13CipherSuite, compress}; + +/// Common configuration for a set of server sessions. +/// +/// Making one of these is cheap, though one of the inputs may be expensive: gathering trust roots +/// from the operating system to add to the [`RootCertStore`] passed to a `ClientVerifier` +/// builder may take on the order of a few hundred milliseconds. +/// +/// These must be created via the [`ServerConfig::builder()`] or [`ServerConfig::builder()`] +/// function. +/// +/// # Defaults +/// +/// * [`ServerConfig::max_fragment_size`]: the default is `None` (meaning 16kB). +/// * [`ServerConfig::session_storage`]: if the `std` feature is enabled, the default stores 256 +/// sessions in memory. If the `std` feature is not enabled, the default is to not store any +/// sessions. In a no-std context, by enabling the `hashbrown` feature you may provide your +/// own `session_storage` using [`ServerSessionMemoryCache`] and a `crate::lock::MakeMutex` +/// implementation. +/// * [`ServerConfig::alpn_protocols`]: the default is empty -- no ALPN protocol is negotiated. +/// * [`ServerConfig::key_log`]: key material is not logged. +/// * [`ServerConfig::send_tls13_tickets`]: 2 tickets are sent. +/// * [`ServerConfig::cert_compressors`]: depends on the crate features, see [`compress::default_cert_compressors()`]. +/// * [`ServerConfig::cert_compression_cache`]: caches the most recently used 4 compressions +/// * [`ServerConfig::cert_decompressors`]: depends on the crate features, see [`compress::default_cert_decompressors()`]. +/// +/// # Sharing resumption storage between `ServerConfig`s +/// +/// In a program using many `ServerConfig`s it may improve resumption rates +/// (which has a significant impact on connection performance) if those +/// configs share [`ServerConfig::session_storage`] or [`ServerConfig::ticketer`]. +/// +/// However, caution is needed: other fields influence the security of a session +/// and resumption between them can be surprising. If sharing +/// [`ServerConfig::session_storage`] or [`ServerConfig::ticketer`] between two +/// `ServerConfig`s, you should also evaluate the following fields and ensure +/// they are equivalent: +/// +/// * `ServerConfig::verifier` -- client authentication requirements, +/// * [`ServerConfig::cert_resolver`] -- server identities. +/// +/// To illustrate, imagine two `ServerConfig`s `A` and `B`. `A` requires +/// client authentication, `B` does not. If `A` and `B` shared a resumption store, +/// it would be possible for a session originated by `B` (that is, an unauthenticated client) +/// to be inserted into the store, and then resumed by `A`. This would give a false +/// impression to the user of `A` that the client was authenticated. This is possible +/// whether the resumption is performed statefully (via [`ServerConfig::session_storage`]) +/// or statelessly (via [`ServerConfig::ticketer`]). +/// +/// _Unlike_ `ClientConfig`, rustls does not enforce any policy here. +/// +/// [`RootCertStore`]: crate::RootCertStore +/// [`ServerSessionMemoryCache`]: crate::server::handy::ServerSessionMemoryCache +#[derive(Clone, Debug)] +pub struct ServerConfig { + /// Source of randomness and other crypto. + pub(crate) provider: Arc, + + /// How to select a cipher suite to use for a TLS session. + pub cipher_suite_selector: &'static dyn CipherSuiteSelector, + + /// The maximum size of plaintext input to be emitted in a single TLS record. + /// A value of None is equivalent to the [TLS maximum] of 16 kB. + /// + /// rustls enforces an arbitrary minimum of 32 bytes for this field. + /// Out of range values are reported as errors from [ServerConnection::new]. + /// + /// Setting this value to a little less than the TCP MSS may improve latency + /// for stream-y workloads. + /// + /// [TLS maximum]: https://datatracker.ietf.org/doc/html/rfc8446#section-5.1 + /// [ServerConnection::new]: crate::server::ServerConnection::new + pub max_fragment_size: Option, + + /// How to store client sessions. + /// + /// See [ServerConfig#sharing-resumption-storage-between-serverconfigs] + /// for a warning related to this field. + pub session_storage: Arc, + + /// How to produce tickets. + /// + /// See [ServerConfig#sharing-resumption-storage-between-serverconfigs] + /// for a warning related to this field. + pub ticketer: Option>, + + /// How to choose a server cert and key. This is usually set by + /// [ConfigBuilder::with_single_cert] or [ConfigBuilder::with_server_credential_resolver]. + /// For async applications, see also [`Acceptor`][super::Acceptor]. + pub cert_resolver: Arc, + + /// Protocol names we support, most preferred first. + /// If empty we don't do ALPN at all. + pub alpn_protocols: Vec>, + + /// How to verify client certificates. + pub(super) verifier: Arc, + + /// How to output key material for debugging. The default + /// does nothing. + pub key_log: Arc, + + /// Allows traffic secrets to be extracted after the handshake, + /// e.g. for kTLS setup. + pub enable_secret_extraction: bool, + + /// Amount of early data to accept for sessions created by + /// this config. Specify 0 to disable early data. The + /// default is 0. + /// + /// Read the early data via + /// [`ServerConnection::early_data()`][super::ServerConnection::early_data()]. + /// + /// The units for this are _both_ plaintext bytes, _and_ ciphertext + /// bytes, depending on whether the server accepts a client's early_data + /// or not. It is therefore recommended to include some slop in + /// this value to account for the unknown amount of ciphertext + /// expansion in the latter case. + pub max_early_data_size: u32, + + /// Whether the server should send "0.5RTT" data. This means the server + /// sends data after its first flight of handshake messages, without + /// waiting for the client to complete the handshake. + /// + /// This can improve TTFB latency for either server-speaks-first protocols, + /// or client-speaks-first protocols when paired with "0RTT" data. This + /// comes at the cost of a subtle weakening of the normal handshake + /// integrity guarantees that TLS provides. Note that the initial + /// `ClientHello` is indirectly authenticated because it is included + /// in the transcript used to derive the keys used to encrypt the data. + /// + /// This only applies to TLS1.3 connections. TLS1.2 connections cannot + /// do this optimisation and this setting is ignored for them. It is + /// also ignored for TLS1.3 connections that even attempt client + /// authentication. + /// + /// This defaults to false. This means the first application data + /// sent by the server comes after receiving and validating the client's + /// handshake up to the `Finished` message. This is the safest option. + pub send_half_rtt_data: bool, + + /// How many TLS1.3 tickets to send immediately after a successful + /// handshake. + /// + /// Because TLS1.3 tickets are single-use, this allows + /// a client to perform multiple resumptions. + /// + /// The default is 2. + /// + /// If this is 0, no tickets are sent and clients will not be able to + /// do any resumption. + pub send_tls13_tickets: usize, + + /// If set to `true`, requires the client to support the extended + /// master secret extraction method defined in [RFC 7627]. + /// + /// The default is `true` if the configured [`CryptoProvider`] is FIPS-compliant, + /// false otherwise. + /// + /// It must be set to `true` to meet FIPS requirement mentioned in section + /// **D.Q Transition of the TLS 1.2 KDF to Support the Extended Master + /// Secret** from [FIPS 140-3 IG.pdf]. + /// + /// [RFC 7627]: https://datatracker.ietf.org/doc/html/rfc7627 + /// [FIPS 140-3 IG.pdf]: https://csrc.nist.gov/csrc/media/Projects/cryptographic-module-validation-program/documents/fips%20140-3/FIPS%20140-3%20IG.pdf + pub require_ems: bool, + + /// Provides the current system time + pub time_provider: Arc, + + /// How to compress the server's certificate chain. + /// + /// If a client supports this extension, and advertises support + /// for one of the compression algorithms included here, the + /// server certificate will be compressed according to [RFC8779]. + /// + /// This only applies to TLS1.3 connections. It is ignored for + /// TLS1.2 connections. + /// + /// [RFC8779]: https://datatracker.ietf.org/doc/rfc8879/ + pub cert_compressors: Vec<&'static dyn compress::CertCompressor>, + + /// Caching for compressed certificates. + /// + /// This is optional: [`compress::CompressionCache::Disabled`] gives + /// a cache that does no caching. + pub cert_compression_cache: Arc, + + /// How to decompress the clients's certificate chain. + /// + /// If this is non-empty, the [RFC8779] certificate compression + /// extension is offered when requesting client authentication, + /// and any compressed certificates are transparently decompressed + /// during the handshake. + /// + /// This only applies to TLS1.3 connections. It is ignored for + /// TLS1.2 connections. + /// + /// [RFC8779]: https://datatracker.ietf.org/doc/rfc8879/ + pub cert_decompressors: Vec<&'static dyn compress::CertDecompressor>, + + /// Policy for how an invalid Server Name Indication (SNI) value from a client is handled. + pub invalid_sni_policy: InvalidSniPolicy, +} + +impl ServerConfig { + /// Create a builder for a server configuration with a specific [`CryptoProvider`]. + /// + /// This will use the provider's configured ciphersuites. This implies which TLS + /// protocol versions are enabled. + /// + /// This function always succeeds. Any internal consistency problems with `provider` + /// are reported at the end of the builder process. + /// + /// For more information, see the [`ConfigBuilder`] documentation. + pub fn builder(provider: Arc) -> ConfigBuilder { + Self::builder_with_details(provider, Arc::new(DefaultTimeProvider)) + } + + /// Create a builder for a server configuration with no default implementation details. + /// + /// This API must be used by `no_std` users. + /// + /// You must provide a specific [`TimeProvider`]. + /// + /// You must provide a specific [`CryptoProvider`]. + /// + /// This will use the provider's configured ciphersuites. This implies which TLS + /// protocol versions are enabled. + /// + /// This function always succeeds. Any internal consistency problems with `provider` + /// are reported at the end of the builder process. + /// + /// For more information, see the [`ConfigBuilder`] documentation. + pub fn builder_with_details( + provider: Arc, + time_provider: Arc, + ) -> ConfigBuilder { + ConfigBuilder { + state: WantsVerifier { + client_ech_mode: None, + }, + provider, + time_provider, + side: PhantomData, + } + } + + /// Return the FIPS validation status for connections made with this configuration. + /// + /// This is different from [`CryptoProvider::fips()`]: [`CryptoProvider::fips()`] + /// is concerned only with cryptography, whereas this _also_ covers TLS-level + /// configuration that NIST recommends. + pub fn fips(&self) -> FipsStatus { + match self.require_ems { + true => self.provider.fips(), + false => FipsStatus::Unvalidated, + } + } + + /// Return the crypto provider used to construct this client configuration. + pub fn crypto_provider(&self) -> &Arc { + &self.provider + } + + pub(crate) fn supports_version(&self, v: ProtocolVersion) -> bool { + self.provider.supports_version(v) + } + + pub(super) fn current_time(&self) -> Result { + self.time_provider + .current_time() + .ok_or(Error::FailedToGetCurrentTime) + } +} + +/// A trait for the ability to store server session data. +/// +/// The keys and values are opaque. +/// +/// Inserted keys are randomly chosen by the library and have +/// no internal structure (in other words, you may rely on all +/// bits being uniformly random). Queried keys are untrusted data. +/// +/// Both the keys and values should be treated as +/// **highly sensitive data**, containing enough key material +/// to break all security of the corresponding sessions. +/// +/// Implementations can be lossy (in other words, forgetting +/// key/value pairs) without any negative security consequences. +/// +/// However, note that `take` **must** reliably delete a returned +/// value. If it does not, there may be security consequences. +/// +/// `put` and `take` are mutating operations; this isn't expressed +/// in the type system to allow implementations freedom in +/// how to achieve interior mutability. `Mutex` is a common +/// choice. +pub trait StoresServerSessions: Debug + Send + Sync { + /// Store session secrets encoded in `value` against `key`, + /// overwrites any existing value against `key`. Returns `true` + /// if the value was stored. + fn put(&self, key: ServerSessionKey<'_>, value: Vec) -> bool; + + /// Find a value with the given `key`. Return it, or None + /// if it doesn't exist. + fn get(&self, key: ServerSessionKey<'_>) -> Option>; + + /// Find a value with the given `key`. Return it and delete it; + /// or None if it doesn't exist. + fn take(&self, key: ServerSessionKey<'_>) -> Option>; + + /// Whether the store can cache another session. This is used to indicate to clients + /// whether their session can be resumed; the implementation is not required to remember + /// a session even if it returns `true` here. + fn can_cache(&self) -> bool; +} + +/// How to choose a certificate chain and signing key for use +/// in server authentication. +/// +/// This is suitable when selecting a certificate does not require +/// I/O or when the application is using blocking I/O anyhow. +/// +/// For applications that use async I/O and need to do I/O to choose +/// a certificate (for instance, fetching a certificate from a data store), +/// the [`Acceptor`][super::Acceptor] interface is more suitable. +pub trait ServerCredentialResolver: Debug + Send + Sync { + /// Choose a certificate chain and matching key given simplified ClientHello information. + /// + /// The `SelectedCredential` returned from this method contains an identity and a + /// one-time-use [`Signer`] wrapping the private key. This is usually obtained via a + /// [`Credentials`], on which an implementation can call [`Credentials::signer()`]. + /// An implementation can either store long-lived [`Credentials`] values, or instantiate + /// them as needed using one of its constructors. + /// + /// Yielding an `Error` will abort the handshake. Some relevant error variants: + /// + /// * [`PeerIncompatible::NoSignatureSchemesInCommon`] + /// * [`PeerIncompatible::NoServerNameProvided`] + /// * [`Error::NoSuitableCertificate`] + /// + /// [`Credentials`]: crate::crypto::Credentials + /// [`Credentials::signer()`]: crate::crypto::Credentials::signer + /// [`Signer`]: crate::crypto::Signer + /// [`PeerIncompatible::NoSignatureSchemesInCommon`]: crate::error::PeerIncompatible::NoSignatureSchemesInCommon + /// [`PeerIncompatible::NoServerNameProvided`]: crate::error::PeerIncompatible::NoServerNameProvided + fn resolve(&self, client_hello: &ClientHello<'_>) -> Result; + + /// Returns which [`CertificateType`]s this resolver supports. + /// + /// Returning an empty slice will result in an error. The default implementation signals + /// support for X.509 certificates. Implementations should return the same value every time. + /// + /// See [RFC 7250](https://tools.ietf.org/html/rfc7250) for more information. + fn supported_certificate_types(&self) -> &'static [CertificateType] { + &[CertificateType::X509] + } +} + +/// A struct representing the received Client Hello +#[derive(Debug)] +pub struct ClientHello<'a> { + pub(super) server_name: Option>>, + pub(super) signature_schemes: &'a [SignatureScheme], + pub(super) alpn: Option<&'a Vec>>, + pub(super) server_cert_types: Option<&'a [CertificateType]>, + pub(super) client_cert_types: Option<&'a [CertificateType]>, + pub(super) cipher_suites: &'a [CipherSuite], + /// The [certificate_authorities] extension, if it was sent by the client. + /// + /// [certificate_authorities]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.4 + pub(super) certificate_authorities: Option<&'a [DistinguishedName]>, + pub(super) named_groups: Option<&'a [NamedGroup]>, +} + +impl<'a> ClientHello<'a> { + pub(super) fn new( + input: &'a ClientHelloInput<'a>, + signature_schemes: &'a [SignatureScheme], + sni: Option<&'a DnsName<'static>>, + version: ProtocolVersion, + ) -> Self { + Self { + server_name: sni.map(Cow::Borrowed), + signature_schemes, + alpn: input.client_hello.protocols.as_ref(), + server_cert_types: input + .client_hello + .server_certificate_types + .as_deref(), + client_cert_types: input + .client_hello + .client_certificate_types + .as_deref(), + cipher_suites: &input.client_hello.cipher_suites, + // We adhere to the TLS 1.2 RFC by not exposing this to the cert resolver if TLS version is 1.2 + certificate_authorities: match version { + ProtocolVersion::TLSv1_2 => None, + _ => input + .client_hello + .certificate_authority_names + .as_deref(), + }, + named_groups: input + .client_hello + .named_groups + .as_deref(), + } + } + + /// Get the server name indicator. + /// + /// Returns `None` if the client did not supply a SNI. + pub fn server_name(&self) -> Option<&DnsName<'_>> { + self.server_name.as_deref() + } + + /// Get the compatible signature schemes. + /// + /// Returns standard-specified default if the client omitted this extension. + pub fn signature_schemes(&self) -> &[SignatureScheme] { + self.signature_schemes + } + + /// Get the ALPN protocol identifiers submitted by the client. + /// + /// Returns `None` if the client did not include an ALPN extension. + /// + /// Application Layer Protocol Negotiation (ALPN) is a TLS extension that lets a client + /// submit a set of identifiers that each a represent an application-layer protocol. + /// The server will then pick its preferred protocol from the set submitted by the client. + /// Each identifier is represented as a byte array, although common values are often ASCII-encoded. + /// See the official RFC-7301 specifications at + /// for more information on ALPN. + /// + /// For example, a HTTP client might specify "http/1.1" and/or "h2". Other well-known values + /// are listed in the at IANA registry at + /// . + /// + /// The server can specify supported ALPN protocols by setting [`ServerConfig::alpn_protocols`]. + /// During the handshake, the server will select the first protocol configured that the client supports. + pub fn alpn(&self) -> Option> { + self.alpn.map(|protocols| { + protocols + .iter() + .map(|proto| proto.as_ref()) + }) + } + + /// Get cipher suites. + pub fn cipher_suites(&self) -> &[CipherSuite] { + self.cipher_suites + } + + /// Get the server certificate types offered in the ClientHello. + /// + /// Returns `None` if the client did not include a certificate type extension. + pub fn server_cert_types(&self) -> Option<&'a [CertificateType]> { + self.server_cert_types + } + + /// Get the client certificate types offered in the ClientHello. + /// + /// Returns `None` if the client did not include a certificate type extension. + pub fn client_cert_types(&self) -> Option<&'a [CertificateType]> { + self.client_cert_types + } + + /// Get the [certificate_authorities] extension sent by the client. + /// + /// Returns `None` if the client did not send this extension. + /// + /// [certificate_authorities]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.4 + pub fn certificate_authorities(&self) -> Option<&'a [DistinguishedName]> { + self.certificate_authorities + } + + /// Get the [`named_groups`] extension sent by the client. + /// + /// This means different things in different versions of TLS: + /// + /// Originally it was introduced as the "[`elliptic_curves`]" extension for TLS1.2. + /// It described the elliptic curves supported by a client for all purposes: key + /// exchange, signature verification (for server authentication), and signing (for + /// client auth). Later [RFC7919] extended this to include FFDHE "named groups", + /// but FFDHE groups in this context only relate to key exchange. + /// + /// In TLS1.3 it was renamed to "[`named_groups`]" and now describes all types + /// of key exchange mechanisms, and does not relate at all to elliptic curves + /// used for signatures. + /// + /// [`elliptic_curves`]: https://datatracker.ietf.org/doc/html/rfc4492#section-5.1.1 + /// [RFC7919]: https://datatracker.ietf.org/doc/html/rfc7919#section-2 + /// [`named_groups`]:https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.7 + pub fn named_groups(&self) -> Option<&'a [NamedGroup]> { + self.named_groups + } +} + +/// A policy describing how an invalid Server Name Indication (SNI) value from a client is handled by the server. +/// +/// The only valid form of SNI according to relevant RFCs ([RFC6066], [RFC1035]) is +/// non-IP-address host name, however some misconfigured clients may send a bare IP address, or +/// another invalid value. Some servers may wish to ignore these invalid values instead of producing +/// an error. +/// +/// By default, Rustls will ignore invalid values that are an IP address (the most common misconfiguration) +/// and error for all other invalid values. +/// +/// When an SNI value is ignored, Rustls treats the client as if it sent no SNI at all. +/// +/// [RFC1035]: https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.1 +/// [RFC6066]: https://datatracker.ietf.org/doc/html/rfc6066#section-3 +#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)] +#[non_exhaustive] +pub enum InvalidSniPolicy { + /// Reject all ClientHello messages that contain an invalid SNI value. + RejectAll, + /// Ignore an invalid SNI value in ClientHello messages if the value is an IP address. + /// + /// "Ignoring SNI" means accepting the ClientHello message, but acting as if the client sent no SNI. + #[default] + IgnoreIpAddresses, + /// Ignore all invalid SNI in ClientHello messages. + /// + /// "Ignoring SNI" means accepting the ClientHello message, but acting as if the client sent no SNI. + IgnoreAll, +} + +impl InvalidSniPolicy { + /// Returns the valid SNI value, or ignores the invalid SNI value if allowed by this policy; otherwise returns + /// an error. + pub(super) fn accept( + &self, + payload: Option<&ServerNamePayload<'_>>, + ) -> Result>, Error> { + let Some(payload) = payload else { + return Ok(None); + }; + if let Some(server_name) = payload.to_dns_name_normalized() { + return Ok(Some(server_name)); + } + match (self, payload) { + (Self::IgnoreAll, _) => Ok(None), + (Self::IgnoreIpAddresses, ServerNamePayload::IpAddress) => Ok(None), + _ => Err(Error::PeerMisbehaved( + PeerMisbehaved::ServerNameMustContainOneHostName, + )), + } + } +} + +impl ConfigBuilder { + /// Choose how to verify client certificates. + pub fn with_client_cert_verifier( + self, + client_cert_verifier: Arc, + ) -> ConfigBuilder { + ConfigBuilder { + state: WantsServerCert { + verifier: client_cert_verifier, + }, + provider: self.provider, + time_provider: self.time_provider, + side: PhantomData, + } + } + + /// Disable client authentication. + pub fn with_no_client_auth(self) -> ConfigBuilder { + self.with_client_cert_verifier(Arc::new(NoClientAuth)) + } +} + +/// A config builder state where the caller must supply how to provide a server certificate to +/// the connecting peer. +/// +/// For more information, see the [`ConfigBuilder`] documentation. +#[derive(Clone, Debug)] +pub struct WantsServerCert { + verifier: Arc, +} + +impl ConfigBuilder { + /// Sets a single certificate chain and matching private key. This + /// certificate and key is used for all subsequent connections, + /// irrespective of things like SNI hostname. + /// + /// Note that the end-entity certificate must have the + /// [Subject Alternative Name](https://tools.ietf.org/html/rfc6125#section-4.1) + /// extension to describe, e.g., the valid DNS name. The `commonName` field is + /// disregarded. + /// + /// `cert_chain` is a vector of DER-encoded certificates. + /// `key_der` is a DER-encoded private key as PKCS#1, PKCS#8, or SEC1. The + /// `aws-lc-rs` and `ring` [`CryptoProvider`]s support + /// all three encodings, but other `CryptoProvider`s may not. + /// + /// This function fails if `key_der` is invalid, or if the + /// `SubjectPublicKeyInfo` from the private key does not match the public + /// key for the end-entity certificate from the `cert_chain`. + #[cfg(feature = "webpki")] + pub fn with_single_cert( + self, + identity: Arc>, + key_der: PrivateKeyDer<'static>, + ) -> Result { + let credentials = Credentials::from_der(identity, key_der, self.crypto_provider())?; + self.with_server_credential_resolver(Arc::new(SingleCredential::from(credentials))) + } + + /// Sets a single certificate chain, matching private key and optional OCSP + /// response. This certificate and key is used for all + /// subsequent connections, irrespective of things like SNI hostname. + /// + /// `cert_chain` is a vector of DER-encoded certificates. + /// `key_der` is a DER-encoded private key as PKCS#1, PKCS#8, or SEC1. The + /// `aws-lc-rs` and `ring` [`CryptoProvider`]s support + /// all three encodings, but other `CryptoProvider`s may not. + /// `ocsp` is a DER-encoded OCSP response. Ignored if zero length. + /// + /// This function fails if `key_der` is invalid, or if the + /// `SubjectPublicKeyInfo` from the private key does not match the public + /// key for the end-entity certificate from the `cert_chain`. + #[cfg(feature = "webpki")] + pub fn with_single_cert_with_ocsp( + self, + identity: Arc>, + key_der: PrivateKeyDer<'static>, + ocsp: Arc<[u8]>, + ) -> Result { + let mut credentials = Credentials::from_der(identity, key_der, self.crypto_provider())?; + if !ocsp.is_empty() { + credentials.ocsp = Some(ocsp); + } + self.with_server_credential_resolver(Arc::new(SingleCredential::from(credentials))) + } + + /// Sets a custom [`ServerCredentialResolver`]. + pub fn with_server_credential_resolver( + self, + cert_resolver: Arc, + ) -> Result { + self.provider.consistency_check()?; + let require_ems = !matches!(self.provider.fips(), FipsStatus::Unvalidated); + Ok(ServerConfig { + provider: self.provider, + cipher_suite_selector: &PreferClientOrder, + max_fragment_size: None, + session_storage: handy::ServerSessionMemoryCache::new(256), + ticketer: None, + cert_resolver, + alpn_protocols: Vec::new(), + verifier: self.state.verifier, + key_log: Arc::new(NoKeyLog {}), + enable_secret_extraction: false, + max_early_data_size: 0, + send_half_rtt_data: false, + send_tls13_tickets: 2, + require_ems, + time_provider: self.time_provider, + cert_compressors: compress::default_cert_compressors().to_vec(), + cert_compression_cache: Arc::new(compress::CompressionCache::default()), + cert_decompressors: compress::default_cert_decompressors().to_vec(), + invalid_sni_policy: InvalidSniPolicy::default(), + }) + } +} + +/// A [`CipherSuiteSelector`] implementation that prioritizes client order. +#[expect(clippy::exhaustive_structs)] +#[derive(Debug)] +pub struct PreferClientOrder; + +impl CipherSuiteSelector for PreferClientOrder { + fn select_tls12_cipher_suite( + &self, + client: &mut dyn Iterator, + server: &[&'static Tls12CipherSuite], + ) -> Option<&'static Tls12CipherSuite> { + self.select(client, server) + } + + fn select_tls13_cipher_suite( + &self, + client: &mut dyn Iterator, + server: &[&'static Tls13CipherSuite], + ) -> Option<&'static Tls13CipherSuite> { + self.select(client, server) + } +} + +impl PreferClientOrder { + fn select( + &self, + client: &mut dyn Iterator, + _server: &[&'static T], + ) -> Option<&'static T> { + client.next() + } +} + +/// A [`CipherSuiteSelector`] implementation that prioritizes server order. +#[expect(clippy::exhaustive_structs)] +#[derive(Debug)] +pub struct PreferServerOrder; + +impl CipherSuiteSelector for PreferServerOrder { + fn select_tls12_cipher_suite( + &self, + client: &mut dyn Iterator, + server: &[&'static Tls12CipherSuite], + ) -> Option<&'static Tls12CipherSuite> { + client + .filter_map(|cs| { + server + .iter() + .position(|&ss| ss == cs) + .map(|pos| (pos, cs)) + }) + .min_by_key(|&(pos, _)| pos) + .map(|(_, cs)| cs) + } + + fn select_tls13_cipher_suite( + &self, + client: &mut dyn Iterator, + server: &[&'static Tls13CipherSuite], + ) -> Option<&'static Tls13CipherSuite> { + client + .filter_map(|cs| { + server + .iter() + .position(|&ss| ss == cs) + .map(|pos| (pos, cs)) + }) + .min_by_key(|&(pos, _)| pos) + .map(|(_, cs)| cs) + } +} + +impl VersionSuiteSelector for T { + fn select( + &self, + client: &mut dyn Iterator, + server: &[&'static Tls12CipherSuite], + ) -> Option<&'static Tls12CipherSuite> { + self.select_tls12_cipher_suite(client, server) + } +} + +impl VersionSuiteSelector for T { + fn select( + &self, + client: &mut dyn Iterator, + server: &[&'static Tls13CipherSuite], + ) -> Option<&'static Tls13CipherSuite> { + self.select_tls13_cipher_suite(client, server) + } +} + +pub(super) trait VersionSuiteSelector { + fn select( + &self, + client: &mut dyn Iterator, + server: &[&'static T], + ) -> Option<&'static T>; +} + +/// A filter that chooses the cipher suite to use for a TLS session. +pub trait CipherSuiteSelector: Debug + Send + Sync { + /// Choose a cipher suite, given the client's and server's options, in preference order. + /// + /// The `client` list is generated in order from the [`CipherSuite`] values received in the + /// `ClientHello`, filtered to only contain suites that the server supports. The `server` + /// list comes from the [`ServerConfig`]'s [`CryptoProvider`]. + /// + /// Yields the chosen cipher suite supported by both sides, or `None` to indicate that no + /// mutually supported cipher suite could be agreed on. + fn select_tls12_cipher_suite( + &self, + client: &mut dyn Iterator, + server: &[&'static Tls12CipherSuite], + ) -> Option<&'static Tls12CipherSuite>; + + /// Choose a cipher suite, given the client's and server's options, in preference order. + /// + /// The `client` list is generated in order from the [`CipherSuite`] values received in the + /// `ClientHello`, filtered to only contain suites that the server supports. The `server` + /// list comes from the [`ServerConfig`]'s [`CryptoProvider`]. + /// + /// Yields the chosen cipher suite supported by both sides, or `None` to indicate that no + /// mutually supported cipher suite could be agreed on. + fn select_tls13_cipher_suite( + &self, + server: &mut dyn Iterator, + server: &[&'static Tls13CipherSuite], + ) -> Option<&'static Tls13CipherSuite>; +} diff --git a/rustls/src/server/connection.rs b/rustls/src/server/connection.rs new file mode 100644 index 00000000000..f1fab9e1d97 --- /dev/null +++ b/rustls/src/server/connection.rs @@ -0,0 +1,658 @@ +use alloc::borrow::Cow; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::fmt; +use core::fmt::{Debug, Formatter}; +use core::ops::Deref; +use std::io; + +use pki_types::{DnsName, FipsStatus}; + +use super::config::{ClientHello, ServerConfig}; +use crate::common_state::{CommonState, ConnectionOutputs, EarlyDataEvent, Event, Protocol, Side}; +use crate::conn::private::SideOutput; +use crate::conn::{ + Connection, ConnectionCommon, ConnectionCore, KeyingMaterialExporter, Reader, SendPath, + SideCommonOutput, Writer, +}; +#[cfg(doc)] +use crate::crypto; +use crate::crypto::cipher::Payload; +use crate::error::{ApiMisuse, Error, ErrorWithAlert}; +use crate::log::trace; +use crate::msgs::{ServerExtensionsInput, ServerNamePayload}; +use crate::server::hs::{ChooseConfig, ExpectClientHello, ReadClientHello, ServerState}; +use crate::suites::ExtractedSecrets; +use crate::sync::Arc; +use crate::vecbuf::ChunkVecBuffer; + +/// This represents a single TLS server connection. +/// +/// Send TLS-protected data to the peer using the `io::Write` trait implementation. +/// Read data from the peer using the `io::Read` trait implementation. +pub struct ServerConnection { + pub(super) inner: ConnectionCommon, +} + +impl ServerConnection { + /// Make a new ServerConnection. `config` controls how + /// we behave in the TLS protocol. + pub fn new(config: Arc) -> Result { + let fips = config.fips(); + Ok(Self { + inner: ConnectionCommon::new( + ConnectionCore::for_server( + config, + ServerExtensionsInput::default(), + Protocol::Tcp, + )?, + fips, + ), + }) + } + + /// Retrieves the server name, if any, used to select the certificate and + /// private key. + /// + /// This returns `None` until some time after the client's server name indication + /// (SNI) extension value is processed during the handshake. It will never be + /// `None` when the connection is ready to send or process application data, + /// unless the client does not support SNI. + /// + /// This is useful for application protocols that need to enforce that the + /// server name matches an application layer protocol hostname. For + /// example, HTTP/1.1 servers commonly expect the `Host:` header field of + /// every request on a connection to match the hostname in the SNI extension + /// when the client provides the SNI extension. + /// + /// The server name is also used to match sessions during session resumption. + pub fn server_name(&self) -> Option<&DnsName<'_>> { + self.inner.core.side.server_name() + } + + /// Application-controlled portion of the resumption ticket supplied by the client, if any. + /// + /// Recovered from the prior session's `set_resumption_data`. Integrity is guaranteed by rustls. + /// + /// Returns `Some` if and only if a valid resumption ticket has been received from the client. + pub fn received_resumption_data(&self) -> Option<&[u8]> { + self.inner + .core + .side + .received_resumption_data() + } + + /// Set the resumption data to embed in future resumption tickets supplied to the client. + /// + /// Defaults to the empty byte string. Must be less than 2^15 bytes to allow room for other + /// data. Should be called while `is_handshaking` returns true to ensure all transmitted + /// resumption tickets are affected. + /// + /// Integrity will be assured by rustls, but the data will be visible to the client. If secrecy + /// from the client is desired, encrypt the data separately. + pub fn set_resumption_data(&mut self, data: &[u8]) -> Result<(), Error> { + assert!(data.len() < 2usize.pow(15)); + match &mut self.inner.core.state { + Ok(st) => st.set_resumption_data(data), + Err(e) => Err(e.clone()), + } + } + + /// Returns an `io::Read` implementer you can read bytes from that are + /// received from a client as TLS1.3 0RTT/"early" data, during the handshake. + /// + /// This returns `None` in many circumstances, such as : + /// + /// - Early data is disabled if [`ServerConfig::max_early_data_size`] is zero (the default). + /// - The session negotiated with the client is not TLS1.3. + /// - The client just doesn't support early data. + /// - The connection doesn't resume an existing session. + /// - The client hasn't sent a full ClientHello yet. + pub fn early_data(&mut self) -> Option> { + if self + .inner + .core + .side + .early_data + .was_accepted() + { + Some(ReadEarlyData::new(&mut self.inner)) + } else { + None + } + } + + /// Extract secrets, so they can be used when configuring kTLS, for example. + /// Should be used with care as it exposes secret key material. + pub fn dangerous_extract_secrets(self) -> Result { + self.inner.dangerous_extract_secrets() + } +} + +impl Connection for ServerConnection { + fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result { + self.inner.read_tls(rd) + } + + fn write_tls(&mut self, wr: &mut dyn io::Write) -> Result { + self.inner.write_tls(wr) + } + + fn wants_read(&self) -> bool { + self.inner.wants_read() + } + + fn wants_write(&self) -> bool { + self.inner.wants_write() + } + + fn reader(&mut self) -> Reader<'_> { + self.inner.reader() + } + + fn writer(&mut self) -> Writer<'_> { + self.inner.writer() + } + + fn process_new_packets(&mut self) -> Result { + self.inner.process_new_packets() + } + + fn exporter(&mut self) -> Result { + self.inner.exporter() + } + + fn dangerous_extract_secrets(self) -> Result { + self.inner.dangerous_extract_secrets() + } + + fn set_buffer_limit(&mut self, limit: Option) { + self.inner.set_buffer_limit(limit) + } + + fn set_plaintext_buffer_limit(&mut self, limit: Option) { + self.inner + .set_plaintext_buffer_limit(limit) + } + + fn refresh_traffic_keys(&mut self) -> Result<(), Error> { + self.inner.refresh_traffic_keys() + } + + fn send_close_notify(&mut self) { + self.inner.send_close_notify(); + } + + fn is_handshaking(&self) -> bool { + self.inner.is_handshaking() + } + + fn fips(&self) -> FipsStatus { + self.inner.fips + } +} + +impl Deref for ServerConnection { + type Target = ConnectionOutputs; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl Debug for ServerConnection { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("ServerConnection") + .finish_non_exhaustive() + } +} + +/// Handle a server-side connection before configuration is available. +/// +/// `Acceptor` allows the caller to choose a [`ServerConfig`] after reading +/// the [`ClientHello`] of an incoming connection. This is useful for +/// servers that choose different certificates or cipher suites based on the +/// characteristics of the `ClientHello`. In particular it is useful for +/// servers that need to do some I/O to load a certificate and its private key +/// and don't want to use the blocking interface provided by +/// [`ServerCredentialResolver`][crate::server::ServerCredentialResolver]. +/// +/// Create an Acceptor with [`Acceptor::default()`]. +/// +/// # Example +/// +/// ```no_run +/// # #[cfg(feature = "aws-lc-rs")] { +/// # fn choose_server_config( +/// # _: rustls::server::ClientHello, +/// # ) -> std::sync::Arc { +/// # unimplemented!(); +/// # } +/// # #[allow(unused_variables)] +/// # fn main() { +/// use rustls::server::{Acceptor, ServerConfig}; +/// let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); +/// for stream in listener.incoming() { +/// let mut stream = stream.unwrap(); +/// let mut acceptor = Acceptor::default(); +/// let accepted = loop { +/// acceptor.read_tls(&mut stream).unwrap(); +/// if let Some(accepted) = acceptor.accept().unwrap() { +/// break accepted; +/// } +/// }; +/// +/// // For some user-defined choose_server_config: +/// let config = choose_server_config(accepted.client_hello()); +/// let conn = accepted +/// .into_connection(config) +/// .unwrap(); +/// +/// // Proceed with handling the ServerConnection. +/// } +/// # } +/// # } +/// ``` +pub struct Acceptor { + inner: Option>, +} + +impl Default for Acceptor { + /// Return an empty Acceptor, ready to receive bytes from a new client connection. + fn default() -> Self { + Self { + inner: Some(ConnectionCommon::new( + ConnectionCore::for_acceptor(Protocol::Tcp), + FipsStatus::Unvalidated, + )), + } + } +} + +impl Acceptor { + /// Read TLS content from `rd`. + /// + /// Returns an error if this `Acceptor` has already yielded an [`Accepted`]. For more details, + /// refer to [`Connection::read_tls()`]. + /// + /// [`Connection::read_tls()`]: crate::Connection::read_tls + pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result { + match &mut self.inner { + Some(conn) => conn.read_tls(rd), + None => Err(io::Error::other( + "acceptor cannot read after successful acceptance", + )), + } + } + + /// Check if a `ClientHello` message has been received. + /// + /// Returns `Ok(None)` if the complete `ClientHello` has not yet been received. + /// Do more I/O and then call this function again. + /// + /// Returns `Ok(Some(accepted))` if the connection has been accepted. Call + /// `accepted.into_connection()` to continue. Do not call this function again. + /// + /// Returns `Err((err, alert))` if an error occurred. If an alert is returned, the + /// application should call `alert.write()` to send the alert to the client. It should + /// not call `accept()` again. + pub fn accept(&mut self) -> Result, (Error, AcceptedAlert)> { + let Some(mut connection) = self.inner.take() else { + return Err(( + ApiMisuse::AcceptorPolledAfterCompletion.into(), + AcceptedAlert::empty(), + )); + }; + + if let Err(e) = connection.process_new_packets() { + return Err(AcceptedAlert::from_error(e, connection.core.common.send)); + } + + let Ok(ServerState::ChooseConfig(_)) = connection.core.state else { + self.inner = Some(connection); + return Ok(None); + }; + + let Ok(ServerState::ChooseConfig(choose_config)) = core::mem::replace( + &mut connection.core.state, + Err(Error::Unreachable("Accepted misused state")), + ) else { + unreachable!(); // checked in previous block + }; + + Ok(Some(Accepted { + connection, + choose_config, + })) + } +} + +/// Represents a TLS alert resulting from handling the client's `ClientHello` message. +/// +/// When [`Acceptor::accept()`] returns an error, it yields an `AcceptedAlert` such that the +/// application can communicate failure to the client via [`AcceptedAlert::write()`]. +pub struct AcceptedAlert(ChunkVecBuffer); + +impl AcceptedAlert { + pub(super) fn from_error(error: Error, mut send: SendPath) -> (Error, Self) { + let ErrorWithAlert { error, data } = ErrorWithAlert::new(error, &mut send); + let mut output = ChunkVecBuffer::new(None); + output.append(data); + (error, Self(output)) + } + + pub(super) fn empty() -> Self { + Self(ChunkVecBuffer::new(None)) + } + + /// Send the alert to the client. + /// + /// To account for short writes this function should be called repeatedly until it + /// returns `Ok(0)` or an error. + pub fn write(&mut self, wr: &mut dyn io::Write) -> Result { + self.0.write_to(wr) + } + + /// Send the alert to the client. + /// + /// This function will invoke the writer until the buffer is empty. + pub fn write_all(&mut self, wr: &mut dyn io::Write) -> Result<(), io::Error> { + while self.write(wr)? != 0 {} + Ok(()) + } +} + +impl Debug for AcceptedAlert { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("AcceptedAlert") + .finish_non_exhaustive() + } +} + +/// Allows reading of early data in resumed TLS1.3 connections. +/// +/// "Early data" is also known as "0-RTT data". +/// +/// This type implements [`io::Read`]. +pub struct ReadEarlyData<'a> { + common: &'a mut ConnectionCommon, +} + +impl<'a> ReadEarlyData<'a> { + fn new(common: &'a mut ConnectionCommon) -> Self { + ReadEarlyData { common } + } + + /// Returns the "early" exporter that can derive key material for use in early data + /// + /// See [RFC5705][] for general details on what exporters are, and [RFC8446 S7.5][] for + /// specific details on the "early" exporter. + /// + /// **Beware** that the early exporter requires care, as it is subject to the same + /// potential for replay as early data itself. See [RFC8446 appendix E.5.1][] for + /// more detail. + /// + /// This function can be called at most once per connection. This function will error: + /// if called more than once per connection. + /// + /// If you are looking for the normal exporter, this is available from + /// [`Connection::exporter()`]. + /// + /// [RFC5705]: https://datatracker.ietf.org/doc/html/rfc5705 + /// [RFC8446 S7.5]: https://datatracker.ietf.org/doc/html/rfc8446#section-7.5 + /// [RFC8446 appendix E.5.1]: https://datatracker.ietf.org/doc/html/rfc8446#appendix-E.5.1 + /// [`Connection::exporter()`]: crate::conn::Connection::exporter() + pub fn exporter(&mut self) -> Result { + self.common.core.early_exporter() + } +} + +impl io::Read for ReadEarlyData<'_> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.common + .core + .side + .early_data + .read(buf) + } +} + +/// Represents a `ClientHello` message received through the [`Acceptor`]. +/// +/// Contains the state required to resume the connection through [`Accepted::into_connection()`]. +pub struct Accepted { + // invariant: `connection.core.state` is `Err(_)` and requires restoring + connection: ConnectionCommon, + choose_config: Box, +} + +impl Accepted { + /// Get the [`ClientHello`] for this connection. + pub fn client_hello(&self) -> ClientHello<'_> { + let client_hello = self.choose_config.client_hello(); + let server_name = client_hello + .server_name + .as_ref() + .and_then(ServerNamePayload::to_dns_name_normalized) + .map(Cow::Owned); + let ch = ClientHello { + server_name, + signature_schemes: client_hello + .signature_schemes + .as_deref() + .unwrap_or_default(), + alpn: client_hello.protocols.as_ref(), + server_cert_types: client_hello + .server_certificate_types + .as_deref(), + client_cert_types: client_hello + .client_certificate_types + .as_deref(), + cipher_suites: &client_hello.cipher_suites, + certificate_authorities: client_hello + .certificate_authority_names + .as_deref(), + named_groups: client_hello.named_groups.as_deref(), + }; + + trace!("Accepted::client_hello(): {ch:#?}"); + ch + } + + /// Convert the [`Accepted`] into a [`ServerConnection`]. + /// + /// Takes the state returned from [`Acceptor::accept()`] as well as the [`ServerConfig`] that + /// should be used for the session. Returns an error if configuration-dependent validation of + /// the received `ClientHello` message fails. + pub fn into_connection( + mut self, + config: Arc, + ) -> Result { + if let Err(err) = self + .connection + .send + .set_max_fragment_size(config.max_fragment_size) + { + // We have a connection here, but it won't contain an alert since the error + // is with the fragment size configured in the `ServerConfig`. + return Err((err, AcceptedAlert::empty())); + } + self.connection.fips = config.fips(); + + let mut output = SideCommonOutput { + side: &mut self.connection.core.side, + quic: None, + common: &mut self.connection.core.common, + }; + + let state = match self.choose_config.use_config( + config, + ServerExtensionsInput::default(), + &mut output, + ) { + Ok(state) => state, + Err(err) => { + return Err(AcceptedAlert::from_error( + err, + self.connection.core.common.send, + )); + } + }; + self.connection.core.state = Ok(state); + + Ok(ServerConnection { + inner: self.connection, + }) + } +} + +impl Debug for Accepted { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("Accepted") + .finish_non_exhaustive() + } +} + +#[derive(Default)] +pub(super) enum EarlyDataState { + #[default] + New, + Accepted { + received: ChunkVecBuffer, + }, +} + +impl EarlyDataState { + fn accept(&mut self) { + *self = Self::Accepted { + received: ChunkVecBuffer::new(None), + }; + } + + fn was_accepted(&self) -> bool { + matches!(self, Self::Accepted { .. }) + } + + #[expect(dead_code)] + fn peek(&self) -> Option<&[u8]> { + match self { + Self::Accepted { received, .. } => received.peek(), + _ => None, + } + } + + #[expect(dead_code)] + fn pop(&mut self) -> Option> { + match self { + Self::Accepted { received, .. } => received.pop(), + _ => None, + } + } + + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + Self::Accepted { received, .. } => received.read(buf), + _ => Err(io::Error::from(io::ErrorKind::BrokenPipe)), + } + } + + fn take_received_plaintext(&mut self, bytes: Payload<'_>) { + let Self::Accepted { received } = self else { + return; + }; + + received.append(bytes.into_vec()); + } +} + +impl ConnectionCore { + pub(crate) fn for_server( + config: Arc, + extra_exts: ServerExtensionsInput, + protocol: Protocol, + ) -> Result { + let mut common = CommonState::new(Side::Server); + common + .send + .set_max_fragment_size(config.max_fragment_size)?; + Ok(Self::new( + Box::new(ExpectClientHello::new( + config, + extra_exts, + Vec::new(), + protocol, + )) + .into(), + ServerConnectionData::default(), + common, + )) + } + + pub(crate) fn for_acceptor(protocol: Protocol) -> Self { + Self::new( + ReadClientHello::new(protocol).into(), + ServerConnectionData::default(), + CommonState::new(Side::Server), + ) + } +} + +/// State associated with a server connection. +#[derive(Default)] +pub(crate) struct ServerConnectionData { + sni: Option>, + received_resumption_data: Option>, + early_data: EarlyDataState, +} + +impl ServerConnectionData { + pub(crate) fn received_resumption_data(&self) -> Option<&[u8]> { + self.received_resumption_data.as_deref() + } + + pub(crate) fn server_name(&self) -> Option<&DnsName<'static>> { + self.sni.as_ref() + } +} + +impl SideOutput for ServerConnectionData { + fn emit(&mut self, ev: Event<'_>) { + match ev { + Event::EarlyApplicationData(data) => self + .early_data + .take_received_plaintext(data), + Event::EarlyData(EarlyDataEvent::Accepted) => self.early_data.accept(), + Event::ReceivedServerName(sni) => self.sni = sni, + Event::ResumptionData(data) => self.received_resumption_data = Some(data), + _ => unreachable!(), + } + } +} + +/// State associated with a server connection. +#[expect(clippy::exhaustive_structs)] +#[derive(Debug)] +pub struct ServerSide; + +impl crate::conn::SideData for ServerSide {} + +impl crate::conn::private::Side for ServerSide { + type Data = ServerConnectionData; + type State = ServerState; +} + +#[cfg(test)] +mod tests { + use std::format; + + use super::*; + + // these branches not reachable externally, unless something else goes wrong. + #[test] + fn test_read_in_new_state() { + assert_eq!( + format!("{:?}", EarlyDataState::default().read(&mut [0u8; 5])), + "Err(Kind(BrokenPipe))" + ); + } +} diff --git a/rustls/src/server/handy.rs b/rustls/src/server/handy.rs index ba2570abfdd..f6e7f8278ef 100644 --- a/rustls/src/server/handy.rs +++ b/rustls/src/server/handy.rs @@ -1,22 +1,21 @@ -use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt::Debug; -use crate::server::ClientHello; -use crate::{server, sign}; +use crate::server::{ServerSessionKey, StoresServerSessions}; /// Something which never stores sessions. +#[expect(clippy::exhaustive_structs)] #[derive(Debug)] pub struct NoServerSessionStorage {} -impl server::StoresServerSessions for NoServerSessionStorage { - fn put(&self, _id: Vec, _sec: Vec) -> bool { +impl StoresServerSessions for NoServerSessionStorage { + fn put(&self, _id: ServerSessionKey<'_>, _sec: Vec) -> bool { false } - fn get(&self, _id: &[u8]) -> Option> { + fn get(&self, _id: ServerSessionKey<'_>) -> Option> { None } - fn take(&self, _id: &[u8]) -> Option> { + fn take(&self, _id: ServerSessionKey<'_>) -> Option> { None } fn can_cache(&self) -> bool { @@ -24,14 +23,14 @@ impl server::StoresServerSessions for NoServerSessionStorage { } } -#[cfg(any(feature = "std", feature = "hashbrown"))] mod cache { - use alloc::sync::Arc; - use alloc::vec::Vec; - use core::fmt::{Debug, Formatter}; + use core::fmt::Formatter; + use super::*; + use crate::limited_cache; use crate::lock::Mutex; - use crate::{limited_cache, server}; + use crate::server::StoresServerSessions; + use crate::sync::Arc; /// An implementer of `StoresServerSessions` that stores everything /// in memory. If enforces a limit on the number of stored sessions @@ -44,43 +43,35 @@ mod cache { /// Make a new ServerSessionMemoryCache. `size` is the maximum /// number of stored sessions, and may be rounded-up for /// efficiency. - #[cfg(feature = "std")] pub fn new(size: usize) -> Arc { Arc::new(Self { cache: Mutex::new(limited_cache::LimitedCache::new(size)), }) } - - /// Make a new ServerSessionMemoryCache. `size` is the maximum - /// number of stored sessions, and may be rounded-up for - /// efficiency. - #[cfg(not(feature = "std"))] - pub fn new(size: usize) -> Arc { - Arc::new(Self { - cache: Mutex::new::(limited_cache::LimitedCache::new(size)), - }) - } } - impl server::StoresServerSessions for ServerSessionMemoryCache { - fn put(&self, key: Vec, value: Vec) -> bool { + impl StoresServerSessions for ServerSessionMemoryCache { + fn put(&self, key: ServerSessionKey<'_>, value: Vec) -> bool { self.cache .lock() .unwrap() - .insert(key, value); + .insert(key.as_ref().to_vec(), value); true } - fn get(&self, key: &[u8]) -> Option> { + fn get(&self, key: ServerSessionKey<'_>) -> Option> { self.cache .lock() .unwrap() - .get(key) + .get(key.as_ref()) .cloned() } - fn take(&self, key: &[u8]) -> Option> { - self.cache.lock().unwrap().remove(key) + fn take(&self, key: ServerSessionKey<'_>) -> Option> { + self.cache + .lock() + .unwrap() + .remove(key.as_ref()) } fn can_cache(&self) -> bool { @@ -91,7 +82,7 @@ mod cache { impl Debug for ServerSessionMemoryCache { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("ServerSessionMemoryCache") - .finish() + .finish_non_exhaustive() } } @@ -105,146 +96,79 @@ mod cache { #[test] fn test_serversessionmemorycache_accepts_put() { let c = ServerSessionMemoryCache::new(4); - assert!(c.put(vec![0x01], vec![0x02])); + assert!(c.put(ServerSessionKey::new(&[0x01]), vec![0x02])); } #[test] fn test_serversessionmemorycache_persists_put() { let c = ServerSessionMemoryCache::new(4); - assert!(c.put(vec![0x01], vec![0x02])); - assert_eq!(c.get(&[0x01]), Some(vec![0x02])); - assert_eq!(c.get(&[0x01]), Some(vec![0x02])); + assert!(c.put(ServerSessionKey::new(&[0x01]), vec![0x02])); + assert_eq!(c.get(ServerSessionKey::new(&[0x01])), Some(vec![0x02])); + assert_eq!(c.get(ServerSessionKey::new(&[0x01])), Some(vec![0x02])); } #[test] fn test_serversessionmemorycache_overwrites_put() { let c = ServerSessionMemoryCache::new(4); - assert!(c.put(vec![0x01], vec![0x02])); - assert!(c.put(vec![0x01], vec![0x04])); - assert_eq!(c.get(&[0x01]), Some(vec![0x04])); + assert!(c.put(ServerSessionKey::new(&[0x01]), vec![0x02])); + assert!(c.put(ServerSessionKey::new(&[0x01]), vec![0x04])); + assert_eq!(c.get(ServerSessionKey::new(&[0x01])), Some(vec![0x04])); } #[test] fn test_serversessionmemorycache_drops_to_maintain_size_invariant() { let c = ServerSessionMemoryCache::new(2); - assert!(c.put(vec![0x01], vec![0x02])); - assert!(c.put(vec![0x03], vec![0x04])); - assert!(c.put(vec![0x05], vec![0x06])); - assert!(c.put(vec![0x07], vec![0x08])); - assert!(c.put(vec![0x09], vec![0x0a])); - - let count = c.get(&[0x01]).iter().count() - + c.get(&[0x03]).iter().count() - + c.get(&[0x05]).iter().count() - + c.get(&[0x07]).iter().count() - + c.get(&[0x09]).iter().count(); + assert!(c.put(ServerSessionKey::new(&[0x01]), vec![0x02])); + assert!(c.put(ServerSessionKey::new(&[0x03]), vec![0x04])); + assert!(c.put(ServerSessionKey::new(&[0x05]), vec![0x06])); + assert!(c.put(ServerSessionKey::new(&[0x07]), vec![0x08])); + assert!(c.put(ServerSessionKey::new(&[0x09]), vec![0x0a])); + + let count = c + .get(ServerSessionKey::new(&[0x01])) + .iter() + .count() + + c.get(ServerSessionKey::new(&[0x03])) + .iter() + .count() + + c.get(ServerSessionKey::new(&[0x05])) + .iter() + .count() + + c.get(ServerSessionKey::new(&[0x07])) + .iter() + .count() + + c.get(ServerSessionKey::new(&[0x09])) + .iter() + .count(); assert!(count < 5); } } } -#[cfg(any(feature = "std", feature = "hashbrown"))] pub use cache::ServerSessionMemoryCache; -/// Something which never produces tickets. -#[derive(Debug)] -pub(super) struct NeverProducesTickets {} - -impl server::ProducesTickets for NeverProducesTickets { - fn enabled(&self) -> bool { - false - } - fn lifetime(&self) -> u32 { - 0 - } - fn encrypt(&self, _bytes: &[u8]) -> Option> { - None - } - fn decrypt(&self, _bytes: &[u8]) -> Option> { - None - } -} - -/// Something which always resolves to the same cert chain. -#[derive(Debug)] -pub(super) struct AlwaysResolvesChain(Arc); - -impl AlwaysResolvesChain { - /// Creates an `AlwaysResolvesChain`, using the supplied `CertifiedKey`. - pub(super) fn new(certified_key: sign::CertifiedKey) -> Self { - Self(Arc::new(certified_key)) - } - - /// Creates an `AlwaysResolvesChain`, using the supplied `CertifiedKey` and OCSP response. - /// - /// If non-empty, the given OCSP response is attached. - pub(super) fn new_with_extras(certified_key: sign::CertifiedKey, ocsp: Vec) -> Self { - let mut r = Self::new(certified_key); - - { - let cert = Arc::make_mut(&mut r.0); - if !ocsp.is_empty() { - cert.ocsp = Some(ocsp); - } - } - - r - } -} - -impl server::ResolvesServerCert for AlwaysResolvesChain { - fn resolve(&self, _client_hello: ClientHello<'_>) -> Option> { - Some(Arc::clone(&self.0)) - } -} - -/// An exemplar `ResolvesServerCert` implementation that always resolves to a single -/// [RFC 7250] raw public key. -/// -/// [RFC 7250]: https://tools.ietf.org/html/rfc7250 -#[derive(Clone, Debug)] -pub struct AlwaysResolvesServerRawPublicKeys(Arc); - -impl AlwaysResolvesServerRawPublicKeys { - /// Create a new `AlwaysResolvesServerRawPublicKeys` instance. - pub fn new(certified_key: Arc) -> Self { - Self(certified_key) - } -} - -impl server::ResolvesServerCert for AlwaysResolvesServerRawPublicKeys { - fn resolve(&self, _client_hello: ClientHello<'_>) -> Option> { - Some(Arc::clone(&self.0)) - } - - fn only_raw_public_keys(&self) -> bool { - true - } -} - -#[cfg(any(feature = "std", feature = "hashbrown"))] +#[cfg(feature = "webpki")] mod sni_resolver { - use alloc::string::{String, ToString}; - use alloc::sync::Arc; use core::fmt::Debug; use pki_types::{DnsName, ServerName}; - use crate::error::Error; + use crate::crypto::{CertificateIdentity, Credentials, Identity, SelectedCredential}; + use crate::error::{Error, PeerIncompatible}; use crate::hash_map::HashMap; - use crate::server::ClientHello; - use crate::webpki::{verify_server_name, ParsedCertificate}; - use crate::{server, sign}; + use crate::server::{self, ClientHello}; + use crate::sync::Arc; + use crate::webpki::{ParsedCertificate, verify_server_name}; /// Something that resolves do different cert chains/keys based /// on client-supplied server name (via SNI). #[derive(Debug)] - pub struct ResolvesServerCertUsingSni { - by_name: HashMap>, + pub struct ServerNameResolver { + by_name: HashMap, Arc>, } - impl ResolvesServerCertUsingSni { + impl ServerNameResolver { /// Create a new and empty (i.e., knows no certificates) resolver. pub fn new() -> Self { Self { @@ -252,19 +176,11 @@ mod sni_resolver { } } - /// Add a new `sign::CertifiedKey` to be used for the given SNI `name`. + /// Add a new `Credentials` to be used for the given SNI `name`. /// - /// This function fails if `name` is not a valid DNS name, or if - /// it's not valid for the supplied certificate, or if the certificate - /// chain is syntactically faulty. - pub fn add(&mut self, name: &str, ck: sign::CertifiedKey) -> Result<(), Error> { - let server_name = { - let checked_name = DnsName::try_from(name) - .map_err(|_| Error::General("Bad DNS name".into())) - .map(|name| name.to_lowercase_owned())?; - ServerName::DnsName(checked_name) - }; - + /// This function fails if the `name` is not valid for the supplied certificate, or if + /// the certificate chain is syntactically faulty. + pub fn add(&mut self, name: DnsName<'static>, ck: Credentials) -> Result<(), Error> { // Check the certificate chain for validity: // - it should be non-empty list // - the first certificate should be parsable as a x509v3, @@ -274,110 +190,118 @@ mod sni_resolver { // These checks are not security-sensitive. They are the // *server* attempting to detect accidental misconfiguration. - ck.end_entity_cert() - .and_then(ParsedCertificate::try_from) - .and_then(|cert| verify_server_name(&cert, &server_name))?; - - if let ServerName::DnsName(name) = server_name { - self.by_name - .insert(name.as_ref().to_string(), Arc::new(ck)); + let wrapped = ServerName::DnsName(name); + if let Identity::X509(CertificateIdentity { end_entity, .. }) = &*ck.identity { + let parsed = ParsedCertificate::try_from(end_entity)?; + verify_server_name(&parsed, &wrapped)?; } + + let ServerName::DnsName(name) = wrapped else { + unreachable!() + }; + + self.by_name.insert(name, Arc::new(ck)); Ok(()) } } - impl server::ResolvesServerCert for ResolvesServerCertUsingSni { - fn resolve(&self, client_hello: ClientHello<'_>) -> Option> { - if let Some(name) = client_hello.server_name() { - self.by_name.get(name).cloned() - } else { - // This kind of resolver requires SNI - None + impl server::ServerCredentialResolver for ServerNameResolver { + fn resolve(&self, client_hello: &ClientHello<'_>) -> Result { + let Some(name) = client_hello.server_name() else { + return Err(PeerIncompatible::NoServerNameProvided.into()); + }; + + let Some(credentials) = self.by_name.get(name) else { + return Err(Error::NoSuitableCertificate); + }; + + match credentials.signer(client_hello.signature_schemes) { + Some(signer) => Ok(signer), + None => Err(PeerIncompatible::NoSignatureSchemesInCommon.into()), } } } #[cfg(test)] mod tests { + use alloc::borrow::Cow; + use super::*; - use crate::server::ResolvesServerCert; + use crate::server::ServerCredentialResolver; #[test] - fn test_resolvesservercertusingsni_requires_sni() { - let rscsni = ResolvesServerCertUsingSni::new(); - assert!(rscsni - .resolve(ClientHello { - server_name: &None, - signature_schemes: &[], - alpn: None, - server_cert_types: None, - client_cert_types: None, - cipher_suites: &[], - certificate_authorities: None, - }) - .is_none()); + fn test_server_name_resolver_requires_sni() { + let rscsni = ServerNameResolver::new(); + assert!( + rscsni + .resolve(&ClientHello { + server_name: None, + signature_schemes: &[], + alpn: None, + server_cert_types: None, + client_cert_types: None, + cipher_suites: &[], + certificate_authorities: None, + named_groups: None, + }) + .is_err() + ); } #[test] - fn test_resolvesservercertusingsni_handles_unknown_name() { - let rscsni = ResolvesServerCertUsingSni::new(); + fn test_server_name_resolver_handles_unknown_name() { + let rscsni = ServerNameResolver::new(); let name = DnsName::try_from("hello.com") .unwrap() .to_owned(); - assert!(rscsni - .resolve(ClientHello { - server_name: &Some(name), - signature_schemes: &[], - alpn: None, - server_cert_types: None, - client_cert_types: None, - cipher_suites: &[], - certificate_authorities: None, - }) - .is_none()); + assert!( + rscsni + .resolve(&ClientHello { + server_name: Some(Cow::Borrowed(&name)), + signature_schemes: &[], + alpn: None, + server_cert_types: None, + client_cert_types: None, + cipher_suites: &[], + certificate_authorities: None, + named_groups: None, + }) + .is_err() + ); } } } -#[cfg(any(feature = "std", feature = "hashbrown"))] -pub use sni_resolver::ResolvesServerCertUsingSni; +#[cfg(feature = "webpki")] +pub use sni_resolver::ServerNameResolver; #[cfg(test)] mod tests { use std::vec; use super::*; - use crate::server::{ProducesTickets, StoresServerSessions}; + use crate::server::StoresServerSessions; #[test] fn test_noserversessionstorage_drops_put() { let c = NoServerSessionStorage {}; - assert!(!c.put(vec![0x01], vec![0x02])); + assert!(!c.put(ServerSessionKey::new(&[0x01]), vec![0x02])); } #[test] fn test_noserversessionstorage_denies_gets() { let c = NoServerSessionStorage {}; - c.put(vec![0x01], vec![0x02]); - assert_eq!(c.get(&[]), None); - assert_eq!(c.get(&[0x01]), None); - assert_eq!(c.get(&[0x02]), None); + c.put(ServerSessionKey::new(&[0x01]), vec![0x02]); + assert_eq!(c.get(ServerSessionKey::new(&[])), None); + assert_eq!(c.get(ServerSessionKey::new(&[0x01])), None); + assert_eq!(c.get(ServerSessionKey::new(&[0x02])), None); } #[test] fn test_noserversessionstorage_denies_takes() { let c = NoServerSessionStorage {}; - assert_eq!(c.take(&[]), None); - assert_eq!(c.take(&[0x01]), None); - assert_eq!(c.take(&[0x02]), None); - } - - #[test] - fn test_neverproducestickets_does_nothing() { - let npt = NeverProducesTickets {}; - assert!(!npt.enabled()); - assert_eq!(0, npt.lifetime()); - assert_eq!(None, npt.encrypt(&[])); - assert_eq!(None, npt.decrypt(&[])); + assert_eq!(c.take(ServerSessionKey::new(&[])), None); + assert_eq!(c.take(ServerSessionKey::new(&[0x01])), None); + assert_eq!(c.take(ServerSessionKey::new(&[0x02])), None); } } diff --git a/rustls/src/server/hs.rs b/rustls/src/server/hs.rs index 2d2a45cc575..617164eac91 100644 --- a/rustls/src/server/hs.rs +++ b/rustls/src/server/hs.rs @@ -1,112 +1,269 @@ use alloc::borrow::ToOwned; use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt; use pki_types::DnsName; -use super::server_conn::ServerConnectionData; -#[cfg(feature = "tls12")] -use super::tls12; -use crate::common_state::{ - KxState, Protocol, RawKeyNegotationResult, RawKeyNegotiationParams, State, -}; -use crate::conn::ConnectionRandoms; -use crate::crypto::SupportedKxGroup; -use crate::enums::{ - AlertDescription, CipherSuite, HandshakeType, ProtocolVersion, SignatureAlgorithm, - SignatureScheme, -}; -use crate::error::{Error, PeerIncompatible, PeerMisbehaved}; +use super::config::{CipherSuiteSelector, VersionSuiteSelector}; +use super::{ClientHello, CommonServerSessionValue, ServerConfig, tls12, tls13}; +use crate::SupportedCipherSuite; +use crate::common_state::{Event, Output, OutputEvent, Protocol}; +use crate::conn::{ConnectionRandoms, Input}; +use crate::crypto::hash::Hash; +use crate::crypto::kx::{KeyExchangeAlgorithm, NamedGroup, SupportedKxGroup}; +use crate::crypto::{CipherSuite, CryptoProvider, SelectedCredential, SignatureScheme}; +use crate::enums::{ApplicationProtocol, CertificateType, HandshakeType, ProtocolVersion}; +use crate::error::{ApiMisuse, Error, PeerIncompatible, PeerMisbehaved}; use crate::hash_hs::{HandshakeHash, HandshakeHashBuffer}; +use crate::kernel::KernelState; use crate::log::{debug, trace}; -use crate::msgs::enums::{CertificateType, Compression, ExtensionType, NamedGroup}; -#[cfg(feature = "tls12")] -use crate::msgs::handshake::SessionId; -use crate::msgs::handshake::{ - ClientHelloPayload, ConvertProtocolNameList, ConvertServerNameList, HandshakePayload, - KeyExchangeAlgorithm, Random, ServerExtension, +use crate::msgs::{ + ClientHelloPayload, Compression, HandshakeAlignedProof, HandshakePayload, Message, + MessagePayload, Random, ServerExtensions, ServerExtensionsInput, ServerNamePayload, SessionId, + SingleProtocolName, TransportParameters, }; -use crate::msgs::message::{Message, MessagePayload}; -use crate::msgs::persist; -use crate::server::common::ActiveCertifiedKey; -use crate::server::{tls13, ClientHello, ServerConfig}; -use crate::{suites, SupportedCipherSuite}; - -pub(super) type NextState<'a> = Box + 'a>; -pub(super) type NextStateOrError<'a> = Result, Error>; -pub(super) type ServerContext<'a> = crate::common_state::Context<'a, ServerConnectionData>; - -pub(super) fn can_resume( - suite: SupportedCipherSuite, - sni: &Option>, - using_ems: bool, - resumedata: &persist::ServerSessionValue, -) -> bool { - // The RFCs underspecify what happens if we try to resume to - // an unoffered/varying suite. We merely don't resume in weird cases. - // - // RFC 6066 says "A server that implements this extension MUST NOT accept - // the request to resume the session if the server_name extension contains - // a different name. Instead, it proceeds with a full handshake to - // establish a new session." - resumedata.cipher_suite == suite.suite() - && (resumedata.extended_ms == using_ems || (resumedata.extended_ms && !using_ems)) - && &resumedata.sni == sni +use crate::sealed::Sealed; +use crate::suites::{PartiallyExtractedSecrets, Suite}; +use crate::sync::Arc; +use crate::tls12::Tls12CipherSuite; +use crate::tls13::Tls13CipherSuite; +use crate::tls13::key_schedule::KeyScheduleTrafficSend; + +pub(crate) enum ServerState { + /// Reading an entire ClientHello + /// + /// Validations that do not rely on a config can be performed here, but most are + /// deferred until [`ServerState::ClientHello`]. + ReadClientHello(ReadClientHello), + + /// Choose a [`ServerConfig`] based on the received ClientHello. + ChooseConfig(Box), + + /// Processing the received ClientHello. + ClientHello(Box), + Tls12(tls12::Tls12State), + Tls13(tls13::Tls13State), } -#[derive(Default)] -pub(super) struct ExtensionProcessing { - // extensions to reply with - pub(super) exts: Vec, - #[cfg(feature = "tls12")] - pub(super) send_ticket: bool, +impl ServerState { + pub(crate) fn set_resumption_data(&mut self, resumption_data: &[u8]) -> Result<(), Error> { + match self { + Self::ReadClientHello(e) => e.set_resumption_data(resumption_data), + Self::ChooseConfig(e) => e.set_resumption_data(resumption_data), + Self::ClientHello(e) => e.set_resumption_data(resumption_data), + _ => Err(ApiMisuse::ResumptionDataProvidedTooLate.into()), + } + } } -impl ExtensionProcessing { - pub(super) fn new() -> Self { - Default::default() +impl crate::conn::StateMachine for ServerState { + fn handle<'m>(self, input: Input<'m>, output: &mut dyn Output<'m>) -> Result { + match self { + Self::ReadClientHello(r) => r.handle(input, output), + Self::ChooseConfig(_) => { + Err(Error::Unreachable("ChooseConfig cannot process a message")) + } + Self::ClientHello(e) => e.handle(input, output), + Self::Tls12(sm) => sm.handle(input, output), + Self::Tls13(sm) => sm.handle(input, output), + } + } + + fn wants_input(&self) -> bool { + !matches!(self, Self::ChooseConfig(_)) + } + + fn handle_decrypt_error(&mut self) {} + + fn into_external_state( + self, + send_keys: &Option>, + ) -> Result<(PartiallyExtractedSecrets, Box), Error> { + match self { + Self::Tls13(tls13::Tls13State::Traffic(e)) => e.into_external_state(send_keys), + Self::Tls12(tls12::Tls12State::Traffic(e)) => e.into_external_state(send_keys), + _ => Err(Error::HandshakeNotComplete), + } } +} + +pub(super) struct Tls12Extensions { + pub(super) alpn_protocol: Option>, + pub(super) send_ticket: bool, +} - pub(super) fn process_common( - &mut self, +impl Tls12Extensions { + pub(super) fn new( + extra_exts: ServerExtensionsInput, + ocsp_response: &mut Option<&[u8]>, + resumedata: Option<&CommonServerSessionValue<'_>>, + hello: &ClientHelloPayload, + output: &mut dyn Output<'_>, + using_ems: bool, config: &ServerConfig, - cx: &mut ServerContext<'_>, + ) -> Result<(Self, Box>), Error> { + let ep = ExtensionProcessing::new(hello, config); + let (alpn_protocol, mut extensions) = + ep.process_common(extra_exts, output, ocsp_response, resumedata)?; + + // Renegotiation. + // (We don't do reneg at all, but would support the secure version if we did.) + if hello.renegotiation_info.is_some() + || hello + .cipher_suites + .contains(&CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + { + extensions.renegotiation_info = Some(Vec::new().into()); + } + + // Tickets: + // If we get any SessionTicket extension and have tickets enabled, + // we send an ack. + let send_ticket = if hello.session_ticket.is_some() && config.ticketer.is_some() { + extensions.session_ticket_ack = Some(()); + true + } else { + false + }; + + // Confirm use of EMS if offered. + if using_ems { + extensions.extended_master_secret_ack = Some(()); + } + + // Send confirmation of OCSP staple request if we will send one. + if let Some([_, ..]) = ocsp_response { + extensions.certificate_status_request_ack = Some(()); + } + + let out = Self { + alpn_protocol, + send_ticket, + }; + + Ok((out, extensions)) + } +} + +pub(super) struct Tls13Extensions { + pub(super) certificate_types: CertificateTypes, + pub(super) alpn_protocol: Option>, +} + +impl Tls13Extensions { + pub(super) fn new( + extra_exts: ServerExtensionsInput, ocsp_response: &mut Option<&[u8]>, + resumedata: Option<&CommonServerSessionValue<'_>>, hello: &ClientHelloPayload, - resumedata: Option<&persist::ServerSessionValue>, - extra_exts: Vec, - ) -> Result<(), Error> { - // ALPN - let our_protocols = &config.alpn_protocols; - let maybe_their_protocols = hello.alpn_extension(); - if let Some(their_protocols) = maybe_their_protocols { - let their_protocols = their_protocols.to_slices(); + output: &mut dyn Output<'_>, + config: &ServerConfig, + ) -> Result<(Self, Box>), Error> { + let ep = ExtensionProcessing::new(hello, config); + let (alpn_protocol, mut extensions) = + ep.process_common(extra_exts, output, ocsp_response, resumedata)?; + + let expected_client_type = select_cert_type( + hello + .client_certificate_types + .as_deref(), + config + .verifier + .supported_certificate_types(), + )?; + + let expected_server_type = select_cert_type( + hello + .server_certificate_types + .as_deref(), + config + .cert_resolver + .supported_certificate_types(), + )?; - if their_protocols - .iter() - .any(|protocol| protocol.is_empty()) - { - return Err(PeerMisbehaved::OfferedEmptyApplicationProtocol.into()); - } + if hello.client_certificate_types.is_some() && config.verifier.offer_client_auth() { + extensions.client_certificate_type = Some(expected_client_type); + } + if hello.server_certificate_types.is_some() { + extensions.server_certificate_type = Some(expected_server_type); + } - cx.common.alpn_protocol = our_protocols - .iter() - .find(|protocol| their_protocols.contains(&protocol.as_slice())) - .cloned(); - if let Some(ref selected_protocol) = cx.common.alpn_protocol { - debug!("Chosen ALPN protocol {:?}", selected_protocol); - self.exts - .push(ServerExtension::make_alpn(&[selected_protocol])); + let out = Self { + certificate_types: CertificateTypes { + client: expected_client_type, + }, + alpn_protocol, + }; + + Ok((out, extensions)) + } +} + +struct ExtensionProcessing<'a> { + config: &'a ServerConfig, + hello: &'a ClientHelloPayload, +} + +impl<'a> ExtensionProcessing<'a> { + fn new(client_hello: &'a ClientHelloPayload, config: &'a ServerConfig) -> Self { + Self { + config, + hello: client_hello, + } + } + + fn process_common( + self, + extra_exts: ServerExtensionsInput, + output: &mut dyn Output<'_>, + ocsp_response: &mut Option<&[u8]>, + resumedata: Option<&CommonServerSessionValue<'_>>, + ) -> Result< + ( + Option>, + Box>, + ), + Error, + > { + let Self { config, hello } = self; + let mut extensions = Box::new(ServerExtensions::default()); + + let ServerExtensionsInput { + transport_parameters, + } = extra_exts; + if let Some(TransportParameters::Quic(v)) = transport_parameters { + extensions.transport_parameters = Some(v); + } + + // ALPN + let our_protocols = &config.alpn_protocols; + let chosen_protocol = if let Some(their_protocols) = &hello.protocols { + if let Some(selected_protocol) = our_protocols.iter().find(|ours| { + their_protocols + .iter() + .any(|theirs| theirs.as_ref() == ours.as_ref()) + }) { + debug!("Chosen ALPN protocol {selected_protocol:?}"); + + Some(selected_protocol) } else if !our_protocols.is_empty() { - return Err(cx.common.send_fatal_alert( - AlertDescription::NoApplicationProtocol, - Error::NoApplicationProtocol, - )); + return Err(Error::NoApplicationProtocol); + } else { + None } + } else { + None + }; + + // Enact ALPN selection by telling peer and high-level API. + if let Some(protocol) = &chosen_protocol { + extensions.selected_protocol = Some(SingleProtocolName::new((*protocol).to_owned())); + output.output(OutputEvent::ApplicationProtocol((*protocol).to_owned())); } - if cx.common.is_quic() { + if let Some(quic) = output.quic() { // QUIC has strict ALPN, unlike TLS's more backwards-compatible behavior. RFC 9001 // says: "The server MUST treat the inability to select a compatible application // protocol as a connection error of type 0x0178". We judge that ALPN was desired @@ -114,196 +271,183 @@ impl ExtensionProcessing { // protocols were configured locally or offered by the client. This helps prevent // successful establishment of connections between peers that can't understand // each other. - if cx.common.alpn_protocol.is_none() - && (!our_protocols.is_empty() || maybe_their_protocols.is_some()) + if chosen_protocol.is_none() && (!our_protocols.is_empty() || hello.protocols.is_some()) { - return Err(cx.common.send_fatal_alert( - AlertDescription::NoApplicationProtocol, - Error::NoApplicationProtocol, - )); + return Err(Error::NoApplicationProtocol); } - match hello.quic_params_extension() { - Some(params) => cx.common.quic.params = Some(params), + match hello.transport_parameters.as_ref() { + Some(params) => quic.transport_parameters(params.to_owned().into_vec()), None => { - return Err(cx - .common - .missing_extension(PeerMisbehaved::MissingQuicTransportParameters)); + return Err(PeerMisbehaved::MissingQuicTransportParameters.into()); } } } let for_resume = resumedata.is_some(); // SNI - if !for_resume && hello.sni_extension().is_some() { - self.exts - .push(ServerExtension::ServerNameAck); + if let (false, Some(ServerNamePayload::SingleDnsName(_))) = (for_resume, &hello.server_name) + { + extensions.server_name_ack = Some(()); } - // Send status_request response if we have one. This is not allowed - // if we're resuming, and is only triggered if we have an OCSP response - // to send. - if !for_resume - && hello - .find_extension(ExtensionType::StatusRequest) - .is_some() + // Discard OCSP response if it is not necessary. + if for_resume + || hello + .certificate_status_request + .is_none() { - if ocsp_response.is_some() && !cx.common.is_tls13() { - // Only TLS1.2 sends confirmation in ServerHello - self.exts - .push(ServerExtension::CertificateStatusAck); - } - } else { - // Throw away any OCSP response so we don't try to send it later. ocsp_response.take(); } - self.validate_server_cert_type_extension(hello, config, cx)?; - self.validate_client_cert_type_extension(hello, config, cx)?; - - self.exts.extend(extra_exts); - - Ok(()) + Ok((chosen_protocol.map(|p| p.to_owned()), extensions)) } +} - #[cfg(feature = "tls12")] - pub(super) fn process_tls12( - &mut self, - config: &ServerConfig, - hello: &ClientHelloPayload, - using_ems: bool, - ) { - // Renegotiation. - // (We don't do reneg at all, but would support the secure version if we did.) - let secure_reneg_offered = hello - .find_extension(ExtensionType::RenegotiationInfo) - .is_some() - || hello - .cipher_suites - .contains(&CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV); +fn select_cert_type( + client: Option<&[CertificateType]>, + server: &[CertificateType], +) -> Result { + if server.is_empty() { + return Err(ApiMisuse::NoSupportedCertificateTypes.into()); + } - if secure_reneg_offered { - self.exts - .push(ServerExtension::make_empty_renegotiation_info()); + // https://www.rfc-editor.org/rfc/rfc7250#section-4.1 + // If the client has no remaining certificate types to send in + // the client hello, other than the default X.509 type, it MUST omit the + // client_certificate_type extension in the client hello. + + // If the client has no remaining certificate types to send in + // the client hello, other than the default X.509 certificate type, it + // MUST omit the entire server_certificate_type extension from the + // client hello. + let client = match client { + Some([]) => { + return Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()); + } + Some(c) => c, + None => { + return match server.contains(&CertificateType::X509) { + true => Ok(CertificateType::X509), + false => Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()), + }; } + }; - // Tickets: - // If we get any SessionTicket extension and have tickets enabled, - // we send an ack. - if hello - .find_extension(ExtensionType::SessionTicket) - .is_some() - && config.ticketer.enabled() - { - self.send_ticket = true; - self.exts - .push(ServerExtension::SessionTicketAck); + for &ct in client { + if server.contains(&ct) { + return Ok(ct); } + } - // Confirm use of EMS if offered. - if using_ems { - self.exts - .push(ServerExtension::ExtendedMasterSecretAck); + Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()) +} + +pub(super) struct CertificateTypes { + pub(super) client: CertificateType, +} + +pub(crate) struct ReadClientHello { + protocol: Protocol, + resumption_data: Vec, +} + +impl ReadClientHello { + pub(crate) fn new(protocol: Protocol) -> Self { + Self { + protocol, + resumption_data: Vec::new(), } } - fn validate_server_cert_type_extension( - &mut self, - hello: &ClientHelloPayload, - config: &ServerConfig, - cx: &mut ServerContext<'_>, - ) -> Result<(), Error> { - let requires_server_rpk = config - .cert_resolver - .only_raw_public_keys(); - let client_allows_rpk = hello - .server_certificate_extension() - .map(|certificate_types| certificate_types.contains(&CertificateType::RawPublicKey)) - .unwrap_or(false); - - let raw_key_negotation_params = RawKeyNegotiationParams { - peer_supports_raw_key: client_allows_rpk, - local_expects_raw_key: requires_server_rpk, - extension_type: ExtensionType::ServerCertificateType, - }; + pub(crate) fn handle<'m>( + self, + input: Input<'m>, + _output: &mut dyn Output<'_>, + ) -> Result { + ClientHelloInput::from_input(&input)?; + Ok(Box::new(ChooseConfig { + client_hello: Input { + message: input.message.into_owned(), + aligned_handshake: input.aligned_handshake, + }, + resumption_data: self.resumption_data, + protocol: self.protocol, + }) + .into()) + } - self.process_cert_type_extension( - raw_key_negotation_params.validate_raw_key_negotiation(), - cx, - ) + fn set_resumption_data(&mut self, resumption_data: &[u8]) -> Result<(), Error> { + self.resumption_data = resumption_data.to_vec(); + Ok(()) } +} - fn validate_client_cert_type_extension( - &mut self, - hello: &ClientHelloPayload, - config: &ServerConfig, - cx: &mut ServerContext<'_>, - ) -> Result<(), Error> { - let requires_client_rpk = config - .verifier - .requires_raw_public_keys(); - let client_offers_rpk = hello - .client_certificate_extension() - .map(|certificate_types| certificate_types.contains(&CertificateType::RawPublicKey)) - .unwrap_or(false); - - let raw_key_negotation_params = RawKeyNegotiationParams { - peer_supports_raw_key: client_offers_rpk, - local_expects_raw_key: requires_client_rpk, - extension_type: ExtensionType::ClientCertificateType, - }; - self.process_cert_type_extension( - raw_key_negotation_params.validate_raw_key_negotiation(), - cx, - ) +impl From for ServerState { + fn from(value: ReadClientHello) -> Self { + Self::ReadClientHello(value) } +} - fn process_cert_type_extension( - &mut self, - raw_key_negotiation_result: RawKeyNegotationResult, - cx: &mut ServerContext<'_>, - ) -> Result<(), Error> { - match raw_key_negotiation_result { - RawKeyNegotationResult::Negotiated(ExtensionType::ClientCertificateType) => { - self.exts - .push(ServerExtension::ClientCertType( - CertificateType::RawPublicKey, - )); - } - RawKeyNegotationResult::Negotiated(ExtensionType::ServerCertificateType) => { - self.exts - .push(ServerExtension::ServerCertType( - CertificateType::RawPublicKey, - )); - } - RawKeyNegotationResult::Err(err) => { - return Err(cx - .common - .send_fatal_alert(AlertDescription::HandshakeFailure, err)); - } - RawKeyNegotationResult::NotNegotiated => {} - RawKeyNegotationResult::Negotiated(_) => unreachable!( - "The extension type should only ever be ClientCertificateType or ServerCertificateType" - ), +pub(crate) struct ChooseConfig { + protocol: Protocol, + resumption_data: Vec, + client_hello: Input<'static>, +} + +impl ChooseConfig { + pub(crate) fn use_config( + self, + config: Arc, + extra_exts: ServerExtensionsInput, + output: &mut dyn Output<'_>, + ) -> Result { + ExpectClientHello::new(config, extra_exts, self.resumption_data, self.protocol) + .with_input(ClientHelloInput::from_input(&self.client_hello)?, output) + } + + pub(crate) fn client_hello(&self) -> &ClientHelloPayload { + match &self.client_hello.message.payload { + MessagePayload::Handshake { parsed, .. } => match &parsed.0 { + HandshakePayload::ClientHello(ch) => ch, + _ => unreachable!(), + }, + _ => unreachable!(), } + } + + fn set_resumption_data(&mut self, resumption_data: &[u8]) -> Result<(), Error> { + self.resumption_data = resumption_data.to_vec(); Ok(()) } } -pub(super) struct ExpectClientHello { +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::ChooseConfig(value) + } +} + +pub(crate) struct ExpectClientHello { pub(super) config: Arc, - pub(super) extra_exts: Vec, + pub(super) protocol: Protocol, + pub(super) extra_exts: ServerExtensionsInput, pub(super) transcript: HandshakeHashOrBuffer, - #[cfg(feature = "tls12")] pub(super) session_id: SessionId, - #[cfg(feature = "tls12")] + pub(super) sni: Option>, + pub(super) resumption_data: Vec, pub(super) using_ems: bool, pub(super) done_retry: bool, pub(super) send_tickets: usize, } impl ExpectClientHello { - pub(super) fn new(config: Arc, extra_exts: Vec) -> Self { + pub(super) fn new( + config: Arc, + extra_exts: ServerExtensionsInput, + resumption_data: Vec, + protocol: Protocol, + ) -> Self { let mut transcript_buffer = HandshakeHashBuffer::new(); if config.verifier.offer_client_auth() { @@ -312,11 +456,12 @@ impl ExpectClientHello { Self { config, + protocol, extra_exts, transcript: HandshakeHashOrBuffer::Buffer(transcript_buffer), - #[cfg(feature = "tls12")] session_id: SessionId::empty(), - #[cfg(feature = "tls12")] + sni: None, + resumption_data, using_ems: false, done_retry: false, send_tickets: 0, @@ -324,13 +469,11 @@ impl ExpectClientHello { } /// Continues handling of a `ClientHello` message once config and certificate are available. - pub(super) fn with_certified_key( + pub(super) fn with_input( self, - mut sig_schemes: Vec, - client_hello: &ClientHelloPayload, - m: &Message<'_>, - cx: &mut ServerContext<'_>, - ) -> NextStateOrError<'static> { + input: ClientHelloInput<'_>, + output: &mut dyn Output<'_>, + ) -> Result { let tls13_enabled = self .config .supports_version(ProtocolVersion::TLSv1_3); @@ -339,196 +482,139 @@ impl ExpectClientHello { .supports_version(ProtocolVersion::TLSv1_2); // Are we doing TLS1.3? - let maybe_versions_ext = client_hello.versions_extension(); - let version = if let Some(versions) = maybe_versions_ext { - if versions.contains(&ProtocolVersion::TLSv1_3) && tls13_enabled { - ProtocolVersion::TLSv1_3 - } else if !versions.contains(&ProtocolVersion::TLSv1_2) || !tls12_enabled { - return Err(cx.common.send_fatal_alert( - AlertDescription::ProtocolVersion, - PeerIncompatible::Tls12NotOfferedOrEnabled, - )); - } else if cx.common.is_quic() { - return Err(cx.common.send_fatal_alert( - AlertDescription::ProtocolVersion, - PeerIncompatible::Tls13RequiredForQuic, - )); + if let Some(versions) = &input.client_hello.supported_versions { + if versions.tls13 && tls13_enabled { + self.with_version::(input, output) + } else if !versions.tls12 || !tls12_enabled { + Err(PeerIncompatible::Tls12NotOfferedOrEnabled.into()) + } else if self.protocol.is_quic() { + Err(PeerIncompatible::Tls13RequiredForQuic.into()) } else { - ProtocolVersion::TLSv1_2 + self.with_version::(input, output) } - } else if u16::from(client_hello.client_version) < u16::from(ProtocolVersion::TLSv1_2) { - return Err(cx.common.send_fatal_alert( - AlertDescription::ProtocolVersion, - PeerIncompatible::Tls12NotOffered, - )); + } else if u16::from(input.client_hello.client_version) < u16::from(ProtocolVersion::TLSv1_2) + { + Err(PeerIncompatible::Tls12NotOffered.into()) } else if !tls12_enabled && tls13_enabled { - return Err(cx.common.send_fatal_alert( - AlertDescription::ProtocolVersion, - PeerIncompatible::SupportedVersionsExtensionRequired, - )); - } else if cx.common.is_quic() { - return Err(cx.common.send_fatal_alert( - AlertDescription::ProtocolVersion, - PeerIncompatible::Tls13RequiredForQuic, - )); + Err(PeerIncompatible::SupportedVersionsExtensionRequired.into()) + } else if self.protocol.is_quic() { + Err(PeerIncompatible::Tls13RequiredForQuic.into()) } else { - ProtocolVersion::TLSv1_2 - }; + self.with_version::(input, output) + } + } + + fn with_version( + mut self, + input: ClientHelloInput<'_>, + output: &mut dyn Output<'_>, + ) -> Result + where + CryptoProvider: Borrow<[&'static T]>, + SupportedCipherSuite: From<&'static T>, + dyn CipherSuiteSelector: VersionSuiteSelector, + { + output.output(OutputEvent::ProtocolVersion(T::VERSION)); - cx.common.negotiated_version = Some(version); + let sni = self + .config + .invalid_sni_policy + .accept(input.client_hello.server_name.as_ref())?; + output.emit(Event::ReceivedServerName(sni.clone())); + + if self.done_retry { + let ch_sni = input + .client_hello + .server_name + .as_ref() + .and_then(ServerNamePayload::to_dns_name_normalized); + if self.sni != ch_sni { + return Err(PeerMisbehaved::ServerNameDifferedOnRetry.into()); + } + } // We communicate to the upper layer what kind of key they should choose // via the sigschemes value. Clients tend to treat this extension // orthogonally to offered ciphersuites (even though, in TLS1.2 it is not). // So: reduce the offered sigschemes to those compatible with the // intersection of ciphersuites. - let client_suites = self - .config - .provider - .cipher_suites + let suites = >::borrow(&self.config.provider); + let client_suites = suites .iter() - .copied() - .filter(|scs| { - client_hello + .filter(|&&scs| { + input + .client_hello .cipher_suites .contains(&scs.suite()) }) .collect::>(); - sig_schemes - .retain(|scheme| suites::compatible_sigscheme_for_suites(*scheme, &client_suites)); + let mut sig_schemes = input.sig_schemes.clone(); + if T::VERSION == ProtocolVersion::TLSv1_2 { + sig_schemes.retain(|scheme| { + client_suites + .iter() + .any(|&suite| suite.usable_for_signature_scheme(*scheme)) + }); + } else if T::VERSION == ProtocolVersion::TLSv1_3 { + sig_schemes.retain(SignatureScheme::supported_in_tls13); + } - // We adhere to the TLS 1.2 RFC by not exposing this to the cert resolver if TLS version is 1.2 - let certificate_authorities = match version { - ProtocolVersion::TLSv1_2 => None, - _ => client_hello.certificate_authorities_extension(), - }; // Choose a certificate. - let certkey = { - let client_hello = ClientHello { - server_name: &cx.data.sni, - signature_schemes: &sig_schemes, - alpn: client_hello.alpn_extension(), - client_cert_types: client_hello.server_certificate_extension(), - server_cert_types: client_hello.client_certificate_extension(), - cipher_suites: &client_hello.cipher_suites, - certificate_authorities, - }; - trace!("Resolving server certificate: {client_hello:#?}"); - - let certkey = self - .config - .cert_resolver - .resolve(client_hello); - - certkey.ok_or_else(|| { - cx.common.send_fatal_alert( - AlertDescription::AccessDenied, - Error::General("no server certificate chain resolved".to_owned()), - ) - })? - }; - let certkey = ActiveCertifiedKey::from_certified_key(&certkey); - - let (suite, skxg) = self - .choose_suite_and_kx_group( - version, - certkey.get_key().algorithm(), - cx.common.protocol, - client_hello - .namedgroups_extension() - .unwrap_or(&[]), - &client_hello.cipher_suites, - ) - .map_err(|incompat| { - cx.common - .send_fatal_alert(AlertDescription::HandshakeFailure, incompat) - })?; - - debug!("decided upon suite {:?}", suite); - cx.common.suite = Some(suite); - cx.common.kx_state = KxState::Start(skxg); - - // Start handshake hash. - let starting_hash = suite.hash_provider(); - let transcript = match self.transcript { - HandshakeHashOrBuffer::Buffer(inner) => inner.start_hash(starting_hash), - HandshakeHashOrBuffer::Hash(inner) - if inner.algorithm() == starting_hash.algorithm() => - { - inner - } - _ => { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::HandshakeHashVariedAfterRetry, - )); - } - }; - - // Save their Random. - let randoms = ConnectionRandoms::new( - client_hello.random, - Random::new(self.config.provider.secure_random)?, - ); - match suite { - SupportedCipherSuite::Tls13(suite) => tls13::CompleteClientHelloHandling { - config: self.config, - transcript, - suite, - randoms, - done_retry: self.done_retry, - send_tickets: self.send_tickets, - extra_exts: self.extra_exts, - } - .handle_client_hello(cx, certkey, m, client_hello, skxg, sig_schemes), - #[cfg(feature = "tls12")] - SupportedCipherSuite::Tls12(suite) => tls12::CompleteClientHelloHandling { - config: self.config, - transcript, - session_id: self.session_id, - suite, - using_ems: self.using_ems, - randoms, - send_ticket: self.send_tickets > 0, - extra_exts: self.extra_exts, - } - .handle_client_hello( - cx, - certkey, - m, - client_hello, - skxg, - sig_schemes, - tls13_enabled, - ), - } + let credentials = self + .config + .cert_resolver + .resolve(&ClientHello::new( + &input, + &sig_schemes, + sni.as_ref(), + T::VERSION, + ))?; + self.sni = sni; + + let (suite, skxg) = self.choose_suite_and_kx_group( + suites, + credentials.signer.scheme(), + input + .client_hello + .named_groups + .as_deref() + .unwrap_or_default(), + &input.client_hello.cipher_suites, + )?; + + debug!("decided upon suite {suite:?}"); + output.output(OutputEvent::CipherSuite(suite.into())); + + suite + .server_handler() + .handle_client_hello(suite, skxg, credentials, input, self, output) } - fn choose_suite_and_kx_group( + fn choose_suite_and_kx_group( &self, - selected_version: ProtocolVersion, - sig_key_algorithm: SignatureAlgorithm, - protocol: Protocol, + suites: &[&'static T], + sig_scheme: SignatureScheme, client_groups: &[NamedGroup], client_suites: &[CipherSuite], - ) -> Result<(SupportedCipherSuite, &'static dyn SupportedKxGroup), PeerIncompatible> { + ) -> Result<(&'static T, &'static dyn SupportedKxGroup), PeerIncompatible> + where + SupportedCipherSuite: From<&'static T>, + dyn CipherSuiteSelector: VersionSuiteSelector, + { // Determine which `KeyExchangeAlgorithm`s are theoretically possible, based // on the offered and supported groups. let mut ecdhe_possible = false; let mut ffdhe_possible = false; let mut ffdhe_offered = false; - let mut supported_groups = Vec::with_capacity(client_groups.len()); + let mut supported_groups: Vec<&'static dyn SupportedKxGroup> = + Vec::with_capacity(client_groups.len()); for offered_group in client_groups { let supported = self .config .provider - .kx_groups - .iter() - .find(|skxg| { - skxg.usable_for_version(selected_version) && skxg.name() == *offered_group - }); + .find_kx_group(*offered_group, T::VERSION); match offered_group.key_exchange_algorithm() { KeyExchangeAlgorithm::DHE => { @@ -540,10 +626,12 @@ impl ExpectClientHello { } } - supported_groups.push(supported); + if let Some(supported) = supported { + supported_groups.push(supported); + } } - let first_supported_dhe_kxg = if selected_version == ProtocolVersion::TLSv1_2 { + let first_supported_dhe_kxg = if T::VERSION == ProtocolVersion::TLSv1_2 { // https://datatracker.ietf.org/doc/html/rfc7919#section-4 (paragraph 2) let first_supported_dhe_kxg = self .config @@ -562,69 +650,55 @@ impl ExpectClientHello { return Err(PeerIncompatible::NoKxGroupsInCommon); } - let mut suitable_suites_iter = self - .config - .provider - .cipher_suites - .iter() - .filter(|suite| { - // Reduce our supported ciphersuites by the certified key's algorithm. - suite.usable_for_signature_algorithm(sig_key_algorithm) - // And version - && suite.version().version == selected_version - // And protocol - && suite.usable_for_protocol(protocol) - // And support one of key exchange groups - && (ecdhe_possible && suite.usable_for_kx_algorithm(KeyExchangeAlgorithm::ECDHE) - || ffdhe_possible && suite.usable_for_kx_algorithm(KeyExchangeAlgorithm::DHE)) - }); - // RFC 7919 (https://datatracker.ietf.org/doc/html/rfc7919#section-4) requires us to send // the InsufficientSecurity alert in case we don't recognize client's FFDHE groups (i.e., // `suitable_suites` becomes empty). But that does not make a lot of sense (e.g., client // proposes FFDHE4096 and we only support FFDHE2048), so we ignore that requirement here, // and continue to send HandshakeFailure. - let suite = if self.config.ignore_client_order { - suitable_suites_iter.find(|suite| client_suites.contains(&suite.suite())) - } else { - let suitable_suites = suitable_suites_iter.collect::>(); - client_suites - .iter() - .find_map(|client_suite| { - suitable_suites - .iter() - .find(|x| *client_suite == x.suite()) - }) - .copied() - } - .ok_or(PeerIncompatible::NoCipherSuitesInCommon)?; + let mut client_suites = client_suites + .iter() + .filter_map(|&suite| { + let &suite = suites + .iter() + .find(|ss| ss.suite() == suite)?; + + // Reduce our supported ciphersuites by the certified key's algorithm. + (suite.usable_for_signature_scheme(sig_scheme) + // And support for one of the key exchange groups + && (ecdhe_possible && suite.usable_for_kx_algorithm(KeyExchangeAlgorithm::ECDHE) + || ffdhe_possible && suite.usable_for_kx_algorithm(KeyExchangeAlgorithm::DHE))) + .then_some(suite) + }); + + let suite = self + .config + .cipher_suite_selector + .select(&mut client_suites, suites) + .ok_or(PeerIncompatible::NoCipherSuitesInCommon)?; // Finally, choose a key exchange group that is compatible with the selected cipher // suite. let maybe_skxg = supported_groups .iter() - .find_map(|maybe_skxg| match maybe_skxg { - Some(skxg) => suite - .usable_for_kx_algorithm(skxg.name().key_exchange_algorithm()) - .then_some(*skxg), - None => None, + .find(|kx_group| { + suite.usable_for_kx_algorithm(kx_group.name().key_exchange_algorithm()) }); - if selected_version == ProtocolVersion::TLSv1_3 { + if T::VERSION == ProtocolVersion::TLSv1_3 { // This unwrap is structurally guaranteed by the early return for `!ffdhe_possible && !ecdhe_possible` - return Ok((*suite, *maybe_skxg.unwrap())); + return Ok((suite, *maybe_skxg.unwrap())); } // For TLS1.2, the server can unilaterally choose a DHE group if it has one and // there was no better option. match maybe_skxg { - Some(skxg) => Ok((*suite, *skxg)), + Some(skxg) => Ok((suite, *skxg)), None if suite.usable_for_kx_algorithm(KeyExchangeAlgorithm::DHE) => { // If kx for the selected cipher suite is DHE and no DHE groups are specified in the extension, // the server is free to choose DHE params, we choose the first DHE kx group of the provider. if let Some(server_selected_ffdhe_skxg) = first_supported_dhe_kxg { - Ok((*suite, *server_selected_ffdhe_skxg)) + Ok((suite, *server_selected_ffdhe_skxg)) } else { Err(PeerIncompatible::NoKxGroupsInCommon) } @@ -632,113 +706,107 @@ impl ExpectClientHello { None => Err(PeerIncompatible::NoKxGroupsInCommon), } } + + pub(super) fn randoms(&self, input: &ClientHelloInput<'_>) -> Result { + Ok(ConnectionRandoms::new( + input.client_hello.random, + Random::new(self.config.provider.secure_random)?, + )) + } } -impl State for ExpectClientHello { - fn handle<'m>( - self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> NextStateOrError<'m> - where - Self: 'm, - { - let (client_hello, sig_schemes) = process_client_hello(&m, self.done_retry, cx)?; - self.with_certified_key(sig_schemes, client_hello, &m, cx) +impl ExpectClientHello { + pub(crate) fn handle<'m>( + self, + input: Input<'m>, + output: &mut dyn Output<'_>, + ) -> Result { + let input = ClientHelloInput::from_input(&input)?; + self.with_input(input, output) } - fn into_owned(self: Box) -> NextState<'static> { - self + fn set_resumption_data(&mut self, resumption_data: &[u8]) -> Result<(), Error> { + self.resumption_data = resumption_data.to_vec(); + Ok(()) } } -/// Configuration-independent validation of a `ClientHello` message. -/// -/// This represents the first part of the `ClientHello` handling, where we do all validation that -/// doesn't depend on a `ServerConfig` being available and extract everything needed to build a -/// [`ClientHello`] value for a [`ResolvesServerCert`]. -/// -/// Note that this will modify `data.sni` even if config or certificate resolution fail. -/// -/// [`ResolvesServerCert`]: crate::server::ResolvesServerCert -pub(super) fn process_client_hello<'m>( - m: &'m Message<'m>, - done_retry: bool, - cx: &mut ServerContext<'_>, -) -> Result<(&'m ClientHelloPayload, Vec), Error> { - let client_hello = - require_handshake_msg!(m, HandshakeType::ClientHello, HandshakePayload::ClientHello)?; - trace!("we got a clienthello {:?}", client_hello); - - if !client_hello - .compression_methods - .contains(&Compression::Null) - { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerIncompatible::NullCompressionRequired, - )); +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::ClientHello(value) } +} - if client_hello.has_duplicate_extension() { - return Err(cx.common.send_fatal_alert( - AlertDescription::DecodeError, - PeerMisbehaved::DuplicateClientHelloExtensions, - )); - } +pub(crate) trait ServerHandler: fmt::Debug + Sealed + Send + Sync { + fn handle_client_hello( + &self, + suite: &'static T, + kx_group: &'static dyn SupportedKxGroup, + credentials: SelectedCredential, + input: ClientHelloInput<'_>, + st: ExpectClientHello, + output: &mut dyn Output<'_>, + ) -> Result; +} - // No handshake messages should follow this one in this flight. - cx.common.check_aligned_handshake()?; - - // Extract and validate the SNI DNS name, if any, before giving it to - // the cert resolver. In particular, if it is invalid then we should - // send an Illegal Parameter alert instead of the Internal Error alert - // (or whatever) that we'd send if this were checked later or in a - // different way. - let sni: Option> = match client_hello.sni_extension() { - Some(sni) => { - if sni.has_duplicate_names_for_type() { - return Err(cx.common.send_fatal_alert( - AlertDescription::DecodeError, - PeerMisbehaved::DuplicateServerNameTypes, - )); - } +pub(crate) struct ClientHelloInput<'a> { + pub(super) message: &'a Message<'a>, + pub(super) client_hello: &'a ClientHelloPayload, + pub(super) sig_schemes: &'a Vec, + pub(super) proof: HandshakeAlignedProof, +} - if let Some(hostname) = sni.single_hostname() { - Some(hostname.to_lowercase_owned()) - } else { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::ServerNameMustContainOneHostName, - )); - } +impl<'a> ClientHelloInput<'a> { + /// Configuration-independent validation of a `ClientHello` message. + /// + /// This represents the first part of the `ClientHello` handling, where we do all validation that + /// doesn't depend on a `ServerConfig` being available and extract everything needed to build a + /// [`ClientHello`] value for a [`ServerCredentialResolver`]. + /// + /// [`ServerCredentialResolver`]: crate::server::ServerCredentialResolver + pub(super) fn from_input(input: &'a Input<'a>) -> Result { + let client_hello = require_handshake_msg!( + input.message, + HandshakeType::ClientHello, + HandshakePayload::ClientHello + )?; + trace!("we got a clienthello {client_hello:?}"); + + if !client_hello + .compression_methods + .contains(&Compression::Null) + { + return Err(PeerIncompatible::NullCompressionRequired.into()); } - None => None, - }; - // save only the first SNI - if let (Some(sni), false) = (&sni, done_retry) { - // Save the SNI into the session. - // The SNI hostname is immutable once set. - assert!(cx.data.sni.is_none()); - cx.data.sni = Some(sni.clone()); - } else if cx.data.sni != sni { - return Err(PeerMisbehaved::ServerNameDifferedOnRetry.into()); - } + // No handshake messages should follow this one in this flight. + let proof = input.check_aligned_handshake()?; - let sig_schemes = client_hello - .sigalgs_extension() - .ok_or_else(|| { - cx.common.send_fatal_alert( - AlertDescription::HandshakeFailure, - PeerIncompatible::SignatureAlgorithmsExtensionRequired, - ) - })?; + let sig_schemes = client_hello + .signature_schemes + .as_ref() + .ok_or(PeerIncompatible::SignatureAlgorithmsExtensionRequired)?; - Ok((client_hello, sig_schemes.to_owned())) + Ok(ClientHelloInput { + message: &input.message, + client_hello, + sig_schemes, + proof, + }) + } } pub(crate) enum HandshakeHashOrBuffer { Buffer(HandshakeHashBuffer), Hash(HandshakeHash), } + +impl HandshakeHashOrBuffer { + pub(super) fn start(self, hash: &'static dyn Hash) -> Result { + match self { + Self::Buffer(inner) => Ok(inner.start_hash(hash)), + Self::Hash(inner) if inner.algorithm() == hash.algorithm() => Ok(inner), + _ => Err(PeerMisbehaved::HandshakeHashVariedAfterRetry.into()), + } + } +} diff --git a/rustls/src/server/mod.rs b/rustls/src/server/mod.rs new file mode 100644 index 00000000000..08cdbdedd75 --- /dev/null +++ b/rustls/src/server/mod.rs @@ -0,0 +1,219 @@ +use alloc::vec::Vec; + +use pki_types::{DnsName, UnixTime}; + +use crate::crypto::cipher::Payload; +use crate::crypto::{CipherSuite, Identity}; +use crate::enums::{ApplicationProtocol, ProtocolVersion}; +use crate::error::InvalidMessage; +use crate::msgs::{Codec, MaybeEmpty, Reader, SessionId, SizedPayload}; +pub use crate::verify::NoClientAuth; +#[cfg(feature = "webpki")] +pub use crate::webpki::{ + ClientVerifierBuilder, ParsedCertificate, VerifierBuilderError, WebPkiClientVerifier, +}; + +pub(crate) mod config; +pub use config::{ + CipherSuiteSelector, ClientHello, InvalidSniPolicy, PreferClientOrder, PreferServerOrder, + ServerConfig, ServerCredentialResolver, StoresServerSessions, WantsServerCert, +}; + +mod connection; +pub use connection::{ + Accepted, AcceptedAlert, Acceptor, ReadEarlyData, ServerConnection, ServerSide, +}; + +pub(crate) mod handy; +#[cfg(feature = "webpki")] +pub use handy::ServerNameResolver; +pub use handy::{NoServerSessionStorage, ServerSessionMemoryCache}; + +mod hs; +pub(crate) use hs::ServerHandler; + +mod tls12; +pub(crate) use tls12::TLS12_HANDLER; +use tls12::Tls12ServerSessionValue; + +mod tls13; +pub(crate) use tls13::TLS13_HANDLER; +use tls13::Tls13ServerSessionValue; + +/// Dangerous configuration that should be audited and used with extreme care. +pub mod danger { + pub use crate::verify::{ + ClientIdentity, ClientVerifier, PeerVerified, SignatureVerificationInput, + }; +} + +#[cfg(test)] +mod test; + +#[derive(Debug)] +pub(crate) enum ServerSessionValue<'a> { + Tls12(Tls12ServerSessionValue<'a>), + Tls13(Tls13ServerSessionValue<'a>), +} + +impl<'a> Codec<'a> for ServerSessionValue<'a> { + fn encode(&self, bytes: &mut Vec) { + match self { + Self::Tls12(value) => { + ProtocolVersion::TLSv1_2.encode(bytes); + value.encode(bytes); + } + Self::Tls13(value) => { + ProtocolVersion::TLSv1_3.encode(bytes); + value.encode(bytes); + } + } + } + + fn read(r: &mut Reader<'a>) -> Result { + match ProtocolVersion::read(r)? { + ProtocolVersion::TLSv1_2 => Ok(Self::Tls12(Tls12ServerSessionValue::read(r)?)), + ProtocolVersion::TLSv1_3 => Ok(Self::Tls13(Tls13ServerSessionValue::read(r)?)), + _ => Err(InvalidMessage::UnknownProtocolVersion), + } + } +} + +#[derive(Debug)] +pub(crate) struct CommonServerSessionValue<'a> { + pub(crate) creation_time_sec: u64, + pub(crate) sni: Option>, + pub(crate) cipher_suite: CipherSuite, + pub(crate) peer_identity: Option>, + pub(crate) alpn: Option>, + pub(crate) application_data: SizedPayload<'a, u16, MaybeEmpty>, +} + +impl<'a> CommonServerSessionValue<'a> { + pub(crate) fn new( + sni: Option<&DnsName<'a>>, + cipher_suite: CipherSuite, + peer_identity: Option>, + alpn: Option>, + application_data: Vec, + creation_time: UnixTime, + ) -> Self { + Self { + creation_time_sec: creation_time.as_secs(), + sni: sni.map(|s| s.to_owned()), + cipher_suite, + peer_identity, + alpn, + application_data: SizedPayload::from(Payload::new(application_data)), + } + } + + fn into_owned(self) -> CommonServerSessionValue<'static> { + CommonServerSessionValue { + creation_time_sec: self.creation_time_sec, + sni: self.sni.map(|s| s.to_owned()), + cipher_suite: self.cipher_suite, + peer_identity: self + .peer_identity + .map(|i| i.into_owned()), + alpn: self.alpn.map(|a| a.to_owned()), + application_data: self.application_data.into_owned(), + } + } + + pub(crate) fn can_resume(&self, suite: CipherSuite, sni: Option<&DnsName<'_>>) -> bool { + // The RFCs underspecify what happens if we try to resume to + // an unoffered/varying suite. We merely don't resume in weird cases. + // + // RFC 6066 says "A server that implements this extension MUST NOT accept + // the request to resume the session if the server_name extension contains + // a different name. Instead, it proceeds with a full handshake to + // establish a new session." + // + // RFC 8446: "The server MUST ensure that it selects + // a compatible PSK (if any) and cipher suite." + self.cipher_suite == suite && self.sni.as_ref() == sni + } +} + +impl Codec<'_> for CommonServerSessionValue<'_> { + fn encode(&self, bytes: &mut Vec) { + self.creation_time_sec.encode(bytes); + if let Some(sni) = &self.sni { + 1u8.encode(bytes); + let sni_bytes: &str = sni.as_ref(); + SizedPayload::::from(Payload::Borrowed(sni_bytes.as_bytes())) + .encode(bytes); + } else { + 0u8.encode(bytes); + } + self.cipher_suite.encode(bytes); + if let Some(identity) = &self.peer_identity { + 1u8.encode(bytes); + identity.encode(bytes); + } else { + 0u8.encode(bytes); + } + if let Some(alpn) = &self.alpn { + 1u8.encode(bytes); + alpn.encode(bytes); + } else { + 0u8.encode(bytes); + } + self.application_data.encode(bytes); + } + + fn read(r: &mut Reader<'_>) -> Result { + let creation_time_sec = u64::read(r)?; + let sni = match u8::read(r)? { + 1 => { + let dns_name = SizedPayload::::read(r)?; + let dns_name = match DnsName::try_from(dns_name.bytes()) { + Ok(dns_name) => dns_name.to_owned(), + Err(_) => return Err(InvalidMessage::InvalidServerName), + }; + + Some(dns_name) + } + _ => None, + }; + + Ok(Self { + creation_time_sec, + sni, + cipher_suite: CipherSuite::read(r)?, + peer_identity: match u8::read(r)? { + 1 => Some(Identity::read(r)?.into_owned()), + _ => None, + }, + alpn: match u8::read(r)? { + 1 => Some(ApplicationProtocol::read(r)?.to_owned()), + _ => None, + }, + application_data: SizedPayload::read(r)?.into_owned(), + }) + } +} + +/// A key that identifies a server-side resumable session. +pub struct ServerSessionKey<'a> { + inner: &'a [u8], +} + +impl<'a> ServerSessionKey<'a> { + pub(crate) fn new(inner: &'a [u8]) -> Self { + Self { inner } + } +} + +impl<'a> From<&'a SessionId> for ServerSessionKey<'a> { + fn from(session_id: &'a SessionId) -> Self { + Self::new(session_id.as_ref()) + } +} + +impl AsRef<[u8]> for ServerSessionKey<'_> { + fn as_ref(&self) -> &[u8] { + self.inner + } +} diff --git a/rustls/src/server/server_conn.rs b/rustls/src/server/server_conn.rs deleted file mode 100644 index ad46cdf28f2..00000000000 --- a/rustls/src/server/server_conn.rs +++ /dev/null @@ -1,1191 +0,0 @@ -use alloc::boxed::Box; -use alloc::sync::Arc; -use alloc::vec::Vec; -use core::fmt; -use core::fmt::{Debug, Formatter}; -use core::marker::PhantomData; -use core::ops::{Deref, DerefMut}; -#[cfg(feature = "std")] -use std::io; - -use pki_types::{DnsName, UnixTime}; - -use super::hs; -use crate::builder::ConfigBuilder; -use crate::common_state::{CommonState, Side}; -#[cfg(feature = "std")] -use crate::common_state::{Protocol, State}; -use crate::conn::{ConnectionCommon, ConnectionCore, UnbufferedConnectionCommon}; -#[cfg(doc)] -use crate::crypto; -use crate::crypto::CryptoProvider; -use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme}; -use crate::error::Error; -use crate::log::trace; -use crate::msgs::base::Payload; -use crate::msgs::enums::CertificateType; -use crate::msgs::handshake::{ClientHelloPayload, ProtocolName, ServerExtension}; -use crate::msgs::message::Message; -#[cfg(feature = "std")] -use crate::time_provider::DefaultTimeProvider; -use crate::time_provider::TimeProvider; -use crate::vecbuf::ChunkVecBuffer; -#[cfg(feature = "std")] -use crate::WantsVerifier; -use crate::{compress, sign, verify, versions, DistinguishedName, KeyLog, WantsVersions}; - -/// A trait for the ability to store server session data. -/// -/// The keys and values are opaque. -/// -/// Inserted keys are randomly chosen by the library and have -/// no internal structure (in other words, you may rely on all -/// bits being uniformly random). Queried keys are untrusted data. -/// -/// Both the keys and values should be treated as -/// **highly sensitive data**, containing enough key material -/// to break all security of the corresponding sessions. -/// -/// Implementations can be lossy (in other words, forgetting -/// key/value pairs) without any negative security consequences. -/// -/// However, note that `take` **must** reliably delete a returned -/// value. If it does not, there may be security consequences. -/// -/// `put` and `take` are mutating operations; this isn't expressed -/// in the type system to allow implementations freedom in -/// how to achieve interior mutability. `Mutex` is a common -/// choice. -pub trait StoresServerSessions: Debug + Send + Sync { - /// Store session secrets encoded in `value` against `key`, - /// overwrites any existing value against `key`. Returns `true` - /// if the value was stored. - fn put(&self, key: Vec, value: Vec) -> bool; - - /// Find a value with the given `key`. Return it, or None - /// if it doesn't exist. - fn get(&self, key: &[u8]) -> Option>; - - /// Find a value with the given `key`. Return it and delete it; - /// or None if it doesn't exist. - fn take(&self, key: &[u8]) -> Option>; - - /// Whether the store can cache another session. This is used to indicate to clients - /// whether their session can be resumed; the implementation is not required to remember - /// a session even if it returns `true` here. - fn can_cache(&self) -> bool; -} - -/// A trait for the ability to encrypt and decrypt tickets. -pub trait ProducesTickets: Debug + Send + Sync { - /// Returns true if this implementation will encrypt/decrypt - /// tickets. Should return false if this is a dummy - /// implementation: the server will not send the SessionTicket - /// extension and will not call the other functions. - fn enabled(&self) -> bool; - - /// Returns the lifetime in seconds of tickets produced now. - /// The lifetime is provided as a hint to clients that the - /// ticket will not be useful after the given time. - /// - /// This lifetime must be implemented by key rolling and - /// erasure, *not* by storing a lifetime in the ticket. - /// - /// The objective is to limit damage to forward secrecy caused - /// by tickets, not just limiting their lifetime. - fn lifetime(&self) -> u32; - - /// Encrypt and authenticate `plain`, returning the resulting - /// ticket. Return None if `plain` cannot be encrypted for - /// some reason: an empty ticket will be sent and the connection - /// will continue. - fn encrypt(&self, plain: &[u8]) -> Option>; - - /// Decrypt `cipher`, validating its authenticity protection - /// and recovering the plaintext. `cipher` is fully attacker - /// controlled, so this decryption must be side-channel free, - /// panic-proof, and otherwise bullet-proof. If the decryption - /// fails, return None. - fn decrypt(&self, cipher: &[u8]) -> Option>; -} - -/// How to choose a certificate chain and signing key for use -/// in server authentication. -/// -/// This is suitable when selecting a certificate does not require -/// I/O or when the application is using blocking I/O anyhow. -/// -/// For applications that use async I/O and need to do I/O to choose -/// a certificate (for instance, fetching a certificate from a data store), -/// the [`Acceptor`] interface is more suitable. -pub trait ResolvesServerCert: Debug + Send + Sync { - /// Choose a certificate chain and matching key given simplified - /// ClientHello information. - /// - /// Return `None` to abort the handshake. - fn resolve(&self, client_hello: ClientHello<'_>) -> Option>; - - /// Return true when the server only supports raw public keys. - fn only_raw_public_keys(&self) -> bool { - false - } -} - -/// A struct representing the received Client Hello -#[derive(Debug)] -pub struct ClientHello<'a> { - pub(super) server_name: &'a Option>, - pub(super) signature_schemes: &'a [SignatureScheme], - pub(super) alpn: Option<&'a Vec>, - pub(super) server_cert_types: Option<&'a [CertificateType]>, - pub(super) client_cert_types: Option<&'a [CertificateType]>, - pub(super) cipher_suites: &'a [CipherSuite], - /// The [certificate_authorities] extension, if it was sent by the client. - /// - /// [certificate_authorities]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.4 - pub(super) certificate_authorities: Option<&'a [DistinguishedName]>, -} - -impl<'a> ClientHello<'a> { - /// Get the server name indicator. - /// - /// Returns `None` if the client did not supply a SNI. - pub fn server_name(&self) -> Option<&str> { - self.server_name - .as_ref() - .map( as AsRef>::as_ref) - } - - /// Get the compatible signature schemes. - /// - /// Returns standard-specified default if the client omitted this extension. - pub fn signature_schemes(&self) -> &[SignatureScheme] { - self.signature_schemes - } - - /// Get the ALPN protocol identifiers submitted by the client. - /// - /// Returns `None` if the client did not include an ALPN extension. - /// - /// Application Layer Protocol Negotiation (ALPN) is a TLS extension that lets a client - /// submit a set of identifiers that each a represent an application-layer protocol. - /// The server will then pick its preferred protocol from the set submitted by the client. - /// Each identifier is represented as a byte array, although common values are often ASCII-encoded. - /// See the official RFC-7301 specifications at - /// for more information on ALPN. - /// - /// For example, a HTTP client might specify "http/1.1" and/or "h2". Other well-known values - /// are listed in the at IANA registry at - /// . - /// - /// The server can specify supported ALPN protocols by setting [`ServerConfig::alpn_protocols`]. - /// During the handshake, the server will select the first protocol configured that the client supports. - pub fn alpn(&self) -> Option> { - self.alpn.map(|protocols| { - protocols - .iter() - .map(|proto| proto.as_ref()) - }) - } - - /// Get cipher suites. - pub fn cipher_suites(&self) -> &[CipherSuite] { - self.cipher_suites - } - - /// Get the server certificate types offered in the ClientHello. - /// - /// Returns `None` if the client did not include a certificate type extension. - pub fn server_cert_types(&self) -> Option<&'a [CertificateType]> { - self.server_cert_types - } - - /// Get the client certificate types offered in the ClientHello. - /// - /// Returns `None` if the client did not include a certificate type extension. - pub fn client_cert_types(&self) -> Option<&'a [CertificateType]> { - self.client_cert_types - } - - /// Get the [certificate_authorities] extension sent by the client. - /// - /// Returns `None` if the client did not send this extension. - /// - /// [certificate_authorities]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.4 - pub fn certificate_authorities(&self) -> Option<&'a [DistinguishedName]> { - self.certificate_authorities - } -} - -/// Common configuration for a set of server sessions. -/// -/// Making one of these is cheap, though one of the inputs may be expensive: gathering trust roots -/// from the operating system to add to the [`RootCertStore`] passed to a `ClientCertVerifier` -/// builder may take on the order of a few hundred milliseconds. -/// -/// These must be created via the [`ServerConfig::builder()`] or [`ServerConfig::builder_with_provider()`] -/// function. -/// -/// # Defaults -/// -/// * [`ServerConfig::max_fragment_size`]: the default is `None` (meaning 16kB). -/// * [`ServerConfig::session_storage`]: if the `std` feature is enabled, the default stores 256 -/// sessions in memory. If the `std` feature is not enabled, the default is to not store any -/// sessions. In a no-std context, by enabling the `hashbrown` feature you may provide your -/// own `session_storage` using [`ServerSessionMemoryCache`] and a `crate::lock::MakeMutex` -/// implementation. -/// * [`ServerConfig::alpn_protocols`]: the default is empty -- no ALPN protocol is negotiated. -/// * [`ServerConfig::key_log`]: key material is not logged. -/// * [`ServerConfig::send_tls13_tickets`]: 2 tickets are sent. -/// * [`ServerConfig::cert_compressors`]: depends on the crate features, see [`compress::default_cert_compressors()`]. -/// * [`ServerConfig::cert_compression_cache`]: caches the most recently used 4 compressions -/// * [`ServerConfig::cert_decompressors`]: depends on the crate features, see [`compress::default_cert_decompressors()`]. -/// -/// [`RootCertStore`]: crate::RootCertStore -/// [`ServerSessionMemoryCache`]: crate::server::handy::ServerSessionMemoryCache -#[derive(Clone, Debug)] -pub struct ServerConfig { - /// Source of randomness and other crypto. - pub(super) provider: Arc, - - /// Ignore the client's ciphersuite order. Instead, - /// choose the top ciphersuite in the server list - /// which is supported by the client. - pub ignore_client_order: bool, - - /// The maximum size of plaintext input to be emitted in a single TLS record. - /// A value of None is equivalent to the [TLS maximum] of 16 kB. - /// - /// rustls enforces an arbitrary minimum of 32 bytes for this field. - /// Out of range values are reported as errors from [ServerConnection::new]. - /// - /// Setting this value to a little less than the TCP MSS may improve latency - /// for stream-y workloads. - /// - /// [TLS maximum]: https://datatracker.ietf.org/doc/html/rfc8446#section-5.1 - /// [ServerConnection::new]: crate::server::ServerConnection::new - pub max_fragment_size: Option, - - /// How to store client sessions. - pub session_storage: Arc, - - /// How to produce tickets. - pub ticketer: Arc, - - /// How to choose a server cert and key. This is usually set by - /// [ConfigBuilder::with_single_cert] or [ConfigBuilder::with_cert_resolver]. - /// For async applications, see also [Acceptor]. - pub cert_resolver: Arc, - - /// Protocol names we support, most preferred first. - /// If empty we don't do ALPN at all. - pub alpn_protocols: Vec>, - - /// Supported protocol versions, in no particular order. - /// The default is all supported versions. - pub(super) versions: versions::EnabledVersions, - - /// How to verify client certificates. - pub(super) verifier: Arc, - - /// How to output key material for debugging. The default - /// does nothing. - pub key_log: Arc, - - /// Allows traffic secrets to be extracted after the handshake, - /// e.g. for kTLS setup. - pub enable_secret_extraction: bool, - - /// Amount of early data to accept for sessions created by - /// this config. Specify 0 to disable early data. The - /// default is 0. - /// - /// Read the early data via [`ServerConnection::early_data`]. - /// - /// The units for this are _both_ plaintext bytes, _and_ ciphertext - /// bytes, depending on whether the server accepts a client's early_data - /// or not. It is therefore recommended to include some slop in - /// this value to account for the unknown amount of ciphertext - /// expansion in the latter case. - pub max_early_data_size: u32, - - /// Whether the server should send "0.5RTT" data. This means the server - /// sends data after its first flight of handshake messages, without - /// waiting for the client to complete the handshake. - /// - /// This can improve TTFB latency for either server-speaks-first protocols, - /// or client-speaks-first protocols when paired with "0RTT" data. This - /// comes at the cost of a subtle weakening of the normal handshake - /// integrity guarantees that TLS provides. Note that the initial - /// `ClientHello` is indirectly authenticated because it is included - /// in the transcript used to derive the keys used to encrypt the data. - /// - /// This only applies to TLS1.3 connections. TLS1.2 connections cannot - /// do this optimisation and this setting is ignored for them. It is - /// also ignored for TLS1.3 connections that even attempt client - /// authentication. - /// - /// This defaults to false. This means the first application data - /// sent by the server comes after receiving and validating the client's - /// handshake up to the `Finished` message. This is the safest option. - pub send_half_rtt_data: bool, - - /// How many TLS1.3 tickets to send immediately after a successful - /// handshake. - /// - /// Because TLS1.3 tickets are single-use, this allows - /// a client to perform multiple resumptions. - /// - /// The default is 2. - /// - /// If this is 0, no tickets are sent and clients will not be able to - /// do any resumption. - pub send_tls13_tickets: usize, - - /// If set to `true`, requires the client to support the extended - /// master secret extraction method defined in [RFC 7627]. - /// - /// The default is `true` if the "fips" crate feature is enabled, - /// `false` otherwise. - /// - /// It must be set to `true` to meet FIPS requirement mentioned in section - /// **D.Q Transition of the TLS 1.2 KDF to Support the Extended Master - /// Secret** from [FIPS 140-3 IG.pdf]. - /// - /// [RFC 7627]: https://datatracker.ietf.org/doc/html/rfc7627 - /// [FIPS 140-3 IG.pdf]: https://csrc.nist.gov/csrc/media/Projects/cryptographic-module-validation-program/documents/fips%20140-3/FIPS%20140-3%20IG.pdf - #[cfg(feature = "tls12")] - pub require_ems: bool, - - /// Provides the current system time - pub time_provider: Arc, - - /// How to compress the server's certificate chain. - /// - /// If a client supports this extension, and advertises support - /// for one of the compression algorithms included here, the - /// server certificate will be compressed according to [RFC8779]. - /// - /// This only applies to TLS1.3 connections. It is ignored for - /// TLS1.2 connections. - /// - /// [RFC8779]: https://datatracker.ietf.org/doc/rfc8879/ - pub cert_compressors: Vec<&'static dyn compress::CertCompressor>, - - /// Caching for compressed certificates. - /// - /// This is optional: [`compress::CompressionCache::Disabled`] gives - /// a cache that does no caching. - pub cert_compression_cache: Arc, - - /// How to decompress the clients's certificate chain. - /// - /// If this is non-empty, the [RFC8779] certificate compression - /// extension is offered when requesting client authentication, - /// and any compressed certificates are transparently decompressed - /// during the handshake. - /// - /// This only applies to TLS1.3 connections. It is ignored for - /// TLS1.2 connections. - /// - /// [RFC8779]: https://datatracker.ietf.org/doc/rfc8879/ - pub cert_decompressors: Vec<&'static dyn compress::CertDecompressor>, -} - -impl ServerConfig { - /// Create a builder for a server configuration with - /// [the process-default `CryptoProvider`][CryptoProvider#using-the-per-process-default-cryptoprovider] - /// and safe protocol version defaults. - /// - /// For more information, see the [`ConfigBuilder`] documentation. - #[cfg(feature = "std")] - pub fn builder() -> ConfigBuilder { - Self::builder_with_protocol_versions(versions::DEFAULT_VERSIONS) - } - - /// Create a builder for a server configuration with - /// [the process-default `CryptoProvider`][CryptoProvider#using-the-per-process-default-cryptoprovider] - /// and the provided protocol versions. - /// - /// Panics if - /// - the supported versions are not compatible with the provider (eg. - /// the combination of ciphersuites supported by the provider and supported - /// versions lead to zero cipher suites being usable), - /// - if a `CryptoProvider` cannot be resolved using a combination of - /// the crate features and process default. - /// - /// For more information, see the [`ConfigBuilder`] documentation. - #[cfg(feature = "std")] - pub fn builder_with_protocol_versions( - versions: &[&'static versions::SupportedProtocolVersion], - ) -> ConfigBuilder { - // Safety assumptions: - // 1. that the provider has been installed (explicitly or implicitly) - // 2. that the process-level default provider is usable with the supplied protocol versions. - Self::builder_with_provider(Arc::clone( - CryptoProvider::get_default_or_install_from_crate_features(), - )) - .with_protocol_versions(versions) - .unwrap() - } - - /// Create a builder for a server configuration with a specific [`CryptoProvider`]. - /// - /// This will use the provider's configured ciphersuites. You must additionally choose - /// which protocol versions to enable, using `with_protocol_versions` or - /// `with_safe_default_protocol_versions` and handling the `Result` in case a protocol - /// version is not supported by the provider's ciphersuites. - /// - /// For more information, see the [`ConfigBuilder`] documentation. - #[cfg(feature = "std")] - pub fn builder_with_provider( - provider: Arc, - ) -> ConfigBuilder { - ConfigBuilder { - state: WantsVersions {}, - provider, - time_provider: Arc::new(DefaultTimeProvider), - side: PhantomData, - } - } - - /// Create a builder for a server configuration with no default implementation details. - /// - /// This API must be used by `no_std` users. - /// - /// You must provide a specific [`TimeProvider`]. - /// - /// You must provide a specific [`CryptoProvider`]. - /// - /// This will use the provider's configured ciphersuites. You must additionally choose - /// which protocol versions to enable, using `with_protocol_versions` or - /// `with_safe_default_protocol_versions` and handling the `Result` in case a protocol - /// version is not supported by the provider's ciphersuites. - /// - /// For more information, see the [`ConfigBuilder`] documentation. - pub fn builder_with_details( - provider: Arc, - time_provider: Arc, - ) -> ConfigBuilder { - ConfigBuilder { - state: WantsVersions {}, - provider, - time_provider, - side: PhantomData, - } - } - - /// Return `true` if connections made with this `ServerConfig` will - /// operate in FIPS mode. - /// - /// This is different from [`CryptoProvider::fips()`]: [`CryptoProvider::fips()`] - /// is concerned only with cryptography, whereas this _also_ covers TLS-level - /// configuration that NIST recommends. - pub fn fips(&self) -> bool { - #[cfg(feature = "tls12")] - { - self.provider.fips() && self.require_ems - } - - #[cfg(not(feature = "tls12"))] - { - self.provider.fips() - } - } - - /// Return the crypto provider used to construct this client configuration. - pub fn crypto_provider(&self) -> &Arc { - &self.provider - } - - /// We support a given TLS version if it's quoted in the configured - /// versions *and* at least one ciphersuite for this version is - /// also configured. - pub(crate) fn supports_version(&self, v: ProtocolVersion) -> bool { - self.versions.contains(v) - && self - .provider - .cipher_suites - .iter() - .any(|cs| cs.version().version == v) - } - - #[cfg(feature = "std")] - pub(crate) fn supports_protocol(&self, proto: Protocol) -> bool { - self.provider - .cipher_suites - .iter() - .any(|cs| cs.usable_for_protocol(proto)) - } - - pub(super) fn current_time(&self) -> Result { - self.time_provider - .current_time() - .ok_or(Error::FailedToGetCurrentTime) - } -} - -#[cfg(feature = "std")] -mod connection { - use alloc::boxed::Box; - use alloc::sync::Arc; - use alloc::vec::Vec; - use core::fmt; - use core::fmt::{Debug, Formatter}; - use core::ops::{Deref, DerefMut}; - use std::io; - - use super::{Accepted, Accepting, EarlyDataState, ServerConfig, ServerConnectionData}; - use crate::common_state::{CommonState, Context, Side}; - use crate::conn::{ConnectionCommon, ConnectionCore}; - use crate::error::Error; - use crate::server::hs; - use crate::suites::ExtractedSecrets; - use crate::vecbuf::ChunkVecBuffer; - - /// Allows reading of early data in resumed TLS1.3 connections. - /// - /// "Early data" is also known as "0-RTT data". - /// - /// This structure implements [`std::io::Read`]. - pub struct ReadEarlyData<'a> { - early_data: &'a mut EarlyDataState, - } - - impl<'a> ReadEarlyData<'a> { - fn new(early_data: &'a mut EarlyDataState) -> Self { - ReadEarlyData { early_data } - } - } - - impl io::Read for ReadEarlyData<'_> { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.early_data.read(buf) - } - - #[cfg(read_buf)] - fn read_buf(&mut self, cursor: core::io::BorrowedCursor<'_>) -> io::Result<()> { - self.early_data.read_buf(cursor) - } - } - - /// This represents a single TLS server connection. - /// - /// Send TLS-protected data to the peer using the `io::Write` trait implementation. - /// Read data from the peer using the `io::Read` trait implementation. - pub struct ServerConnection { - pub(super) inner: ConnectionCommon, - } - - impl ServerConnection { - /// Make a new ServerConnection. `config` controls how - /// we behave in the TLS protocol. - pub fn new(config: Arc) -> Result { - Ok(Self { - inner: ConnectionCommon::from(ConnectionCore::for_server(config, Vec::new())?), - }) - } - - /// Retrieves the server name, if any, used to select the certificate and - /// private key. - /// - /// This returns `None` until some time after the client's server name indication - /// (SNI) extension value is processed during the handshake. It will never be - /// `None` when the connection is ready to send or process application data, - /// unless the client does not support SNI. - /// - /// This is useful for application protocols that need to enforce that the - /// server name matches an application layer protocol hostname. For - /// example, HTTP/1.1 servers commonly expect the `Host:` header field of - /// every request on a connection to match the hostname in the SNI extension - /// when the client provides the SNI extension. - /// - /// The server name is also used to match sessions during session resumption. - pub fn server_name(&self) -> Option<&str> { - self.inner.core.get_sni_str() - } - - /// Application-controlled portion of the resumption ticket supplied by the client, if any. - /// - /// Recovered from the prior session's `set_resumption_data`. Integrity is guaranteed by rustls. - /// - /// Returns `Some` if and only if a valid resumption ticket has been received from the client. - pub fn received_resumption_data(&self) -> Option<&[u8]> { - self.inner - .core - .data - .received_resumption_data - .as_ref() - .map(|x| &x[..]) - } - - /// Set the resumption data to embed in future resumption tickets supplied to the client. - /// - /// Defaults to the empty byte string. Must be less than 2^15 bytes to allow room for other - /// data. Should be called while `is_handshaking` returns true to ensure all transmitted - /// resumption tickets are affected. - /// - /// Integrity will be assured by rustls, but the data will be visible to the client. If secrecy - /// from the client is desired, encrypt the data separately. - pub fn set_resumption_data(&mut self, data: &[u8]) { - assert!(data.len() < 2usize.pow(15)); - self.inner.core.data.resumption_data = data.into(); - } - - /// Explicitly discard early data, notifying the client - /// - /// Useful if invariants encoded in `received_resumption_data()` cannot be respected. - /// - /// Must be called while `is_handshaking` is true. - pub fn reject_early_data(&mut self) { - self.inner.core.reject_early_data() - } - - /// Returns an `io::Read` implementer you can read bytes from that are - /// received from a client as TLS1.3 0RTT/"early" data, during the handshake. - /// - /// This returns `None` in many circumstances, such as : - /// - /// - Early data is disabled if [`ServerConfig::max_early_data_size`] is zero (the default). - /// - The session negotiated with the client is not TLS1.3. - /// - The client just doesn't support early data. - /// - The connection doesn't resume an existing session. - /// - The client hasn't sent a full ClientHello yet. - pub fn early_data(&mut self) -> Option> { - let data = &mut self.inner.core.data; - if data.early_data.was_accepted() { - Some(ReadEarlyData::new(&mut data.early_data)) - } else { - None - } - } - - /// Return true if the connection was made with a `ServerConfig` that is FIPS compatible. - /// - /// This is different from [`crate::crypto::CryptoProvider::fips()`]: - /// it is concerned only with cryptography, whereas this _also_ covers TLS-level - /// configuration that NIST recommends, as well as ECH HPKE suites if applicable. - pub fn fips(&self) -> bool { - self.inner.core.common_state.fips - } - - /// Extract secrets, so they can be used when configuring kTLS, for example. - /// Should be used with care as it exposes secret key material. - pub fn dangerous_extract_secrets(self) -> Result { - self.inner.dangerous_extract_secrets() - } - } - - impl Debug for ServerConnection { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("ServerConnection") - .finish() - } - } - - impl Deref for ServerConnection { - type Target = ConnectionCommon; - - fn deref(&self) -> &Self::Target { - &self.inner - } - } - - impl DerefMut for ServerConnection { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } - } - - impl From for crate::Connection { - fn from(conn: ServerConnection) -> Self { - Self::Server(conn) - } - } - - /// Handle a server-side connection before configuration is available. - /// - /// `Acceptor` allows the caller to choose a [`ServerConfig`] after reading - /// the [`super::ClientHello`] of an incoming connection. This is useful for servers - /// that choose different certificates or cipher suites based on the - /// characteristics of the `ClientHello`. In particular it is useful for - /// servers that need to do some I/O to load a certificate and its private key - /// and don't want to use the blocking interface provided by - /// [`super::ResolvesServerCert`]. - /// - /// Create an Acceptor with [`Acceptor::default()`]. - /// - /// # Example - /// - /// ```no_run - /// # #[cfg(feature = "aws_lc_rs")] { - /// # fn choose_server_config( - /// # _: rustls::server::ClientHello, - /// # ) -> std::sync::Arc { - /// # unimplemented!(); - /// # } - /// # #[allow(unused_variables)] - /// # fn main() { - /// use rustls::server::{Acceptor, ServerConfig}; - /// let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); - /// for stream in listener.incoming() { - /// let mut stream = stream.unwrap(); - /// let mut acceptor = Acceptor::default(); - /// let accepted = loop { - /// acceptor.read_tls(&mut stream).unwrap(); - /// if let Some(accepted) = acceptor.accept().unwrap() { - /// break accepted; - /// } - /// }; - /// - /// // For some user-defined choose_server_config: - /// let config = choose_server_config(accepted.client_hello()); - /// let conn = accepted - /// .into_connection(config) - /// .unwrap(); - /// - /// // Proceed with handling the ServerConnection. - /// } - /// # } - /// # } - /// ``` - pub struct Acceptor { - inner: Option>, - } - - impl Default for Acceptor { - /// Return an empty Acceptor, ready to receive bytes from a new client connection. - fn default() -> Self { - Self { - inner: Some( - ConnectionCore::new( - Box::new(Accepting), - ServerConnectionData::default(), - CommonState::new(Side::Server), - ) - .into(), - ), - } - } - } - - impl Acceptor { - /// Read TLS content from `rd`. - /// - /// Returns an error if this `Acceptor` has already yielded an [`Accepted`]. For more details, - /// refer to [`Connection::read_tls()`]. - /// - /// [`Connection::read_tls()`]: crate::Connection::read_tls - pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result { - match &mut self.inner { - Some(conn) => conn.read_tls(rd), - None => Err(io::Error::new( - io::ErrorKind::Other, - "acceptor cannot read after successful acceptance", - )), - } - } - - /// Check if a `ClientHello` message has been received. - /// - /// Returns `Ok(None)` if the complete `ClientHello` has not yet been received. - /// Do more I/O and then call this function again. - /// - /// Returns `Ok(Some(accepted))` if the connection has been accepted. Call - /// `accepted.into_connection()` to continue. Do not call this function again. - /// - /// Returns `Err((err, alert))` if an error occurred. If an alert is returned, the - /// application should call `alert.write()` to send the alert to the client. It should - /// not call `accept()` again. - pub fn accept(&mut self) -> Result, (Error, AcceptedAlert)> { - let Some(mut connection) = self.inner.take() else { - return Err(( - Error::General("Acceptor polled after completion".into()), - AcceptedAlert::empty(), - )); - }; - - let message = match connection.first_handshake_message() { - Ok(Some(msg)) => msg, - Ok(None) => { - self.inner = Some(connection); - return Ok(None); - } - Err(err) => return Err((err, AcceptedAlert::from(connection))), - }; - - let mut cx = Context::from(&mut connection); - let sig_schemes = match hs::process_client_hello(&message, false, &mut cx) { - Ok((_, sig_schemes)) => sig_schemes, - Err(err) => { - return Err((err, AcceptedAlert::from(connection))); - } - }; - - Ok(Some(Accepted { - connection, - message, - sig_schemes, - })) - } - } - - /// Represents a TLS alert resulting from handling the client's `ClientHello` message. - /// - /// When [`Acceptor::accept()`] returns an error, it yields an `AcceptedAlert` such that the - /// application can communicate failure to the client via [`AcceptedAlert::write()`]. - pub struct AcceptedAlert(ChunkVecBuffer); - - impl AcceptedAlert { - pub(super) fn empty() -> Self { - Self(ChunkVecBuffer::new(None)) - } - - /// Send the alert to the client. - /// - /// To account for short writes this function should be called repeatedly until it - /// returns `Ok(0)` or an error. - pub fn write(&mut self, wr: &mut dyn io::Write) -> Result { - self.0.write_to(wr) - } - - /// Send the alert to the client. - /// - /// This function will invoke the writer until the buffer is empty. - pub fn write_all(&mut self, wr: &mut dyn io::Write) -> Result<(), io::Error> { - while self.write(wr)? != 0 {} - Ok(()) - } - } - - impl From> for AcceptedAlert { - fn from(conn: ConnectionCommon) -> Self { - Self(conn.core.common_state.sendable_tls) - } - } - - impl Debug for AcceptedAlert { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("AcceptedAlert").finish() - } - } -} - -#[cfg(feature = "std")] -pub use connection::{AcceptedAlert, Acceptor, ReadEarlyData, ServerConnection}; - -/// Unbuffered version of `ServerConnection` -/// -/// See the [`crate::unbuffered`] module docs for more details -pub struct UnbufferedServerConnection { - inner: UnbufferedConnectionCommon, -} - -impl UnbufferedServerConnection { - /// Make a new ServerConnection. `config` controls how we behave in the TLS protocol. - pub fn new(config: Arc) -> Result { - Ok(Self { - inner: UnbufferedConnectionCommon::from(ConnectionCore::for_server( - config, - Vec::new(), - )?), - }) - } -} - -impl Deref for UnbufferedServerConnection { - type Target = UnbufferedConnectionCommon; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl DerefMut for UnbufferedServerConnection { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - -impl UnbufferedConnectionCommon { - pub(crate) fn pop_early_data(&mut self) -> Option> { - self.core.data.early_data.pop() - } -} - -/// Represents a `ClientHello` message received through the [`Acceptor`]. -/// -/// Contains the state required to resume the connection through [`Accepted::into_connection()`]. -pub struct Accepted { - connection: ConnectionCommon, - message: Message<'static>, - sig_schemes: Vec, -} - -impl Accepted { - /// Get the [`ClientHello`] for this connection. - pub fn client_hello(&self) -> ClientHello<'_> { - let payload = Self::client_hello_payload(&self.message); - let ch = ClientHello { - server_name: &self.connection.core.data.sni, - signature_schemes: &self.sig_schemes, - alpn: payload.alpn_extension(), - server_cert_types: payload.server_certificate_extension(), - client_cert_types: payload.client_certificate_extension(), - cipher_suites: &payload.cipher_suites, - certificate_authorities: payload.certificate_authorities_extension(), - }; - - trace!("Accepted::client_hello(): {ch:#?}"); - ch - } - - /// Convert the [`Accepted`] into a [`ServerConnection`]. - /// - /// Takes the state returned from [`Acceptor::accept()`] as well as the [`ServerConfig`] and - /// [`sign::CertifiedKey`] that should be used for the session. Returns an error if - /// configuration-dependent validation of the received `ClientHello` message fails. - #[cfg(feature = "std")] - pub fn into_connection( - mut self, - config: Arc, - ) -> Result { - if let Err(err) = self - .connection - .set_max_fragment_size(config.max_fragment_size) - { - // We have a connection here, but it won't contain an alert since the error - // is with the fragment size configured in the `ServerConfig`. - return Err((err, AcceptedAlert::empty())); - } - - self.connection.enable_secret_extraction = config.enable_secret_extraction; - - let state = hs::ExpectClientHello::new(config, Vec::new()); - let mut cx = hs::ServerContext::from(&mut self.connection); - - let ch = Self::client_hello_payload(&self.message); - let new = match state.with_certified_key(self.sig_schemes, ch, &self.message, &mut cx) { - Ok(new) => new, - Err(err) => return Err((err, AcceptedAlert::from(self.connection))), - }; - - self.connection.replace_state(new); - Ok(ServerConnection { - inner: self.connection, - }) - } - - fn client_hello_payload<'a>(message: &'a Message<'_>) -> &'a ClientHelloPayload { - match &message.payload { - crate::msgs::message::MessagePayload::Handshake { parsed, .. } => match &parsed.payload - { - crate::msgs::handshake::HandshakePayload::ClientHello(ch) => ch, - _ => unreachable!(), - }, - _ => unreachable!(), - } - } -} - -impl Debug for Accepted { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("Accepted").finish() - } -} - -#[cfg(feature = "std")] -struct Accepting; - -#[cfg(feature = "std")] -impl State for Accepting { - fn handle<'m>( - self: Box, - _cx: &mut hs::ServerContext<'_>, - _m: Message<'m>, - ) -> Result + 'm>, Error> - where - Self: 'm, - { - Err(Error::General("unreachable state".into())) - } - - fn into_owned(self: Box) -> hs::NextState<'static> { - self - } -} - -pub(super) enum EarlyDataState { - New, - Accepted { - received: ChunkVecBuffer, - left: usize, - }, - Rejected, -} - -impl Default for EarlyDataState { - fn default() -> Self { - Self::New - } -} - -impl EarlyDataState { - pub(super) fn reject(&mut self) { - *self = Self::Rejected; - } - - pub(super) fn accept(&mut self, max_size: usize) { - *self = Self::Accepted { - received: ChunkVecBuffer::new(Some(max_size)), - left: max_size, - }; - } - - #[cfg(feature = "std")] - fn was_accepted(&self) -> bool { - matches!(self, Self::Accepted { .. }) - } - - pub(super) fn was_rejected(&self) -> bool { - matches!(self, Self::Rejected) - } - - fn pop(&mut self) -> Option> { - match self { - Self::Accepted { - ref mut received, .. - } => received.pop(), - _ => None, - } - } - - #[cfg(feature = "std")] - fn read(&mut self, buf: &mut [u8]) -> io::Result { - match self { - Self::Accepted { - ref mut received, .. - } => received.read(buf), - _ => Err(io::Error::from(io::ErrorKind::BrokenPipe)), - } - } - - #[cfg(read_buf)] - fn read_buf(&mut self, cursor: core::io::BorrowedCursor<'_>) -> io::Result<()> { - match self { - Self::Accepted { - ref mut received, .. - } => received.read_buf(cursor), - _ => Err(io::Error::from(io::ErrorKind::BrokenPipe)), - } - } - - pub(super) fn take_received_plaintext(&mut self, bytes: Payload<'_>) -> bool { - let available = bytes.bytes().len(); - match self { - Self::Accepted { - ref mut received, - ref mut left, - } if received.apply_limit(available) == available && available <= *left => { - received.append(bytes.into_vec()); - *left -= available; - true - } - _ => false, - } - } -} - -impl Debug for EarlyDataState { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::New => write!(f, "EarlyDataState::New"), - Self::Accepted { received, left } => write!( - f, - "EarlyDataState::Accepted {{ received: {}, left: {} }}", - received.len(), - left - ), - Self::Rejected => write!(f, "EarlyDataState::Rejected"), - } - } -} - -impl ConnectionCore { - pub(crate) fn for_server( - config: Arc, - extra_exts: Vec, - ) -> Result { - let mut common = CommonState::new(Side::Server); - common.set_max_fragment_size(config.max_fragment_size)?; - common.enable_secret_extraction = config.enable_secret_extraction; - common.fips = config.fips(); - Ok(Self::new( - Box::new(hs::ExpectClientHello::new(config, extra_exts)), - ServerConnectionData::default(), - common, - )) - } - - #[cfg(feature = "std")] - pub(crate) fn reject_early_data(&mut self) { - assert!( - self.common_state.is_handshaking(), - "cannot retroactively reject early data" - ); - self.data.early_data.reject(); - } - - #[cfg(feature = "std")] - pub(crate) fn get_sni_str(&self) -> Option<&str> { - self.data.get_sni_str() - } -} - -/// State associated with a server connection. -#[derive(Default, Debug)] -pub struct ServerConnectionData { - pub(super) sni: Option>, - pub(super) received_resumption_data: Option>, - pub(super) resumption_data: Vec, - pub(super) early_data: EarlyDataState, -} - -impl ServerConnectionData { - #[cfg(feature = "std")] - pub(super) fn get_sni_str(&self) -> Option<&str> { - self.sni.as_ref().map(AsRef::as_ref) - } -} - -impl crate::conn::SideData for ServerConnectionData {} - -#[cfg(feature = "std")] -#[cfg(test)] -mod tests { - use std::format; - - use super::*; - - // these branches not reachable externally, unless something else goes wrong. - #[test] - fn test_read_in_new_state() { - assert_eq!( - format!("{:?}", EarlyDataState::default().read(&mut [0u8; 5])), - "Err(Kind(BrokenPipe))" - ); - } - - #[cfg(read_buf)] - #[test] - fn test_read_buf_in_new_state() { - use core::io::BorrowedBuf; - - let mut buf = [0u8; 5]; - let mut buf: BorrowedBuf<'_> = buf.as_mut_slice().into(); - assert_eq!( - format!("{:?}", EarlyDataState::default().read_buf(buf.unfilled())), - "Err(Kind(BrokenPipe))" - ); - } -} diff --git a/rustls/src/server/test.rs b/rustls/src/server/test.rs new file mode 100644 index 00000000000..4392213463b --- /dev/null +++ b/rustls/src/server/test.rs @@ -0,0 +1,512 @@ +use alloc::borrow::Cow; +use alloc::boxed::Box; +use std::{error, vec}; + +use pki_types::UnixTime; + +use super::hs::ClientHelloInput; +use super::{ + CommonServerSessionValue, ServerConfig, ServerConnection, ServerSessionValue, + Tls13ServerSessionValue, +}; +use crate::conn::{Connection, Input}; +use crate::crypto::cipher::FakeAead; +use crate::crypto::kx::ffdhe::{FFDHE2048, FfdheGroup}; +use crate::crypto::kx::{ + ActiveKeyExchange, KeyExchangeAlgorithm, NamedGroup, SharedSecret, StartedKeyExchange, + SupportedKxGroup, +}; +use crate::crypto::test_provider::{FAKE_HASH, FAKE_HMAC}; +use crate::crypto::{ + CertificateIdentity, CipherSuite, Credentials, CryptoProvider, Identity, SignatureScheme, + SingleCredential, TEST_PROVIDER, tls12, tls12_only, +}; +use crate::enums::{CertificateType, ProtocolVersion}; +use crate::error::{Error, PeerIncompatible}; +use crate::msgs::{ + ClientExtensions, ClientHelloPayload, Codec, Compression, HEADER_SIZE, HandshakeMessagePayload, + HandshakePayload, KeyShareEntry, Message, MessagePayload, Random, Reader, SessionId, + SizedPayload, SupportedProtocolVersions, +}; +use crate::pki_types::pem::PemObject; +use crate::pki_types::{CertificateDer, FipsStatus, PrivateKeyDer}; +use crate::suites::CipherSuiteCommon; +use crate::sync::Arc; +use crate::tls12::Tls12CipherSuite; +use crate::tls13::Tls13CipherSuite; +use crate::version::TLS12_VERSION; + +#[test] +fn serversessionvalue_is_debug() { + use std::{println, vec}; + let ssv = ServerSessionValue::Tls13(Tls13ServerSessionValue::new( + CommonServerSessionValue::new( + None, + CipherSuite::TLS13_AES_128_GCM_SHA256, + None, + None, + vec![4, 5, 6], + UnixTime::now(), + ), + &[1, 2, 3], + 0x12345678, + )); + println!("{ssv:?}"); + println!("{:#04x?}", ssv.get_encoding()); +} + +#[test] +fn serversessionvalue_no_sni() { + let bytes = [ + 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x69, 0x7a, 0x4a, 0xdf, 0x00, 0x13, 0x01, 0x00, 0x00, + 0x00, 0x03, 0x04, 0x05, 0x06, 0x03, 0x01, 0x02, 0x03, 0x12, 0x34, 0x56, 0x78, + ]; + let mut rd = Reader::new(&bytes); + let ssv = ServerSessionValue::read(&mut rd).unwrap(); + assert_eq!(ssv.get_encoding(), bytes); +} + +#[test] +fn serversessionvalue_with_cert() { + std::eprintln!( + "{:#04x?}", + ServerSessionValue::Tls13(Tls13ServerSessionValue::new( + CommonServerSessionValue::new( + None, + CipherSuite::TLS13_AES_128_GCM_SHA256, + Some(Identity::X509(CertificateIdentity { + end_entity: CertificateDer::from(&[10, 11, 12][..]), + intermediates: alloc::vec![], + })), + None, + alloc::vec![4, 5, 6], + UnixTime::now(), + ), + &[1, 2, 3], + 0x12345678, + )) + .get_encoding() + ); + + let bytes = [ + 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x69, 0x7a, 0x4b, 0x06, 0x00, 0x13, 0x01, 0x01, 0x00, + 0x00, 0x00, 0x03, 0x0a, 0x0b, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x05, 0x06, + 0x03, 0x01, 0x02, 0x03, 0x12, 0x34, 0x56, 0x78, + ]; + let mut rd = Reader::new(&bytes); + let ssv = ServerSessionValue::read(&mut rd).unwrap(); + assert_eq!(ssv.get_encoding(), bytes); +} + +#[test] +fn null_compression_required() { + assert_eq!( + test_process_client_hello(ClientHelloPayload { + compression_methods: vec![], + ..minimal_client_hello() + }), + Err(PeerIncompatible::NullCompressionRequired.into()), + ); +} + +fn test_process_client_hello(hello: ClientHelloPayload) -> Result<(), Error> { + let m = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::ClientHello( + hello, + ))), + }; + + ClientHelloInput::from_input(&Input { + message: m, + aligned_handshake: None, + }) + .map(|_| ()) +} + +#[test] +fn test_server_preference_cipher_suite_selection() { + // Configure a server to use the default CipherSuiteSelector. + let mut provider = ffdhe_provider(TEST_PROVIDER); + static SERVER_CIPHERS_TLS13: &[&Tls13CipherSuite] = &[]; + static SERVER_CIPHERS_TLS12: &[&Tls12CipherSuite] = &[ + &TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + &TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + ]; + provider.tls13_cipher_suites = Cow::Borrowed(SERVER_CIPHERS_TLS13); + provider.tls12_cipher_suites = Cow::Borrowed(SERVER_CIPHERS_TLS12); + let config = ServerConfig::builder(provider.into()) + .with_no_client_auth() + .with_single_cert(server_identity(), server_key()) + .unwrap(); + + // The server should choose the first cipher suite in its supported list that is + // also supported by the client. + let mut ch = minimal_client_hello(); + ch.cipher_suites.clear(); + ch.cipher_suites.extend([ + CipherSuite::TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + ]); + let selected_suite = select_cipher_suite(ServerConnection::new(config.into()).unwrap(), ch); + assert_eq!( + selected_suite.unwrap(), + CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 + ); +} + +// Process the `ClientHelloPayload` and return the `CipherSuite` from the resulting ServerHello. +fn select_cipher_suite( + mut conn: ServerConnection, + client_hello: ClientHelloPayload, +) -> Result> { + let ch = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::ClientHello( + client_hello, + ))), + }; + conn.read_tls(&mut ch.into_wire_bytes().as_slice())?; + conn.process_new_packets()?; + + let mut flight = vec![]; + conn.write_tls(&mut &mut flight) + .unwrap(); + let mut r = Reader::new(&flight[HEADER_SIZE..]); + let HandshakeMessagePayload(HandshakePayload::ServerHello(server_hello)) = + HandshakeMessagePayload::read(&mut r).unwrap() + else { + panic!("expected ServerHello"); + }; + Ok(server_hello.cipher_suite) +} + +#[test] +fn test_server_rejects_no_extended_master_secret_extension_when_require_ems_or_fips() { + let provider = tls12_only(TEST_PROVIDER.clone()); + let mut config = ServerConfig::builder(provider.into()) + .with_no_client_auth() + .with_single_cert(server_identity(), server_key()) + .unwrap(); + + if !matches!(config.provider.fips(), FipsStatus::Unvalidated) { + assert!(config.require_ems); + } else { + config.require_ems = true; + } + let mut conn = ServerConnection::new(config.into()).unwrap(); + + let mut ch = minimal_client_hello(); + ch.extensions + .extended_master_secret_request + .take(); + let ch = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::ClientHello( + ch, + ))), + }; + conn.read_tls(&mut ch.into_wire_bytes().as_slice()) + .unwrap(); + + assert_eq!( + conn.process_new_packets(), + Err(Error::PeerIncompatible( + PeerIncompatible::ExtendedMasterSecretExtensionRequired + )) + ); +} + +#[test] +fn server_picks_ffdhe_group_when_clienthello_has_no_ffdhe_group_in_groups_ext() { + let config = ServerConfig::builder(Arc::new(CryptoProvider { + tls13_cipher_suites: Cow::default(), + ..ffdhe_provider(TEST_PROVIDER.clone()) + })) + .with_no_client_auth() + .with_single_cert(server_identity(), server_key()) + .unwrap(); + + let mut ch = minimal_client_hello(); + ch.cipher_suites.push( + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 + .common + .suite, + ); + + server_chooses_ffdhe_group_for_client_hello(ServerConnection::new(config.into()).unwrap(), ch); +} + +#[test] +fn server_picks_ffdhe_group_when_clienthello_has_no_groups_ext() { + let config = ServerConfig::builder(Arc::new(CryptoProvider { + tls13_cipher_suites: Cow::default(), + ..ffdhe_provider(TEST_PROVIDER.clone()) + })) + .with_no_client_auth() + .with_single_cert(server_identity(), server_key()) + .unwrap(); + + let mut ch = minimal_client_hello(); + ch.cipher_suites.push( + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 + .common + .suite, + ); + ch.extensions.named_groups.take(); + + server_chooses_ffdhe_group_for_client_hello(ServerConnection::new(config.into()).unwrap(), ch); +} + +#[test] +fn server_accepts_client_with_no_ecpoints_extension_and_only_ffdhe_cipher_suites() { + let config = ServerConfig::builder(Arc::new(CryptoProvider { + tls13_cipher_suites: Cow::default(), + ..ffdhe_provider(TEST_PROVIDER.clone()) + })) + .with_no_client_auth() + .with_single_cert(server_identity(), server_key()) + .unwrap(); + + let mut ch = minimal_client_hello(); + ch.cipher_suites.push( + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 + .common + .suite, + ); + ch.extensions.ec_point_formats.take(); + + server_chooses_ffdhe_group_for_client_hello(ServerConnection::new(config.into()).unwrap(), ch); +} + +fn server_chooses_ffdhe_group_for_client_hello( + mut conn: ServerConnection, + client_hello: ClientHelloPayload, +) { + let ch = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::ClientHello( + client_hello, + ))), + }; + conn.read_tls(&mut ch.into_wire_bytes().as_slice()) + .unwrap(); + conn.process_new_packets().unwrap(); + + let mut flight = vec![]; + conn.write_tls(&mut &mut flight) + .unwrap(); + + let mut r = Reader::new(&flight[HEADER_SIZE..]); + assert!(matches!( + HandshakeMessagePayload::read(&mut r).unwrap(), + HandshakeMessagePayload(HandshakePayload::ServerHello(_)) + )); + assert!(matches!( + HandshakeMessagePayload::read(&mut r).unwrap(), + HandshakeMessagePayload(HandshakePayload::Certificate(_)) + )); + + let HandshakeMessagePayload(HandshakePayload::ServerKeyExchange(skx)) = + HandshakeMessagePayload::read(&mut r).unwrap() + else { + panic!("unexpected third message"); + }; + + skx.unwrap_given_kxa(KeyExchangeAlgorithm::DHE) + .expect("DHE not used"); +} + +#[test] +fn test_server_requiring_rpk_client_rejects_x509_client() { + let Some(server_config) = server_config_for_rpk(TEST_PROVIDER.clone()) else { + return; + }; + + let mut ch = minimal_client_hello(); + ch.extensions.client_certificate_types = Some(vec![CertificateType::X509]); + let ch = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::ClientHello( + ch, + ))), + }; + + let mut conn = ServerConnection::new(Arc::new(server_config)).unwrap(); + conn.read_tls(&mut ch.into_wire_bytes().as_slice()) + .unwrap(); + assert_eq!( + conn.process_new_packets().unwrap_err(), + PeerIncompatible::IncorrectCertificateTypeExtension.into(), + ); +} + +#[test] +fn test_rpk_only_server_rejects_x509_only_client() { + let Some(server_config) = server_config_for_rpk(TEST_PROVIDER.clone()) else { + return; + }; + + let mut ch = minimal_client_hello(); + ch.extensions.server_certificate_types = Some(vec![CertificateType::X509]); + let ch = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::ClientHello( + ch, + ))), + }; + + let mut conn = ServerConnection::new(Arc::new(server_config)).unwrap(); + conn.read_tls(&mut ch.into_wire_bytes().as_slice()) + .unwrap(); + + assert_eq!( + conn.process_new_packets().unwrap_err(), + PeerIncompatible::IncorrectCertificateTypeExtension.into(), + ); +} + +fn server_config_for_rpk(provider: CryptoProvider) -> Option { + let provider = CryptoProvider { + kx_groups: Cow::Owned(vec![ + provider.find_kx_group(NamedGroup::X25519, ProtocolVersion::TLSv1_2)?, + ]), + ..provider + }; + + let credentials = SingleCredential::from(server_credentials(&provider)); + Some( + ServerConfig::builder(Arc::new(provider)) + .with_no_client_auth() + .with_server_credential_resolver(Arc::new(credentials)) + .unwrap(), + ) +} + +fn server_credentials(provider: &CryptoProvider) -> Credentials { + let key = provider + .key_provider + .load_private_key(server_key()) + .unwrap(); + let identity = Arc::from(Identity::RawPublicKey( + key.public_key().unwrap().into_owned(), + )); + Credentials::new_unchecked(identity, key) +} + +fn server_key() -> PrivateKeyDer<'static> { + PrivateKeyDer::from_pem_reader( + &mut include_bytes!("../../../test-ca/ecdsa-p256/end.key").as_slice(), + ) + .unwrap() +} + +fn server_identity() -> Arc> { + Arc::new( + Identity::from_cert_chain(vec![ + CertificateDer::from(&include_bytes!("../../../test-ca/ecdsa-p256/end.der")[..]), + CertificateDer::from(&include_bytes!("../../../test-ca/ecdsa-p256/inter.der")[..]), + ]) + .unwrap(), + ) +} + +fn ffdhe_provider(provider: CryptoProvider) -> CryptoProvider { + CryptoProvider { + kx_groups: Cow::Owned(vec![FAKE_FFDHE_GROUP]), + tls12_cipher_suites: Cow::Owned(vec![&TLS_DHE_RSA_WITH_AES_128_GCM_SHA256]), + ..provider + } +} + +static FAKE_FFDHE_GROUP: &'static dyn SupportedKxGroup = &FakeFfdheGroup; + +#[derive(Debug)] +struct FakeFfdheGroup; + +impl SupportedKxGroup for FakeFfdheGroup { + fn ffdhe_group(&self) -> Option> { + Some(FFDHE2048) + } + + fn name(&self) -> NamedGroup { + NamedGroup::FFDHE2048 + } + + fn start(&self) -> Result { + Ok(StartedKeyExchange::Single(Box::new(ActiveFakeFfdhe))) + } +} + +#[derive(Debug)] +struct ActiveFakeFfdhe; + +impl ActiveKeyExchange for ActiveFakeFfdhe { + #[cfg_attr(coverage_nightly, coverage(off))] + fn complete(self: Box, _peer_pub_key: &[u8]) -> Result { + todo!() + } + + fn pub_key(&self) -> &[u8] { + b"ActiveFakeFfdhe pub key" + } + + fn ffdhe_group(&self) -> Option> { + Some(FFDHE2048) + } + + fn group(&self) -> NamedGroup { + NamedGroup::FFDHE2048 + } +} + +static TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: Tls12CipherSuite = Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + hash_provider: FAKE_HASH, + confidentiality_limit: 1, + }, + kx: KeyExchangeAlgorithm::DHE, + protocol_version: TLS12_VERSION, + prf_provider: &tls12::PrfUsingHmac(FAKE_HMAC), + sign: &[SignatureScheme::ECDSA_NISTP256_SHA256], + aead_alg: &FakeAead, +}; + +static TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: Tls12CipherSuite = Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + hash_provider: FAKE_HASH, + confidentiality_limit: 0, + }, + kx: KeyExchangeAlgorithm::DHE, + protocol_version: TLS12_VERSION, + prf_provider: &tls12::PrfUsingHmac(FAKE_HMAC), + sign: &[SignatureScheme::ECDSA_NISTP256_SHA256], + aead_alg: &FakeAead, +}; + +fn minimal_client_hello() -> ClientHelloPayload { + ClientHelloPayload { + client_version: ProtocolVersion::TLSv1_3, + random: Random::from([0u8; 32]), + session_id: SessionId::empty(), + cipher_suites: vec![CipherSuite(0xff13), CipherSuite(0xff12)], + compression_methods: vec![Compression::Null], + extensions: Box::new(ClientExtensions { + signature_schemes: Some(vec![SignatureScheme::ECDSA_NISTP256_SHA256]), + named_groups: Some(vec![NamedGroup::from(0xfe00)]), + supported_versions: Some(SupportedProtocolVersions { + tls12: true, + tls13: true, + }), + key_shares: Some(vec![KeyShareEntry { + group: NamedGroup::from(0xfe00), + payload: SizedPayload::from(vec![0xab; 32]), + }]), + extended_master_secret_request: Some(()), + ..ClientExtensions::default() + }), + } +} diff --git a/rustls/src/server/tls12.rs b/rustls/src/server/tls12.rs index 3313216998c..73a54d0cbda 100644 --- a/rustls/src/server/tls12.rs +++ b/rustls/src/server/tls12.rs @@ -1,393 +1,462 @@ use alloc::boxed::Box; -use alloc::string::ToString; -use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; -pub(super) use client_hello::CompleteClientHelloHandling; -use pki_types::UnixTime; +pub(crate) use client_hello::TLS12_HANDLER; +use pki_types::{DnsName, UnixTime}; use subtle::ConstantTimeEq; +use zeroize::Zeroize; -use super::common::ActiveCertifiedKey; -use super::hs::{self, ServerContext}; -use super::server_conn::{ProducesTickets, ServerConfig, ServerConnectionData}; +use super::config::ServerConfig; +use super::hs::ServerState; +use super::{CommonServerSessionValue, ServerSessionKey, ServerSessionValue}; use crate::check::inappropriate_message; -use crate::common_state::{CommonState, HandshakeFlightTls12, HandshakeKind, Side, State}; -use crate::conn::ConnectionRandoms; -use crate::crypto::ActiveKeyExchange; -use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion}; -use crate::error::{Error, PeerIncompatible, PeerMisbehaved}; +use crate::common_state::{Event, HandshakeFlightTls12, HandshakeKind, Output, OutputEvent, Side}; +use crate::conn::kernel::KernelState; +use crate::conn::{ConnectionRandoms, Input}; +use crate::crypto::cipher::{MessageDecrypter, MessageEncrypter, Payload}; +use crate::crypto::kx::{ActiveKeyExchange, SupportedKxGroup}; +use crate::crypto::{Identity, TicketProducer}; +use crate::enums::{ + ApplicationProtocol, CertificateType, ContentType, HandshakeType, ProtocolVersion, +}; +use crate::error::{ApiMisuse, Error, InvalidMessage, PeerIncompatible, PeerMisbehaved}; use crate::hash_hs::HandshakeHash; use crate::log::{debug, trace}; -use crate::msgs::base::Payload; -use crate::msgs::ccs::ChangeCipherSpecPayload; -use crate::msgs::codec::Codec; -use crate::msgs::handshake::{ - CertificateChain, ClientKeyExchangeParams, HandshakeMessagePayload, HandshakePayload, - NewSessionTicketPayload, SessionId, +use crate::msgs::{ + CertificateChain, ChangeCipherSpecPayload, ClientKeyExchangeParams, Codec, + HandshakeAlignedProof, HandshakeMessagePayload, HandshakePayload, Message, MessagePayload, + NewSessionTicketPayload, NewSessionTicketPayloadTls13, Reader, SessionId, }; -use crate::msgs::message::{Message, MessagePayload}; -use crate::msgs::persist; use crate::suites::PartiallyExtractedSecrets; +use crate::sync::Arc; use crate::tls12::{self, ConnectionSecrets, Tls12CipherSuite}; -use crate::verify; +use crate::tls13::key_schedule::KeyScheduleTrafficSend; +use crate::verify::{ClientIdentity, SignatureVerificationInput}; +use crate::{ConnectionTrafficSecrets, verify}; + +#[expect(private_interfaces)] +pub(crate) enum Tls12State { + Certificate(Box), + ClientKx(Box), + CertificateVerify(Box), + ChangeCipherSpec(Box), + Finished(Box), + Traffic(Box), +} -mod client_hello { - use pki_types::CertificateDer; +impl Tls12State { + pub(crate) fn handle<'m>( + self, + input: Input<'m>, + output: &mut dyn Output<'m>, + ) -> Result { + match self { + Self::Certificate(e) => e.handle(input, output), + Self::ClientKx(e) => e.handle(input, output), + Self::CertificateVerify(e) => e.handle(input, output), + Self::ChangeCipherSpec(e) => e.handle(input, output), + Self::Finished(e) => e.handle(input, output), + Self::Traffic(e) => e.handle(input, output), + } + } +} +mod client_hello { use super::*; - use crate::common_state::KxState; - use crate::crypto::SupportedKxGroup; - use crate::enums::SignatureScheme; - use crate::msgs::enums::{ClientCertificateType, Compression, ECPointFormat}; - use crate::msgs::handshake::{ - CertificateRequestPayload, CertificateStatus, ClientExtension, ClientHelloPayload, - ClientSessionTicket, Random, ServerExtension, ServerHelloPayload, ServerKeyExchange, - ServerKeyExchangeParams, ServerKeyExchangePayload, + use crate::common_state::OutputEvent; + use crate::crypto::kx::SupportedKxGroup; + use crate::crypto::{SelectedCredential, Signer}; + use crate::msgs::{ + CertificateRequestPayload, CertificateStatus, ClientCertificateType, ClientHelloPayload, + ClientSessionTicket, Compression, Random, ServerExtensionsInput, ServerHelloPayload, + ServerKeyExchange, ServerKeyExchangeParams, ServerKeyExchangePayload, }; - use crate::sign; + use crate::sealed::Sealed; + use crate::server::hs::{ClientHelloInput, ExpectClientHello, ServerHandler, Tls12Extensions}; use crate::verify::DigitallySignedStruct; - pub(in crate::server) struct CompleteClientHelloHandling { - pub(in crate::server) config: Arc, - pub(in crate::server) transcript: HandshakeHash, - pub(in crate::server) session_id: SessionId, - pub(in crate::server) suite: &'static Tls12CipherSuite, - pub(in crate::server) using_ems: bool, - pub(in crate::server) randoms: ConnectionRandoms, - pub(in crate::server) send_ticket: bool, - pub(in crate::server) extra_exts: Vec, - } - - impl CompleteClientHelloHandling { - pub(in crate::server) fn handle_client_hello( - mut self, - cx: &mut ServerContext<'_>, - server_key: ActiveCertifiedKey<'_>, - chm: &Message<'_>, - client_hello: &ClientHelloPayload, - selected_kxg: &'static dyn SupportedKxGroup, - sigschemes_ext: Vec, - tls13_enabled: bool, - ) -> hs::NextStateOrError<'static> { + pub(crate) static TLS12_HANDLER: &dyn ServerHandler = &Handler; + + #[derive(Debug)] + struct Handler; + + impl ServerHandler for Handler { + fn handle_client_hello( + &self, + suite: &'static Tls12CipherSuite, + kx_group: &'static dyn SupportedKxGroup, + credentials: SelectedCredential, + input: ClientHelloInput<'_>, + mut st: ExpectClientHello, + output: &mut dyn Output<'_>, + ) -> Result { + let mut randoms = st.randoms(&input)?; + let mut transcript = st + .transcript + .start(suite.common.hash_provider)?; + // -- TLS1.2 only from hereon in -- - self.transcript.add_message(chm); - - if client_hello.ems_support_offered() { - self.using_ems = true; - } else if self.config.require_ems { - return Err(cx.common.send_fatal_alert( - AlertDescription::HandshakeFailure, - PeerIncompatible::ExtendedMasterSecretExtensionRequired, - )); + transcript.add_message(input.message); + + if input + .client_hello + .extended_master_secret_request + .is_some() + { + st.using_ems = true; + } else if st.config.require_ems { + return Err(PeerIncompatible::ExtendedMasterSecretExtensionRequired.into()); } // "RFC 4492 specified that if this extension is missing, // it means that only the uncompressed point format is // supported" // - - let ecpoints_ext = client_hello - .ecpoints_extension() - .unwrap_or(&[ECPointFormat::Uncompressed]); + let supported_ec_point_formats = input + .client_hello + .ec_point_formats + .unwrap_or_default(); - trace!("ecpoints {:?}", ecpoints_ext); + trace!("ecpoints {supported_ec_point_formats:?}"); - if !ecpoints_ext.contains(&ECPointFormat::Uncompressed) { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerIncompatible::UncompressedEcPointsRequired, - )); + if !supported_ec_point_formats.uncompressed { + return Err(PeerIncompatible::UncompressedEcPointsRequired.into()); } // -- If TLS1.3 is enabled, signal the downgrade in the server random - if tls13_enabled { - self.randoms.server[24..].copy_from_slice(&tls12::DOWNGRADE_SENTINEL); + if !st + .config + .provider + .tls13_cipher_suites + .is_empty() + { + randoms.server[24..].copy_from_slice(&tls12::DOWNGRADE_SENTINEL); } - // -- Check for resumption -- - // We can do this either by (in order of preference): - // 1. receiving a ticket that decrypts - // 2. receiving a sessionid that is in our cache - // - // If we receive a ticket, the sessionid won't be in our - // cache, so don't check. - // - // If either works, we end up with a ServerConnectionValue - // which is passed to start_resumption and concludes - // our handling of the ClientHello. - // - let mut ticket_received = false; - let resume_data = client_hello - .ticket_extension() - .and_then(|ticket_ext| match ticket_ext { - ClientExtension::SessionTicket(ClientSessionTicket::Offer(ticket)) => { - Some(ticket) - } - _ => None, - }) - .and_then(|ticket| { - ticket_received = true; - debug!("Ticket received"); - let data = self - .config - .ticketer - .decrypt(ticket.bytes()); - if data.is_none() { - debug!("Ticket didn't decrypt"); - } - data - }) - .or_else(|| { - // Perhaps resume? If we received a ticket, the sessionid - // does not correspond to a real session. - if client_hello.session_id.is_empty() || ticket_received { - return None; - } - - self.config - .session_storage - .get(client_hello.session_id.as_ref()) - }) - .and_then(|x| persist::ServerSessionValue::read_bytes(&x).ok()) - .filter(|resumedata| { - hs::can_resume(self.suite.into(), &cx.data.sni, self.using_ems, resumedata) - }); + let (ticket_received, resume_data) = check_session( + input.client_hello, + st.sni.as_ref(), + st.using_ems, + suite, + &st.config, + ); if let Some(data) = resume_data { - return self.start_resumption(cx, client_hello, &client_hello.session_id, data); - } - - // Now we have chosen a ciphersuite, we can make kx decisions. - let sigschemes = self - .suite - .resolve_sig_schemes(&sigschemes_ext); - - if sigschemes.is_empty() { - return Err(cx.common.send_fatal_alert( - AlertDescription::HandshakeFailure, - PeerIncompatible::NoSignatureSchemesInCommon, - )); + let proof = input.proof; + return start_resumption( + suite, + st.using_ems, + output, + input, + st.sni, + st.resumption_data, + transcript, + randoms, + st.extra_exts, + st.config, + data, + proof, + ); } - let ecpoint = ECPointFormat::SUPPORTED - .iter() - .find(|format| ecpoints_ext.contains(format)) - .cloned() - .ok_or_else(|| { - cx.common.send_fatal_alert( - AlertDescription::HandshakeFailure, - PeerIncompatible::NoEcPointFormatsInCommon, - ) - })?; - - debug_assert_eq!(ecpoint, ECPointFormat::Uncompressed); - - let mut ocsp_response = server_key.get_ocsp(); + let mut ocsp_response = credentials.ocsp.as_deref(); // If we're not offered a ticket or a potential session ID, allocate a session ID. - if !self.config.session_storage.can_cache() { - self.session_id = SessionId::empty(); - } else if self.session_id.is_empty() && !ticket_received { - self.session_id = SessionId::random(self.config.provider.secure_random)?; + if !st.config.session_storage.can_cache() { + st.session_id = SessionId::empty(); + } else if st.session_id.is_empty() && !ticket_received { + st.session_id = SessionId::random(st.config.provider.secure_random)?; } - cx.common.kx_state = KxState::Start(selected_kxg); - cx.common.handshake_kind = Some(HandshakeKind::Full); + output.output(OutputEvent::HandshakeKind(HandshakeKind::Full)); - let mut flight = HandshakeFlightTls12::new(&mut self.transcript); + let mut flight = HandshakeFlightTls12::new(&mut transcript); - self.send_ticket = emit_server_hello( + let Tls12Extensions { + alpn_protocol, + send_ticket, + } = emit_server_hello( &mut flight, - &self.config, - cx, - self.session_id, - self.suite, - self.using_ems, + &st.config, + output, + st.session_id, + suite, + st.using_ems, &mut ocsp_response, - client_hello, + input.client_hello, None, - &self.randoms, - self.extra_exts, + &randoms, + st.extra_exts, )?; - emit_certificate(&mut flight, server_key.get_cert()); - if let Some(ocsp_response) = ocsp_response { - emit_cert_status(&mut flight, ocsp_response); + emit_certificate(&mut flight, &credentials); + match ocsp_response { + None | Some([]) => {} + Some(response) => emit_cert_status(&mut flight, response), } - let server_kx = emit_server_kx( - &mut flight, - sigschemes, - selected_kxg, - server_key.get_key(), - &self.randoms, - )?; - let doing_client_auth = emit_certificate_req(&mut flight, &self.config)?; + let server_kx = emit_server_kx(&mut flight, kx_group, credentials.signer, &randoms)?; + let doing_client_auth = emit_certificate_req(&mut flight, &st.config)?; emit_server_hello_done(&mut flight); - flight.finish(cx.common); + flight.finish(output); + let hs = HandshakeState { + config: st.config, + transcript, + session_id: st.session_id, + alpn_protocol, + sni: st.sni, + resumption_data: st.resumption_data, + using_ems: st.using_ems, + send_ticket, + }; if doing_client_auth { Ok(Box::new(ExpectCertificate { - config: self.config, - transcript: self.transcript, - randoms: self.randoms, - session_id: self.session_id, - suite: self.suite, - using_ems: self.using_ems, + hs, + randoms, + suite, server_kx, - send_ticket: self.send_ticket, - })) + }) + .into()) } else { Ok(Box::new(ExpectClientKx { - config: self.config, - transcript: self.transcript, - randoms: self.randoms, - session_id: self.session_id, - suite: self.suite, - using_ems: self.using_ems, + hs, + randoms, + suite, server_kx, - client_cert: None, - send_ticket: self.send_ticket, - })) + peer_identity: None, + }) + .into()) } } + } - fn start_resumption( - mut self, - cx: &mut ServerContext<'_>, - client_hello: &ClientHelloPayload, - id: &SessionId, - resumedata: persist::ServerSessionValue, - ) -> hs::NextStateOrError<'static> { - debug!("Resuming connection"); - - if resumedata.extended_ms && !self.using_ems { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::ResumptionAttemptedWithVariedEms, - )); + impl Sealed for Handler {} + + /// Check for resumption + fn check_session( + hello: &ClientHelloPayload, + sni: Option<&DnsName<'_>>, + using_ems: bool, + suite: &'static Tls12CipherSuite, + config: &ServerConfig, + ) -> (bool, Option>) { + // First, check for a ticket that decrypts + let (ticket, encoded) = match hello.session_ticket.as_ref() { + Some(ClientSessionTicket::Offer(ticket)) => { + debug!("Ticket received"); + let data = config + .ticketer + .as_ref() + .and_then(|ticketer| ticketer.decrypt(ticket.bytes())); + match data { + Some(data) => (true, Some(data)), + None => { + debug!("Ticket didn't decrypt"); + (true, None) + } + } } + Some(_) | None => (false, None), + }; - self.session_id = *id; - let mut flight = HandshakeFlightTls12::new(&mut self.transcript); - self.send_ticket = emit_server_hello( - &mut flight, - &self.config, - cx, - self.session_id, - self.suite, - self.using_ems, - &mut None, - client_hello, - Some(&resumedata), - &self.randoms, - self.extra_exts, - )?; - flight.finish(cx.common); + let (ticket, encoded) = match (ticket, encoded) { + (_, Some(data)) => (true, data), + // If we've received a ticket, the session ID won't be in our cache, so skip checking + (false, None) if !hello.session_id.is_empty() => { + // Check for a session ID in our cache + let store = &config.session_storage; + match store.get(ServerSessionKey::from(&hello.session_id)) { + Some(data) => (false, data), + None => return (false, None), + } + } + (ticket, None) => return (ticket, None), + }; - let secrets = ConnectionSecrets::new_resume( - self.randoms, - self.suite, - &resumedata.master_secret.0, - ); - self.config.key_log.log( - "CLIENT_RANDOM", - &secrets.randoms.client, - &secrets.master_secret, - ); - cx.common - .start_encryption_tls12(&secrets, Side::Server); - cx.common.peer_certificates = resumedata.client_cert_chain; - cx.common.handshake_kind = Some(HandshakeKind::Resumed); + // Try to parse the encoded session value + let Ok(ServerSessionValue::Tls12(session)) = ServerSessionValue::read_bytes(&encoded) + else { + return (ticket, None); + }; + + // Check that the session is compatible with the current connection + if !session + .common + .can_resume(suite.common.suite, sni) + { + return (ticket, None); + } + + match session.extended_ms == using_ems || session.extended_ms && !using_ems { + true => (ticket, Some(session.into_owned())), + false => (ticket, None), + } + } - if self.send_ticket { - let now = self.config.current_time()?; + fn start_resumption( + suite: &'static Tls12CipherSuite, + using_ems: bool, + output: &mut dyn Output<'_>, + input: ClientHelloInput<'_>, + sni: Option>, + resumption_data: Vec, + mut transcript: HandshakeHash, + randoms: ConnectionRandoms, + extra_exts: ServerExtensionsInput, + config: Arc, + resumedata: Tls12ServerSessionValue<'static>, + proof: HandshakeAlignedProof, + ) -> Result { + debug!("Resuming connection"); + + if resumedata.extended_ms && !using_ems { + return Err(PeerMisbehaved::ResumptionAttemptedWithVariedEms.into()); + } + let session_id = input.client_hello.session_id; + let mut flight = HandshakeFlightTls12::new(&mut transcript); + let Tls12Extensions { + alpn_protocol, + send_ticket, + } = emit_server_hello( + &mut flight, + &config, + output, + session_id, + suite, + using_ems, + &mut None, + input.client_hello, + Some(&resumedata.common), + &randoms, + extra_exts, + )?; + flight.finish(output); + + let mut hs = HandshakeState { + config, + transcript, + session_id, + alpn_protocol, + sni, + resumption_data: resumption_data.to_vec(), + using_ems, + send_ticket, + }; + + let secrets = + ConnectionSecrets::new_resume(randoms, suite, resumedata.master_secret.as_ref()); + hs.config.key_log.log( + "CLIENT_RANDOM", + &secrets.randoms.client, + secrets.master_secret(), + ); + + output.output(OutputEvent::HandshakeKind(HandshakeKind::Resumed)); + output.emit(Event::ResumptionData( + resumedata + .common + .application_data + .bytes() + .to_vec(), + )); + + if send_ticket { + let now = hs.config.current_time()?; + + if let Some(ticketer) = hs.config.ticketer.as_deref() { emit_ticket( &secrets, - &mut self.transcript, - self.using_ems, - cx, - &*self.config.ticketer, + &mut hs.transcript, + using_ems, + resumedata.common.peer_identity.as_ref(), + hs.alpn_protocol.as_ref(), + hs.sni.as_ref(), + resumption_data, + output, + ticketer, now, )?; } - emit_ccs(cx.common); - cx.common - .record_layer - .start_encrypting(); - emit_finished(&secrets, &mut self.transcript, cx.common); - - Ok(Box::new(ExpectCcs { - config: self.config, - secrets, - transcript: self.transcript, - session_id: self.session_id, - using_ems: self.using_ems, - resuming: true, - send_ticket: self.send_ticket, - })) } + emit_ccs(output); + + let (dec, encrypter) = secrets.make_cipher_pair(Side::Server); + output.send().set_encrypter( + encrypter, + secrets + .suite() + .common + .confidentiality_limit, + ); + emit_finished(&secrets, &mut hs.transcript, output, &proof); + + Ok(Box::new(ExpectCcs { + hs, + secrets, + peer_identity: resumedata.common.peer_identity, + resuming_decrypter: Some(dec), + }) + .into()) } fn emit_server_hello( flight: &mut HandshakeFlightTls12<'_>, config: &ServerConfig, - cx: &mut ServerContext<'_>, + output: &mut dyn Output<'_>, session_id: SessionId, suite: &'static Tls12CipherSuite, using_ems: bool, ocsp_response: &mut Option<&[u8]>, hello: &ClientHelloPayload, - resumedata: Option<&persist::ServerSessionValue>, + resumedata: Option<&CommonServerSessionValue<'_>>, randoms: &ConnectionRandoms, - extra_exts: Vec, - ) -> Result { - let mut ep = hs::ExtensionProcessing::new(); - ep.process_common(config, cx, ocsp_response, hello, resumedata, extra_exts)?; - ep.process_tls12(config, hello, using_ems); - - let sh = HandshakeMessagePayload { - typ: HandshakeType::ServerHello, - payload: HandshakePayload::ServerHello(ServerHelloPayload { - legacy_version: ProtocolVersion::TLSv1_2, - random: Random::from(randoms.server), - session_id, - cipher_suite: suite.common.suite, - compression_method: Compression::Null, - extensions: ep.exts, - }), - }; - trace!("sending server hello {:?}", sh); - flight.add(sh); + extra_exts: ServerExtensionsInput, + ) -> Result { + let (out, extensions) = Tls12Extensions::new( + extra_exts, + ocsp_response, + resumedata, + hello, + output, + using_ems, + config, + )?; - Ok(ep.send_ticket) + let sh = HandshakeMessagePayload(HandshakePayload::ServerHello(ServerHelloPayload { + legacy_version: ProtocolVersion::TLSv1_2, + random: Random::from(randoms.server), + session_id, + cipher_suite: suite.common.suite, + compression_method: Compression::Null, + extensions, + })); + + trace!("sending server hello {sh:?}"); + flight.add(sh); + Ok(out) } - fn emit_certificate( - flight: &mut HandshakeFlightTls12<'_>, - cert_chain: &[CertificateDer<'static>], - ) { - flight.add(HandshakeMessagePayload { - typ: HandshakeType::Certificate, - payload: HandshakePayload::Certificate(CertificateChain(cert_chain.to_vec())), - }); + fn emit_certificate(flight: &mut HandshakeFlightTls12<'_>, credentials: &SelectedCredential) { + flight.add(HandshakeMessagePayload(HandshakePayload::Certificate( + CertificateChain::from_signer(credentials), + ))); } fn emit_cert_status(flight: &mut HandshakeFlightTls12<'_>, ocsp: &[u8]) { - flight.add(HandshakeMessagePayload { - typ: HandshakeType::CertificateStatus, - payload: HandshakePayload::CertificateStatus(CertificateStatus::new(ocsp)), - }); + flight.add(HandshakeMessagePayload( + HandshakePayload::CertificateStatus(CertificateStatus::new(ocsp)), + )); } fn emit_server_kx( flight: &mut HandshakeFlightTls12<'_>, - sigschemes: Vec, selected_group: &'static dyn SupportedKxGroup, - signing_key: &dyn sign::SigningKey, + credentials: Box, randoms: &ConnectionRandoms, - ) -> Result, Error> { - let kx = selected_group.start()?; + ) -> Result { + let kx = selected_group.start()?.into_single(); let kx_params = ServerKeyExchangeParams::new(&*kx); let mut msg = Vec::new(); @@ -395,22 +464,21 @@ mod client_hello { msg.extend(randoms.server); kx_params.encode(&mut msg); - let signer = signing_key - .choose_scheme(&sigschemes) - .ok_or_else(|| Error::General("incompatible signing key".to_string()))?; - let sigscheme = signer.scheme(); - let sig = signer.sign(&msg)?; + let sigscheme = credentials.scheme(); + let sig = credentials.sign(&msg)?; let skx = ServerKeyExchangePayload::from(ServerKeyExchange { params: kx_params, dss: DigitallySignedStruct::new(sigscheme, sig), }); - flight.add(HandshakeMessagePayload { - typ: HandshakeType::ServerKeyExchange, - payload: HandshakePayload::ServerKeyExchange(skx), - }); - Ok(kx) + flight.add(HandshakeMessagePayload( + HandshakePayload::ServerKeyExchange(skx), + )); + Ok(GroupAndKeyExchange { + kx, + group: selected_group, + }) } fn emit_certificate_req( @@ -439,377 +507,378 @@ mod client_hello { canames: names, }; - let creq = HandshakeMessagePayload { - typ: HandshakeType::CertificateRequest, - payload: HandshakePayload::CertificateRequest(cr), - }; + let creq = HandshakeMessagePayload(HandshakePayload::CertificateRequest(cr)); - trace!("Sending CertificateRequest {:?}", creq); + trace!("Sending CertificateRequest {creq:?}"); flight.add(creq); Ok(true) } fn emit_server_hello_done(flight: &mut HandshakeFlightTls12<'_>) { - flight.add(HandshakeMessagePayload { - typ: HandshakeType::ServerHelloDone, - payload: HandshakePayload::ServerHelloDone, - }); + flight.add(HandshakeMessagePayload(HandshakePayload::ServerHelloDone)); } } // --- Process client's Certificate for client auth --- struct ExpectCertificate { - config: Arc, - transcript: HandshakeHash, + hs: HandshakeState, randoms: ConnectionRandoms, - session_id: SessionId, suite: &'static Tls12CipherSuite, - using_ems: bool, - server_kx: Box, - send_ticket: bool, + server_kx: GroupAndKeyExchange, } -impl State for ExpectCertificate { - fn handle<'m>( +impl ExpectCertificate { + fn handle( mut self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - self.transcript.add_message(&m); + Input { message, .. }: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { + self.hs.transcript.add_message(&message); let cert_chain = require_handshake_msg_move!( - m, + message, HandshakeType::Certificate, HandshakePayload::Certificate )?; // If we can't determine if the auth is mandatory, abort let mandatory = self + .hs .config .verifier .client_auth_mandatory(); - trace!("certs {:?}", cert_chain); + trace!("certs {cert_chain:?}"); - let client_cert = match cert_chain.split_first() { + let peer_identity = match Identity::from_peer(cert_chain.0, CertificateType::X509)? { None if mandatory => { - return Err(cx.common.send_fatal_alert( - AlertDescription::CertificateRequired, - Error::NoCertificatesPresented, - )); + return Err(PeerMisbehaved::NoCertificatesPresented.into()); } None => { debug!("client auth requested but no certificate supplied"); - self.transcript.abandon_client_auth(); + self.hs.transcript.abandon_client_auth(); None } - Some((end_entity, intermediates)) => { - let now = self.config.current_time()?; - - self.config + Some(identity) => { + self.hs + .config .verifier - .verify_client_cert(end_entity, intermediates, now) - .map_err(|err| { - cx.common - .send_cert_verify_error_alert(err) + .verify_identity(&ClientIdentity { + identity: &identity, + now: self.hs.config.current_time()?, })?; - - Some(cert_chain) + Some(identity.into_owned()) } }; Ok(Box::new(ExpectClientKx { - config: self.config, - transcript: self.transcript, + hs: self.hs, randoms: self.randoms, - session_id: self.session_id, suite: self.suite, - using_ems: self.using_ems, server_kx: self.server_kx, - client_cert, - send_ticket: self.send_ticket, - })) + peer_identity, + }) + .into()) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::Certificate(value)) } } // --- Process client's KeyExchange --- -struct ExpectClientKx<'a> { - config: Arc, - transcript: HandshakeHash, +struct ExpectClientKx { + hs: HandshakeState, randoms: ConnectionRandoms, - session_id: SessionId, suite: &'static Tls12CipherSuite, - using_ems: bool, - server_kx: Box, - client_cert: Option>, - send_ticket: bool, + server_kx: GroupAndKeyExchange, + peer_identity: Option>, } -impl State for ExpectClientKx<'_> { - fn handle<'m>( +impl ExpectClientKx { + fn handle( mut self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { + Input { message, .. }: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { let client_kx = require_handshake_msg!( - m, + message, HandshakeType::ClientKeyExchange, HandshakePayload::ClientKeyExchange )?; - self.transcript.add_message(&m); + self.hs.transcript.add_message(&message); let ems_seed = self + .hs .using_ems - .then(|| self.transcript.current_hash()); + .then(|| self.hs.transcript.current_hash()); // Complete key agreement, and set up encryption with the // resulting premaster secret. - let peer_kx_params = tls12::decode_kx_params::( - self.suite.kx, - cx.common, - client_kx.bytes(), - )?; + let peer_kx_params = + tls12::decode_kx_params::(self.suite.kx, client_kx.bytes())?; + let secrets = ConnectionSecrets::from_key_exchange( - self.server_kx, + self.server_kx.kx, peer_kx_params.pub_key(), ems_seed, self.randoms, self.suite, - ) - .map_err(|err| { - cx.common - .send_fatal_alert(AlertDescription::IllegalParameter, err) - })?; - cx.common.kx_state.complete(); + )?; + output.output(OutputEvent::KeyExchangeGroup(self.server_kx.group)); - self.config.key_log.log( + self.hs.config.key_log.log( "CLIENT_RANDOM", &secrets.randoms.client, - &secrets.master_secret, + secrets.master_secret(), ); - cx.common - .start_encryption_tls12(&secrets, Side::Server); - if let Some(client_cert) = self.client_cert { - Ok(Box::new(ExpectCertificateVerify { - config: self.config, + match self.peer_identity { + Some(peer_identity) => Ok(Box::new(ExpectCertificateVerify { + hs: self.hs, secrets, - transcript: self.transcript, - session_id: self.session_id, - using_ems: self.using_ems, - client_cert, - send_ticket: self.send_ticket, - })) - } else { - Ok(Box::new(ExpectCcs { - config: self.config, + peer_identity, + }) + .into()), + _ => Ok(Box::new(ExpectCcs { + hs: self.hs, secrets, - transcript: self.transcript, - session_id: self.session_id, - using_ems: self.using_ems, - resuming: false, - send_ticket: self.send_ticket, - })) + peer_identity: None, + resuming_decrypter: None, + }) + .into()), } } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - Box::new(ExpectClientKx { - config: self.config, - transcript: self.transcript, - randoms: self.randoms, - session_id: self.session_id, - suite: self.suite, - using_ems: self.using_ems, - server_kx: self.server_kx, - client_cert: self - .client_cert - .map(|cert| cert.into_owned()), - send_ticket: self.send_ticket, - }) +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::ClientKx(value)) } } // --- Process client's certificate proof --- -struct ExpectCertificateVerify<'a> { - config: Arc, +struct ExpectCertificateVerify { + hs: HandshakeState, secrets: ConnectionSecrets, - transcript: HandshakeHash, - session_id: SessionId, - using_ems: bool, - client_cert: CertificateChain<'a>, - send_ticket: bool, + peer_identity: Identity<'static>, } -impl State for ExpectCertificateVerify<'_> { - fn handle<'m>( +impl ExpectCertificateVerify { + fn handle( mut self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - let rc = { - let sig = require_handshake_msg!( - m, - HandshakeType::CertificateVerify, - HandshakePayload::CertificateVerify - )?; + Input { message, .. }: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { + let signature = require_handshake_msg!( + message, + HandshakeType::CertificateVerify, + HandshakePayload::CertificateVerify + )?; - match self.transcript.take_handshake_buf() { - Some(msgs) => { - let certs = &self.client_cert; - self.config - .verifier - .verify_tls12_signature(&msgs, &certs[0], sig) - } - None => { - // This should be unreachable; the handshake buffer was initialized with - // client authentication if the verifier wants to offer it. - // `transcript.abandon_client_auth()` can extract it, but its only caller in - // this flow will also set `ExpectClientKx::client_cert` to `None`, making it - // impossible to reach this state. - return Err(cx.common.send_fatal_alert( - AlertDescription::AccessDenied, - Error::General("client authentication not set up".into()), - )); - } + match self.hs.transcript.take_handshake_buf() { + Some(msgs) => { + self.hs + .config + .verifier + .verify_tls12_signature(&SignatureVerificationInput { + message: &msgs, + signer: &self.peer_identity.as_signer(), + signature, + })?; + } + None => { + // This should be unreachable; the handshake buffer was initialized with + // client authentication if the verifier wants to offer it. + // `transcript.abandon_client_auth()` can extract it, but its only caller in + // this flow will also set `ExpectClientKx::client_cert` to `None`, making it + // impossible to reach this state. + return Err(Error::Unreachable("client authentication not set up")); } - }; - - if let Err(e) = rc { - return Err(cx - .common - .send_cert_verify_error_alert(e)); } trace!("client CertificateVerify OK"); - cx.common.peer_certificates = Some(self.client_cert.into_owned()); - self.transcript.add_message(&m); + self.hs.transcript.add_message(&message); Ok(Box::new(ExpectCcs { - config: self.config, + hs: self.hs, secrets: self.secrets, - transcript: self.transcript, - session_id: self.session_id, - using_ems: self.using_ems, - resuming: false, - send_ticket: self.send_ticket, - })) + peer_identity: Some(self.peer_identity), + resuming_decrypter: None, + }) + .into()) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - Box::new(ExpectCertificateVerify { - config: self.config, - secrets: self.secrets, - transcript: self.transcript, - session_id: self.session_id, - using_ems: self.using_ems, - client_cert: self.client_cert.into_owned(), - send_ticket: self.send_ticket, - }) +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::CertificateVerify(value)) } } // --- Process client's ChangeCipherSpec --- struct ExpectCcs { - config: Arc, + hs: HandshakeState, secrets: ConnectionSecrets, - transcript: HandshakeHash, - session_id: SessionId, - using_ems: bool, - resuming: bool, - send_ticket: bool, + peer_identity: Option>, + resuming_decrypter: Option>, } -impl State for ExpectCcs { - fn handle<'m>( +impl ExpectCcs { + fn handle( self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { + input: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { + match input.message.payload { MessagePayload::ChangeCipherSpec(..) => {} payload => { return Err(inappropriate_message( &payload, &[ContentType::ChangeCipherSpec], - )) + )); } } // CCS should not be received interleaved with fragmented handshake-level // message. - cx.common.check_aligned_handshake()?; + let proof = input.check_aligned_handshake()?; + + let (decrypter, pending_encrypter) = match self.resuming_decrypter { + Some(dec) => (dec, None), + None => { + let (dec, enc) = self + .secrets + .make_cipher_pair(Side::Server); + (dec, Some(enc)) + } + }; + + output + .receive() + .decrypt_state + .set_message_decrypter(decrypter, &proof); - cx.common - .record_layer - .start_decrypting(); Ok(Box::new(ExpectFinished { - config: self.config, + hs: self.hs, secrets: self.secrets, - transcript: self.transcript, - session_id: self.session_id, - using_ems: self.using_ems, - resuming: self.resuming, - send_ticket: self.send_ticket, - })) + peer_identity: self.peer_identity, + resuming: pending_encrypter.is_none(), + pending_encrypter, + }) + .into()) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::ChangeCipherSpec(value)) } } -// --- Process client's Finished --- -fn get_server_connection_value_tls12( - secrets: &ConnectionSecrets, - using_ems: bool, - cx: &ServerContext<'_>, - time_now: UnixTime, -) -> persist::ServerSessionValue { - let version = ProtocolVersion::TLSv1_2; - - let mut v = persist::ServerSessionValue::new( - cx.data.sni.as_ref(), - version, - secrets.suite().common.suite, - secrets.master_secret(), - cx.common.peer_certificates.clone(), - cx.common.alpn_protocol.clone(), - cx.data.resumption_data.clone(), - time_now, - 0, - ); +#[derive(Debug)] +pub(crate) struct Tls12ServerSessionValue<'a> { + common: CommonServerSessionValue<'a>, + master_secret: ZeroizingCow<'a, 48>, + extended_ms: bool, +} + +impl<'a> Tls12ServerSessionValue<'a> { + fn new( + common: CommonServerSessionValue<'a>, + master_secret: &'a [u8; 48], + extended_ms: bool, + ) -> Self { + Self { + common, + master_secret: ZeroizingCow::Borrowed(master_secret), + extended_ms, + } + } + + fn into_owned(self) -> Tls12ServerSessionValue<'static> { + Tls12ServerSessionValue { + common: self.common.into_owned(), + master_secret: ZeroizingCow::Owned(match self.master_secret { + ZeroizingCow::Borrowed(b) => *b, + ZeroizingCow::Owned(o) => o, + }), + extended_ms: self.extended_ms, + } + } +} + +impl Codec<'_> for Tls12ServerSessionValue<'_> { + fn encode(&self, bytes: &mut Vec) { + self.common.encode(bytes); + bytes.extend_from_slice(self.master_secret.as_ref()); + (self.extended_ms as u8).encode(bytes); + } + + fn read(r: &mut Reader<'_>) -> Result { + Ok(Self { + common: CommonServerSessionValue::read(r)?, + master_secret: ZeroizingCow::Owned(r.take_array("MasterSecret").copied()?), + extended_ms: matches!(u8::read(r)?, 1), + }) + } +} + +impl<'a> From> for ServerSessionValue<'a> { + fn from(value: Tls12ServerSessionValue<'a>) -> Self { + Self::Tls12(value) + } +} + +#[derive(Debug)] +enum ZeroizingCow<'a, const N: usize> { + Borrowed(&'a [u8; N]), + Owned([u8; N]), +} - if using_ems { - v.set_extended_ms_used(); +impl AsRef<[u8; N]> for ZeroizingCow<'_, N> { + fn as_ref(&self) -> &[u8; N] { + match self { + ZeroizingCow::Borrowed(b) => b, + ZeroizingCow::Owned(o) => o, + } } +} - v +impl Drop for ZeroizingCow<'_, N> { + #[inline(never)] + fn drop(&mut self) { + if let ZeroizingCow::Owned(o) = self { + o.zeroize(); + } + } } fn emit_ticket( secrets: &ConnectionSecrets, transcript: &mut HandshakeHash, using_ems: bool, - cx: &mut ServerContext<'_>, - ticketer: &dyn ProducesTickets, + peer_identity: Option<&Identity<'static>>, + alpn_protocol: Option<&ApplicationProtocol<'_>>, + sni: Option<&DnsName<'static>>, + resumption_data: Vec, + output: &mut dyn Output<'_>, + ticketer: &dyn TicketProducer, now: UnixTime, ) -> Result<(), Error> { - let plain = get_server_connection_value_tls12(secrets, using_ems, cx, now).get_encoding(); + let plain = ServerSessionValue::from(Tls12ServerSessionValue::new( + CommonServerSessionValue::new( + sni, + secrets.suite().common.suite, + peer_identity.cloned(), + alpn_protocol.map(|p| p.to_owned()), + resumption_data, + now, + ), + secrets.master_secret(), + using_ems, + )) + .get_encoding(); // If we can't produce a ticket for some reason, we can't // report an error. Send an empty one. @@ -820,97 +889,104 @@ fn emit_ticket( let m = Message { version: ProtocolVersion::TLSv1_2, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::NewSessionTicket, - payload: HandshakePayload::NewSessionTicket(NewSessionTicketPayload::new( + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::NewSessionTicket(NewSessionTicketPayload::new( ticket_lifetime, ticket, )), - }), + )), }; transcript.add_message(&m); - cx.common.send_msg(m, false); + output.send_msg(m, false); Ok(()) } -fn emit_ccs(common: &mut CommonState) { - let m = Message { - version: ProtocolVersion::TLSv1_2, - payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}), - }; - - common.send_msg(m, false); +fn emit_ccs(output: &mut dyn Output<'_>) { + output.send_msg( + Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}), + }, + false, + ); } fn emit_finished( secrets: &ConnectionSecrets, transcript: &mut HandshakeHash, - common: &mut CommonState, + output: &mut dyn Output<'_>, + proof: &HandshakeAlignedProof, ) { let vh = transcript.current_hash(); - let verify_data = secrets.server_verify_data(&vh); - let verify_data_payload = Payload::new(verify_data); + let verify_data = secrets.server_verify_data(&vh, proof); + let verify_data_payload = Payload::Borrowed(&verify_data); let f = Message { version: ProtocolVersion::TLSv1_2, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::Finished, - payload: HandshakePayload::Finished(verify_data_payload), - }), + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::Finished( + verify_data_payload, + ))), }; transcript.add_message(&f); - common.send_msg(f, true); + output.send_msg(f, true); } -struct ExpectFinished { - config: Arc, +pub(super) struct ExpectFinished { + hs: HandshakeState, secrets: ConnectionSecrets, - transcript: HandshakeHash, - session_id: SessionId, - using_ems: bool, + peer_identity: Option>, resuming: bool, - send_ticket: bool, + pending_encrypter: Option>, } -impl State for ExpectFinished { - fn handle<'m>( +impl ExpectFinished { + fn handle( mut self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - let finished = - require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?; + input: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { + let finished = require_handshake_msg!( + input.message, + HandshakeType::Finished, + HandshakePayload::Finished + )?; - cx.common.check_aligned_handshake()?; + let proof = input.check_aligned_handshake()?; - let vh = self.transcript.current_hash(); - let expect_verify_data = self.secrets.client_verify_data(&vh); + let vh = self.hs.transcript.current_hash(); + let expect_verify_data = self + .secrets + .client_verify_data(&vh, &proof); - let _fin_verified = + let fin_verified = match ConstantTimeEq::ct_eq(&expect_verify_data[..], finished.bytes()).into() { true => verify::FinishedMessageVerified::assertion(), false => { - return Err(cx - .common - .send_fatal_alert(AlertDescription::DecryptError, Error::DecryptError)); + return Err(PeerMisbehaved::IncorrectFinished.into()); } }; // Save connection, perhaps - if !self.resuming && !self.session_id.is_empty() { - let now = self.config.current_time()?; - - let value = get_server_connection_value_tls12(&self.secrets, self.using_ems, cx, now); - - let worked = self - .config - .session_storage - .put(self.session_id.as_ref().to_vec(), value.get_encoding()); + if !self.resuming && !self.hs.session_id.is_empty() { + let value = ServerSessionValue::from(Tls12ServerSessionValue::new( + CommonServerSessionValue::new( + self.hs.sni.as_ref(), + self.secrets.suite().common.suite, + self.peer_identity.clone(), + self.hs.alpn_protocol.clone(), + self.hs.resumption_data.to_vec(), + self.hs.config.current_time()?, + ), + self.secrets.master_secret(), + self.hs.using_ems, + )); + + let worked = self.hs.config.session_storage.put( + ServerSessionKey::from(&self.hs.session_id), + value.get_encoding(), + ); if worked { debug!("Session saved"); } else { @@ -919,60 +995,97 @@ impl State for ExpectFinished { } // Send our CCS and Finished. - self.transcript.add_message(&m); - if !self.resuming { - if self.send_ticket { - let now = self.config.current_time()?; - emit_ticket( - &self.secrets, - &mut self.transcript, - self.using_ems, - cx, - &*self.config.ticketer, - now, - )?; + self.hs + .transcript + .add_message(&input.message); + if let Some(encrypter) = self.pending_encrypter { + assert!(!self.resuming); + if self.hs.send_ticket { + let now = self.hs.config.current_time()?; + if let Some(ticketer) = self.hs.config.ticketer.as_deref() { + emit_ticket( + &self.secrets, + &mut self.hs.transcript, + self.hs.using_ems, + self.peer_identity.as_ref(), + self.hs.alpn_protocol.as_ref(), + self.hs.sni.as_ref(), + self.hs.resumption_data, + output, + ticketer, + now, + )?; + } } - emit_ccs(cx.common); - cx.common - .record_layer - .start_encrypting(); - emit_finished(&self.secrets, &mut self.transcript, cx.common); + emit_ccs(output); + output.send().set_encrypter( + encrypter, + self.secrets + .suite() + .common + .confidentiality_limit, + ); + emit_finished(&self.secrets, &mut self.hs.transcript, output, &proof); } - cx.common - .start_traffic(&mut cx.sendable_plaintext); + if let Some(identity) = self.peer_identity { + output.output(OutputEvent::PeerIdentity(identity)); + } + + let extracted_secrets = self + .hs + .config + .enable_secret_extraction + .then(|| { + self.secrets + .extract_secrets(Side::Server) + }); + + output.output(OutputEvent::Exporter(self.secrets.into_exporter())); + output.start_traffic(); + Ok(Box::new(ExpectTraffic { - secrets: self.secrets, - _fin_verified, - })) + extracted_secrets, + _fin_verified: fin_verified, + }) + .into()) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::Finished(value)) } } +struct HandshakeState { + config: Arc, + transcript: HandshakeHash, + session_id: SessionId, + alpn_protocol: Option>, + sni: Option>, + resumption_data: Vec, + using_ems: bool, + send_ticket: bool, +} + // --- Process traffic --- -struct ExpectTraffic { - secrets: ConnectionSecrets, +pub(super) struct ExpectTraffic { + // only `Some` if `config.enable_secret_extraction` is true + extracted_secrets: Option>, _fin_verified: verify::FinishedMessageVerified, } impl ExpectTraffic {} -impl State for ExpectTraffic { +impl ExpectTraffic { fn handle<'m>( self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { - MessagePayload::ApplicationData(payload) => cx - .common - .take_received_plaintext(payload), + Input { message, .. }: Input<'m>, + output: &mut dyn Output<'m>, + ) -> Result { + match message.payload { + MessagePayload::ApplicationData(payload) => output.received_plaintext(payload), payload => { return Err(inappropriate_message( &payload, @@ -980,26 +1093,43 @@ impl State for ExpectTraffic { )); } } - Ok(self) + Ok(self.into()) + } + + pub(super) fn into_external_state( + mut self: Box, + _send_keys: &Option>, + ) -> Result<(PartiallyExtractedSecrets, Box), Error> { + match self.extracted_secrets.take() { + Some(extracted_secrets) => Ok((extracted_secrets?, self)), + None => Err(ApiMisuse::SecretExtractionRequiresPriorOptIn.into()), + } + } +} + +impl KernelState for ExpectTraffic { + fn update_rx_secret(&mut self) -> Result { + Err(ApiMisuse::KeyUpdateNotAvailableForTls12.into()) } - fn export_keying_material( + #[cfg_attr(coverage_nightly, coverage(off))] + fn handle_new_session_ticket( &self, - output: &mut [u8], - label: &[u8], - context: Option<&[u8]>, + _message: &NewSessionTicketPayloadTls13, ) -> Result<(), Error> { - self.secrets - .export_keying_material(output, label, context); - Ok(()) + unreachable!( + "server connections should never have handle_new_session_ticket called on them" + ) } +} - fn extract_secrets(&self) -> Result { - self.secrets - .extract_secrets(Side::Server) +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls12(Tls12State::Traffic(value)) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self - } +struct GroupAndKeyExchange { + group: &'static dyn SupportedKxGroup, + kx: Box, } diff --git a/rustls/src/server/tls13.rs b/rustls/src/server/tls13.rs index e90de2fba3a..94d60b38cd0 100644 --- a/rustls/src/server/tls13.rs +++ b/rustls/src/server/tls13.rs @@ -1,394 +1,319 @@ use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; +use core::time::Duration; -pub(super) use client_hello::CompleteClientHelloHandling; -use pki_types::{CertificateDer, UnixTime}; +pub(crate) use client_hello::TLS13_HANDLER; +use pki_types::{DnsName, UnixTime}; use subtle::ConstantTimeEq; +use zeroize::Zeroizing; -use super::hs::{self, HandshakeHashOrBuffer, ServerContext}; -use super::server_conn::ServerConnectionData; +use super::config::ServerConfig; +use super::hs::{HandshakeHashOrBuffer, ServerState}; +use super::{CommonServerSessionValue, ServerSessionKey, ServerSessionValue}; use crate::check::{inappropriate_handshake_message, inappropriate_message}; -use crate::common_state::{ - CommonState, HandshakeFlightTls13, HandshakeKind, Protocol, Side, State, +use crate::common_state::{Event, HandshakeFlightTls13, HandshakeKind, Output, OutputEvent, Side}; +use crate::conn::kernel::KernelState; +use crate::conn::{ConnectionRandoms, Input, TrafficTemperCounters}; +use crate::crypto::cipher::Payload; +use crate::crypto::kx::NamedGroup; +use crate::crypto::{Identity, rand}; +use crate::enums::{ + ApplicationProtocol, CertificateType, ContentType, HandshakeType, ProtocolVersion, }; -use crate::conn::ConnectionRandoms; -use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion}; -use crate::error::{Error, InvalidMessage, PeerIncompatible, PeerMisbehaved}; +use crate::error::{ApiMisuse, Error, InvalidMessage, PeerIncompatible, PeerMisbehaved}; use crate::hash_hs::HandshakeHash; use crate::log::{debug, trace, warn}; -use crate::msgs::codec::{Codec, Reader}; -use crate::msgs::enums::KeyUpdateRequest; -use crate::msgs::handshake::{ - CertificateChain, CertificatePayloadTls13, HandshakeMessagePayload, HandshakePayload, - NewSessionTicketExtension, NewSessionTicketPayloadTls13, CERTIFICATE_MAX_SIZE_LIMIT, +use crate::msgs::{ + CERTIFICATE_MAX_SIZE_LIMIT, CertificatePayloadTls13, Codec, HandshakeMessagePayload, + HandshakePayload, KeyUpdateRequest, Message, MessagePayload, NewSessionTicketPayloadTls13, + PresharedKeyIdentity, Reader, SizedPayload, }; -use crate::msgs::message::{Message, MessagePayload}; -use crate::msgs::persist; -use crate::server::ServerConfig; +use crate::server::hs::ExpectClientHello; use crate::suites::PartiallyExtractedSecrets; +use crate::sync::Arc; use crate::tls13::key_schedule::{ - KeyScheduleTraffic, KeyScheduleTrafficWithClientFinishedPending, ResumptionSecret, + KeyScheduleResumption, KeyScheduleTrafficReceive, KeyScheduleTrafficSend, + KeyScheduleTrafficWithClientFinishedPending, }; use crate::tls13::{ - construct_client_verify_message, construct_server_verify_message, Tls13CipherSuite, + Tls13CipherSuite, construct_client_verify_message, construct_server_verify_message, }; -use crate::{compress, rand, verify}; +use crate::verify::ClientIdentity; +use crate::{ConnectionTrafficSecrets, compress, verify}; + +#[expect(private_interfaces)] +pub(crate) enum Tls13State { + SkipRejectedEarlyData(Box), + CertificateOrCompressedCertificate(Box), + Certificate(Box), + CertificateVerify(Box), + EarlyData(Box), + Finished(Box), + Traffic(Box), + QuicTraffic(Box), +} + +impl Tls13State { + pub(crate) fn handle<'m>( + self, + input: Input<'m>, + output: &mut dyn Output<'m>, + ) -> Result { + match self { + Self::SkipRejectedEarlyData(e) => e.handle(input, output), + Self::CertificateOrCompressedCertificate(e) => e.handle(input, output), + Self::Certificate(e) => e.handle(input, output), + Self::CertificateVerify(e) => e.handle(input, output), + Self::EarlyData(e) => e.handle(input, output), + Self::Finished(e) => e.handle(input, output), + Self::Traffic(e) => e.handle(input, output), + Self::QuicTraffic(e) => e.handle(input, output), + } + } +} mod client_hello { use super::*; + use crate::common_state::{EarlyDataEvent, Protocol}; use crate::compress::CertCompressor; - use crate::crypto::SupportedKxGroup; - use crate::enums::SignatureScheme; - use crate::msgs::base::{Payload, PayloadU8}; - use crate::msgs::ccs::ChangeCipherSpecPayload; - use crate::msgs::enums::{Compression, NamedGroup, PSKKeyExchangeMode}; - use crate::msgs::handshake::{ - CertReqExtension, CertificatePayloadTls13, CertificateRequestPayloadTls13, - ClientHelloPayload, HelloRetryExtension, HelloRetryRequest, KeyShareEntry, Random, - ServerExtension, ServerHelloPayload, SessionId, + use crate::crypto::cipher::Payload; + use crate::crypto::kx::SupportedKxGroup; + use crate::crypto::{SelectedCredential, Signer}; + use crate::enums::ApplicationProtocol; + use crate::msgs::{ + CertificatePayloadTls13, CertificateRequestExtensions, CertificateRequestPayloadTls13, + ChangeCipherSpecPayload, ClientHelloPayload, Compression, HandshakeAlignedProof, + HelloRetryRequest, HelloRetryRequestExtensions, KeyShareEntry, Random, ServerExtensions, + ServerExtensionsInput, ServerHelloPayload, SessionId, SizedPayload, }; - use crate::server::common::ActiveCertifiedKey; - use crate::sign; + use crate::sealed::Sealed; + use crate::server::Tls13ServerSessionValue; + use crate::server::hs::{ClientHelloInput, ExpectClientHello, ServerHandler, Tls13Extensions}; use crate::tls13::key_schedule::{ - KeyScheduleEarly, KeyScheduleHandshake, KeySchedulePreHandshake, + KeyScheduleEarlyServer, KeyScheduleHandshake, KeySchedulePreHandshake, }; use crate::verify::DigitallySignedStruct; - #[derive(PartialEq)] - pub(super) enum EarlyDataDecision { - Disabled, - RequestedButRejected, - Accepted, - } + pub(crate) static TLS13_HANDLER: &'static dyn ServerHandler = &Handler; - pub(in crate::server) struct CompleteClientHelloHandling { - pub(in crate::server) config: Arc, - pub(in crate::server) transcript: HandshakeHash, - pub(in crate::server) suite: &'static Tls13CipherSuite, - pub(in crate::server) randoms: ConnectionRandoms, - pub(in crate::server) done_retry: bool, - pub(in crate::server) send_tickets: usize, - pub(in crate::server) extra_exts: Vec, - } + #[derive(Debug)] + struct Handler; - fn max_early_data_size(configured: u32) -> usize { - if configured != 0 { - configured as usize - } else { - // The relevant max_early_data_size may in fact be unknowable: if - // we (the server) have turned off early_data but the client has - // a stale ticket from when we allowed early_data: we'll naturally - // reject early_data but need an upper bound on the amount of data - // to drop. - // - // Use a single maximum-sized message. - 16384 - } - } - - impl CompleteClientHelloHandling { - fn check_binder( + impl ServerHandler for Handler { + fn handle_client_hello( &self, suite: &'static Tls13CipherSuite, - client_hello: &Message<'_>, - psk: &[u8], - binder: &[u8], - ) -> bool { - let binder_plaintext = match &client_hello.payload { - MessagePayload::Handshake { parsed, .. } => parsed.encoding_for_binder_signing(), - _ => unreachable!(), - }; - - let handshake_hash = self + kx_group: &'static dyn SupportedKxGroup, + signer: SelectedCredential, + input: ClientHelloInput<'_>, + mut st: ExpectClientHello, + output: &mut dyn Output<'_>, + ) -> Result { + let randoms = st.randoms(&input)?; + let mut transcript = st .transcript - .hash_given(&binder_plaintext); - - let key_schedule = KeyScheduleEarly::new(suite, psk); - let real_binder = - key_schedule.resumption_psk_binder_key_and_sign_verify_data(&handshake_hash); - - ConstantTimeEq::ct_eq(real_binder.as_ref(), binder).into() - } - - fn attempt_tls13_ticket_decryption( - &mut self, - ticket: &[u8], - ) -> Option { - if self.config.ticketer.enabled() { - self.config - .ticketer - .decrypt(ticket) - .and_then(|plain| persist::ServerSessionValue::read_bytes(&plain).ok()) - } else { - self.config - .session_storage - .take(ticket) - .and_then(|plain| persist::ServerSessionValue::read_bytes(&plain).ok()) + .start(suite.common.hash_provider)?; + + if input + .client_hello + .compression_methods + .len() + != 1 + { + return Err(PeerMisbehaved::OfferedIncorrectCompressions.into()); } - } - pub(in crate::server) fn handle_client_hello( - mut self, - cx: &mut ServerContext<'_>, - server_key: ActiveCertifiedKey<'_>, - chm: &Message<'_>, - client_hello: &ClientHelloPayload, - selected_kxg: &'static dyn SupportedKxGroup, - mut sigschemes_ext: Vec, - ) -> hs::NextStateOrError<'static> { - if client_hello.compression_methods.len() != 1 { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::OfferedIncorrectCompressions, - )); - } - - sigschemes_ext.retain(SignatureScheme::supported_in_tls13); - - let shares_ext = client_hello - .keyshare_extension() - .ok_or_else(|| { - cx.common.send_fatal_alert( - AlertDescription::HandshakeFailure, - PeerIncompatible::KeyShareExtensionRequired, - ) - })?; - - if client_hello.has_keyshare_extension_with_duplicates() { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::OfferedDuplicateKeyShares, - )); + let shares_ext = input + .client_hello + .key_shares + .as_ref() + .ok_or(PeerIncompatible::KeyShareExtensionRequired)?; + + if input + .client_hello + .has_keyshare_extension_with_duplicates() + { + return Err(PeerMisbehaved::OfferedDuplicateKeyShares.into()); } - if client_hello.has_certificate_compression_extension_with_duplicates() { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::OfferedDuplicateCertificateCompressions, - )); + if input + .client_hello + .has_certificate_compression_extension_with_duplicates() + { + return Err(PeerMisbehaved::OfferedDuplicateCertificateCompressions.into()); } - let cert_compressor = client_hello - .certificate_compression_extension() + let cert_compressor = input + .client_hello + .certificate_compression_algorithms + .as_ref() .and_then(|offered| // prefer server order when choosing a compression: the client's // extension here does not denote any preference. - self.config + st.config .cert_compressors .iter() .find(|compressor| offered.contains(&compressor.algorithm())) - .cloned()); + .copied()); - let early_data_requested = client_hello.early_data_extension_offered(); + let early_data_requested = input + .client_hello + .early_data_request + .is_some(); // EarlyData extension is illegal in second ClientHello - if self.done_retry && early_data_requested { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::EarlyDataAttemptedInSecondClientHello, - ) - }); + if st.done_retry && early_data_requested { + return Err(PeerMisbehaved::EarlyDataAttemptedInSecondClientHello.into()); } // See if there is a KeyShare for the selected kx group. - let chosen_share_and_kxg = shares_ext.iter().find_map(|share| { - (share.group == selected_kxg.name()).then_some((share, selected_kxg)) - }); + let chosen_share_and_kxg = shares_ext + .iter() + .find_map(|share| (share.group == kx_group.name()).then_some((share, kx_group))); let Some(chosen_share_and_kxg) = chosen_share_and_kxg else { // We don't have a suitable key share. Send a HelloRetryRequest // for the mutually_preferred_group. - self.transcript.add_message(chm); + transcript.add_message(input.message); - if self.done_retry { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::RefusedToFollowHelloRetryRequest, - )); + if st.done_retry { + return Err(PeerMisbehaved::RefusedToFollowHelloRetryRequest.into()); } emit_hello_retry_request( - &mut self.transcript, - self.suite, - client_hello.session_id, - cx.common, - selected_kxg.name(), + &mut transcript, + suite, + input.client_hello.session_id, + output, + kx_group.name(), ); - emit_fake_ccs(cx.common); + if !st.protocol.is_quic() { + emit_fake_ccs(output); + } - let skip_early_data = max_early_data_size(self.config.max_early_data_size); + let skip_early_data = max_early_data_size(st.config.max_early_data_size); - let next = Box::new(hs::ExpectClientHello { - config: self.config, - transcript: HandshakeHashOrBuffer::Hash(self.transcript), - #[cfg(feature = "tls12")] + let next = Box::new(ExpectClientHello { + transcript: HandshakeHashOrBuffer::Hash(transcript), session_id: SessionId::empty(), - #[cfg(feature = "tls12")] using_ems: false, done_retry: true, - send_tickets: self.send_tickets, - extra_exts: self.extra_exts, + ..st }); - return if early_data_requested { Ok(Box::new(ExpectAndSkipRejectedEarlyData { skip_data_left: skip_early_data, next, - })) + }) + .into()) } else { - Ok(next) + Ok(next.into()) }; }; - let mut chosen_psk_index = None; - let mut resumedata = None; - - if let Some(psk_offer) = client_hello.psk() { - if !client_hello.check_psk_ext_is_last() { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::PskExtensionMustBeLast, - )); - } - - // "A client MUST provide a "psk_key_exchange_modes" extension if it - // offers a "pre_shared_key" extension. If clients offer - // "pre_shared_key" without a "psk_key_exchange_modes" extension, - // servers MUST abort the handshake." - RFC8446 4.2.9 - if client_hello.psk_modes().is_none() { - return Err(cx.common.send_fatal_alert( - AlertDescription::MissingExtension, - PeerMisbehaved::MissingPskModesExtension, - )); - } - - if psk_offer.binders.is_empty() { - return Err(cx.common.send_fatal_alert( - AlertDescription::DecodeError, - PeerMisbehaved::MissingBinderInPskExtension, - )); - } - - if psk_offer.binders.len() != psk_offer.identities.len() { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::PskExtensionWithMismatchedIdsAndBinders, - )); - } - - let now = self.config.current_time()?; - - for (i, psk_id) in psk_offer.identities.iter().enumerate() { - let maybe_resume_data = self - .attempt_tls13_ticket_decryption(&psk_id.identity.0) - .map(|resumedata| { - resumedata.set_freshness(psk_id.obfuscated_ticket_age, now) - }) - .filter(|resumedata| { - hs::can_resume(self.suite.into(), &cx.data.sni, false, resumedata) - }); - - let Some(resume) = maybe_resume_data else { - continue; - }; - - if !self.check_binder( - self.suite, - chm, - &resume.master_secret.0, - psk_offer.binders[i].as_ref(), - ) { - return Err(cx.common.send_fatal_alert( - AlertDescription::DecryptError, - PeerMisbehaved::IncorrectBinder, - )); - } - - chosen_psk_index = Some(i); - resumedata = Some(resume); - break; - } - } + let mut resuming = handle_psk_offer( + &input, + &transcript, + st.sni.as_ref(), + suite, + st.protocol, + &st.config, + )?; - if !client_hello.psk_mode_offered(PSKKeyExchangeMode::PSK_DHE_KE) { - debug!("Client unwilling to resume, DHE_KE not offered"); - self.send_tickets = 0; - chosen_psk_index = None; - resumedata = None; + if !input + .client_hello + .preshared_key_modes + .as_ref() + .map(|offer| offer.psk_dhe) + .unwrap_or_default() + { + debug!("Client unwilling to resume, PSK_DHE_KE not offered"); + st.send_tickets = 0; + resuming = None; } else { - self.send_tickets = self.config.send_tls13_tickets; + st.send_tickets = st.config.send_tls13_tickets; } - if let Some(ref resume) = resumedata { - cx.data.received_resumption_data = Some(resume.application_data.0.clone()); - cx.common - .peer_certificates - .clone_from(&resume.client_cert_chain); + if let Some((_, session)) = &resuming { + output.emit(Event::ResumptionData( + session + .common + .application_data + .bytes() + .to_vec(), + )); } - let full_handshake = resumedata.is_none(); - self.transcript.add_message(chm); + let full_handshake = resuming.is_none(); + transcript.add_message(input.message); let key_schedule = emit_server_hello( - &mut self.transcript, - &self.randoms, - self.suite, - cx, - &client_hello.session_id, + &mut transcript, + &randoms, + suite, + st.protocol, + output, + &input.client_hello.session_id, chosen_share_and_kxg, - chosen_psk_index, - resumedata - .as_ref() - .map(|x| &x.master_secret.0[..]), - &self.config, + resuming.as_ref(), + &input.proof, + &st.config, )?; - if !self.done_retry { - emit_fake_ccs(cx.common); + if !st.done_retry && !st.protocol.is_quic() { + emit_fake_ccs(output); } - if full_handshake { - cx.common - .handshake_kind - .get_or_insert(HandshakeKind::Full); - } else { - cx.common.handshake_kind = Some(HandshakeKind::Resumed); - } + output.output(OutputEvent::HandshakeKind( + match (full_handshake, st.done_retry) { + (true, true) => HandshakeKind::FullWithHelloRetryRequest, + (true, false) => HandshakeKind::Full, + (false, true) => HandshakeKind::ResumedWithHelloRetryRequest, + (false, false) => HandshakeKind::Resumed, + }, + )); - let mut ocsp_response = server_key.get_ocsp(); - let mut flight = HandshakeFlightTls13::new(&mut self.transcript); - let doing_early_data = emit_encrypted_extensions( + let mut ocsp_response = signer.ocsp.as_deref(); + let mut flight = HandshakeFlightTls13::new(&mut transcript); + let ( + Tls13Extensions { + certificate_types, + alpn_protocol, + }, + doing_early_data, + ) = emit_encrypted_extensions( &mut flight, - self.suite, - cx, + suite, + output, &mut ocsp_response, - client_hello, - resumedata.as_ref(), - self.extra_exts, - &self.config, + input.client_hello, + resuming + .as_ref() + .map(|(_, session)| session), + st.extra_exts, + &st.config, )?; let doing_client_auth = if full_handshake { - let client_auth = emit_certificate_req_tls13(&mut flight, &self.config)?; + let client_auth = emit_certificate_req_tls13(&mut flight, &st.config)?; if let Some(compressor) = cert_compressor { emit_compressed_certificate_tls13( &mut flight, - &self.config, - server_key.get_cert(), + &st.config, + &signer, ocsp_response, compressor, ); } else { - emit_certificate_tls13(&mut flight, server_key.get_cert(), ocsp_response); + emit_certificate_tls13( + &mut flight, + CertificatePayloadTls13::new( + signer.identity.as_certificates(), + ocsp_response, + ), + ); } - emit_certificate_verify_tls13( - &mut flight, - cx.common, - server_key.get_key(), - &sigschemes_ext, - )?; + emit_certificate_verify_tls13(&mut flight, signer.signer)?; client_auth } else { false @@ -398,121 +323,227 @@ mod client_hello { // are encrypted with the handshake keys. match doing_early_data { EarlyDataDecision::Disabled => { - key_schedule.set_handshake_decrypter(None, cx.common); - cx.data.early_data.reject(); + key_schedule.set_handshake_decrypter(None, output.receive(), &input.proof); } EarlyDataDecision::RequestedButRejected => { - debug!("Client requested early_data, but not accepted: switching to handshake keys with trial decryption"); + debug!( + "Client requested early_data, but not accepted: switching to handshake keys with trial decryption" + ); key_schedule.set_handshake_decrypter( - Some(max_early_data_size(self.config.max_early_data_size)), - cx.common, + Some(max_early_data_size(st.config.max_early_data_size)), + output.receive(), + &input.proof, ); - cx.data.early_data.reject(); } - EarlyDataDecision::Accepted => { - cx.data - .early_data - .accept(self.config.max_early_data_size as usize); + EarlyDataDecision::Accepted { .. } => { + output.emit(Event::EarlyData(EarlyDataEvent::Accepted)); } } - cx.common.check_aligned_handshake()?; - let key_schedule_traffic = - emit_finished_tls13(flight, &self.randoms, cx, key_schedule, &self.config); + let key_schedule_traffic = emit_finished_tls13( + flight, + &randoms, + output, + key_schedule, + &st.config, + &input.proof, + ); - if !doing_client_auth && self.config.send_half_rtt_data { + if !doing_client_auth && st.config.send_half_rtt_data { // Application data can be sent immediately after Finished, in one // flight. However, if client auth is enabled, we don't want to send // application data to an unauthenticated peer. - cx.common - .start_outgoing_traffic(&mut cx.sendable_plaintext); + output.send().start_traffic(); } + let hs = HandshakeState { + config: st.config, + transcript, + suite, + alpn_protocol, + sni: st.sni, + resumption_data: st.resumption_data, + send_tickets: st.send_tickets, + }; + if doing_client_auth { - if self - .config - .cert_decompressors - .is_empty() - { + if hs.config.cert_decompressors.is_empty() { Ok(Box::new(ExpectCertificate { - config: self.config, - transcript: self.transcript, - suite: self.suite, + hs, key_schedule: key_schedule_traffic, - send_tickets: self.send_tickets, - message_already_in_transcript: false, - })) + expected_certificate_type: certificate_types.client, + }) + .into()) } else { Ok(Box::new(ExpectCertificateOrCompressedCertificate { - config: self.config, - transcript: self.transcript, - suite: self.suite, + hs, key_schedule: key_schedule_traffic, - send_tickets: self.send_tickets, - })) + expected_certificate_type: certificate_types.client, + }) + .into()) } - } else if doing_early_data == EarlyDataDecision::Accepted && !cx.common.is_quic() { + } else if matches!(doing_early_data, EarlyDataDecision::Accepted { .. }) + && !st.protocol.is_quic() + { + let EarlyDataDecision::Accepted { max_length } = doing_early_data else { + unreachable!(); + }; // Not used for QUIC: RFC 9001 §8.3: Clients MUST NOT send the EndOfEarlyData // message. A server MUST treat receipt of a CRYPTO frame in a 0-RTT packet as a // connection error of type PROTOCOL_VIOLATION. Ok(Box::new(ExpectEarlyData { - config: self.config, - transcript: self.transcript, - suite: self.suite, + hs, key_schedule: key_schedule_traffic, - send_tickets: self.send_tickets, - })) + peer_identity: resuming.and_then(|(_, session)| session.common.peer_identity), + remaining_length: max_length as usize, + }) + .into()) } else { Ok(Box::new(ExpectFinished { - config: self.config, - transcript: self.transcript, - suite: self.suite, + hs, key_schedule: key_schedule_traffic, - send_tickets: self.send_tickets, - })) + peer_identity: resuming.and_then(|(_, session)| session.common.peer_identity), + }) + .into()) + } + } + } + + impl Sealed for Handler {} + + #[derive(PartialEq)] + pub(super) enum EarlyDataDecision { + Disabled, + RequestedButRejected, + Accepted { max_length: u32 }, + } + + fn max_early_data_size(configured: u32) -> usize { + if configured != 0 { + configured as usize + } else { + // The relevant max_early_data_size may in fact be unknowable: if + // we (the server) have turned off early_data but the client has + // a stale ticket from when we allowed early_data: we'll naturally + // reject early_data but need an upper bound on the amount of data + // to drop. + // + // Use a single maximum-sized message. + 16384 + } + } + + fn handle_psk_offer( + input: &ClientHelloInput<'_>, + transcript: &HandshakeHash, + sni: Option<&DnsName<'_>>, + suite: &'static Tls13CipherSuite, + protocol: Protocol, + config: &ServerConfig, + ) -> Result)>, Error> { + let Some(psk_offer) = &input.client_hello.preshared_key_offer else { + return Ok(None); + }; + + // "A client MUST provide a "psk_key_exchange_modes" extension if it + // offers a "pre_shared_key" extension. If clients offer + // "pre_shared_key" without a "psk_key_exchange_modes" extension, + // servers MUST abort the handshake." - RFC8446 4.2.9 + if input + .client_hello + .preshared_key_modes + .is_none() + { + return Err(PeerMisbehaved::MissingPskModesExtension.into()); + } + + if psk_offer.binders.is_empty() { + return Err(PeerMisbehaved::MissingBinderInPskExtension.into()); + } + + if psk_offer.binders.len() != psk_offer.identities.len() { + return Err(PeerMisbehaved::PskExtensionWithMismatchedIdsAndBinders.into()); + } + + let now = config.current_time()?; + for (i, psk_id) in psk_offer.identities.iter().enumerate() { + let Some(mut session) = Tls13ServerSessionValue::from_ticket(psk_id, config) else { + continue; + }; + + session.set_freshness(psk_id.obfuscated_ticket_age, now); + if !session + .common + .can_resume(suite.common.suite, sni) + { + continue; + } + + if !check_binder( + transcript, + &KeyScheduleEarlyServer::new(protocol, suite, session.secret.bytes()), + input.message, + psk_offer.binders[i].as_ref(), + ) { + return Err(PeerMisbehaved::IncorrectBinder.into()); } + + return Ok(Some((i, session.into_owned()))); } + + Ok(None) + } + + fn check_binder( + transcript: &HandshakeHash, + key_schedule: &KeyScheduleEarlyServer, + client_hello: &Message<'_>, + binder: &[u8], + ) -> bool { + let binder_plaintext = match &client_hello.payload { + MessagePayload::Handshake { parsed, encoded } => { + &encoded.bytes()[..encoded.bytes().len() - parsed.total_binder_length()] + } + _ => unreachable!(), + }; + + let handshake_hash = transcript.hash_given(binder_plaintext); + + let real_binder = + key_schedule.resumption_psk_binder_key_and_sign_verify_data(&handshake_hash); + + ConstantTimeEq::ct_eq(real_binder.as_ref(), binder).into() } fn emit_server_hello( transcript: &mut HandshakeHash, randoms: &ConnectionRandoms, suite: &'static Tls13CipherSuite, - cx: &mut ServerContext<'_>, + protocol: Protocol, + output: &mut dyn Output<'_>, session_id: &SessionId, share_and_kxgroup: (&KeyShareEntry, &'static dyn SupportedKxGroup), - chosen_psk_idx: Option, - resuming_psk: Option<&[u8]>, + resuming: Option<&(usize, Tls13ServerSessionValue<'_>)>, + proof: &HandshakeAlignedProof, config: &ServerConfig, ) -> Result { - let mut extensions = Vec::new(); - // Prepare key exchange; the caller already found the matching SupportedKxGroup let (share, kxgroup) = share_and_kxgroup; debug_assert_eq!(kxgroup.name(), share.group); - let ckx = kxgroup - .start_and_complete(&share.payload.0) - .map_err(|err| { - cx.common - .send_fatal_alert(AlertDescription::IllegalParameter, err) - })?; - cx.common.kx_state.complete(); - - extensions.push(ServerExtension::KeyShare(KeyShareEntry::new( - ckx.group, - ckx.pub_key, - ))); - extensions.push(ServerExtension::SupportedVersions(ProtocolVersion::TLSv1_3)); + let ckx = kxgroup.start_and_complete(share.payload.bytes())?; + output.output(OutputEvent::KeyExchangeGroup(kxgroup)); - if let Some(psk_idx) = chosen_psk_idx { - extensions.push(ServerExtension::PresharedKey(psk_idx as u16)); - } + let extensions = Box::new(ServerExtensions { + key_share: Some(KeyShareEntry::new(ckx.group, ckx.pub_key)), + preshared_key: resuming.map(|&(idx, _)| idx as u16), + selected_version: Some(ProtocolVersion::TLSv1_3), + ..Default::default() + }); let sh = Message { version: ProtocolVersion::TLSv1_2, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::ServerHello, - payload: HandshakePayload::ServerHello(ServerHelloPayload { + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ServerHello(ServerHelloPayload { legacy_version: ProtocolVersion::TLSv1_2, random: Random::from(randoms.server), session_id: *session_id, @@ -520,30 +551,40 @@ mod client_hello { compression_method: Compression::Null, extensions, }), - }), + )), }; - cx.common.check_aligned_handshake()?; - let client_hello_hash = transcript.hash_given(&[]); - trace!("sending server hello {:?}", sh); + trace!("sending server hello {sh:?}"); transcript.add_message(&sh); - cx.common.send_msg(sh, false); + output.send_msg(sh, false); // Start key schedule - let key_schedule_pre_handshake = if let Some(psk) = resuming_psk { - let early_key_schedule = KeyScheduleEarly::new(suite, psk); + let key_schedule_pre_handshake = if let Some((_, psk)) = resuming { + let early_key_schedule = + KeyScheduleEarlyServer::new(protocol, suite, psk.secret.bytes()); early_key_schedule.client_early_traffic_secret( &client_hello_hash, &*config.key_log, &randoms.client, - cx.common, + output, + proof, ); + if config.max_early_data_size > 0 { + output.output(OutputEvent::EarlyExporter( + early_key_schedule.early_exporter( + &client_hello_hash, + &*config.key_log, + &randoms.client, + ), + )); + } + KeySchedulePreHandshake::from(early_key_schedule) } else { - KeySchedulePreHandshake::new(suite) + KeySchedulePreHandshake::new(Side::Server, protocol, suite) }; // Do key exchange @@ -554,67 +595,62 @@ mod client_hello { handshake_hash, &*config.key_log, &randoms.client, - cx.common, + output, ); Ok(key_schedule) } - fn emit_fake_ccs(common: &mut CommonState) { - if common.is_quic() { - return; - } + fn emit_fake_ccs(output: &mut dyn Output<'_>) { let m = Message { version: ProtocolVersion::TLSv1_2, payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}), }; - common.send_msg(m, false); + output.send_msg(m, false); } fn emit_hello_retry_request( transcript: &mut HandshakeHash, suite: &'static Tls13CipherSuite, session_id: SessionId, - common: &mut CommonState, + output: &mut dyn Output<'_>, group: NamedGroup, ) { - let mut req = HelloRetryRequest { + let req = HelloRetryRequest { legacy_version: ProtocolVersion::TLSv1_2, session_id, cipher_suite: suite.common.suite, - extensions: Vec::new(), + extensions: HelloRetryRequestExtensions { + key_share: Some(group), + supported_versions: Some(ProtocolVersion::TLSv1_3), + ..Default::default() + }, }; - req.extensions - .push(HelloRetryExtension::KeyShare(group)); - req.extensions - .push(HelloRetryExtension::SupportedVersions( - ProtocolVersion::TLSv1_3, - )); - let m = Message { version: ProtocolVersion::TLSv1_2, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::HelloRetryRequest, - payload: HandshakePayload::HelloRetryRequest(req), - }), + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::HelloRetryRequest(req), + )), }; - trace!("Requesting retry {:?}", m); + trace!("Requesting retry {m:?}"); transcript.rollup_for_hrr(); transcript.add_message(&m); - common.send_msg(m, false); - common.handshake_kind = Some(HandshakeKind::FullWithHelloRetryRequest); + output.send_msg(m, false); } fn decide_if_early_data_allowed( - cx: &mut ServerContext<'_>, + output: &mut dyn Output<'_>, client_hello: &ClientHelloPayload, - resumedata: Option<&persist::ServerSessionValue>, + resumedata: Option<&Tls13ServerSessionValue<'_>>, + chosen_alpn_protocol: Option<&ApplicationProtocol<'_>>, suite: &'static Tls13CipherSuite, config: &ServerConfig, ) -> EarlyDataDecision { - let early_data_requested = client_hello.early_data_extension_offered(); + let early_data_requested = client_hello + .early_data_request + .is_some(); let rejected_or_disabled = match early_data_requested { true => EarlyDataDecision::RequestedButRejected, false => EarlyDataDecision::Disabled, @@ -627,7 +663,7 @@ mod client_hello { /* Non-zero max_early_data_size controls whether early_data is allowed at all. * We also require stateful resumption. */ - let early_data_configured = config.max_early_data_size > 0 && !config.ticketer.enabled(); + let early_data_configured = config.max_early_data_size > 0 && config.ticketer.is_none(); /* "For PSKs provisioned via NewSessionTicket, a server MUST validate * that the ticket age for the selected PSK identity (computed by @@ -647,16 +683,16 @@ mod client_hello { * (RFC8446, 4.2.10) */ let early_data_possible = early_data_requested && resume.is_fresh() - && Some(resume.version) == cx.common.negotiated_version - && resume.cipher_suite == suite.common.suite - && resume.alpn.as_ref().map(|x| &x.0) == cx.common.alpn_protocol.as_ref(); + && resume.common.cipher_suite == suite.common.suite + && resume.common.alpn.as_ref() == chosen_alpn_protocol; - if early_data_configured && early_data_possible && !cx.data.early_data.was_rejected() { - EarlyDataDecision::Accepted + if early_data_configured && early_data_possible { + EarlyDataDecision::Accepted { + max_length: config.max_early_data_size, + } } else { - if cx.common.is_quic() { - // Clobber value set in tls13::emit_server_hello - cx.common.quic.early_secret = None; + if let Some(quic) = output.quic() { + quic.early_secret(None); } rejected_or_disabled @@ -666,29 +702,39 @@ mod client_hello { fn emit_encrypted_extensions( flight: &mut HandshakeFlightTls13<'_>, suite: &'static Tls13CipherSuite, - cx: &mut ServerContext<'_>, + output: &mut dyn Output<'_>, ocsp_response: &mut Option<&[u8]>, hello: &ClientHelloPayload, - resumedata: Option<&persist::ServerSessionValue>, - extra_exts: Vec, + resumedata: Option<&Tls13ServerSessionValue<'_>>, + extra_exts: ServerExtensionsInput, config: &ServerConfig, - ) -> Result { - let mut ep = hs::ExtensionProcessing::new(); - ep.process_common(config, cx, ocsp_response, hello, resumedata, extra_exts)?; + ) -> Result<(Tls13Extensions, EarlyDataDecision), Error> { + let (out, mut extensions) = Tls13Extensions::new( + extra_exts, + ocsp_response, + resumedata.map(|r| &r.common), + hello, + output, + config, + )?; - let early_data = decide_if_early_data_allowed(cx, hello, resumedata, suite, config); - if early_data == EarlyDataDecision::Accepted { - ep.exts.push(ServerExtension::EarlyData); + let early_data = decide_if_early_data_allowed( + output, + hello, + resumedata, + out.alpn_protocol.as_ref(), + suite, + config, + ); + if let EarlyDataDecision::Accepted { .. } = early_data { + extensions.early_data_ack = Some(()); } - let ee = HandshakeMessagePayload { - typ: HandshakeType::EncryptedExtensions, - payload: HandshakePayload::EncryptedExtensions(ep.exts), - }; + let ee = HandshakeMessagePayload(HandshakePayload::EncryptedExtensions(extensions)); - trace!("sending encrypted extensions {:?}", ee); + trace!("sending encrypted extensions {ee:?}"); flight.add(ee); - Ok(early_data) + Ok((out, early_data)) } fn emit_certificate_req_tls13( @@ -699,114 +745,87 @@ mod client_hello { return Ok(false); } - let mut cr = CertificateRequestPayloadTls13 { - context: PayloadU8::empty(), - extensions: Vec::new(), - }; - - let schemes = config - .verifier - .supported_verify_schemes(); - cr.extensions - .push(CertReqExtension::SignatureAlgorithms(schemes.to_vec())); - - if !config.cert_decompressors.is_empty() { - cr.extensions - .push(CertReqExtension::CertificateCompressionAlgorithms( + let cr = CertificateRequestPayloadTls13 { + context: SizedPayload::from(Payload::Borrowed(&[])), + extensions: CertificateRequestExtensions { + signature_algorithms: Some( config - .cert_decompressors - .iter() - .map(|decomp| decomp.algorithm()) - .collect(), - )); - } - - let authorities = config.verifier.root_hint_subjects(); - if !authorities.is_empty() { - cr.extensions - .push(CertReqExtension::AuthorityNames(authorities.to_vec())); - } - - let creq = HandshakeMessagePayload { - typ: HandshakeType::CertificateRequest, - payload: HandshakePayload::CertificateRequestTls13(cr), + .verifier + .supported_verify_schemes(), + ), + authority_names: match config + .verifier + .root_hint_subjects() + .as_ref() + { + [] => None, + authorities => Some(authorities.to_vec()), + }, + certificate_compression_algorithms: match config.cert_decompressors.as_slice() { + &[] => None, + decomps => Some( + decomps + .iter() + .map(|decomp| decomp.algorithm()) + .collect(), + ), + }, + }, }; - trace!("Sending CertificateRequest {:?}", creq); + let creq = HandshakeMessagePayload(HandshakePayload::CertificateRequestTls13(cr)); + + trace!("Sending CertificateRequest {creq:?}"); flight.add(creq); Ok(true) } fn emit_certificate_tls13( flight: &mut HandshakeFlightTls13<'_>, - cert_chain: &[CertificateDer<'static>], - ocsp_response: Option<&[u8]>, + payload: CertificatePayloadTls13<'_>, ) { - let cert = HandshakeMessagePayload { - typ: HandshakeType::Certificate, - payload: HandshakePayload::CertificateTls13(CertificatePayloadTls13::new( - cert_chain.iter(), - ocsp_response, - )), - }; - - trace!("sending certificate {:?}", cert); + let cert = HandshakeMessagePayload(HandshakePayload::CertificateTls13(payload)); + trace!("sending certificate {cert:?}"); flight.add(cert); } fn emit_compressed_certificate_tls13( flight: &mut HandshakeFlightTls13<'_>, config: &ServerConfig, - cert_chain: &[CertificateDer<'static>], + signer: &SelectedCredential, ocsp_response: Option<&[u8]>, cert_compressor: &'static dyn CertCompressor, ) { - let payload = CertificatePayloadTls13::new(cert_chain.iter(), ocsp_response); - + let payload = + CertificatePayloadTls13::new(signer.identity.as_certificates(), ocsp_response); let Ok(entry) = config .cert_compression_cache .compression_for(cert_compressor, &payload) else { - return emit_certificate_tls13(flight, cert_chain, ocsp_response); + return emit_certificate_tls13(flight, payload); }; - let c = HandshakeMessagePayload { - typ: HandshakeType::CompressedCertificate, - payload: HandshakePayload::CompressedCertificate(entry.compressed_cert_payload()), - }; + let c = HandshakeMessagePayload(HandshakePayload::CompressedCertificate( + entry.compressed_cert_payload(), + )); - trace!("sending compressed certificate {:?}", c); + trace!("sending compressed certificate {c:?}"); flight.add(c); } fn emit_certificate_verify_tls13( flight: &mut HandshakeFlightTls13<'_>, - common: &mut CommonState, - signing_key: &dyn sign::SigningKey, - schemes: &[SignatureScheme], + signer: Box, ) -> Result<(), Error> { let message = construct_server_verify_message(&flight.transcript.current_hash()); - - let signer = signing_key - .choose_scheme(schemes) - .ok_or_else(|| { - common.send_fatal_alert( - AlertDescription::HandshakeFailure, - PeerIncompatible::NoSignatureSchemesInCommon, - ) - })?; - let scheme = signer.scheme(); let sig = signer.sign(message.as_ref())?; let cv = DigitallySignedStruct::new(scheme, sig); - let cv = HandshakeMessagePayload { - typ: HandshakeType::CertificateVerify, - payload: HandshakePayload::CertificateVerify(cv), - }; + let cv = HandshakeMessagePayload(HandshakePayload::CertificateVerify(cv)); - trace!("sending certificate-verify {:?}", cv); + trace!("sending certificate-verify {cv:?}"); flight.add(cv); Ok(()) } @@ -814,23 +833,21 @@ mod client_hello { fn emit_finished_tls13( mut flight: HandshakeFlightTls13<'_>, randoms: &ConnectionRandoms, - cx: &mut ServerContext<'_>, + output: &mut dyn Output<'_>, key_schedule: KeyScheduleHandshake, config: &ServerConfig, + proof: &HandshakeAlignedProof, ) -> KeyScheduleTrafficWithClientFinishedPending { let handshake_hash = flight.transcript.current_hash(); - let verify_data = key_schedule.sign_server_finish(&handshake_hash); + let verify_data = key_schedule.sign_server_finish(&handshake_hash, proof); let verify_data_payload = Payload::new(verify_data.as_ref()); - let fin = HandshakeMessagePayload { - typ: HandshakeType::Finished, - payload: HandshakePayload::Finished(verify_data_payload), - }; + let fin = HandshakeMessagePayload(HandshakePayload::Finished(verify_data_payload)); - trace!("sending finished {:?}", fin); + trace!("sending finished {fin:?}"); flight.add(fin); let hash_at_server_fin = flight.transcript.current_hash(); - flight.finish(cx.common); + flight.finish(output); // Now move to application data keys. Read key change is deferred until // the Finish message is received & validated. @@ -838,94 +855,75 @@ mod client_hello { hash_at_server_fin, &*config.key_log, &randoms.client, - cx.common, + output, ) } } struct ExpectAndSkipRejectedEarlyData { skip_data_left: usize, - next: Box, + next: Box, } -impl State for ExpectAndSkipRejectedEarlyData { - fn handle<'m>( +impl ExpectAndSkipRejectedEarlyData { + fn handle( mut self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { + input: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { /* "The server then ignores early data by skipping all records with an external * content type of "application_data" (indicating that they are encrypted), * up to the configured max_early_data_size." * (RFC8446, 14.2.10) */ - if let MessagePayload::ApplicationData(ref skip_data) = m.payload { + if let MessagePayload::ApplicationData(skip_data) = &input.message.payload { if skip_data.bytes().len() <= self.skip_data_left { self.skip_data_left -= skip_data.bytes().len(); - return Ok(self); + return Ok(self.into()); } } - self.next.handle(cx, m) + self.next.handle(input, output) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::SkipRejectedEarlyData(value)) } } struct ExpectCertificateOrCompressedCertificate { - config: Arc, - transcript: HandshakeHash, - suite: &'static Tls13CipherSuite, + hs: HandshakeState, key_schedule: KeyScheduleTrafficWithClientFinishedPending, - send_tickets: usize, + expected_certificate_type: CertificateType, } -impl State for ExpectCertificateOrCompressedCertificate { - fn handle<'m>( +impl ExpectCertificateOrCompressedCertificate { + fn handle( self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { + input: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { + match input.message.payload { MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateTls13(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateTls13(..)), .. - } => Box::new(ExpectCertificate { - config: self.config, - transcript: self.transcript, - suite: self.suite, + } => ExpectCertificate { + hs: self.hs, key_schedule: self.key_schedule, - send_tickets: self.send_tickets, - message_already_in_transcript: false, - }) - .handle(cx, m), + expected_certificate_type: self.expected_certificate_type, + } + .handle_input(input), MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CompressedCertificate(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CompressedCertificate(..)), .. - } => Box::new(ExpectCompressedCertificate { - config: self.config, - transcript: self.transcript, - suite: self.suite, + } => ExpectCompressedCertificate { + hs: self.hs, key_schedule: self.key_schedule, - send_tickets: self.send_tickets, - }) - .handle(cx, m), + expected_certificate_type: self.expected_certificate_type, + } + .handle_input(input), payload => Err(inappropriate_handshake_message( &payload, @@ -937,249 +935,200 @@ impl State for ExpectCertificateOrCompressedCertificate { )), } } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::CertificateOrCompressedCertificate(value)) } } struct ExpectCompressedCertificate { - config: Arc, - transcript: HandshakeHash, - suite: &'static Tls13CipherSuite, + hs: HandshakeState, key_schedule: KeyScheduleTrafficWithClientFinishedPending, - send_tickets: usize, + expected_certificate_type: CertificateType, } -impl State for ExpectCompressedCertificate { - fn handle<'m>( - mut self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - self.transcript.add_message(&m); +impl ExpectCompressedCertificate { + fn handle_input(mut self, Input { message, .. }: Input<'_>) -> Result { + self.hs.transcript.add_message(&message); let compressed_cert = require_handshake_msg_move!( - m, + message, HandshakeType::CompressedCertificate, HandshakePayload::CompressedCertificate )?; let selected_decompressor = self + .hs .config .cert_decompressors .iter() .find(|item| item.algorithm() == compressed_cert.alg); let Some(decompressor) = selected_decompressor else { - return Err(cx.common.send_fatal_alert( - AlertDescription::BadCertificate, - PeerMisbehaved::SelectedUnofferedCertCompression, - )); + return Err(PeerMisbehaved::SelectedUnofferedCertCompression.into()); }; if compressed_cert.uncompressed_len as usize > CERTIFICATE_MAX_SIZE_LIMIT { - return Err(cx.common.send_fatal_alert( - AlertDescription::BadCertificate, - InvalidMessage::MessageTooLarge, - )); + return Err(InvalidMessage::CertificatePayloadTooLarge.into()); } let mut decompress_buffer = vec![0u8; compressed_cert.uncompressed_len as usize]; if let Err(compress::DecompressionFailed) = - decompressor.decompress(compressed_cert.compressed.0.bytes(), &mut decompress_buffer) + decompressor.decompress(compressed_cert.compressed.bytes(), &mut decompress_buffer) { - return Err(cx.common.send_fatal_alert( - AlertDescription::BadCertificate, - PeerMisbehaved::InvalidCertCompression, - )); + return Err(PeerMisbehaved::InvalidCertCompression.into()); } - let cert_payload = - match CertificatePayloadTls13::read(&mut Reader::init(&decompress_buffer)) { - Ok(cm) => cm, - Err(err) => { - return Err(cx - .common - .send_fatal_alert(AlertDescription::BadCertificate, err)); - } - }; + let cert_payload = CertificatePayloadTls13::read(&mut Reader::new(&decompress_buffer))?; trace!( "Client certificate decompressed using {:?} ({} bytes -> {})", compressed_cert.alg, - compressed_cert - .compressed - .0 - .bytes() - .len(), + compressed_cert.compressed.bytes().len(), compressed_cert.uncompressed_len, ); - let m = Message { - version: ProtocolVersion::TLSv1_3, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::Certificate, - payload: HandshakePayload::CertificateTls13(cert_payload.into_owned()), - }), - }; - - Box::new(ExpectCertificate { - config: self.config, - transcript: self.transcript, - suite: self.suite, + ExpectCertificate { + hs: self.hs, key_schedule: self.key_schedule, - send_tickets: self.send_tickets, - message_already_in_transcript: true, - }) - .handle(cx, m) - } - - fn into_owned(self: Box) -> hs::NextState<'static> { - self + expected_certificate_type: self.expected_certificate_type, + } + .handle_certificate(cert_payload) } } struct ExpectCertificate { - config: Arc, - transcript: HandshakeHash, - suite: &'static Tls13CipherSuite, + hs: HandshakeState, key_schedule: KeyScheduleTrafficWithClientFinishedPending, - send_tickets: usize, - message_already_in_transcript: bool, + expected_certificate_type: CertificateType, } -impl State for ExpectCertificate { - fn handle<'m>( - mut self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - if !self.message_already_in_transcript { - self.transcript.add_message(&m); - } - let certp = require_handshake_msg_move!( - m, +impl ExpectCertificate { + fn handle_input(mut self, Input { message, .. }: Input<'_>) -> Result { + self.hs.transcript.add_message(&message); + self.handle_certificate(require_handshake_msg_move!( + message, HandshakeType::Certificate, HandshakePayload::CertificateTls13 - )?; + )?) + } + fn handle_certificate( + mut self, + certp: CertificatePayloadTls13<'_>, + ) -> Result { // We don't send any CertificateRequest extensions, so any extensions // here are illegal. - if certp.any_entry_has_extension() { + if certp + .entries + .iter() + .any(|e| !e.extensions.only_contains(&[])) + { return Err(PeerMisbehaved::UnsolicitedCertExtension.into()); } let client_cert = certp.into_certificate_chain(); let mandatory = self + .hs .config .verifier .client_auth_mandatory(); - let Some((end_entity, intermediates)) = client_cert.split_first() else { + let peer_identity = Identity::from_peer(client_cert.0, self.expected_certificate_type)?; + + let Some(peer_identity) = peer_identity else { if !mandatory { debug!("client auth requested but no certificate supplied"); - self.transcript.abandon_client_auth(); + self.hs.transcript.abandon_client_auth(); return Ok(Box::new(ExpectFinished { - config: self.config, - suite: self.suite, + hs: self.hs, key_schedule: self.key_schedule, - transcript: self.transcript, - send_tickets: self.send_tickets, - })); + peer_identity: None, + }) + .into()); } - return Err(cx.common.send_fatal_alert( - AlertDescription::CertificateRequired, - Error::NoCertificatesPresented, - )); + return Err(PeerMisbehaved::NoCertificatesPresented.into()); }; - let now = self.config.current_time()?; - - self.config + self.hs + .config .verifier - .verify_client_cert(end_entity, intermediates, now) - .map_err(|err| { - cx.common - .send_cert_verify_error_alert(err) + .verify_identity(&ClientIdentity { + identity: &peer_identity, + now: self.hs.config.current_time()?, })?; Ok(Box::new(ExpectCertificateVerify { - config: self.config, - suite: self.suite, - transcript: self.transcript, + hs: self.hs, key_schedule: self.key_schedule, - client_cert: client_cert.into_owned(), - send_tickets: self.send_tickets, - })) + peer_identity: peer_identity.into_owned(), + }) + .into()) + } +} + +impl ExpectCertificate { + fn handle( + self: Box, + input: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { + self.handle_input(input) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::Certificate(value)) } } struct ExpectCertificateVerify { - config: Arc, - transcript: HandshakeHash, - suite: &'static Tls13CipherSuite, + hs: HandshakeState, key_schedule: KeyScheduleTrafficWithClientFinishedPending, - client_cert: CertificateChain<'static>, - send_tickets: usize, + peer_identity: Identity<'static>, } -impl State for ExpectCertificateVerify { - fn handle<'m>( +impl ExpectCertificateVerify { + fn handle( mut self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - let rc = { - let sig = require_handshake_msg!( - m, - HandshakeType::CertificateVerify, - HandshakePayload::CertificateVerify - )?; - let handshake_hash = self.transcript.current_hash(); - self.transcript.abandon_client_auth(); - let certs = &self.client_cert; - let msg = construct_client_verify_message(&handshake_hash); - - self.config - .verifier - .verify_tls13_signature(msg.as_ref(), &certs[0], sig) - }; + Input { message, .. }: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { + let signature = require_handshake_msg!( + message, + HandshakeType::CertificateVerify, + HandshakePayload::CertificateVerify + )?; + let handshake_hash = self.hs.transcript.current_hash(); + self.hs.transcript.abandon_client_auth(); - if let Err(e) = rc { - return Err(cx - .common - .send_cert_verify_error_alert(e)); - } + self.hs + .config + .verifier + .verify_tls13_signature(&verify::SignatureVerificationInput { + message: construct_client_verify_message(&handshake_hash).as_ref(), + signer: &self.peer_identity.as_signer(), + signature, + })?; trace!("client CertificateVerify OK"); - cx.common.peer_certificates = Some(self.client_cert); - self.transcript.add_message(&m); + self.hs.transcript.add_message(&message); Ok(Box::new(ExpectFinished { - config: self.config, - suite: self.suite, + hs: self.hs, key_schedule: self.key_schedule, - transcript: self.transcript, - send_tickets: self.send_tickets, - })) + peer_identity: Some(self.peer_identity), + }) + .into()) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::CertificateVerify(value)) } } @@ -1187,54 +1136,47 @@ impl State for ExpectCertificateVerify { // followed by a terminating handshake EndOfEarlyData message --- struct ExpectEarlyData { - config: Arc, - transcript: HandshakeHash, - suite: &'static Tls13CipherSuite, + hs: HandshakeState, key_schedule: KeyScheduleTrafficWithClientFinishedPending, - send_tickets: usize, + peer_identity: Option>, + remaining_length: usize, } -impl State for ExpectEarlyData { - fn handle<'m>( +impl ExpectEarlyData { + fn handle( mut self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { + input: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { + match input.message.payload { MessagePayload::ApplicationData(payload) => { - match cx - .data - .early_data - .take_received_plaintext(payload) + self.remaining_length = match self + .remaining_length + .checked_sub(payload.bytes().len()) { - true => Ok(self), - false => Err(cx.common.send_fatal_alert( - AlertDescription::UnexpectedMessage, - PeerMisbehaved::TooMuchEarlyDataReceived, - )), - } + Some(sub) => sub, + None => return Err(PeerMisbehaved::TooMuchEarlyDataReceived.into()), + }; + + output.emit(Event::EarlyApplicationData(payload)); + Ok(self.into()) } MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - typ: HandshakeType::EndOfEarlyData, - payload: HandshakePayload::EndOfEarlyData, - }, + parsed: HandshakeMessagePayload(HandshakePayload::EndOfEarlyData), .. } => { + let proof = input.check_aligned_handshake()?; self.key_schedule - .update_decrypter(cx.common); - self.transcript.add_message(&m); + .update_decrypter(output.receive(), &proof); + self.hs + .transcript + .add_message(&input.message); Ok(Box::new(ExpectFinished { - config: self.config, - suite: self.suite, + hs: self.hs, key_schedule: self.key_schedule, - transcript: self.transcript, - send_tickets: self.send_tickets, - })) + peer_identity: self.peer_identity, + }) + .into()) } payload => Err(inappropriate_handshake_message( &payload, @@ -1243,91 +1185,197 @@ impl State for ExpectEarlyData { )), } } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::EarlyData(value)) } } -// --- Process client's Finished --- -fn get_server_session_value( - suite: &'static Tls13CipherSuite, - secret: &ResumptionSecret<'_>, - cx: &ServerContext<'_>, - nonce: &[u8], - time_now: UnixTime, +#[derive(Debug)] +pub(crate) struct Tls13ServerSessionValue<'a> { + common: CommonServerSessionValue<'a>, + secret: ZeroizingCow<'a>, age_obfuscation_offset: u32, -) -> persist::ServerSessionValue { - let version = ProtocolVersion::TLSv1_3; - - let secret = secret.derive_ticket_psk(nonce); - - persist::ServerSessionValue::new( - cx.data.sni.as_ref(), - version, - suite.common.suite, - secret.as_ref(), - cx.common.peer_certificates.clone(), - cx.common.alpn_protocol.clone(), - cx.data.resumption_data.clone(), - time_now, - age_obfuscation_offset, - ) + + // not encoded vv + freshness: Option, +} + +impl<'a> Tls13ServerSessionValue<'a> { + fn from_ticket( + id: &PresharedKeyIdentity, + config: &ServerConfig, + ) -> Option> { + let plain = match config.ticketer.as_deref() { + Some(ticketer) => ticketer.decrypt(id.identity.bytes())?, + None => config + .session_storage + .take(ServerSessionKey::new(id.identity.bytes()))?, + }; + + let Ok(ServerSessionValue::Tls13(tls13)) = ServerSessionValue::read_bytes(&plain) else { + return None; + }; + + Some(tls13.into_owned()) + } + + pub(super) fn new( + common: CommonServerSessionValue<'a>, + secret: &'a [u8], + age_obfuscation_offset: u32, + ) -> Self { + Self { + common, + secret: ZeroizingCow::Borrowed(SizedPayload::from(Payload::Borrowed(secret))), + age_obfuscation_offset, + freshness: None, + } + } + + fn into_owned(self) -> Tls13ServerSessionValue<'static> { + Tls13ServerSessionValue { + common: self.common.into_owned(), + secret: ZeroizingCow::Owned(match self.secret { + ZeroizingCow::Borrowed(b) => Zeroizing::from(b.into_owned()), + ZeroizingCow::Owned(o) => o, + }), + age_obfuscation_offset: self.age_obfuscation_offset, + freshness: self.freshness, + } + } + + fn set_freshness(&mut self, obfuscated_client_age_ms: u32, time_now: UnixTime) { + let client_age_ms = obfuscated_client_age_ms.wrapping_sub(self.age_obfuscation_offset); + let server_age_ms = (time_now + .as_secs() + .saturating_sub(self.common.creation_time_sec) as u32) + .saturating_mul(1000); + + let age_difference = server_age_ms.abs_diff(client_age_ms); + self.freshness = Some(age_difference <= MAX_FRESHNESS_SKEW_MS); + } + + fn is_fresh(&self) -> bool { + self.freshness.unwrap_or_default() + } +} + +impl<'a> Codec<'a> for Tls13ServerSessionValue<'a> { + fn encode(&self, bytes: &mut Vec) { + self.common.encode(bytes); + self.secret.encode(bytes); + self.age_obfuscation_offset + .encode(bytes); + } + + fn read(r: &mut Reader<'a>) -> Result { + Ok(Self { + common: CommonServerSessionValue::read(r)?, + secret: ZeroizingCow::read(r)?, + age_obfuscation_offset: u32::read(r)?, + freshness: None, + }) + } +} + +impl<'a> From> for ServerSessionValue<'a> { + fn from(value: Tls13ServerSessionValue<'a>) -> Self { + Self::Tls13(value) + } +} + +#[derive(Debug)] +enum ZeroizingCow<'a> { + Borrowed(SizedPayload<'a, u8>), + Owned(Zeroizing>), +} + +impl<'a> ZeroizingCow<'a> { + fn bytes(&self) -> &[u8] { + match self { + ZeroizingCow::Borrowed(b) => b.bytes(), + ZeroizingCow::Owned(o) => o.bytes(), + } + } +} + +impl<'a> Codec<'a> for ZeroizingCow<'a> { + fn encode(&self, bytes: &mut Vec) { + match self { + ZeroizingCow::Borrowed(b) => b.encode(bytes), + ZeroizingCow::Owned(o) => o.encode(bytes), + } + } + + fn read(r: &mut Reader<'a>) -> Result { + Ok(ZeroizingCow::Borrowed(SizedPayload::read(r)?)) + } } struct ExpectFinished { - config: Arc, - transcript: HandshakeHash, - suite: &'static Tls13CipherSuite, + hs: HandshakeState, key_schedule: KeyScheduleTrafficWithClientFinishedPending, - send_tickets: usize, + peer_identity: Option>, } impl ExpectFinished { fn emit_ticket( flight: &mut HandshakeFlightTls13<'_>, suite: &'static Tls13CipherSuite, - cx: &ServerContext<'_>, - secret: &ResumptionSecret<'_>, + peer_identity: Option>, + chosen_alpn_protocol: Option>, + sni: Option>, + resumption_data: &[u8], + resumption: &KeyScheduleResumption, config: &ServerConfig, ) -> Result<(), Error> { let secure_random = config.provider.secure_random; - let nonce = rand::random_vec(secure_random, 32)?; + let nonce = rand::random_array(secure_random)?; let age_add = rand::random_u32(secure_random)?; let now = config.current_time()?; - - let plain = - get_server_session_value(suite, secret, cx, &nonce, now, age_add).get_encoding(); - - let stateless = config.ticketer.enabled(); - let (ticket, lifetime) = if stateless { - let Some(ticket) = config.ticketer.encrypt(&plain) else { + let secret = resumption.derive_ticket_psk(&nonce); + let plain = ServerSessionValue::from(Tls13ServerSessionValue::new( + CommonServerSessionValue::new( + sni.as_ref(), + suite.common.suite, + peer_identity, + chosen_alpn_protocol, + resumption_data.to_vec(), + now, + ), + secret.as_ref(), + age_add, + )) + .get_encoding(); + + let ticketer = config.ticketer.as_deref(); + let (ticket, lifetime) = if let Some(ticketer) = ticketer { + let Some(ticket) = ticketer.encrypt(&plain) else { return Ok(()); }; - (ticket, config.ticketer.lifetime()) + (ticket, ticketer.lifetime()) } else { - let id = rand::random_vec(secure_random, 32)?; + let id = rand::random_array::<32>(secure_random)?; let stored = config .session_storage - .put(id.clone(), plain); + .put(ServerSessionKey::new(&id), plain); if !stored { trace!("resumption not available; not issuing ticket"); return Ok(()); } - let stateful_lifetime = 24 * 60 * 60; // this is a bit of a punt - (id, stateful_lifetime) + let stateful_lifetime = Duration::from_secs(24 * 60 * 60); // this is a bit of a punt + (id.to_vec(), stateful_lifetime) }; let mut payload = NewSessionTicketPayloadTls13::new(lifetime, age_add, nonce, ticket); if config.max_early_data_size > 0 { - if !stateless { - payload - .exts - .push(NewSessionTicketExtension::EarlyData( - config.max_early_data_size, - )); + if ticketer.is_none() { + payload.extensions.max_early_data_size = Some(config.max_early_data_size); } else { // We implement RFC8446 section 8.1: by enforcing that 0-RTT is // only possible if using stateful resumption @@ -1335,134 +1383,163 @@ impl ExpectFinished { } } - let t = HandshakeMessagePayload { - typ: HandshakeType::NewSessionTicket, - payload: HandshakePayload::NewSessionTicketTls13(payload), - }; - trace!("sending new ticket {:?} (stateless: {})", t, stateless); + let t = HandshakeMessagePayload(HandshakePayload::NewSessionTicketTls13(payload)); + trace!( + "sending new ticket {t:?} (stateless: {})", + ticketer.is_some() + ); flight.add(t); Ok(()) } } -impl State for ExpectFinished { - fn handle<'m>( +impl ExpectFinished { + fn handle( mut self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - let finished = - require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?; - - let handshake_hash = self.transcript.current_hash(); - let (key_schedule_traffic, expect_verify_data) = self + input: Input<'_>, + output: &mut dyn Output<'_>, + ) -> Result { + let finished = require_handshake_msg!( + input.message, + HandshakeType::Finished, + HandshakePayload::Finished + )?; + + let handshake_hash = self.hs.transcript.current_hash(); + let proof = input.check_aligned_handshake()?; + let (key_schedule_before_finished, expect_verify_data) = self .key_schedule - .sign_client_finish(&handshake_hash, cx.common); + .sign_client_finish(&handshake_hash, output.receive(), &proof); let fin = match ConstantTimeEq::ct_eq(expect_verify_data.as_ref(), finished.bytes()).into() { true => verify::FinishedMessageVerified::assertion(), - false => { - return Err(cx - .common - .send_fatal_alert(AlertDescription::DecryptError, Error::DecryptError)); - } + false => return Err(PeerMisbehaved::IncorrectFinished.into()), }; // Note: future derivations include Client Finished, but not the // main application data keying. - self.transcript.add_message(&m); + self.hs + .transcript + .add_message(&input.message); - cx.common.check_aligned_handshake()?; + let (key_schedule_traffic, exporter, resumption) = + key_schedule_before_finished.into_traffic(self.hs.transcript.current_hash()); - let handshake_hash = self.transcript.current_hash(); - let resumption = ResumptionSecret::new(&key_schedule_traffic, &handshake_hash); - - let mut flight = HandshakeFlightTls13::new(&mut self.transcript); - for _ in 0..self.send_tickets { - Self::emit_ticket(&mut flight, self.suite, cx, &resumption, &self.config)?; + let mut flight = HandshakeFlightTls13::new(&mut self.hs.transcript); + for _ in 0..self.hs.send_tickets { + Self::emit_ticket( + &mut flight, + self.hs.suite, + self.peer_identity.clone(), + self.hs.alpn_protocol.clone(), + self.hs.sni.clone(), + &self.hs.resumption_data, + &resumption, + &self.hs.config, + )?; } - flight.finish(cx.common); + flight.finish(output); - // Application data may now flow, even if we have client auth enabled. - cx.common - .start_traffic(&mut cx.sendable_plaintext); + let (key_schedule_send, key_schedule_recv) = key_schedule_traffic.split(); - Ok(match cx.common.is_quic() { - true => Box::new(ExpectQuicTraffic { - key_schedule: key_schedule_traffic, - _fin_verified: fin, - }), + // Application data may now flow, even if we have client auth enabled. + if let Some(identity) = self.peer_identity { + output.output(OutputEvent::PeerIdentity(identity)); + } + output.output(OutputEvent::Exporter(Box::new(exporter))); + output + .send() + .update_key_schedule(Box::new(key_schedule_send)); + output.start_traffic(); + + Ok(match key_schedule_recv.protocol().is_quic() { + true => Box::new(ExpectQuicTraffic { _fin_verified: fin }).into(), false => Box::new(ExpectTraffic { - key_schedule: key_schedule_traffic, + config: self.hs.config, + counters: TrafficTemperCounters::default(), + key_schedule_recv, _fin_verified: fin, - }), + }) + .into(), }) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::Finished(value)) } } +struct HandshakeState { + config: Arc, + transcript: HandshakeHash, + suite: &'static Tls13CipherSuite, + alpn_protocol: Option>, + sni: Option>, + resumption_data: Vec, + send_tickets: usize, +} + // --- Process traffic --- -struct ExpectTraffic { - key_schedule: KeyScheduleTraffic, +pub(super) struct ExpectTraffic { + config: Arc, + key_schedule_recv: KeyScheduleTrafficReceive, + counters: TrafficTemperCounters, _fin_verified: verify::FinishedMessageVerified, } impl ExpectTraffic { fn handle_key_update( &mut self, - common: &mut CommonState, + input: Input<'_>, + output: &mut dyn Output<'_>, key_update_request: &KeyUpdateRequest, ) -> Result<(), Error> { - if let Protocol::Quic = common.protocol { - return Err(common.send_fatal_alert( - AlertDescription::UnexpectedMessage, - PeerMisbehaved::KeyUpdateReceivedInQuicConnection, - )); + if self + .key_schedule_recv + .protocol() + .is_quic() + { + return Err(PeerMisbehaved::KeyUpdateReceivedInQuicConnection.into()); } - common.check_aligned_handshake()?; + let proof = input.check_aligned_handshake()?; - if common.should_update_key(key_update_request)? { - self.key_schedule - .update_encrypter_and_notify(common); + match *key_update_request { + KeyUpdateRequest::UpdateNotRequested => {} + KeyUpdateRequest::UpdateRequested => output.send().ensure_key_update_queued(), + _ => return Err(InvalidMessage::InvalidKeyUpdate.into()), } // Update our read-side keys. - self.key_schedule - .update_decrypter(common); + self.key_schedule_recv + .update_decrypter(output.receive(), &proof); Ok(()) } } -impl State for ExpectTraffic { +impl ExpectTraffic { fn handle<'m>( mut self: Box, - cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { - match m.payload { - MessagePayload::ApplicationData(payload) => cx - .common - .take_received_plaintext(payload), + input: Input<'m>, + output: &mut dyn Output<'m>, + ) -> Result { + match input.message.payload { + MessagePayload::ApplicationData(payload) => { + self.counters.received_app_data(); + output.received_plaintext(payload); + } MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::KeyUpdate(key_update), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::KeyUpdate(key_update)), .. - } => self.handle_key_update(cx.common, &key_update)?, + } => { + self.counters + .received_handshake_message()?; + self.handle_key_update(input, output, &key_update)? + } payload => { return Err(inappropriate_handshake_message( &payload, @@ -1472,63 +1549,93 @@ impl State for ExpectTraffic { } } - Ok(self) + Ok(self.into()) } - fn export_keying_material( - &self, - output: &mut [u8], - label: &[u8], - context: Option<&[u8]>, - ) -> Result<(), Error> { - self.key_schedule - .export_keying_material(output, label, context) + pub(super) fn into_external_state( + self: Box, + send_keys: &Option>, + ) -> Result<(PartiallyExtractedSecrets, Box), Error> { + if !self.config.enable_secret_extraction { + return Err(ApiMisuse::SecretExtractionRequiresPriorOptIn.into()); + } + let Some(send_keys) = send_keys else { + return Err(Error::Unreachable( + "send_keys required for TLS1.3 into_external_state", + )); + }; + Ok(( + PartiallyExtractedSecrets { + tx: send_keys.extract()?, + rx: self.key_schedule_recv.extract()?, + }, + self, + )) } +} - fn extract_secrets(&self) -> Result { - self.key_schedule - .extract_secrets(Side::Server) +impl KernelState for ExpectTraffic { + fn update_rx_secret(&mut self) -> Result { + self.key_schedule_recv + .refresh_traffic_secret() } - fn send_key_update_request(&mut self, common: &mut CommonState) -> Result<(), Error> { - self.key_schedule - .request_key_update_and_update_encrypter(common) + fn handle_new_session_ticket( + &self, + _message: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + unreachable!( + "server connections should never have handle_new_session_ticket called on them" + ) } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::Traffic(value)) } } struct ExpectQuicTraffic { - key_schedule: KeyScheduleTraffic, _fin_verified: verify::FinishedMessageVerified, } -impl State for ExpectQuicTraffic { - fn handle<'m>( - self: Box, - _cx: &mut ServerContext<'_>, - m: Message<'m>, - ) -> hs::NextStateOrError<'m> - where - Self: 'm, - { +impl ExpectQuicTraffic { + fn handle( + self, + Input { message, .. }: Input<'_>, + _output: &mut dyn Output<'_>, + ) -> Result { // reject all messages - Err(inappropriate_message(&m.payload, &[])) + Err(inappropriate_message(&message.payload, &[])) + } +} + +impl KernelState for ExpectQuicTraffic { + #[cfg_attr(coverage_nightly, coverage(off))] + fn update_rx_secret(&mut self) -> Result { + Err(Error::Unreachable( + "QUIC connections do not support key updates", + )) } - fn export_keying_material( + #[cfg_attr(coverage_nightly, coverage(off))] + fn handle_new_session_ticket( &self, - output: &mut [u8], - label: &[u8], - context: Option<&[u8]>, + _message: &NewSessionTicketPayloadTls13, ) -> Result<(), Error> { - self.key_schedule - .export_keying_material(output, label, context) + unreachable!("handle_new_session_ticket should not be called for server-side connections") } +} - fn into_owned(self: Box) -> hs::NextState<'static> { - self +impl From> for ServerState { + fn from(value: Box) -> Self { + Self::Tls13(Tls13State::QuicTraffic(value)) } } + +/// This is the maximum allowed skew between server and client clocks, over +/// the maximum ticket lifetime period. This encompasses TCP retransmission +/// times in case packet loss occurs when the client sends the ClientHello +/// or receives the NewSessionTicket, _and_ actual clock skew over this period. +static MAX_FRESHNESS_SKEW_MS: u32 = 60 * 1000; diff --git a/rustls/src/suites.rs b/rustls/src/suites.rs index 9e6f1c782fd..8ee5b95f6df 100644 --- a/rustls/src/suites.rs +++ b/rustls/src/suites.rs @@ -1,24 +1,23 @@ use core::fmt; +use pki_types::FipsStatus; + use crate::common_state::Protocol; use crate::crypto::cipher::{AeadKey, Iv}; -use crate::crypto::{self, KeyExchangeAlgorithm}; -use crate::enums::{CipherSuite, SignatureAlgorithm, SignatureScheme}; -use crate::msgs::handshake::ALL_KEY_EXCHANGE_ALGORITHMS; -#[cfg(feature = "tls12")] +use crate::crypto::kx::KeyExchangeAlgorithm; +use crate::crypto::{CipherSuite, SignatureScheme, hash}; +use crate::enums::ProtocolVersion; use crate::tls12::Tls12CipherSuite; use crate::tls13::Tls13CipherSuite; -#[cfg(feature = "tls12")] -use crate::versions::TLS12; -use crate::versions::{SupportedProtocolVersion, TLS13}; /// Common state for cipher suites (both for TLS 1.2 and TLS 1.3) +#[expect(clippy::exhaustive_structs)] pub struct CipherSuiteCommon { /// The TLS enumeration naming this cipher suite. pub suite: CipherSuite, /// Which hash function the suite uses. - pub hash_provider: &'static dyn crypto::hash::Hash, + pub hash_provider: &'static dyn hash::Hash, /// Number of TCP-TLS messages that can be safely encrypted with a single key of this type /// @@ -28,7 +27,7 @@ pub struct CipherSuiteCommon { /// /// This is to be set on the assumption that messages are maximally sized -- /// each is 214 bytes. It **does not** consider confidentiality limits for - /// QUIC connections - see the [`quic::KeyBuilder.confidentiality_limit`] field for + /// QUIC connections - see the [`quic::PacketKey::confidentiality_limit`] field for /// this context. /// /// For AES-GCM implementations, this should be set to 224 to limit attack @@ -43,6 +42,7 @@ pub struct CipherSuiteCommon { /// ``` /// [AEBounds]: https://eprint.iacr.org/2024/051.pdf /// [draft-irtf-aead-limits-08]: https://www.ietf.org/archive/id/draft-irtf-cfrg-aead-limits-08.html#section-5.1.1 + /// [`quic::PacketKey::confidentiality_limit`]: crate::quic::PacketKey::confidentiality_limit /// /// For chacha20-poly1305 implementations, this should be set to `u64::MAX`: /// see @@ -53,7 +53,7 @@ impl CipherSuiteCommon { /// Return `true` if this is backed by a FIPS-approved implementation. /// /// This means all the constituent parts that do cryptography return `true` for `fips()`. - pub fn fips(&self) -> bool { + pub fn fips(&self) -> FipsStatus { self.hash_provider.fips() } } @@ -62,10 +62,10 @@ impl CipherSuiteCommon { /// /// This type carries both configuration and implementation. Compare with /// [`CipherSuite`], which carries solely a cipher suite identifier. +#[non_exhaustive] #[derive(Clone, Copy, PartialEq)] pub enum SupportedCipherSuite { /// A TLS 1.2 cipher suite - #[cfg(feature = "tls12")] Tls12(&'static Tls12CipherSuite), /// A TLS 1.3 cipher suite Tls13(&'static Tls13CipherSuite), @@ -78,112 +78,52 @@ impl SupportedCipherSuite { } /// The hash function the ciphersuite uses. - pub(crate) fn hash_provider(&self) -> &'static dyn crypto::hash::Hash { + pub(crate) fn hash_provider(&self) -> &'static dyn hash::Hash { self.common().hash_provider } pub(crate) fn common(&self) -> &CipherSuiteCommon { match self { - #[cfg(feature = "tls12")] Self::Tls12(inner) => &inner.common, Self::Tls13(inner) => &inner.common, } } - /// Return the inner `Tls13CipherSuite` for this suite, if it is a TLS1.3 suite. - pub fn tls13(&self) -> Option<&'static Tls13CipherSuite> { + /// Return true if this suite is usable for the given [`Protocol`]. + pub(crate) fn usable_for_protocol(&self, proto: Protocol) -> bool { match self { - #[cfg(feature = "tls12")] - Self::Tls12(_) => None, - Self::Tls13(inner) => Some(inner), + Self::Tls12(tls12) => tls12.usable_for_protocol(proto), + Self::Tls13(tls13) => tls13.usable_for_protocol(proto), } } +} - /// Return supported protocol version for the cipher suite. - pub fn version(&self) -> &'static SupportedProtocolVersion { - match self { - #[cfg(feature = "tls12")] - Self::Tls12(_) => &TLS12, - Self::Tls13(_) => &TLS13, - } +impl fmt::Debug for SupportedCipherSuite { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.suite().fmt(f) } +} - /// Return true if this suite is usable for a key only offering `sig_alg` - /// signatures. This resolves to true for all TLS1.3 suites. - pub fn usable_for_signature_algorithm(&self, _sig_alg: SignatureAlgorithm) -> bool { - match self { - Self::Tls13(_) => true, // no constraint expressed by ciphersuite (e.g., TLS1.3) - #[cfg(feature = "tls12")] - Self::Tls12(inner) => inner - .sign - .iter() - .any(|scheme| scheme.algorithm() == _sig_alg), - } - } +pub(crate) trait Suite: fmt::Debug { + fn client_handler(&self) -> &'static dyn crate::client::ClientHandler; - /// Return true if this suite is usable for the given [`Protocol`]. - /// - /// All cipher suites are usable for TCP-TLS. Only TLS1.3 suites - /// with `Tls13CipherSuite::quic` provided are usable for QUIC. - pub(crate) fn usable_for_protocol(&self, proto: Protocol) -> bool { - match proto { - Protocol::Tcp => true, - Protocol::Quic => self - .tls13() - .and_then(|cs| cs.quic) - .is_some(), - } - } + fn server_handler(&self) -> &'static dyn crate::server::ServerHandler; - /// Return `true` if this is backed by a FIPS-approved implementation. - pub fn fips(&self) -> bool { - match self { - #[cfg(feature = "tls12")] - Self::Tls12(cs) => cs.fips(), - Self::Tls13(cs) => cs.fips(), - } - } + fn usable_for_protocol(&self, proto: Protocol) -> bool; - /// Return the list of `KeyExchangeAlgorithm`s supported by this cipher suite. - /// - /// TLS 1.3 cipher suites support both ECDHE and DHE key exchange, but TLS 1.2 suites - /// support one or the other. - pub(crate) fn key_exchange_algorithms(&self) -> &[KeyExchangeAlgorithm] { - match self { - #[cfg(feature = "tls12")] - Self::Tls12(tls12) => core::slice::from_ref(&tls12.kx), - Self::Tls13(_) => ALL_KEY_EXCHANGE_ALGORITHMS, - } - } + fn usable_for_signature_scheme(&self, _scheme: SignatureScheme) -> bool; - /// Say if the given `KeyExchangeAlgorithm` is supported by this cipher suite. - /// - /// TLS 1.3 cipher suites support all key exchange types, but TLS 1.2 suites - /// support only one. - pub(crate) fn usable_for_kx_algorithm(&self, _kxa: KeyExchangeAlgorithm) -> bool { - match self { - #[cfg(feature = "tls12")] - Self::Tls12(tls12) => tls12.kx == _kxa, - Self::Tls13(_) => true, - } + fn usable_for_kx_algorithm(&self, _kxa: KeyExchangeAlgorithm) -> bool { + true } -} -impl fmt::Debug for SupportedCipherSuite { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.suite().fmt(f) + fn suite(&self) -> CipherSuite { + self.common().suite } -} -/// Return true if `sigscheme` is usable by any of the given suites. -pub(crate) fn compatible_sigscheme_for_suites( - sigscheme: SignatureScheme, - common_suites: &[SupportedCipherSuite], -) -> bool { - let sigalg = sigscheme.algorithm(); - common_suites - .iter() - .any(|&suite| suite.usable_for_signature_algorithm(sigalg)) + fn common(&self) -> &CipherSuiteCommon; + + const VERSION: ProtocolVersion; } /// Secrets for transmitting/receiving data over a TLS session. @@ -191,6 +131,7 @@ pub(crate) fn compatible_sigscheme_for_suites( /// After performing a handshake with rustls, these secrets can be extracted /// to configure kTLS for a socket, and have the kernel take over encryption /// and/or decryption. +#[expect(clippy::exhaustive_structs)] pub struct ExtractedSecrets { /// sequence number and secrets for the "tx" (transmit) direction pub tx: (u64, ConnectionTrafficSecrets), @@ -238,31 +179,41 @@ pub enum ConnectionTrafficSecrets { /// Initialization vector iv: Iv, }, + + /// Secrets for the SM4_GCM AEAD algorithm + Sm4Gcm { + /// AEAD Key + key: AeadKey, + /// Initialization vector + iv: Iv, + }, + + /// Secrets for the SM4_CCM AEAD algorithm + Sm4Ccm { + /// AEAD Key + key: AeadKey, + /// Initialization vector + iv: Iv, + }, } #[cfg(test)] -#[macro_rules_attribute::apply(test_for_each_provider)] mod tests { use std::println; - use super::provider::tls13::*; + use super::SupportedCipherSuite; + use crate::crypto::TEST_PROVIDER; #[test] fn test_scs_is_debug() { - println!("{:?}", super::provider::ALL_CIPHER_SUITES); - } - - #[test] - fn test_can_resume_to() { - assert!(TLS13_AES_128_GCM_SHA256 - .tls13() - .unwrap() - .can_resume_from(TLS13_CHACHA20_POLY1305_SHA256_INTERNAL) - .is_some()); - assert!(TLS13_AES_256_GCM_SHA384 - .tls13() - .unwrap() - .can_resume_from(TLS13_CHACHA20_POLY1305_SHA256_INTERNAL) - .is_none()); + println!( + "{:?}", + SupportedCipherSuite::Tls13( + TEST_PROVIDER + .tls13_cipher_suites + .first() + .unwrap() + ) + ); } } diff --git a/rustls/src/test_macros.rs b/rustls/src/test_macros.rs deleted file mode 100644 index 237578e008c..00000000000 --- a/rustls/src/test_macros.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! Macros used for unit testing. - -/// Instantiate the given test functions once for each built-in provider. -/// -/// The selected provider module is bound as `provider`; you can rely on this -/// having the union of the items common to the `crypto::ring` and -/// `crypto::aws_lc_rs` modules. -#[cfg(test)] -macro_rules! test_for_each_provider { - ($($tt:tt)+) => { - #[cfg(feature = "ring")] - mod test_with_ring { - use crate::crypto::ring as provider; - #[allow(unused_imports)] - use super::*; - $($tt)+ - } - - #[cfg(feature = "aws_lc_rs")] - mod test_with_aws_lc_rs { - use crate::crypto::aws_lc_rs as provider; - #[allow(unused_imports)] - use super::*; - $($tt)+ - } - }; -} - -/// Instantiate the given benchmark functions once for each built-in provider. -/// -/// The selected provider module is bound as `provider`; you can rely on this -/// having the union of the items common to the `crypto::ring` and -/// `crypto::aws_lc_rs` modules. -#[cfg(bench)] -macro_rules! bench_for_each_provider { - ($($tt:tt)+) => { - #[cfg(feature = "ring")] - mod bench_with_ring { - use crate::crypto::ring as provider; - #[allow(unused_imports)] - use super::*; - $($tt)+ - } - - #[cfg(feature = "aws_lc_rs")] - mod bench_with_aws_lc_rs { - use crate::crypto::aws_lc_rs as provider; - #[allow(unused_imports)] - use super::*; - $($tt)+ - } - }; -} - -#[cfg(test)] -#[macro_rules_attribute::apply(test_for_each_provider)] -mod tests { - #[test] - fn test_each_provider() { - std::println!("provider is {:?}", super::provider::default_provider()); - } -} - -#[cfg(all(test, bench))] -#[macro_rules_attribute::apply(bench_for_each_provider)] -mod benchmarks { - #[bench] - fn bench_each_provider(b: &mut test::Bencher) { - b.iter(|| super::provider::default_provider()); - } -} diff --git a/rustls/src/testdata/cert-arstechnica.0.der b/rustls/src/testdata/cert-arstechnica.0.der deleted file mode 100644 index ac81cd85ec8..00000000000 Binary files a/rustls/src/testdata/cert-arstechnica.0.der and /dev/null differ diff --git a/rustls/src/testdata/cert-arstechnica.1.der b/rustls/src/testdata/cert-arstechnica.1.der deleted file mode 100644 index 93f1fb0c6b5..00000000000 Binary files a/rustls/src/testdata/cert-arstechnica.1.der and /dev/null differ diff --git a/rustls/src/testdata/cert-arstechnica.3.der b/rustls/src/testdata/cert-arstechnica.3.der deleted file mode 100644 index 75df0cc7c0b..00000000000 Binary files a/rustls/src/testdata/cert-arstechnica.3.der and /dev/null differ diff --git a/rustls/src/testdata/cert-duckduckgo.0.der b/rustls/src/testdata/cert-duckduckgo.0.der deleted file mode 100644 index 9f8267ca925..00000000000 Binary files a/rustls/src/testdata/cert-duckduckgo.0.der and /dev/null differ diff --git a/rustls/src/testdata/cert-duckduckgo.1.der b/rustls/src/testdata/cert-duckduckgo.1.der deleted file mode 100644 index dd6a50f3273..00000000000 Binary files a/rustls/src/testdata/cert-duckduckgo.1.der and /dev/null differ diff --git a/rustls/src/testdata/cert-github.0.der b/rustls/src/testdata/cert-github.0.der deleted file mode 100644 index 86d6fce22f2..00000000000 Binary files a/rustls/src/testdata/cert-github.0.der and /dev/null differ diff --git a/rustls/src/testdata/cert-github.1.der b/rustls/src/testdata/cert-github.1.der deleted file mode 100644 index 78a66bb47b4..00000000000 Binary files a/rustls/src/testdata/cert-github.1.der and /dev/null differ diff --git a/rustls/src/testdata/cert-google.0.der b/rustls/src/testdata/cert-google.0.der deleted file mode 100644 index e8c41b21f57..00000000000 Binary files a/rustls/src/testdata/cert-google.0.der and /dev/null differ diff --git a/rustls/src/testdata/cert-google.1.der b/rustls/src/testdata/cert-google.1.der deleted file mode 100644 index 66715042432..00000000000 Binary files a/rustls/src/testdata/cert-google.1.der and /dev/null differ diff --git a/rustls/src/testdata/cert-hn.0.der b/rustls/src/testdata/cert-hn.0.der deleted file mode 100644 index bc42b61abcb..00000000000 Binary files a/rustls/src/testdata/cert-hn.0.der and /dev/null differ diff --git a/rustls/src/testdata/cert-hn.1.der b/rustls/src/testdata/cert-hn.1.der deleted file mode 100644 index dd6a50f3273..00000000000 Binary files a/rustls/src/testdata/cert-hn.1.der and /dev/null differ diff --git a/rustls/src/testdata/cert-reddit.0.der b/rustls/src/testdata/cert-reddit.0.der deleted file mode 100644 index 3a26d368cee..00000000000 Binary files a/rustls/src/testdata/cert-reddit.0.der and /dev/null differ diff --git a/rustls/src/testdata/cert-reddit.1.der b/rustls/src/testdata/cert-reddit.1.der deleted file mode 100644 index dd6a50f3273..00000000000 Binary files a/rustls/src/testdata/cert-reddit.1.der and /dev/null differ diff --git a/rustls/src/testdata/cert-rustlang.0.der b/rustls/src/testdata/cert-rustlang.0.der deleted file mode 100644 index 3af1cadfd8f..00000000000 Binary files a/rustls/src/testdata/cert-rustlang.0.der and /dev/null differ diff --git a/rustls/src/testdata/cert-rustlang.1.der b/rustls/src/testdata/cert-rustlang.1.der deleted file mode 100644 index 93f1fb0c6b5..00000000000 Binary files a/rustls/src/testdata/cert-rustlang.1.der and /dev/null differ diff --git a/rustls/src/testdata/cert-rustlang.2.der b/rustls/src/testdata/cert-rustlang.2.der deleted file mode 100644 index 1dfb0e70faa..00000000000 Binary files a/rustls/src/testdata/cert-rustlang.2.der and /dev/null differ diff --git a/rustls/src/testdata/cert-rustlang.3.der b/rustls/src/testdata/cert-rustlang.3.der deleted file mode 100644 index 75df0cc7c0b..00000000000 Binary files a/rustls/src/testdata/cert-rustlang.3.der and /dev/null differ diff --git a/rustls/src/testdata/cert-servo.0.der b/rustls/src/testdata/cert-servo.0.der deleted file mode 100644 index 0b6271ff545..00000000000 Binary files a/rustls/src/testdata/cert-servo.0.der and /dev/null differ diff --git a/rustls/src/testdata/cert-servo.1.der b/rustls/src/testdata/cert-servo.1.der deleted file mode 100644 index 41c742136ce..00000000000 Binary files a/rustls/src/testdata/cert-servo.1.der and /dev/null differ diff --git a/rustls/src/testdata/cert-stackoverflow.0.der b/rustls/src/testdata/cert-stackoverflow.0.der deleted file mode 100644 index 68068a77c5b..00000000000 Binary files a/rustls/src/testdata/cert-stackoverflow.0.der and /dev/null differ diff --git a/rustls/src/testdata/cert-stackoverflow.1.der b/rustls/src/testdata/cert-stackoverflow.1.der deleted file mode 100644 index 2d66ea723ea..00000000000 Binary files a/rustls/src/testdata/cert-stackoverflow.1.der and /dev/null differ diff --git a/rustls/src/testdata/cert-stackoverflow.2.der b/rustls/src/testdata/cert-stackoverflow.2.der deleted file mode 100644 index 79a33ba5908..00000000000 Binary files a/rustls/src/testdata/cert-stackoverflow.2.der and /dev/null differ diff --git a/rustls/src/testdata/cert-twitter.0.der b/rustls/src/testdata/cert-twitter.0.der deleted file mode 100644 index 36f4e06d476..00000000000 Binary files a/rustls/src/testdata/cert-twitter.0.der and /dev/null differ diff --git a/rustls/src/testdata/cert-twitter.1.der b/rustls/src/testdata/cert-twitter.1.der deleted file mode 100644 index 608f16c7921..00000000000 Binary files a/rustls/src/testdata/cert-twitter.1.der and /dev/null differ diff --git a/rustls/src/testdata/cert-wapo.0.der b/rustls/src/testdata/cert-wapo.0.der deleted file mode 100644 index 94d2cd97be2..00000000000 Binary files a/rustls/src/testdata/cert-wapo.0.der and /dev/null differ diff --git a/rustls/src/testdata/cert-wapo.1.der b/rustls/src/testdata/cert-wapo.1.der deleted file mode 100644 index 99ced211ba8..00000000000 Binary files a/rustls/src/testdata/cert-wapo.1.der and /dev/null differ diff --git a/rustls/src/testdata/cert-wikipedia.0.der b/rustls/src/testdata/cert-wikipedia.0.der deleted file mode 100644 index 5452038166d..00000000000 Binary files a/rustls/src/testdata/cert-wikipedia.0.der and /dev/null differ diff --git a/rustls/src/testdata/cert-wikipedia.1.der b/rustls/src/testdata/cert-wikipedia.1.der deleted file mode 100644 index 7d8413a3474..00000000000 Binary files a/rustls/src/testdata/cert-wikipedia.1.der and /dev/null differ diff --git a/rustls/src/ticketer.rs b/rustls/src/ticketer.rs index 1a9ca2c72ca..aeb5f845f7a 100644 --- a/rustls/src/ticketer.rs +++ b/rustls/src/ticketer.rs @@ -1,276 +1,95 @@ use alloc::boxed::Box; use alloc::vec::Vec; use core::mem; -#[cfg(feature = "std")] +use core::time::Duration; use std::sync::{RwLock, RwLockReadGuard}; +use std::time::Instant; -use pki_types::UnixTime; - -use crate::lock::{Mutex, MutexGuard}; -use crate::server::ProducesTickets; -#[cfg(not(feature = "std"))] -use crate::time_provider::TimeProvider; -use crate::{rand, Error}; - -#[derive(Debug)] -pub(crate) struct TicketSwitcherState { - next: Option>, - current: Box, - previous: Option>, - next_switch_time: u64, -} +use crate::crypto::TicketProducer; +use crate::error::Error; /// A ticketer that has a 'current' sub-ticketer and a single /// 'previous' ticketer. It creates a new ticketer every so /// often, demoting the current ticketer. -#[cfg_attr(feature = "std", derive(Debug))] -pub struct TicketSwitcher { - pub(crate) generator: fn() -> Result, rand::GetRandomFailed>, - lifetime: u32, - state: Mutex, - #[cfg(not(feature = "std"))] - time_provider: &'static dyn TimeProvider, +pub struct TicketRotator { + pub(crate) generator: fn() -> Result, Error>, + lifetime: Duration, + state: RwLock, } -impl TicketSwitcher { - /// Creates a new `TicketSwitcher`, which rotates through sub-ticketers +impl TicketRotator { + /// Creates a new `TicketRotator`, which rotates through sub-ticketers /// based on the passage of time. /// /// `lifetime` is in seconds, and is how long the current ticketer /// is used to generate new tickets. Tickets are accepted for no - /// longer than twice this duration. `generator` produces a new - /// `ProducesTickets` implementation. - #[cfg(feature = "std")] - #[deprecated(note = "use TicketRotator instead")] - pub fn new( - lifetime: u32, - generator: fn() -> Result, rand::GetRandomFailed>, - ) -> Result { - Ok(Self { - generator, - lifetime, - state: Mutex::new(TicketSwitcherState { - next: Some(generator()?), - current: generator()?, - previous: None, - next_switch_time: UnixTime::now() - .as_secs() - .saturating_add(u64::from(lifetime)), - }), - }) - } - - /// Creates a new `TicketSwitcher`, which rotates through sub-ticketers - /// based on the passage of time. + /// longer than twice this duration. This means a given ticket will + /// be usable for at least one `lifetime`, and at most two `lifetime`s + /// (depending on when its creation falls in the replacement cycle.) /// - /// `lifetime` is in seconds, and is how long the current ticketer - /// is used to generate new tickets. Tickets are accepted for no - /// longer than twice this duration. `generator` produces a new - /// `ProducesTickets` implementation. - #[cfg(not(feature = "std"))] - pub fn new( - lifetime: u32, - generator: fn() -> Result, rand::GetRandomFailed>, - time_provider: &'static dyn TimeProvider, + /// `generator` produces a new [`TicketProducer`] implementation. + pub fn new( + lifetime: Duration, + generator: fn() -> Result, Error>, ) -> Result { Ok(Self { generator, lifetime, - state: Mutex::new::(TicketSwitcherState { - next: Some(generator()?), - current: generator()?, + state: RwLock::new(TicketRotatorState { + current: Some(Generation { + producer: generator()?, + expires_at: Instant::now() + lifetime, + }), previous: None, - next_switch_time: time_provider - .current_time() - .unwrap() - .as_secs() - .saturating_add(u64::from(lifetime)), }), - time_provider, }) } - /// If it's time, demote the `current` ticketer to `previous` (so it - /// does no new encryptions but can do decryption) and use next for a - /// new `current` ticketer. - /// - /// Calling this regularly will ensure timely key erasure. Otherwise, - /// key erasure will be delayed until the next encrypt/decrypt call. - /// - /// For efficiency, this is also responsible for locking the state mutex - /// and returning the mutexguard. - pub(crate) fn maybe_roll(&self, now: UnixTime) -> Option> { - // The code below aims to make switching as efficient as possible - // in the common case that the generator never fails. To achieve this - // we run the following steps: - // 1. If no switch is necessary, just return the mutexguard - // 2. Shift over all of the ticketers (so current becomes previous, - // and next becomes current). After this, other threads can - // start using the new current ticketer. - // 3. unlock mutex and generate new ticketer. - // 4. Place new ticketer in next and return current - // - // There are a few things to note here. First, we don't check whether - // a new switch might be needed in step 4, even though, due to locking - // and entropy collection, significant amounts of time may have passed. - // This is to guarantee that the thread doing the switch will eventually - // make progress. - // - // Second, because next may be None, step 2 can fail. In that case - // we enter a recovery mode where we generate 2 new ticketers, one for - // next and one for the current ticketer. We then take the mutex a - // second time and redo the time check to see if a switch is still - // necessary. - // - // This somewhat convoluted approach ensures good availability of the - // mutex, by ensuring that the state is usable and the mutex not held - // during generation. It also ensures that, so long as the inner - // ticketer never generates panics during encryption/decryption, - // we are guaranteed to never panic when holding the mutex. - - let now = now.as_secs(); - let mut are_recovering = false; // Are we recovering from previous failure? - { - // Scope the mutex so we only take it for as long as needed - let mut state = self.state.lock()?; - - // Fast path in case we do not need to switch to the next ticketer yet - if now <= state.next_switch_time { - return Some(state); - } - - // Make the switch, or mark for recovery if not possible - if let Some(next) = state.next.take() { - state.previous = Some(mem::replace(&mut state.current, next)); - state.next_switch_time = now.saturating_add(u64::from(self.lifetime)); - } else { - are_recovering = true; - } - } + fn encrypt_at(&self, message: &[u8], now: Instant) -> Option> { + let state = self.maybe_roll(now)?; - // We always need a next, so generate it now - let next = (self.generator)().ok()?; - if !are_recovering { - // Normal path, generate new next and place it in the state - let mut state = self.state.lock()?; - state.next = Some(next); - Some(state) - } else { - // Recovering, generate also a new current ticketer, and modify state - // as needed. (we need to redo the time check, otherwise this might - // result in very rapid switching of ticketers) - let new_current = (self.generator)().ok()?; - let mut state = self.state.lock()?; - state.next = Some(next); - if now > state.next_switch_time { - state.previous = Some(mem::replace(&mut state.current, new_current)); - state.next_switch_time = now.saturating_add(u64::from(self.lifetime)); - } - Some(state) + // If we have a current ticketer, use it. We don't need to check its + // expiration time; if it would have expired, we would have rolled above. + if let Some(current) = &state.current { + return current.producer.encrypt(message); } - } -} -impl ProducesTickets for TicketSwitcher { - fn lifetime(&self) -> u32 { - self.lifetime * 2 - } + // If we don't have a previous ticketer, we can't encrypt. + let Some(prev) = &state.previous else { + return None; + }; - fn enabled(&self) -> bool { - true - } + // If the previous ticketer is more than one `lifetime` old, decline to encrypt. + if !prev.in_grace_period(now, self.lifetime) { + return None; + } - fn encrypt(&self, message: &[u8]) -> Option> { - #[cfg(feature = "std")] - let now = UnixTime::now(); - #[cfg(not(feature = "std"))] - let now = self - .time_provider - .current_time() - .unwrap(); - - self.maybe_roll(now)? - .current - .encrypt(message) + prev.producer.encrypt(message) } - fn decrypt(&self, ciphertext: &[u8]) -> Option> { - #[cfg(feature = "std")] - let now = UnixTime::now(); - #[cfg(not(feature = "std"))] - let now = self - .time_provider - .current_time() - .unwrap(); - + fn decrypt_at(&self, ciphertext: &[u8], now: Instant) -> Option> { let state = self.maybe_roll(now)?; - // Decrypt with the current key; if that fails, try with the previous. - state - .current - .decrypt(ciphertext) - .or_else(|| { - state - .previous - .as_ref() - .and_then(|previous| previous.decrypt(ciphertext)) - }) - } -} - -#[cfg(not(feature = "std"))] -impl core::fmt::Debug for TicketSwitcher { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("TicketSwitcher") - .field("generator", &self.generator) - .field("lifetime", &self.lifetime) - .field("state", &**self.state.lock().unwrap()) - .finish() - } -} + // If we have a current ticketer, use it. We don't need to check its + // expiration time; if it would have expired, we would have rolled above. + if let Some(current) = &state.current { + // If decryption fails, we're going to try the previous ticketer below. + if let Some(plain) = current.producer.decrypt(ciphertext) { + return Some(plain); + } + } -#[cfg(feature = "std")] -#[derive(Debug)] -pub(crate) struct TicketRotatorState { - current: Box, - previous: Option>, - next_switch_time: u64, -} + // If we don't have a previous ticketer, we can't decrypt. + let Some(prev) = &state.previous else { + return None; + }; -/// A ticketer that has a 'current' sub-ticketer and a single -/// 'previous' ticketer. It creates a new ticketer every so -/// often, demoting the current ticketer. -#[cfg(feature = "std")] -pub struct TicketRotator { - pub(crate) generator: fn() -> Result, rand::GetRandomFailed>, - lifetime: u32, - state: RwLock, -} + // If the previous ticketer is more than one `lifetime` old, decline to decrypt. + if !prev.in_grace_period(now, self.lifetime) { + return None; + } -#[cfg(feature = "std")] -impl TicketRotator { - /// Creates a new `TicketRotator`, which rotates through sub-ticketers - /// based on the passage of time. - /// - /// `lifetime` is in seconds, and is how long the current ticketer - /// is used to generate new tickets. Tickets are accepted for no - /// longer than twice this duration. `generator` produces a new - /// `ProducesTickets` implementation. - pub fn new( - lifetime: u32, - generator: fn() -> Result, rand::GetRandomFailed>, - ) -> Result { - Ok(Self { - generator, - lifetime, - state: RwLock::new(TicketRotatorState { - current: generator()?, - previous: None, - next_switch_time: UnixTime::now() - .as_secs() - .saturating_add(u64::from(lifetime)), - }), - }) + prev.producer.decrypt(ciphertext) } /// If it's time, demote the `current` ticketer to `previous` (so it @@ -284,81 +103,218 @@ impl TicketRotator { /// and returning it for read. pub(crate) fn maybe_roll( &self, - now: UnixTime, + now: Instant, ) -> Option> { - let now = now.as_secs(); - // Fast, common, & read-only path in case we do not need to switch // to the next ticketer yet { let read = self.state.read().ok()?; + match &read.current { + Some(current) if now <= current.expires_at => return Some(read), + _ => {} + } + } - if now <= read.next_switch_time { - return Some(read); + let mut write = self.state.write().ok()?; + if let Some(current) = &write.current { + if now <= current.expires_at { + // Another thread beat us to it. Nothing to do. + drop(write); + return self.state.read().ok(); } } // We need to switch ticketers, and make a new one. // Generate a potential "next" ticketer outside the lock. - let next = (self.generator)().ok()?; - - let mut write = self.state.write().ok()?; - - if now <= write.next_switch_time { - // Another thread beat us to it. Nothing to do. - drop(write); - - return self.state.read().ok(); - } + let next = (self.generator)() + .ok() + .map(|producer| Generation { + producer, + expires_at: now + self.lifetime, + }); // Now we have: // - confirmed we need rotation // - confirmed we are the thread that will do it // - successfully made the replacement ticketer - write.previous = Some(mem::replace(&mut write.current, next)); - write.next_switch_time = now.saturating_add(u64::from(self.lifetime)); + let prev = mem::replace(&mut write.current, next); + if prev.is_some() { + write.previous = prev; + } drop(write); self.state.read().ok() } } -#[cfg(feature = "std")] -impl ProducesTickets for TicketRotator { - fn lifetime(&self) -> u32 { - self.lifetime * 2 - } - - fn enabled(&self) -> bool { - true - } - +impl TicketProducer for TicketRotator { fn encrypt(&self, message: &[u8]) -> Option> { - self.maybe_roll(UnixTime::now())? - .current - .encrypt(message) + self.encrypt_at(message, Instant::now()) } fn decrypt(&self, ciphertext: &[u8]) -> Option> { - let state = self.maybe_roll(UnixTime::now())?; + self.decrypt_at(ciphertext, Instant::now()) + } - // Decrypt with the current key; if that fails, try with the previous. - state - .current - .decrypt(ciphertext) - .or_else(|| { - state - .previous - .as_ref() - .and_then(|previous| previous.decrypt(ciphertext)) - }) + fn lifetime(&self) -> Duration { + self.lifetime } } -#[cfg(feature = "std")] impl core::fmt::Debug for TicketRotator { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("TicketRotator") .finish_non_exhaustive() } } + +#[derive(Debug)] +pub(crate) struct TicketRotatorState { + current: Option, + previous: Option, +} + +#[derive(Debug)] +struct Generation { + producer: Box, + expires_at: Instant, +} + +impl Generation { + fn in_grace_period(&self, now: Instant, lifetime: Duration) -> bool { + now <= self.expires_at + lifetime + } +} + +#[cfg(test)] +mod tests { + use core::sync::atomic::{AtomicU8, Ordering}; + use core::time::Duration; + + use super::*; + + #[test] + fn ticketrotator_switching_test() { + let t = TicketRotator::new(Duration::from_secs(1), FakeTicketer::new).unwrap(); + let now = Instant::now(); + let cipher1 = t.encrypt(b"ticket 1").unwrap(); + assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); + { + // Trigger new ticketer + t.maybe_roll(now + Duration::from_secs(10)); + } + let cipher2 = t.encrypt(b"ticket 2").unwrap(); + assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); + assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); + { + // Trigger new ticketer + t.maybe_roll(now + Duration::from_secs(20)); + } + let cipher3 = t.encrypt(b"ticket 3").unwrap(); + assert!(t.decrypt(&cipher1).is_none()); + assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); + assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3"); + } + + #[test] + fn ticketrotator_remains_usable_over_temporary_ticketer_creation_failure() { + let mut t = TicketRotator::new(Duration::from_secs(1), FakeTicketer::new).unwrap(); + let expiry = t + .state + .read() + .unwrap() + .current + .as_ref() + .unwrap() + .expires_at; + let cipher1 = t.encrypt(b"ticket 1").unwrap(); + assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); + t.generator = fail_generator; + + // Failed new ticketer; this means we still need to rotate. + let t1 = expiry; + drop(t.maybe_roll(t1)); + assert!(t.encrypt_at(b"ticket 2", t1).is_some()); + + // check post-failure encryption/decryption still works + let t2 = expiry + Duration::from_secs(1); + let cipher3 = t.encrypt_at(b"ticket 3", t2).unwrap(); + assert_eq!(t.decrypt_at(&cipher1, t2).unwrap(), b"ticket 1"); + assert_eq!(t.decrypt_at(&cipher3, t2).unwrap(), b"ticket 3"); + + let t3 = expiry + Duration::from_secs(2); + assert_eq!(t.encrypt_at(b"ticket 4", t3), None); + assert_eq!(t.decrypt_at(&cipher3, t3), None); + + // do the rotation for real + t.generator = FakeTicketer::new; + let t4 = expiry + Duration::from_secs(3); + drop(t.maybe_roll(t4)); + + let t5 = expiry + Duration::from_secs(4); + let cipher5 = t.encrypt_at(b"ticket 5", t5).unwrap(); + assert!(t.decrypt_at(&cipher1, t5).is_none()); + assert!(t.decrypt_at(&cipher3, t5).is_none()); + assert_eq!(t.decrypt_at(&cipher5, t5).unwrap(), b"ticket 5"); + + // Cover the case where both ticketers are unavailable + t.generator = fail_generator; + let mut write = t.state.write().unwrap(); + write.current = None; + write.previous = None; + drop(write); + assert!(t.encrypt(b"ticket 6").is_none()); + } + + #[derive(Debug)] + struct FakeTicketer { + gen: u8, + } + + impl FakeTicketer { + #[expect(clippy::new_ret_no_self)] + fn new() -> Result, Error> { + Ok(Box::new(Self { + gen: std::dbg!(FAKE_GEN.fetch_add(1, Ordering::SeqCst)), + })) + } + } + + impl TicketProducer for FakeTicketer { + fn encrypt(&self, message: &[u8]) -> Option> { + let mut v = Vec::with_capacity(1 + message.len()); + v.push(self.gen); + v.extend( + message + .iter() + .copied() + .map(|b| b ^ self.gen), + ); + Some(v) + } + + fn decrypt(&self, ciphertext: &[u8]) -> Option> { + if ciphertext.first()? != &self.gen { + return None; + } + + Some( + ciphertext[1..] + .iter() + .copied() + .map(|b| b ^ self.gen) + .collect(), + ) + } + + fn lifetime(&self) -> Duration { + Duration::ZERO // Left to the rotator + } + } + + static FAKE_GEN: AtomicU8 = AtomicU8::new(0); + + fn fail_generator() -> Result, Error> { + Err(Error::FailedToGetRandomBytes) + } +} diff --git a/rustls/src/time_provider.rs b/rustls/src/time_provider.rs index e43f4d6ddff..efb22c54d35 100644 --- a/rustls/src/time_provider.rs +++ b/rustls/src/time_provider.rs @@ -17,12 +17,11 @@ pub trait TimeProvider: Debug + Send + Sync { fn current_time(&self) -> Option; } -#[derive(Debug)] -#[cfg(feature = "std")] /// Default `TimeProvider` implementation that uses `std` +#[expect(clippy::exhaustive_structs)] +#[derive(Debug)] pub struct DefaultTimeProvider; -#[cfg(feature = "std")] impl TimeProvider for DefaultTimeProvider { fn current_time(&self) -> Option { Some(UnixTime::now()) diff --git a/rustls/src/tls12/mod.rs b/rustls/src/tls12.rs similarity index 58% rename from rustls/src/tls12/mod.rs rename to rustls/src/tls12.rs index f0b179e715e..abcf00e45c8 100644 --- a/rustls/src/tls12/mod.rs +++ b/rustls/src/tls12.rs @@ -3,24 +3,39 @@ use alloc::vec; use alloc::vec::Vec; use core::fmt; -use zeroize::Zeroize; +use pki_types::FipsStatus; +use zeroize::Zeroizing; -use crate::common_state::{CommonState, Side}; -use crate::conn::ConnectionRandoms; -use crate::crypto; +use crate::common_state::{Protocol, Side}; +use crate::conn::{ConnectionRandoms, Exporter}; use crate::crypto::cipher::{AeadKey, MessageDecrypter, MessageEncrypter, Tls12AeadAlgorithm}; -use crate::crypto::hash; -use crate::enums::{AlertDescription, SignatureScheme}; -use crate::error::{Error, InvalidMessage}; -use crate::msgs::codec::{Codec, Reader}; -use crate::msgs::handshake::{KeyExchangeAlgorithm, KxDecode}; -use crate::suites::{CipherSuiteCommon, PartiallyExtractedSecrets, SupportedCipherSuite}; +use crate::crypto::kx::{ActiveKeyExchange, KeyExchangeAlgorithm}; +use crate::crypto::tls12::PrfSecret; +use crate::crypto::{self, SignatureScheme, hash}; +use crate::enums::ProtocolVersion; +use crate::error::{ApiMisuse, Error, InvalidMessage}; +use crate::msgs::{Codec, HandshakeAlignedProof, KxDecode, Reader}; +use crate::suites::{CipherSuiteCommon, PartiallyExtractedSecrets, Suite, SupportedCipherSuite}; +use crate::version::Tls12Version; /// A TLS 1.2 cipher suite supported by rustls. +#[expect(clippy::exhaustive_structs)] pub struct Tls12CipherSuite { /// Common cipher suite fields. pub common: CipherSuiteCommon, + /// The associated protocol version. + /// + /// This field should have the value [`rustls::version::TLS12_VERSION`]. + /// + /// This value contains references to the TLS1.2 protocol handling code. + /// This means that a program that does not contain any `Tls12CipherSuite` + /// values also does not contain any reference to the TLS1.2 protocol handling + /// code, and the linker can remove it. + /// + /// [`rustls::version::TLS12_VERSION`]: crate::version::TLS12_VERSION + pub protocol_version: &'static Tls12Version, + /// How to compute the TLS1.2 PRF for the suite's hash function. /// /// If you have a TLS1.2 PRF implementation, you should directly implement the [`crypto::tls12::Prf`] trait. @@ -59,18 +74,55 @@ impl Tls12CipherSuite { self.sign .iter() .filter(|pref| offered.contains(pref)) - .cloned() + .copied() .collect() } - /// Return `true` if this is backed by a FIPS-approved implementation. + /// Return the FIPS validation status of this implementation. /// - /// This means all the constituent parts that do cryptography return `true` for `fips()`. - pub fn fips(&self) -> bool { - self.common.fips() && self.prf_provider.fips() && self.aead_alg.fips() + /// This is the combination of the constituent parts of the cipher suite. + pub fn fips(&self) -> FipsStatus { + let status = Ord::min(self.common.fips(), self.prf_provider.fips()); + Ord::min(status, self.aead_alg.fips()) } } +impl Suite for Tls12CipherSuite { + fn client_handler(&self) -> &'static dyn crate::client::ClientHandler { + self.protocol_version.client + } + + fn server_handler(&self) -> &'static dyn crate::server::ServerHandler { + self.protocol_version.server + } + + /// Does this suite support the `proto` protocol? + /// + /// All TLS1.2 suites support TCP-TLS. No TLS1.2 suites support QUIC. + fn usable_for_protocol(&self, proto: Protocol) -> bool { + matches!(proto, Protocol::Tcp) + } + + /// Say if the given `KeyExchangeAlgorithm` is supported by this cipher suite. + fn usable_for_kx_algorithm(&self, kxa: KeyExchangeAlgorithm) -> bool { + self.kx == kxa + } + + /// Return true if this suite is usable for a key only offering `sig_alg` + /// signatures. + fn usable_for_signature_scheme(&self, scheme: SignatureScheme) -> bool { + self.sign + .iter() + .any(|s| s.algorithm() == scheme.algorithm()) + } + + fn common(&self) -> &CipherSuiteCommon { + &self.common + } + + const VERSION: ProtocolVersion = ProtocolVersion::TLSv1_2; +} + impl From<&'static Tls12CipherSuite> for SupportedCipherSuite { fn from(s: &'static Tls12CipherSuite) -> Self { Self::Tls12(s) @@ -87,7 +139,7 @@ impl fmt::Debug for Tls12CipherSuite { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Tls12CipherSuite") .field("suite", &self.common.suite) - .finish() + .finish_non_exhaustive() } } @@ -95,28 +147,27 @@ impl fmt::Debug for Tls12CipherSuite { pub(crate) struct ConnectionSecrets { pub(crate) randoms: ConnectionRandoms, suite: &'static Tls12CipherSuite, - pub(crate) master_secret: [u8; 48], + master_secret: Zeroizing<[u8; 48]>, + + /// `master_secret` ready to be used as a TLS1.2 PRF secret. + /// + /// Zeroizing this on drop is left to the implementer of the trait. + master_secret_prf: Box, } impl ConnectionSecrets { pub(crate) fn from_key_exchange( - kx: Box, + kx: Box, peer_pub_key: &[u8], ems_seed: Option, randoms: ConnectionRandoms, suite: &'static Tls12CipherSuite, ) -> Result { - let mut ret = Self { - randoms, - suite, - master_secret: [0u8; 48], - }; - let (label, seed) = match ems_seed { Some(seed) => ("extended master secret", Seed::Ems(seed)), None => ( "master secret", - Seed::Randoms(join_randoms(&ret.randoms.client, &ret.randoms.server)), + Seed::Randoms(join_randoms(&randoms.client, &randoms.server)), ), }; @@ -124,32 +175,41 @@ impl ConnectionSecrets { // slice parameters are non-empty. // `label` is guaranteed non-empty because it's assigned from a `&str` above. // `seed.as_ref()` is guaranteed non-empty by documentation on the AsRef impl. - ret.suite + let mut master_secret = [0u8; 48]; + suite.prf_provider.for_key_exchange( + &mut master_secret, + kx, + peer_pub_key, + label.as_bytes(), + seed.as_ref(), + )?; + let master_secret = Zeroizing::new(master_secret); + + let master_secret_prf = suite .prf_provider - .for_key_exchange( - &mut ret.master_secret, - kx, - peer_pub_key, - label.as_bytes(), - seed.as_ref(), - )?; - - Ok(ret) + .new_secret(&master_secret); + + Ok(Self { + randoms, + suite, + master_secret, + master_secret_prf, + }) } pub(crate) fn new_resume( randoms: ConnectionRandoms, suite: &'static Tls12CipherSuite, - master_secret: &[u8], + master_secret: &[u8; 48], ) -> Self { - let mut ret = Self { + Self { randoms, suite, - master_secret: [0u8; 48], - }; - ret.master_secret - .copy_from_slice(master_secret); - ret + master_secret: Zeroizing::new(*master_secret), + master_secret_prf: suite + .prf_provider + .new_secret(master_secret), + } } /// Make a `MessageCipherPair` based on the given supported ciphersuite `self.suite`, @@ -190,7 +250,7 @@ impl ConnectionSecrets { ) } - fn make_key_block(&self) -> Vec { + fn make_key_block(&self) -> Zeroizing> { let shape = self.suite.aead_alg.key_block_shape(); let len = (shape.enc_key_len + shape.fixed_iv_len) * 2 + shape.explicit_nonce_len; @@ -200,63 +260,59 @@ impl ConnectionSecrets { // NOTE: opposite order to above for no good reason. // Don't design security protocols on drugs, kids. let randoms = join_randoms(&self.randoms.server, &self.randoms.client); - self.suite.prf_provider.for_secret( - &mut out, - &self.master_secret, - b"key expansion", - &randoms, - ); + self.master_secret_prf + .prf(&mut out, b"key expansion", &randoms); - out + Zeroizing::new(out) } pub(crate) fn suite(&self) -> &'static Tls12CipherSuite { self.suite } - pub(crate) fn master_secret(&self) -> &[u8] { - &self.master_secret[..] + pub(crate) fn master_secret(&self) -> &[u8; 48] { + &self.master_secret } - fn make_verify_data(&self, handshake_hash: &hash::Output, label: &[u8]) -> Vec { - let mut out = vec![0u8; 12]; - - self.suite.prf_provider.for_secret( - &mut out, - &self.master_secret, - label, - handshake_hash.as_ref(), - ); - + fn make_verify_data( + &self, + handshake_hash: &hash::Output, + label: &[u8], + _proof: &HandshakeAlignedProof, + ) -> [u8; 12] { + let mut out = [0u8; 12]; + self.master_secret_prf + .prf(&mut out, label, handshake_hash.as_ref()); out } - pub(crate) fn client_verify_data(&self, handshake_hash: &hash::Output) -> Vec { - self.make_verify_data(handshake_hash, b"client finished") - } - - pub(crate) fn server_verify_data(&self, handshake_hash: &hash::Output) -> Vec { - self.make_verify_data(handshake_hash, b"server finished") + pub(crate) fn client_verify_data( + &self, + handshake_hash: &hash::Output, + proof: &HandshakeAlignedProof, + ) -> [u8; 12] { + self.make_verify_data(handshake_hash, b"client finished", proof) } - pub(crate) fn export_keying_material( + pub(crate) fn server_verify_data( &self, - output: &mut [u8], - label: &[u8], - context: Option<&[u8]>, - ) { - let mut randoms = Vec::new(); - randoms.extend_from_slice(&self.randoms.client); - randoms.extend_from_slice(&self.randoms.server); - if let Some(context) = context { - assert!(context.len() <= 0xffff); - (context.len() as u16).encode(&mut randoms); - randoms.extend_from_slice(context); - } + handshake_hash: &hash::Output, + proof: &HandshakeAlignedProof, + ) -> [u8; 12] { + self.make_verify_data(handshake_hash, b"server finished", proof) + } - self.suite - .prf_provider - .for_secret(output, &self.master_secret, label, &randoms); + pub(crate) fn into_exporter(self) -> Box { + let Self { + randoms, + master_secret_prf, + master_secret: _, + suite: _, + } = self; + Box::new(Tls12Exporter { + randoms, + master_secret_prf, + }) } pub(crate) fn extract_secrets(&self, side: Side) -> Result { @@ -288,9 +344,33 @@ impl ConnectionSecrets { } } -impl Drop for ConnectionSecrets { - fn drop(&mut self) { - self.master_secret.zeroize(); +pub(crate) struct Tls12Exporter { + randoms: ConnectionRandoms, + master_secret_prf: Box, +} + +impl Exporter for Tls12Exporter { + fn derive(&self, label: &[u8], context: Option<&[u8]>, output: &mut [u8]) -> Result<(), Error> { + let mut randoms = Vec::with_capacity( + 32 + 32 + + context + .as_ref() + .map(|c| 2 + c.len()) + .unwrap_or_default(), + ); + randoms.extend_from_slice(&self.randoms.client); + randoms.extend_from_slice(&self.randoms.server); + if let Some(context) = context { + match u16::try_from(context.len()) { + Ok(len) => len.encode(&mut randoms), + Err(_) => return Err(ApiMisuse::ExporterContextTooLong.into()), + } + randoms.extend_from_slice(context); + } + + self.master_secret_prf + .prf(output, label, &randoms); + Ok(()) } } @@ -322,55 +402,50 @@ type MessageCipherPair = (Box, Box); pub(crate) fn decode_kx_params<'a, T: KxDecode<'a>>( kx_algorithm: KeyExchangeAlgorithm, - common: &mut CommonState, kx_params: &'a [u8], ) -> Result { - let mut rd = Reader::init(kx_params); + let mut rd = Reader::new(kx_params); let kx_params = T::decode(&mut rd, kx_algorithm)?; match rd.any_left() { false => Ok(kx_params), - true => Err(common.send_fatal_alert( - AlertDescription::DecodeError, - InvalidMessage::InvalidDhParams, - )), + true => Err(InvalidMessage::InvalidDhParams.into()), } } pub(crate) const DOWNGRADE_SENTINEL: [u8; 8] = [0x44, 0x4f, 0x57, 0x4e, 0x47, 0x52, 0x44, 0x01]; #[cfg(test)] -#[macro_rules_attribute::apply(test_for_each_provider)] mod tests { - use super::provider::kx_group::X25519; use super::*; - use crate::common_state::{CommonState, Side}; - use crate::msgs::handshake::{ServerEcdhParams, ServerKeyExchangeParams}; + use crate::crypto::TEST_PROVIDER; + use crate::crypto::kx::NamedGroup; + use crate::msgs::{ServerEcdhParams, ServerKeyExchangeParams}; #[test] fn server_ecdhe_remaining_bytes() { - let key = X25519.start().unwrap(); + let Some(kx_group) = + TEST_PROVIDER.find_kx_group(NamedGroup::X25519, ProtocolVersion::TLSv1_3) + else { + return; + }; + + let key = kx_group.start().unwrap(); let server_params = ServerEcdhParams::new(&*key); let mut server_buf = Vec::new(); server_params.encode(&mut server_buf); server_buf.push(34); - let mut common = CommonState::new(Side::Client); - assert!(decode_kx_params::( - KeyExchangeAlgorithm::ECDHE, - &mut common, - &server_buf - ) - .is_err()); + assert!( + decode_kx_params::(KeyExchangeAlgorithm::ECDHE, &server_buf) + .is_err() + ); } #[test] fn client_ecdhe_invalid() { - let mut common = CommonState::new(Side::Server); - assert!(decode_kx_params::( - KeyExchangeAlgorithm::ECDHE, - &mut common, - &[34], - ) - .is_err()); + assert!( + decode_kx_params::(KeyExchangeAlgorithm::ECDHE, &[34],) + .is_err() + ); } } diff --git a/rustls/src/tls13/key_schedule.rs b/rustls/src/tls13/key_schedule.rs index 8b6afb850e2..1d3e4ed518f 100644 --- a/rustls/src/tls13/key_schedule.rs +++ b/rustls/src/tls13/key_schedule.rs @@ -1,102 +1,132 @@ //! Key schedule maintenance for TLS1.3 use alloc::boxed::Box; -use alloc::string::ToString; +use core::ops::Deref; -use crate::common_state::{CommonState, Side}; +use crate::common_state::{Output, Protocol, Side}; +use crate::conn::{Exporter, ReceivePath, SendOutput}; use crate::crypto::cipher::{AeadKey, Iv, MessageDecrypter, Tls13AeadAlgorithm}; -use crate::crypto::tls13::{expand, Hkdf, HkdfExpander, OkmBlock, OutputLengthError}; -use crate::crypto::{hash, hmac, SharedSecret}; -use crate::error::Error; -use crate::msgs::message::Message; -use crate::suites::PartiallyExtractedSecrets; -use crate::{quic, KeyLog, Tls13CipherSuite}; +use crate::crypto::kx::SharedSecret; +use crate::crypto::tls13::{Hkdf, HkdfExpander, OkmBlock, OutputLengthError, expand}; +use crate::crypto::{hash, hmac}; +use crate::error::{ApiMisuse, Error}; +use crate::msgs::{HandshakeAlignedProof, Message}; +use crate::{ConnectionTrafficSecrets, KeyLog, Tls13CipherSuite}; -/// The kinds of secret we can extract from `KeySchedule`. -#[derive(Debug, Clone, Copy, PartialEq)] -enum SecretKind { - ResumptionPskBinderKey, - ClientEarlyTrafficSecret, - ClientHandshakeTrafficSecret, - ServerHandshakeTrafficSecret, - ClientApplicationTrafficSecret, - ServerApplicationTrafficSecret, - ExporterMasterSecret, - ResumptionMasterSecret, - DerivedSecret, - ServerEchConfirmationSecret, - ServerEchHrrConfirmationSecret, -} +// We express the state of a contained KeySchedule using these +// typestates. This means we can write code that cannot accidentally +// (e.g.) encrypt application data using a KeySchedule solely constructed +// with an empty or trivial secret, or extract the wrong kind of secrets +// at a given point. -impl SecretKind { - fn to_bytes(self) -> &'static [u8] { - use self::SecretKind::*; - match self { - ResumptionPskBinderKey => b"res binder", - ClientEarlyTrafficSecret => b"c e traffic", - ClientHandshakeTrafficSecret => b"c hs traffic", - ServerHandshakeTrafficSecret => b"s hs traffic", - ClientApplicationTrafficSecret => b"c ap traffic", - ServerApplicationTrafficSecret => b"s ap traffic", - ExporterMasterSecret => b"exp master", - ResumptionMasterSecret => b"res master", - DerivedSecret => b"derived", - // https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#section-7.2 - ServerEchConfirmationSecret => b"ech accept confirmation", - // https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#section-7.2.1 - ServerEchHrrConfirmationSecret => b"hrr ech accept confirmation", - } +pub(crate) struct KeyScheduleEarlyClient(KeyScheduleEarly); + +impl KeyScheduleEarlyClient { + pub(crate) fn new(protocol: Protocol, suite: &'static Tls13CipherSuite, secret: &[u8]) -> Self { + Self(KeyScheduleEarly::new(Side::Client, protocol, suite, secret)) } - fn log_label(self) -> Option<&'static str> { - use self::SecretKind::*; - Some(match self { - ClientEarlyTrafficSecret => "CLIENT_EARLY_TRAFFIC_SECRET", - ClientHandshakeTrafficSecret => "CLIENT_HANDSHAKE_TRAFFIC_SECRET", - ServerHandshakeTrafficSecret => "SERVER_HANDSHAKE_TRAFFIC_SECRET", - ClientApplicationTrafficSecret => "CLIENT_TRAFFIC_SECRET_0", - ServerApplicationTrafficSecret => "SERVER_TRAFFIC_SECRET_0", - ExporterMasterSecret => "EXPORTER_SECRET", - _ => { - return None; - } - }) + /// Computes the `client_early_traffic_secret` and installs it as encrypter. + pub(crate) fn client_early_traffic_secret( + &self, + hs_hash: &hash::Output, + key_log: &dyn KeyLog, + client_random: &[u8; 32], + output: &mut dyn Output<'_>, + ) { + self.0.ks.set_encrypter( + &self + .0 + .client_early_traffic_secret(hs_hash, key_log, client_random, output), + output.send(), + ); + } + + pub(crate) fn protocol(&self) -> Protocol { + self.0.ks.protocol } } -/// This is the TLS1.3 key schedule. It stores the current secret and -/// the type of hash. This isn't used directly; but only through the -/// typestates. -struct KeySchedule { - current: Box, - suite: &'static Tls13CipherSuite, +impl Deref for KeyScheduleEarlyClient { + type Target = KeyScheduleEarly; + + fn deref(&self) -> &Self::Target { + &self.0 + } } -// We express the state of a contained KeySchedule using these -// typestates. This means we can write code that cannot accidentally -// (e.g.) encrypt application data using a KeySchedule solely constructed -// with an empty or trivial secret, or extract the wrong kind of secrets -// at a given point. +pub(crate) struct KeyScheduleEarlyServer(KeyScheduleEarly); + +impl KeyScheduleEarlyServer { + pub(crate) fn new(protocol: Protocol, suite: &'static Tls13CipherSuite, secret: &[u8]) -> Self { + Self(KeyScheduleEarly::new(Side::Server, protocol, suite, secret)) + } + + /// Computes the `client_early_traffic_secret` and installs it as decrypter. + pub(crate) fn client_early_traffic_secret( + &self, + hs_hash: &hash::Output, + key_log: &dyn KeyLog, + client_random: &[u8; 32], + output: &mut dyn Output<'_>, + proof: &HandshakeAlignedProof, + ) { + self.0.ks.set_decrypter( + &self + .0 + .client_early_traffic_secret(hs_hash, key_log, client_random, output), + output.receive(), + proof, + ); + } +} + +impl Deref for KeyScheduleEarlyServer { + type Target = KeyScheduleEarly; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} -/// KeySchedule for early data stage. +/// The "early secret" stage of the key schedule WITH a PSK. +/// +/// This is only useful when you need to use one of the binder +/// keys, the "client_early_traffic_secret", or +/// "early_exporter_master_secret". +/// +/// See [`KeySchedulePreHandshake`] for more information. pub(crate) struct KeyScheduleEarly { ks: KeySchedule, } impl KeyScheduleEarly { - pub(crate) fn new(suite: &'static Tls13CipherSuite, secret: &[u8]) -> Self { + fn new( + local: Side, + protocol: Protocol, + suite: &'static Tls13CipherSuite, + secret: &[u8], + ) -> Self { Self { - ks: KeySchedule::new(suite, secret), + ks: KeySchedule::new(local, protocol, suite, secret), } } - pub(crate) fn client_early_traffic_secret( + /// Computes the `client_early_traffic_secret` and returns it. + /// + /// `hs_hash` is `Transcript-Hash(ClientHello)`. + /// + /// ```text + /// Derive-Secret(., "c e traffic", ClientHello) + /// = client_early_traffic_secret + /// ``` + fn client_early_traffic_secret( &self, hs_hash: &hash::Output, key_log: &dyn KeyLog, client_random: &[u8; 32], - common: &mut CommonState, - ) { + output: &mut dyn Output<'_>, + ) -> OkmBlock { let client_early_traffic_secret = self.ks.derive_logged_secret( SecretKind::ClientEarlyTrafficSecret, hs_hash.as_ref(), @@ -104,49 +134,90 @@ impl KeyScheduleEarly { client_random, ); - match common.side { - Side::Client => self - .ks - .set_encrypter(&client_early_traffic_secret, common), - Side::Server => self - .ks - .set_decrypter(&client_early_traffic_secret, common), - } - - if common.is_quic() { + if let Some(quic) = output.quic() { // If 0-RTT should be rejected, this will be clobbered by ExtensionProcessing // before the application can see. - common.quic.early_secret = Some(client_early_traffic_secret); + quic.early_secret(Some(client_early_traffic_secret.clone())); } + + client_early_traffic_secret } pub(crate) fn resumption_psk_binder_key_and_sign_verify_data( &self, hs_hash: &hash::Output, - ) -> hmac::Tag { + ) -> hmac::PublicTag { let resumption_psk_binder_key = self .ks .derive_for_empty_hash(SecretKind::ResumptionPskBinderKey); self.ks .sign_verify_data(&resumption_psk_binder_key, hs_hash) } + + pub(crate) fn early_exporter( + &self, + hs_hash: &hash::Output, + key_log: &dyn KeyLog, + client_random: &[u8; 32], + ) -> Box { + let early_exporter_secret = self.ks.derive_logged_secret( + SecretKind::EarlyExporterMasterSecret, + hs_hash.as_ref(), + key_log, + client_random, + ); + Box::new(KeyScheduleExporter { + ks: self.ks.inner, + current_exporter_secret: early_exporter_secret, + }) + } + + pub(crate) fn hash(&self) -> &'static dyn hash::Hash { + self.ks.inner.suite.common.hash_provider + } } -/// Pre-handshake key schedule +/// The "early secret" stage of the key schedule. /// -/// The inner `KeySchedule` is either constructed without any secrets based on the HKDF algorithm -/// or is extracted from a `KeyScheduleEarly`. This can then be used to derive the `KeyScheduleHandshakeStart`. +/// Call [`KeySchedulePreHandshake::new`] to create it without +/// a PSK, or use [`From`] to create it with +/// a PSK. +/// +/// ```text +/// 0 +/// | +/// v +/// PSK -> HKDF-Extract = Early Secret +/// | +/// +-----> Derive-Secret(., "ext binder" | "res binder", "") +/// | = binder_key +/// | +/// +-----> Derive-Secret(., "c e traffic", ClientHello) +/// | = client_early_traffic_secret +/// | +/// +-----> Derive-Secret(., "e exp master", ClientHello) +/// | = early_exporter_master_secret +/// v +/// Derive-Secret(., "derived", "") +/// ``` pub(crate) struct KeySchedulePreHandshake { ks: KeySchedule, } impl KeySchedulePreHandshake { - pub(crate) fn new(suite: &'static Tls13CipherSuite) -> Self { + /// Creates a key schedule without a PSK. + pub(crate) fn new(local: Side, protocol: Protocol, suite: &'static Tls13CipherSuite) -> Self { Self { - ks: KeySchedule::new_with_empty_secret(suite), + ks: KeySchedule::new_with_empty_secret(local, protocol, suite), } } + /// `shared_secret` is the "(EC)DHE" secret input to + /// "HKDF-Extract": + /// + /// ```text + /// (EC)DHE -> HKDF-Extract = Handshake Secret + /// ``` pub(crate) fn into_handshake( mut self, shared_secret: SharedSecret, @@ -157,13 +228,23 @@ impl KeySchedulePreHandshake { } } -impl From for KeySchedulePreHandshake { - fn from(KeyScheduleEarly { ks }: KeyScheduleEarly) -> Self { +/// Creates a key schedule with a PSK. +impl From for KeySchedulePreHandshake { + fn from(KeyScheduleEarlyClient(KeyScheduleEarly { ks }): KeyScheduleEarlyClient) -> Self { + Self { ks } + } +} + +/// Creates a key schedule with a PSK. +impl From for KeySchedulePreHandshake { + fn from(KeyScheduleEarlyServer(KeyScheduleEarly { ks }): KeyScheduleEarlyServer) -> Self { Self { ks } } } /// KeySchedule during handshake. +/// +/// Created by [`KeySchedulePreHandshake`]. pub(crate) struct KeyScheduleHandshakeStart { ks: KeySchedule, } @@ -176,21 +257,25 @@ impl KeyScheduleHandshakeStart { suite: &'static Tls13CipherSuite, key_log: &dyn KeyLog, client_random: &[u8; 32], - common: &mut CommonState, + output: &mut dyn Output<'_>, + proof: &HandshakeAlignedProof, ) -> KeyScheduleHandshake { - debug_assert_eq!(common.side, Side::Client); + debug_assert_eq!(self.ks.side, Side::Client); // Suite might have changed due to resumption - self.ks.suite = suite; - let new = self.into_handshake(hs_hash, key_log, client_random, common); + self.ks.inner.suite = suite; + let new = self.into_handshake(hs_hash, key_log, client_random, output); // Decrypt with the peer's key, encrypt with our own key - new.ks - .set_decrypter(&new.server_handshake_traffic_secret, common); + new.ks.set_decrypter( + &new.server_handshake_traffic_secret, + output.receive(), + proof, + ); if !early_data_enabled { // Set the client encryption key for handshakes if early data is not used new.ks - .set_encrypter(&new.client_handshake_traffic_secret, common); + .set_encrypter(&new.client_handshake_traffic_secret, output.send()); } new @@ -201,27 +286,27 @@ impl KeyScheduleHandshakeStart { hs_hash: hash::Output, key_log: &dyn KeyLog, client_random: &[u8; 32], - common: &mut CommonState, + output: &mut dyn Output<'_>, ) -> KeyScheduleHandshake { - debug_assert_eq!(common.side, Side::Server); - let new = self.into_handshake(hs_hash, key_log, client_random, common); + debug_assert_eq!(self.ks.side, Side::Server); + let new = self.into_handshake(hs_hash, key_log, client_random, output); // Set up to encrypt with handshake secrets, but decrypt with early_data keys. // If not doing early_data after all, this is corrected later to the handshake // keys (now stored in key_schedule). new.ks - .set_encrypter(&new.server_handshake_traffic_secret, common); + .set_encrypter(&new.server_handshake_traffic_secret, output.send()); new } pub(crate) fn server_ech_confirmation_secret( - &mut self, + &self, client_hello_inner_random: &[u8], hs_hash: hash::Output, ) -> [u8; 8] { /* - Per ietf-tls-esni-17 section 7.2: - + Per RFC 9849 section 7.2: + accept_confirmation = HKDF-Expand-Label( HKDF-Extract(0, ClientHelloInner.random), "ech accept confirmation", @@ -243,7 +328,7 @@ impl KeyScheduleHandshakeStart { hs_hash: hash::Output, key_log: &dyn KeyLog, client_random: &[u8; 32], - common: &mut CommonState, + output: &mut dyn Output<'_>, ) -> KeyScheduleHandshake { // Use an empty handshake hash for the initial handshake. let client_secret = self.ks.derive_logged_secret( @@ -260,15 +345,14 @@ impl KeyScheduleHandshakeStart { client_random, ); - if common.is_quic() { - common.quic.hs_secrets = Some(quic::Secrets::new( + if let Some(quic) = output.quic() { + quic.handshake_secrets( client_secret.clone(), server_secret.clone(), self.ks.suite, self.ks.suite.quic.unwrap(), - common.side, - common.quic.version, - )); + self.ks.side, + ); } KeyScheduleHandshake { @@ -286,32 +370,40 @@ pub(crate) struct KeyScheduleHandshake { } impl KeyScheduleHandshake { - pub(crate) fn sign_server_finish(&self, hs_hash: &hash::Output) -> hmac::Tag { + pub(crate) fn sign_server_finish( + &self, + hs_hash: &hash::Output, + _proof: &HandshakeAlignedProof, + ) -> hmac::PublicTag { self.ks .sign_finish(&self.server_handshake_traffic_secret, hs_hash) } - pub(crate) fn set_handshake_encrypter(&self, common: &mut CommonState) { - debug_assert_eq!(common.side, Side::Client); + pub(crate) fn set_handshake_encrypter(&self, send: &mut dyn SendOutput) { + debug_assert_eq!(self.ks.side, Side::Client); self.ks - .set_encrypter(&self.client_handshake_traffic_secret, common); + .set_encrypter(&self.client_handshake_traffic_secret, send); } pub(crate) fn set_handshake_decrypter( &self, skip_requested: Option, - common: &mut CommonState, + receive: &mut ReceivePath, + proof: &HandshakeAlignedProof, ) { - debug_assert_eq!(common.side, Side::Server); + debug_assert_eq!(self.ks.side, Side::Server); let secret = &self.client_handshake_traffic_secret; match skip_requested { - None => self.ks.set_decrypter(secret, common), - Some(max_early_data_size) => common - .record_layer + None => self + .ks + .set_decrypter(secret, receive, proof), + Some(max_early_data_size) => receive + .decrypt_state .set_message_decrypter_with_trial_decryption( self.ks .derive_decrypter(&self.client_handshake_traffic_secret), max_early_data_size, + proof, ), } } @@ -321,34 +413,34 @@ impl KeyScheduleHandshake { hs_hash: hash::Output, key_log: &dyn KeyLog, client_random: &[u8; 32], - common: &mut CommonState, + output: &mut dyn Output<'_>, ) -> KeyScheduleTrafficWithClientFinishedPending { - debug_assert_eq!(common.side, Side::Server); + debug_assert_eq!(self.ks.side, Side::Server); - let traffic = KeyScheduleTraffic::new(self.ks, hs_hash, key_log, client_random); - let (_client_secret, server_secret) = ( - &traffic.current_client_traffic_secret, - &traffic.current_server_traffic_secret, + let before_finished = + KeyScheduleBeforeFinished::new(self.ks, hs_hash, key_log, client_random); + let (client_secret, server_secret) = ( + &before_finished.current_client_traffic_secret, + &before_finished.current_server_traffic_secret, ); - traffic + before_finished .ks - .set_encrypter(server_secret, common); + .set_encrypter(server_secret, output.send()); - if common.is_quic() { - common.quic.traffic_secrets = Some(quic::Secrets::new( - _client_secret.clone(), + if let Some(quic) = output.quic() { + quic.traffic_secrets( + client_secret.clone(), server_secret.clone(), - traffic.ks.suite, - traffic.ks.suite.quic.unwrap(), - common.side, - common.quic.version, - )); + before_finished.ks.suite, + before_finished.ks.suite.quic.unwrap(), + before_finished.ks.side, + ); } KeyScheduleTrafficWithClientFinishedPending { handshake_client_traffic_secret: self.client_handshake_traffic_secret, - traffic, + before_finished, } } @@ -358,50 +450,144 @@ impl KeyScheduleHandshake { handshake_hash: hash::Output, key_log: &dyn KeyLog, client_random: &[u8; 32], - ) -> (KeyScheduleClientBeforeFinished, hmac::Tag) { - let traffic = KeyScheduleTraffic::new(self.ks, pre_finished_hash, key_log, client_random); - let tag = traffic + ) -> (KeyScheduleClientBeforeFinished, hmac::PublicTag) { + let before_finished = + KeyScheduleBeforeFinished::new(self.ks, pre_finished_hash, key_log, client_random); + let tag = before_finished .ks .sign_finish(&self.client_handshake_traffic_secret, &handshake_hash); - (KeyScheduleClientBeforeFinished { traffic }, tag) + (KeyScheduleClientBeforeFinished(before_finished), tag) + } + + pub(crate) fn protocol(&self) -> Protocol { + self.ks.protocol } } -pub(crate) struct KeyScheduleClientBeforeFinished { - traffic: KeyScheduleTraffic, +/// Keys derived (but not installed) before client's Finished message. +pub(crate) struct KeyScheduleBeforeFinished { + ks: KeySchedule, + current_client_traffic_secret: OkmBlock, + current_server_traffic_secret: OkmBlock, + current_exporter_secret: OkmBlock, +} + +impl KeyScheduleBeforeFinished { + fn new( + mut ks: KeySchedule, + hs_hash: hash::Output, + key_log: &dyn KeyLog, + client_random: &[u8; 32], + ) -> Self { + ks.input_empty(); + + let current_client_traffic_secret = ks.derive_logged_secret( + SecretKind::ClientApplicationTrafficSecret, + hs_hash.as_ref(), + key_log, + client_random, + ); + + let current_server_traffic_secret = ks.derive_logged_secret( + SecretKind::ServerApplicationTrafficSecret, + hs_hash.as_ref(), + key_log, + client_random, + ); + + let current_exporter_secret = ks.derive_logged_secret( + SecretKind::ExporterMasterSecret, + hs_hash.as_ref(), + key_log, + client_random, + ); + + Self { + ks, + current_client_traffic_secret, + current_server_traffic_secret, + current_exporter_secret, + } + } + + pub(crate) fn into_traffic( + self, + hs_hash: hash::Output, + ) -> ( + KeyScheduleTraffic, + KeyScheduleExporter, + KeyScheduleResumption, + ) { + let Self { + ks, + current_client_traffic_secret, + current_server_traffic_secret, + current_exporter_secret, + } = self; + + let resumption_master_secret = + ks.derive(SecretKind::ResumptionMasterSecret, hs_hash.as_ref()); + + ( + KeyScheduleTraffic { + ks: ks.inner, + current_client_traffic_secret, + current_server_traffic_secret, + }, + KeyScheduleExporter { + ks: ks.inner, + current_exporter_secret, + }, + KeyScheduleResumption { + ks: ks.inner, + resumption_master_secret, + }, + ) + } } +/// Client-side key schedule before the finished message is sent. +/// +/// This differs from `KeyScheduleTrafficWithClientFinishedPending` because +/// none of the final traffic secrets are installed yet. After the finished +/// message is sent, `into_traffic()` does that. +pub(crate) struct KeyScheduleClientBeforeFinished(KeyScheduleBeforeFinished); + impl KeyScheduleClientBeforeFinished { - pub(crate) fn into_traffic(self, common: &mut CommonState) -> KeyScheduleTraffic { - debug_assert_eq!(common.side, Side::Client); + pub(crate) fn into_traffic( + self, + output: &mut dyn Output<'_>, + hs_hash: hash::Output, + proof: &HandshakeAlignedProof, + ) -> ( + KeyScheduleTraffic, + KeyScheduleExporter, + KeyScheduleResumption, + ) { + let next = self.0; + + debug_assert_eq!(next.ks.side, Side::Client); let (client_secret, server_secret) = ( - &self - .traffic - .current_client_traffic_secret, - &self - .traffic - .current_server_traffic_secret, + &next.current_client_traffic_secret, + &next.current_server_traffic_secret, ); - self.traffic - .ks - .set_decrypter(server_secret, common); - self.traffic - .ks - .set_encrypter(client_secret, common); + next.ks + .set_decrypter(server_secret, output.receive(), proof); + next.ks + .set_encrypter(client_secret, output.send()); - if common.is_quic() { - common.quic.traffic_secrets = Some(quic::Secrets::new( + if let Some(quic) = output.quic() { + quic.traffic_secrets( client_secret.clone(), server_secret.clone(), - self.traffic.ks.suite, - self.traffic.ks.suite.quic.unwrap(), - common.side, - common.quic.version, - )); + next.ks.suite, + next.ks.suite.quic.unwrap(), + next.ks.side, + ); } - self.traffic + next.into_traffic(hs_hash) } } @@ -410,247 +596,252 @@ impl KeyScheduleClientBeforeFinished { /// through signing the client finished hash. pub(crate) struct KeyScheduleTrafficWithClientFinishedPending { handshake_client_traffic_secret: OkmBlock, - traffic: KeyScheduleTraffic, + before_finished: KeyScheduleBeforeFinished, } impl KeyScheduleTrafficWithClientFinishedPending { - pub(crate) fn update_decrypter(&self, common: &mut CommonState) { - debug_assert_eq!(common.side, Side::Server); - self.traffic - .ks - .set_decrypter(&self.handshake_client_traffic_secret, common); + pub(crate) fn update_decrypter( + &self, + receive: &mut ReceivePath, + proof: &HandshakeAlignedProof, + ) { + debug_assert_eq!(self.before_finished.ks.side, Side::Server); + self.before_finished.ks.set_decrypter( + &self.handshake_client_traffic_secret, + receive, + proof, + ); } pub(crate) fn sign_client_finish( self, hs_hash: &hash::Output, - common: &mut CommonState, - ) -> (KeyScheduleTraffic, hmac::Tag) { - debug_assert_eq!(common.side, Side::Server); + receive: &mut ReceivePath, + proof: &HandshakeAlignedProof, + ) -> (KeyScheduleBeforeFinished, hmac::PublicTag) { + debug_assert_eq!(self.before_finished.ks.side, Side::Server); let tag = self - .traffic + .before_finished .ks .sign_finish(&self.handshake_client_traffic_secret, hs_hash); // Install keying to read future messages. - self.traffic.ks.set_decrypter( + self.before_finished.ks.set_decrypter( &self - .traffic + .before_finished .current_client_traffic_secret, - common, + receive, + proof, ); - (self.traffic, tag) + (self.before_finished, tag) } } /// KeySchedule during traffic stage. All traffic & exporter keys are guaranteed /// to be available. pub(crate) struct KeyScheduleTraffic { - ks: KeySchedule, + ks: KeyScheduleSuite, current_client_traffic_secret: OkmBlock, current_server_traffic_secret: OkmBlock, - current_exporter_secret: OkmBlock, } impl KeyScheduleTraffic { - fn new( - mut ks: KeySchedule, - hs_hash: hash::Output, - key_log: &dyn KeyLog, - client_random: &[u8; 32], - ) -> Self { - ks.input_empty(); - - let current_client_traffic_secret = ks.derive_logged_secret( - SecretKind::ClientApplicationTrafficSecret, - hs_hash.as_ref(), - key_log, - client_random, - ); + pub(crate) fn split(self) -> (KeyScheduleTrafficSend, KeyScheduleTrafficReceive) { + let (send, receive) = match self.ks.side { + Side::Client => ( + self.current_client_traffic_secret, + self.current_server_traffic_secret, + ), + Side::Server => ( + self.current_server_traffic_secret, + self.current_client_traffic_secret, + ), + }; - let current_server_traffic_secret = ks.derive_logged_secret( - SecretKind::ServerApplicationTrafficSecret, - hs_hash.as_ref(), - key_log, - client_random, - ); + ( + KeyScheduleTrafficSend { + ks: self.ks, + current: send, + }, + KeyScheduleTrafficReceive { + ks: self.ks, + current: receive, + }, + ) + } +} - let current_exporter_secret = ks.derive_logged_secret( - SecretKind::ExporterMasterSecret, - hs_hash.as_ref(), - key_log, - client_random, - ); +/// KeySchedule during traffic stage for send direction. +pub(crate) struct KeyScheduleTrafficSend { + ks: KeyScheduleSuite, + current: OkmBlock, +} - Self { - ks, - current_client_traffic_secret, - current_server_traffic_secret, - current_exporter_secret, - } +impl KeyScheduleTrafficSend { + pub(crate) fn update_encrypter_for_key_update(&mut self, send: &mut dyn SendOutput) { + let secret = self.ks.derive_next(&self.current); + self.ks.set_encrypter(&secret, send); + self.current = secret; } - pub(crate) fn update_encrypter_and_notify(&mut self, common: &mut CommonState) { - let secret = self.next_application_traffic_secret(common.side); - common.enqueue_key_update_notification(); - self.ks.set_encrypter(&secret, common); + pub(crate) fn request_key_update_and_update_encrypter(&mut self, send: &mut dyn SendOutput) { + send.send_msg(Message::build_key_update_request(), true); + let secret = self.ks.derive_next(&self.current); + self.ks.set_encrypter(&secret, send); + self.current = secret; } - pub(crate) fn request_key_update_and_update_encrypter( - &mut self, - common: &mut CommonState, - ) -> Result<(), Error> { - common.check_aligned_handshake()?; - common.send_msg_encrypt(Message::build_key_update_request().into()); - let secret = self.next_application_traffic_secret(common.side); - self.ks.set_encrypter(&secret, common); - Ok(()) + pub(crate) fn refresh_traffic_secret(&mut self) -> Result { + self.current = self.ks.derive_next(&self.current); + self.extract() } - pub(crate) fn update_decrypter(&mut self, common: &mut CommonState) { - let secret = self.next_application_traffic_secret(common.side.peer()); - self.ks.set_decrypter(&secret, common); + pub(crate) fn extract(&self) -> Result { + let (key, iv) = expand_secret( + &self.current, + self.ks.suite.hkdf_provider, + self.ks.suite.aead_alg.key_len(), + self.ks.suite.aead_alg.iv_len(), + ); + Ok(self + .ks + .suite + .aead_alg + .extract_keys(key, iv)?) } +} - pub(crate) fn next_application_traffic_secret(&mut self, side: Side) -> OkmBlock { - let current = match side { - Side::Client => &mut self.current_client_traffic_secret, - Side::Server => &mut self.current_server_traffic_secret, - }; - - let secret = self.ks.derive_next(current); - *current = secret.clone(); - secret - } +/// KeySchedule during traffic stage for receive direction. +pub(crate) struct KeyScheduleTrafficReceive { + ks: KeyScheduleSuite, + current: OkmBlock, +} - pub(crate) fn export_keying_material( - &self, - out: &mut [u8], - label: &[u8], - context: Option<&[u8]>, - ) -> Result<(), Error> { +impl KeyScheduleTrafficReceive { + pub(crate) fn update_decrypter( + &mut self, + receive: &mut ReceivePath, + proof: &HandshakeAlignedProof, + ) { + let secret = self.ks.derive_next(&self.current); self.ks - .export_keying_material(&self.current_exporter_secret, out, label, context) + .set_decrypter(&secret, receive, proof); + self.current = secret; } - pub(crate) fn extract_secrets(&self, side: Side) -> Result { - fn expand( - secret: &OkmBlock, - hkdf: &'static dyn Hkdf, - aead_key_len: usize, - ) -> (AeadKey, Iv) { - let expander = hkdf.expander_for_okm(secret); - - ( - hkdf_expand_label_aead_key(expander.as_ref(), aead_key_len, b"key", &[]), - hkdf_expand_label(expander.as_ref(), b"iv", &[]), - ) - } + pub(crate) fn refresh_traffic_secret(&mut self) -> Result { + self.current = self.ks.derive_next(&self.current); + self.extract() + } - let (client_key, client_iv) = expand( - &self.current_client_traffic_secret, - self.ks.suite.hkdf_provider, - self.ks.suite.aead_alg.key_len(), - ); - let (server_key, server_iv) = expand( - &self.current_server_traffic_secret, + pub(crate) fn extract(&self) -> Result { + let (key, iv) = expand_secret( + &self.current, self.ks.suite.hkdf_provider, self.ks.suite.aead_alg.key_len(), + self.ks.suite.aead_alg.iv_len(), ); - let client_secrets = self + Ok(self .ks .suite .aead_alg - .extract_keys(client_key, client_iv)?; - let server_secrets = self - .ks - .suite - .aead_alg - .extract_keys(server_key, server_iv)?; + .extract_keys(key, iv)?) + } - let (tx, rx) = match side { - Side::Client => (client_secrets, server_secrets), - Side::Server => (server_secrets, client_secrets), - }; - Ok(PartiallyExtractedSecrets { tx, rx }) + pub(crate) fn protocol(&self) -> Protocol { + self.ks.protocol } } -pub(crate) struct ResumptionSecret<'a> { - kst: &'a KeyScheduleTraffic, - resumption_master_secret: OkmBlock, +pub(crate) struct KeyScheduleExporter { + ks: KeyScheduleSuite, + current_exporter_secret: OkmBlock, } -impl<'a> ResumptionSecret<'a> { - pub(crate) fn new(kst: &'a KeyScheduleTraffic, hs_hash: &hash::Output) -> Self { - ResumptionSecret { - kst, - resumption_master_secret: kst - .ks - .derive(SecretKind::ResumptionMasterSecret, hs_hash.as_ref()), - } +impl Exporter for KeyScheduleExporter { + fn derive(&self, label: &[u8], context: Option<&[u8]>, out: &mut [u8]) -> Result<(), Error> { + self.ks + .export_keying_material(&self.current_exporter_secret, label, context, out) } +} + +pub(crate) struct KeyScheduleResumption { + ks: KeyScheduleSuite, + resumption_master_secret: OkmBlock, +} +impl KeyScheduleResumption { pub(crate) fn derive_ticket_psk(&self, nonce: &[u8]) -> OkmBlock { - self.kst - .ks + self.ks .derive_ticket_psk(&self.resumption_master_secret, nonce) } } +fn expand_secret( + secret: &OkmBlock, + hkdf: &'static dyn Hkdf, + aead_key_len: usize, + iv_len: usize, +) -> (AeadKey, Iv) { + let expander = hkdf.expander_for_okm(secret); + + ( + hkdf_expand_label_aead_key(expander.as_ref(), aead_key_len, b"key", &[]), + derive_traffic_iv(expander.as_ref(), iv_len), + ) +} + +/// This is the TLS1.3 key schedule. It stores the current secret and +/// the type of hash. This isn't used directly; but only through the +/// typestates. +struct KeySchedule { + current: Box, + inner: KeyScheduleSuite, +} + impl KeySchedule { - fn new(suite: &'static Tls13CipherSuite, secret: &[u8]) -> Self { + fn new( + side: Side, + protocol: Protocol, + suite: &'static Tls13CipherSuite, + secret: &[u8], + ) -> Self { Self { current: suite .hkdf_provider .extract_from_secret(None, secret), - suite, + inner: KeyScheduleSuite { + side, + protocol, + suite, + }, } } - fn set_encrypter(&self, secret: &OkmBlock, common: &mut CommonState) { - let expander = self - .suite - .hkdf_provider - .expander_for_okm(secret); - let key = derive_traffic_key(expander.as_ref(), self.suite.aead_alg); - let iv = derive_traffic_iv(expander.as_ref()); - - common - .record_layer - .set_message_encrypter( - self.suite.aead_alg.encrypter(key, iv), - self.suite.common.confidentiality_limit, - ); - } - - fn set_decrypter(&self, secret: &OkmBlock, common: &mut CommonState) { - common - .record_layer - .set_message_decrypter(self.derive_decrypter(secret)); - } - - fn derive_decrypter(&self, secret: &OkmBlock) -> Box { - let expander = self - .suite - .hkdf_provider - .expander_for_okm(secret); - let key = derive_traffic_key(expander.as_ref(), self.suite.aead_alg); - let iv = derive_traffic_iv(expander.as_ref()); - self.suite.aead_alg.decrypter(key, iv) - } - - fn new_with_empty_secret(suite: &'static Tls13CipherSuite) -> Self { + /// Creates a key schedule without a PSK. + fn new_with_empty_secret( + side: Side, + protocol: Protocol, + suite: &'static Tls13CipherSuite, + ) -> Self { Self { current: suite .hkdf_provider .extract_from_zero_ikm(None), - suite, + inner: KeyScheduleSuite { + side, + protocol, + suite, + }, } } /// Input the empty secret. + /// + /// RFC 8446: "If a given secret is not available, then the + /// 0-value consisting of a string of Hash.length bytes set + /// to zeros is used." fn input_empty(&mut self) { let salt = self.derive_for_empty_hash(SecretKind::DerivedSecret); self.current = self @@ -669,6 +860,12 @@ impl KeySchedule { } /// Derive a secret of given `kind`, using current handshake hash `hs_hash`. + /// + /// More specifically + /// ```text + /// Derive-Secret(., "derived", Messages) + /// ``` + /// where `hs_hash` is `Messages`. fn derive(&self, kind: SecretKind, hs_hash: &[u8]) -> OkmBlock { hkdf_expand_label_block(self.current.as_ref(), kind.to_bytes(), hs_hash) } @@ -692,28 +889,94 @@ impl KeySchedule { } /// Derive a secret of given `kind` using the hash of the empty string - /// for the handshake hash. Useful only for - /// `SecretKind::ResumptionPSKBinderKey` and - /// `SecretKind::DerivedSecret`. + /// for the handshake hash. + /// + /// More specifically: + /// ```text + /// Derive-Secret(., Label, "") + /// ``` + /// where `kind` is `Label`. + /// + /// Useful only for the following `SecretKind`s: + /// - `SecretKind::ExternalPskBinderKey` + /// - `SecretKind::ResumptionPSKBinderKey` + /// - `SecretKind::DerivedSecret` fn derive_for_empty_hash(&self, kind: SecretKind) -> OkmBlock { - let empty_hash = self - .suite - .common - .hash_provider - .start() - .finish(); + let hp = self.suite.common.hash_provider; + let empty_hash = hp + .algorithm() + .hash_for_empty_input() + .unwrap_or_else(|| hp.hash(b"")); self.derive(kind, empty_hash.as_ref()) } +} + +impl Deref for KeySchedule { + type Target = KeyScheduleSuite; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// This is a component part of `KeySchedule`, and groups operations +/// that do not depend on the root key schedule secret. +#[derive(Clone, Copy)] +struct KeyScheduleSuite { + side: Side, + protocol: Protocol, + suite: &'static Tls13CipherSuite, +} + +impl KeyScheduleSuite { + fn set_encrypter(&self, secret: &OkmBlock, send: &mut dyn SendOutput) { + let expander = self + .suite + .hkdf_provider + .expander_for_okm(secret); + let key = derive_traffic_key(expander.as_ref(), self.suite.aead_alg); + let iv = derive_traffic_iv(expander.as_ref(), self.suite.aead_alg.iv_len()); + + send.set_encrypter( + self.suite.aead_alg.encrypter(key, iv), + self.suite.common.confidentiality_limit, + ); + } + + fn set_decrypter( + &self, + secret: &OkmBlock, + receive: &mut ReceivePath, + proof: &HandshakeAlignedProof, + ) { + receive + .decrypt_state + .set_message_decrypter(self.derive_decrypter(secret), proof); + } + + fn derive_decrypter(&self, secret: &OkmBlock) -> Box { + let expander = self + .suite + .hkdf_provider + .expander_for_okm(secret); + let key = derive_traffic_key(expander.as_ref(), self.suite.aead_alg); + let iv = derive_traffic_iv(expander.as_ref(), self.suite.aead_alg.iv_len()); + self.suite.aead_alg.decrypter(key, iv) + } /// Sign the finished message consisting of `hs_hash` using a current /// traffic secret. - fn sign_finish(&self, base_key: &OkmBlock, hs_hash: &hash::Output) -> hmac::Tag { + /// + /// See RFC 8446 section 4.4.4. + fn sign_finish(&self, base_key: &OkmBlock, hs_hash: &hash::Output) -> hmac::PublicTag { self.sign_verify_data(base_key, hs_hash) } /// Sign the finished message consisting of `hs_hash` using the key material /// `base_key`. - fn sign_verify_data(&self, base_key: &OkmBlock, hs_hash: &hash::Output) -> hmac::Tag { + /// + /// See RFC 8446 section 4.4.4. + fn sign_verify_data(&self, base_key: &OkmBlock, hs_hash: &hash::Output) -> hmac::PublicTag { let expander = self .suite .hkdf_provider @@ -723,6 +986,8 @@ impl KeySchedule { self.suite .hkdf_provider .hmac_sign(&hmac_key, hs_hash.as_ref()) + // this is published in the handshake, in the Finished message or PSK binder + .into_public() } /// Derive the next application traffic secret, returning it. @@ -747,9 +1012,9 @@ impl KeySchedule { fn export_keying_material( &self, current_exporter_secret: &OkmBlock, - out: &mut [u8], label: &[u8], context: Option<&[u8]>, + out: &mut [u8], ) -> Result<(), Error> { let secret = { let h_empty = self @@ -776,25 +1041,25 @@ impl KeySchedule { .hkdf_provider .expander_for_okm(&secret); hkdf_expand_label_slice(expander.as_ref(), b"exporter", h_context.as_ref(), out) - .map_err(|_| Error::General("exporting too much".to_string())) + .map_err(|_| ApiMisuse::ExporterOutputTooLong.into()) } } /// [HKDF-Expand-Label] where the output is an AEAD key. /// /// [HKDF-Expand-Label]: -pub fn derive_traffic_key( +pub(crate) fn derive_traffic_key( expander: &dyn HkdfExpander, aead_alg: &dyn Tls13AeadAlgorithm, ) -> AeadKey { hkdf_expand_label_aead_key(expander, aead_alg.key_len(), b"key", &[]) } -/// [HKDF-Expand-Label] where the output is an IV. +/// [HKDF-Expand-Label] where the output is an IV with a specified length. /// /// [HKDF-Expand-Label]: -pub fn derive_traffic_iv(expander: &dyn HkdfExpander) -> Iv { - hkdf_expand_label(expander, b"iv", &[]) +pub(crate) fn derive_traffic_iv(expander: &dyn HkdfExpander, iv_len: usize) -> Iv { + hkdf_expand_label_iv(expander, b"iv", &[], iv_len) } /// [HKDF-Expand-Label] where the output length is a compile-time constant, and therefore @@ -828,8 +1093,22 @@ pub(crate) fn hkdf_expand_label_aead_key( context: &[u8], ) -> AeadKey { hkdf_expand_label_inner(expander, label, context, key_len, |e, info| { - let key: AeadKey = expand(e, info); - key.with_length(key_len) + expand::(e, info).with_length(key_len) + }) +} + +/// [HKDF-Expand-Label] where the output is an IV. +pub(crate) fn hkdf_expand_label_iv( + expander: &dyn HkdfExpander, + label: &[u8], + context: &[u8], + iv_len: usize, +) -> Iv { + hkdf_expand_label_inner(expander, label, context, iv_len, |e, info| { + let mut buf = [0u8; Iv::MAX_LEN]; + e.expand_slice(info, &mut buf[..iv_len]) + .unwrap(); + Iv::new(&buf[..iv_len]).expect("IV length from cipher suite must be within MAX_LEN") }) } @@ -853,8 +1132,8 @@ pub(crate) fn server_ech_hrr_confirmation_secret( hs_hash: hash::Output, ) -> [u8; 8] { /* - Per ietf-tls-esni-17 section 7.2.1: - + Per RFC 9849 section 7.2.1: + hrr_accept_confirmation = HKDF-Expand-Label( HKDF-Extract(0, ClientHelloInner1.random), "hrr ech accept confirmation", @@ -898,19 +1177,68 @@ where f(expander, info) } -#[cfg(test)] -#[macro_rules_attribute::apply(test_for_each_provider)] +/// The kinds of secret we can extract from `KeySchedule`. +#[derive(Debug, Clone, Copy, PartialEq)] +enum SecretKind { + ResumptionPskBinderKey, + ClientEarlyTrafficSecret, + EarlyExporterMasterSecret, + ClientHandshakeTrafficSecret, + ServerHandshakeTrafficSecret, + ClientApplicationTrafficSecret, + ServerApplicationTrafficSecret, + ExporterMasterSecret, + ResumptionMasterSecret, + DerivedSecret, + ServerEchConfirmationSecret, + ServerEchHrrConfirmationSecret, +} + +impl SecretKind { + fn to_bytes(self) -> &'static [u8] { + use self::SecretKind::*; + match self { + ResumptionPskBinderKey => b"res binder", + ClientEarlyTrafficSecret => b"c e traffic", + EarlyExporterMasterSecret => b"e exp master", + ClientHandshakeTrafficSecret => b"c hs traffic", + ServerHandshakeTrafficSecret => b"s hs traffic", + ClientApplicationTrafficSecret => b"c ap traffic", + ServerApplicationTrafficSecret => b"s ap traffic", + ExporterMasterSecret => b"exp master", + ResumptionMasterSecret => b"res master", + DerivedSecret => b"derived", + // https://datatracker.ietf.org/doc/html/rfc9849#section-7.2 + ServerEchConfirmationSecret => b"ech accept confirmation", + // https://datatracker.ietf.org/doc/html/rfc9849#section-7.2.1 + ServerEchHrrConfirmationSecret => b"hrr ech accept confirmation", + } + } + + fn log_label(self) -> Option<&'static str> { + use self::SecretKind::*; + Some(match self { + ClientEarlyTrafficSecret => "CLIENT_EARLY_TRAFFIC_SECRET", + EarlyExporterMasterSecret => "EARLY_EXPORTER_SECRET", + ClientHandshakeTrafficSecret => "CLIENT_HANDSHAKE_TRAFFIC_SECRET", + ServerHandshakeTrafficSecret => "SERVER_HANDSHAKE_TRAFFIC_SECRET", + ClientApplicationTrafficSecret => "CLIENT_TRAFFIC_SECRET_0", + ServerApplicationTrafficSecret => "SERVER_TRAFFIC_SECRET_0", + ExporterMasterSecret => "EXPORTER_SECRET", + _ => { + return None; + } + }) + } +} + +#[cfg(all(test, any(target_arch = "aarch64", target_arch = "x86_64")))] mod tests { use core::fmt::Debug; - use std::prelude::v1::*; - use std::vec; - use super::provider::ring_like::aead; - use super::provider::tls13::{ - TLS13_AES_128_GCM_SHA256_INTERNAL, TLS13_CHACHA20_POLY1305_SHA256_INTERNAL, - }; - use super::{derive_traffic_iv, derive_traffic_key, KeySchedule, SecretKind}; - use crate::KeyLog; + use super::*; + use crate::crypto::TLS13_TEST_SUITE; + use crate::key_log::KeyLog; #[test] fn test_vectors() { @@ -993,7 +1321,8 @@ mod tests { 0x0d, 0xb2, 0x8f, 0x98, 0x85, 0x86, 0xa1, 0xb7, 0xe4, 0xd5, 0xc6, 0x9c, ]; - let mut ks = KeySchedule::new_with_empty_secret(TLS13_CHACHA20_POLY1305_SHA256_INTERNAL); + let suite = TLS13_TEST_SUITE; + let mut ks = KeySchedule::new_with_empty_secret(Side::Server, Protocol::Tcp, suite); ks.input_secret(&ecdhe_secret); assert_traffic_secret( @@ -1003,6 +1332,7 @@ mod tests { &client_hts, &client_hts_key, &client_hts_iv, + suite, ); assert_traffic_secret( @@ -1012,6 +1342,7 @@ mod tests { &server_hts, &server_hts_key, &server_hts_iv, + suite, ); ks.input_empty(); @@ -1023,6 +1354,7 @@ mod tests { &client_ats, &client_ats_key, &client_ats_iv, + suite, ); assert_traffic_secret( @@ -1032,9 +1364,11 @@ mod tests { &server_ats, &server_ats_key, &server_ats_iv, + suite, ); } + #[track_caller] fn assert_traffic_secret( ks: &KeySchedule, kind: SecretKind, @@ -1042,60 +1376,43 @@ mod tests { expected_traffic_secret: &[u8], expected_key: &[u8], expected_iv: &[u8], + suite: &Tls13CipherSuite, ) { - #[derive(Debug)] - struct Log<'a>(&'a [u8]); - impl KeyLog for Log<'_> { - fn log(&self, _label: &str, _client_random: &[u8], secret: &[u8]) { - assert_eq!(self.0, secret); - } - } let log = Log(expected_traffic_secret); let traffic_secret = ks.derive_logged_secret(kind, hash, &log, &[0; 32]); // Since we can't test key equality, we test the output of sealing with the key instead. - let aead_alg = &aead::AES_128_GCM; - let expander = TLS13_AES_128_GCM_SHA256_INTERNAL + let expander = suite .hkdf_provider .expander_for_okm(&traffic_secret); - let key = derive_traffic_key( - expander.as_ref(), - TLS13_AES_128_GCM_SHA256_INTERNAL.aead_alg, - ); - let key = aead::UnboundKey::new(aead_alg, key.as_ref()).unwrap(); - let seal_output = seal_zeroes(key); - let expected_key = aead::UnboundKey::new(aead_alg, expected_key).unwrap(); - let expected_seal_output = seal_zeroes(expected_key); - assert_eq!(seal_output, expected_seal_output); - assert!(seal_output.len() >= 48); // Sanity check. - - let iv = derive_traffic_iv(expander.as_ref()); - assert_eq!(iv.as_ref(), expected_iv); - } - - fn seal_zeroes(key: aead::UnboundKey) -> Vec { - let key = aead::LessSafeKey::new(key); - let mut seal_output = vec![0; 32]; - key.seal_in_place_append_tag( - aead::Nonce::assume_unique_for_key([0; aead::NONCE_LEN]), - aead::Aad::empty(), - &mut seal_output, - ) - .unwrap(); - seal_output + + let actual_key = derive_traffic_key(expander.as_ref(), suite.aead_alg); + assert_eq!(actual_key.as_ref(), expected_key); + let actual_iv = derive_traffic_iv(expander.as_ref(), suite.aead_alg.iv_len()); + assert_eq!(actual_iv.as_ref(), expected_iv); + } + + #[derive(Debug)] + struct Log<'a>(&'a [u8]); + + impl KeyLog for Log<'_> { + fn log(&self, _label: &str, _client_random: &[u8], secret: &[u8]) { + assert_eq!(self.0, secret); + } } } #[cfg(all(test, bench))] -#[macro_rules_attribute::apply(bench_for_each_provider)] mod benchmarks { #[bench] fn bench_sha256(b: &mut test::Bencher) { use core::fmt::Debug; - use super::provider::tls13::TLS13_CHACHA20_POLY1305_SHA256_INTERNAL; - use super::{derive_traffic_iv, derive_traffic_key, KeySchedule, SecretKind}; + use super::{ + KeySchedule, Protocol, SecretKind, Side, derive_traffic_iv, derive_traffic_key, + }; use crate::KeyLog; + use crate::crypto::test_provider::TLS13_TEST_SUITE; fn extract_traffic_secret(ks: &KeySchedule, kind: SecretKind) { #[derive(Debug)] @@ -1107,19 +1424,22 @@ mod benchmarks { let hash = [0u8; 32]; let traffic_secret = ks.derive_logged_secret(kind, &hash, &Log, &[0u8; 32]); - let traffic_secret_expander = TLS13_CHACHA20_POLY1305_SHA256_INTERNAL + let traffic_secret_expander = TLS13_TEST_SUITE .hkdf_provider .expander_for_okm(&traffic_secret); test::black_box(derive_traffic_key( traffic_secret_expander.as_ref(), - TLS13_CHACHA20_POLY1305_SHA256_INTERNAL.aead_alg, + TLS13_TEST_SUITE.aead_alg, + )); + test::black_box(derive_traffic_iv( + traffic_secret_expander.as_ref(), + TLS13_TEST_SUITE.aead_alg.iv_len(), )); - test::black_box(derive_traffic_iv(traffic_secret_expander.as_ref())); } b.iter(|| { let mut ks = - KeySchedule::new_with_empty_secret(TLS13_CHACHA20_POLY1305_SHA256_INTERNAL); + KeySchedule::new_with_empty_secret(Side::Client, Protocol::Tcp, TLS13_TEST_SUITE); ks.input_secret(&[0u8; 32]); extract_traffic_secret(&ks, SecretKind::ClientHandshakeTrafficSecret); diff --git a/rustls/src/tls13/mod.rs b/rustls/src/tls13/mod.rs index ab55c889613..4193d9b3d55 100644 --- a/rustls/src/tls13/mod.rs +++ b/rustls/src/tls13/mod.rs @@ -1,16 +1,33 @@ use core::fmt; -use crate::crypto; -use crate::crypto::hash; -use crate::suites::{CipherSuiteCommon, SupportedCipherSuite}; +use pki_types::FipsStatus; + +use crate::common_state::Protocol; +use crate::crypto::{self, SignatureScheme, hash}; +use crate::enums::ProtocolVersion; +use crate::suites::{CipherSuiteCommon, Suite, SupportedCipherSuite}; +use crate::version::Tls13Version; pub(crate) mod key_schedule; /// A TLS 1.3 cipher suite supported by rustls. +#[expect(clippy::exhaustive_structs)] pub struct Tls13CipherSuite { /// Common cipher suite fields. pub common: CipherSuiteCommon, + /// The associated protocol version. + /// + /// This field should have the value [`rustls::version::TLS13_VERSION`]. + /// + /// This value contains references to the TLS1.3 protocol handling code. + /// This means that a program that does not contain any `Tls13CipherSuite` + /// values also does not contain any reference to the TLS1.3 protocol handling + /// code, and the linker can remove it. + /// + /// [`rustls::version::TLS13_VERSION`]: crate::version::TLS13_VERSION + pub protocol_version: &'static Tls13Version, + /// How to complete HKDF with the suite's hash function. /// /// If you have a HKDF implementation, you should directly implement the `crypto::tls13::Hkdf` @@ -42,27 +59,61 @@ impl Tls13CipherSuite { .then_some(prev) } - /// Return `true` if this is backed by a FIPS-approved implementation. + /// Return the FIPS validation status of this implementation. /// - /// This means all the constituent parts that do cryptography return `true` for `fips()`. - pub fn fips(&self) -> bool { + /// This is the combination of the constituent parts of the cipher suite. + pub fn fips(&self) -> FipsStatus { let Self { common, + protocol_version: _, hkdf_provider, aead_alg, quic, } = self; - common.fips() - && hkdf_provider.fips() - && aead_alg.fips() - && quic.map(|q| q.fips()).unwrap_or(true) + + let mut status = Ord::min(common.fips(), hkdf_provider.fips()); + status = Ord::min(status, aead_alg.fips()); + match quic { + Some(quic) => Ord::min(status, quic.fips()), + None => status, + } } /// Returns a `quic::Suite` for the ciphersuite, if supported. pub fn quic_suite(&'static self) -> Option { self.quic - .map(|quic| crate::quic::Suite { quic, suite: self }) + .map(|quic| crate::quic::Suite { suite: self, quic }) + } +} + +impl Suite for Tls13CipherSuite { + fn client_handler(&self) -> &'static dyn crate::client::ClientHandler { + self.protocol_version.client + } + + fn server_handler(&self) -> &'static dyn crate::server::ServerHandler { + self.protocol_version.server } + + /// Does this suite support the `proto` protocol? + /// + /// All TLS1.3 suites support TCP-TLS. QUIC support is conditional on `quic` slot. + fn usable_for_protocol(&self, proto: Protocol) -> bool { + match proto { + Protocol::Tcp => true, + Protocol::Quic(_) => self.quic.is_some(), + } + } + + fn usable_for_signature_scheme(&self, scheme: SignatureScheme) -> bool { + scheme.supported_in_tls13() + } + + fn common(&self) -> &CipherSuiteCommon { + &self.common + } + + const VERSION: ProtocolVersion = ProtocolVersion::TLSv1_3; } impl From<&'static Tls13CipherSuite> for SupportedCipherSuite { @@ -81,7 +132,7 @@ impl fmt::Debug for Tls13CipherSuite { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Tls13CipherSuite") .field("suite", &self.common.suite) - .finish() + .finish_non_exhaustive() } } @@ -123,3 +174,33 @@ impl AsRef<[u8]> for VerifyMessage { const SERVER_CONSTANT: &[u8; 34] = b"TLS 1.3, server CertificateVerify\x00"; const CLIENT_CONSTANT: &[u8; 34] = b"TLS 1.3, client CertificateVerify\x00"; const MAX_VERIFY_MSG: usize = 64 + CLIENT_CONSTANT.len() + hash::Output::MAX_LEN; + +#[cfg(test)] +mod tests { + use crate::crypto::{CipherSuite, TEST_PROVIDER, tls13_suite}; + + #[test] + fn test_can_resume_to() { + let Some(cha_poly) = TEST_PROVIDER + .tls13_cipher_suites + .iter() + .find(|cs| cs.common.suite == CipherSuite::TLS13_CHACHA20_POLY1305_SHA256) + else { + return; + }; + + let aes_128_gcm = tls13_suite(CipherSuite::TLS13_AES_128_GCM_SHA256, &TEST_PROVIDER); + assert!( + aes_128_gcm + .can_resume_from(cha_poly) + .is_some() + ); + + let aes_256_gcm = tls13_suite(CipherSuite::TLS13_AES_256_GCM_SHA384, &TEST_PROVIDER); + assert!( + aes_256_gcm + .can_resume_from(cha_poly) + .is_none() + ); + } +} diff --git a/rustls/src/vecbuf.rs b/rustls/src/vecbuf.rs index 813b99f4dce..6bd026761db 100644 --- a/rustls/src/vecbuf.rs +++ b/rustls/src/vecbuf.rs @@ -1,13 +1,10 @@ use alloc::collections::VecDeque; use alloc::vec::Vec; -use core::{cmp, mem}; -#[cfg(feature = "std")] +use core::mem; use std::io; -#[cfg(feature = "std")] use std::io::Read; -#[cfg(feature = "std")] -use crate::msgs::message::OutboundChunks; +use crate::crypto::cipher::OutboundPlain; /// This is a byte buffer that is built from a deque of byte vectors. /// @@ -59,23 +56,15 @@ impl ChunkVecBuffer { - self.prefix_used } - /// For a proposed append of `len` bytes, how many - /// bytes should we actually append to adhere to the - /// currently set `limit`? - pub(crate) fn apply_limit(&self, len: usize) -> usize { - if let Some(limit) = self.limit { - let space = limit.saturating_sub(self.len()); - cmp::min(len, space) - } else { - len - } - } - /// Take and append the given `bytes`. pub(crate) fn append(&mut self, bytes: Vec) -> usize { let len = bytes.len(); if !bytes.is_empty() { + if self.chunks.is_empty() { + debug_assert_eq!(self.prefix_used, 0); + } + self.chunks.push_back(bytes); } @@ -97,36 +86,60 @@ impl ChunkVecBuffer { first } - #[cfg(read_buf)] - /// Read data out of this object, writing it into `cursor`. - pub(crate) fn read_buf(&mut self, mut cursor: core::io::BorrowedCursor<'_>) -> io::Result<()> { - while !self.is_empty() && cursor.capacity() > 0 { - let chunk = &self.chunks[0][self.prefix_used..]; - let used = cmp::min(chunk.len(), cursor.capacity()); - cursor.append(&chunk[..used]); - self.consume(used); + /// Inspect the first chunk from this object. + pub(crate) fn peek(&self) -> Option<&[u8]> { + self.chunks + .front() + .map(|ch| ch.as_slice()) + } + + pub(crate) fn take(&mut self) -> Vec> { + if self.chunks.is_empty() { + return Vec::new(); + } + mem::take(&mut self.chunks).into() + } + + pub(crate) fn take_one_vec(&mut self) -> Vec { + let Some(mut first) = self.chunks.pop_front() else { + return Vec::new(); + }; + + while let Some(chunk) = self.chunks.pop_front() { + first.extend_from_slice(&chunk); } - Ok(()) + first } } -#[cfg(feature = "std")] impl ChunkVecBuffer { pub(crate) fn is_full(&self) -> bool { self.limit - .map(|limit| self.len() > limit) + .map(|limit| self.len() >= limit) .unwrap_or_default() } /// Append a copy of `bytes`, perhaps a prefix if /// we're near the limit. - pub(crate) fn append_limited_copy(&mut self, payload: OutboundChunks<'_>) -> usize { + pub(crate) fn append_limited_copy(&mut self, payload: OutboundPlain<'_>) -> usize { let take = self.apply_limit(payload.len()); self.append(payload.split_at(take).0.to_vec()); take } + /// For a proposed append of `len` bytes, how many + /// bytes should we actually append to adhere to the + /// currently set `limit`? + pub(crate) fn apply_limit(&self, len: usize) -> usize { + let Some(limit) = self.limit else { + return len; + }; + + let space = limit.saturating_sub(self.len()); + Ord::min(len, space) + } + /// Read data out of this object, writing it into `buf` /// and returning how many bytes were written there. pub(crate) fn read(&mut self, buf: &mut [u8]) -> io::Result { @@ -142,6 +155,19 @@ impl ChunkVecBuffer { Ok(offs) } + pub(crate) fn consume_first_chunk(&mut self, used: usize) { + // this backs (infallible) `BufRead::consume`, where `used` is + // user-supplied. + assert!( + used <= self + .chunk() + .map(|ch| ch.len()) + .unwrap_or_default(), + "illegal `BufRead::consume` usage", + ); + self.consume(used); + } + fn consume(&mut self, used: usize) { // first, mark the rightmost extent of the used buffer self.prefix_used += used; @@ -150,12 +176,17 @@ impl ChunkVecBuffer { // buffers while let Some(buf) = self.chunks.front() { if self.prefix_used < buf.len() { - break; + return; } else { self.prefix_used -= buf.len(); self.chunks.pop_front(); } } + + debug_assert_eq!( + self.prefix_used, 0, + "attempted to `ChunkVecBuffer::consume` more than available" + ); } /// Read data out of this object, passing it `wr` @@ -170,14 +201,34 @@ impl ChunkVecBuffer { *iov = io::IoSlice::new(&chunk[prefix..]); prefix = 0; } - let len = cmp::min(bufs.len(), self.chunks.len()); - let used = wr.write_vectored(&bufs[..len])?; + let len = Ord::min(bufs.len(), self.chunks.len()); + let bufs = &bufs[..len]; + let used = wr.write_vectored(bufs)?; + let available_bytes = bufs.iter().map(|ch| ch.len()).sum(); + + if used > available_bytes { + // This is really unrecoverable, since the amount of data written + // is now unknown. Consume all the potentially-written data in + // case the caller ignores the error. + // See for background. + self.consume(available_bytes); + return Err(io::Error::other(std::format!( + "illegal write_vectored return value ({used} > {available_bytes})" + ))); + } self.consume(used); Ok(used) } + + /// Returns the first contiguous chunk of data, or None if empty. + pub(crate) fn chunk(&self) -> Option<&[u8]> { + self.chunks + .front() + .map(|chunk| &chunk[self.prefix_used..]) + } } -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod tests { use alloc::vec; use alloc::vec::Vec; @@ -238,44 +289,9 @@ mod tests { } } } - - #[cfg(read_buf)] - #[test] - fn read_buf() { - use core::io::BorrowedBuf; - use core::mem::MaybeUninit; - - { - let mut cvb = ChunkVecBuffer::new(None); - cvb.append(b"test ".to_vec()); - cvb.append(b"fixture ".to_vec()); - cvb.append(b"data".to_vec()); - - let mut buf = [MaybeUninit::::uninit(); 8]; - let mut buf: BorrowedBuf<'_> = buf.as_mut_slice().into(); - cvb.read_buf(buf.unfilled()).unwrap(); - assert_eq!(buf.filled(), b"test fix"); - buf.clear(); - cvb.read_buf(buf.unfilled()).unwrap(); - assert_eq!(buf.filled(), b"ture dat"); - buf.clear(); - cvb.read_buf(buf.unfilled()).unwrap(); - assert_eq!(buf.filled(), b"a"); - } - - { - let mut cvb = ChunkVecBuffer::new(None); - cvb.append(b"short message".to_vec()); - - let mut buf = [MaybeUninit::::uninit(); 1024]; - let mut buf: BorrowedBuf<'_> = buf.as_mut_slice().into(); - cvb.read_buf(buf.unfilled()).unwrap(); - assert_eq!(buf.filled(), b"short message"); - } - } } -#[cfg(bench)] +#[cfg(all(test, bench))] mod benchmarks { use alloc::vec; diff --git a/rustls/src/verify.rs b/rustls/src/verify.rs index ce07e3a0eb0..c2e3b0dfb4d 100644 --- a/rustls/src/verify.rs +++ b/rustls/src/verify.rs @@ -1,13 +1,16 @@ use alloc::vec::Vec; use core::fmt::Debug; +use core::hash::Hasher; -use pki_types::{CertificateDer, ServerName, UnixTime}; +use pki_types::{CertificateDer, ServerName, SubjectPublicKeyInfoDer, UnixTime}; -use crate::enums::SignatureScheme; +use crate::crypto::cipher::Payload; +use crate::crypto::{Identity, SignatureScheme}; +use crate::enums::CertificateType; use crate::error::{Error, InvalidMessage}; -use crate::msgs::base::PayloadU16; -use crate::msgs::codec::{Codec, Reader}; -use crate::msgs::handshake::DistinguishedName; +use crate::msgs::{Codec, ListLength, MaybeEmpty, NonEmpty, Reader, SizedPayload, TlsListElement}; +use crate::sync::Arc; +use crate::x509::wrap_in_sequence; // Marker types. These are used to bind the fact some verification // (certificate chain or handshake signature) has taken place into @@ -19,83 +22,21 @@ use crate::msgs::handshake::DistinguishedName; // means their origins can be precisely determined by looking // for their `assertion` constructors. -/// Zero-sized marker type representing verification of a signature. -#[derive(Debug)] -pub struct HandshakeSignatureValid(()); - -impl HandshakeSignatureValid { - /// Make a `HandshakeSignatureValid` - pub fn assertion() -> Self { - Self(()) - } -} - -#[derive(Debug)] -pub(crate) struct FinishedMessageVerified(()); - -impl FinishedMessageVerified { - pub(crate) fn assertion() -> Self { - Self(()) - } -} - -/// Zero-sized marker type representing verification of a server cert chain. -#[allow(unreachable_pub)] -#[derive(Debug)] -pub struct ServerCertVerified(()); - -#[allow(unreachable_pub)] -impl ServerCertVerified { - /// Make a `ServerCertVerified` - pub fn assertion() -> Self { - Self(()) - } -} - -/// Zero-sized marker type representing verification of a client cert chain. -#[derive(Debug)] -pub struct ClientCertVerified(()); - -impl ClientCertVerified { - /// Make a `ClientCertVerified` - pub fn assertion() -> Self { - Self(()) - } -} - /// Something that can verify a server certificate chain, and verify /// signatures made by certificates. -#[allow(unreachable_pub)] -pub trait ServerCertVerifier: Debug + Send + Sync { - /// Verify the end-entity certificate `end_entity` is valid for the - /// hostname `dns_name` and chains to at least one trust anchor. - /// - /// `intermediates` contains all certificates other than `end_entity` that - /// were sent as part of the server's [Certificate] message. It is in the - /// same order that the server sent them and may be empty. +pub trait ServerVerifier: Debug + Send + Sync { + /// Verify the server's identity. /// /// Note that none of the certificates have been parsed yet, so it is the responsibility of /// the implementer to handle invalid data. It is recommended that the implementer returns - /// [`Error::InvalidCertificate(CertificateError::BadEncoding)`] when these cases are encountered. + /// [`Error::InvalidCertificate`] containing [`CertificateError::BadEncoding`] when these cases are encountered. /// /// [Certificate]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.2 - fn verify_server_cert( - &self, - end_entity: &CertificateDer<'_>, - intermediates: &[CertificateDer<'_>], - server_name: &ServerName<'_>, - ocsp_response: &[u8], - now: UnixTime, - ) -> Result; + /// [`CertificateError::BadEncoding`]: crate::error::CertificateError::BadEncoding + fn verify_identity(&self, identity: &ServerIdentity<'_>) -> Result; /// Verify a signature allegedly by the given server certificate. /// - /// `message` is not hashed, and needs hashing during the verification. - /// The signature and algorithm are within `dss`. `cert` contains the - /// public key to use. - /// - /// `cert` has already been validated by [`ServerCertVerifier::verify_server_cert`]. - /// /// If and only if the signature is valid, return `Ok(HandshakeSignatureValid)`. /// Otherwise, return an error -- rustls will send an alert and abort the /// connection. @@ -105,9 +46,7 @@ pub trait ServerCertVerifier: Debug + Send + Sync { /// in fact bound to the specific curve implied in their name. fn verify_tls12_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result; /// Verify a signature allegedly by the given server certificate. @@ -119,16 +58,12 @@ pub trait ServerCertVerifier: Debug + Send + Sync { /// must only validate signatures using public keys on the right curve -- /// rustls does not enforce this requirement for you. /// - /// `cert` has already been validated by [`ServerCertVerifier::verify_server_cert`]. - /// /// If and only if the signature is valid, return `Ok(HandshakeSignatureValid)`. /// Otherwise, return an error -- rustls will send an alert and abort the /// connection. fn verify_tls13_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result; /// Return the list of SignatureSchemes that this verifier will handle, @@ -137,10 +72,20 @@ pub trait ServerCertVerifier: Debug + Send + Sync { /// This should be in priority order, with the most preferred first. fn supported_verify_schemes(&self) -> Vec; - /// Returns whether this verifier requires raw public keys as defined - /// in [RFC 7250](https://tools.ietf.org/html/rfc7250). - fn requires_raw_public_keys(&self) -> bool { - false + /// Return true if this verifier will process stapled OCSP responses. + /// + /// This controls whether a client will ask the server for a stapled OCSP response. + /// There is no guarantee the server will provide one. + fn request_ocsp_response(&self) -> bool; + + /// Returns which [`CertificateType`]s this verifier supports. + /// + /// Returning an empty slice will result in an error. The default implementation signals + /// support for X.509 certificates. Implementations should return the same value every time. + /// + /// See [RFC 7250](https://tools.ietf.org/html/rfc7250) for more information. + fn supported_certificate_types(&self) -> &'static [CertificateType] { + &[CertificateType::X509] } /// Return the [`DistinguishedName`]s of certificate authorities that this verifier trusts. @@ -149,87 +94,56 @@ pub trait ServerCertVerifier: Debug + Send + Sync { /// Note that this is only applicable to TLS 1.3. /// /// [`certificate_authorities`]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.4 - fn root_hint_subjects(&self) -> Option<&[DistinguishedName]> { + fn root_hint_subjects(&self) -> Option> { None } + + /// Instance configuration should be input to `h`. + fn hash_config(&self, h: &mut dyn Hasher); } -/// Something that can verify a client certificate chain -#[allow(unreachable_pub)] -pub trait ClientCertVerifier: Debug + Send + Sync { - /// Returns `true` to enable the server to request a client certificate and - /// `false` to skip requesting a client certificate. Defaults to `true`. - fn offer_client_auth(&self) -> bool { - true - } +/// Data required to verify a server's identity. +#[non_exhaustive] +#[derive(Debug)] +pub struct ServerIdentity<'a> { + /// Identity information presented by the server. + pub identity: &'a Identity<'a>, + /// The server name the client specified when connecting to the server. + pub server_name: &'a ServerName<'a>, + /// OCSP response stapled to the server's `Certificate` message, if any. + /// + /// Empty if no OCSP response was received, and that also + /// covers the case where `request_ocsp_response()` returns false. + pub ocsp_response: &'a [u8], + /// Current time against which time-sensitive inputs should be validated. + pub now: UnixTime, +} - /// Return `true` to require a client certificate and `false` to make - /// client authentication optional. - /// Defaults to `self.offer_client_auth()`. - fn client_auth_mandatory(&self) -> bool { - self.offer_client_auth() +impl<'a> ServerIdentity<'a> { + /// Create a new `ServerIdentity` instance with empty OCSP response. + pub fn new(identity: &'a Identity<'a>, server_name: &'a ServerName<'a>, now: UnixTime) -> Self { + Self { + identity, + server_name, + ocsp_response: &[], + now, + } } +} - /// Returns the [`DistinguishedName`] [subjects] that the server will hint to clients to - /// identify acceptable authentication trust anchors. - /// - /// These hint values help the client pick a client certificate it believes the server will - /// accept. The hints must be DER-encoded X.500 distinguished names, per [RFC 5280 A.1]. They - /// are sent in the [`certificate_authorities`] extension of a [`CertificateRequest`] message - /// when [ClientCertVerifier::offer_client_auth] is true. When an empty list is sent the client - /// should always provide a client certificate if it has one. - /// - /// Generally this list should contain the [`DistinguishedName`] of each root trust - /// anchor in the root cert store that the server is configured to use for authenticating - /// presented client certificates. - /// - /// In some circumstances this list may be customized to include [`DistinguishedName`] entries - /// that do not correspond to a trust anchor in the server's root cert store. For example, - /// the server may be configured to trust a root CA that cross-signed an issuer certificate - /// that the client considers a trust anchor. From the server's perspective the cross-signed - /// certificate is an intermediate, and not present in the server's root cert store. The client - /// may have the cross-signed certificate configured as a trust anchor, and be unaware of the - /// root CA that cross-signed it. If the server's hints list only contained the subjects of the - /// server's root store the client would consider a client certificate issued by the cross-signed - /// issuer unacceptable, since its subject was not hinted. To avoid this circumstance the server - /// should customize the hints list to include the subject of the cross-signed issuer in addition - /// to the subjects from the root cert store. - /// - /// [subjects]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6 - /// [RFC 5280 A.1]: https://www.rfc-editor.org/rfc/rfc5280#appendix-A.1 - /// [`CertificateRequest`]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.3.2 - /// [`certificate_authorities`]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.4 - fn root_hint_subjects(&self) -> &[DistinguishedName]; - - /// Verify the end-entity certificate `end_entity` is valid, acceptable, - /// and chains to at least one of the trust anchors trusted by - /// this verifier. - /// - /// `intermediates` contains the intermediate certificates the - /// client sent along with the end-entity certificate; it is in the same - /// order that the peer sent them and may be empty. +/// Something that can verify a client certificate chain +pub trait ClientVerifier: Debug + Send + Sync { + /// Verify the client's identity. /// /// Note that none of the certificates have been parsed yet, so it is the responsibility of /// the implementer to handle invalid data. It is recommended that the implementer returns - /// an [InvalidCertificate] error with the [BadEncoding] variant when these cases are encountered. + /// a [`CertificateError::BadEncoding`] error when these cases are encountered. /// - /// [InvalidCertificate]: Error#variant.InvalidCertificate - /// [BadEncoding]: crate::CertificateError#variant.BadEncoding - fn verify_client_cert( - &self, - end_entity: &CertificateDer<'_>, - intermediates: &[CertificateDer<'_>], - now: UnixTime, - ) -> Result; + /// [`CertificateError::BadEncoding`]: crate::error::CertificateError::BadEncoding + fn verify_identity(&self, identity: &ClientIdentity<'_>) -> Result; /// Verify a signature allegedly by the given client certificate. /// - /// `message` is not hashed, and needs hashing during the verification. - /// The signature and algorithm are within `dss`. `cert` contains the - /// public key to use. - /// - /// `cert` has already been validated by [`ClientCertVerifier::verify_client_cert`]. - /// /// If and only if the signature is valid, return `Ok(HandshakeSignatureValid)`. /// Otherwise, return an error -- rustls will send an alert and abort the /// connection. @@ -239,9 +153,7 @@ pub trait ClientCertVerifier: Debug + Send + Sync { /// in fact bound to the specific curve implied in their name. fn verify_tls12_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result; /// Verify a signature allegedly by the given client certificate. @@ -255,69 +167,143 @@ pub trait ClientCertVerifier: Debug + Send + Sync { /// rustls does not enforce this requirement for you. fn verify_tls13_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result; + /// Returns the [`DistinguishedName`] [subjects] that the server will hint to clients to + /// identify acceptable authentication trust anchors. + /// + /// These hint values help the client pick a client certificate it believes the server will + /// accept. The hints must be DER-encoded X.500 distinguished names, per [RFC 5280 A.1]. They + /// are sent in the [`certificate_authorities`] extension of a [`CertificateRequest`] message + /// when [ClientVerifier::offer_client_auth] is true. When an empty list is sent the client + /// should always provide a client certificate if it has one. + /// + /// Generally this list should contain the [`DistinguishedName`] of each root trust + /// anchor in the root cert store that the server is configured to use for authenticating + /// presented client certificates. + /// + /// In some circumstances this list may be customized to include [`DistinguishedName`] entries + /// that do not correspond to a trust anchor in the server's root cert store. For example, + /// the server may be configured to trust a root CA that cross-signed an issuer certificate + /// that the client considers a trust anchor. From the server's perspective the cross-signed + /// certificate is an intermediate, and not present in the server's root cert store. The client + /// may have the cross-signed certificate configured as a trust anchor, and be unaware of the + /// root CA that cross-signed it. If the server's hints list only contained the subjects of the + /// server's root store the client would consider a client certificate issued by the cross-signed + /// issuer unacceptable, since its subject was not hinted. To avoid this circumstance the server + /// should customize the hints list to include the subject of the cross-signed issuer in addition + /// to the subjects from the root cert store. + /// + /// [subjects]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6 + /// [RFC 5280 A.1]: https://www.rfc-editor.org/rfc/rfc5280#appendix-A.1 + /// [`CertificateRequest`]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.3.2 + /// [`certificate_authorities`]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.4 + fn root_hint_subjects(&self) -> Arc<[DistinguishedName]>; + + /// Return `true` to require a client certificate and `false` to make + /// client authentication optional. + /// Defaults to `self.offer_client_auth()`. + fn client_auth_mandatory(&self) -> bool { + self.offer_client_auth() + } + + /// Returns `true` to enable the server to request a client certificate and + /// `false` to skip requesting a client certificate. Defaults to `true`. + fn offer_client_auth(&self) -> bool { + true + } + /// Return the list of SignatureSchemes that this verifier will handle, /// in `verify_tls12_signature` and `verify_tls13_signature` calls. /// /// This should be in priority order, with the most preferred first. fn supported_verify_schemes(&self) -> Vec; - /// Returns whether this verifier requires raw public keys as defined - /// in [RFC 7250](https://tools.ietf.org/html/rfc7250). - fn requires_raw_public_keys(&self) -> bool { - false + /// Returns which [`CertificateType`]s this verifier supports. + /// + /// Returning an empty slice will result in an error. The default implementation signals + /// support for X.509 certificates. Implementations should return the same value every time. + /// + /// See [RFC 7250](https://tools.ietf.org/html/rfc7250) for more information. + fn supported_certificate_types(&self) -> &'static [CertificateType] { + &[CertificateType::X509] } } +/// Data required to verify a client's identity. +#[non_exhaustive] +#[derive(Debug)] +pub struct ClientIdentity<'a> { + /// Identity information presented by the client. + pub identity: &'a Identity<'a>, + /// Current time against which time-sensitive inputs should be validated. + pub now: UnixTime, +} + +/// Input for message signature verification. +#[non_exhaustive] +#[derive(Debug)] +pub struct SignatureVerificationInput<'a> { + /// The message is not hashed, and needs hashing during verification. + pub message: &'a [u8], + /// The public key to use. + /// + /// `signer` has already been validated by the point this is called. + pub signer: &'a SignerPublicKey<'a>, + /// The signature scheme and payload. + pub signature: &'a DigitallySignedStruct, +} + +/// Public key used to verify a signature. +/// +/// Used as part of [`SignatureVerificationInput`]. +#[non_exhaustive] +#[derive(Debug)] +pub enum SignerPublicKey<'a> { + /// An X.509 certificate for the signing peer. + X509(&'a CertificateDer<'a>), + /// A raw public key, as defined in [RFC 7250](https://tools.ietf.org/html/rfc7250). + RawPublicKey(&'a SubjectPublicKeyInfoDer<'a>), +} + /// Turns off client authentication. /// /// In contrast to using /// `WebPkiClientVerifier::builder(roots).allow_unauthenticated().build()`, the `NoClientAuth` -/// `ClientCertVerifier` will not offer client authentication at all, vs offering but not +/// `ClientVerifier` will not offer client authentication at all, vs offering but not /// requiring it. +#[expect(clippy::exhaustive_structs)] #[derive(Debug)] pub struct NoClientAuth; -impl ClientCertVerifier for NoClientAuth { - fn offer_client_auth(&self) -> bool { - false - } - - fn root_hint_subjects(&self) -> &[DistinguishedName] { - unimplemented!(); - } - - fn verify_client_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _now: UnixTime, - ) -> Result { +impl ClientVerifier for NoClientAuth { + fn verify_identity(&self, _identity: &ClientIdentity<'_>) -> Result { unimplemented!(); } fn verify_tls12_signature( &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &DigitallySignedStruct, + _input: &SignatureVerificationInput<'_>, ) -> Result { unimplemented!(); } fn verify_tls13_signature( &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &DigitallySignedStruct, + _input: &SignatureVerificationInput<'_>, ) -> Result { unimplemented!(); } + fn root_hint_subjects(&self) -> Arc<[DistinguishedName]> { + unimplemented!(); + } + + fn offer_client_auth(&self) -> bool { + false + } + fn supported_verify_schemes(&self) -> Vec { unimplemented!(); } @@ -328,20 +314,20 @@ impl ClientCertVerifier for NoClientAuth { pub struct DigitallySignedStruct { /// The [`SignatureScheme`] used to produce the signature. pub scheme: SignatureScheme, - sig: PayloadU16, + sig: SizedPayload<'static, u16, MaybeEmpty>, } impl DigitallySignedStruct { pub(crate) fn new(scheme: SignatureScheme, sig: Vec) -> Self { Self { scheme, - sig: PayloadU16::new(sig), + sig: SizedPayload::from(Payload::new(sig)), } } /// Get the signature. pub fn signature(&self) -> &[u8] { - &self.sig.0 + self.sig.bytes() } } @@ -352,10 +338,86 @@ impl Codec<'_> for DigitallySignedStruct { } fn read(r: &mut Reader<'_>) -> Result { - let scheme = SignatureScheme::read(r)?; - let sig = PayloadU16::read(r)?; + Ok(Self { + scheme: SignatureScheme::read(r)?, + sig: SizedPayload::read(r)?.into_owned(), + }) + } +} + +wrapped_payload!( + /// A `DistinguishedName` is a `Vec` wrapped in internal types. + /// + /// It contains the DER or BER encoded [`Subject` field from RFC 5280](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6) + /// for a single certificate. The Subject field is [encoded as an RFC 5280 `Name`](https://datatracker.ietf.org/doc/html/rfc5280#page-116). + /// It can be decoded using [x509-parser's FromDer trait](https://docs.rs/x509-parser/latest/x509_parser/prelude/trait.FromDer.html). + /// + /// ```ignore + /// for name in distinguished_names { + /// use x509_parser::prelude::FromDer; + /// println!("{}", x509_parser::x509::X509Name::from_der(&name.0)?.1); + /// } + /// ``` + /// + /// The TLS encoding is defined in RFC5246: `opaque DistinguishedName<1..2^16-1>;` + pub struct DistinguishedName, + SizedPayload, +); + +impl DistinguishedName { + /// Create a [`DistinguishedName`] after prepending its outer SEQUENCE encoding. + /// + /// This can be decoded using [x509-parser's FromDer trait](https://docs.rs/x509-parser/latest/x509_parser/prelude/trait.FromDer.html). + /// + /// ```ignore + /// use x509_parser::prelude::FromDer; + /// println!("{}", x509_parser::x509::X509Name::from_der(dn.as_ref())?.1); + /// ``` + pub fn in_sequence(bytes: &[u8]) -> Self { + Self(SizedPayload::from(Payload::new(wrap_in_sequence(bytes)))) + } +} + +impl PartialEq for DistinguishedName { + fn eq(&self, other: &Self) -> bool { + self.0.bytes() == other.0.bytes() + } +} + +/// RFC8446: `DistinguishedName authorities<3..2^16-1>;` however, +/// RFC5246: `DistinguishedName certificate_authorities<0..2^16-1>;` +impl TlsListElement for DistinguishedName { + const SIZE_LEN: ListLength = ListLength::U16; +} - Ok(Self { scheme, sig }) +/// Zero-sized marker type representing verification of a signature. +#[derive(Debug)] +pub struct HandshakeSignatureValid(()); + +impl HandshakeSignatureValid { + /// Make a `HandshakeSignatureValid` + pub fn assertion() -> Self { + Self(()) + } +} + +#[derive(Debug)] +pub(crate) struct FinishedMessageVerified(()); + +impl FinishedMessageVerified { + pub(crate) fn assertion() -> Self { + Self(()) + } +} + +/// Zero-sized marker type representing verification of the peer's identity. +#[derive(Debug)] +pub struct PeerVerified(()); + +impl PeerVerified { + /// Make a `PeerVerified` + pub fn assertion() -> Self { + Self(()) } } @@ -364,8 +426,8 @@ fn assertions_are_debug() { use std::format; assert_eq!( - format!("{:?}", ClientCertVerified::assertion()), - "ClientCertVerified(())" + format!("{:?}", PeerVerified::assertion()), + "PeerVerified(())" ); assert_eq!( format!("{:?}", HandshakeSignatureValid::assertion()), @@ -375,8 +437,4 @@ fn assertions_are_debug() { format!("{:?}", FinishedMessageVerified::assertion()), "FinishedMessageVerified(())" ); - assert_eq!( - format!("{:?}", ServerCertVerified::assertion()), - "ServerCertVerified(())" - ); } diff --git a/rustls/src/verifybench.rs b/rustls/src/verifybench.rs deleted file mode 100644 index cab32e366fe..00000000000 --- a/rustls/src/verifybench.rs +++ /dev/null @@ -1,224 +0,0 @@ -// This program does benchmarking of the functions in verify.rs, -// that do certificate chain validation and signature verification. - -#![cfg(bench)] - -use core::time::Duration; -use std::prelude::v1::*; - -use pki_types::{CertificateDer, ServerName, UnixTime}; -use webpki_roots; - -use crate::crypto::CryptoProvider; -use crate::verify::ServerCertVerifier; -use crate::webpki::{RootCertStore, WebPkiServerVerifier}; - -#[macro_rules_attribute::apply(bench_for_each_provider)] -mod benchmarks { - use super::{provider, Context}; - - #[bench] - fn reddit_cert(b: &mut test::Bencher) { - let ctx = Context::new( - provider::default_provider(), - "reddit.com", - &[ - include_bytes!("testdata/cert-reddit.0.der"), - include_bytes!("testdata/cert-reddit.1.der"), - ], - ); - b.iter(|| ctx.verify_once()); - } - - #[bench] - fn github_cert(b: &mut test::Bencher) { - let ctx = Context::new( - provider::default_provider(), - "github.com", - &[ - include_bytes!("testdata/cert-github.0.der"), - include_bytes!("testdata/cert-github.1.der"), - ], - ); - b.iter(|| ctx.verify_once()); - } - - #[bench] - fn arstechnica_cert(b: &mut test::Bencher) { - let ctx = Context::new( - provider::default_provider(), - "arstechnica.com", - &[ - include_bytes!("testdata/cert-arstechnica.0.der"), - include_bytes!("testdata/cert-arstechnica.1.der"), - include_bytes!("testdata/cert-arstechnica.2.der"), - include_bytes!("testdata/cert-arstechnica.3.der"), - ], - ); - b.iter(|| ctx.verify_once()); - } - - #[bench] - fn servo_cert(b: &mut test::Bencher) { - let ctx = Context::new( - provider::default_provider(), - "servo.org", - &[ - include_bytes!("testdata/cert-servo.0.der"), - include_bytes!("testdata/cert-servo.1.der"), - ], - ); - b.iter(|| ctx.verify_once()); - } - - #[bench] - fn twitter_cert(b: &mut test::Bencher) { - let ctx = Context::new( - provider::default_provider(), - "twitter.com", - &[ - include_bytes!("testdata/cert-twitter.0.der"), - include_bytes!("testdata/cert-twitter.1.der"), - ], - ); - b.iter(|| ctx.verify_once()); - } - - #[bench] - fn wikipedia_cert(b: &mut test::Bencher) { - let ctx = Context::new( - provider::default_provider(), - "wikipedia.org", - &[ - include_bytes!("testdata/cert-wikipedia.0.der"), - include_bytes!("testdata/cert-wikipedia.1.der"), - ], - ); - b.iter(|| ctx.verify_once()); - } - - #[bench] - fn google_cert(b: &mut test::Bencher) { - let ctx = Context::new( - provider::default_provider(), - "www.google.com", - &[ - include_bytes!("testdata/cert-google.0.der"), - include_bytes!("testdata/cert-google.1.der"), - ], - ); - b.iter(|| ctx.verify_once()); - } - - #[bench] - fn hn_cert(b: &mut test::Bencher) { - let ctx = Context::new( - provider::default_provider(), - "news.ycombinator.com", - &[ - include_bytes!("testdata/cert-hn.0.der"), - include_bytes!("testdata/cert-hn.1.der"), - ], - ); - b.iter(|| ctx.verify_once()); - } - - #[bench] - fn stackoverflow_cert(b: &mut test::Bencher) { - let ctx = Context::new( - provider::default_provider(), - "stackoverflow.com", - &[ - include_bytes!("testdata/cert-stackoverflow.0.der"), - include_bytes!("testdata/cert-stackoverflow.1.der"), - ], - ); - b.iter(|| ctx.verify_once()); - } - - #[bench] - fn duckduckgo_cert(b: &mut test::Bencher) { - let ctx = Context::new( - provider::default_provider(), - "duckduckgo.com", - &[ - include_bytes!("testdata/cert-duckduckgo.0.der"), - include_bytes!("testdata/cert-duckduckgo.1.der"), - ], - ); - b.iter(|| ctx.verify_once()); - } - - #[bench] - fn rustlang_cert(b: &mut test::Bencher) { - let ctx = Context::new( - provider::default_provider(), - "www.rust-lang.org", - &[ - include_bytes!("testdata/cert-rustlang.0.der"), - include_bytes!("testdata/cert-rustlang.1.der"), - include_bytes!("testdata/cert-rustlang.2.der"), - ], - ); - b.iter(|| ctx.verify_once()); - } - - #[bench] - fn wapo_cert(b: &mut test::Bencher) { - let ctx = Context::new( - provider::default_provider(), - "www.washingtonpost.com", - &[ - include_bytes!("testdata/cert-wapo.0.der"), - include_bytes!("testdata/cert-wapo.1.der"), - ], - ); - b.iter(|| ctx.verify_once()); - } -} - -struct Context { - server_name: ServerName<'static>, - chain: Vec>, - now: UnixTime, - verifier: WebPkiServerVerifier, -} - -impl Context { - fn new(provider: CryptoProvider, domain: &'static str, certs: &[&'static [u8]]) -> Self { - let mut roots = RootCertStore::empty(); - roots.extend( - webpki_roots::TLS_SERVER_ROOTS - .iter() - .cloned(), - ); - Self { - server_name: domain.try_into().unwrap(), - chain: certs - .iter() - .copied() - .map(|bytes| CertificateDer::from(bytes.to_vec())) - .collect(), - now: UnixTime::since_unix_epoch(Duration::from_secs(1_640_870_720)), - verifier: WebPkiServerVerifier::new_without_revocation( - roots, - provider.signature_verification_algorithms, - ), - } - } - - fn verify_once(&self) { - const OCSP_RESPONSE: &[u8] = &[]; - - let (end_entity, intermediates) = self.chain.split_first().unwrap(); - self.verifier - .verify_server_cert( - end_entity, - intermediates, - &self.server_name, - OCSP_RESPONSE, - self.now, - ) - .unwrap(); - } -} diff --git a/rustls/src/versions.rs b/rustls/src/versions.rs index 4acdf99ae1e..7d6df290b59 100644 --- a/rustls/src/versions.rs +++ b/rustls/src/versions.rs @@ -1,42 +1,50 @@ -use core::fmt; - use crate::enums::ProtocolVersion; +use crate::tls12::Tls12CipherSuite; +use crate::tls13::Tls13CipherSuite; /// A TLS protocol version supported by rustls. /// -/// All possible instances of this class are provided by the library in +/// All possible values of this enum are provided by the library in /// the [`ALL_VERSIONS`] array, as well as individually as [`TLS12`] /// and [`TLS13`]. #[non_exhaustive] -#[derive(Eq, PartialEq)] -pub struct SupportedProtocolVersion { +#[derive(Debug)] +pub enum SupportedProtocolVersion { + /// The TLS1.2 protocol version. + TLS12(&'static Tls12Version), + /// The TLS1.3 protocol version. + TLS13(&'static Tls13Version), +} + +impl SupportedProtocolVersion { /// The TLS enumeration naming this version. - pub version: ProtocolVersion, + pub const fn version(&self) -> ProtocolVersion { + match self { + Self::TLS12(_) => ProtocolVersion::TLSv1_2, + Self::TLS13(_) => ProtocolVersion::TLSv1_3, + } + } } -impl fmt::Debug for SupportedProtocolVersion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.version.fmt(f) +impl PartialEq for SupportedProtocolVersion { + fn eq(&self, other: &Self) -> bool { + matches!( + (self, other), + (Self::TLS12(_), Self::TLS12(_)) | (Self::TLS13(_), Self::TLS13(_)) + ) } } +impl Eq for SupportedProtocolVersion {} + /// TLS1.2 -#[cfg(feature = "tls12")] -pub static TLS12: SupportedProtocolVersion = SupportedProtocolVersion { - version: ProtocolVersion::TLSv1_2, -}; +pub static TLS12: SupportedProtocolVersion = SupportedProtocolVersion::TLS12(TLS12_VERSION); /// TLS1.3 -pub static TLS13: SupportedProtocolVersion = SupportedProtocolVersion { - version: ProtocolVersion::TLSv1_3, -}; +pub static TLS13: SupportedProtocolVersion = SupportedProtocolVersion::TLS13(TLS13_VERSION); /// A list of all the protocol versions supported by rustls. -pub static ALL_VERSIONS: &[&SupportedProtocolVersion] = &[ - &TLS13, - #[cfg(feature = "tls12")] - &TLS12, -]; +pub static ALL_VERSIONS: &[&SupportedProtocolVersion] = &[&TLS13, &TLS12]; /// The version configuration that an application should use by default. /// @@ -45,53 +53,42 @@ pub static ALL_VERSIONS: &[&SupportedProtocolVersion] = &[ /// versions. pub static DEFAULT_VERSIONS: &[&SupportedProtocolVersion] = ALL_VERSIONS; -#[derive(Clone, Copy)] -pub(crate) struct EnabledVersions { - #[cfg(feature = "tls12")] - tls12: Option<&'static SupportedProtocolVersion>, - tls13: Option<&'static SupportedProtocolVersion>, -} - -impl fmt::Debug for EnabledVersions { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut list = &mut f.debug_list(); - #[cfg(feature = "tls12")] - if let Some(v) = self.tls12 { - list = list.entry(v); - } - if let Some(v) = self.tls13 { - list = list.entry(v); - } - list.finish() - } -} - -impl EnabledVersions { - pub(crate) fn new(versions: &[&'static SupportedProtocolVersion]) -> Self { - let mut ev = Self { - #[cfg(feature = "tls12")] - tls12: None, - tls13: None, - }; +/// Internal data for handling the TLS1.2 protocol. +/// +/// This value refers to TLS1.2 protocol handling code. This means +/// that if your program does not refer to this value, all that code +/// can be removed by the linker. +pub static TLS12_VERSION: &Tls12Version = &Tls12Version { + client: crate::client::TLS12_HANDLER, + server: crate::server::TLS12_HANDLER, +}; - for v in versions { - match v.version { - #[cfg(feature = "tls12")] - ProtocolVersion::TLSv1_2 => ev.tls12 = Some(v), - ProtocolVersion::TLSv1_3 => ev.tls13 = Some(v), - _ => {} - } - } +/// Internal data for handling the TLS1.3 protocol. +/// +/// This value refers to TLS1.3 protocol handling code. This means +/// that if your program does not refer to this value, all that code +/// can be removed by the linker. +pub static TLS13_VERSION: &Tls13Version = &Tls13Version { + client: crate::client::TLS13_HANDLER, + server: crate::server::TLS13_HANDLER, +}; - ev - } +/// Internal data for handling the TLS1.2 protocol. +/// +/// There is one value of this type. It is `TLS12_VERSION`. +#[non_exhaustive] +#[derive(Debug)] +pub struct Tls12Version { + pub(crate) client: &'static dyn crate::client::ClientHandler, + pub(crate) server: &'static dyn crate::server::ServerHandler, +} - pub(crate) fn contains(&self, version: ProtocolVersion) -> bool { - match version { - #[cfg(feature = "tls12")] - ProtocolVersion::TLSv1_2 => self.tls12.is_some(), - ProtocolVersion::TLSv1_3 => self.tls13.is_some(), - _ => false, - } - } +/// Internal data for handling the TLS1.3 protocol. +/// +/// There is one value of this type. It is `TLS13_VERSION`. +#[non_exhaustive] +#[derive(Debug)] +pub struct Tls13Version { + pub(crate) client: &'static dyn crate::client::ClientHandler, + pub(crate) server: &'static dyn crate::server::ServerHandler, } diff --git a/rustls/src/webpki/anchors.rs b/rustls/src/webpki/anchors.rs index e4558db889b..743957c6bac 100644 --- a/rustls/src/webpki/anchors.rs +++ b/rustls/src/webpki/anchors.rs @@ -5,12 +5,14 @@ use pki_types::{CertificateDer, TrustAnchor}; use webpki::anchor_from_trusted_cert; use super::pki_error; +use crate::Error; use crate::log::{debug, trace}; -use crate::{DistinguishedName, Error}; +use crate::verify::DistinguishedName; /// A container for root certificates able to provide a root-of-trust /// for connection authentication. -#[derive(Clone)] +#[expect(clippy::exhaustive_structs)] +#[derive(Clone, Hash)] pub struct RootCertStore { /// The list of roots. pub roots: Vec>, @@ -37,7 +39,6 @@ impl RootCertStore { let mut invalid_count = 0; for der_cert in der_certs { - #[cfg_attr(not(feature = "logging"), allow(unused_variables))] match anchor_from_trusted_cert(&der_cert) { Ok(anchor) => { self.roots.push(anchor.to_owned()); @@ -45,15 +46,14 @@ impl RootCertStore { } Err(err) => { trace!("invalid cert der {:?}", der_cert.as_ref()); - debug!("certificate parsing failed: {:?}", err); + debug!("certificate parsing failed: {err:?}"); invalid_count += 1; } }; } debug!( - "add_parsable_certificates processed {} valid and {} invalid certs", - valid_count, invalid_count + "add_parsable_certificates processed {valid_count} valid and {invalid_count} invalid certs" ); (valid_count, invalid_count) @@ -119,7 +119,7 @@ impl Extend> for RootCertStore { impl fmt::Debug for RootCertStore { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RootCertStore") - .field("roots", &format!("({} roots)", &self.roots.len())) + .field("roots", &format!("({} roots)", self.roots.len())) .finish() } } @@ -135,10 +135,10 @@ fn root_cert_store_debug() { subject_public_key_info: Der::from_slice(&[]), name_constraints: None, }; - let store = RootCertStore::from_iter(iter::repeat(ta).take(138)); + let store = RootCertStore::from_iter(iter::repeat_n(ta, 138)); assert_eq!( - format!("{:?}", store), + format!("{store:?}"), "RootCertStore { roots: \"(138 roots)\" }" ); } diff --git a/rustls/src/webpki/client_verifier.rs b/rustls/src/webpki/client_verifier.rs index bbe9db36be0..7ce4b0adc0f 100644 --- a/rustls/src/webpki/client_verifier.rs +++ b/rustls/src/webpki/client_verifier.rs @@ -1,30 +1,34 @@ -use alloc::sync::Arc; use alloc::vec::Vec; -use pki_types::{CertificateDer, CertificateRevocationListDer, UnixTime}; -use webpki::{CertRevocationList, ExpirationPolicy, RevocationCheckDepth, UnknownStatusPolicy}; +use pki_types::CertificateRevocationListDer; +use webpki::{ + CertRevocationList, ExpirationPolicy, ExtendedKeyUsage, RevocationCheckDepth, + UnknownStatusPolicy, +}; -use super::{pki_error, VerifierBuilderError}; +use super::{VerifierBuilderError, pki_error}; +#[cfg(doc)] +use crate::ConfigBuilder; #[cfg(doc)] use crate::crypto; -use crate::crypto::{CryptoProvider, WebPkiSupportedAlgorithms}; +use crate::crypto::{CryptoProvider, Identity, SignatureScheme, WebPkiSupportedAlgorithms}; +use crate::error::ApiMisuse; #[cfg(doc)] use crate::server::ServerConfig; +use crate::sync::Arc; use crate::verify::{ - ClientCertVerified, ClientCertVerifier, DigitallySignedStruct, HandshakeSignatureValid, - NoClientAuth, + ClientIdentity, ClientVerifier, DistinguishedName, HandshakeSignatureValid, NoClientAuth, + PeerVerified, SignatureVerificationInput, }; use crate::webpki::parse_crls; -use crate::webpki::verify::{verify_tls12_signature, verify_tls13_signature, ParsedCertificate}; -#[cfg(doc)] -use crate::ConfigBuilder; -use crate::{DistinguishedName, Error, RootCertStore, SignatureScheme}; +use crate::webpki::verify::{ParsedCertificate, verify_tls12_signature, verify_tls13_signature}; +use crate::{Error, RootCertStore}; /// A builder for configuring a `webpki` client certificate verifier. /// /// For more information, see the [`WebPkiClientVerifier`] documentation. #[derive(Debug, Clone)] -pub struct ClientCertVerifierBuilder { +pub struct ClientVerifierBuilder { roots: Arc, root_hint_subjects: Vec, crls: Vec>, @@ -35,19 +39,20 @@ pub struct ClientCertVerifierBuilder { supported_algs: WebPkiSupportedAlgorithms, } -impl ClientCertVerifierBuilder { +impl ClientVerifierBuilder { pub(crate) fn new( roots: Arc, supported_algs: WebPkiSupportedAlgorithms, ) -> Self { + let root_hint_subjects = roots.subjects(); Self { - root_hint_subjects: roots.subjects(), roots, + root_hint_subjects, crls: Vec::new(), - anon_policy: AnonymousClientPolicy::Deny, revocation_check_depth: RevocationCheckDepth::Chain, unknown_revocation_policy: UnknownStatusPolicy::Deny, revocation_expiration_policy: ExpirationPolicy::Ignore, + anon_policy: AnonymousClientPolicy::Deny, supported_algs, } } @@ -59,7 +64,7 @@ impl ClientCertVerifierBuilder { /// hint subjects, indicating the client should make a free choice of which certificate /// to send. /// - /// See [`ClientCertVerifier::root_hint_subjects`] for more information on + /// See [`ClientVerifier::root_hint_subjects`] for more information on /// circumstances where you may want to clear the default hint subjects. pub fn clear_root_hint_subjects(mut self) -> Self { self.root_hint_subjects = Vec::default(); @@ -73,7 +78,7 @@ impl ClientCertVerifierBuilder { /// existing hint subjects. Calling this function with empty `subjects` will have no /// effect. /// - /// See [`ClientCertVerifier::root_hint_subjects`] for more information on + /// See [`ClientVerifier::root_hint_subjects`] for more information on /// circumstances where you may want to override the default hint subjects. pub fn add_root_hint_subjects( mut self, @@ -161,7 +166,7 @@ impl ClientCertVerifierBuilder { /// If `with_signature_verification_algorithms` was not called on the builder, a default set of /// signature verification algorithms is used, controlled by the selected [`CryptoProvider`]. /// - /// Once built, the provided `Arc` can be used with a Rustls + /// Once built, the provided `Arc` can be used with a Rustls /// [`ServerConfig`] to configure client certificate validation using /// [`with_client_cert_verifier`][ConfigBuilder::with_client_cert_verifier]. /// @@ -169,63 +174,60 @@ impl ClientCertVerifierBuilder { /// This function will return a [`VerifierBuilderError`] if: /// 1. No trust anchors have been provided. /// 2. DER encoded CRLs have been provided that can not be parsed successfully. - pub fn build(self) -> Result, VerifierBuilderError> { + pub fn build(self) -> Result { if self.roots.is_empty() { return Err(VerifierBuilderError::NoRootAnchors); } - Ok(Arc::new(WebPkiClientVerifier::new( + Ok(WebPkiClientVerifier::new( self.roots, - self.root_hint_subjects, + Arc::from(self.root_hint_subjects), parse_crls(self.crls)?, self.revocation_check_depth, self.unknown_revocation_policy, self.revocation_expiration_policy, self.anon_policy, self.supported_algs, - ))) + )) } } /// A client certificate verifier that uses the `webpki` crate[^1] to perform client certificate /// validation. /// -/// It must be created via the [`WebPkiClientVerifier::builder()`] or -/// [`WebPkiClientVerifier::builder_with_provider()`] functions. +/// It must be created via [`WebPkiClientVerifier::builder()`]. /// -/// Once built, the provided `Arc` can be used with a Rustls [`ServerConfig`] +/// Once built, the provided `Arc` can be used with a Rustls [`ServerConfig`] /// to configure client certificate validation using [`with_client_cert_verifier`][ConfigBuilder::with_client_cert_verifier]. /// /// Example: /// /// To require all clients present a client certificate issued by a trusted CA: /// ```no_run -/// # #[cfg(any(feature = "ring", feature = "aws_lc_rs"))] { /// # use rustls::RootCertStore; /// # use rustls::server::WebPkiClientVerifier; +/// # let DEFAULT_PROVIDER = rustls::crypto::CryptoProvider::get_default().unwrap(); /// # let roots = RootCertStore::empty(); -/// let client_verifier = WebPkiClientVerifier::builder(roots.into()) +/// let client_verifier = WebPkiClientVerifier::builder(roots.into(), &DEFAULT_PROVIDER) /// .build() /// .unwrap(); -/// # } /// ``` /// /// Or, to allow clients presenting a client certificate authenticated by a trusted CA, or /// anonymous clients that present no client certificate: /// ```no_run -/// # #[cfg(any(feature = "ring", feature = "aws_lc_rs"))] { /// # use rustls::RootCertStore; /// # use rustls::server::WebPkiClientVerifier; +/// # let DEFAULT_PROVIDER = rustls::crypto::CryptoProvider::get_default().unwrap(); /// # let roots = RootCertStore::empty(); -/// let client_verifier = WebPkiClientVerifier::builder(roots.into()) +/// let client_verifier = WebPkiClientVerifier::builder(roots.into(), &DEFAULT_PROVIDER) /// .allow_unauthenticated() /// .build() /// .unwrap(); -/// # } /// ``` /// /// If you wish to disable advertising client authentication: -/// ```no_run +/// ``` /// # use rustls::RootCertStore; /// # use rustls::server::WebPkiClientVerifier; /// # let roots = RootCertStore::empty(); @@ -235,23 +237,23 @@ impl ClientCertVerifierBuilder { /// You can also configure the client verifier to check for certificate revocation with /// client certificate revocation lists (CRLs): /// ```no_run -/// # #[cfg(any(feature = "ring", feature = "aws_lc_rs"))] { /// # use rustls::RootCertStore; -/// # use rustls::server::{WebPkiClientVerifier}; +/// # use rustls::server::WebPkiClientVerifier; +/// # let DEFAULT_PROVIDER = rustls::crypto::CryptoProvider::get_default().unwrap(); /// # let roots = RootCertStore::empty(); /// # let crls = Vec::new(); -/// let client_verifier = WebPkiClientVerifier::builder(roots.into()) +/// let client_verifier = WebPkiClientVerifier::builder(roots.into(), &DEFAULT_PROVIDER) /// .with_crls(crls) /// .build() /// .unwrap(); -/// # } /// ``` /// /// [^1]: #[derive(Debug)] pub struct WebPkiClientVerifier { roots: Arc, - root_hint_subjects: Vec, + root_hint_subjects: Arc<[DistinguishedName]>, + eku_validator: ExtendedKeyUsage, crls: Vec>, revocation_check_depth: RevocationCheckDepth, unknown_revocation_policy: UnknownStatusPolicy, @@ -261,23 +263,6 @@ pub struct WebPkiClientVerifier { } impl WebPkiClientVerifier { - /// Create a builder for the `webpki` client certificate verifier configuration using - /// the [process-default `CryptoProvider`][CryptoProvider#using-the-per-process-default-cryptoprovider]. - /// - /// Client certificate authentication will be offered by the server, and client certificates - /// will be verified using the trust anchors found in the provided `roots`. If you - /// wish to disable client authentication use [`WebPkiClientVerifier::no_client_auth()`] instead. - /// - /// Use [`Self::builder_with_provider`] if you wish to specify an explicit provider. - /// - /// For more information, see the [`ClientCertVerifierBuilder`] documentation. - pub fn builder(roots: Arc) -> ClientCertVerifierBuilder { - Self::builder_with_provider( - roots, - Arc::clone(CryptoProvider::get_default_or_install_from_crate_features()), - ) - } - /// Create a builder for the `webpki` client certificate verifier configuration using /// a specified [`CryptoProvider`]. /// @@ -287,12 +272,9 @@ impl WebPkiClientVerifier { /// /// The cryptography used comes from the specified [`CryptoProvider`]. /// - /// For more information, see the [`ClientCertVerifierBuilder`] documentation. - pub fn builder_with_provider( - roots: Arc, - provider: Arc, - ) -> ClientCertVerifierBuilder { - ClientCertVerifierBuilder::new(roots, provider.signature_verification_algorithms) + /// For more information, see the [`ClientVerifierBuilder`] documentation. + pub fn builder(roots: Arc, provider: &CryptoProvider) -> ClientVerifierBuilder { + ClientVerifierBuilder::new(roots, provider.signature_verification_algorithms) } /// Create a new `WebPkiClientVerifier` that disables client authentication. The server will @@ -300,7 +282,7 @@ impl WebPkiClientVerifier { /// /// This is in contrast to using `WebPkiClientVerifier::builder().allow_unauthenticated().build()`, /// which will produce a verifier that will offer client authentication, but not require it. - pub fn no_client_auth() -> Arc { + pub fn no_client_auth() -> Arc { Arc::new(NoClientAuth {}) } @@ -320,7 +302,7 @@ impl WebPkiClientVerifier { /// * `supported_algs` specifies which signature verification algorithms should be used. pub(crate) fn new( roots: Arc, - root_hint_subjects: Vec, + root_hint_subjects: Arc<[DistinguishedName]>, crls: Vec>, revocation_check_depth: RevocationCheckDepth, unknown_revocation_policy: UnknownStatusPolicy, @@ -331,6 +313,7 @@ impl WebPkiClientVerifier { Self { roots, root_hint_subjects, + eku_validator: ExtendedKeyUsage::client_auth(), crls, revocation_check_depth, unknown_revocation_policy, @@ -341,32 +324,17 @@ impl WebPkiClientVerifier { } } -impl ClientCertVerifier for WebPkiClientVerifier { - fn offer_client_auth(&self) -> bool { - true - } - - fn client_auth_mandatory(&self) -> bool { - match self.anonymous_policy { - AnonymousClientPolicy::Allow => false, - AnonymousClientPolicy::Deny => true, - } - } - - fn root_hint_subjects(&self) -> &[DistinguishedName] { - &self.root_hint_subjects - } - - fn verify_client_cert( - &self, - end_entity: &CertificateDer<'_>, - intermediates: &[CertificateDer<'_>], - now: UnixTime, - ) -> Result { - let cert = ParsedCertificate::try_from(end_entity)?; +impl ClientVerifier for WebPkiClientVerifier { + fn verify_identity(&self, identity: &ClientIdentity<'_>) -> Result { + let certificates = match identity.identity { + Identity::X509(certificates) => certificates, + Identity::RawPublicKey(_) => { + return Err(ApiMisuse::UnverifiableCertificateType.into()); + } + }; + let cert = ParsedCertificate::try_from(&certificates.end_entity)?; let crl_refs = self.crls.iter().collect::>(); - let revocation = if self.crls.is_empty() { None } else { @@ -386,32 +354,43 @@ impl ClientCertVerifier for WebPkiClientVerifier { .verify_for_usage( self.supported_algs.all, &self.roots.roots, - intermediates, - now, - webpki::KeyUsage::client_auth(), + &certificates.intermediates, + identity.now, + &self.eku_validator, revocation, None, ) .map_err(pki_error) - .map(|_| ClientCertVerified::assertion()) + .map(|_| PeerVerified::assertion()) } fn verify_tls12_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result { - verify_tls12_signature(message, cert, dss, &self.supported_algs) + verify_tls12_signature(input, &self.supported_algs) } fn verify_tls13_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result { - verify_tls13_signature(message, cert, dss, &self.supported_algs) + verify_tls13_signature(input, &self.supported_algs) + } + + fn root_hint_subjects(&self) -> Arc<[DistinguishedName]> { + self.root_hint_subjects.clone() + } + + fn client_auth_mandatory(&self) -> bool { + match self.anonymous_policy { + AnonymousClientPolicy::Allow => false, + AnonymousClientPolicy::Deny => true, + } + } + + fn offer_client_auth(&self) -> bool { + true } fn supported_verify_schemes(&self) -> Vec { @@ -429,18 +408,19 @@ pub(crate) enum AnonymousClientPolicy { } #[cfg(test)] -#[macro_rules_attribute::apply(test_for_each_provider)] mod tests { - use std::prelude::v1::*; - use std::sync::Arc; + use alloc::vec::Vec; use std::{format, println, vec}; use pki_types::pem::PemObject; use pki_types::{CertificateDer, CertificateRevocationListDer}; - use super::{provider, WebPkiClientVerifier}; - use crate::server::VerifierBuilderError; + use super::WebPkiClientVerifier; use crate::RootCertStore; + use crate::crypto::TEST_PROVIDER; + use crate::error::CertRevocationListError; + use crate::server::VerifierBuilderError; + use crate::sync::Arc; fn load_crls(crls_der: &[&[u8]]) -> Vec> { crls_der @@ -483,12 +463,9 @@ mod tests { fn test_client_verifier_required_auth() { // We should be able to build a verifier that requires client authentication, and does // no revocation checking. - let builder = WebPkiClientVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ); + let builder = WebPkiClientVerifier::builder(test_roots(), &TEST_PROVIDER); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -496,13 +473,10 @@ mod tests { fn test_client_verifier_optional_auth() { // We should be able to build a verifier that allows client authentication, and anonymous // access, and does no revocation checking. - let builder = WebPkiClientVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .allow_unauthenticated(); + let builder = + WebPkiClientVerifier::builder(test_roots(), &TEST_PROVIDER).allow_unauthenticated(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -511,38 +485,29 @@ mod tests { // We should be able to build a verifier that requires client authentication, and does // no revocation checking, that hasn't been configured to determine how to handle // unauthenticated clients yet. - let builder = WebPkiClientVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ); + let builder = WebPkiClientVerifier::builder(test_roots(), &TEST_PROVIDER); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } #[test] - fn test_client_verifier_without_crls_opptional_auth() { + fn test_client_verifier_without_crls_optional_auth() { // We should be able to build a verifier that allows client authentication, // and anonymous access, that does no revocation checking. - let builder = WebPkiClientVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .allow_unauthenticated(); + let builder = + WebPkiClientVerifier::builder(test_roots(), &TEST_PROVIDER).allow_unauthenticated(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } #[test] fn test_with_invalid_crls() { // Trying to build a client verifier with invalid CRLs should error at build time. - let result = WebPkiClientVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .with_crls(vec![CertificateRevocationListDer::from(vec![0xFF])]) - .build(); + let result = WebPkiClientVerifier::builder(test_roots(), &TEST_PROVIDER) + .with_crls(vec![CertificateRevocationListDer::from(vec![0xFF])]) + .build(); assert!(matches!(result, Err(VerifierBuilderError::InvalidCrl(_)))); } @@ -554,17 +519,15 @@ mod tests { load_crls(&[ include_bytes!("../../../test-ca/eddsa/client.revoked.crl.pem").as_slice(), ]); - let builder = WebPkiClientVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .with_crls(initial_crls.clone()) - .with_crls(extra_crls.clone()); + + let builder = WebPkiClientVerifier::builder(test_roots(), &TEST_PROVIDER) + .with_crls(initial_crls.clone()) + .with_crls(extra_crls.clone()); // There should be the expected number of crls. assert_eq!(builder.crls.len(), initial_crls.len() + extra_crls.len()); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -572,13 +535,10 @@ mod tests { fn test_client_verifier_with_crls_required_auth_implicit() { // We should be able to build a verifier that requires client authentication, and that does // revocation checking with CRLs, and that does not allow any anonymous access. - let builder = WebPkiClientVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .with_crls(test_crls()); + let builder = + WebPkiClientVerifier::builder(test_roots(), &TEST_PROVIDER).with_crls(test_crls()); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -586,67 +546,52 @@ mod tests { fn test_client_verifier_with_crls_optional_auth() { // We should be able to build a verifier that supports client authentication, that does // revocation checking with CRLs, and that allows anonymous access. - let builder = WebPkiClientVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .with_crls(test_crls()) - .allow_unauthenticated(); + let builder = WebPkiClientVerifier::builder(test_roots(), &TEST_PROVIDER) + .with_crls(test_crls()) + .allow_unauthenticated(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } #[test] fn test_client_verifier_ee_only() { // We should be able to build a client verifier that only checks EE revocation status. - let builder = WebPkiClientVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .with_crls(test_crls()) - .only_check_end_entity_revocation(); + let builder = WebPkiClientVerifier::builder(test_roots(), &TEST_PROVIDER) + .with_crls(test_crls()) + .only_check_end_entity_revocation(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } #[test] fn test_client_verifier_allow_unknown() { // We should be able to build a client verifier that allows unknown revocation status - let builder = WebPkiClientVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .with_crls(test_crls()) - .allow_unknown_revocation_status(); + let builder = WebPkiClientVerifier::builder(test_roots(), &TEST_PROVIDER) + .with_crls(test_crls()) + .allow_unknown_revocation_status(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } #[test] fn test_client_verifier_enforce_expiration() { // We should be able to build a client verifier that allows unknown revocation status - let builder = WebPkiClientVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .with_crls(test_crls()) - .enforce_revocation_expiration(); + let builder = WebPkiClientVerifier::builder(test_roots(), &TEST_PROVIDER) + .with_crls(test_crls()) + .enforce_revocation_expiration(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } #[test] fn test_builder_no_roots() { // Trying to create a client verifier builder with no trust anchors should fail at build time - let result = WebPkiClientVerifier::builder_with_provider( - RootCertStore::empty().into(), - provider::default_provider().into(), - ) - .build(); + let result = + WebPkiClientVerifier::builder(RootCertStore::empty().into(), &TEST_PROVIDER).build(); assert!(matches!(result, Err(VerifierBuilderError::NoRootAnchors))); } @@ -654,12 +599,12 @@ mod tests { fn smoke() { let all = vec![ VerifierBuilderError::NoRootAnchors, - VerifierBuilderError::InvalidCrl(crate::CertRevocationListError::ParseError), + VerifierBuilderError::InvalidCrl(CertRevocationListError::ParseError), ]; for err in all { - let _ = format!("{:?}", err); - let _ = format!("{}", err); + let _ = format!("{err:?}"); + let _ = format!("{err}"); } } } diff --git a/rustls/src/webpki/mod.rs b/rustls/src/webpki/mod.rs index 3b890fa8a3a..3d25c122ab4 100644 --- a/rustls/src/webpki/mod.rs +++ b/rustls/src/webpki/mod.rs @@ -1,12 +1,12 @@ -#[cfg(feature = "std")] -use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt; use pki_types::CertificateRevocationListDer; -use webpki::{CertRevocationList, OwnedCertRevocationList}; +use webpki::{CertRevocationList, InvalidNameContext, OwnedCertRevocationList}; -use crate::error::{CertRevocationListError, CertificateError, Error, OtherError}; +use crate::error::{ + CertRevocationListError, CertificateError, Error, ExtendedKeyPurpose, OtherError, +}; mod anchors; mod client_verifier; @@ -14,16 +14,11 @@ mod server_verifier; mod verify; pub use anchors::RootCertStore; -pub use client_verifier::{ClientCertVerifierBuilder, WebPkiClientVerifier}; -pub use server_verifier::{ServerCertVerifierBuilder, WebPkiServerVerifier}; -// Conditionally exported from crate. -#[allow(unreachable_pub)] -pub use verify::{ - verify_server_cert_signed_by_trust_anchor, verify_server_name, ParsedCertificate, -}; +pub use client_verifier::{ClientVerifierBuilder, WebPkiClientVerifier}; +pub use server_verifier::{ServerVerifierBuilder, WebPkiServerVerifier}; pub use verify::{ - verify_tls12_signature, verify_tls13_signature, verify_tls13_signature_with_raw_key, - WebPkiSupportedAlgorithms, + ParsedCertificate, verify_identity_signed_by_trust_anchor, verify_server_name, + verify_tls12_signature, verify_tls13_signature, }; /// An error that can occur when building a certificate verifier. @@ -46,51 +41,101 @@ impl fmt::Display for VerifierBuilderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NoRootAnchors => write!(f, "no root trust anchors were provided"), - Self::InvalidCrl(e) => write!(f, "provided CRL could not be parsed: {:?}", e), + Self::InvalidCrl(e) => write!(f, "provided CRL could not be parsed: {e:?}"), } } } -#[cfg(feature = "std")] -impl std::error::Error for VerifierBuilderError {} +impl core::error::Error for VerifierBuilderError {} fn pki_error(error: webpki::Error) -> Error { use webpki::Error::*; match error { BadDer | BadDerTime | TrailingData(_) => CertificateError::BadEncoding.into(), - CertNotValidYet => CertificateError::NotValidYet.into(), - CertExpired | InvalidCertValidity => CertificateError::Expired.into(), + CertNotValidYet { time, not_before } => { + CertificateError::NotValidYetContext { time, not_before }.into() + } + CertExpired { time, not_after } => { + CertificateError::ExpiredContext { time, not_after }.into() + } + InvalidCertValidity => CertificateError::Expired.into(), UnknownIssuer => CertificateError::UnknownIssuer.into(), - CertNotValidForName => CertificateError::NotValidForName.into(), + CertNotValidForName(InvalidNameContext { + expected, + presented, + }) => CertificateError::NotValidForNameContext { + expected, + presented, + } + .into(), CertRevoked => CertificateError::Revoked.into(), UnknownRevocationStatus => CertificateError::UnknownRevocationStatus.into(), - CrlExpired => CertificateError::ExpiredRevocationList.into(), + CrlExpired { time, next_update } => { + CertificateError::ExpiredRevocationListContext { time, next_update }.into() + } IssuerNotCrlSigner => CertRevocationListError::IssuerInvalidForCrl.into(), - InvalidSignatureForPublicKey - | UnsupportedSignatureAlgorithm - | UnsupportedSignatureAlgorithmForPublicKey => CertificateError::BadSignature.into(), + InvalidSignatureForPublicKey => CertificateError::BadSignature.into(), + UnsupportedSignatureAlgorithm(cx) => CertificateError::UnsupportedSignatureAlgorithm { + signature_algorithm_id: cx.signature_algorithm_id, + supported_algorithms: cx.supported_algorithms, + } + .into(), + UnsupportedSignatureAlgorithmForPublicKey(cx) => { + CertificateError::UnsupportedSignatureAlgorithmForPublicKey { + signature_algorithm_id: cx.signature_algorithm_id, + public_key_algorithm_id: cx.public_key_algorithm_id, + } + .into() + } + + InvalidCrlSignatureForPublicKey => CertRevocationListError::BadSignature.into(), + UnsupportedCrlSignatureAlgorithm(cx) => { + CertRevocationListError::UnsupportedSignatureAlgorithm { + signature_algorithm_id: cx.signature_algorithm_id, + supported_algorithms: cx.supported_algorithms, + } + .into() + } + UnsupportedCrlSignatureAlgorithmForPublicKey(cx) => { + CertRevocationListError::UnsupportedSignatureAlgorithmForPublicKey { + signature_algorithm_id: cx.signature_algorithm_id, + public_key_algorithm_id: cx.public_key_algorithm_id, + } + .into() + } - InvalidCrlSignatureForPublicKey - | UnsupportedCrlSignatureAlgorithm - | UnsupportedCrlSignatureAlgorithmForPublicKey => { - CertRevocationListError::BadSignature.into() + RequiredEkuNotFound(webpki::RequiredEkuNotFoundContext { required, present }) => { + CertificateError::InvalidPurposeContext { + required: ExtendedKeyPurpose::for_values(required.oid_values()), + presented: present + .into_iter() + .map(|eku| ExtendedKeyPurpose::for_values(eku.into_iter())) + .collect(), + } + .into() } - _ => CertificateError::Other(OtherError( - #[cfg(feature = "std")] - Arc::new(error), - )) - .into(), + _ => CertificateError::Other(OtherError::new(error)).into(), } } fn crl_error(e: webpki::Error) -> CertRevocationListError { use webpki::Error::*; match e { - InvalidCrlSignatureForPublicKey - | UnsupportedCrlSignatureAlgorithm - | UnsupportedCrlSignatureAlgorithmForPublicKey => CertRevocationListError::BadSignature, + InvalidCrlSignatureForPublicKey => CertRevocationListError::BadSignature, + UnsupportedCrlSignatureAlgorithm(cx) => { + CertRevocationListError::UnsupportedSignatureAlgorithm { + signature_algorithm_id: cx.signature_algorithm_id, + supported_algorithms: cx.supported_algorithms, + } + } + UnsupportedCrlSignatureAlgorithmForPublicKey(cx) => { + CertRevocationListError::UnsupportedSignatureAlgorithmForPublicKey { + signature_algorithm_id: cx.signature_algorithm_id, + public_key_algorithm_id: cx.public_key_algorithm_id, + } + } InvalidCrlNumber => CertRevocationListError::InvalidCrlNumber, InvalidSerialNumber => CertRevocationListError::InvalidRevokedCertSerialNumber, IssuerNotCrlSigner => CertRevocationListError::IssuerInvalidForCrl, @@ -101,10 +146,7 @@ fn crl_error(e: webpki::Error) -> CertRevocationListError { UnsupportedIndirectCrl => CertRevocationListError::UnsupportedIndirectCrl, UnsupportedRevocationReason => CertRevocationListError::UnsupportedRevocationReason, - _ => CertRevocationListError::Other(OtherError( - #[cfg(feature = "std")] - Arc::new(e), - )), + _ => CertRevocationListError::Other(OtherError::new(e)), } } @@ -117,23 +159,47 @@ fn parse_crls( .map_err(crl_error) } +#[cfg(test)] mod tests { + use alloc::vec; + + use super::*; + #[test] fn pki_crl_errors() { - use super::{pki_error, CertRevocationListError, CertificateError, Error}; - // CRL signature errors should be turned into BadSignature. assert_eq!( pki_error(webpki::Error::InvalidCrlSignatureForPublicKey), Error::InvalidCertRevocationList(CertRevocationListError::BadSignature), ); + assert_eq!( - pki_error(webpki::Error::UnsupportedCrlSignatureAlgorithm), - Error::InvalidCertRevocationList(CertRevocationListError::BadSignature), + pki_error(webpki::Error::UnsupportedCrlSignatureAlgorithm( + webpki::UnsupportedSignatureAlgorithmContext { + signature_algorithm_id: vec![], + supported_algorithms: vec![], + } + )), + Error::InvalidCertRevocationList( + CertRevocationListError::UnsupportedSignatureAlgorithm { + signature_algorithm_id: vec![], + supported_algorithms: vec![], + } + ) ); assert_eq!( - pki_error(webpki::Error::UnsupportedCrlSignatureAlgorithmForPublicKey), - Error::InvalidCertRevocationList(CertRevocationListError::BadSignature), + pki_error(webpki::Error::UnsupportedCrlSignatureAlgorithmForPublicKey( + webpki::UnsupportedSignatureAlgorithmForPublicKeyContext { + signature_algorithm_id: vec![], + public_key_algorithm_id: vec![], + } + )), + Error::InvalidCertRevocationList( + CertRevocationListError::UnsupportedSignatureAlgorithmForPublicKey { + signature_algorithm_id: vec![], + public_key_algorithm_id: vec![], + } + ) ); // Revoked cert errors should be turned into Revoked. @@ -151,18 +217,32 @@ mod tests { #[test] fn crl_error_from_webpki() { - use super::crl_error; - use super::CertRevocationListError::*; - + use CertRevocationListError::*; let testcases = &[ (webpki::Error::InvalidCrlSignatureForPublicKey, BadSignature), ( - webpki::Error::UnsupportedCrlSignatureAlgorithm, - BadSignature, + webpki::Error::UnsupportedCrlSignatureAlgorithm( + webpki::UnsupportedSignatureAlgorithmContext { + signature_algorithm_id: vec![], + supported_algorithms: vec![], + }, + ), + UnsupportedSignatureAlgorithm { + signature_algorithm_id: vec![], + supported_algorithms: vec![], + }, ), ( - webpki::Error::UnsupportedCrlSignatureAlgorithmForPublicKey, - BadSignature, + webpki::Error::UnsupportedCrlSignatureAlgorithmForPublicKey( + webpki::UnsupportedSignatureAlgorithmForPublicKeyContext { + signature_algorithm_id: vec![], + public_key_algorithm_id: vec![], + }, + ), + UnsupportedSignatureAlgorithmForPublicKey { + signature_algorithm_id: vec![], + public_key_algorithm_id: vec![], + }, ), (webpki::Error::InvalidCrlNumber, InvalidCrlNumber), ( @@ -189,7 +269,9 @@ mod tests { ), ]; for t in testcases { - assert_eq!(crl_error(t.0), t.1); + std::println!("webpki: {:?}", t.0); + std::println!("rustls: {:?}", t.1); + assert_eq!(crl_error(t.0.clone()), t.1); } assert!(matches!( diff --git a/rustls/src/webpki/server_verifier.rs b/rustls/src/webpki/server_verifier.rs index 40ccfcd371f..8251726b6f3 100644 --- a/rustls/src/webpki/server_verifier.rs +++ b/rustls/src/webpki/server_verifier.rs @@ -1,28 +1,30 @@ -use alloc::sync::Arc; use alloc::vec::Vec; +use core::hash::{Hash, Hasher}; -use pki_types::{CertificateDer, CertificateRevocationListDer, ServerName, UnixTime}; +use pki_types::CertificateRevocationListDer; use webpki::{CertRevocationList, ExpirationPolicy, RevocationCheckDepth, UnknownStatusPolicy}; -use crate::crypto::{CryptoProvider, WebPkiSupportedAlgorithms}; -use crate::log::trace; +use crate::crypto::{CryptoProvider, Identity, SignatureScheme, WebPkiSupportedAlgorithms}; +use crate::error::ApiMisuse; +use crate::sync::Arc; use crate::verify::{ - DigitallySignedStruct, HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier, + HandshakeSignatureValid, PeerVerified, ServerIdentity, ServerVerifier, + SignatureVerificationInput, }; use crate::webpki::verify::{ - verify_server_cert_signed_by_trust_anchor_impl, verify_tls12_signature, verify_tls13_signature, - ParsedCertificate, + ParsedCertificate, verify_identity_signed_by_trust_anchor_impl, verify_tls12_signature, + verify_tls13_signature, }; -use crate::webpki::{parse_crls, verify_server_name, VerifierBuilderError}; +use crate::webpki::{VerifierBuilderError, parse_crls, verify_server_name}; #[cfg(doc)] -use crate::{crypto, ConfigBuilder, ServerConfig}; -use crate::{Error, RootCertStore, SignatureScheme}; +use crate::{ConfigBuilder, ServerConfig, crypto}; +use crate::{DynHasher, Error, RootCertStore}; /// A builder for configuring a `webpki` server certificate verifier. /// /// For more information, see the [`WebPkiServerVerifier`] documentation. #[derive(Debug, Clone)] -pub struct ServerCertVerifierBuilder { +pub struct ServerVerifierBuilder { roots: Arc, crls: Vec>, revocation_check_depth: RevocationCheckDepth, @@ -31,7 +33,7 @@ pub struct ServerCertVerifierBuilder { supported_algs: WebPkiSupportedAlgorithms, } -impl ServerCertVerifierBuilder { +impl ServerVerifierBuilder { pub(crate) fn new( roots: Arc, supported_algs: WebPkiSupportedAlgorithms, @@ -103,7 +105,7 @@ impl ServerCertVerifierBuilder { /// If `with_signature_verification_algorithms` was not called on the builder, a default set of /// signature verification algorithms is used, controlled by the selected [`crypto::CryptoProvider`]. /// - /// Once built, the provided `Arc` can be used with a Rustls + /// Once built, the provided `Arc` can be used with a Rustls /// [`ServerConfig`] to configure client certificate validation using /// [`with_client_cert_verifier`][ConfigBuilder::with_client_cert_verifier]. /// @@ -111,7 +113,7 @@ impl ServerCertVerifierBuilder { /// This function will return a [`VerifierBuilderError`] if: /// 1. No trust anchors have been provided. /// 2. DER encoded CRLs have been provided that can not be parsed successfully. - pub fn build(self) -> Result, VerifierBuilderError> { + pub fn build(self) -> Result { if self.roots.is_empty() { return Err(VerifierBuilderError::NoRootAnchors); } @@ -123,14 +125,12 @@ impl ServerCertVerifierBuilder { self.unknown_revocation_policy, self.revocation_expiration_policy, self.supported_algs, - ) - .into()) + )) } } -/// Default `ServerCertVerifier`, see the trait impl for more information. -#[allow(unreachable_pub)] -#[derive(Debug)] +/// Default `ServerVerifier`, see the trait impl for more information. +#[derive(Debug, Hash)] pub struct WebPkiServerVerifier { roots: Arc, crls: Vec>, @@ -140,23 +140,7 @@ pub struct WebPkiServerVerifier { supported: WebPkiSupportedAlgorithms, } -#[allow(unreachable_pub)] impl WebPkiServerVerifier { - /// Create a builder for the `webpki` server certificate verifier configuration using - /// the [process-default `CryptoProvider`][CryptoProvider#using-the-per-process-default-cryptoprovider]. - /// - /// Server certificates will be verified using the trust anchors found in the provided `roots`. - /// - /// Use [`Self::builder_with_provider`] if you wish to specify an explicit provider. - /// - /// For more information, see the [`ServerCertVerifierBuilder`] documentation. - pub fn builder(roots: Arc) -> ServerCertVerifierBuilder { - Self::builder_with_provider( - roots, - Arc::clone(CryptoProvider::get_default_or_install_from_crate_features()), - ) - } - /// Create a builder for the `webpki` server certificate verifier configuration using /// a specified [`CryptoProvider`]. /// @@ -164,12 +148,9 @@ impl WebPkiServerVerifier { /// /// The cryptography used comes from the specified [`CryptoProvider`]. /// - /// For more information, see the [`ServerCertVerifierBuilder`] documentation. - pub fn builder_with_provider( - roots: Arc, - provider: Arc, - ) -> ServerCertVerifierBuilder { - ServerCertVerifierBuilder::new(roots, provider.signature_verification_algorithms) + /// For more information, see the [`ServerVerifierBuilder`] documentation. + pub fn builder(roots: Arc, provider: &CryptoProvider) -> ServerVerifierBuilder { + ServerVerifierBuilder::new(roots, provider.signature_verification_algorithms) } /// Short-cut for creating a `WebPkiServerVerifier` that does not perform certificate revocation @@ -218,7 +199,7 @@ impl WebPkiServerVerifier { } } -impl ServerCertVerifier for WebPkiServerVerifier { +impl ServerVerifier for WebPkiServerVerifier { /// Will verify the certificate is valid in the following ways: /// - Signed by a trusted `RootCertStore` CA /// - Not Expired @@ -229,18 +210,16 @@ impl ServerCertVerifier for WebPkiServerVerifier { /// each certificate in the chain to a root CA (excluding the root itself), or only the /// end entity certificate. Similarly, unknown revocation status may be treated as an error /// or allowed based on configuration. - fn verify_server_cert( - &self, - end_entity: &CertificateDer<'_>, - intermediates: &[CertificateDer<'_>], - server_name: &ServerName<'_>, - ocsp_response: &[u8], - now: UnixTime, - ) -> Result { - let cert = ParsedCertificate::try_from(end_entity)?; + fn verify_identity(&self, identity: &ServerIdentity<'_>) -> Result { + let certificates = match identity.identity { + Identity::X509(certificates) => certificates, + Identity::RawPublicKey(_) => { + return Err(ApiMisuse::UnverifiableCertificateType.into()); + } + }; + let cert = ParsedCertificate::try_from(&certificates.end_entity)?; let crl_refs = self.crls.iter().collect::>(); - let revocation = if self.crls.is_empty() { None } else { @@ -260,58 +239,58 @@ impl ServerCertVerifier for WebPkiServerVerifier { // Note: we use the crate-internal `_impl` fn here in order to provide revocation // checking information, if applicable. - verify_server_cert_signed_by_trust_anchor_impl( + verify_identity_signed_by_trust_anchor_impl( &cert, &self.roots, - intermediates, + &certificates.intermediates, revocation, - now, + identity.now, self.supported.all, )?; - if !ocsp_response.is_empty() { - trace!("Unvalidated OCSP response: {:?}", ocsp_response.to_vec()); - } - - verify_server_name(&cert, server_name)?; - Ok(ServerCertVerified::assertion()) + verify_server_name(&cert, identity.server_name)?; + Ok(PeerVerified::assertion()) } fn verify_tls12_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result { - verify_tls12_signature(message, cert, dss, &self.supported) + verify_tls12_signature(input, &self.supported) } fn verify_tls13_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result { - verify_tls13_signature(message, cert, dss, &self.supported) + verify_tls13_signature(input, &self.supported) } fn supported_verify_schemes(&self) -> Vec { self.supported.supported_schemes() } + + fn request_ocsp_response(&self) -> bool { + false + } + + fn hash_config(&self, h: &mut dyn Hasher) { + self.hash(&mut DynHasher(h)); + } } #[cfg(test)] -#[macro_rules_attribute::apply(test_for_each_provider)] mod tests { - use std::prelude::v1::*; - use std::sync::Arc; + use alloc::vec::Vec; use std::{println, vec}; use pki_types::pem::PemObject; use pki_types::{CertificateDer, CertificateRevocationListDer}; - use super::{provider, VerifierBuilderError, WebPkiServerVerifier}; + use super::{VerifierBuilderError, WebPkiServerVerifier}; use crate::RootCertStore; + use crate::crypto::TEST_PROVIDER; + use crate::sync::Arc; fn load_crls(crls_der: &[&[u8]]) -> Vec> { crls_der @@ -347,12 +326,9 @@ mod tests { #[test] fn test_with_invalid_crls() { // Trying to build a server verifier with invalid CRLs should error at build time. - let result = WebPkiServerVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .with_crls(vec![CertificateRevocationListDer::from(vec![0xFF])]) - .build(); + let result = WebPkiServerVerifier::builder(test_roots(), &TEST_PROVIDER) + .with_crls(vec![CertificateRevocationListDer::from(vec![0xFF])]) + .build(); assert!(matches!(result, Err(VerifierBuilderError::InvalidCrl(_)))); } @@ -365,41 +341,32 @@ mod tests { include_bytes!("../../../test-ca/eddsa/client.revoked.crl.pem").as_slice(), ]); - let builder = WebPkiServerVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .with_crls(initial_crls.clone()) - .with_crls(extra_crls.clone()); + let builder = WebPkiServerVerifier::builder(test_roots(), &TEST_PROVIDER) + .with_crls(initial_crls.clone()) + .with_crls(extra_crls.clone()); // There should be the expected number of crls. assert_eq!(builder.crls.len(), initial_crls.len() + extra_crls.len()); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } #[test] fn test_builder_no_roots() { // Trying to create a server verifier builder with no trust anchors should fail at build time - let result = WebPkiServerVerifier::builder_with_provider( - RootCertStore::empty().into(), - provider::default_provider().into(), - ) - .build(); + let result = + WebPkiServerVerifier::builder(RootCertStore::empty().into(), &TEST_PROVIDER).build(); assert!(matches!(result, Err(VerifierBuilderError::NoRootAnchors))); } #[test] fn test_server_verifier_ee_only() { // We should be able to build a server cert. verifier that only checks the EE cert. - let builder = WebPkiServerVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .only_check_end_entity_revocation(); + let builder = WebPkiServerVerifier::builder(test_roots(), &TEST_PROVIDER) + .only_check_end_entity_revocation(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -407,13 +374,10 @@ mod tests { fn test_server_verifier_allow_unknown() { // We should be able to build a server cert. verifier that allows unknown revocation // status. - let builder = WebPkiServerVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .allow_unknown_revocation_status(); + let builder = WebPkiServerVerifier::builder(test_roots(), &TEST_PROVIDER) + .allow_unknown_revocation_status(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -421,14 +385,11 @@ mod tests { fn test_server_verifier_allow_unknown_ee_only() { // We should be able to build a server cert. verifier that allows unknown revocation // status and only checks the EE cert. - let builder = WebPkiServerVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .allow_unknown_revocation_status() - .only_check_end_entity_revocation(); + let builder = WebPkiServerVerifier::builder(test_roots(), &TEST_PROVIDER) + .allow_unknown_revocation_status() + .only_check_end_entity_revocation(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -436,13 +397,10 @@ mod tests { fn test_server_verifier_enforce_expiration() { // We should be able to build a server cert. verifier that allows unknown revocation // status. - let builder = WebPkiServerVerifier::builder_with_provider( - test_roots(), - provider::default_provider().into(), - ) - .enforce_revocation_expiration(); + let builder = WebPkiServerVerifier::builder(test_roots(), &TEST_PROVIDER) + .enforce_revocation_expiration(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } } diff --git a/rustls/src/webpki/verify.rs b/rustls/src/webpki/verify.rs index 38caa93cdee..bdfbd2f9c53 100644 --- a/rustls/src/webpki/verify.rs +++ b/rustls/src/webpki/verify.rs @@ -1,15 +1,13 @@ -use alloc::vec::Vec; -use core::fmt; - use pki_types::{ CertificateDer, ServerName, SignatureVerificationAlgorithm, SubjectPublicKeyInfoDer, UnixTime, }; +use webpki::ExtendedKeyUsage; use super::anchors::RootCertStore; use super::pki_error; -use crate::enums::SignatureScheme; -use crate::error::{Error, PeerMisbehaved}; -use crate::verify::{DigitallySignedStruct, HandshakeSignatureValid}; +use crate::crypto::WebPkiSupportedAlgorithms; +use crate::error::{ApiMisuse, Error, PeerMisbehaved}; +use crate::verify::{HandshakeSignatureValid, SignatureVerificationInput, SignerPublicKey}; /// Verify that the end-entity certificate `end_entity` is a valid server cert /// and chains to at least one of the trust anchors in the `roots` [RootCertStore]. @@ -22,15 +20,14 @@ use crate::verify::{DigitallySignedStruct, HandshakeSignatureValid}; /// `intermediates` contains all certificates other than `end_entity` that /// were sent as part of the server's `Certificate` message. It is in the /// same order that the server sent them and may be empty. -#[allow(dead_code)] -pub fn verify_server_cert_signed_by_trust_anchor( +pub fn verify_identity_signed_by_trust_anchor( cert: &ParsedCertificate<'_>, roots: &RootCertStore, intermediates: &[CertificateDer<'_>], now: UnixTime, supported_algs: &[&dyn SignatureVerificationAlgorithm], ) -> Result<(), Error> { - verify_server_cert_signed_by_trust_anchor_impl( + verify_identity_signed_by_trust_anchor_impl( cert, roots, intermediates, @@ -43,7 +40,7 @@ pub fn verify_server_cert_signed_by_trust_anchor( /// Verify that the `end_entity` has an alternative name matching the `server_name`. /// /// Note: this only verifies the name and should be used in conjunction with more verification -/// like [verify_server_cert_signed_by_trust_anchor] +/// like [verify_identity_signed_by_trust_anchor] pub fn verify_server_name( cert: &ParsedCertificate<'_>, server_name: &ServerName<'_>, @@ -53,76 +50,6 @@ pub fn verify_server_name( .map_err(pki_error) } -/// Describes which `webpki` signature verification algorithms are supported and -/// how they map to TLS [`SignatureScheme`]s. -#[derive(Clone, Copy)] -#[allow(unreachable_pub)] -pub struct WebPkiSupportedAlgorithms { - /// A list of all supported signature verification algorithms. - /// - /// Used for verifying certificate chains. - /// - /// The order of this list is not significant. - pub all: &'static [&'static dyn SignatureVerificationAlgorithm], - - /// A mapping from TLS `SignatureScheme`s to matching webpki signature verification algorithms. - /// - /// This is one (`SignatureScheme`) to many ([`SignatureVerificationAlgorithm`]) because - /// (depending on the protocol version) there is not necessary a 1-to-1 mapping. - /// - /// For TLS1.2, all `SignatureVerificationAlgorithm`s are tried in sequence. - /// - /// For TLS1.3, only the first is tried. - /// - /// The supported schemes in this mapping is communicated to the peer and the order is significant. - /// The first mapping is our highest preference. - pub mapping: &'static [( - SignatureScheme, - &'static [&'static dyn SignatureVerificationAlgorithm], - )], -} - -impl WebPkiSupportedAlgorithms { - /// Return all the `scheme` items in `mapping`, maintaining order. - pub fn supported_schemes(&self) -> Vec { - self.mapping - .iter() - .map(|item| item.0) - .collect() - } - - /// Return the first item in `mapping` that matches `scheme`. - fn convert_scheme( - &self, - scheme: SignatureScheme, - ) -> Result<&[&'static dyn SignatureVerificationAlgorithm], Error> { - self.mapping - .iter() - .filter_map(|item| if item.0 == scheme { Some(item.1) } else { None }) - .next() - .ok_or_else(|| PeerMisbehaved::SignedHandshakeWithUnadvertisedSigScheme.into()) - } - - /// Return `true` if all cryptography is FIPS-approved. - pub fn fips(&self) -> bool { - self.all.iter().all(|alg| alg.fips()) - && self - .mapping - .iter() - .all(|item| item.1.iter().all(|alg| alg.fips())) - } -} - -impl fmt::Debug for WebPkiSupportedAlgorithms { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "WebPkiSupportedAlgorithms {{ all: [ .. ], mapping: ")?; - f.debug_list() - .entries(self.mapping.iter().map(|item| item.0)) - .finish()?; - write!(f, " }}") - } -} - /// Wrapper around internal representation of a parsed certificate. /// /// This is used in order to avoid parsing twice when specifying custom verification @@ -151,73 +78,75 @@ impl<'a> TryFrom<&'a CertificateDer<'a>> for ParsedCertificate<'a> { /// [`SignatureVerificationAlgorithm`], this function will map to several candidates and try each in /// succession until one succeeds or we exhaust all candidates. /// -/// See [WebPkiSupportedAlgorithms::mapping] for more information. +/// See [`WebPkiSupportedAlgorithms::mapping()`] for more information. pub fn verify_tls12_signature( - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, supported_schemes: &WebPkiSupportedAlgorithms, ) -> Result { - let possible_algs = supported_schemes.convert_scheme(dss.scheme)?; - let cert = webpki::EndEntityCert::try_from(cert).map_err(pki_error)?; + let possible_algs = supported_schemes.convert_scheme(input.signature.scheme)?; + let cert = match input.signer { + SignerPublicKey::X509(cert_der) => { + webpki::EndEntityCert::try_from(*cert_der).map_err(pki_error)? + } + SignerPublicKey::RawPublicKey(_) => { + return Err(ApiMisuse::InvalidSignerForProtocolVersion.into()); + } + }; + let mut error = None; for alg in possible_algs { - match cert.verify_signature(*alg, message, dss.signature()) { - Err(webpki::Error::UnsupportedSignatureAlgorithmForPublicKey) => continue, + match cert.verify_signature(*alg, input.message, input.signature.signature()) { + Err(err @ webpki::Error::UnsupportedSignatureAlgorithmForPublicKey(_)) => { + error = Some(err); + continue; + } Err(e) => return Err(pki_error(e)), Ok(()) => return Ok(HandshakeSignatureValid::assertion()), } } - Err(pki_error( - webpki::Error::UnsupportedSignatureAlgorithmForPublicKey, - )) + Err(match error { + Some(e) => pki_error(e), + None => Error::ApiMisuse(ApiMisuse::NoSignatureVerificationAlgorithms), + }) } /// Verify a message signature using the `cert` public key and the first TLS 1.3 compatible /// supported scheme. /// /// This function verifies the `dss` signature over `message` using the subject public key from -/// `cert`. Unlike [verify_tls12_signature], this function only tries the first matching scheme. See -/// [WebPkiSupportedAlgorithms::mapping] for more information. +/// `cert`. Unlike [`verify_tls12_signature()`], this function only tries the first matching scheme. See +/// [`WebPkiSupportedAlgorithms::mapping()`] for more information. pub fn verify_tls13_signature( - msg: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, supported_schemes: &WebPkiSupportedAlgorithms, ) -> Result { - if !dss.scheme.supported_in_tls13() { + if !input + .signature + .scheme + .supported_in_tls13() + { return Err(PeerMisbehaved::SignedHandshakeWithUnadvertisedSigScheme.into()); } - let alg = supported_schemes.convert_scheme(dss.scheme)?[0]; - - let cert = webpki::EndEntityCert::try_from(cert).map_err(pki_error)?; - - cert.verify_signature(alg, msg, dss.signature()) - .map_err(pki_error) - .map(|_| HandshakeSignatureValid::assertion()) -} - -/// Verify a message signature using a raw public key and the first TLS 1.3 compatible -/// supported scheme. -pub fn verify_tls13_signature_with_raw_key( - msg: &[u8], - spki: &SubjectPublicKeyInfoDer<'_>, - dss: &DigitallySignedStruct, - supported_schemes: &WebPkiSupportedAlgorithms, -) -> Result { - if !dss.scheme.supported_in_tls13() { - return Err(PeerMisbehaved::SignedHandshakeWithUnadvertisedSigScheme.into()); + let &alg = supported_schemes + .convert_scheme(input.signature.scheme)? + .first() + .ok_or(Error::ApiMisuse( + ApiMisuse::NoSignatureVerificationAlgorithms, + ))?; + + match input.signer { + SignerPublicKey::X509(cert_der) => { + webpki::EndEntityCert::try_from(*cert_der).and_then(|cert| { + cert.verify_signature(alg, input.message, input.signature.signature()) + }) + } + SignerPublicKey::RawPublicKey(spki) => webpki::RawPublicKeyEntity::try_from(*spki) + .and_then(|rpk| rpk.verify_signature(alg, input.message, input.signature.signature())), } - - let raw_key = webpki::RawPublicKeyEntity::try_from(spki).map_err(pki_error)?; - let alg = supported_schemes.convert_scheme(dss.scheme)?[0]; - - raw_key - .verify_signature(alg, msg, dss.signature()) - .map_err(pki_error) - .map(|_| HandshakeSignatureValid::assertion()) + .map_err(pki_error) + .map(|_| HandshakeSignatureValid::assertion()) } /// Verify that the end-entity certificate `end_entity` is a valid server cert @@ -229,11 +158,11 @@ pub fn verify_tls13_signature_with_raw_key( /// /// `revocation` controls how revocation checking is performed, if at all. /// -/// This function exists to be used by [`verify_server_cert_signed_by_trust_anchor`], +/// This function exists to be used by [`verify_identity_signed_by_trust_anchor`], /// and differs only in providing a `Option` argument. We -/// can't include this argument in `verify_server_cert_signed_by_trust_anchor` because +/// can't include this argument in `verify_identity_signed_by_trust_anchor` because /// it will leak the webpki types into Rustls' public API. -pub(crate) fn verify_server_cert_signed_by_trust_anchor_impl( +pub(crate) fn verify_identity_signed_by_trust_anchor_impl( cert: &ParsedCertificate<'_>, roots: &RootCertStore, intermediates: &[CertificateDer<'_>], @@ -246,7 +175,7 @@ pub(crate) fn verify_server_cert_signed_by_trust_anchor_impl( &roots.roots, intermediates, now, - webpki::KeyUsage::server_auth(), + &ExtendedKeyUsage::server_auth(), revocation, None, ); @@ -258,9 +187,36 @@ pub(crate) fn verify_server_cert_signed_by_trust_anchor_impl( #[cfg(test)] mod tests { + use alloc::vec; use std::format; use super::*; + use crate::crypto::{SignatureScheme, TEST_PROVIDER}; + use crate::verify::DigitallySignedStruct; + + #[test] + fn tls13_empty_signature_mapping_panics() { + let supported = WebPkiSupportedAlgorithms { + all: TEST_PROVIDER + .signature_verification_algorithms + .all, + mapping: &[(SignatureScheme::ED25519, &[])], + }; + + let cert = CertificateDer::from(vec![0u8]); // never parsed; panic happens first + let dss = DigitallySignedStruct::new(SignatureScheme::ED25519, vec![]); + let signer = SignerPublicKey::X509(&cert); + let input = SignatureVerificationInput { + message: b"hello", + signer: &signer, + signature: &dss, + }; + + assert_eq!( + verify_tls13_signature(&input, &supported).unwrap_err(), + Error::ApiMisuse(ApiMisuse::NoSignatureVerificationAlgorithms) + ); + } #[test] fn certificate_debug() { @@ -270,12 +226,17 @@ mod tests { ); } - #[cfg(feature = "ring")] #[test] fn webpki_supported_algorithms_is_debug() { assert_eq!( - "WebPkiSupportedAlgorithms { all: [ .. ], mapping: [ECDSA_NISTP384_SHA384, ECDSA_NISTP256_SHA256, ED25519, RSA_PSS_SHA512, RSA_PSS_SHA384, RSA_PSS_SHA256, RSA_PKCS1_SHA512, RSA_PKCS1_SHA384, RSA_PKCS1_SHA256] }", - format!("{:?}", crate::crypto::ring::default_provider().signature_verification_algorithms) + "WebPkiSupportedAlgorithms { all: [ .. ], mapping: [] }", + format!( + "{:?}", + WebPkiSupportedAlgorithms { + all: &[], + mapping: &[] + } + ) ); } } diff --git a/rustls/src/x509.rs b/rustls/src/x509.rs index 2620fe8a4cd..c3dd86fd751 100644 --- a/rustls/src/x509.rs +++ b/rustls/src/x509.rs @@ -7,23 +7,11 @@ pub(crate) fn wrap_in_sequence(bytes: &[u8]) -> Vec { asn1_wrap(DER_SEQUENCE_TAG, bytes, &[]) } -/// Prepend stuff to `bytes_a` + `bytes_b` to put it in a DER SEQUENCE. -#[cfg_attr(not(feature = "ring"), allow(dead_code))] -pub(crate) fn wrap_concat_in_sequence(bytes_a: &[u8], bytes_b: &[u8]) -> Vec { - asn1_wrap(DER_SEQUENCE_TAG, bytes_a, bytes_b) -} - /// Prepend stuff to `bytes` to put it in a DER BIT STRING. pub(crate) fn wrap_in_bit_string(bytes: &[u8]) -> Vec { asn1_wrap(DER_BIT_STRING_TAG, &[0u8], bytes) } -/// Prepend stuff to `bytes` to put it in a DER OCTET STRING. -#[cfg_attr(not(feature = "ring"), allow(dead_code))] -pub(crate) fn wrap_in_octet_string(bytes: &[u8]) -> Vec { - asn1_wrap(DER_OCTET_STRING_TAG, bytes, &[]) -} - fn asn1_wrap(tag: u8, bytes_a: &[u8], bytes_b: &[u8]) -> Vec { let len = bytes_a.len() + bytes_b.len(); @@ -59,7 +47,6 @@ fn asn1_wrap(tag: u8, bytes_a: &[u8], bytes_b: &[u8]) -> Vec { const DER_SEQUENCE_TAG: u8 = 0x30; const DER_BIT_STRING_TAG: u8 = 0x03; -const DER_OCTET_STRING_TAG: u8 = 0x04; #[cfg(test)] mod tests { diff --git a/rustls/tests/api.rs b/rustls/tests/api.rs deleted file mode 100644 index a9bc0f3de51..00000000000 --- a/rustls/tests/api.rs +++ /dev/null @@ -1,8341 +0,0 @@ -//! Assorted public API tests. - -#![allow(clippy::duplicate_mod)] - -use std::fmt::Debug; -use std::io::{self, IoSlice, Read, Write}; -use std::ops::{Deref, DerefMut}; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex}; -use std::{fmt, mem}; - -use pki_types::{CertificateDer, IpAddr, ServerName, UnixTime}; -use rustls::client::{verify_server_cert_signed_by_trust_anchor, ResolvesClientCert, Resumption}; -use rustls::crypto::{ActiveKeyExchange, CryptoProvider, SharedSecret, SupportedKxGroup}; -use rustls::internal::msgs::base::Payload; -use rustls::internal::msgs::codec::Codec; -use rustls::internal::msgs::enums::{AlertLevel, CertificateType, Compression}; -use rustls::internal::msgs::handshake::{ - ClientExtension, ClientHelloPayload, HandshakeMessagePayload, HandshakePayload, Random, - ServerExtension, ServerName as ServerNameExtensionItem, SessionId, -}; -use rustls::internal::msgs::message::{Message, MessagePayload, PlainMessage}; -use rustls::server::{ClientHello, ParsedCertificate, ResolvesServerCert}; -#[cfg(feature = "aws_lc_rs")] -use rustls::{ - client::{EchConfig, EchGreaseConfig, EchMode}, - crypto::aws_lc_rs::hpke::ALL_SUPPORTED_SUITES, - internal::msgs::base::PayloadU16, - internal::msgs::handshake::{ - EchConfigContents, EchConfigPayload, HpkeKeyConfig, HpkeSymmetricCipherSuite, - }, - pki_types::{DnsName, EchConfigListBytes}, -}; -use rustls::{ - sign, AlertDescription, CertificateError, CipherSuite, ClientConfig, ClientConnection, - ConnectionCommon, ConnectionTrafficSecrets, ContentType, DistinguishedName, Error, - HandshakeKind, HandshakeType, InconsistentKeys, InvalidMessage, KeyLog, NamedGroup, - PeerIncompatible, PeerMisbehaved, ProtocolVersion, ServerConfig, ServerConnection, SideData, - SignatureScheme, Stream, StreamOwned, SupportedCipherSuite, -}; - -use super::*; - -mod common; -use common::*; -use provider::cipher_suite; -use provider::sign::RsaSigningKey; -use rustls::ProtocolVersion::TLSv1_2; - -mod test_raw_keys { - use rustls::crypto::cipher::{ - InboundOpaqueMessage, MessageDecrypter, MessageEncrypter, OutboundChunks, - OutboundPlainMessage, - }; - use rustls::crypto::tls13::OkmBlock; - use rustls::internal::{derive_traffic_iv, derive_traffic_key}; - use rustls::{Connection, Tls13CipherSuite}; - - use super::*; - - #[test] - fn successful_raw_key_connection_and_correct_peer_certificates() { - for kt in ALL_KEY_TYPES { - let client_config = make_client_config_with_raw_key_support(*kt); - let server_config = make_server_config_with_raw_key_support(*kt); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake(&mut client, &mut server); - - // Test that the client peer certificate is the server's public key - match client.peer_certificates() { - Some(certificates) => { - assert_eq!(certificates.len(), 1); - let cert: CertificateDer<'_> = certificates[0].clone(); - assert_eq!(cert.as_ref(), kt.get_spki().as_ref()); - } - None => { - unreachable!("Client should have received a certificate") - } - } - - // Test that the server peer certificate is the client's public key - match server.peer_certificates() { - Some(certificates) => { - assert_eq!(certificates.len(), 1); - let cert = certificates[0].clone(); - assert_eq!(cert.as_ref(), kt.get_client_spki().as_ref()); - } - None => { - unreachable!("Server should have received a certificate") - } - } - } - } - - #[test] - fn correct_certificate_type_extensions_from_client_hello() { - for kt in ALL_KEY_TYPES { - let client_config = make_client_config_with_raw_key_support(*kt); - let mut server_config = make_server_config_with_raw_key_support(*kt); - - server_config.cert_resolver = Arc::new(ServerCheckCertResolve { - expected_client_cert_types: Some(vec![CertificateType::RawPublicKey]), - expected_server_cert_types: Some(vec![CertificateType::RawPublicKey]), - ..Default::default() - }); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - let err = do_handshake_until_error(&mut client, &mut server); - assert!(err.is_err()); - } - } - - #[test] - fn only_client_supports_raw_keys() { - for kt in ALL_KEY_TYPES { - let client_config_rpk = make_client_config_with_raw_key_support(*kt); - let server_config = make_server_config(*kt); - - let (mut client_rpk, mut server) = - make_pair_for_configs(client_config_rpk, server_config); - - // The client - match do_handshake_until_error(&mut client_rpk, &mut server) { - Err(err) => { - assert_eq!( - err, - ErrorFromPeer::Server(Error::PeerIncompatible( - PeerIncompatible::UnsolicitedCertificateTypeExtension - )) - ) - } - _ => { - unreachable!("Expected error because client is incorrectly configured") - } - } - } - } - - #[test] - fn only_server_supports_raw_keys() { - for kt in ALL_KEY_TYPES { - let client_config = make_client_config_with_versions(*kt, &[&rustls::version::TLS13]); - let server_config_rpk = make_server_config_with_raw_key_support(*kt); - - let (mut client, mut server_rpk) = - make_pair_for_configs(client_config, server_config_rpk); - - match do_handshake_until_error(&mut client, &mut server_rpk) { - Err(err) => { - assert_eq!( - err, - ErrorFromPeer::Server(Error::PeerIncompatible( - PeerIncompatible::IncorrectCertificateTypeExtension - )) - ) - } - _ => { - unreachable!("Expected error because client is incorrectly configured") - } - } - } - } - - #[test] - fn incorrectly_alter_client_hello() { - for kt in ALL_KEY_TYPES { - let client_config = Arc::new(make_client_config(*kt)); - let server_config_rpk = Arc::new(make_server_config_with_raw_key_support(*kt)); - - // Alter Client Hello client certificate extension - let (client, server) = make_pair_for_arc_configs(&client_config, &server_config_rpk); - let server_cert_altered = do_handshake_altered( - client, - |_: &mut Message| -> Altered { Altered::InPlace }, - |msg: &mut Message| { - alter_client_hello_message(msg, Some(&vec![CertificateType::X509]), None) - }, - server, - ); - match server_cert_altered { - Ok(_) => unreachable!("Expected error because server cert is altered"), - Err(err) => assert_eq!( - err, - ErrorFromPeer::Server(Error::PeerIncompatible( - PeerIncompatible::IncorrectCertificateTypeExtension - )) - ), - } - - // Alter Server Hello server certificate extension - let (client, server) = make_pair_for_arc_configs(&client_config, &server_config_rpk); - let client_cert_altered = do_handshake_altered( - client, - |_: &mut Message| -> Altered { Altered::InPlace }, - |msg: &mut Message| { - alter_client_hello_message(msg, None, Some(&vec![CertificateType::X509])) - }, - server, - ); - match client_cert_altered { - Ok(_) => unreachable!("Expected error because server cert is altered"), - Err(err) => assert_eq!( - err, - ErrorFromPeer::Server(Error::PeerIncompatible( - PeerIncompatible::IncorrectCertificateTypeExtension - )) - ), - } - } - } - - #[test] - fn incorrectly_alter_server_hello() { - for kt in ALL_KEY_TYPES { - let supported_suite = cipher_suite::TLS13_AES_256_GCM_SHA384; - - // Alter Server Hello server certificate extension and expect IncorrectCertificateTypeExtension error - let client_config_rpk = make_client_config_with_raw_key_support(*kt); - let server_config_rpk = make_server_config_with_raw_key_support(*kt); - add_keylog_and_do_altered_handshake( - client_config_rpk, - server_config_rpk, - supported_suite, - Some(&CertificateType::X509), - None, - Error::PeerIncompatible(PeerIncompatible::IncorrectCertificateTypeExtension), - ); - - // Alter Server Hello client certificate extension and expect IncorrectCertificateTypeExtension error - let client_config_rpk = make_client_config_with_raw_key_support(*kt); - let server_config_rpk = make_server_config_with_raw_key_support(*kt); - add_keylog_and_do_altered_handshake( - client_config_rpk, - server_config_rpk, - supported_suite, - None, - Some(&CertificateType::X509), - Error::PeerIncompatible(PeerIncompatible::IncorrectCertificateTypeExtension), - ); - - // Alter Server Hello server certificate extension and expect UnexpectedCertificateTypeExtension error - let client_config = make_client_config(*kt); - let server_config_rpk = make_server_config(*kt); - add_keylog_and_do_altered_handshake( - client_config, - server_config_rpk, - supported_suite, - Some(&CertificateType::X509), - None, - Error::PeerMisbehaved(PeerMisbehaved::UnsolicitedEncryptedExtension), - ); - - // Alter Server Hello client certificate extension and expect UnexpectedCertificateTypeExtension error - let client_config = make_client_config(*kt); - let server_config_rpk = make_server_config(*kt); - add_keylog_and_do_altered_handshake( - client_config, - server_config_rpk, - supported_suite, - None, - Some(&CertificateType::X509), - Error::PeerMisbehaved(PeerMisbehaved::UnsolicitedEncryptedExtension), - ); - } - } - - fn add_keylog_and_do_altered_handshake( - client_config: ClientConfig, - mut server_config: ServerConfig, - supported_suite: SupportedCipherSuite, - server_cert_type: Option<&CertificateType>, - client_cert_type: Option<&CertificateType>, - expected_error: Error, - ) { - let keylog_to_vec = Arc::new(KeyLogToVec::new("server")); - server_config.key_log = keylog_to_vec.clone(); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - - // Client -> Server (Client Hello) - transfer(&mut client, &mut server); - server - .process_new_packets() - .map_err(ErrorFromPeer::Server) - .unwrap(); - - // Server -> Client (Server Hello, Server Change Cipher Spec, Server Encrypted Extensions, etc) - let mut server = Connection::Server(server); - let mut client = Connection::Client(client); - transfer_altered( - &mut server, - |msg| { - alter_server_hello_message( - msg, - server_cert_type, - client_cert_type, - supported_suite, - &keylog_to_vec, - ) - }, - &mut client, - ); - - match client.process_new_packets() { - Ok(_) => unreachable!("Expected error because server cert is altered"), - Err(err) => assert_eq!(err, expected_error), - } - } - - fn alter_client_hello_message( - msg: &mut Message, - server_cert_types: Option<&Vec>, - client_cert_types: Option<&Vec>, - ) -> Altered { - if let MessagePayload::Handshake { parsed, encoded } = &mut msg.payload { - if let HandshakePayload::ClientHello(ch) = &mut parsed.payload { - for extension in ch.extensions.iter_mut() { - if let ClientExtension::ClientCertTypes(cert_type) = extension { - if let Some(client_cert_types) = client_cert_types { - cert_type.clear(); - if !client_cert_types.is_empty() { - cert_type.extend_from_slice(client_cert_types) - } - } - }; - if let ClientExtension::ServerCertTypes(cert_type) = extension { - if let Some(server_cert_types) = server_cert_types { - cert_type.clear(); - if !server_cert_types.is_empty() { - cert_type.extend_from_slice(server_cert_types) - } - } - }; - } - } - *encoded = Payload::new(parsed.get_encoding()); - } - Altered::InPlace - } - - fn alter_server_hello_message( - msg: &mut Message, - server_cert_type: Option<&CertificateType>, - client_cert_type: Option<&CertificateType>, - cipher_suite: SupportedCipherSuite, - keylog_to_vec: &Arc, - ) -> Altered { - if msg.payload.content_type() != ContentType::ApplicationData { - // transfer_altered will forward multiple messages, but we are only interested in - // application data, which contains the server's encrypted extensions - return Altered::InPlace; - } - - // Derive Encrypter and Decrypter from the keylog and cipher suite - let (mut encrypter, mut decrypter) = - derive_message_encrypter_and_decrypter(cipher_suite.tls13().unwrap(), keylog_to_vec); - - // Decrypt raw payload - let mut raw_payload = Vec::new(); - msg.payload.encode(&mut raw_payload); - let mut bytes = raw_payload.clone(); - let incoming = InboundOpaqueMessage::new( - ContentType::ApplicationData, - ProtocolVersion::TLSv1_3, - &mut bytes, - ); - let decrypted_msg = decrypter.decrypt(incoming, 0).unwrap(); - - // Manipulate Message - let mut msg = Message::try_from(decrypted_msg).unwrap(); - - let encoded = if let MessagePayload::Handshake { parsed, .. } = &mut msg.payload { - if let HandshakePayload::EncryptedExtensions(enc_ext) = &mut parsed.payload { - let mut sct_present = false; - let mut cct_present = false; - for extension in enc_ext.iter_mut() { - if let ServerExtension::ClientCertType(cert_type) = extension { - if let Some(cct) = client_cert_type { - *cert_type = *cct; - } - cct_present = true; - }; - if let ServerExtension::ServerCertType(cert_type) = extension { - if let Some(sct) = server_cert_type { - *cert_type = *sct; - } - sct_present = true; - }; - } - if !sct_present { - if let Some(sct) = server_cert_type { - enc_ext.push(ServerExtension::ServerCertType(*sct)); - } - } - if !cct_present { - if let Some(cct) = client_cert_type { - enc_ext.push(ServerExtension::ClientCertType(*cct)); - } - } - } - Payload::new(parsed.get_encoding()) - } else { - panic!("Expected to succesfully encode handshake message"); - }; - - // // Re-encrypt - let outgoing = OutboundPlainMessage { - typ: ContentType::Handshake, - version: ProtocolVersion::TLSv1_3, - payload: OutboundChunks::Single(encoded.bytes()), - }; - Altered::Raw( - encrypter - .encrypt(outgoing, 0) - .unwrap() - .encode(), - ) - } - - fn derive_message_encrypter_and_decrypter( - cipher_suite: &Tls13CipherSuite, - keylog_to_vec: &Arc, - ) -> (Box, Box) { - let keylog_vec = keylog_to_vec.take(); - let keylog_item = keylog_vec - .iter() - .find(|item| item.label == "SERVER_HANDSHAKE_TRAFFIC_SECRET") - .unwrap(); - let expander = cipher_suite - .hkdf_provider - .expander_for_okm(&OkmBlock::new(&keylog_item.secret)); - - // Derive Encrypter - let key = derive_traffic_key(expander.as_ref(), cipher_suite.aead_alg); - let iv = derive_traffic_iv(expander.as_ref()); - let encrypter = cipher_suite.aead_alg.encrypter(key, iv); - - // Derive Decrypter - let key = derive_traffic_key(expander.as_ref(), cipher_suite.aead_alg); - let iv = derive_traffic_iv(expander.as_ref()); - let decrypter = cipher_suite.aead_alg.decrypter(key, iv); - - (encrypter, decrypter) - } -} - -fn alpn_test_error( - server_protos: Vec>, - client_protos: Vec>, - agreed: Option<&[u8]>, - expected_error: Option, -) { - let mut server_config = make_server_config(KeyType::Rsa2048); - server_config.alpn_protocols = server_protos; - - let server_config = Arc::new(server_config); - - for version in rustls::ALL_VERSIONS { - let mut client_config = make_client_config_with_versions(KeyType::Rsa2048, &[version]); - client_config - .alpn_protocols - .clone_from(&client_protos); - - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - - assert_eq!(client.alpn_protocol(), None); - assert_eq!(server.alpn_protocol(), None); - let error = do_handshake_until_error(&mut client, &mut server); - assert_eq!(client.alpn_protocol(), agreed); - assert_eq!(server.alpn_protocol(), agreed); - assert_eq!(error.err(), expected_error); - } -} - -fn alpn_test(server_protos: Vec>, client_protos: Vec>, agreed: Option<&[u8]>) { - alpn_test_error(server_protos, client_protos, agreed, None) -} - -#[test] -fn alpn() { - // no support - alpn_test(vec![], vec![], None); - - // server support - alpn_test(vec![b"server-proto".to_vec()], vec![], None); - - // client support - alpn_test(vec![], vec![b"client-proto".to_vec()], None); - - // no overlap - alpn_test_error( - vec![b"server-proto".to_vec()], - vec![b"client-proto".to_vec()], - None, - Some(ErrorFromPeer::Server(Error::NoApplicationProtocol)), - ); - - // server chooses preference - alpn_test( - vec![b"server-proto".to_vec(), b"client-proto".to_vec()], - vec![b"client-proto".to_vec(), b"server-proto".to_vec()], - Some(b"server-proto"), - ); - - // case sensitive - alpn_test_error( - vec![b"PROTO".to_vec()], - vec![b"proto".to_vec()], - None, - Some(ErrorFromPeer::Server(Error::NoApplicationProtocol)), - ); -} - -fn version_test( - client_versions: &[&'static rustls::SupportedProtocolVersion], - server_versions: &[&'static rustls::SupportedProtocolVersion], - result: Option, -) { - let client_versions = if client_versions.is_empty() { - rustls::ALL_VERSIONS - } else { - client_versions - }; - let server_versions = if server_versions.is_empty() { - rustls::ALL_VERSIONS - } else { - server_versions - }; - - let client_config = make_client_config_with_versions(KeyType::Rsa2048, client_versions); - let server_config = make_server_config_with_versions(KeyType::Rsa2048, server_versions); - - println!( - "version {:?} {:?} -> {:?}", - client_versions, server_versions, result - ); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - - assert_eq!(client.protocol_version(), None); - assert_eq!(server.protocol_version(), None); - if result.is_none() { - let err = do_handshake_until_error(&mut client, &mut server); - assert!(err.is_err()); - } else { - do_handshake(&mut client, &mut server); - assert_eq!(client.protocol_version(), result); - assert_eq!(server.protocol_version(), result); - } -} - -#[test] -fn versions() { - // default -> 1.3 - version_test(&[], &[], Some(ProtocolVersion::TLSv1_3)); - - // client default, server 1.2 -> 1.2 - #[cfg(feature = "tls12")] - version_test( - &[], - &[&rustls::version::TLS12], - Some(ProtocolVersion::TLSv1_2), - ); - - // client 1.2, server default -> 1.2 - #[cfg(feature = "tls12")] - version_test( - &[&rustls::version::TLS12], - &[], - Some(ProtocolVersion::TLSv1_2), - ); - - // client 1.2, server 1.3 -> fail - #[cfg(feature = "tls12")] - version_test(&[&rustls::version::TLS12], &[&rustls::version::TLS13], None); - - // client 1.3, server 1.2 -> fail - #[cfg(feature = "tls12")] - version_test(&[&rustls::version::TLS13], &[&rustls::version::TLS12], None); - - // client 1.3, server 1.2+1.3 -> 1.3 - #[cfg(feature = "tls12")] - version_test( - &[&rustls::version::TLS13], - &[&rustls::version::TLS12, &rustls::version::TLS13], - Some(ProtocolVersion::TLSv1_3), - ); - - // client 1.2+1.3, server 1.2 -> 1.2 - #[cfg(feature = "tls12")] - version_test( - &[&rustls::version::TLS13, &rustls::version::TLS12], - &[&rustls::version::TLS12], - Some(ProtocolVersion::TLSv1_2), - ); -} - -fn check_read(reader: &mut dyn io::Read, bytes: &[u8]) { - let mut buf = vec![0u8; bytes.len() + 1]; - assert_eq!(bytes.len(), reader.read(&mut buf).unwrap()); - assert_eq!(bytes, &buf[..bytes.len()]); -} - -fn check_read_err(reader: &mut dyn io::Read, err_kind: io::ErrorKind) { - let mut buf = vec![0u8; 1]; - let err = reader.read(&mut buf).unwrap_err(); - assert!(matches!(err, err if err.kind() == err_kind)) -} - -#[cfg(read_buf)] -fn check_read_buf(reader: &mut dyn io::Read, bytes: &[u8]) { - use core::io::BorrowedBuf; - use std::mem::MaybeUninit; - - let mut buf = [MaybeUninit::::uninit(); 128]; - let mut buf: BorrowedBuf<'_> = buf.as_mut_slice().into(); - reader.read_buf(buf.unfilled()).unwrap(); - assert_eq!(buf.filled(), bytes); -} - -#[cfg(read_buf)] -fn check_read_buf_err(reader: &mut dyn io::Read, err_kind: io::ErrorKind) { - use core::io::BorrowedBuf; - use std::mem::MaybeUninit; - - let mut buf = [MaybeUninit::::uninit(); 1]; - let mut buf: BorrowedBuf<'_> = buf.as_mut_slice().into(); - let err = reader - .read_buf(buf.unfilled()) - .unwrap_err(); - assert!(matches!(err, err if err.kind() == err_kind)) -} - -#[test] -fn config_builder_for_client_rejects_empty_kx_groups() { - assert_eq!( - ClientConfig::builder_with_provider( - CryptoProvider { - kx_groups: Vec::default(), - ..provider::default_provider() - } - .into() - ) - .with_safe_default_protocol_versions() - .err(), - Some(Error::General("no kx groups configured".into())) - ); -} - -#[test] -fn config_builder_for_client_rejects_empty_cipher_suites() { - assert_eq!( - ClientConfig::builder_with_provider( - CryptoProvider { - cipher_suites: Vec::default(), - ..provider::default_provider() - } - .into() - ) - .with_safe_default_protocol_versions() - .err(), - Some(Error::General("no usable cipher suites configured".into())) - ); -} - -#[cfg(feature = "tls12")] -#[test] -fn config_builder_for_client_rejects_incompatible_cipher_suites() { - assert_eq!( - ClientConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![cipher_suite::TLS13_AES_256_GCM_SHA384], - ..provider::default_provider() - } - .into() - ) - .with_protocol_versions(&[&rustls::version::TLS12]) - .err(), - Some(Error::General("no usable cipher suites configured".into())) - ); -} - -#[test] -fn config_builder_for_server_rejects_empty_kx_groups() { - assert_eq!( - ServerConfig::builder_with_provider( - CryptoProvider { - kx_groups: Vec::default(), - ..provider::default_provider() - } - .into() - ) - .with_safe_default_protocol_versions() - .err(), - Some(Error::General("no kx groups configured".into())) - ); -} - -#[test] -fn config_builder_for_server_rejects_empty_cipher_suites() { - assert_eq!( - ServerConfig::builder_with_provider( - CryptoProvider { - cipher_suites: Vec::default(), - ..provider::default_provider() - } - .into() - ) - .with_safe_default_protocol_versions() - .err(), - Some(Error::General("no usable cipher suites configured".into())) - ); -} - -#[cfg(feature = "tls12")] -#[test] -fn config_builder_for_server_rejects_incompatible_cipher_suites() { - assert_eq!( - ServerConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![cipher_suite::TLS13_AES_256_GCM_SHA384], - ..provider::default_provider() - } - .into() - ) - .with_protocol_versions(&[&rustls::version::TLS12]) - .err(), - Some(Error::General("no usable cipher suites configured".into())) - ); -} - -#[test] -fn config_builder_for_client_with_time() { - ClientConfig::builder_with_details( - provider::default_provider().into(), - Arc::new(rustls::time_provider::DefaultTimeProvider), - ) - .with_safe_default_protocol_versions() - .unwrap(); -} - -#[test] -fn config_builder_for_server_with_time() { - ServerConfig::builder_with_details( - provider::default_provider().into(), - Arc::new(rustls::time_provider::DefaultTimeProvider), - ) - .with_safe_default_protocol_versions() - .unwrap(); -} - -#[test] -fn buffered_client_data_sent() { - let server_config = Arc::new(make_server_config(KeyType::Rsa2048)); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(KeyType::Rsa2048, &[version]); - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - - assert_eq!(5, client.writer().write(b"hello").unwrap()); - - do_handshake(&mut client, &mut server); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - - check_read(&mut server.reader(), b"hello"); - } -} - -#[test] -fn buffered_server_data_sent() { - let server_config = Arc::new(make_server_config(KeyType::Rsa2048)); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(KeyType::Rsa2048, &[version]); - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - - assert_eq!(5, server.writer().write(b"hello").unwrap()); - - do_handshake(&mut client, &mut server); - transfer(&mut server, &mut client); - client.process_new_packets().unwrap(); - - check_read(&mut client.reader(), b"hello"); - } -} - -#[test] -fn buffered_both_data_sent() { - let server_config = Arc::new(make_server_config(KeyType::Rsa2048)); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(KeyType::Rsa2048, &[version]); - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - - assert_eq!( - 12, - server - .writer() - .write(b"from-server!") - .unwrap() - ); - assert_eq!( - 12, - client - .writer() - .write(b"from-client!") - .unwrap() - ); - - do_handshake(&mut client, &mut server); - - transfer(&mut server, &mut client); - client.process_new_packets().unwrap(); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - - check_read(&mut client.reader(), b"from-server!"); - check_read(&mut server.reader(), b"from-client!"); - } -} - -#[test] -fn client_can_get_server_cert() { - for kt in ALL_KEY_TYPES { - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(*kt, &[version]); - let (mut client, mut server) = - make_pair_for_configs(client_config, make_server_config(*kt)); - do_handshake(&mut client, &mut server); - - let certs = client.peer_certificates(); - assert_eq!(certs, Some(kt.get_chain().as_slice())); - } - } -} - -#[test] -fn client_can_get_server_cert_after_resumption() { - for kt in ALL_KEY_TYPES { - let server_config = make_server_config(*kt); - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(*kt, &[version]); - let (mut client, mut server) = - make_pair_for_configs(client_config.clone(), server_config.clone()); - do_handshake(&mut client, &mut server); - - let original_certs = client.peer_certificates(); - - let (mut client, mut server) = - make_pair_for_configs(client_config.clone(), server_config.clone()); - do_handshake(&mut client, &mut server); - - let resumed_certs = client.peer_certificates(); - - assert_eq!(original_certs, resumed_certs); - } - } -} - -#[test] -fn server_can_get_client_cert() { - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config_with_mandatory_client_auth(*kt)); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - do_handshake(&mut client, &mut server); - - let certs = server.peer_certificates(); - assert_eq!(certs, Some(kt.get_client_chain().as_slice())); - } - } -} - -#[test] -fn server_can_get_client_cert_after_resumption() { - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config_with_mandatory_client_auth(*kt)); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); - let client_config = Arc::new(client_config); - let (mut client, mut server) = - make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - let original_certs = server.peer_certificates(); - - let (mut client, mut server) = - make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - let resumed_certs = server.peer_certificates(); - assert_eq!(original_certs, resumed_certs); - } - } -} - -#[test] -fn resumption_combinations() { - let expected_kx = match provider_is_fips() { - true => NamedGroup::secp256r1, - false => NamedGroup::X25519, - }; - for kt in ALL_KEY_TYPES { - let server_config = make_server_config(*kt); - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(*kt, &[version]); - let (mut client, mut server) = - make_pair_for_configs(client_config.clone(), server_config.clone()); - do_handshake(&mut client, &mut server); - - assert_eq!(client.handshake_kind(), Some(HandshakeKind::Full)); - assert_eq!(server.handshake_kind(), Some(HandshakeKind::Full)); - assert_eq!( - client - .negotiated_key_exchange_group() - .unwrap() - .name(), - expected_kx - ); - assert_eq!( - server - .negotiated_key_exchange_group() - .unwrap() - .name(), - expected_kx - ); - - let (mut client, mut server) = - make_pair_for_configs(client_config.clone(), server_config.clone()); - do_handshake(&mut client, &mut server); - - assert_eq!(client.handshake_kind(), Some(HandshakeKind::Resumed)); - assert_eq!(server.handshake_kind(), Some(HandshakeKind::Resumed)); - if version.version == TLSv1_2 { - assert!(client - .negotiated_key_exchange_group() - .is_none()); - assert!(server - .negotiated_key_exchange_group() - .is_none()); - } else { - assert_eq!( - client - .negotiated_key_exchange_group() - .unwrap() - .name(), - expected_kx - ); - assert_eq!( - server - .negotiated_key_exchange_group() - .unwrap() - .name(), - expected_kx - ); - } - } - } -} - -#[test] -fn test_config_builders_debug() { - if !provider_is_ring() { - return; - } - - let b = ServerConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![cipher_suite::TLS13_CHACHA20_POLY1305_SHA256], - kx_groups: vec![provider::kx_group::X25519], - ..provider::default_provider() - } - .into(), - ); - let _ = format!("{:?}", b); - let b = server_config_builder_with_versions(&[&rustls::version::TLS13]); - let _ = format!("{:?}", b); - let b = b.with_no_client_auth(); - let _ = format!("{:?}", b); - - let b = ClientConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![cipher_suite::TLS13_CHACHA20_POLY1305_SHA256], - kx_groups: vec![provider::kx_group::X25519], - ..provider::default_provider() - } - .into(), - ); - let _ = format!("{:?}", b); - let b = client_config_builder_with_versions(&[&rustls::version::TLS13]); - let _ = format!("{:?}", b); -} - -/// Test that the server handles combination of `offer_client_auth()` returning true -/// and `client_auth_mandatory` returning `Some(false)`. This exercises both the -/// client's and server's ability to "recover" from the server asking for a client -/// certificate and not being given one. -#[test] -fn server_allow_any_anonymous_or_authenticated_client() { - let kt = KeyType::Rsa2048; - for client_cert_chain in [None, Some(kt.get_client_chain())] { - let client_auth_roots = get_client_root_store(kt); - let client_auth = webpki_client_verifier_builder(client_auth_roots.clone()) - .allow_unauthenticated() - .build() - .unwrap(); - - let server_config = server_config_builder() - .with_client_cert_verifier(client_auth) - .with_single_cert(kt.get_chain(), kt.get_key()) - .unwrap(); - let server_config = Arc::new(server_config); - - for version in rustls::ALL_VERSIONS { - let client_config = if client_cert_chain.is_some() { - make_client_config_with_versions_with_auth(kt, &[version]) - } else { - make_client_config_with_versions(kt, &[version]) - }; - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - do_handshake(&mut client, &mut server); - - let certs = server.peer_certificates(); - assert_eq!(certs, client_cert_chain.as_deref()); - } - } -} - -fn check_read_and_close(reader: &mut dyn io::Read, expect: &[u8]) { - check_read(reader, expect); - assert!(matches!(reader.read(&mut [0u8; 5]), Ok(0))); -} - -#[test] -fn server_close_notify() { - let kt = KeyType::Rsa2048; - let server_config = Arc::new(make_server_config_with_mandatory_client_auth(kt)); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions_with_auth(kt, &[version]); - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - do_handshake(&mut client, &mut server); - - // check that alerts don't overtake appdata - assert_eq!( - 12, - server - .writer() - .write(b"from-server!") - .unwrap() - ); - assert_eq!( - 12, - client - .writer() - .write(b"from-client!") - .unwrap() - ); - server.send_close_notify(); - - transfer(&mut server, &mut client); - let io_state = client.process_new_packets().unwrap(); - assert!(io_state.peer_has_closed()); - check_read_and_close(&mut client.reader(), b"from-server!"); - - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - check_read(&mut server.reader(), b"from-client!"); - } -} - -#[test] -fn client_close_notify() { - let kt = KeyType::Rsa2048; - let server_config = Arc::new(make_server_config_with_mandatory_client_auth(kt)); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions_with_auth(kt, &[version]); - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - do_handshake(&mut client, &mut server); - - // check that alerts don't overtake appdata - assert_eq!( - 12, - server - .writer() - .write(b"from-server!") - .unwrap() - ); - assert_eq!( - 12, - client - .writer() - .write(b"from-client!") - .unwrap() - ); - client.send_close_notify(); - - transfer(&mut client, &mut server); - let io_state = server.process_new_packets().unwrap(); - assert!(io_state.peer_has_closed()); - check_read_and_close(&mut server.reader(), b"from-client!"); - - transfer(&mut server, &mut client); - client.process_new_packets().unwrap(); - check_read(&mut client.reader(), b"from-server!"); - } -} - -#[test] -fn server_closes_uncleanly() { - let kt = KeyType::Rsa2048; - let server_config = Arc::new(make_server_config(kt)); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(kt, &[version]); - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - do_handshake(&mut client, &mut server); - - // check that unclean EOF reporting does not overtake appdata - assert_eq!( - 12, - server - .writer() - .write(b"from-server!") - .unwrap() - ); - assert_eq!( - 12, - client - .writer() - .write(b"from-client!") - .unwrap() - ); - - transfer(&mut server, &mut client); - transfer_eof(&mut client); - let io_state = client.process_new_packets().unwrap(); - assert!(!io_state.peer_has_closed()); - check_read(&mut client.reader(), b"from-server!"); - - check_read_err( - &mut client.reader() as &mut dyn io::Read, - io::ErrorKind::UnexpectedEof, - ); - - // may still transmit pending frames - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - check_read(&mut server.reader(), b"from-client!"); - } -} - -#[test] -fn client_closes_uncleanly() { - let kt = KeyType::Rsa2048; - let server_config = Arc::new(make_server_config(kt)); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(kt, &[version]); - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - do_handshake(&mut client, &mut server); - - // check that unclean EOF reporting does not overtake appdata - assert_eq!( - 12, - server - .writer() - .write(b"from-server!") - .unwrap() - ); - assert_eq!( - 12, - client - .writer() - .write(b"from-client!") - .unwrap() - ); - - transfer(&mut client, &mut server); - transfer_eof(&mut server); - let io_state = server.process_new_packets().unwrap(); - assert!(!io_state.peer_has_closed()); - check_read(&mut server.reader(), b"from-client!"); - - check_read_err( - &mut server.reader() as &mut dyn io::Read, - io::ErrorKind::UnexpectedEof, - ); - - // may still transmit pending frames - transfer(&mut server, &mut client); - client.process_new_packets().unwrap(); - check_read(&mut client.reader(), b"from-server!"); - } -} - -#[test] -fn test_tls13_valid_early_plaintext_alert() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - - // Perform the start of a TLS 1.3 handshake, sending a client hello to the server. - // The client will not have written a CCS or any encrypted messages to the server yet. - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - - // Inject a plaintext alert from the client. The server should accept this since: - // * It hasn't decrypted any messages from the peer yet. - // * The message content type is Alert. - // * The payload size is indicative of a plaintext alert message. - // * The negotiated protocol version is TLS 1.3. - server - .read_tls(&mut io::Cursor::new(&build_alert( - AlertLevel::Fatal, - AlertDescription::UnknownCA, - &[], - ))) - .unwrap(); - - // The server should process the plaintext alert without error. - assert_eq!( - server.process_new_packets(), - Err(Error::AlertReceived(AlertDescription::UnknownCA)), - ); -} - -#[test] -fn test_tls13_too_short_early_plaintext_alert() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - - // Perform the start of a TLS 1.3 handshake, sending a client hello to the server. - // The client will not have written a CCS or any encrypted messages to the server yet. - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - - // Inject a plaintext alert from the client. The server should attempt to decrypt this message - // because the payload length is too large to be considered an early plaintext alert. - server - .read_tls(&mut io::Cursor::new(&build_alert( - AlertLevel::Fatal, - AlertDescription::UnknownCA, - &[0xff], - ))) - .unwrap(); - - // The server should produce a decrypt error trying to decrypt the plaintext alert. - assert_eq!(server.process_new_packets(), Err(Error::DecryptError),); -} - -#[test] -fn test_tls13_late_plaintext_alert() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - - // Complete a bi-directional TLS1.3 handshake. After this point no plaintext messages - // should occur. - do_handshake(&mut client, &mut server); - - // Inject a plaintext alert from the client. The server should attempt to decrypt this message. - server - .read_tls(&mut io::Cursor::new(&build_alert( - AlertLevel::Fatal, - AlertDescription::UnknownCA, - &[], - ))) - .unwrap(); - - // The server should produce a decrypt error, trying to decrypt a plaintext alert. - assert_eq!(server.process_new_packets(), Err(Error::DecryptError)); -} - -fn build_alert(level: AlertLevel, desc: AlertDescription, suffix: &[u8]) -> Vec { - let mut v = vec![ContentType::Alert.into()]; - ProtocolVersion::TLSv1_2.encode(&mut v); - ((2 + suffix.len()) as u16).encode(&mut v); - level.encode(&mut v); - desc.encode(&mut v); - v.extend_from_slice(suffix); - v -} - -#[derive(Default, Debug)] -struct ServerCheckCertResolve { - expected_sni: Option, - expected_sigalgs: Option>, - expected_alpn: Option>>, - expected_cipher_suites: Option>, - expected_server_cert_types: Option>, - expected_client_cert_types: Option>, -} - -impl ResolvesServerCert for ServerCheckCertResolve { - fn resolve(&self, client_hello: ClientHello) -> Option> { - if client_hello - .signature_schemes() - .is_empty() - { - panic!("no signature schemes shared by client"); - } - - if client_hello.cipher_suites().is_empty() { - panic!("no cipher suites shared by client"); - } - - if let Some(expected_sni) = &self.expected_sni { - let sni: &str = client_hello - .server_name() - .expect("sni unexpectedly absent"); - assert_eq!(expected_sni, sni); - } - - if let Some(expected_sigalgs) = &self.expected_sigalgs { - assert_eq!( - expected_sigalgs, - client_hello.signature_schemes(), - "unexpected signature schemes" - ); - } - - if let Some(expected_alpn) = &self.expected_alpn { - let alpn = client_hello - .alpn() - .expect("alpn unexpectedly absent") - .collect::>(); - assert_eq!(alpn.len(), expected_alpn.len()); - - for (got, wanted) in alpn.iter().zip(expected_alpn.iter()) { - assert_eq!(got, &wanted.as_slice()); - } - } - - if let Some(expected_cipher_suites) = &self.expected_cipher_suites { - assert_eq!( - expected_cipher_suites, - client_hello.cipher_suites(), - "unexpected cipher suites" - ); - } - - if let Some(expected_server_cert) = &self.expected_server_cert_types { - assert_eq!( - expected_server_cert, - client_hello - .server_cert_types() - .expect("Server cert types not present"), - "unexpected server cert" - ); - } - - if let Some(expected_client_cert) = &self.expected_client_cert_types { - assert_eq!( - expected_client_cert, - client_hello - .client_cert_types() - .expect("Client cert types not present"), - "unexpected client cert" - ); - } - - None - } -} - -#[test] -fn server_cert_resolve_with_sni() { - for kt in ALL_KEY_TYPES { - let client_config = make_client_config(*kt); - let mut server_config = make_server_config(*kt); - - server_config.cert_resolver = Arc::new(ServerCheckCertResolve { - expected_sni: Some("the-value-from-sni".into()), - ..Default::default() - }); - - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("the-value-from-sni")) - .unwrap(); - let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); - - let err = do_handshake_until_error(&mut client, &mut server); - assert!(err.is_err()); - } -} - -#[test] -fn server_cert_resolve_with_alpn() { - for kt in ALL_KEY_TYPES { - let mut client_config = make_client_config(*kt); - client_config.alpn_protocols = vec!["foo".into(), "bar".into()]; - - let mut server_config = make_server_config(*kt); - server_config.cert_resolver = Arc::new(ServerCheckCertResolve { - expected_alpn: Some(vec![b"foo".to_vec(), b"bar".to_vec()]), - ..Default::default() - }); - - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("sni-value")).unwrap(); - let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); - - let err = do_handshake_until_error(&mut client, &mut server); - assert!(err.is_err()); - } -} - -#[test] -fn client_trims_terminating_dot() { - for kt in ALL_KEY_TYPES { - let client_config = make_client_config(*kt); - let mut server_config = make_server_config(*kt); - - server_config.cert_resolver = Arc::new(ServerCheckCertResolve { - expected_sni: Some("some-host.com".into()), - ..Default::default() - }); - - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("some-host.com.")).unwrap(); - let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); - - let err = do_handshake_until_error(&mut client, &mut server); - assert!(err.is_err()); - } -} - -#[test] -fn server_ignores_sni_with_ip_address() { - fn insert_ip_address_server_name(msg: &mut Message) -> Altered { - alter_sni_extension( - msg, - |snr| { - snr.clear(); - snr.push(ServerNameExtensionItem::read_bytes(b"\x00\x00\x071.1.1.1").unwrap()); - }, - |parsed, _encoded| Payload::new(parsed.get_encoding()), - ) - } - - check_sni_error( - insert_ip_address_server_name, - Error::General("no server certificate chain resolved".to_string()), - ); -} - -#[test] -fn server_rejects_sni_with_illegal_dns_name() { - fn insert_illegal_server_name(msg: &mut Message) -> Altered { - alter_sni_extension( - msg, - |_| (), - |_, encoded| { - // replace "localhost" with invalid DNS name - let mut altered = encoded.clone().into_vec(); - let needle = b"localhost"; - let index = altered - .windows(needle.len()) - .position(|window| window == needle) - .unwrap(); - altered[index..index + needle.len()].copy_from_slice(b"ab@cd.com"); - Payload::new(altered) - }, - ) - } - - check_sni_error( - insert_illegal_server_name, - Error::InvalidMessage(InvalidMessage::InvalidServerName), - ); -} - -fn alter_sni_extension( - msg: &mut Message, - alter_inner: impl Fn(&mut Vec), - alter_encoding: impl Fn(&mut HandshakeMessagePayload, &mut Payload) -> Payload<'static>, -) -> Altered { - if let MessagePayload::Handshake { parsed, encoded } = &mut msg.payload { - if let HandshakePayload::ClientHello(ch) = &mut parsed.payload { - for mut ext in ch.extensions.iter_mut() { - if let ClientExtension::ServerName(snr) = &mut ext { - alter_inner(snr); - } - } - *encoded = alter_encoding(parsed, encoded); - } - } - - Altered::InPlace -} - -fn check_sni_error(alteration: impl Fn(&mut Message) -> Altered, expected_error: Error) { - for kt in ALL_KEY_TYPES { - let client_config = make_client_config(*kt); - let mut server_config = make_server_config(*kt); - - server_config.cert_resolver = Arc::new(ServerCheckNoSni {}); - - let client = - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let server = ServerConnection::new(Arc::new(server_config)).unwrap(); - let (mut client, mut server) = (client.into(), server.into()); - - transfer_altered(&mut client, &alteration, &mut server); - assert_eq!(server.process_new_packets(), Err(expected_error.clone()),); - - let rustls::Connection::Server(server_inner) = server else { - unreachable!(); - }; - assert_eq!(None, server_inner.server_name()); - } -} - -#[cfg(feature = "tls12")] -fn check_sigalgs_reduced_by_ciphersuite( - kt: KeyType, - suite: CipherSuite, - expected_sigalgs: Vec, -) { - let client_config = finish_client_config( - kt, - ClientConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![find_suite(suite)], - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - let mut server_config = make_server_config(kt); - - server_config.cert_resolver = Arc::new(ServerCheckCertResolve { - expected_sigalgs: Some(expected_sigalgs), - expected_cipher_suites: Some(vec![suite, CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV]), - ..Default::default() - }); - - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); - - let err = do_handshake_until_error(&mut client, &mut server); - assert!(err.is_err()); -} - -#[cfg(feature = "tls12")] -#[test] -fn server_cert_resolve_reduces_sigalgs_for_rsa_ciphersuite() { - check_sigalgs_reduced_by_ciphersuite( - KeyType::Rsa2048, - CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - vec![ - SignatureScheme::RSA_PSS_SHA512, - SignatureScheme::RSA_PSS_SHA384, - SignatureScheme::RSA_PSS_SHA256, - SignatureScheme::RSA_PKCS1_SHA512, - SignatureScheme::RSA_PKCS1_SHA384, - SignatureScheme::RSA_PKCS1_SHA256, - ], - ); -} - -#[cfg(feature = "tls12")] -#[test] -fn server_cert_resolve_reduces_sigalgs_for_ecdsa_ciphersuite() { - check_sigalgs_reduced_by_ciphersuite( - KeyType::EcdsaP256, - CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - if provider_is_aws_lc_rs() { - vec![ - SignatureScheme::ECDSA_NISTP384_SHA384, - SignatureScheme::ECDSA_NISTP256_SHA256, - SignatureScheme::ECDSA_NISTP521_SHA512, - SignatureScheme::ED25519, - ] - } else { - vec![ - SignatureScheme::ECDSA_NISTP384_SHA384, - SignatureScheme::ECDSA_NISTP256_SHA256, - SignatureScheme::ED25519, - ] - }, - ); -} - -#[derive(Debug)] -struct ServerCheckNoSni {} - -impl ResolvesServerCert for ServerCheckNoSni { - fn resolve(&self, client_hello: ClientHello) -> Option> { - assert!(client_hello.server_name().is_none()); - - None - } -} - -#[test] -fn client_with_sni_disabled_does_not_send_sni() { - for kt in ALL_KEY_TYPES { - let mut server_config = make_server_config(*kt); - server_config.cert_resolver = Arc::new(ServerCheckNoSni {}); - let server_config = Arc::new(server_config); - - for version in rustls::ALL_VERSIONS { - let mut client_config = make_client_config_with_versions(*kt, &[version]); - client_config.enable_sni = false; - - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("value-not-sent")) - .unwrap(); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - - let err = do_handshake_until_error(&mut client, &mut server); - assert!(err.is_err()); - } - } -} - -#[test] -fn client_checks_server_certificate_with_given_name() { - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config(*kt)); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(*kt, &[version]); - let mut client = ClientConnection::new( - Arc::new(client_config), - server_name("not-the-right-hostname.com"), - ) - .unwrap(); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - - let err = do_handshake_until_error(&mut client, &mut server); - assert_eq!( - err, - Err(ErrorFromPeer::Client(Error::InvalidCertificate( - CertificateError::NotValidForName - ))) - ); - } - } -} - -#[test] -fn client_checks_server_certificate_with_given_ip_address() { - fn check_server_name( - client_config: Arc, - server_config: Arc, - name: &'static str, - ) -> Result<(), ErrorFromPeer> { - let mut client = ClientConnection::new(client_config, server_name(name)).unwrap(); - let mut server = ServerConnection::new(server_config).unwrap(); - do_handshake_until_error(&mut client, &mut server) - } - - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config(*kt)); - - for version in rustls::ALL_VERSIONS { - let client_config = Arc::new(make_client_config_with_versions(*kt, &[version])); - - // positive ipv4 case - assert_eq!( - check_server_name(client_config.clone(), server_config.clone(), "198.51.100.1"), - Ok(()), - ); - - // negative ipv4 case - assert_eq!( - check_server_name(client_config.clone(), server_config.clone(), "198.51.100.2"), - Err(ErrorFromPeer::Client(Error::InvalidCertificate( - CertificateError::NotValidForName - ))) - ); - - // positive ipv6 case - assert_eq!( - check_server_name(client_config.clone(), server_config.clone(), "2001:db8::1"), - Ok(()), - ); - - // negative ipv6 case - assert_eq!( - check_server_name(client_config.clone(), server_config.clone(), "2001:db8::2"), - Err(ErrorFromPeer::Client(Error::InvalidCertificate( - CertificateError::NotValidForName - ))) - ); - } - } -} - -#[test] -fn client_check_server_certificate_ee_revoked() { - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config(*kt)); - - // Setup a server verifier that will check the EE certificate's revocation status. - let crls = vec![kt.end_entity_crl()]; - let builder = webpki_server_verifier_builder(get_client_root_store(*kt)) - .with_crls(crls) - .only_check_end_entity_revocation(); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_verifier(&[version], builder.clone()); - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - - // We expect the handshake to fail since the server's EE certificate is revoked. - let err = do_handshake_until_error(&mut client, &mut server); - assert_eq!( - err, - Err(ErrorFromPeer::Client(Error::InvalidCertificate( - CertificateError::Revoked - ))) - ); - } - } -} - -#[test] -fn client_check_server_certificate_ee_unknown_revocation() { - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config(*kt)); - - // Setup a server verifier builder that will check the EE certificate's revocation status, but not - // allow unknown revocation status (the default). We'll provide CRLs that are not relevant - // to the EE cert to ensure its status is unknown. - let unrelated_crls = vec![kt.intermediate_crl()]; - let forbid_unknown_verifier = webpki_server_verifier_builder(get_client_root_store(*kt)) - .with_crls(unrelated_crls.clone()) - .only_check_end_entity_revocation(); - - // Also set up a verifier builder that will allow unknown revocation status. - let allow_unknown_verifier = webpki_server_verifier_builder(get_client_root_store(*kt)) - .with_crls(unrelated_crls) - .only_check_end_entity_revocation() - .allow_unknown_revocation_status(); - - for version in rustls::ALL_VERSIONS { - let client_config = - make_client_config_with_verifier(&[version], forbid_unknown_verifier.clone()); - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - - // We expect if we use the forbid_unknown_verifier that the handshake will fail since the - // server's EE certificate's revocation status is unknown given the CRLs we've provided. - let err = do_handshake_until_error(&mut client, &mut server); - assert!(matches!( - err, - Err(ErrorFromPeer::Client(Error::InvalidCertificate( - CertificateError::UnknownRevocationStatus - ))) - )); - - // We expect if we use the allow_unknown_verifier that the handshake will not fail. - let client_config = - make_client_config_with_verifier(&[version], allow_unknown_verifier.clone()); - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - let res = do_handshake_until_error(&mut client, &mut server); - assert!(res.is_ok()); - } - } -} - -#[test] -fn client_check_server_certificate_intermediate_revoked() { - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config(*kt)); - - // Setup a server verifier builder that will check the full chain revocation status against a CRL - // that marks the intermediate certificate as revoked. We allow unknown revocation status - // so the EE cert's unknown status doesn't cause an error. - let crls = vec![kt.intermediate_crl()]; - let full_chain_verifier_builder = - webpki_server_verifier_builder(get_client_root_store(*kt)) - .with_crls(crls.clone()) - .allow_unknown_revocation_status(); - - // Also set up a verifier builder that will use the same CRL, but only check the EE certificate - // revocation status. - let ee_verifier_builder = webpki_server_verifier_builder(get_client_root_store(*kt)) - .with_crls(crls.clone()) - .only_check_end_entity_revocation() - .allow_unknown_revocation_status(); - - for version in rustls::ALL_VERSIONS { - let client_config = - make_client_config_with_verifier(&[version], full_chain_verifier_builder.clone()); - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - - // We expect the handshake to fail when using the full chain verifier since the intermediate's - // EE certificate is revoked. - let err = do_handshake_until_error(&mut client, &mut server); - assert_eq!( - err, - Err(ErrorFromPeer::Client(Error::InvalidCertificate( - CertificateError::Revoked - ))) - ); - - let client_config = - make_client_config_with_verifier(&[version], ee_verifier_builder.clone()); - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - // We expect the handshake to succeed when we use the verifier that only checks the EE certificate - // revocation status. The revoked intermediate status should not be checked. - let res = do_handshake_until_error(&mut client, &mut server); - assert!(res.is_ok()) - } - } -} - -#[test] -fn client_check_server_certificate_ee_crl_expired() { - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config(*kt)); - - // Setup a server verifier that will check the EE certificate's revocation status, with CRL expiration enforced. - let crls = vec![kt.end_entity_crl_expired()]; - let enforce_expiration_builder = webpki_server_verifier_builder(get_client_root_store(*kt)) - .with_crls(crls) - .only_check_end_entity_revocation() - .enforce_revocation_expiration(); - - // Also setup a server verifier without CRL expiration enforced. - let crls = vec![kt.end_entity_crl_expired()]; - let ignore_expiration_builder = webpki_server_verifier_builder(get_client_root_store(*kt)) - .with_crls(crls) - .only_check_end_entity_revocation(); - - for version in rustls::ALL_VERSIONS { - let client_config = - make_client_config_with_verifier(&[version], enforce_expiration_builder.clone()); - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - - // We expect the handshake to fail since the CRL is expired. - let err = do_handshake_until_error(&mut client, &mut server); - assert_eq!( - err, - Err(ErrorFromPeer::Client(Error::InvalidCertificate( - CertificateError::ExpiredRevocationList - ))) - ); - - let client_config = - make_client_config_with_verifier(&[version], ignore_expiration_builder.clone()); - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - - // We expect the handshake to succeed when CRL expiration is ignored. - let res = do_handshake_until_error(&mut client, &mut server); - assert!(res.is_ok()) - } - } -} - -/// Simple smoke-test of the webpki verify_server_cert_signed_by_trust_anchor helper API. -/// This public API is intended to be used by consumers implementing their own verifier and -/// so isn't used by the other existing verifier tests. -#[test] -fn client_check_server_certificate_helper_api() { - for kt in ALL_KEY_TYPES { - let chain = kt.get_chain(); - let correct_roots = get_client_root_store(*kt); - let incorrect_roots = get_client_root_store(match kt { - KeyType::Rsa2048 => KeyType::EcdsaP256, - _ => KeyType::Rsa2048, - }); - // Using the correct trust anchors, we should verify without error. - assert!(verify_server_cert_signed_by_trust_anchor( - &ParsedCertificate::try_from(chain.first().unwrap()).unwrap(), - &correct_roots, - &[chain.get(1).unwrap().clone()], - UnixTime::now(), - webpki::ALL_VERIFICATION_ALGS, - ) - .is_ok()); - // Using the wrong trust anchors, we should get the expected error. - assert_eq!( - verify_server_cert_signed_by_trust_anchor( - &ParsedCertificate::try_from(chain.first().unwrap()).unwrap(), - &incorrect_roots, - &[chain.get(1).unwrap().clone()], - UnixTime::now(), - webpki::ALL_VERIFICATION_ALGS, - ) - .unwrap_err(), - Error::InvalidCertificate(CertificateError::UnknownIssuer) - ); - } -} - -#[derive(Debug)] -struct ClientCheckCertResolve { - query_count: AtomicUsize, - expect_queries: usize, - expect_root_hint_subjects: Vec>, - expect_sigschemes: Vec, -} - -impl ClientCheckCertResolve { - fn new( - expect_queries: usize, - expect_root_hint_subjects: Vec>, - expect_sigschemes: Vec, - ) -> Self { - Self { - query_count: AtomicUsize::new(0), - expect_queries, - expect_root_hint_subjects, - expect_sigschemes, - } - } -} - -impl Drop for ClientCheckCertResolve { - fn drop(&mut self) { - if !std::thread::panicking() { - let count = self.query_count.load(Ordering::SeqCst); - assert_eq!(count, self.expect_queries); - } - } -} - -impl ResolvesClientCert for ClientCheckCertResolve { - fn resolve( - &self, - root_hint_subjects: &[&[u8]], - sigschemes: &[SignatureScheme], - ) -> Option> { - self.query_count - .fetch_add(1, Ordering::SeqCst); - - if sigschemes.is_empty() { - panic!("no signature schemes shared by server"); - } - - assert_eq!(sigschemes, self.expect_sigschemes); - assert_eq!(root_hint_subjects, self.expect_root_hint_subjects); - - None - } - - fn has_certs(&self) -> bool { - true - } -} - -fn test_client_cert_resolve( - key_type: KeyType, - server_config: Arc, - expected_root_hint_subjects: Vec>, -) { - for version in rustls::ALL_VERSIONS { - println!("{:?} {:?}:", version.version, key_type); - - let mut client_config = make_client_config_with_versions(key_type, &[version]); - client_config.client_auth_cert_resolver = Arc::new(ClientCheckCertResolve::new( - 1, - expected_root_hint_subjects.clone(), - default_signature_schemes(version.version), - )); - - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - - assert_eq!( - do_handshake_until_error(&mut client, &mut server), - Err(ErrorFromPeer::Server(Error::NoCertificatesPresented)) - ); - } -} - -fn default_signature_schemes(version: ProtocolVersion) -> Vec { - let mut v = vec![]; - - v.extend_from_slice(&[ - SignatureScheme::ECDSA_NISTP384_SHA384, - SignatureScheme::ECDSA_NISTP256_SHA256, - SignatureScheme::ED25519, - SignatureScheme::RSA_PSS_SHA512, - SignatureScheme::RSA_PSS_SHA384, - SignatureScheme::RSA_PSS_SHA256, - ]); - - if provider_is_aws_lc_rs() { - v.insert(2, SignatureScheme::ECDSA_NISTP521_SHA512); - } - - if version == ProtocolVersion::TLSv1_2 { - v.extend_from_slice(&[ - SignatureScheme::RSA_PKCS1_SHA512, - SignatureScheme::RSA_PKCS1_SHA384, - SignatureScheme::RSA_PKCS1_SHA256, - ]); - } - - v -} - -#[test] -fn client_cert_resolve_default() { - // Test that in the default configuration that a client cert resolver gets the expected - // CA subject hints, and supported signature algorithms. - for key_type in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config_with_mandatory_client_auth(*key_type)); - - // In a default configuration we expect that the verifier's trust anchors are used - // for the hint subjects. - let expected_root_hint_subjects = vec![key_type - .ca_distinguished_name() - .to_vec()]; - - test_client_cert_resolve(*key_type, server_config, expected_root_hint_subjects); - } -} - -#[test] -fn client_cert_resolve_server_no_hints() { - // Test that a server can provide no hints and the client cert resolver gets the expected - // arguments. - for key_type in ALL_KEY_TYPES { - // Build a verifier with no hint subjects. - let verifier = webpki_client_verifier_builder(get_client_root_store(*key_type)) - .clear_root_hint_subjects(); - let server_config = make_server_config_with_client_verifier(*key_type, verifier); - let expected_root_hint_subjects = Vec::default(); // no hints expected. - test_client_cert_resolve(*key_type, server_config.into(), expected_root_hint_subjects); - } -} - -#[test] -fn client_cert_resolve_server_added_hint() { - // Test that a server can add an extra subject above/beyond those found in its trust store - // and the client cert resolver gets the expected arguments. - let extra_name = b"0\x1a1\x180\x16\x06\x03U\x04\x03\x0c\x0fponyland IDK CA".to_vec(); - for key_type in ALL_KEY_TYPES { - let expected_hint_subjects = vec![ - key_type - .ca_distinguished_name() - .to_vec(), - extra_name.clone(), - ]; - // Create a verifier that adds the extra_name as a hint subject in addition to the ones - // from the root cert store. - let verifier = webpki_client_verifier_builder(get_client_root_store(*key_type)) - .add_root_hint_subjects([DistinguishedName::from(extra_name.clone())].into_iter()); - let server_config = make_server_config_with_client_verifier(*key_type, verifier); - test_client_cert_resolve(*key_type, server_config.into(), expected_hint_subjects); - } -} - -#[test] -fn client_auth_works() { - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config_with_mandatory_client_auth(*kt)); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - do_handshake(&mut client, &mut server); - } - } -} - -#[test] -fn client_mandatory_auth_client_revocation_works() { - for kt in ALL_KEY_TYPES { - // Create a server configuration that includes a CRL that specifies the client certificate - // is revoked. - let relevant_crls = vec![kt.client_crl()]; - // Only check the EE certificate status. See client_mandatory_auth_intermediate_revocation_works - // for testing revocation status of the whole chain. - let ee_verifier_builder = webpki_client_verifier_builder(get_client_root_store(*kt)) - .with_crls(relevant_crls) - .only_check_end_entity_revocation(); - let revoked_server_config = Arc::new(make_server_config_with_client_verifier( - *kt, - ee_verifier_builder, - )); - - // Create a server configuration that includes a CRL that doesn't cover the client certificate, - // and uses the default behaviour of treating unknown revocation status as an error. - let unrelated_crls = vec![kt.intermediate_crl()]; - let ee_verifier_builder = webpki_client_verifier_builder(get_client_root_store(*kt)) - .with_crls(unrelated_crls.clone()) - .only_check_end_entity_revocation(); - let missing_client_crl_server_config = Arc::new(make_server_config_with_client_verifier( - *kt, - ee_verifier_builder, - )); - - // Create a server configuration that includes a CRL that doesn't cover the client certificate, - // but change the builder to allow unknown revocation status. - let ee_verifier_builder = webpki_client_verifier_builder(get_client_root_store(*kt)) - .with_crls(unrelated_crls.clone()) - .only_check_end_entity_revocation() - .allow_unknown_revocation_status(); - let allow_missing_client_crl_server_config = Arc::new( - make_server_config_with_client_verifier(*kt, ee_verifier_builder), - ); - - for version in rustls::ALL_VERSIONS { - // Connecting to the server with a CRL that indicates the client certificate is revoked - // should fail with the expected error. - let client_config = - Arc::new(make_client_config_with_versions_with_auth(*kt, &[version])); - let (mut client, mut server) = - make_pair_for_arc_configs(&client_config, &revoked_server_config); - let err = do_handshake_until_error(&mut client, &mut server); - assert_eq!( - err, - Err(ErrorFromPeer::Server(Error::InvalidCertificate( - CertificateError::Revoked - ))) - ); - // Connecting to the server missing CRL information for the client certificate should - // fail with the expected unknown revocation status error. - let (mut client, mut server) = - make_pair_for_arc_configs(&client_config, &missing_client_crl_server_config); - let res = do_handshake_until_error(&mut client, &mut server); - assert!(matches!( - res, - Err(ErrorFromPeer::Server(Error::InvalidCertificate( - CertificateError::UnknownRevocationStatus - ))) - )); - // Connecting to the server missing CRL information for the client should not error - // if the server's verifier allows unknown revocation status. - let (mut client, mut server) = - make_pair_for_arc_configs(&client_config, &allow_missing_client_crl_server_config); - let res = do_handshake_until_error(&mut client, &mut server); - assert!(res.is_ok()); - } - } -} - -#[test] -fn client_mandatory_auth_intermediate_revocation_works() { - for kt in ALL_KEY_TYPES { - // Create a server configuration that includes a CRL that specifies the intermediate certificate - // is revoked. We check the full chain for revocation status (default), and allow unknown - // revocation status so the EE's unknown revocation status isn't an error. - let crls = vec![kt.intermediate_crl()]; - let full_chain_verifier_builder = - webpki_client_verifier_builder(get_client_root_store(*kt)) - .with_crls(crls.clone()) - .allow_unknown_revocation_status(); - let full_chain_server_config = Arc::new(make_server_config_with_client_verifier( - *kt, - full_chain_verifier_builder, - )); - - // Also create a server configuration that uses the same CRL, but that only checks the EE - // cert revocation status. - let ee_only_verifier_builder = webpki_client_verifier_builder(get_client_root_store(*kt)) - .with_crls(crls) - .only_check_end_entity_revocation() - .allow_unknown_revocation_status(); - let ee_server_config = Arc::new(make_server_config_with_client_verifier( - *kt, - ee_only_verifier_builder, - )); - - for version in rustls::ALL_VERSIONS { - // When checking the full chain, we expect an error - the intermediate is revoked. - let client_config = - Arc::new(make_client_config_with_versions_with_auth(*kt, &[version])); - let (mut client, mut server) = - make_pair_for_arc_configs(&client_config, &full_chain_server_config); - let err = do_handshake_until_error(&mut client, &mut server); - assert_eq!( - err, - Err(ErrorFromPeer::Server(Error::InvalidCertificate( - CertificateError::Revoked - ))) - ); - // However, when checking just the EE cert we expect no error - the intermediate's - // revocation status should not be checked. - let (mut client, mut server) = - make_pair_for_arc_configs(&client_config, &ee_server_config); - assert!(do_handshake_until_error(&mut client, &mut server).is_ok()); - } - } -} - -#[test] -fn client_optional_auth_client_revocation_works() { - for kt in ALL_KEY_TYPES { - // Create a server configuration that includes a CRL that specifies the client certificate - // is revoked. - let crls = vec![kt.client_crl()]; - let server_config = Arc::new(make_server_config_with_optional_client_auth(*kt, crls)); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - // Because the client certificate is revoked, the handshake should fail. - let err = do_handshake_until_error(&mut client, &mut server); - assert_eq!( - err, - Err(ErrorFromPeer::Server(Error::InvalidCertificate( - CertificateError::Revoked - ))) - ); - } - } -} - -#[test] -fn client_error_is_sticky() { - let (mut client, _) = make_pair(KeyType::Rsa2048); - client - .read_tls(&mut b"\x16\x03\x03\x00\x08\x0f\x00\x00\x04junk".as_ref()) - .unwrap(); - let mut err = client.process_new_packets(); - assert!(err.is_err()); - err = client.process_new_packets(); - assert!(err.is_err()); -} - -#[test] -fn server_error_is_sticky() { - let (_, mut server) = make_pair(KeyType::Rsa2048); - server - .read_tls(&mut b"\x16\x03\x03\x00\x08\x0f\x00\x00\x04junk".as_ref()) - .unwrap(); - let mut err = server.process_new_packets(); - assert!(err.is_err()); - err = server.process_new_packets(); - assert!(err.is_err()); -} - -#[test] -fn server_flush_does_nothing() { - let (_, mut server) = make_pair(KeyType::Rsa2048); - assert!(matches!(server.writer().flush(), Ok(()))); -} - -#[test] -fn client_flush_does_nothing() { - let (mut client, _) = make_pair(KeyType::Rsa2048); - assert!(matches!(client.writer().flush(), Ok(()))); -} - -#[allow(clippy::no_effect)] -#[test] -fn server_is_send_and_sync() { - let (_, server) = make_pair(KeyType::Rsa2048); - &server as &dyn Send; - &server as &dyn Sync; -} - -#[allow(clippy::no_effect)] -#[test] -fn client_is_send_and_sync() { - let (client, _) = make_pair(KeyType::Rsa2048); - &client as &dyn Send; - &client as &dyn Sync; -} - -#[test] -fn server_respects_buffer_limit_pre_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - - server.set_buffer_limit(Some(32)); - - assert_eq!( - server - .writer() - .write(b"01234567890123456789") - .unwrap(), - 20 - ); - assert_eq!( - server - .writer() - .write(b"01234567890123456789") - .unwrap(), - 12 - ); - - do_handshake(&mut client, &mut server); - transfer(&mut server, &mut client); - client.process_new_packets().unwrap(); - - check_read(&mut client.reader(), b"01234567890123456789012345678901"); -} - -#[test] -fn server_respects_buffer_limit_pre_handshake_with_vectored_write() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - - server.set_buffer_limit(Some(32)); - - assert_eq!( - server - .writer() - .write_vectored(&[ - IoSlice::new(b"01234567890123456789"), - IoSlice::new(b"01234567890123456789") - ]) - .unwrap(), - 32 - ); - - do_handshake(&mut client, &mut server); - transfer(&mut server, &mut client); - client.process_new_packets().unwrap(); - - check_read(&mut client.reader(), b"01234567890123456789012345678901"); -} - -#[test] -fn server_respects_buffer_limit_post_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - - // this test will vary in behaviour depending on the default suites - do_handshake(&mut client, &mut server); - server.set_buffer_limit(Some(48)); - - assert_eq!( - server - .writer() - .write(b"01234567890123456789") - .unwrap(), - 20 - ); - assert_eq!( - server - .writer() - .write(b"01234567890123456789") - .unwrap(), - 6 - ); - - transfer(&mut server, &mut client); - client.process_new_packets().unwrap(); - - check_read(&mut client.reader(), b"01234567890123456789012345"); -} - -#[test] -fn client_respects_buffer_limit_pre_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - - client.set_buffer_limit(Some(32)); - - assert_eq!( - client - .writer() - .write(b"01234567890123456789") - .unwrap(), - 20 - ); - assert_eq!( - client - .writer() - .write(b"01234567890123456789") - .unwrap(), - 12 - ); - - do_handshake(&mut client, &mut server); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - - check_read(&mut server.reader(), b"01234567890123456789012345678901"); -} - -#[test] -fn client_respects_buffer_limit_pre_handshake_with_vectored_write() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - - client.set_buffer_limit(Some(32)); - - assert_eq!( - client - .writer() - .write_vectored(&[ - IoSlice::new(b"01234567890123456789"), - IoSlice::new(b"01234567890123456789") - ]) - .unwrap(), - 32 - ); - - do_handshake(&mut client, &mut server); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - - check_read(&mut server.reader(), b"01234567890123456789012345678901"); -} - -#[test] -fn client_respects_buffer_limit_post_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - - do_handshake(&mut client, &mut server); - client.set_buffer_limit(Some(48)); - - assert_eq!( - client - .writer() - .write(b"01234567890123456789") - .unwrap(), - 20 - ); - assert_eq!( - client - .writer() - .write(b"01234567890123456789") - .unwrap(), - 6 - ); - - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - - check_read(&mut server.reader(), b"01234567890123456789012345"); -} - -struct OtherSession<'a, C, S> -where - C: DerefMut + Deref>, - S: SideData, -{ - sess: &'a mut C, - pub reads: usize, - pub writevs: Vec>, - fail_ok: bool, - pub short_writes: bool, - pub last_error: Option, - pub buffered: bool, - buffer: Vec>, -} - -impl<'a, C, S> OtherSession<'a, C, S> -where - C: DerefMut + Deref>, - S: SideData, -{ - fn new(sess: &'a mut C) -> OtherSession<'a, C, S> { - OtherSession { - sess, - reads: 0, - writevs: vec![], - fail_ok: false, - short_writes: false, - last_error: None, - buffered: false, - buffer: vec![], - } - } - - fn new_buffered(sess: &'a mut C) -> OtherSession<'a, C, S> { - let mut os = OtherSession::new(sess); - os.buffered = true; - os - } - - fn new_fails(sess: &'a mut C) -> OtherSession<'a, C, S> { - let mut os = OtherSession::new(sess); - os.fail_ok = true; - os - } - - fn flush_vectored(&mut self, b: &[io::IoSlice<'_>]) -> io::Result { - let mut total = 0; - let mut lengths = vec![]; - for bytes in b { - let write_len = if self.short_writes { - if bytes.len() > 5 { - bytes.len() / 2 - } else { - bytes.len() - } - } else { - bytes.len() - }; - - let l = self - .sess - .read_tls(&mut io::Cursor::new(&bytes[..write_len]))?; - lengths.push(l); - total += l; - if bytes.len() != l { - break; - } - } - - let rc = self.sess.process_new_packets(); - if !self.fail_ok { - rc.unwrap(); - } else if rc.is_err() { - self.last_error = rc.err(); - } - - self.writevs.push(lengths); - Ok(total) - } -} - -impl io::Read for OtherSession<'_, C, S> -where - C: DerefMut + Deref>, - S: SideData, -{ - fn read(&mut self, mut b: &mut [u8]) -> io::Result { - self.reads += 1; - self.sess.write_tls(b.by_ref()) - } -} - -impl io::Write for OtherSession<'_, C, S> -where - C: DerefMut + Deref>, - S: SideData, -{ - fn write(&mut self, _: &[u8]) -> io::Result { - unreachable!() - } - - fn flush(&mut self) -> io::Result<()> { - if !self.buffer.is_empty() { - let buffer = mem::take(&mut self.buffer); - let slices = buffer - .iter() - .map(|b| io::IoSlice::new(b)) - .collect::>(); - self.flush_vectored(&slices)?; - } - Ok(()) - } - - fn write_vectored(&mut self, b: &[io::IoSlice<'_>]) -> io::Result { - if self.buffered { - self.buffer - .extend(b.iter().map(|s| s.to_vec())); - return Ok(b.iter().map(|s| s.len()).sum()); - } - self.flush_vectored(b) - } -} - -#[test] -fn server_read_returns_wouldblock_when_no_data() { - let (_, mut server) = make_pair(KeyType::Rsa2048); - assert!(matches!(server.reader().read(&mut [0u8; 1]), - Err(err) if err.kind() == io::ErrorKind::WouldBlock)); -} - -#[test] -fn client_read_returns_wouldblock_when_no_data() { - let (mut client, _) = make_pair(KeyType::Rsa2048); - assert!(matches!(client.reader().read(&mut [0u8; 1]), - Err(err) if err.kind() == io::ErrorKind::WouldBlock)); -} - -#[test] -fn new_server_returns_initial_io_state() { - let (_, mut server) = make_pair(KeyType::Rsa2048); - let io_state = server.process_new_packets().unwrap(); - println!("IoState is Debug {:?}", io_state); - assert_eq!(io_state.plaintext_bytes_to_read(), 0); - assert!(!io_state.peer_has_closed()); - assert_eq!(io_state.tls_bytes_to_write(), 0); -} - -#[test] -fn new_client_returns_initial_io_state() { - let (mut client, _) = make_pair(KeyType::Rsa2048); - let io_state = client.process_new_packets().unwrap(); - println!("IoState is Debug {:?}", io_state); - assert_eq!(io_state.plaintext_bytes_to_read(), 0); - assert!(!io_state.peer_has_closed()); - assert!(io_state.tls_bytes_to_write() > 200); -} - -#[test] -fn client_complete_io_for_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - - assert!(client.is_handshaking()); - let (rdlen, wrlen) = client - .complete_io(&mut OtherSession::new(&mut server)) - .unwrap(); - assert!(rdlen > 0 && wrlen > 0); - assert!(!client.is_handshaking()); - assert!(!client.wants_write()); -} - -#[test] -fn buffered_client_complete_io_for_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - - assert!(client.is_handshaking()); - let (rdlen, wrlen) = client - .complete_io(&mut OtherSession::new_buffered(&mut server)) - .unwrap(); - assert!(rdlen > 0 && wrlen > 0); - assert!(!client.is_handshaking()); - assert!(!client.wants_write()); -} - -#[test] -fn client_complete_io_for_handshake_eof() { - let (mut client, _) = make_pair(KeyType::Rsa2048); - let mut input = io::Cursor::new(Vec::new()); - - assert!(client.is_handshaking()); - let err = client - .complete_io(&mut input) - .unwrap_err(); - assert_eq!(io::ErrorKind::UnexpectedEof, err.kind()); -} - -#[test] -fn client_complete_io_for_write() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); - - do_handshake(&mut client, &mut server); - - client - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - client - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - { - let mut pipe = OtherSession::new(&mut server); - let (rdlen, wrlen) = client.complete_io(&mut pipe).unwrap(); - assert!(rdlen == 0 && wrlen > 0); - println!("{:?}", pipe.writevs); - assert_eq!(pipe.writevs, vec![vec![42, 42]]); - } - check_read( - &mut server.reader(), - b"0123456789012345678901234567890123456789", - ); - } -} - -#[test] -fn buffered_client_complete_io_for_write() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); - - do_handshake(&mut client, &mut server); - - client - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - client - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - { - let mut pipe = OtherSession::new_buffered(&mut server); - let (rdlen, wrlen) = client.complete_io(&mut pipe).unwrap(); - assert!(rdlen == 0 && wrlen > 0); - println!("{:?}", pipe.writevs); - assert_eq!(pipe.writevs, vec![vec![42, 42]]); - } - check_read( - &mut server.reader(), - b"0123456789012345678901234567890123456789", - ); - } -} - -#[test] -fn client_complete_io_for_read() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); - - do_handshake(&mut client, &mut server); - - server - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - { - let mut pipe = OtherSession::new(&mut server); - let (rdlen, wrlen) = client.complete_io(&mut pipe).unwrap(); - assert!(rdlen > 0 && wrlen == 0); - assert_eq!(pipe.reads, 1); - } - check_read(&mut client.reader(), b"01234567890123456789"); - } -} - -#[test] -fn server_complete_io_for_handshake() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); - - assert!(server.is_handshaking()); - let (rdlen, wrlen) = server - .complete_io(&mut OtherSession::new(&mut client)) - .unwrap(); - assert!(rdlen > 0 && wrlen > 0); - assert!(!server.is_handshaking()); - assert!(!server.wants_write()); - } -} - -#[test] -fn server_complete_io_for_handshake_eof() { - let (_, mut server) = make_pair(KeyType::Rsa2048); - let mut input = io::Cursor::new(Vec::new()); - - assert!(server.is_handshaking()); - let err = server - .complete_io(&mut input) - .unwrap_err(); - assert_eq!(io::ErrorKind::UnexpectedEof, err.kind()); -} - -#[test] -fn server_complete_io_for_write() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); - - do_handshake(&mut client, &mut server); - - server - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - server - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - { - let mut pipe = OtherSession::new(&mut client); - let (rdlen, wrlen) = server.complete_io(&mut pipe).unwrap(); - assert!(rdlen == 0 && wrlen > 0); - assert_eq!(pipe.writevs, vec![vec![42, 42]]); - } - check_read( - &mut client.reader(), - b"0123456789012345678901234567890123456789", - ); - } -} - -#[test] -fn server_complete_io_for_write_eof() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); - - do_handshake(&mut client, &mut server); - - // Queue 20 bytes to write. - server - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - { - const BYTES_BEFORE_EOF: usize = 5; - let mut eof_writer = EofWriter::::default(); - - // Only BYTES_BEFORE_EOF should be written. - let (rdlen, wrlen) = server - .complete_io(&mut eof_writer) - .unwrap(); - assert_eq!(rdlen, 0); - assert_eq!(wrlen, BYTES_BEFORE_EOF); - - // Now nothing should be written. - let (rdlen, wrlen) = server - .complete_io(&mut eof_writer) - .unwrap(); - assert_eq!(rdlen, 0); - assert_eq!(wrlen, 0); - } - } -} - -#[derive(Default)] -struct EofWriter { - written: usize, -} - -impl std::io::Write for EofWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - let prev = self.written; - self.written = N.min(self.written + buf.len()); - Ok(self.written - prev) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl std::io::Read for EofWriter { - fn read(&mut self, _: &mut [u8]) -> io::Result { - panic!() // This is a writer, it should not be read from. - } -} - -#[test] -fn server_complete_io_for_read() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); - - do_handshake(&mut client, &mut server); - - client - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - { - let mut pipe = OtherSession::new(&mut client); - let (rdlen, wrlen) = server.complete_io(&mut pipe).unwrap(); - assert!(rdlen > 0 && wrlen == 0); - assert_eq!(pipe.reads, 1); - } - check_read(&mut server.reader(), b"01234567890123456789"); - } -} - -#[test] -fn client_stream_write() { - test_client_stream_write(StreamKind::Ref); - test_client_stream_write(StreamKind::Owned); -} - -#[test] -fn server_stream_write() { - test_server_stream_write(StreamKind::Ref); - test_server_stream_write(StreamKind::Owned); -} - -#[derive(Debug, Copy, Clone)] -enum StreamKind { - Owned, - Ref, -} - -fn test_client_stream_write(stream_kind: StreamKind) { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); - let data = b"hello"; - { - let mut pipe = OtherSession::new(&mut server); - let mut stream: Box = match stream_kind { - StreamKind::Ref => Box::new(Stream::new(&mut client, &mut pipe)), - StreamKind::Owned => Box::new(StreamOwned::new(client, pipe)), - }; - assert_eq!(stream.write(data).unwrap(), 5); - } - check_read(&mut server.reader(), data); - } -} - -fn test_server_stream_write(stream_kind: StreamKind) { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); - let data = b"hello"; - { - let mut pipe = OtherSession::new(&mut client); - let mut stream: Box = match stream_kind { - StreamKind::Ref => Box::new(Stream::new(&mut server, &mut pipe)), - StreamKind::Owned => Box::new(StreamOwned::new(server, pipe)), - }; - assert_eq!(stream.write(data).unwrap(), 5); - } - check_read(&mut client.reader(), data); - } -} - -#[test] -fn client_stream_read() { - test_client_stream_read(StreamKind::Ref, ReadKind::Buf); - test_client_stream_read(StreamKind::Owned, ReadKind::Buf); - #[cfg(read_buf)] - { - test_client_stream_read(StreamKind::Ref, ReadKind::BorrowedBuf); - test_client_stream_read(StreamKind::Owned, ReadKind::BorrowedBuf); - } -} - -#[test] -fn server_stream_read() { - test_server_stream_read(StreamKind::Ref, ReadKind::Buf); - test_server_stream_read(StreamKind::Owned, ReadKind::Buf); - #[cfg(read_buf)] - { - test_server_stream_read(StreamKind::Ref, ReadKind::BorrowedBuf); - test_server_stream_read(StreamKind::Owned, ReadKind::BorrowedBuf); - } -} - -#[derive(Debug, Copy, Clone)] -enum ReadKind { - Buf, - #[cfg(read_buf)] - BorrowedBuf, -} - -fn test_stream_read(read_kind: ReadKind, mut stream: impl Read, data: &[u8]) { - match read_kind { - ReadKind::Buf => { - check_read(&mut stream, data); - check_read_err(&mut stream, io::ErrorKind::UnexpectedEof) - } - #[cfg(read_buf)] - ReadKind::BorrowedBuf => { - check_read_buf(&mut stream, data); - check_read_buf_err(&mut stream, io::ErrorKind::UnexpectedEof) - } - } -} - -fn test_client_stream_read(stream_kind: StreamKind, read_kind: ReadKind) { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); - let data = b"world"; - server.writer().write_all(data).unwrap(); - - { - let mut pipe = OtherSession::new(&mut server); - transfer_eof(&mut client); - - let stream: Box = match stream_kind { - StreamKind::Ref => Box::new(Stream::new(&mut client, &mut pipe)), - StreamKind::Owned => Box::new(StreamOwned::new(client, pipe)), - }; - - test_stream_read(read_kind, stream, data) - } - } -} - -fn test_server_stream_read(stream_kind: StreamKind, read_kind: ReadKind) { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); - let data = b"world"; - client.writer().write_all(data).unwrap(); - - { - let mut pipe = OtherSession::new(&mut client); - transfer_eof(&mut server); - - let stream: Box = match stream_kind { - StreamKind::Ref => Box::new(Stream::new(&mut server, &mut pipe)), - StreamKind::Owned => Box::new(StreamOwned::new(server, pipe)), - }; - - test_stream_read(read_kind, stream, data) - } - } -} - -#[test] -fn test_client_write_and_vectored_write_equivalence() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - do_handshake(&mut client, &mut server); - - const N: usize = 1000; - - let data_chunked: Vec = std::iter::repeat(IoSlice::new(b"A")) - .take(N) - .collect(); - let bytes_written_chunked = client - .writer() - .write_vectored(&data_chunked) - .unwrap(); - let bytes_sent_chunked = transfer(&mut client, &mut server); - println!("write_vectored returned {bytes_written_chunked} and sent {bytes_sent_chunked}"); - - let data_contiguous = &[b'A'; N]; - let bytes_written_contiguous = client - .writer() - .write(data_contiguous) - .unwrap(); - let bytes_sent_contiguous = transfer(&mut client, &mut server); - println!("write returned {bytes_written_contiguous} and sent {bytes_sent_contiguous}"); - - assert_eq!(bytes_written_chunked, bytes_written_contiguous); - assert_eq!(bytes_sent_chunked, bytes_sent_contiguous); -} - -struct FailsWrites { - errkind: io::ErrorKind, - after: usize, -} - -impl io::Read for FailsWrites { - fn read(&mut self, _b: &mut [u8]) -> io::Result { - Ok(0) - } -} - -impl io::Write for FailsWrites { - fn write(&mut self, b: &[u8]) -> io::Result { - if self.after > 0 { - self.after -= 1; - Ok(b.len()) - } else { - Err(io::Error::new(self.errkind, "oops")) - } - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -#[test] -fn stream_write_reports_underlying_io_error_before_plaintext_processed() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - do_handshake(&mut client, &mut server); - - let mut pipe = FailsWrites { - errkind: io::ErrorKind::ConnectionAborted, - after: 0, - }; - client - .writer() - .write_all(b"hello") - .unwrap(); - let mut client_stream = Stream::new(&mut client, &mut pipe); - let rc = client_stream.write(b"world"); - assert!(rc.is_err()); - let err = rc.err().unwrap(); - assert_eq!(err.kind(), io::ErrorKind::ConnectionAborted); -} - -#[test] -fn stream_write_swallows_underlying_io_error_after_plaintext_processed() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - do_handshake(&mut client, &mut server); - - let mut pipe = FailsWrites { - errkind: io::ErrorKind::ConnectionAborted, - after: 1, - }; - client - .writer() - .write_all(b"hello") - .unwrap(); - let mut client_stream = Stream::new(&mut client, &mut pipe); - let rc = client_stream.write(b"world"); - assert_eq!(format!("{:?}", rc), "Ok(5)"); -} - -fn make_disjoint_suite_configs() -> (ClientConfig, ServerConfig) { - let kt = KeyType::Rsa2048; - let client_provider = CryptoProvider { - cipher_suites: vec![cipher_suite::TLS13_CHACHA20_POLY1305_SHA256], - ..provider::default_provider() - }; - let server_config = finish_server_config( - kt, - ServerConfig::builder_with_provider(client_provider.into()) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - let server_provider = CryptoProvider { - cipher_suites: vec![cipher_suite::TLS13_AES_256_GCM_SHA384], - ..provider::default_provider() - }; - let client_config = finish_client_config( - kt, - ClientConfig::builder_with_provider(server_provider.into()) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - (client_config, server_config) -} - -#[test] -fn client_stream_handshake_error() { - let (client_config, server_config) = make_disjoint_suite_configs(); - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - - { - let mut pipe = OtherSession::new_fails(&mut server); - let mut client_stream = Stream::new(&mut client, &mut pipe); - let rc = client_stream.write(b"hello"); - assert!(rc.is_err()); - assert_eq!( - format!("{:?}", rc), - "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" - ); - let rc = client_stream.write(b"hello"); - assert!(rc.is_err()); - assert_eq!( - format!("{:?}", rc), - "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" - ); - } -} - -#[test] -fn client_streamowned_handshake_error() { - let (client_config, server_config) = make_disjoint_suite_configs(); - let (client, mut server) = make_pair_for_configs(client_config, server_config); - - let pipe = OtherSession::new_fails(&mut server); - let mut client_stream = StreamOwned::new(client, pipe); - let rc = client_stream.write(b"hello"); - assert!(rc.is_err()); - assert_eq!( - format!("{:?}", rc), - "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" - ); - let rc = client_stream.write(b"hello"); - assert!(rc.is_err()); - assert_eq!( - format!("{:?}", rc), - "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" - ); - - let (_, _) = client_stream.into_parts(); -} - -#[test] -fn server_stream_handshake_error() { - let (client_config, server_config) = make_disjoint_suite_configs(); - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - - client - .writer() - .write_all(b"world") - .unwrap(); - - { - let mut pipe = OtherSession::new_fails(&mut client); - let mut server_stream = Stream::new(&mut server, &mut pipe); - let mut bytes = [0u8; 5]; - let rc = server_stream.read(&mut bytes); - assert!(rc.is_err()); - assert_eq!( - format!("{:?}", rc), - "Err(Custom { kind: InvalidData, error: PeerIncompatible(NoCipherSuitesInCommon) })" - ); - } -} - -#[test] -fn server_streamowned_handshake_error() { - let (client_config, server_config) = make_disjoint_suite_configs(); - let (mut client, server) = make_pair_for_configs(client_config, server_config); - - client - .writer() - .write_all(b"world") - .unwrap(); - - let pipe = OtherSession::new_fails(&mut client); - let mut server_stream = StreamOwned::new(server, pipe); - let mut bytes = [0u8; 5]; - let rc = server_stream.read(&mut bytes); - assert!(rc.is_err()); - assert_eq!( - format!("{:?}", rc), - "Err(Custom { kind: InvalidData, error: PeerIncompatible(NoCipherSuitesInCommon) })" - ); -} - -#[test] -fn server_config_is_clone() { - let _ = make_server_config(KeyType::Rsa2048); -} - -#[test] -fn client_config_is_clone() { - let _ = make_client_config(KeyType::Rsa2048); -} - -#[test] -fn client_connection_is_debug() { - let (client, _) = make_pair(KeyType::Rsa2048); - println!("{:?}", client); -} - -#[test] -fn server_connection_is_debug() { - let (_, server) = make_pair(KeyType::Rsa2048); - println!("{:?}", server); -} - -#[test] -fn server_complete_io_for_handshake_ending_with_alert() { - let (client_config, server_config) = make_disjoint_suite_configs(); - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - - assert!(server.is_handshaking()); - - let mut pipe = OtherSession::new_fails(&mut client); - let rc = server.complete_io(&mut pipe); - assert!(rc.is_err(), "server io failed due to handshake failure"); - assert!(!server.wants_write(), "but server did send its alert"); - assert_eq!( - format!("{:?}", pipe.last_error), - "Some(AlertReceived(HandshakeFailure))", - "which was received by client" - ); -} - -#[test] -fn server_exposes_offered_sni() { - let kt = KeyType::Rsa2048; - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(kt, &[version]); - let mut client = ClientConnection::new( - Arc::new(client_config), - server_name("second.testserver.com"), - ) - .unwrap(); - let mut server = ServerConnection::new(Arc::new(make_server_config(kt))).unwrap(); - - assert_eq!(None, server.server_name()); - do_handshake(&mut client, &mut server); - assert_eq!(Some("second.testserver.com"), server.server_name()); - } -} - -#[test] -fn server_exposes_offered_sni_smashed_to_lowercase() { - // webpki actually does this for us in its DnsName type - let kt = KeyType::Rsa2048; - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(kt, &[version]); - let mut client = ClientConnection::new( - Arc::new(client_config), - server_name("SECOND.TESTServer.com"), - ) - .unwrap(); - let mut server = ServerConnection::new(Arc::new(make_server_config(kt))).unwrap(); - - assert_eq!(None, server.server_name()); - do_handshake(&mut client, &mut server); - assert_eq!(Some("second.testserver.com"), server.server_name()); - } -} - -#[test] -fn server_exposes_offered_sni_even_if_resolver_fails() { - let kt = KeyType::Rsa2048; - let resolver = rustls::server::ResolvesServerCertUsingSni::new(); - - let mut server_config = make_server_config(kt); - server_config.cert_resolver = Arc::new(resolver); - let server_config = Arc::new(server_config); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(kt, &[version]); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("thisdoesNOTexist.com")) - .unwrap(); - - assert_eq!(None, server.server_name()); - transfer(&mut client, &mut server); - assert_eq!( - server.process_new_packets(), - Err(Error::General( - "no server certificate chain resolved".to_string() - )) - ); - assert_eq!(Some("thisdoesnotexist.com"), server.server_name()); - } -} - -#[test] -fn sni_resolver_works() { - let kt = KeyType::Rsa2048; - let mut resolver = rustls::server::ResolvesServerCertUsingSni::new(); - let signing_key = RsaSigningKey::new(&kt.get_key()).unwrap(); - let signing_key: Arc = Arc::new(signing_key); - resolver - .add( - "localhost", - sign::CertifiedKey::new(kt.get_chain(), signing_key.clone()), - ) - .unwrap(); - - let mut server_config = make_server_config(kt); - server_config.cert_resolver = Arc::new(resolver); - let server_config = Arc::new(server_config); - - let mut server1 = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - let mut client1 = - ClientConnection::new(Arc::new(make_client_config(kt)), server_name("localhost")).unwrap(); - let err = do_handshake_until_error(&mut client1, &mut server1); - assert_eq!(err, Ok(())); - - let mut server2 = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - let mut client2 = ClientConnection::new( - Arc::new(make_client_config(kt)), - server_name("notlocalhost"), - ) - .unwrap(); - let err = do_handshake_until_error(&mut client2, &mut server2); - assert_eq!( - err, - Err(ErrorFromPeer::Server(Error::General( - "no server certificate chain resolved".into() - ))) - ); -} - -#[test] -fn sni_resolver_rejects_wrong_names() { - let kt = KeyType::Rsa2048; - let mut resolver = rustls::server::ResolvesServerCertUsingSni::new(); - let signing_key = RsaSigningKey::new(&kt.get_key()).unwrap(); - let signing_key: Arc = Arc::new(signing_key); - - assert_eq!( - Ok(()), - resolver.add( - "localhost", - sign::CertifiedKey::new(kt.get_chain(), signing_key.clone()) - ) - ); - assert_eq!( - Err(Error::InvalidCertificate(CertificateError::NotValidForName)), - resolver.add( - "not-localhost", - sign::CertifiedKey::new(kt.get_chain(), signing_key.clone()) - ) - ); - assert_eq!( - Err(Error::General("Bad DNS name".into())), - resolver.add( - "not ascii 🦀", - sign::CertifiedKey::new(kt.get_chain(), signing_key.clone()) - ) - ); -} - -#[test] -fn sni_resolver_lower_cases_configured_names() { - let kt = KeyType::Rsa2048; - let mut resolver = rustls::server::ResolvesServerCertUsingSni::new(); - let signing_key = RsaSigningKey::new(&kt.get_key()).unwrap(); - let signing_key: Arc = Arc::new(signing_key); - - assert_eq!( - Ok(()), - resolver.add( - "LOCALHOST", - sign::CertifiedKey::new(kt.get_chain(), signing_key.clone()) - ) - ); - - let mut server_config = make_server_config(kt); - server_config.cert_resolver = Arc::new(resolver); - let server_config = Arc::new(server_config); - - let mut server1 = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - let mut client1 = - ClientConnection::new(Arc::new(make_client_config(kt)), server_name("localhost")).unwrap(); - let err = do_handshake_until_error(&mut client1, &mut server1); - assert_eq!(err, Ok(())); -} - -#[test] -fn sni_resolver_lower_cases_queried_names() { - // actually, the handshake parser does this, but the effect is the same. - let kt = KeyType::Rsa2048; - let mut resolver = rustls::server::ResolvesServerCertUsingSni::new(); - let signing_key = RsaSigningKey::new(&kt.get_key()).unwrap(); - let signing_key: Arc = Arc::new(signing_key); - - assert_eq!( - Ok(()), - resolver.add( - "localhost", - sign::CertifiedKey::new(kt.get_chain(), signing_key.clone()) - ) - ); - - let mut server_config = make_server_config(kt); - server_config.cert_resolver = Arc::new(resolver); - let server_config = Arc::new(server_config); - - let mut server1 = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - let mut client1 = - ClientConnection::new(Arc::new(make_client_config(kt)), server_name("LOCALHOST")).unwrap(); - let err = do_handshake_until_error(&mut client1, &mut server1); - assert_eq!(err, Ok(())); -} - -#[test] -fn sni_resolver_rejects_bad_certs() { - let kt = KeyType::Rsa2048; - let mut resolver = rustls::server::ResolvesServerCertUsingSni::new(); - let signing_key = RsaSigningKey::new(&kt.get_key()).unwrap(); - let signing_key: Arc = Arc::new(signing_key); - - assert_eq!( - Err(Error::NoCertificatesPresented), - resolver.add( - "localhost", - sign::CertifiedKey::new(vec![], signing_key.clone()) - ) - ); - - let bad_chain = vec![CertificateDer::from(vec![0xa0])]; - assert_eq!( - Err(Error::InvalidCertificate(CertificateError::BadEncoding)), - resolver.add( - "localhost", - sign::CertifiedKey::new(bad_chain, signing_key.clone()) - ) - ); -} - -#[test] -fn test_keys_match() { - // Consistent: Both of these should have the same SPKI values - let expect_consistent = - sign::CertifiedKey::new(KeyType::Rsa2048.get_chain(), Arc::new(SigningKeySomeSpki)); - assert!(matches!(expect_consistent.keys_match(), Ok(()))); - - // Inconsistent: These should not have the same SPKI values - let expect_inconsistent = - sign::CertifiedKey::new(KeyType::EcdsaP256.get_chain(), Arc::new(SigningKeySomeSpki)); - assert!(matches!( - expect_inconsistent.keys_match(), - Err(Error::InconsistentKeys(InconsistentKeys::KeyMismatch)) - )); - - // Unknown: This signing key returns None for its SPKI, so we can't tell if the certified key is consistent - let expect_unknown = - sign::CertifiedKey::new(KeyType::Rsa2048.get_chain(), Arc::new(SigningKeyNoneSpki)); - assert!(matches!( - expect_unknown.keys_match(), - Err(Error::InconsistentKeys(InconsistentKeys::Unknown)) - )); -} - -/// Represents a SigningKey that returns None for its SPKI via the default impl. -#[derive(Debug)] -struct SigningKeyNoneSpki; - -impl sign::SigningKey for SigningKeyNoneSpki { - fn choose_scheme(&self, _offered: &[SignatureScheme]) -> Option> { - unimplemented!("Not meant to be called during tests") - } - - fn algorithm(&self) -> rustls::SignatureAlgorithm { - unimplemented!("Not meant to be called during tests") - } -} - -/// Represents a SigningKey that returns Some for its SPKI. -#[derive(Debug)] -struct SigningKeySomeSpki; - -impl sign::SigningKey for SigningKeySomeSpki { - fn public_key(&self) -> Option { - let chain = KeyType::Rsa2048.get_chain(); - let cert = ParsedCertificate::try_from(chain.first().unwrap()).unwrap(); - Some( - cert.subject_public_key_info() - .into_owned(), - ) - } - - fn choose_scheme(&self, _offered: &[SignatureScheme]) -> Option> { - unimplemented!("Not meant to be called during tests") - } - - fn algorithm(&self) -> rustls::SignatureAlgorithm { - unimplemented!("Not meant to be called during tests") - } -} - -fn do_exporter_test(client_config: ClientConfig, server_config: ServerConfig) { - let mut client_secret = [0u8; 64]; - let mut server_secret = [0u8; 64]; - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - - assert_eq!( - Err(Error::HandshakeNotComplete), - client.export_keying_material(&mut client_secret, b"label", Some(b"context")) - ); - assert_eq!( - Err(Error::HandshakeNotComplete), - server.export_keying_material(&mut server_secret, b"label", Some(b"context")) - ); - do_handshake(&mut client, &mut server); - - assert!(client - .export_keying_material(&mut client_secret, b"label", Some(b"context")) - .is_ok()); - assert!(server - .export_keying_material(&mut server_secret, b"label", Some(b"context")) - .is_ok()); - assert_eq!(client_secret.to_vec(), server_secret.to_vec()); - - let mut empty = vec![]; - assert_eq!( - client - .export_keying_material(&mut empty, b"label", Some(b"context")) - .err(), - Some(Error::General( - "export_keying_material with zero-length output".into() - )) - ); - assert_eq!( - server - .export_keying_material(&mut empty, b"label", Some(b"context")) - .err(), - Some(Error::General( - "export_keying_material with zero-length output".into() - )) - ); - - assert!(client - .export_keying_material(&mut client_secret, b"label", None) - .is_ok()); - assert_ne!(client_secret.to_vec(), server_secret.to_vec()); - assert!(server - .export_keying_material(&mut server_secret, b"label", None) - .is_ok()); - assert_eq!(client_secret.to_vec(), server_secret.to_vec()); -} - -#[cfg(feature = "tls12")] -#[test] -fn test_tls12_exporter() { - for kt in ALL_KEY_TYPES { - let client_config = make_client_config_with_versions(*kt, &[&rustls::version::TLS12]); - let server_config = make_server_config(*kt); - - do_exporter_test(client_config, server_config); - } -} - -#[test] -fn test_tls13_exporter() { - for kt in ALL_KEY_TYPES { - let client_config = make_client_config_with_versions(*kt, &[&rustls::version::TLS13]); - let server_config = make_server_config(*kt); - - do_exporter_test(client_config, server_config); - } -} - -#[test] -fn test_tls13_exporter_maximum_output_length() { - let client_config = - make_client_config_with_versions(KeyType::EcdsaP256, &[&rustls::version::TLS13]); - let server_config = make_server_config(KeyType::EcdsaP256); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake(&mut client, &mut server); - - assert_eq!( - client.negotiated_cipher_suite(), - Some(find_suite(CipherSuite::TLS13_AES_256_GCM_SHA384)) - ); - - let mut maximum_allowed_output_client = [0u8; 255 * 48]; - let mut maximum_allowed_output_server = [0u8; 255 * 48]; - client - .export_keying_material( - &mut maximum_allowed_output_client, - b"label", - Some(b"context"), - ) - .unwrap(); - server - .export_keying_material( - &mut maximum_allowed_output_server, - b"label", - Some(b"context"), - ) - .unwrap(); - - assert_eq!(maximum_allowed_output_client, maximum_allowed_output_server); - - let mut too_long_output = [0u8; 255 * 48 + 1]; - assert_eq!( - client - .export_keying_material(&mut too_long_output, b"label", Some(b"context"),) - .err(), - Some(Error::General("exporting too much".into())) - ); - assert_eq!( - server - .export_keying_material(&mut too_long_output, b"label", Some(b"context"),) - .err(), - Some(Error::General("exporting too much".into())) - ); -} - -fn find_suite(suite: CipherSuite) -> SupportedCipherSuite { - for scs in provider::ALL_CIPHER_SUITES - .iter() - .copied() - { - if scs.suite() == suite { - return scs; - } - } - - panic!("find_suite given unsupported suite"); -} - -fn test_ciphersuites() -> Vec<( - &'static rustls::SupportedProtocolVersion, - KeyType, - CipherSuite, -)> { - let mut v = vec![ - ( - &rustls::version::TLS13, - KeyType::Rsa2048, - CipherSuite::TLS13_AES_256_GCM_SHA384, - ), - ( - &rustls::version::TLS13, - KeyType::Rsa2048, - CipherSuite::TLS13_AES_128_GCM_SHA256, - ), - #[cfg(feature = "tls12")] - ( - &rustls::version::TLS12, - KeyType::EcdsaP384, - CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - ), - #[cfg(feature = "tls12")] - ( - &rustls::version::TLS12, - KeyType::EcdsaP384, - CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - ), - #[cfg(feature = "tls12")] - ( - &rustls::version::TLS12, - KeyType::Rsa2048, - CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - ), - #[cfg(feature = "tls12")] - ( - &rustls::version::TLS12, - KeyType::Rsa2048, - CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - ), - ]; - - if !provider_is_fips() { - v.extend_from_slice(&[ - ( - &rustls::version::TLS13, - KeyType::Rsa2048, - CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, - ), - #[cfg(feature = "tls12")] - ( - &rustls::version::TLS12, - KeyType::EcdsaP256, - CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - ), - #[cfg(feature = "tls12")] - ( - &rustls::version::TLS12, - KeyType::Rsa2048, - CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - ), - ]); - } - - v -} - -#[test] -fn negotiated_ciphersuite_default() { - let expected_kx = match provider_is_fips() { - true => NamedGroup::secp256r1, - false => NamedGroup::X25519, - }; - for kt in ALL_KEY_TYPES { - do_suite_and_kx_test( - make_client_config(*kt), - make_server_config(*kt), - find_suite(CipherSuite::TLS13_AES_256_GCM_SHA384), - expected_kx, - ProtocolVersion::TLSv1_3, - ); - } -} - -#[test] -fn all_suites_covered() { - assert_eq!( - provider::DEFAULT_CIPHER_SUITES.len(), - test_ciphersuites().len() - ); -} - -#[test] -fn negotiated_ciphersuite_client() { - let expected_kx = match provider_is_fips() { - true => NamedGroup::secp256r1, - false => NamedGroup::X25519, - }; - for (version, kt, suite) in test_ciphersuites() { - let scs = find_suite(suite); - let client_config = finish_client_config( - kt, - ClientConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![scs], - ..provider::default_provider() - } - .into(), - ) - .with_protocol_versions(&[version]) - .unwrap(), - ); - - do_suite_and_kx_test( - client_config, - make_server_config(kt), - scs, - expected_kx, - version.version, - ); - } -} - -#[test] -fn negotiated_ciphersuite_server() { - let expected_kx = match provider_is_fips() { - true => NamedGroup::secp256r1, - false => NamedGroup::X25519, - }; - for (version, kt, suite) in test_ciphersuites() { - let scs = find_suite(suite); - let server_config = finish_server_config( - kt, - ServerConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![scs], - ..provider::default_provider() - } - .into(), - ) - .with_protocol_versions(&[version]) - .unwrap(), - ); - - do_suite_and_kx_test( - make_client_config(kt), - server_config, - scs, - expected_kx, - version.version, - ); - } -} - -#[test] -fn negotiated_ciphersuite_server_ignoring_client_preference() { - let expected_kx = match provider_is_fips() { - true => NamedGroup::secp256r1, - false => NamedGroup::X25519, - }; - for (version, kt, suite) in test_ciphersuites() { - let scs = find_suite(suite); - let scs_other = if scs.suite() == CipherSuite::TLS13_AES_256_GCM_SHA384 { - find_suite(CipherSuite::TLS13_AES_128_GCM_SHA256) - } else { - find_suite(CipherSuite::TLS13_AES_256_GCM_SHA384) - }; - let mut server_config = finish_server_config( - kt, - ServerConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![scs, scs_other], - ..provider::default_provider() - } - .into(), - ) - .with_protocol_versions(&[version]) - .unwrap(), - ); - server_config.ignore_client_order = true; - - let client_config = finish_client_config( - kt, - ClientConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![scs_other, scs], - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - do_suite_and_kx_test( - client_config, - server_config, - scs, - expected_kx, - version.version, - ); - } -} - -#[derive(Debug, PartialEq)] -struct KeyLogItem { - label: String, - client_random: Vec, - secret: Vec, -} - -#[derive(Debug)] -struct KeyLogToVec { - label: &'static str, - items: Mutex>, -} - -impl KeyLogToVec { - fn new(who: &'static str) -> Self { - Self { - label: who, - items: Mutex::new(vec![]), - } - } - - fn take(&self) -> Vec { - std::mem::take(&mut self.items.lock().unwrap()) - } -} - -impl KeyLog for KeyLogToVec { - fn log(&self, label: &str, client: &[u8], secret: &[u8]) { - let value = KeyLogItem { - label: label.into(), - client_random: client.into(), - secret: secret.into(), - }; - - println!("key log {:?}: {:?}", self.label, value); - - self.items.lock().unwrap().push(value); - } -} - -#[cfg(feature = "tls12")] -#[test] -fn key_log_for_tls12() { - let client_key_log = Arc::new(KeyLogToVec::new("client")); - let server_key_log = Arc::new(KeyLogToVec::new("server")); - - let kt = KeyType::Rsa2048; - let mut client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS12]); - client_config.key_log = client_key_log.clone(); - let client_config = Arc::new(client_config); - - let mut server_config = make_server_config(kt); - server_config.key_log = server_key_log.clone(); - let server_config = Arc::new(server_config); - - // full handshake - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - - let client_full_log = client_key_log.take(); - let server_full_log = server_key_log.take(); - assert_eq!(client_full_log, server_full_log); - assert_eq!(1, client_full_log.len()); - assert_eq!("CLIENT_RANDOM", client_full_log[0].label); - - // resumed - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - - let client_resume_log = client_key_log.take(); - let server_resume_log = server_key_log.take(); - assert_eq!(client_resume_log, server_resume_log); - assert_eq!(1, client_resume_log.len()); - assert_eq!("CLIENT_RANDOM", client_resume_log[0].label); - assert_eq!(client_full_log[0].secret, client_resume_log[0].secret); -} - -#[test] -fn key_log_for_tls13() { - let client_key_log = Arc::new(KeyLogToVec::new("client")); - let server_key_log = Arc::new(KeyLogToVec::new("server")); - - let kt = KeyType::Rsa2048; - let mut client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS13]); - client_config.key_log = client_key_log.clone(); - let client_config = Arc::new(client_config); - - let mut server_config = make_server_config(kt); - server_config.key_log = server_key_log.clone(); - let server_config = Arc::new(server_config); - - // full handshake - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - - let client_full_log = client_key_log.take(); - let server_full_log = server_key_log.take(); - - assert_eq!(5, client_full_log.len()); - assert_eq!("CLIENT_HANDSHAKE_TRAFFIC_SECRET", client_full_log[0].label); - assert_eq!("SERVER_HANDSHAKE_TRAFFIC_SECRET", client_full_log[1].label); - assert_eq!("CLIENT_TRAFFIC_SECRET_0", client_full_log[2].label); - assert_eq!("SERVER_TRAFFIC_SECRET_0", client_full_log[3].label); - assert_eq!("EXPORTER_SECRET", client_full_log[4].label); - - assert_eq!(client_full_log[0], server_full_log[0]); - assert_eq!(client_full_log[1], server_full_log[1]); - assert_eq!(client_full_log[2], server_full_log[2]); - assert_eq!(client_full_log[3], server_full_log[3]); - assert_eq!(client_full_log[4], server_full_log[4]); - - // resumed - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - - let client_resume_log = client_key_log.take(); - let server_resume_log = server_key_log.take(); - - assert_eq!(5, client_resume_log.len()); - assert_eq!( - "CLIENT_HANDSHAKE_TRAFFIC_SECRET", - client_resume_log[0].label - ); - assert_eq!( - "SERVER_HANDSHAKE_TRAFFIC_SECRET", - client_resume_log[1].label - ); - assert_eq!("CLIENT_TRAFFIC_SECRET_0", client_resume_log[2].label); - assert_eq!("SERVER_TRAFFIC_SECRET_0", client_resume_log[3].label); - assert_eq!("EXPORTER_SECRET", client_resume_log[4].label); - - assert_eq!(6, server_resume_log.len()); - assert_eq!("CLIENT_EARLY_TRAFFIC_SECRET", server_resume_log[0].label); - assert_eq!( - "CLIENT_HANDSHAKE_TRAFFIC_SECRET", - server_resume_log[1].label - ); - assert_eq!( - "SERVER_HANDSHAKE_TRAFFIC_SECRET", - server_resume_log[2].label - ); - assert_eq!("CLIENT_TRAFFIC_SECRET_0", server_resume_log[3].label); - assert_eq!("SERVER_TRAFFIC_SECRET_0", server_resume_log[4].label); - assert_eq!("EXPORTER_SECRET", server_resume_log[5].label); - - assert_eq!(client_resume_log[0], server_resume_log[1]); - assert_eq!(client_resume_log[1], server_resume_log[2]); - assert_eq!(client_resume_log[2], server_resume_log[3]); - assert_eq!(client_resume_log[3], server_resume_log[4]); - assert_eq!(client_resume_log[4], server_resume_log[5]); -} - -#[test] -fn vectored_write_for_server_appdata() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - do_handshake(&mut client, &mut server); - - server - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - server - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - { - let mut pipe = OtherSession::new(&mut client); - let wrlen = server.write_tls(&mut pipe).unwrap(); - assert_eq!(84, wrlen); - assert_eq!(pipe.writevs, vec![vec![42, 42]]); - } - check_read( - &mut client.reader(), - b"0123456789012345678901234567890123456789", - ); -} - -#[test] -fn vectored_write_for_client_appdata() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - do_handshake(&mut client, &mut server); - - client - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - client - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - { - let mut pipe = OtherSession::new(&mut server); - let wrlen = client.write_tls(&mut pipe).unwrap(); - assert_eq!(84, wrlen); - assert_eq!(pipe.writevs, vec![vec![42, 42]]); - } - check_read( - &mut server.reader(), - b"0123456789012345678901234567890123456789", - ); -} - -#[test] -fn vectored_write_for_server_handshake_with_half_rtt_data() { - let mut server_config = make_server_config(KeyType::Rsa2048); - server_config.send_half_rtt_data = true; - let (mut client, mut server) = make_pair_for_configs( - make_client_config_with_auth(KeyType::Rsa2048), - server_config, - ); - - server - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - server - .writer() - .write_all(b"0123456789") - .unwrap(); - - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - { - let mut pipe = OtherSession::new(&mut client); - let wrlen = server.write_tls(&mut pipe).unwrap(); - // don't assert exact sizes here, to avoid a brittle test - assert!(wrlen > 2400); // its pretty big (contains cert chain) - assert_eq!(pipe.writevs.len(), 1); // only one writev - assert_eq!(pipe.writevs[0].len(), 5); // at least a server hello/ccs/cert/serverkx/0.5rtt data - } - - client.process_new_packets().unwrap(); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - { - let mut pipe = OtherSession::new(&mut client); - let wrlen = server.write_tls(&mut pipe).unwrap(); - // 2 tickets (in one flight) - assert_eq!(wrlen, 184); - assert_eq!(pipe.writevs, vec![vec![184]]); - } - - assert!(!server.is_handshaking()); - assert!(!client.is_handshaking()); - check_read(&mut client.reader(), b"012345678901234567890123456789"); -} - -fn check_half_rtt_does_not_work(server_config: ServerConfig) { - let (mut client, mut server) = make_pair_for_configs( - make_client_config_with_auth(KeyType::Rsa2048), - server_config, - ); - - server - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - server - .writer() - .write_all(b"0123456789") - .unwrap(); - - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - { - let mut pipe = OtherSession::new(&mut client); - let wrlen = server.write_tls(&mut pipe).unwrap(); - // don't assert exact sizes here, to avoid a brittle test - assert!(wrlen > 2400); // its pretty big (contains cert chain) - assert_eq!(pipe.writevs.len(), 1); // only one writev - assert_eq!(pipe.writevs[0].len(), 3); // at least a server hello/ccs/cert/serverkx data, in one message - } - - // client second flight - client.process_new_packets().unwrap(); - transfer(&mut client, &mut server); - - // when client auth is enabled, we don't sent 0.5-rtt data, as we'd be sending - // it to an unauthenticated peer. so it happens here, in the server's second - // flight (42 and 32 are lengths of appdata sent above). - server.process_new_packets().unwrap(); - { - let mut pipe = OtherSession::new(&mut client); - let wrlen = server.write_tls(&mut pipe).unwrap(); - assert_eq!(wrlen, 258); - assert_eq!(pipe.writevs, vec![vec![184, 42, 32]]); - } - - assert!(!server.is_handshaking()); - assert!(!client.is_handshaking()); - check_read(&mut client.reader(), b"012345678901234567890123456789"); -} - -#[test] -fn vectored_write_for_server_handshake_no_half_rtt_with_client_auth() { - let mut server_config = make_server_config_with_mandatory_client_auth(KeyType::Rsa2048); - server_config.send_half_rtt_data = true; // ask even though it will be ignored - check_half_rtt_does_not_work(server_config); -} - -#[test] -fn vectored_write_for_server_handshake_no_half_rtt_by_default() { - let server_config = make_server_config(KeyType::Rsa2048); - assert!(!server_config.send_half_rtt_data); - check_half_rtt_does_not_work(server_config); -} - -#[test] -fn vectored_write_for_client_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - - client - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - client - .writer() - .write_all(b"0123456789") - .unwrap(); - { - let mut pipe = OtherSession::new(&mut server); - let wrlen = client.write_tls(&mut pipe).unwrap(); - // don't assert exact sizes here, to avoid a brittle test - assert!(wrlen > 200); // just the client hello - assert_eq!(pipe.writevs.len(), 1); // only one writev - assert!(pipe.writevs[0].len() == 1); // only a client hello - } - - transfer(&mut server, &mut client); - client.process_new_packets().unwrap(); - - { - let mut pipe = OtherSession::new(&mut server); - let wrlen = client.write_tls(&mut pipe).unwrap(); - assert_eq!(wrlen, 154); - // CCS, finished, then two application data records - assert_eq!(pipe.writevs, vec![vec![6, 74, 42, 32]]); - } - - assert!(!server.is_handshaking()); - assert!(!client.is_handshaking()); - check_read(&mut server.reader(), b"012345678901234567890123456789"); -} - -#[test] -fn vectored_write_with_slow_client() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - - client.set_buffer_limit(Some(32)); - - do_handshake(&mut client, &mut server); - server - .writer() - .write_all(b"01234567890123456789") - .unwrap(); - - { - let mut pipe = OtherSession::new(&mut client); - pipe.short_writes = true; - let wrlen = server.write_tls(&mut pipe).unwrap() - + server.write_tls(&mut pipe).unwrap() - + server.write_tls(&mut pipe).unwrap() - + server.write_tls(&mut pipe).unwrap() - + server.write_tls(&mut pipe).unwrap() - + server.write_tls(&mut pipe).unwrap(); - assert_eq!(42, wrlen); - assert_eq!( - pipe.writevs, - vec![vec![21], vec![10], vec![5], vec![3], vec![3]] - ); - } - check_read(&mut client.reader(), b"01234567890123456789"); -} - -struct ServerStorage { - storage: Arc, - put_count: AtomicUsize, - get_count: AtomicUsize, - take_count: AtomicUsize, -} - -impl ServerStorage { - fn new() -> Self { - Self { - storage: rustls::server::ServerSessionMemoryCache::new(1024), - put_count: AtomicUsize::new(0), - get_count: AtomicUsize::new(0), - take_count: AtomicUsize::new(0), - } - } - - fn puts(&self) -> usize { - self.put_count.load(Ordering::SeqCst) - } - fn gets(&self) -> usize { - self.get_count.load(Ordering::SeqCst) - } - fn takes(&self) -> usize { - self.take_count.load(Ordering::SeqCst) - } -} - -impl fmt::Debug for ServerStorage { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "(put: {:?}, get: {:?}, take: {:?})", - self.put_count, self.get_count, self.take_count - ) - } -} - -impl rustls::server::StoresServerSessions for ServerStorage { - fn put(&self, key: Vec, value: Vec) -> bool { - self.put_count - .fetch_add(1, Ordering::SeqCst); - self.storage.put(key, value) - } - - fn get(&self, key: &[u8]) -> Option> { - self.get_count - .fetch_add(1, Ordering::SeqCst); - self.storage.get(key) - } - - fn take(&self, key: &[u8]) -> Option> { - self.take_count - .fetch_add(1, Ordering::SeqCst); - self.storage.take(key) - } - - fn can_cache(&self) -> bool { - true - } -} - -#[derive(Debug, Clone)] -#[allow(dead_code)] // complete mock, but not 100% used in tests -enum ClientStorageOp { - SetKxHint(ServerName<'static>, rustls::NamedGroup), - GetKxHint(ServerName<'static>, Option), - SetTls12Session(ServerName<'static>), - GetTls12Session(ServerName<'static>, bool), - RemoveTls12Session(ServerName<'static>), - InsertTls13Ticket(ServerName<'static>), - TakeTls13Ticket(ServerName<'static>, bool), -} - -struct ClientStorage { - storage: Arc, - ops: Mutex>, - alter_max_early_data_size: Option<(u32, u32)>, -} - -impl ClientStorage { - fn new() -> Self { - Self { - storage: Arc::new(rustls::client::ClientSessionMemoryCache::new(1024)), - ops: Mutex::new(Vec::new()), - alter_max_early_data_size: None, - } - } - - fn alter_max_early_data_size(&mut self, expected: u32, altered: u32) { - self.alter_max_early_data_size = Some((expected, altered)); - } - - #[cfg(feature = "tls12")] - fn ops(&self) -> Vec { - self.ops.lock().unwrap().clone() - } - - #[cfg(feature = "tls12")] - fn ops_and_reset(&self) -> Vec { - std::mem::take(&mut self.ops.lock().unwrap()) - } -} - -impl fmt::Debug for ClientStorage { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "(ops: {:?})", self.ops.lock().unwrap()) - } -} - -impl rustls::client::ClientSessionStore for ClientStorage { - fn set_kx_hint(&self, server_name: ServerName<'static>, group: rustls::NamedGroup) { - self.ops - .lock() - .unwrap() - .push(ClientStorageOp::SetKxHint(server_name.clone(), group)); - self.storage - .set_kx_hint(server_name, group) - } - - fn kx_hint(&self, server_name: &ServerName<'_>) -> Option { - let rc = self.storage.kx_hint(server_name); - self.ops - .lock() - .unwrap() - .push(ClientStorageOp::GetKxHint(server_name.to_owned(), rc)); - rc - } - - fn set_tls12_session( - &self, - server_name: ServerName<'static>, - value: rustls::client::Tls12ClientSessionValue, - ) { - self.ops - .lock() - .unwrap() - .push(ClientStorageOp::SetTls12Session(server_name.clone())); - self.storage - .set_tls12_session(server_name, value) - } - - fn tls12_session( - &self, - server_name: &ServerName<'_>, - ) -> Option { - let rc = self.storage.tls12_session(server_name); - self.ops - .lock() - .unwrap() - .push(ClientStorageOp::GetTls12Session( - server_name.to_owned(), - rc.is_some(), - )); - rc - } - - fn remove_tls12_session(&self, server_name: &ServerName<'static>) { - self.ops - .lock() - .unwrap() - .push(ClientStorageOp::RemoveTls12Session(server_name.clone())); - self.storage - .remove_tls12_session(server_name); - } - - fn insert_tls13_ticket( - &self, - server_name: ServerName<'static>, - mut value: rustls::client::Tls13ClientSessionValue, - ) { - if let Some((expected, desired)) = self.alter_max_early_data_size { - assert_eq!(value.max_early_data_size(), expected); - value._private_set_max_early_data_size(desired); - } - - self.ops - .lock() - .unwrap() - .push(ClientStorageOp::InsertTls13Ticket(server_name.clone())); - self.storage - .insert_tls13_ticket(server_name, value); - } - - fn take_tls13_ticket( - &self, - server_name: &ServerName<'static>, - ) -> Option { - let rc = self - .storage - .take_tls13_ticket(server_name); - self.ops - .lock() - .unwrap() - .push(ClientStorageOp::TakeTls13Ticket( - server_name.clone(), - rc.is_some(), - )); - rc - } -} - -#[test] -fn tls13_stateful_resumption() { - let kt = KeyType::Rsa2048; - let client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS13]); - let client_config = Arc::new(client_config); - - let mut server_config = make_server_config(kt); - let storage = Arc::new(ServerStorage::new()); - server_config.session_storage = storage.clone(); - let server_config = Arc::new(server_config); - - // full handshake - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - let (full_c2s, full_s2c) = do_handshake(&mut client, &mut server); - assert_eq!(storage.puts(), 2); - assert_eq!(storage.gets(), 0); - assert_eq!(storage.takes(), 0); - assert_eq!( - client - .peer_certificates() - .map(|certs| certs.len()), - Some(3) - ); - assert_eq!(client.handshake_kind(), Some(HandshakeKind::Full)); - assert_eq!(server.handshake_kind(), Some(HandshakeKind::Full)); - - // resumed - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - let (resume_c2s, resume_s2c) = do_handshake(&mut client, &mut server); - assert!(resume_c2s > full_c2s); - assert!(resume_s2c < full_s2c); - assert_eq!(storage.puts(), 4); - assert_eq!(storage.gets(), 0); - assert_eq!(storage.takes(), 1); - assert_eq!( - client - .peer_certificates() - .map(|certs| certs.len()), - Some(3) - ); - assert_eq!(client.handshake_kind(), Some(HandshakeKind::Resumed)); - assert_eq!(server.handshake_kind(), Some(HandshakeKind::Resumed)); - - // resumed again - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - let (resume2_c2s, resume2_s2c) = do_handshake(&mut client, &mut server); - assert_eq!(resume_s2c, resume2_s2c); - assert_eq!(resume_c2s, resume2_c2s); - assert_eq!(storage.puts(), 6); - assert_eq!(storage.gets(), 0); - assert_eq!(storage.takes(), 2); - assert_eq!( - client - .peer_certificates() - .map(|certs| certs.len()), - Some(3) - ); - assert_eq!(client.handshake_kind(), Some(HandshakeKind::Resumed)); - assert_eq!(server.handshake_kind(), Some(HandshakeKind::Resumed)); -} - -#[test] -fn tls13_stateless_resumption() { - let kt = KeyType::Rsa2048; - let client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS13]); - let client_config = Arc::new(client_config); - - let mut server_config = make_server_config(kt); - server_config.ticketer = provider::Ticketer::new().unwrap(); - let storage = Arc::new(ServerStorage::new()); - server_config.session_storage = storage.clone(); - let server_config = Arc::new(server_config); - - // full handshake - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - let (full_c2s, full_s2c) = do_handshake(&mut client, &mut server); - assert_eq!(storage.puts(), 0); - assert_eq!(storage.gets(), 0); - assert_eq!(storage.takes(), 0); - assert_eq!( - client - .peer_certificates() - .map(|certs| certs.len()), - Some(3) - ); - assert_eq!(client.handshake_kind(), Some(HandshakeKind::Full)); - assert_eq!(server.handshake_kind(), Some(HandshakeKind::Full)); - - // resumed - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - let (resume_c2s, resume_s2c) = do_handshake(&mut client, &mut server); - assert!(resume_c2s > full_c2s); - assert!(resume_s2c < full_s2c); - assert_eq!(storage.puts(), 0); - assert_eq!(storage.gets(), 0); - assert_eq!(storage.takes(), 0); - assert_eq!( - client - .peer_certificates() - .map(|certs| certs.len()), - Some(3) - ); - assert_eq!(client.handshake_kind(), Some(HandshakeKind::Resumed)); - assert_eq!(server.handshake_kind(), Some(HandshakeKind::Resumed)); - - // resumed again - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - let (resume2_c2s, resume2_s2c) = do_handshake(&mut client, &mut server); - assert_eq!(resume_s2c, resume2_s2c); - assert_eq!(resume_c2s, resume2_c2s); - assert_eq!(storage.puts(), 0); - assert_eq!(storage.gets(), 0); - assert_eq!(storage.takes(), 0); - assert_eq!( - client - .peer_certificates() - .map(|certs| certs.len()), - Some(3) - ); - assert_eq!(client.handshake_kind(), Some(HandshakeKind::Resumed)); - assert_eq!(server.handshake_kind(), Some(HandshakeKind::Resumed)); -} - -#[test] -fn early_data_not_available() { - let (mut client, _) = make_pair(KeyType::Rsa2048); - assert!(client.early_data().is_none()); -} - -fn early_data_configs() -> (Arc, Arc) { - let kt = KeyType::Rsa2048; - let mut client_config = make_client_config(kt); - client_config.enable_early_data = true; - client_config.resumption = Resumption::store(Arc::new(ClientStorage::new())); - - let mut server_config = make_server_config(kt); - server_config.max_early_data_size = 1234; - (Arc::new(client_config), Arc::new(server_config)) -} - -#[test] -fn early_data_is_available_on_resumption() { - let (client_config, server_config) = early_data_configs(); - - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - assert!(client.early_data().is_some()); - assert_eq!( - client - .early_data() - .unwrap() - .bytes_left(), - 1234 - ); - client - .early_data() - .unwrap() - .flush() - .unwrap(); - assert_eq!( - client - .early_data() - .unwrap() - .write(b"hello") - .unwrap(), - 5 - ); - do_handshake(&mut client, &mut server); - - let mut received_early_data = [0u8; 5]; - assert_eq!( - server - .early_data() - .expect("early_data didn't happen") - .read(&mut received_early_data) - .expect("early_data failed unexpectedly"), - 5 - ); - assert_eq!(&received_early_data[..], b"hello"); -} - -#[test] -fn early_data_not_available_on_server_before_client_hello() { - let mut server = ServerConnection::new(Arc::new(make_server_config(KeyType::Rsa2048))).unwrap(); - assert!(server.early_data().is_none()); -} - -#[test] -fn early_data_can_be_rejected_by_server() { - let (client_config, server_config) = early_data_configs(); - - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - assert!(client.early_data().is_some()); - assert_eq!( - client - .early_data() - .unwrap() - .bytes_left(), - 1234 - ); - client - .early_data() - .unwrap() - .flush() - .unwrap(); - assert_eq!( - client - .early_data() - .unwrap() - .write(b"hello") - .unwrap(), - 5 - ); - server.reject_early_data(); - do_handshake(&mut client, &mut server); - - assert!(!client.is_early_data_accepted()); -} - -#[test] -fn early_data_is_limited_on_client() { - let (client_config, server_config) = early_data_configs(); - - // warm up - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - assert!(client.early_data().is_some()); - assert_eq!( - client - .early_data() - .unwrap() - .bytes_left(), - 1234 - ); - client - .early_data() - .unwrap() - .flush() - .unwrap(); - assert_eq!( - client - .early_data() - .unwrap() - .write(&[0xaa; 1234 + 1]) - .unwrap(), - 1234 - ); - do_handshake(&mut client, &mut server); - - let mut received_early_data = [0u8; 1234]; - assert_eq!( - server - .early_data() - .expect("early_data didn't happen") - .read(&mut received_early_data) - .expect("early_data failed unexpectedly"), - 1234 - ); - assert_eq!(&received_early_data[..], [0xaa; 1234]); -} - -fn early_data_configs_allowing_client_to_send_excess_data() -> (Arc, Arc) -{ - let (client_config, server_config) = early_data_configs(); - - // adjust client session storage to corrupt received max_early_data_size - let mut client_config = Arc::into_inner(client_config).unwrap(); - let mut storage = ClientStorage::new(); - storage.alter_max_early_data_size(1234, 2024); - client_config.resumption = Resumption::store(Arc::new(storage)); - let client_config = Arc::new(client_config); - - // warm up - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - (client_config, server_config) -} - -#[test] -fn server_detects_excess_early_data() { - let (client_config, server_config) = early_data_configs_allowing_client_to_send_excess_data(); - - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - assert!(client.early_data().is_some()); - assert_eq!( - client - .early_data() - .unwrap() - .bytes_left(), - 2024 - ); - client - .early_data() - .unwrap() - .flush() - .unwrap(); - assert_eq!( - client - .early_data() - .unwrap() - .write(&[0xaa; 2024]) - .unwrap(), - 2024 - ); - assert_eq!( - do_handshake_until_error(&mut client, &mut server), - Err(ErrorFromPeer::Server(Error::PeerMisbehaved( - PeerMisbehaved::TooMuchEarlyDataReceived - ))), - ); -} - -// regression test for https://github.com/rustls/rustls/issues/2096 -#[test] -fn server_detects_excess_streamed_early_data() { - let (client_config, server_config) = early_data_configs_allowing_client_to_send_excess_data(); - - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - assert!(client.early_data().is_some()); - assert_eq!( - client - .early_data() - .unwrap() - .bytes_left(), - 2024 - ); - client - .early_data() - .unwrap() - .flush() - .unwrap(); - assert_eq!( - client - .early_data() - .unwrap() - .write(&[0xaa; 1024]) - .unwrap(), - 1024 - ); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - - let mut received_early_data = [0u8; 1024]; - assert_eq!( - server - .early_data() - .expect("early_data didn't happen") - .read(&mut received_early_data) - .expect("early_data failed unexpectedly"), - 1024 - ); - assert_eq!(&received_early_data[..], [0xaa; 1024]); - - assert_eq!( - client - .early_data() - .unwrap() - .write(&[0xbb; 1000]) - .unwrap(), - 1000 - ); - transfer(&mut client, &mut server); - assert_eq!( - server.process_new_packets(), - Err(Error::PeerMisbehaved( - PeerMisbehaved::TooMuchEarlyDataReceived - )) - ); -} - -mod test_quic { - use rustls::quic::{self, ConnectionCommon}; - - use super::*; - - // Returns the sender's next secrets to use, or the receiver's error. - fn step( - send: &mut ConnectionCommon, - recv: &mut ConnectionCommon, - ) -> Result, Error> { - let mut buf = Vec::new(); - let change = loop { - let prev = buf.len(); - if let Some(x) = send.write_hs(&mut buf) { - break Some(x); - } - if prev == buf.len() { - break None; - } - }; - if let Err(e) = recv.read_hs(&buf) { - return Err(e); - } else { - assert_eq!(recv.alert(), None); - } - - Ok(change) - } - - #[test] - fn test_quic_handshake() { - fn equal_packet_keys(x: &dyn quic::PacketKey, y: &dyn quic::PacketKey) -> bool { - // Check that these two sets of keys are equal. - let mut buf = [0; 32]; - let (header, payload_tag) = buf.split_at_mut(8); - let (payload, tag_buf) = payload_tag.split_at_mut(8); - let tag = x - .encrypt_in_place(42, header, payload) - .unwrap(); - tag_buf.copy_from_slice(tag.as_ref()); - - let result = y.decrypt_in_place(42, header, payload_tag); - match result { - Ok(payload) => payload == [0; 8], - Err(_) => false, - } - } - - fn compatible_keys(x: &quic::KeyChange, y: &quic::KeyChange) -> bool { - fn keys(kc: &quic::KeyChange) -> &quic::Keys { - match kc { - quic::KeyChange::Handshake { keys } => keys, - quic::KeyChange::OneRtt { keys, .. } => keys, - } - } - - let (x, y) = (keys(x), keys(y)); - equal_packet_keys(x.local.packet.as_ref(), y.remote.packet.as_ref()) - && equal_packet_keys(x.remote.packet.as_ref(), y.local.packet.as_ref()) - } - - let kt = KeyType::Rsa2048; - let mut client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS13]); - client_config.enable_early_data = true; - let client_config = Arc::new(client_config); - let mut server_config = make_server_config_with_versions(kt, &[&rustls::version::TLS13]); - server_config.max_early_data_size = 0xffffffff; - let server_config = Arc::new(server_config); - let client_params = &b"client params"[..]; - let server_params = &b"server params"[..]; - - // full handshake - let mut client = quic::ClientConnection::new( - Arc::clone(&client_config), - quic::Version::V1, - server_name("localhost"), - client_params.into(), - ) - .unwrap(); - - let mut server = quic::ServerConnection::new( - Arc::clone(&server_config), - quic::Version::V1, - server_params.into(), - ) - .unwrap(); - - let client_initial = step(&mut client, &mut server).unwrap(); - assert!(client_initial.is_none()); - assert!(client.zero_rtt_keys().is_none()); - assert_eq!(server.quic_transport_parameters(), Some(client_params)); - let server_hs = step(&mut server, &mut client) - .unwrap() - .unwrap(); - assert!(server.zero_rtt_keys().is_none()); - let client_hs = step(&mut client, &mut server) - .unwrap() - .unwrap(); - assert!(compatible_keys(&server_hs, &client_hs)); - assert!(client.is_handshaking()); - let server_1rtt = step(&mut server, &mut client) - .unwrap() - .unwrap(); - assert!(!client.is_handshaking()); - assert_eq!(client.quic_transport_parameters(), Some(server_params)); - assert!(server.is_handshaking()); - let client_1rtt = step(&mut client, &mut server) - .unwrap() - .unwrap(); - assert!(!server.is_handshaking()); - assert!(compatible_keys(&server_1rtt, &client_1rtt)); - assert!(!compatible_keys(&server_hs, &server_1rtt)); - assert!(step(&mut client, &mut server) - .unwrap() - .is_none()); - assert!(step(&mut server, &mut client) - .unwrap() - .is_none()); - - // 0-RTT handshake - let mut client = quic::ClientConnection::new( - Arc::clone(&client_config), - quic::Version::V1, - server_name("localhost"), - client_params.into(), - ) - .unwrap(); - assert!(client - .negotiated_cipher_suite() - .is_some()); - - let mut server = quic::ServerConnection::new( - Arc::clone(&server_config), - quic::Version::V1, - server_params.into(), - ) - .unwrap(); - - step(&mut client, &mut server).unwrap(); - assert_eq!(client.quic_transport_parameters(), Some(server_params)); - { - let client_early = client.zero_rtt_keys().unwrap(); - let server_early = server.zero_rtt_keys().unwrap(); - assert!(equal_packet_keys( - client_early.packet.as_ref(), - server_early.packet.as_ref() - )); - } - step(&mut server, &mut client) - .unwrap() - .unwrap(); - step(&mut client, &mut server) - .unwrap() - .unwrap(); - step(&mut server, &mut client) - .unwrap() - .unwrap(); - assert!(client.is_early_data_accepted()); - - // 0-RTT rejection - { - let client_config = (*client_config).clone(); - let mut client = quic::ClientConnection::new( - Arc::new(client_config), - quic::Version::V1, - server_name("localhost"), - client_params.into(), - ) - .unwrap(); - - let mut server = quic::ServerConnection::new( - Arc::clone(&server_config), - quic::Version::V1, - server_params.into(), - ) - .unwrap(); - server.reject_early_data(); - - step(&mut client, &mut server).unwrap(); - assert_eq!(client.quic_transport_parameters(), Some(server_params)); - assert!(client.zero_rtt_keys().is_some()); - assert!(server.zero_rtt_keys().is_none()); - step(&mut server, &mut client) - .unwrap() - .unwrap(); - step(&mut client, &mut server) - .unwrap() - .unwrap(); - step(&mut server, &mut client) - .unwrap() - .unwrap(); - assert!(!client.is_early_data_accepted()); - } - - // failed handshake - let mut client = quic::ClientConnection::new( - client_config, - quic::Version::V1, - server_name("example.com"), - client_params.into(), - ) - .unwrap(); - - let mut server = - quic::ServerConnection::new(server_config, quic::Version::V1, server_params.into()) - .unwrap(); - - step(&mut client, &mut server).unwrap(); - step(&mut server, &mut client) - .unwrap() - .unwrap(); - assert!(step(&mut server, &mut client).is_err()); - assert_eq!( - client.alert(), - Some(rustls::AlertDescription::BadCertificate) - ); - - // Key updates - - let ( - quic::KeyChange::OneRtt { - next: mut client_secrets, - .. - }, - quic::KeyChange::OneRtt { - next: mut server_secrets, - .. - }, - ) = (client_1rtt, server_1rtt) - else { - unreachable!(); - }; - - let mut client_next = client_secrets.next_packet_keys(); - let mut server_next = server_secrets.next_packet_keys(); - assert!(equal_packet_keys( - client_next.local.as_ref(), - server_next.remote.as_ref() - )); - assert!(equal_packet_keys( - server_next.local.as_ref(), - client_next.remote.as_ref() - )); - - client_next = client_secrets.next_packet_keys(); - server_next = server_secrets.next_packet_keys(); - assert!(equal_packet_keys( - client_next.local.as_ref(), - server_next.remote.as_ref() - )); - assert!(equal_packet_keys( - server_next.local.as_ref(), - client_next.remote.as_ref() - )); - } - - #[test] - fn test_quic_rejects_missing_alpn() { - let client_params = &b"client params"[..]; - let server_params = &b"server params"[..]; - - for &kt in ALL_KEY_TYPES { - let client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS13]); - let client_config = Arc::new(client_config); - - let mut server_config = - make_server_config_with_versions(kt, &[&rustls::version::TLS13]); - server_config.alpn_protocols = vec!["foo".into()]; - let server_config = Arc::new(server_config); - - let mut client = quic::ClientConnection::new( - client_config, - quic::Version::V1, - server_name("localhost"), - client_params.into(), - ) - .unwrap(); - let mut server = - quic::ServerConnection::new(server_config, quic::Version::V1, server_params.into()) - .unwrap(); - - assert_eq!( - step(&mut client, &mut server) - .err() - .unwrap(), - Error::NoApplicationProtocol - ); - - assert_eq!( - server.alert(), - Some(rustls::AlertDescription::NoApplicationProtocol) - ); - } - } - - #[cfg(feature = "tls12")] - #[test] - fn test_quic_no_tls13_error() { - let mut client_config = - make_client_config_with_versions(KeyType::Ed25519, &[&rustls::version::TLS12]); - client_config.alpn_protocols = vec!["foo".into()]; - let client_config = Arc::new(client_config); - - assert!(quic::ClientConnection::new( - client_config, - quic::Version::V1, - server_name("localhost"), - b"client params".to_vec(), - ) - .is_err()); - - let mut server_config = - make_server_config_with_versions(KeyType::Ed25519, &[&rustls::version::TLS12]); - server_config.alpn_protocols = vec!["foo".into()]; - let server_config = Arc::new(server_config); - - assert!(quic::ServerConnection::new( - server_config, - quic::Version::V1, - b"server params".to_vec(), - ) - .is_err()); - } - - #[test] - fn test_quic_invalid_early_data_size() { - let mut server_config = - make_server_config_with_versions(KeyType::Ed25519, &[&rustls::version::TLS13]); - server_config.alpn_protocols = vec!["foo".into()]; - - let cases = [ - (None, true), - (Some(0u32), true), - (Some(5), false), - (Some(0xffff_ffff), true), - ]; - - for &(size, ok) in cases.iter() { - println!("early data size case: {:?}", size); - if let Some(new) = size { - server_config.max_early_data_size = new; - } - - let wrapped = Arc::new(server_config.clone()); - assert_eq!( - quic::ServerConnection::new(wrapped, quic::Version::V1, b"server params".to_vec(),) - .is_ok(), - ok - ); - } - } - - #[test] - #[cfg(feature = "ring")] // uses ring APIs directly - fn test_quic_server_no_params_received() { - let server_config = - make_server_config_with_versions(KeyType::Ed25519, &[&rustls::version::TLS13]); - let server_config = Arc::new(server_config); - - let mut server = quic::ServerConnection::new( - server_config, - quic::Version::V1, - b"server params".to_vec(), - ) - .unwrap(); - - use rustls::internal::msgs::enums::{Compression, NamedGroup}; - use rustls::internal::msgs::handshake::{ - ClientHelloPayload, HandshakeMessagePayload, KeyShareEntry, Random, SessionId, - }; - use rustls::{CipherSuite, HandshakeType, SignatureScheme}; - - let provider = provider::default_provider(); - let mut random = [0; 32]; - provider - .secure_random - .fill(&mut random) - .unwrap(); - let random = Random::from(random); - - let rng = ring::rand::SystemRandom::new(); - let kx = ring::agreement::EphemeralPrivateKey::generate(&ring::agreement::ECDH_P256, &rng) - .unwrap() - .compute_public_key() - .unwrap(); - - let client_hello = MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::ClientHello, - payload: HandshakePayload::ClientHello(ClientHelloPayload { - client_version: ProtocolVersion::TLSv1_3, - random, - session_id: SessionId::random(provider.secure_random).unwrap(), - cipher_suites: vec![CipherSuite::TLS13_AES_128_GCM_SHA256], - compression_methods: vec![Compression::Null], - extensions: vec![ - ClientExtension::SupportedVersions(vec![ProtocolVersion::TLSv1_3]), - ClientExtension::NamedGroups(vec![NamedGroup::secp256r1]), - ClientExtension::SignatureAlgorithms(vec![SignatureScheme::ED25519]), - ClientExtension::KeyShare(vec![KeyShareEntry::new( - NamedGroup::secp256r1, - kx.as_ref(), - )]), - ], - }), - }); - - let mut buf = Vec::with_capacity(512); - client_hello.encode(&mut buf); - assert_eq!( - server.read_hs(buf.as_slice()).err(), - Some(Error::PeerMisbehaved( - PeerMisbehaved::MissingQuicTransportParameters - )) - ); - } - - #[test] - #[cfg(feature = "ring")] // uses ring APIs directly - fn test_quic_server_no_tls12() { - let mut server_config = - make_server_config_with_versions(KeyType::Ed25519, &[&rustls::version::TLS13]); - server_config.alpn_protocols = vec!["foo".into()]; - let server_config = Arc::new(server_config); - - use rustls::internal::msgs::enums::{Compression, NamedGroup}; - use rustls::internal::msgs::handshake::{ - ClientHelloPayload, HandshakeMessagePayload, KeyShareEntry, Random, SessionId, - }; - use rustls::{CipherSuite, HandshakeType, SignatureScheme}; - - let provider = provider::default_provider(); - let mut random = [0; 32]; - provider - .secure_random - .fill(&mut random) - .unwrap(); - let random = Random::from(random); - - let rng = ring::rand::SystemRandom::new(); - let kx = ring::agreement::EphemeralPrivateKey::generate(&ring::agreement::X25519, &rng) - .unwrap() - .compute_public_key() - .unwrap(); - - let mut server = quic::ServerConnection::new( - server_config, - quic::Version::V1, - b"server params".to_vec(), - ) - .unwrap(); - - let client_hello = MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::ClientHello, - payload: HandshakePayload::ClientHello(ClientHelloPayload { - client_version: ProtocolVersion::TLSv1_2, - random, - session_id: SessionId::random(provider.secure_random).unwrap(), - cipher_suites: vec![CipherSuite::TLS13_AES_128_GCM_SHA256], - compression_methods: vec![Compression::Null], - extensions: vec![ - ClientExtension::NamedGroups(vec![NamedGroup::X25519]), - ClientExtension::SignatureAlgorithms(vec![SignatureScheme::ED25519]), - ClientExtension::KeyShare(vec![KeyShareEntry::new( - NamedGroup::X25519, - kx.as_ref(), - )]), - ], - }), - }); - - let mut buf = Vec::with_capacity(512); - client_hello.encode(&mut buf); - assert_eq!( - server.read_hs(buf.as_slice()).err(), - Some(Error::PeerIncompatible( - PeerIncompatible::SupportedVersionsExtensionRequired - )), - ); - } - - #[test] - fn packet_key_api() { - use cipher_suite::TLS13_AES_128_GCM_SHA256; - use rustls::quic::{Keys, Version}; - use rustls::Side; - - // Test vectors: https://www.rfc-editor.org/rfc/rfc9001.html#name-client-initial - const CONNECTION_ID: &[u8] = &[0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08]; - const PACKET_NUMBER: u64 = 2; - const PLAIN_HEADER: &[u8] = &[ - 0xc3, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, - 0x00, 0x00, 0x44, 0x9e, 0x00, 0x00, 0x00, 0x02, - ]; - - const PAYLOAD: &[u8] = &[ - 0x06, 0x00, 0x40, 0xf1, 0x01, 0x00, 0x00, 0xed, 0x03, 0x03, 0xeb, 0xf8, 0xfa, 0x56, - 0xf1, 0x29, 0x39, 0xb9, 0x58, 0x4a, 0x38, 0x96, 0x47, 0x2e, 0xc4, 0x0b, 0xb8, 0x63, - 0xcf, 0xd3, 0xe8, 0x68, 0x04, 0xfe, 0x3a, 0x47, 0xf0, 0x6a, 0x2b, 0x69, 0x48, 0x4c, - 0x00, 0x00, 0x04, 0x13, 0x01, 0x13, 0x02, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x0e, 0x00, 0x00, 0x0b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, - 0x63, 0x6f, 0x6d, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x06, - 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x10, 0x00, 0x07, 0x00, 0x05, 0x04, 0x61, - 0x6c, 0x70, 0x6e, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, - 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x93, 0x70, 0xb2, 0xc9, 0xca, 0xa4, - 0x7f, 0xba, 0xba, 0xf4, 0x55, 0x9f, 0xed, 0xba, 0x75, 0x3d, 0xe1, 0x71, 0xfa, 0x71, - 0xf5, 0x0f, 0x1c, 0xe1, 0x5d, 0x43, 0xe9, 0x94, 0xec, 0x74, 0xd7, 0x48, 0x00, 0x2b, - 0x00, 0x03, 0x02, 0x03, 0x04, 0x00, 0x0d, 0x00, 0x10, 0x00, 0x0e, 0x04, 0x03, 0x05, - 0x03, 0x06, 0x03, 0x02, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x00, 0x2d, 0x00, - 0x02, 0x01, 0x01, 0x00, 0x1c, 0x00, 0x02, 0x40, 0x01, 0x00, 0x39, 0x00, 0x32, 0x04, - 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x05, 0x04, 0x80, 0x00, 0xff, - 0xff, 0x07, 0x04, 0x80, 0x00, 0xff, 0xff, 0x08, 0x01, 0x10, 0x01, 0x04, 0x80, 0x00, - 0x75, 0x30, 0x09, 0x01, 0x10, 0x0f, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, - 0x08, 0x06, 0x04, 0x80, 0x00, 0xff, 0xff, - ]; - - let client_keys = Keys::initial( - Version::V1, - TLS13_AES_128_GCM_SHA256 - .tls13() - .unwrap(), - TLS13_AES_128_GCM_SHA256 - .tls13() - .unwrap() - .quic - .unwrap(), - CONNECTION_ID, - Side::Client, - ); - assert_eq!(client_keys.local.packet.tag_len(), 16); - - let mut buf = Vec::new(); - buf.extend(PLAIN_HEADER); - buf.extend(PAYLOAD); - let header_len = PLAIN_HEADER.len(); - let tag_len = client_keys.local.packet.tag_len(); - let padding_len = 1200 - header_len - PAYLOAD.len() - tag_len; - buf.extend(std::iter::repeat(0).take(padding_len)); - let (header, payload) = buf.split_at_mut(header_len); - let tag = client_keys - .local - .packet - .encrypt_in_place(PACKET_NUMBER, header, payload) - .unwrap(); - - let sample_len = client_keys.local.header.sample_len(); - let sample = &payload[..sample_len]; - let (first, rest) = header.split_at_mut(1); - client_keys - .local - .header - .encrypt_in_place(sample, &mut first[0], &mut rest[17..21]) - .unwrap(); - buf.extend_from_slice(tag.as_ref()); - - const PROTECTED: &[u8] = &[ - 0xc0, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, - 0x00, 0x00, 0x44, 0x9e, 0x7b, 0x9a, 0xec, 0x34, 0xd1, 0xb1, 0xc9, 0x8d, 0xd7, 0x68, - 0x9f, 0xb8, 0xec, 0x11, 0xd2, 0x42, 0xb1, 0x23, 0xdc, 0x9b, 0xd8, 0xba, 0xb9, 0x36, - 0xb4, 0x7d, 0x92, 0xec, 0x35, 0x6c, 0x0b, 0xab, 0x7d, 0xf5, 0x97, 0x6d, 0x27, 0xcd, - 0x44, 0x9f, 0x63, 0x30, 0x00, 0x99, 0xf3, 0x99, 0x1c, 0x26, 0x0e, 0xc4, 0xc6, 0x0d, - 0x17, 0xb3, 0x1f, 0x84, 0x29, 0x15, 0x7b, 0xb3, 0x5a, 0x12, 0x82, 0xa6, 0x43, 0xa8, - 0xd2, 0x26, 0x2c, 0xad, 0x67, 0x50, 0x0c, 0xad, 0xb8, 0xe7, 0x37, 0x8c, 0x8e, 0xb7, - 0x53, 0x9e, 0xc4, 0xd4, 0x90, 0x5f, 0xed, 0x1b, 0xee, 0x1f, 0xc8, 0xaa, 0xfb, 0xa1, - 0x7c, 0x75, 0x0e, 0x2c, 0x7a, 0xce, 0x01, 0xe6, 0x00, 0x5f, 0x80, 0xfc, 0xb7, 0xdf, - 0x62, 0x12, 0x30, 0xc8, 0x37, 0x11, 0xb3, 0x93, 0x43, 0xfa, 0x02, 0x8c, 0xea, 0x7f, - 0x7f, 0xb5, 0xff, 0x89, 0xea, 0xc2, 0x30, 0x82, 0x49, 0xa0, 0x22, 0x52, 0x15, 0x5e, - 0x23, 0x47, 0xb6, 0x3d, 0x58, 0xc5, 0x45, 0x7a, 0xfd, 0x84, 0xd0, 0x5d, 0xff, 0xfd, - 0xb2, 0x03, 0x92, 0x84, 0x4a, 0xe8, 0x12, 0x15, 0x46, 0x82, 0xe9, 0xcf, 0x01, 0x2f, - 0x90, 0x21, 0xa6, 0xf0, 0xbe, 0x17, 0xdd, 0xd0, 0xc2, 0x08, 0x4d, 0xce, 0x25, 0xff, - 0x9b, 0x06, 0xcd, 0xe5, 0x35, 0xd0, 0xf9, 0x20, 0xa2, 0xdb, 0x1b, 0xf3, 0x62, 0xc2, - 0x3e, 0x59, 0x6d, 0x11, 0xa4, 0xf5, 0xa6, 0xcf, 0x39, 0x48, 0x83, 0x8a, 0x3a, 0xec, - 0x4e, 0x15, 0xda, 0xf8, 0x50, 0x0a, 0x6e, 0xf6, 0x9e, 0xc4, 0xe3, 0xfe, 0xb6, 0xb1, - 0xd9, 0x8e, 0x61, 0x0a, 0xc8, 0xb7, 0xec, 0x3f, 0xaf, 0x6a, 0xd7, 0x60, 0xb7, 0xba, - 0xd1, 0xdb, 0x4b, 0xa3, 0x48, 0x5e, 0x8a, 0x94, 0xdc, 0x25, 0x0a, 0xe3, 0xfd, 0xb4, - 0x1e, 0xd1, 0x5f, 0xb6, 0xa8, 0xe5, 0xeb, 0xa0, 0xfc, 0x3d, 0xd6, 0x0b, 0xc8, 0xe3, - 0x0c, 0x5c, 0x42, 0x87, 0xe5, 0x38, 0x05, 0xdb, 0x05, 0x9a, 0xe0, 0x64, 0x8d, 0xb2, - 0xf6, 0x42, 0x64, 0xed, 0x5e, 0x39, 0xbe, 0x2e, 0x20, 0xd8, 0x2d, 0xf5, 0x66, 0xda, - 0x8d, 0xd5, 0x99, 0x8c, 0xca, 0xbd, 0xae, 0x05, 0x30, 0x60, 0xae, 0x6c, 0x7b, 0x43, - 0x78, 0xe8, 0x46, 0xd2, 0x9f, 0x37, 0xed, 0x7b, 0x4e, 0xa9, 0xec, 0x5d, 0x82, 0xe7, - 0x96, 0x1b, 0x7f, 0x25, 0xa9, 0x32, 0x38, 0x51, 0xf6, 0x81, 0xd5, 0x82, 0x36, 0x3a, - 0xa5, 0xf8, 0x99, 0x37, 0xf5, 0xa6, 0x72, 0x58, 0xbf, 0x63, 0xad, 0x6f, 0x1a, 0x0b, - 0x1d, 0x96, 0xdb, 0xd4, 0xfa, 0xdd, 0xfc, 0xef, 0xc5, 0x26, 0x6b, 0xa6, 0x61, 0x17, - 0x22, 0x39, 0x5c, 0x90, 0x65, 0x56, 0xbe, 0x52, 0xaf, 0xe3, 0xf5, 0x65, 0x63, 0x6a, - 0xd1, 0xb1, 0x7d, 0x50, 0x8b, 0x73, 0xd8, 0x74, 0x3e, 0xeb, 0x52, 0x4b, 0xe2, 0x2b, - 0x3d, 0xcb, 0xc2, 0xc7, 0x46, 0x8d, 0x54, 0x11, 0x9c, 0x74, 0x68, 0x44, 0x9a, 0x13, - 0xd8, 0xe3, 0xb9, 0x58, 0x11, 0xa1, 0x98, 0xf3, 0x49, 0x1d, 0xe3, 0xe7, 0xfe, 0x94, - 0x2b, 0x33, 0x04, 0x07, 0xab, 0xf8, 0x2a, 0x4e, 0xd7, 0xc1, 0xb3, 0x11, 0x66, 0x3a, - 0xc6, 0x98, 0x90, 0xf4, 0x15, 0x70, 0x15, 0x85, 0x3d, 0x91, 0xe9, 0x23, 0x03, 0x7c, - 0x22, 0x7a, 0x33, 0xcd, 0xd5, 0xec, 0x28, 0x1c, 0xa3, 0xf7, 0x9c, 0x44, 0x54, 0x6b, - 0x9d, 0x90, 0xca, 0x00, 0xf0, 0x64, 0xc9, 0x9e, 0x3d, 0xd9, 0x79, 0x11, 0xd3, 0x9f, - 0xe9, 0xc5, 0xd0, 0xb2, 0x3a, 0x22, 0x9a, 0x23, 0x4c, 0xb3, 0x61, 0x86, 0xc4, 0x81, - 0x9e, 0x8b, 0x9c, 0x59, 0x27, 0x72, 0x66, 0x32, 0x29, 0x1d, 0x6a, 0x41, 0x82, 0x11, - 0xcc, 0x29, 0x62, 0xe2, 0x0f, 0xe4, 0x7f, 0xeb, 0x3e, 0xdf, 0x33, 0x0f, 0x2c, 0x60, - 0x3a, 0x9d, 0x48, 0xc0, 0xfc, 0xb5, 0x69, 0x9d, 0xbf, 0xe5, 0x89, 0x64, 0x25, 0xc5, - 0xba, 0xc4, 0xae, 0xe8, 0x2e, 0x57, 0xa8, 0x5a, 0xaf, 0x4e, 0x25, 0x13, 0xe4, 0xf0, - 0x57, 0x96, 0xb0, 0x7b, 0xa2, 0xee, 0x47, 0xd8, 0x05, 0x06, 0xf8, 0xd2, 0xc2, 0x5e, - 0x50, 0xfd, 0x14, 0xde, 0x71, 0xe6, 0xc4, 0x18, 0x55, 0x93, 0x02, 0xf9, 0x39, 0xb0, - 0xe1, 0xab, 0xd5, 0x76, 0xf2, 0x79, 0xc4, 0xb2, 0xe0, 0xfe, 0xb8, 0x5c, 0x1f, 0x28, - 0xff, 0x18, 0xf5, 0x88, 0x91, 0xff, 0xef, 0x13, 0x2e, 0xef, 0x2f, 0xa0, 0x93, 0x46, - 0xae, 0xe3, 0x3c, 0x28, 0xeb, 0x13, 0x0f, 0xf2, 0x8f, 0x5b, 0x76, 0x69, 0x53, 0x33, - 0x41, 0x13, 0x21, 0x19, 0x96, 0xd2, 0x00, 0x11, 0xa1, 0x98, 0xe3, 0xfc, 0x43, 0x3f, - 0x9f, 0x25, 0x41, 0x01, 0x0a, 0xe1, 0x7c, 0x1b, 0xf2, 0x02, 0x58, 0x0f, 0x60, 0x47, - 0x47, 0x2f, 0xb3, 0x68, 0x57, 0xfe, 0x84, 0x3b, 0x19, 0xf5, 0x98, 0x40, 0x09, 0xdd, - 0xc3, 0x24, 0x04, 0x4e, 0x84, 0x7a, 0x4f, 0x4a, 0x0a, 0xb3, 0x4f, 0x71, 0x95, 0x95, - 0xde, 0x37, 0x25, 0x2d, 0x62, 0x35, 0x36, 0x5e, 0x9b, 0x84, 0x39, 0x2b, 0x06, 0x10, - 0x85, 0x34, 0x9d, 0x73, 0x20, 0x3a, 0x4a, 0x13, 0xe9, 0x6f, 0x54, 0x32, 0xec, 0x0f, - 0xd4, 0xa1, 0xee, 0x65, 0xac, 0xcd, 0xd5, 0xe3, 0x90, 0x4d, 0xf5, 0x4c, 0x1d, 0xa5, - 0x10, 0xb0, 0xff, 0x20, 0xdc, 0xc0, 0xc7, 0x7f, 0xcb, 0x2c, 0x0e, 0x0e, 0xb6, 0x05, - 0xcb, 0x05, 0x04, 0xdb, 0x87, 0x63, 0x2c, 0xf3, 0xd8, 0xb4, 0xda, 0xe6, 0xe7, 0x05, - 0x76, 0x9d, 0x1d, 0xe3, 0x54, 0x27, 0x01, 0x23, 0xcb, 0x11, 0x45, 0x0e, 0xfc, 0x60, - 0xac, 0x47, 0x68, 0x3d, 0x7b, 0x8d, 0x0f, 0x81, 0x13, 0x65, 0x56, 0x5f, 0xd9, 0x8c, - 0x4c, 0x8e, 0xb9, 0x36, 0xbc, 0xab, 0x8d, 0x06, 0x9f, 0xc3, 0x3b, 0xd8, 0x01, 0xb0, - 0x3a, 0xde, 0xa2, 0xe1, 0xfb, 0xc5, 0xaa, 0x46, 0x3d, 0x08, 0xca, 0x19, 0x89, 0x6d, - 0x2b, 0xf5, 0x9a, 0x07, 0x1b, 0x85, 0x1e, 0x6c, 0x23, 0x90, 0x52, 0x17, 0x2f, 0x29, - 0x6b, 0xfb, 0x5e, 0x72, 0x40, 0x47, 0x90, 0xa2, 0x18, 0x10, 0x14, 0xf3, 0xb9, 0x4a, - 0x4e, 0x97, 0xd1, 0x17, 0xb4, 0x38, 0x13, 0x03, 0x68, 0xcc, 0x39, 0xdb, 0xb2, 0xd1, - 0x98, 0x06, 0x5a, 0xe3, 0x98, 0x65, 0x47, 0x92, 0x6c, 0xd2, 0x16, 0x2f, 0x40, 0xa2, - 0x9f, 0x0c, 0x3c, 0x87, 0x45, 0xc0, 0xf5, 0x0f, 0xba, 0x38, 0x52, 0xe5, 0x66, 0xd4, - 0x45, 0x75, 0xc2, 0x9d, 0x39, 0xa0, 0x3f, 0x0c, 0xda, 0x72, 0x19, 0x84, 0xb6, 0xf4, - 0x40, 0x59, 0x1f, 0x35, 0x5e, 0x12, 0xd4, 0x39, 0xff, 0x15, 0x0a, 0xab, 0x76, 0x13, - 0x49, 0x9d, 0xbd, 0x49, 0xad, 0xab, 0xc8, 0x67, 0x6e, 0xef, 0x02, 0x3b, 0x15, 0xb6, - 0x5b, 0xfc, 0x5c, 0xa0, 0x69, 0x48, 0x10, 0x9f, 0x23, 0xf3, 0x50, 0xdb, 0x82, 0x12, - 0x35, 0x35, 0xeb, 0x8a, 0x74, 0x33, 0xbd, 0xab, 0xcb, 0x90, 0x92, 0x71, 0xa6, 0xec, - 0xbc, 0xb5, 0x8b, 0x93, 0x6a, 0x88, 0xcd, 0x4e, 0x8f, 0x2e, 0x6f, 0xf5, 0x80, 0x01, - 0x75, 0xf1, 0x13, 0x25, 0x3d, 0x8f, 0xa9, 0xca, 0x88, 0x85, 0xc2, 0xf5, 0x52, 0xe6, - 0x57, 0xdc, 0x60, 0x3f, 0x25, 0x2e, 0x1a, 0x8e, 0x30, 0x8f, 0x76, 0xf0, 0xbe, 0x79, - 0xe2, 0xfb, 0x8f, 0x5d, 0x5f, 0xbb, 0xe2, 0xe3, 0x0e, 0xca, 0xdd, 0x22, 0x07, 0x23, - 0xc8, 0xc0, 0xae, 0xa8, 0x07, 0x8c, 0xdf, 0xcb, 0x38, 0x68, 0x26, 0x3f, 0xf8, 0xf0, - 0x94, 0x00, 0x54, 0xda, 0x48, 0x78, 0x18, 0x93, 0xa7, 0xe4, 0x9a, 0xd5, 0xaf, 0xf4, - 0xaf, 0x30, 0x0c, 0xd8, 0x04, 0xa6, 0xb6, 0x27, 0x9a, 0xb3, 0xff, 0x3a, 0xfb, 0x64, - 0x49, 0x1c, 0x85, 0x19, 0x4a, 0xab, 0x76, 0x0d, 0x58, 0xa6, 0x06, 0x65, 0x4f, 0x9f, - 0x44, 0x00, 0xe8, 0xb3, 0x85, 0x91, 0x35, 0x6f, 0xbf, 0x64, 0x25, 0xac, 0xa2, 0x6d, - 0xc8, 0x52, 0x44, 0x25, 0x9f, 0xf2, 0xb1, 0x9c, 0x41, 0xb9, 0xf9, 0x6f, 0x3c, 0xa9, - 0xec, 0x1d, 0xde, 0x43, 0x4d, 0xa7, 0xd2, 0xd3, 0x92, 0xb9, 0x05, 0xdd, 0xf3, 0xd1, - 0xf9, 0xaf, 0x93, 0xd1, 0xaf, 0x59, 0x50, 0xbd, 0x49, 0x3f, 0x5a, 0xa7, 0x31, 0xb4, - 0x05, 0x6d, 0xf3, 0x1b, 0xd2, 0x67, 0xb6, 0xb9, 0x0a, 0x07, 0x98, 0x31, 0xaa, 0xf5, - 0x79, 0xbe, 0x0a, 0x39, 0x01, 0x31, 0x37, 0xaa, 0xc6, 0xd4, 0x04, 0xf5, 0x18, 0xcf, - 0xd4, 0x68, 0x40, 0x64, 0x7e, 0x78, 0xbf, 0xe7, 0x06, 0xca, 0x4c, 0xf5, 0xe9, 0xc5, - 0x45, 0x3e, 0x9f, 0x7c, 0xfd, 0x2b, 0x8b, 0x4c, 0x8d, 0x16, 0x9a, 0x44, 0xe5, 0x5c, - 0x88, 0xd4, 0xa9, 0xa7, 0xf9, 0x47, 0x42, 0x41, 0xe2, 0x21, 0xaf, 0x44, 0x86, 0x00, - 0x18, 0xab, 0x08, 0x56, 0x97, 0x2e, 0x19, 0x4c, 0xd9, 0x34, - ]; - - assert_eq!(&buf, PROTECTED); - - let (header, payload) = buf.split_at_mut(header_len); - let (first, rest) = header.split_at_mut(1); - let sample = &payload[..sample_len]; - - let server_keys = Keys::initial( - Version::V1, - TLS13_AES_128_GCM_SHA256 - .tls13() - .unwrap(), - TLS13_AES_128_GCM_SHA256 - .tls13() - .unwrap() - .quic - .unwrap(), - CONNECTION_ID, - Side::Server, - ); - server_keys - .remote - .header - .decrypt_in_place(sample, &mut first[0], &mut rest[17..21]) - .unwrap(); - let payload = server_keys - .remote - .packet - .decrypt_in_place(PACKET_NUMBER, header, payload) - .unwrap(); - - assert_eq!(&payload[..PAYLOAD.len()], PAYLOAD); - assert_eq!(payload.len(), buf.len() - header_len - tag_len); - } - - #[test] - fn test_quic_exporter() { - for &kt in ALL_KEY_TYPES { - let client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS13]); - let server_config = make_server_config_with_versions(kt, &[&rustls::version::TLS13]); - - do_exporter_test(client_config, server_config); - } - } - - #[test] - fn test_fragmented_append() { - // Create a QUIC client connection. - let client_config = - make_client_config_with_versions(KeyType::Rsa2048, &[&rustls::version::TLS13]); - let client_config = Arc::new(client_config); - let mut client = quic::ClientConnection::new( - Arc::clone(&client_config), - quic::Version::V1, - server_name("localhost"), - b"client params"[..].into(), - ) - .unwrap(); - - // Construct a message that is too large to fit in a single QUIC packet. - // We want the partial pieces to be large enough to overflow the deframer's - // 4096 byte buffer if mishandled. - let mut out = vec![0; 4096]; - let len_bytes = u32::to_be_bytes(9266_u32); - out[1..4].copy_from_slice(&len_bytes[1..]); - - // Read the message - this will put us into a joining handshake message state, buffering - // 4096 bytes into the deframer buffer. - client.read_hs(&out).unwrap(); - - // Read the message again - once more it isn't a complete message, so we'll try to - // append another 4096 bytes into the deframer buffer. - // - // If the deframer mishandles writing into the used buffer space this will panic with - // an index out of range error: - // range end index 8192 out of range for slice of length 4096 - client.read_hs(&out).unwrap(); - } -} // mod test_quic - -#[test] -fn test_client_does_not_offer_sha1() { - use rustls::internal::msgs::codec::Reader; - use rustls::internal::msgs::handshake::HandshakePayload; - use rustls::internal::msgs::message::{MessagePayload, OutboundOpaqueMessage}; - use rustls::HandshakeType; - - for kt in ALL_KEY_TYPES { - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(*kt, &[version]); - let (mut client, _) = make_pair_for_configs(client_config, make_server_config(*kt)); - - assert!(client.wants_write()); - let mut buf = [0u8; 262144]; - let sz = client - .write_tls(&mut buf.as_mut()) - .unwrap(); - let msg = OutboundOpaqueMessage::read(&mut Reader::init(&buf[..sz])).unwrap(); - let msg = Message::try_from(msg.into_plain_message()).unwrap(); - assert!(msg.is_handshake_type(HandshakeType::ClientHello)); - - let client_hello = match msg.payload { - MessagePayload::Handshake { parsed, .. } => match parsed.payload { - HandshakePayload::ClientHello(ch) => ch, - _ => unreachable!(), - }, - _ => unreachable!(), - }; - - let sigalgs = client_hello - .sigalgs_extension() - .unwrap(); - assert!( - !sigalgs.contains(&SignatureScheme::RSA_PKCS1_SHA1), - "sha1 unexpectedly offered" - ); - } - } -} - -#[test] -fn test_client_config_keyshare() { - let kx_groups = vec![provider::kx_group::SECP384R1]; - let client_config = make_client_config_with_kx_groups(KeyType::Rsa2048, kx_groups.clone()); - let server_config = make_server_config_with_kx_groups(KeyType::Rsa2048, kx_groups); - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake_until_error(&mut client, &mut server).unwrap(); -} - -#[test] -fn test_client_config_keyshare_mismatch() { - let client_config = - make_client_config_with_kx_groups(KeyType::Rsa2048, vec![provider::kx_group::SECP384R1]); - let server_config = - make_server_config_with_kx_groups(KeyType::Rsa2048, vec![provider::kx_group::X25519]); - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - assert!(do_handshake_until_error(&mut client, &mut server).is_err()); -} - -#[cfg(feature = "tls12")] -#[test] -fn test_client_sends_helloretryrequest() { - // client sends a secp384r1 key share - let mut client_config = make_client_config_with_kx_groups( - KeyType::Rsa2048, - vec![provider::kx_group::SECP384R1, provider::kx_group::X25519], - ); - - let storage = Arc::new(ClientStorage::new()); - client_config.resumption = Resumption::store(storage.clone()); - - // but server only accepts x25519, so a HRR is required - let server_config = - make_server_config_with_kx_groups(KeyType::Rsa2048, vec![provider::kx_group::X25519]); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - - assert_eq!(client.handshake_kind(), None); - assert_eq!(server.handshake_kind(), None); - - // client sends hello - { - let mut pipe = OtherSession::new(&mut server); - let wrlen = client.write_tls(&mut pipe).unwrap(); - assert!(wrlen > 200); - assert_eq!(pipe.writevs.len(), 1); - assert!(pipe.writevs[0].len() == 1); - } - - assert_eq!(client.handshake_kind(), None); - assert_eq!( - server.handshake_kind(), - Some(HandshakeKind::FullWithHelloRetryRequest) - ); - - // server sends HRR - { - let mut pipe = OtherSession::new(&mut client); - let wrlen = server.write_tls(&mut pipe).unwrap(); - assert!(wrlen < 100); // just the hello retry request - assert_eq!(pipe.writevs.len(), 1); // only one writev - assert!(pipe.writevs[0].len() == 2); // hello retry request and CCS - } - - assert_eq!( - client.handshake_kind(), - Some(HandshakeKind::FullWithHelloRetryRequest) - ); - assert_eq!( - server.handshake_kind(), - Some(HandshakeKind::FullWithHelloRetryRequest) - ); - - // client sends fixed hello - { - let mut pipe = OtherSession::new(&mut server); - let wrlen = client.write_tls(&mut pipe).unwrap(); - assert!(wrlen > 200); // just the client hello retry - assert_eq!(pipe.writevs.len(), 1); // only one writev - assert!(pipe.writevs[0].len() == 2); // only a CCS & client hello retry - } - - // server completes handshake - { - let mut pipe = OtherSession::new(&mut client); - let wrlen = server.write_tls(&mut pipe).unwrap(); - assert!(wrlen > 200); - assert_eq!(pipe.writevs.len(), 1); - assert_eq!(pipe.writevs[0].len(), 2); // { server hello / encrypted exts / cert / cert-verify } / finished - } - - assert_eq!( - client.handshake_kind(), - Some(HandshakeKind::FullWithHelloRetryRequest) - ); - assert_eq!( - server.handshake_kind(), - Some(HandshakeKind::FullWithHelloRetryRequest) - ); - - do_handshake_until_error(&mut client, &mut server).unwrap(); - - // client only did following storage queries: - println!("storage {:#?}", storage.ops()); - assert_eq!(storage.ops().len(), 7); - assert!(matches!( - storage.ops()[0], - ClientStorageOp::TakeTls13Ticket(_, false) - )); - assert!(matches!( - storage.ops()[1], - ClientStorageOp::GetTls12Session(_, false) - )); - assert!(matches!( - storage.ops()[2], - ClientStorageOp::GetKxHint(_, None) - )); - assert!(matches!( - storage.ops()[3], - ClientStorageOp::SetKxHint(_, rustls::NamedGroup::X25519) - )); - assert!(matches!( - storage.ops()[4], - ClientStorageOp::RemoveTls12Session(_) - )); - // server sends 4 tickets by default - assert!(matches!( - storage.ops()[5], - ClientStorageOp::InsertTls13Ticket(_) - )); - assert!(matches!( - storage.ops()[6], - ClientStorageOp::InsertTls13Ticket(_) - )); -} - -#[test] -fn test_client_rejects_hrr_with_varied_session_id() { - use rustls::internal::msgs::handshake::SessionId; - let different_session_id = - SessionId::random(provider::default_provider().secure_random).unwrap(); - - let assert_client_sends_hello_with_secp384 = |msg: &mut Message| -> Altered { - match &mut msg.payload { - MessagePayload::Handshake { parsed, encoded } => match &mut parsed.payload { - HandshakePayload::ClientHello(ch) => { - let keyshares = ch - .keyshare_extension() - .expect("missing key share extension"); - assert_eq!(keyshares.len(), 1); - assert_eq!(keyshares[0].group(), rustls::NamedGroup::secp384r1); - - ch.session_id = different_session_id; - *encoded = Payload::new(parsed.get_encoding()); - } - _ => panic!("unexpected handshake message {parsed:?}"), - }, - _ => panic!("unexpected non-handshake message {msg:?}"), - }; - Altered::InPlace - }; - - let assert_server_requests_retry_and_echoes_session_id = |msg: &mut Message| -> Altered { - match &msg.payload { - MessagePayload::Handshake { parsed, .. } => match &parsed.payload { - HandshakePayload::HelloRetryRequest(hrr) => { - let group = hrr.requested_key_share_group(); - assert_eq!(group, Some(rustls::NamedGroup::X25519)); - - assert_eq!(hrr.session_id, different_session_id); - } - _ => panic!("unexpected handshake message {parsed:?}"), - }, - MessagePayload::ChangeCipherSpec(_) => (), - _ => panic!("unexpected non-handshake message {msg:?}"), - }; - Altered::InPlace - }; - - // client prefers a secp384r1 key share, server only accepts x25519 - let client_config = make_client_config_with_kx_groups( - KeyType::Rsa2048, - vec![provider::kx_group::SECP384R1, provider::kx_group::X25519], - ); - - let server_config = - make_server_config_with_kx_groups(KeyType::Rsa2048, vec![provider::kx_group::X25519]); - - let (client, server) = make_pair_for_configs(client_config, server_config); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered( - &mut client, - assert_client_sends_hello_with_secp384, - &mut server, - ); - server.process_new_packets().unwrap(); - transfer_altered( - &mut server, - assert_server_requests_retry_and_echoes_session_id, - &mut client, - ); - assert_eq!( - client.process_new_packets(), - Err(Error::PeerMisbehaved( - PeerMisbehaved::IllegalHelloRetryRequestWithWrongSessionId - )) - ); -} - -#[cfg(feature = "tls12")] -#[test] -fn test_client_attempts_to_use_unsupported_kx_group() { - // common to both client configs - let shared_storage = Arc::new(ClientStorage::new()); - - // first, client sends a secp-256 share and server agrees. secp-256 is inserted - // into kx group cache. - let mut client_config_1 = - make_client_config_with_kx_groups(KeyType::Rsa2048, vec![provider::kx_group::SECP256R1]); - client_config_1.resumption = Resumption::store(shared_storage.clone()); - - // second, client only supports secp-384 and so kx group cache - // contains an unusable value. - let mut client_config_2 = - make_client_config_with_kx_groups(KeyType::Rsa2048, vec![provider::kx_group::SECP384R1]); - client_config_2.resumption = Resumption::store(shared_storage.clone()); - - let server_config = make_server_config(KeyType::Rsa2048); - - // first handshake - let (mut client_1, mut server) = make_pair_for_configs(client_config_1, server_config.clone()); - do_handshake_until_error(&mut client_1, &mut server).unwrap(); - - let ops = shared_storage.ops(); - println!("storage {:#?}", ops); - assert_eq!(ops.len(), 7); - assert!(matches!( - ops[3], - ClientStorageOp::SetKxHint(_, rustls::NamedGroup::secp256r1) - )); - - // second handshake - let (mut client_2, mut server) = make_pair_for_configs(client_config_2, server_config); - do_handshake_until_error(&mut client_2, &mut server).unwrap(); - - let ops = shared_storage.ops(); - println!("storage {:?} {:#?}", ops.len(), ops); - assert_eq!(ops.len(), 13); - assert!(matches!(ops[7], ClientStorageOp::TakeTls13Ticket(_, true))); - assert!(matches!( - ops[8], - ClientStorageOp::GetKxHint(_, Some(rustls::NamedGroup::secp256r1)) - )); - assert!(matches!( - ops[9], - ClientStorageOp::SetKxHint(_, rustls::NamedGroup::secp384r1) - )); -} - -#[cfg(feature = "tls12")] -#[test] -fn test_client_sends_share_for_less_preferred_group() { - // this is a test for the case described in: - // https://datatracker.ietf.org/doc/draft-davidben-tls-key-share-prediction/ - - // common to both client configs - let shared_storage = Arc::new(ClientStorage::new()); - - // first, client sends a secp384r1 share and server agrees. secp384r1 is inserted - // into kx group cache. - let mut client_config_1 = - make_client_config_with_kx_groups(KeyType::Rsa2048, vec![provider::kx_group::SECP384R1]); - client_config_1.resumption = Resumption::store(shared_storage.clone()); - - // second, client supports (x25519, secp384r1) and so kx group cache - // contains a supported but less-preferred group. - let mut client_config_2 = make_client_config_with_kx_groups( - KeyType::Rsa2048, - vec![provider::kx_group::X25519, provider::kx_group::SECP384R1], - ); - client_config_2.resumption = Resumption::store(shared_storage.clone()); - - let server_config = - make_server_config_with_kx_groups(KeyType::Rsa2048, provider::ALL_KX_GROUPS.to_vec()); - - // first handshake - let (mut client_1, mut server) = make_pair_for_configs(client_config_1, server_config.clone()); - do_handshake_until_error(&mut client_1, &mut server).unwrap(); - - let ops = shared_storage.ops(); - println!("storage {:#?}", ops); - assert_eq!(ops.len(), 7); - assert!(matches!( - ops[3], - ClientStorageOp::SetKxHint(_, rustls::NamedGroup::secp384r1) - )); - - // second handshake (this must HRR to the most-preferred group) - let assert_client_sends_secp384_share = |msg: &mut Message| -> Altered { - match &msg.payload { - MessagePayload::Handshake { parsed, .. } => match &parsed.payload { - HandshakePayload::ClientHello(ch) => { - let keyshares = ch - .keyshare_extension() - .expect("missing key share extension"); - assert_eq!(keyshares.len(), 1); - assert_eq!(keyshares[0].group(), rustls::NamedGroup::secp384r1); - } - _ => panic!("unexpected handshake message {:?}", parsed), - }, - _ => panic!("unexpected non-handshake message {:?}", msg), - }; - Altered::InPlace - }; - - let assert_server_requests_retry_to_x25519 = |msg: &mut Message| -> Altered { - match &msg.payload { - MessagePayload::Handshake { parsed, .. } => match &parsed.payload { - HandshakePayload::HelloRetryRequest(hrr) => { - let group = hrr.requested_key_share_group(); - assert_eq!(group, Some(rustls::NamedGroup::X25519)); - } - _ => panic!("unexpected handshake message {:?}", parsed), - }, - MessagePayload::ChangeCipherSpec(_) => (), - _ => panic!("unexpected non-handshake message {:?}", msg), - }; - Altered::InPlace - }; - - let (client_2, server) = make_pair_for_configs(client_config_2, server_config); - let (mut client_2, mut server) = (client_2.into(), server.into()); - transfer_altered( - &mut client_2, - assert_client_sends_secp384_share, - &mut server, - ); - server.process_new_packets().unwrap(); - transfer_altered( - &mut server, - assert_server_requests_retry_to_x25519, - &mut client_2, - ); - client_2.process_new_packets().unwrap(); -} - -#[cfg(feature = "tls12")] -#[test] -fn test_tls13_client_resumption_does_not_reuse_tickets() { - let shared_storage = Arc::new(ClientStorage::new()); - - let mut client_config = make_client_config(KeyType::Rsa2048); - client_config.resumption = Resumption::store(shared_storage.clone()); - let client_config = Arc::new(client_config); - - let mut server_config = make_server_config(KeyType::Rsa2048); - server_config.send_tls13_tickets = 5; - let server_config = Arc::new(server_config); - - // first handshake: client obtains 5 tickets from server. - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake_until_error(&mut client, &mut server).unwrap(); - - let ops = shared_storage.ops_and_reset(); - println!("storage {:#?}", ops); - assert_eq!(ops.len(), 10); - assert!(matches!(ops[5], ClientStorageOp::InsertTls13Ticket(_))); - assert!(matches!(ops[6], ClientStorageOp::InsertTls13Ticket(_))); - assert!(matches!(ops[7], ClientStorageOp::InsertTls13Ticket(_))); - assert!(matches!(ops[8], ClientStorageOp::InsertTls13Ticket(_))); - assert!(matches!(ops[9], ClientStorageOp::InsertTls13Ticket(_))); - - // 5 subsequent handshakes: all are resumptions - - // Note: we don't do complete the handshakes, because that means - // we get five additional tickets per connection which is unhelpful - // in this test. It also acts to record a "Happy Eyeballs"-type use - // case, where a client speculatively makes many connection attempts - // in parallel without knowledge of which will work due to underlying - // connectivity uncertainty. - for _ in 0..5 { - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - - let ops = shared_storage.ops_and_reset(); - assert!(matches!(ops[0], ClientStorageOp::TakeTls13Ticket(_, true))); - } - - // 6th subsequent handshake: cannot be resumed; we ran out of tickets - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - - let ops = shared_storage.ops_and_reset(); - println!("last {:?}", ops); - assert!(matches!(ops[0], ClientStorageOp::TakeTls13Ticket(_, false))); -} - -#[test] -fn test_client_mtu_reduction() { - struct CollectWrites { - writevs: Vec>, - } - - impl io::Write for CollectWrites { - fn write(&mut self, _: &[u8]) -> io::Result { - panic!() - } - fn flush(&mut self) -> io::Result<()> { - panic!() - } - fn write_vectored(&mut self, b: &[io::IoSlice<'_>]) -> io::Result { - let writes = b - .iter() - .map(|slice| slice.len()) - .collect::>(); - let len = writes.iter().sum(); - self.writevs.push(writes); - Ok(len) - } - } - - fn collect_write_lengths(client: &mut ClientConnection) -> Vec { - let mut collector = CollectWrites { writevs: vec![] }; - - client - .write_tls(&mut collector) - .unwrap(); - assert_eq!(collector.writevs.len(), 1); - collector.writevs[0].clone() - } - - for kt in ALL_KEY_TYPES { - let mut client_config = make_client_config(*kt); - client_config.max_fragment_size = Some(64); - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let writes = collect_write_lengths(&mut client); - println!("writes at mtu=64: {:?}", writes); - assert!(writes.iter().all(|x| *x <= 64)); - assert!(writes.len() > 1); - } -} - -#[test] -fn test_server_mtu_reduction() { - let mut server_config = make_server_config(KeyType::Rsa2048); - server_config.max_fragment_size = Some(64); - server_config.send_half_rtt_data = true; - let (mut client, mut server) = - make_pair_for_configs(make_client_config(KeyType::Rsa2048), server_config); - - let big_data = [0u8; 2048]; - server - .writer() - .write_all(&big_data) - .unwrap(); - - let encryption_overhead = 20; // FIXME: see issue #991 - - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - { - let mut pipe = OtherSession::new(&mut client); - server.write_tls(&mut pipe).unwrap(); - - assert_eq!(pipe.writevs.len(), 1); - assert!(pipe.writevs[0] - .iter() - .all(|x| *x <= 64 + encryption_overhead)); - } - - client.process_new_packets().unwrap(); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - { - let mut pipe = OtherSession::new(&mut client); - server.write_tls(&mut pipe).unwrap(); - assert_eq!(pipe.writevs.len(), 1); - assert!(pipe.writevs[0] - .iter() - .all(|x| *x <= 64 + encryption_overhead)); - } - - client.process_new_packets().unwrap(); - check_read(&mut client.reader(), &big_data); -} - -fn check_client_max_fragment_size(size: usize) -> Option { - let mut client_config = make_client_config(KeyType::Ed25519); - client_config.max_fragment_size = Some(size); - ClientConnection::new(Arc::new(client_config), server_name("localhost")).err() -} - -#[test] -fn bad_client_max_fragment_sizes() { - assert_eq!( - check_client_max_fragment_size(31), - Some(Error::BadMaxFragmentSize) - ); - assert_eq!(check_client_max_fragment_size(32), None); - assert_eq!(check_client_max_fragment_size(64), None); - assert_eq!(check_client_max_fragment_size(1460), None); - assert_eq!(check_client_max_fragment_size(0x4000), None); - assert_eq!(check_client_max_fragment_size(0x4005), None); - assert_eq!( - check_client_max_fragment_size(0x4006), - Some(Error::BadMaxFragmentSize) - ); - assert_eq!( - check_client_max_fragment_size(0xffff), - Some(Error::BadMaxFragmentSize) - ); -} - -#[test] -fn handshakes_complete_and_data_flows_with_gratuitious_max_fragment_sizes() { - // general exercising of msgs::fragmenter and msgs::deframer - for kt in ALL_KEY_TYPES { - for version in rustls::ALL_VERSIONS { - // no hidden significance to these numbers - for frag_size in [37, 61, 101, 257] { - println!("test kt={kt:?} version={version:?} frag={frag_size:?}"); - let mut client_config = make_client_config_with_versions(*kt, &[version]); - client_config.max_fragment_size = Some(frag_size); - let mut server_config = make_server_config(*kt); - server_config.max_fragment_size = Some(frag_size); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake(&mut client, &mut server); - - // check server -> client data flow - let pattern = (0x00..=0xffu8).collect::>(); - assert_eq!(pattern.len(), server.writer().write(&pattern).unwrap()); - transfer(&mut server, &mut client); - client.process_new_packets().unwrap(); - check_read(&mut client.reader(), &pattern); - - // and client -> server - assert_eq!(pattern.len(), client.writer().write(&pattern).unwrap()); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - check_read(&mut server.reader(), &pattern); - } - } - } -} - -fn assert_lt(left: usize, right: usize) { - if left >= right { - panic!("expected {} < {}", left, right); - } -} - -#[test] -fn connection_types_are_not_huge() { - // Arbitrary sizes - assert_lt(mem::size_of::(), 1600); - assert_lt(mem::size_of::(), 1600); - assert_lt( - mem::size_of::(), - 1600, - ); - assert_lt( - mem::size_of::(), - 1600, - ); -} - -#[test] -fn test_server_rejects_duplicate_sni_names() { - fn duplicate_sni_payload(msg: &mut Message) -> Altered { - alter_sni_extension( - msg, - |snr| { - snr.push(snr[0].clone()); - }, - |parsed, _encoded| Payload::new(parsed.get_encoding()), - ) - } - - let (client, server) = make_pair(KeyType::Rsa2048); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered(&mut client, duplicate_sni_payload, &mut server); - assert_eq!( - server.process_new_packets(), - Err(Error::PeerMisbehaved( - PeerMisbehaved::DuplicateServerNameTypes - )) - ); -} - -#[test] -fn test_server_rejects_empty_sni_extension() { - fn empty_sni_payload(msg: &mut Message) -> Altered { - alter_sni_extension( - msg, - |snr| snr.clear(), - |parsed, _encoded| Payload::new(parsed.get_encoding()), - ) - } - - let (client, server) = make_pair(KeyType::Rsa2048); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered(&mut client, empty_sni_payload, &mut server); - assert_eq!( - server.process_new_packets(), - Err(Error::PeerMisbehaved( - PeerMisbehaved::ServerNameMustContainOneHostName - )) - ); -} - -#[test] -fn test_server_rejects_clients_without_any_kx_groups() { - fn delete_kx_groups(msg: &mut Message) -> Altered { - if let MessagePayload::Handshake { parsed, encoded } = &mut msg.payload { - if let HandshakePayload::ClientHello(ch) = &mut parsed.payload { - for mut ext in ch.extensions.iter_mut() { - if let ClientExtension::NamedGroups(ngs) = &mut ext { - ngs.clear(); - } - if let ClientExtension::KeyShare(ks) = &mut ext { - ks.clear(); - } - } - } - - *encoded = Payload::new(parsed.get_encoding()); - } - Altered::InPlace - } - - let (client, server) = make_pair(KeyType::Rsa2048); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered(&mut client, delete_kx_groups, &mut server); - assert_eq!( - server.process_new_packets(), - Err(Error::PeerIncompatible( - PeerIncompatible::NoKxGroupsInCommon - )) - ); -} - -/// Tests that session_ticket(35) extension -/// is not sent if the client does not support TLS 1.2. -#[test] -fn test_no_session_ticket_request_on_tls_1_3() { - /// Panics if TLS 1.2 session_ticket(35) extension is detected. - /// - /// Does not actually alter the payload. - fn panic_on_session_ticket(msg: &mut Message) -> Altered { - let MessagePayload::Handshake { parsed, encoded: _ } = &msg.payload else { - return Altered::InPlace; - }; - - let HandshakePayload::ClientHello(ch) = &parsed.payload else { - return Altered::InPlace; - }; - - for ext in &ch.extensions { - if matches!(ext, ClientExtension::SessionTicket(_)) { - panic!("TLS 1.2 session_ticket extension in TLS 1.3 handshake detected."); - } - } - - Altered::InPlace - } - - let client_config = - make_client_config_with_versions(KeyType::Rsa2048, &[&rustls::version::TLS13]); - let server_config = make_server_config(KeyType::Rsa2048); - - let (client, server) = make_pair_for_configs(client_config, server_config); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered(&mut client, panic_on_session_ticket, &mut server); -} - -#[test] -fn test_server_rejects_clients_without_any_kx_group_overlap() { - for version in rustls::ALL_VERSIONS { - let (mut client, mut server) = make_pair_for_configs( - make_client_config_with_kx_groups(KeyType::Rsa2048, vec![provider::kx_group::X25519]), - finish_server_config( - KeyType::Rsa2048, - ServerConfig::builder_with_provider( - CryptoProvider { - kx_groups: vec![provider::kx_group::SECP384R1], - ..provider::default_provider() - } - .into(), - ) - .with_protocol_versions(&[version]) - .unwrap(), - ), - ); - transfer(&mut client, &mut server); - assert_eq!( - server.process_new_packets(), - Err(Error::PeerIncompatible( - PeerIncompatible::NoKxGroupsInCommon - )) - ); - transfer(&mut server, &mut client); - assert_eq!( - client.process_new_packets(), - Err(Error::AlertReceived(AlertDescription::HandshakeFailure)) - ); - } -} - -#[test] -fn test_client_rejects_illegal_tls13_ccs() { - fn corrupt_ccs(msg: &mut Message) -> Altered { - if let MessagePayload::ChangeCipherSpec(_) = &mut msg.payload { - println!("seen CCS {:?}", msg); - return Altered::Raw(vec![0x14, 0x03, 0x03, 0x00, 0x02, 0x01, 0x02]); - } - Altered::InPlace - } - - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - - let (mut server, mut client) = (server.into(), client.into()); - - transfer_altered(&mut server, corrupt_ccs, &mut client); - assert_eq!( - client.process_new_packets(), - Err(Error::PeerMisbehaved( - PeerMisbehaved::IllegalMiddleboxChangeCipherSpec - )) - ); -} - -#[cfg(feature = "tls12")] -#[test] -fn test_client_rejects_no_extended_master_secret_extension_when_require_ems_or_fips() { - let key_type = KeyType::Rsa2048; - let mut client_config = make_client_config(key_type); - if provider_is_fips() { - assert!(client_config.require_ems); - } else { - client_config.require_ems = true; - } - let mut server_config = finish_server_config( - key_type, - server_config_builder_with_versions(&[&rustls::version::TLS12]), - ); - server_config.require_ems = false; - let (client, server) = make_pair_for_configs(client_config, server_config); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered(&mut client, remove_ems_request, &mut server); - server.process_new_packets().unwrap(); - transfer_altered(&mut server, |_| Altered::InPlace, &mut client); - assert_eq!( - client.process_new_packets(), - Err(Error::PeerIncompatible( - PeerIncompatible::ExtendedMasterSecretExtensionRequired - )) - ); -} - -#[cfg(feature = "tls12")] -#[test] -fn test_server_rejects_no_extended_master_secret_extension_when_require_ems_or_fips() { - let key_type = KeyType::Rsa2048; - let client_config = make_client_config(key_type); - let mut server_config = finish_server_config( - key_type, - server_config_builder_with_versions(&[&rustls::version::TLS12]), - ); - if provider_is_fips() { - assert!(server_config.require_ems); - } else { - server_config.require_ems = true; - } - let (client, server) = make_pair_for_configs(client_config, server_config); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered(&mut client, remove_ems_request, &mut server); - assert_eq!( - server.process_new_packets(), - Err(Error::PeerIncompatible( - PeerIncompatible::ExtendedMasterSecretExtensionRequired - )) - ); -} - -#[cfg(feature = "tls12")] -fn remove_ems_request(msg: &mut Message) -> Altered { - if let MessagePayload::Handshake { parsed, encoded } = &mut msg.payload { - if let HandshakePayload::ClientHello(ch) = &mut parsed.payload { - ch.extensions - .retain(|ext| !matches!(ext, ClientExtension::ExtendedMasterSecretRequest)) - } - - *encoded = Payload::new(parsed.get_encoding()); - } - - Altered::InPlace -} - -/// https://github.com/rustls/rustls/issues/797 -#[cfg(feature = "tls12")] -#[test] -fn test_client_tls12_no_resume_after_server_downgrade() { - let mut client_config = common::make_client_config(KeyType::Ed25519); - let client_storage = Arc::new(ClientStorage::new()); - client_config.resumption = Resumption::store(client_storage.clone()); - let client_config = Arc::new(client_config); - - let server_config_1 = Arc::new(common::finish_server_config( - KeyType::Ed25519, - server_config_builder_with_versions(&[&rustls::version::TLS13]), - )); - - let mut server_config_2 = common::finish_server_config( - KeyType::Ed25519, - server_config_builder_with_versions(&[&rustls::version::TLS12]), - ); - server_config_2.session_storage = Arc::new(rustls::server::NoServerSessionStorage {}); - - dbg!("handshake 1"); - let mut client_1 = - ClientConnection::new(client_config.clone(), "localhost".try_into().unwrap()).unwrap(); - let mut server_1 = ServerConnection::new(server_config_1).unwrap(); - common::do_handshake(&mut client_1, &mut server_1); - - assert_eq!(client_storage.ops().len(), 7); - println!("hs1 storage ops: {:#?}", client_storage.ops()); - assert!(matches!( - client_storage.ops()[3], - ClientStorageOp::SetKxHint(_, _) - )); - assert!(matches!( - client_storage.ops()[4], - ClientStorageOp::RemoveTls12Session(_) - )); - assert!(matches!( - client_storage.ops()[5], - ClientStorageOp::InsertTls13Ticket(_) - )); - - dbg!("handshake 2"); - let mut client_2 = - ClientConnection::new(client_config, "localhost".try_into().unwrap()).unwrap(); - let mut server_2 = ServerConnection::new(Arc::new(server_config_2)).unwrap(); - common::do_handshake(&mut client_2, &mut server_2); - println!("hs2 storage ops: {:#?}", client_storage.ops()); - assert_eq!(client_storage.ops().len(), 9); - - // attempt consumes a TLS1.3 ticket - assert!(matches!( - client_storage.ops()[7], - ClientStorageOp::TakeTls13Ticket(_, true) - )); - - // but ends up with TLS1.2 - assert_eq!( - client_2.protocol_version(), - Some(rustls::ProtocolVersion::TLSv1_2) - ); -} - -#[cfg(feature = "tls12")] -#[test] -fn test_client_with_custom_verifier_can_accept_ecdsa_sha1_signatures() { - fn alter_server_signature_to_ecdsa_sha1(msg: &mut Message) -> Altered { - if let MessagePayload::Handshake { - parsed, - ref mut encoded, - } = &mut msg.payload - { - if let HandshakePayload::ServerKeyExchange(_) = &mut parsed.payload { - // nb. we don't care that this corrupts the signature, key exchange, etc. - let original = encoded.bytes(); - let offset = 40; // of signature scheme - assert_eq!( - &original[offset..offset + 2], - &SignatureScheme::ECDSA_NISTP256_SHA256.to_array(), - "expected ecdsa-sha256" - ); - let mut altered = original.to_vec(); - altered[offset..offset + 2] - .copy_from_slice(&SignatureScheme::ECDSA_SHA1_Legacy.to_array()); - - *encoded = Payload::new(altered); - } - } - Altered::InPlace - } - - let kx_groups = provider::ALL_KX_GROUPS; - let client_config = ClientConfig::builder_with_provider( - CryptoProvider { - kx_groups: kx_groups.to_vec(), - ..provider::default_provider() - } - .into(), - ) - .with_protocol_versions(&[&rustls::version::TLS12]) - .unwrap() - .dangerous() - .with_custom_certificate_verifier(Arc::new(MockServerVerifier::accepts_anything())) - .with_no_client_auth(); - let server_config = make_server_config_with_kx_groups(KeyType::EcdsaP256, kx_groups.to_vec()); - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered( - &mut server, - alter_server_signature_to_ecdsa_sha1, - &mut client, - ); - client.process_new_packets().unwrap(); -} - -#[test] -fn test_acceptor() { - use rustls::server::Acceptor; - - let client_config = Arc::new(make_client_config(KeyType::Ed25519)); - let mut client = ClientConnection::new(client_config, server_name("localhost")).unwrap(); - let mut buf = Vec::new(); - client.write_tls(&mut buf).unwrap(); - - let server_config = Arc::new(make_server_config(KeyType::Ed25519)); - let mut acceptor = Acceptor::default(); - acceptor - .read_tls(&mut buf.as_slice()) - .unwrap(); - let accepted = acceptor.accept().unwrap().unwrap(); - let ch = accepted.client_hello(); - assert_eq!(ch.server_name(), Some("localhost")); - - let server = accepted - .into_connection(server_config) - .unwrap(); - assert!(server.wants_write()); - - // Reusing an acceptor is not allowed - assert_eq!( - acceptor - .read_tls(&mut [0u8].as_ref()) - .err() - .unwrap() - .kind(), - io::ErrorKind::Other, - ); - assert_eq!( - acceptor.accept().err().unwrap().0, - Error::General("Acceptor polled after completion".into()) - ); - - let mut acceptor = Acceptor::default(); - assert!(acceptor.accept().unwrap().is_none()); - acceptor - .read_tls(&mut &buf[..3]) - .unwrap(); // incomplete message - assert!(acceptor.accept().unwrap().is_none()); - - acceptor - .read_tls(&mut [0x80, 0x00].as_ref()) - .unwrap(); // invalid message (len = 32k bytes) - let (err, mut alert) = acceptor.accept().unwrap_err(); - assert_eq!(err, Error::InvalidMessage(InvalidMessage::MessageTooLarge)); - let mut alert_content = Vec::new(); - let _ = alert.write(&mut alert_content); - let expected = build_alert(AlertLevel::Fatal, AlertDescription::DecodeError, &[]); - assert_eq!(alert_content, expected); - - let mut acceptor = Acceptor::default(); - // Minimal valid 1-byte application data message is not a handshake message - acceptor - .read_tls(&mut [0x17, 0x03, 0x03, 0x00, 0x01, 0x00].as_ref()) - .unwrap(); - let (err, mut alert) = acceptor.accept().unwrap_err(); - assert!(matches!(err, Error::InappropriateMessage { .. })); - let mut alert_content = Vec::new(); - let _ = alert.write(&mut alert_content); - assert!(alert_content.is_empty()); // We do not expect an alert for this condition. - - let mut acceptor = Acceptor::default(); - // Minimal 1-byte ClientHello message is not a legal handshake message - acceptor - .read_tls(&mut [0x16, 0x03, 0x03, 0x00, 0x05, 0x01, 0x00, 0x00, 0x01, 0x00].as_ref()) - .unwrap(); - let (err, mut alert) = acceptor.accept().unwrap_err(); - assert!(matches!( - err, - Error::InvalidMessage(InvalidMessage::MissingData(_)) - )); - let mut alert_content = Vec::new(); - let _ = alert.write(&mut alert_content); - let expected = build_alert(AlertLevel::Fatal, AlertDescription::DecodeError, &[]); - assert_eq!(alert_content, expected); -} - -#[test] -fn test_acceptor_rejected_handshake() { - use rustls::server::Acceptor; - - let client_config = finish_client_config( - KeyType::Ed25519, - ClientConfig::builder_with_provider(provider::default_provider().into()) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap(), - ); - let mut client = ClientConnection::new(client_config.into(), server_name("localhost")).unwrap(); - let mut buf = Vec::new(); - client.write_tls(&mut buf).unwrap(); - - let server_config = finish_server_config( - KeyType::Ed25519, - ServerConfig::builder_with_provider(provider::default_provider().into()) - .with_protocol_versions(&[&rustls::version::TLS12]) - .unwrap(), - ); - let mut acceptor = Acceptor::default(); - acceptor - .read_tls(&mut buf.as_slice()) - .unwrap(); - let accepted = acceptor.accept().unwrap().unwrap(); - let ch = accepted.client_hello(); - assert_eq!(ch.server_name(), Some("localhost")); - - let (err, mut alert) = accepted - .into_connection(server_config.into()) - .unwrap_err(); - assert_eq!( - err, - Error::PeerIncompatible(PeerIncompatible::Tls12NotOfferedOrEnabled) - ); - - let mut alert_content = Vec::new(); - let _ = alert.write(&mut alert_content); - let expected = build_alert(AlertLevel::Fatal, AlertDescription::ProtocolVersion, &[]); - assert_eq!(alert_content, expected); -} - -#[test] -fn test_no_warning_logging_during_successful_sessions() { - CountingLogger::install(); - CountingLogger::reset(); - - for kt in ALL_KEY_TYPES { - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(*kt, &[version]); - let (mut client, mut server) = - make_pair_for_configs(client_config, make_server_config(*kt)); - do_handshake(&mut client, &mut server); - } - } - - if cfg!(feature = "logging") { - COUNTS.with(|c| { - println!("After tests: {:?}", c.borrow()); - assert_eq!(c.borrow().warn, 0); - assert_eq!(c.borrow().error, 0); - assert_eq!(c.borrow().info, 0); - assert!(c.borrow().trace > 0); - assert!(c.borrow().debug > 0); - }); - } else { - COUNTS.with(|c| { - println!("After tests: {:?}", c.borrow()); - assert_eq!(c.borrow().warn, 0); - assert_eq!(c.borrow().error, 0); - assert_eq!(c.borrow().info, 0); - assert_eq!(c.borrow().trace, 0); - assert_eq!(c.borrow().debug, 0); - }); - } -} - -/// Test that secrets can be extracted and used for encryption/decryption. -#[cfg(feature = "tls12")] -#[test] -fn test_secret_extraction_enabled() { - // Normally, secret extraction would be used to configure kTLS (TLS offload - // to the kernel). We want this test to run on any platform, though, so - // instead we just compare secrets for equality. - - // TLS 1.2 and 1.3 have different mechanisms for key exchange and handshake, - // and secrets are stored/extracted differently, so we want to test them both. - // We support 3 different AEAD algorithms (AES-128-GCM mode, AES-256-GCM, and - // Chacha20Poly1305), so that's 2*3 = 6 combinations to test. - let kt = KeyType::Rsa2048; - for suite in [ - cipher_suite::TLS13_AES_128_GCM_SHA256, - cipher_suite::TLS13_AES_256_GCM_SHA384, - #[cfg(not(feature = "fips"))] - cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, - cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - #[cfg(not(feature = "fips"))] - cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - ] { - let version = suite.version(); - println!("Testing suite {:?}", suite.suite().as_str()); - - // Only offer the cipher suite (and protocol version) that we're testing - let mut server_config = ServerConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![suite], - ..provider::default_provider() - } - .into(), - ) - .with_protocol_versions(&[version]) - .unwrap() - .with_no_client_auth() - .with_single_cert(kt.get_chain(), kt.get_key()) - .unwrap(); - // Opt into secret extraction from both sides - server_config.enable_secret_extraction = true; - let server_config = Arc::new(server_config); - - let mut client_config = make_client_config(kt); - client_config.enable_secret_extraction = true; - - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - - do_handshake(&mut client, &mut server); - - // The handshake is finished, we're now able to extract traffic secrets - let client_secrets = client - .dangerous_extract_secrets() - .unwrap(); - let server_secrets = server - .dangerous_extract_secrets() - .unwrap(); - - // Comparing secrets for equality is something you should never have to - // do in production code, so ConnectionTrafficSecrets doesn't implement - // PartialEq/Eq on purpose. Instead, we have to get creative. - fn explode_secrets(s: &ConnectionTrafficSecrets) -> (&[u8], &[u8]) { - match s { - ConnectionTrafficSecrets::Aes128Gcm { key, iv } => (key.as_ref(), iv.as_ref()), - ConnectionTrafficSecrets::Aes256Gcm { key, iv } => (key.as_ref(), iv.as_ref()), - ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv } => { - (key.as_ref(), iv.as_ref()) - } - _ => panic!("unexpected secret type"), - } - } - - fn assert_secrets_equal( - (l_seq, l_sec): (u64, ConnectionTrafficSecrets), - (r_seq, r_sec): (u64, ConnectionTrafficSecrets), - ) { - assert_eq!(l_seq, r_seq); - assert_eq!(explode_secrets(&l_sec), explode_secrets(&r_sec)); - } - - assert_secrets_equal(client_secrets.tx, server_secrets.rx); - assert_secrets_equal(client_secrets.rx, server_secrets.tx); - } -} - -#[test] -fn test_secret_extract_produces_correct_variant() { - fn check(suite: SupportedCipherSuite, f: impl Fn(ConnectionTrafficSecrets) -> bool) { - let kt = KeyType::Rsa2048; - - let provider: Arc = CryptoProvider { - cipher_suites: vec![suite], - ..provider::default_provider() - } - .into(); - - let mut server_config = finish_server_config( - kt, - ServerConfig::builder_with_provider(provider.clone()) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - server_config.enable_secret_extraction = true; - let server_config = Arc::new(server_config); - - let mut client_config = finish_client_config( - kt, - ClientConfig::builder_with_provider(provider) - .with_safe_default_protocol_versions() - .unwrap(), - ); - client_config.enable_secret_extraction = true; - - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - - do_handshake(&mut client, &mut server); - - let client_secrets = client - .dangerous_extract_secrets() - .unwrap(); - let server_secrets = server - .dangerous_extract_secrets() - .unwrap(); - - assert!(f(client_secrets.tx.1)); - assert!(f(client_secrets.rx.1)); - assert!(f(server_secrets.tx.1)); - assert!(f(server_secrets.rx.1)); - } - - check(cipher_suite::TLS13_AES_128_GCM_SHA256, |sec| { - matches!(sec, ConnectionTrafficSecrets::Aes128Gcm { .. }) - }); - check(cipher_suite::TLS13_AES_256_GCM_SHA384, |sec| { - matches!(sec, ConnectionTrafficSecrets::Aes256Gcm { .. }) - }); - check(cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, |sec| { - matches!(sec, ConnectionTrafficSecrets::Chacha20Poly1305 { .. }) - }); - - check(cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, |sec| { - matches!(sec, ConnectionTrafficSecrets::Aes128Gcm { .. }) - }); - check(cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, |sec| { - matches!(sec, ConnectionTrafficSecrets::Aes256Gcm { .. }) - }); - check( - cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - |sec| matches!(sec, ConnectionTrafficSecrets::Chacha20Poly1305 { .. }), - ); -} - -/// Test that secrets cannot be extracted unless explicitly enabled, and until -/// the handshake is done. -#[cfg(feature = "tls12")] -#[test] -fn test_secret_extraction_disabled_or_too_early() { - let kt = KeyType::Rsa2048; - let provider = Arc::new(CryptoProvider { - cipher_suites: vec![cipher_suite::TLS13_AES_128_GCM_SHA256], - ..provider::default_provider() - }); - - for (server_enable, client_enable) in [(true, false), (false, true)] { - let mut server_config = ServerConfig::builder_with_provider(provider.clone()) - .with_safe_default_protocol_versions() - .unwrap() - .with_no_client_auth() - .with_single_cert(kt.get_chain(), kt.get_key()) - .unwrap(); - server_config.enable_secret_extraction = server_enable; - let server_config = Arc::new(server_config); - - let mut client_config = make_client_config(kt); - client_config.enable_secret_extraction = client_enable; - - let client_config = Arc::new(client_config); - - let (client, server) = make_pair_for_arc_configs(&client_config, &server_config); - - assert!( - client - .dangerous_extract_secrets() - .is_err(), - "extraction should fail until handshake completes" - ); - assert!( - server - .dangerous_extract_secrets() - .is_err(), - "extraction should fail until handshake completes" - ); - - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - - do_handshake(&mut client, &mut server); - - assert_eq!( - server_enable, - server - .dangerous_extract_secrets() - .is_ok() - ); - assert_eq!( - client_enable, - client - .dangerous_extract_secrets() - .is_ok() - ); - } -} - -#[test] -fn test_received_plaintext_backpressure() { - let kt = KeyType::Rsa2048; - - let server_config = Arc::new( - ServerConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![cipher_suite::TLS13_AES_128_GCM_SHA256], - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap() - .with_no_client_auth() - .with_single_cert(kt.get_chain(), kt.get_key()) - .unwrap(), - ); - - let client_config = Arc::new(make_client_config(kt)); - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - - // Fill the server's received plaintext buffer with 16k bytes - let client_buf = [0; 16_385]; - dbg!(client - .writer() - .write(&client_buf) - .unwrap()); - let mut network_buf = Vec::with_capacity(32_768); - let sent = dbg!(client - .write_tls(&mut network_buf) - .unwrap()); - let mut read = 0; - while read < sent { - let new = dbg!(server - .read_tls(&mut &network_buf[read..sent]) - .unwrap()); - if new == 4096 { - read += new; - } else { - break; - } - } - server.process_new_packets().unwrap(); - - // Send two more bytes from client to server - dbg!(client - .writer() - .write(&client_buf[..2]) - .unwrap()); - let sent = dbg!(client - .write_tls(&mut network_buf) - .unwrap()); - - // Get an error because the received plaintext buffer is full - assert!(server - .read_tls(&mut &network_buf[..sent]) - .is_err()); - - // Read out some of the plaintext - server - .reader() - .read_exact(&mut [0; 2]) - .unwrap(); - - // Now there's room again in the plaintext buffer - assert_eq!( - server - .read_tls(&mut &network_buf[..sent]) - .unwrap(), - 24 - ); -} - -#[test] -fn test_debug_server_name_from_ip() { - assert_eq!( - format!( - "{:?}", - ServerName::IpAddress(IpAddr::try_from("127.0.0.1").unwrap()) - ), - "IpAddress(V4(Ipv4Addr([127, 0, 0, 1])))" - ) -} - -#[test] -fn test_debug_server_name_from_string() { - assert_eq!( - format!("{:?}", ServerName::try_from("a.com").unwrap()), - "DnsName(\"a.com\")" - ) -} - -#[cfg(all(feature = "ring", feature = "aws_lc_rs"))] -#[test] -fn test_explicit_provider_selection() { - let client_config = finish_client_config( - KeyType::Rsa2048, - rustls::ClientConfig::builder_with_provider( - rustls::crypto::ring::default_provider().into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ); - let server_config = finish_server_config( - KeyType::Rsa2048, - rustls::ServerConfig::builder_with_provider( - rustls::crypto::aws_lc_rs::default_provider().into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake(&mut client, &mut server); -} - -#[derive(Debug)] -struct FaultyRandom { - // when empty, `fill_random` requests return `GetRandomFailed` - rand_queue: Mutex<&'static [u8]>, -} - -impl rustls::crypto::SecureRandom for FaultyRandom { - fn fill(&self, output: &mut [u8]) -> Result<(), rustls::crypto::GetRandomFailed> { - let mut queue = self.rand_queue.lock().unwrap(); - - println!( - "fill_random request for {} bytes (got {})", - output.len(), - queue.len() - ); - - if queue.len() < output.len() { - return Err(rustls::crypto::GetRandomFailed); - } - - let fixed_output = &queue[..output.len()]; - output.copy_from_slice(fixed_output); - *queue = &queue[output.len()..]; - Ok(()) - } -} - -#[test] -fn test_client_construction_fails_if_random_source_fails_in_first_request() { - static FAULTY_RANDOM: FaultyRandom = FaultyRandom { - rand_queue: Mutex::new(b""), - }; - - let client_config = finish_client_config( - KeyType::Rsa2048, - rustls::ClientConfig::builder_with_provider( - CryptoProvider { - secure_random: &FAULTY_RANDOM, - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - assert_eq!( - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap_err(), - Error::FailedToGetRandomBytes - ); -} - -#[test] -fn test_client_construction_fails_if_random_source_fails_in_second_request() { - static FAULTY_RANDOM: FaultyRandom = FaultyRandom { - rand_queue: Mutex::new(b"nice random number generator huh"), - }; - - let client_config = finish_client_config( - KeyType::Rsa2048, - rustls::ClientConfig::builder_with_provider( - CryptoProvider { - secure_random: &FAULTY_RANDOM, - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - assert_eq!( - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap_err(), - Error::FailedToGetRandomBytes - ); -} - -#[test] -fn test_client_construction_requires_66_bytes_of_random_material() { - static FAULTY_RANDOM: FaultyRandom = FaultyRandom { - rand_queue: Mutex::new( - b"nice random number generator !!!!!\ - it's really not very good is it?", - ), - }; - - let client_config = finish_client_config( - KeyType::Rsa2048, - rustls::ClientConfig::builder_with_provider( - CryptoProvider { - secure_random: &FAULTY_RANDOM, - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - ClientConnection::new(Arc::new(client_config), server_name("localhost")) - .expect("check how much random material ClientConnection::new consumes"); -} - -#[cfg(feature = "tls12")] -#[test] -fn test_client_removes_tls12_session_if_server_sends_undecryptable_first_message() { - fn inject_corrupt_finished_message(msg: &mut Message) -> Altered { - if let MessagePayload::ChangeCipherSpec(_) = msg.payload { - // interdict "real" ChangeCipherSpec with its encoding, plus a faulty encrypted Finished. - let mut raw_change_cipher_spec = [0x14u8, 0x03, 0x03, 0x00, 0x01, 0x01].to_vec(); - let mut corrupt_finished = [0x16, 0x03, 0x03, 0x00, 0x28].to_vec(); - corrupt_finished.extend([0u8; 0x28]); - - let mut both = vec![]; - both.append(&mut raw_change_cipher_spec); - both.append(&mut corrupt_finished); - - Altered::Raw(both) - } else { - Altered::InPlace - } - } - - let mut client_config = - make_client_config_with_versions(KeyType::Rsa2048, &[&rustls::version::TLS12]); - let storage = Arc::new(ClientStorage::new()); - client_config.resumption = Resumption::store(storage.clone()); - let client_config = Arc::new(client_config); - let server_config = Arc::new(make_server_config(KeyType::Rsa2048)); - - // successful handshake to allow resumption - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - - // resumption - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - let mut client = client.into(); - transfer_altered( - &mut server.into(), - inject_corrupt_finished_message, - &mut client, - ); - - // discard storage operations up to this point, to observe the one we want to test for. - storage.ops_and_reset(); - - // client cannot decrypt faulty Finished, and deletes saved session in case - // server resumption is buggy. - assert_eq!( - Some(Error::DecryptError), - client.process_new_packets().err() - ); - - assert!(matches!( - storage.ops()[0], - ClientStorageOp::RemoveTls12Session(_) - )); -} - -#[test] -fn test_client_fips_service_indicator() { - assert_eq!( - make_client_config(KeyType::Rsa2048).fips(), - provider_is_fips() - ); -} - -#[test] -fn test_server_fips_service_indicator() { - assert_eq!( - make_server_config(KeyType::Rsa2048).fips(), - provider_is_fips() - ); -} - -#[test] -fn test_connection_fips_service_indicator() { - let client_config = Arc::new(make_client_config(KeyType::Rsa2048)); - let server_config = Arc::new(make_server_config(KeyType::Rsa2048)); - let conn_pair = make_pair_for_arc_configs(&client_config, &server_config); - // Each connection's FIPS status should reflect the FIPS status of the config it was created - // from. - assert_eq!(client_config.fips(), conn_pair.0.fips()); - assert_eq!(server_config.fips(), conn_pair.1.fips()); -} - -#[test] -fn test_client_fips_service_indicator_includes_require_ems() { - if !provider_is_fips() { - return; - } - - let mut client_config = make_client_config(KeyType::Rsa2048); - assert!(client_config.fips()); - client_config.require_ems = false; - assert!(!client_config.fips()); -} - -#[test] -fn test_server_fips_service_indicator_includes_require_ems() { - if !provider_is_fips() { - return; - } - - let mut server_config = make_server_config(KeyType::Rsa2048); - assert!(server_config.fips()); - server_config.require_ems = false; - assert!(!server_config.fips()); -} - -#[cfg(feature = "aws_lc_rs")] -#[test] -fn test_client_fips_service_indicator_includes_ech_hpke_suite() { - if !provider_is_fips() { - return; - } - - for suite in ALL_SUPPORTED_SUITES { - let (public_key, _) = suite.generate_key_pair().unwrap(); - - let suite_id = suite.suite(); - let bogus_config = EchConfigPayload::V18(EchConfigContents { - key_config: HpkeKeyConfig { - config_id: 10, - kem_id: suite_id.kem, - public_key: PayloadU16(public_key.0.clone()), - symmetric_cipher_suites: vec![HpkeSymmetricCipherSuite { - kdf_id: suite_id.sym.kdf_id, - aead_id: suite_id.sym.aead_id, - }], - }, - maximum_name_length: 0, - public_name: DnsName::try_from("example.com").unwrap(), - extensions: vec![], - }); - let mut bogus_config_bytes = Vec::new(); - vec![bogus_config].encode(&mut bogus_config_bytes); - let ech_config = - EchConfig::new(EchConfigListBytes::from(bogus_config_bytes), &[*suite]).unwrap(); - - // A ECH client configuration should only be considered FIPS approved if the - // ECH HPKE suite is itself FIPS approved. - let config = ClientConfig::builder_with_provider(provider::default_provider().into()) - .with_ech(EchMode::Enable(ech_config)) - .unwrap(); - let config = finish_client_config(KeyType::Rsa2048, config); - assert_eq!(config.fips(), suite.fips()); - - // The same applies if an ECH GREASE client configuration is used. - let config = ClientConfig::builder_with_provider(provider::default_provider().into()) - .with_ech(EchMode::Grease(EchGreaseConfig::new(*suite, public_key))) - .unwrap(); - let config = finish_client_config(KeyType::Rsa2048, config); - assert_eq!(config.fips(), suite.fips()); - - // And a connection made from a client config should retain the fips status of the - // config w.r.t the HPKE suite. - let conn = ClientConnection::new( - config.into(), - ServerName::DnsName(DnsName::try_from("example.org").unwrap()), - ) - .unwrap(); - assert_eq!(conn.fips(), suite.fips()); - } -} - -#[test] -fn test_complete_io_errors_if_close_notify_received_too_early() { - let mut server = ServerConnection::new(Arc::new(make_server_config(KeyType::Rsa2048))).unwrap(); - let client_hello_followed_by_close_notify_alert = b"\ - \x16\x03\x01\x00\xc8\x01\x00\x00\xc4\x03\x03\xec\x12\xdd\x17\x64\ - \xa4\x39\xfd\x7e\x8c\x85\x46\xb8\x4d\x1e\xa0\x6e\xb3\xd7\xa0\x51\ - \xf0\x3c\xb8\x17\x47\x0d\x4c\x54\xc5\xdf\x72\x00\x00\x1c\xea\xea\ - \xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9\xcc\xa8\xc0\x13\xc0\x14\ - \x00\x9c\x00\x9d\x00\x2f\x00\x35\x00\x0a\x01\x00\x00\x7f\xda\xda\ - \x00\x00\xff\x01\x00\x01\x00\x00\x00\x00\x16\x00\x14\x00\x00\x11\ - \x77\x77\x77\x2e\x77\x69\x6b\x69\x70\x65\x64\x69\x61\x2e\x6f\x72\ - \x67\x00\x17\x00\x00\x00\x23\x00\x00\x00\x0d\x00\x14\x00\x12\x04\ - \x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01\x02\ - \x01\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x00\x10\ - \x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31\ - \x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x0a\x00\x08\ - \x1a\x1a\x00\x1d\x00\x17\x00\x18\x1a\x1a\x00\x01\x00\ - \x15\x03\x03\x00\x02\x01\x00"; - - let mut stream = FakeStream(client_hello_followed_by_close_notify_alert); - assert_eq!( - server - .complete_io(&mut stream) - .unwrap_err() - .kind(), - io::ErrorKind::UnexpectedEof - ); -} - -#[test] -fn test_complete_io_with_no_io_needed() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - do_handshake(&mut client, &mut server); - client - .writer() - .write_all(b"hello") - .unwrap(); - client.send_close_notify(); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - server - .writer() - .write_all(b"hello") - .unwrap(); - server.send_close_notify(); - transfer(&mut server, &mut client); - client.process_new_packets().unwrap(); - - // neither want any IO: both directions are closed. - assert!(!client.wants_write()); - assert!(!client.wants_read()); - assert!(!server.wants_write()); - assert!(!server.wants_read()); - assert_eq!( - client - .complete_io(&mut FakeStream(&[])) - .unwrap(), - (0, 0) - ); - assert_eq!( - server - .complete_io(&mut FakeStream(&[])) - .unwrap(), - (0, 0) - ); -} - -#[test] -fn test_junk_after_close_notify_received() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - do_handshake(&mut client, &mut server); - client - .writer() - .write_all(b"hello") - .unwrap(); - client.send_close_notify(); - - let mut client_buffer = vec![]; - client - .write_tls(&mut io::Cursor::new(&mut client_buffer)) - .unwrap(); - - // add some junk that will be dropped from the deframer buffer - // after the close_notify - client_buffer.extend_from_slice(&[0x17, 0x03, 0x03, 0x01]); - - server - .read_tls(&mut io::Cursor::new(&client_buffer[..])) - .unwrap(); - server.process_new_packets().unwrap(); - server.process_new_packets().unwrap(); // check for desync - - // can read data received prior to close_notify - let mut received_data = [0u8; 128]; - let len = server - .reader() - .read(&mut received_data) - .unwrap(); - assert_eq!(&received_data[..len], b"hello"); - - // but subsequent reads just report clean EOF - assert_eq!( - server - .reader() - .read(&mut received_data) - .unwrap(), - 0 - ); -} - -#[test] -fn test_data_after_close_notify_is_ignored() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - do_handshake(&mut client, &mut server); - - client - .writer() - .write_all(b"before") - .unwrap(); - client.send_close_notify(); - client - .writer() - .write_all(b"after") - .unwrap(); - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - - let mut received_data = [0u8; 128]; - let count = server - .reader() - .read(&mut received_data) - .unwrap(); - assert_eq!(&received_data[..count], b"before"); - assert_eq!( - server - .reader() - .read(&mut received_data) - .unwrap(), - 0 - ); -} - -#[test] -fn test_close_notify_sent_prior_to_handshake_complete() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - client.send_close_notify(); - assert_eq!( - do_handshake_until_error(&mut client, &mut server), - Err(ErrorFromPeer::Server(Error::AlertReceived( - AlertDescription::CloseNotify - ))) - ); -} - -#[test] -fn test_subsequent_close_notify_ignored() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - client.send_close_notify(); - assert!(transfer(&mut client, &mut server) > 0); - - // does nothing - client.send_close_notify(); - assert_eq!(transfer(&mut client, &mut server), 0); -} - -#[test] -fn test_second_close_notify_after_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - do_handshake(&mut client, &mut server); - client.send_close_notify(); - assert!(transfer(&mut client, &mut server) > 0); - server.process_new_packets().unwrap(); - - // does nothing - client.send_close_notify(); - assert_eq!(transfer(&mut client, &mut server), 0); -} - -#[test] -fn test_read_tls_artificial_eof_after_close_notify() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); - do_handshake(&mut client, &mut server); - client.send_close_notify(); - assert!(transfer(&mut client, &mut server) > 0); - server.process_new_packets().unwrap(); - - let buf = [1, 2, 3, 4]; - assert_eq!( - server - .read_tls(&mut io::Cursor::new(buf)) - .unwrap(), - 0 - ); -} - -#[test] -fn test_pinned_ocsp_response_given_to_custom_server_cert_verifier() { - let ocsp_response = b"hello-ocsp-world!"; - let kt = KeyType::EcdsaP256; - - for version in rustls::ALL_VERSIONS { - let server_config = server_config_builder() - .with_no_client_auth() - .with_single_cert_with_ocsp(kt.get_chain(), kt.get_key(), ocsp_response.to_vec()) - .unwrap(); - - let client_config = client_config_builder_with_versions(&[version]) - .dangerous() - .with_custom_certificate_verifier(Arc::new(MockServerVerifier::expects_ocsp_response( - ocsp_response, - ))) - .with_no_client_auth(); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake(&mut client, &mut server); - } -} - -#[cfg(feature = "zlib")] -#[test] -fn test_server_uses_cached_compressed_certificates() { - static COMPRESS_COUNT: AtomicUsize = AtomicUsize::new(0); - - let mut server_config = make_server_config(KeyType::Rsa2048); - server_config.cert_compressors = vec![&CountingCompressor]; - let mut client_config = make_client_config(KeyType::Rsa2048); - client_config.resumption = Resumption::disabled(); - - let server_config = Arc::new(server_config); - let client_config = Arc::new(client_config); - - for _i in 0..10 { - dbg!(_i); - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - dbg!(client.handshake_kind()); - } - - assert_eq!(COMPRESS_COUNT.load(Ordering::SeqCst), 1); - - #[derive(Debug)] - struct CountingCompressor; - - impl rustls::compress::CertCompressor for CountingCompressor { - fn compress( - &self, - input: Vec, - level: rustls::compress::CompressionLevel, - ) -> Result, rustls::compress::CompressionFailed> { - dbg!(COMPRESS_COUNT.fetch_add(1, Ordering::SeqCst)); - rustls::compress::ZLIB_COMPRESSOR.compress(input, level) - } - - fn algorithm(&self) -> rustls::CertificateCompressionAlgorithm { - rustls::CertificateCompressionAlgorithm::Zlib - } - } -} - -#[test] -fn test_server_uses_uncompressed_certificate_if_compression_fails() { - let mut server_config = make_server_config(KeyType::Rsa2048); - server_config.cert_compressors = vec![&FailingCompressor]; - let mut client_config = make_client_config(KeyType::Rsa2048); - client_config.cert_decompressors = vec![&NeverDecompressor]; - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake(&mut client, &mut server); -} - -#[test] -fn test_client_uses_uncompressed_certificate_if_compression_fails() { - let mut server_config = make_server_config_with_mandatory_client_auth(KeyType::Rsa2048); - server_config.cert_decompressors = vec![&NeverDecompressor]; - let mut client_config = make_client_config_with_auth(KeyType::Rsa2048); - client_config.cert_compressors = vec![&FailingCompressor]; - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake(&mut client, &mut server); -} - -#[derive(Debug)] -struct FailingCompressor; - -impl rustls::compress::CertCompressor for FailingCompressor { - fn compress( - &self, - _input: Vec, - _level: rustls::compress::CompressionLevel, - ) -> Result, rustls::compress::CompressionFailed> { - println!("compress called but doesn't work"); - Err(rustls::compress::CompressionFailed) - } - - fn algorithm(&self) -> rustls::CertificateCompressionAlgorithm { - rustls::CertificateCompressionAlgorithm::Zlib - } -} - -#[derive(Debug)] -struct NeverDecompressor; - -impl rustls::compress::CertDecompressor for NeverDecompressor { - fn decompress( - &self, - _input: &[u8], - _output: &mut [u8], - ) -> Result<(), rustls::compress::DecompressionFailed> { - panic!("NeverDecompressor::decompress should not be called"); - } - - fn algorithm(&self) -> rustls::CertificateCompressionAlgorithm { - rustls::CertificateCompressionAlgorithm::Zlib - } -} - -#[cfg(feature = "zlib")] -#[test] -fn test_server_can_opt_out_of_compression_cache() { - static COMPRESS_COUNT: AtomicUsize = AtomicUsize::new(0); - - let mut server_config = make_server_config(KeyType::Rsa2048); - server_config.cert_compressors = vec![&AlwaysInteractiveCompressor]; - server_config.cert_compression_cache = Arc::new(rustls::compress::CompressionCache::Disabled); - let mut client_config = make_client_config(KeyType::Rsa2048); - client_config.resumption = Resumption::disabled(); - - let server_config = Arc::new(server_config); - let client_config = Arc::new(client_config); - - for _i in 0..10 { - dbg!(_i); - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - dbg!(client.handshake_kind()); - } - - assert_eq!(COMPRESS_COUNT.load(Ordering::SeqCst), 10); - - #[derive(Debug)] - struct AlwaysInteractiveCompressor; - - impl rustls::compress::CertCompressor for AlwaysInteractiveCompressor { - fn compress( - &self, - input: Vec, - level: rustls::compress::CompressionLevel, - ) -> Result, rustls::compress::CompressionFailed> { - dbg!(COMPRESS_COUNT.fetch_add(1, Ordering::SeqCst)); - assert_eq!(level, rustls::compress::CompressionLevel::Interactive); - rustls::compress::ZLIB_COMPRESSOR.compress(input, level) - } - - fn algorithm(&self) -> rustls::CertificateCompressionAlgorithm { - rustls::CertificateCompressionAlgorithm::Zlib - } - } -} - -#[test] -fn test_cert_decompression_by_client_produces_invalid_cert_payload() { - let mut server_config = make_server_config(KeyType::Rsa2048); - server_config.cert_compressors = vec![&IdentityCompressor]; - let mut client_config = make_client_config(KeyType::Rsa2048); - client_config.cert_decompressors = vec![&GarbageDecompressor]; - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - assert_eq!( - do_handshake_until_error(&mut client, &mut server), - Err(ErrorFromPeer::Client(Error::InvalidMessage( - InvalidMessage::CertificatePayloadTooLarge - ))) - ); - transfer(&mut client, &mut server); - assert_eq!( - server.process_new_packets(), - Err(Error::AlertReceived(AlertDescription::BadCertificate)) - ); -} - -#[test] -fn test_cert_decompression_by_server_produces_invalid_cert_payload() { - let mut server_config = make_server_config_with_mandatory_client_auth(KeyType::Rsa2048); - server_config.cert_decompressors = vec![&GarbageDecompressor]; - let mut client_config = make_client_config_with_auth(KeyType::Rsa2048); - client_config.cert_compressors = vec![&IdentityCompressor]; - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - assert_eq!( - do_handshake_until_error(&mut client, &mut server), - Err(ErrorFromPeer::Server(Error::InvalidMessage( - InvalidMessage::CertificatePayloadTooLarge - ))) - ); - transfer(&mut server, &mut client); - assert_eq!( - client.process_new_packets(), - Err(Error::AlertReceived(AlertDescription::BadCertificate)) - ); -} - -#[test] -fn test_cert_decompression_by_server_fails() { - let mut server_config = make_server_config_with_mandatory_client_auth(KeyType::Rsa2048); - server_config.cert_decompressors = vec![&FailingDecompressor]; - let mut client_config = make_client_config_with_auth(KeyType::Rsa2048); - client_config.cert_compressors = vec![&IdentityCompressor]; - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - assert_eq!( - do_handshake_until_error(&mut client, &mut server), - Err(ErrorFromPeer::Server(Error::PeerMisbehaved( - PeerMisbehaved::InvalidCertCompression - ))) - ); - transfer(&mut server, &mut client); - assert_eq!( - client.process_new_packets(), - Err(Error::AlertReceived(AlertDescription::BadCertificate)) - ); -} - -#[cfg(feature = "zlib")] -#[test] -fn test_cert_decompression_by_server_would_result_in_excessively_large_cert() { - let server_config = make_server_config_with_mandatory_client_auth(KeyType::Rsa2048); - let mut client_config = make_client_config_with_auth(KeyType::Rsa2048); - - let big_cert = CertificateDer::from(vec![0u8; 0xffff]); - let key = provider::default_provider() - .key_provider - .load_private_key(KeyType::Rsa2048.get_client_key()) - .unwrap(); - let big_cert_and_key = sign::CertifiedKey::new(vec![big_cert], key); - client_config.client_auth_cert_resolver = Arc::new(AlwaysResolves(big_cert_and_key.into())); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - assert_eq!( - do_handshake_until_error(&mut client, &mut server), - Err(ErrorFromPeer::Server(Error::InvalidMessage( - InvalidMessage::MessageTooLarge - ))) - ); - transfer(&mut server, &mut client); - assert_eq!( - client.process_new_packets(), - Err(Error::AlertReceived(AlertDescription::BadCertificate)) - ); - - #[derive(Debug)] - struct AlwaysResolves(Arc); - - impl ResolvesClientCert for AlwaysResolves { - fn resolve( - &self, - _root_hint_subjects: &[&[u8]], - _sigschemes: &[SignatureScheme], - ) -> Option> { - Some(self.0.clone()) - } - - fn has_certs(&self) -> bool { - true - } - } -} - -#[derive(Debug)] -struct GarbageDecompressor; - -impl rustls::compress::CertDecompressor for GarbageDecompressor { - fn decompress( - &self, - _input: &[u8], - output: &mut [u8], - ) -> Result<(), rustls::compress::DecompressionFailed> { - output.fill(0xff); - Ok(()) - } - - fn algorithm(&self) -> rustls::CertificateCompressionAlgorithm { - rustls::CertificateCompressionAlgorithm::Zlib - } -} - -#[derive(Debug)] -struct FailingDecompressor; - -impl rustls::compress::CertDecompressor for FailingDecompressor { - fn decompress( - &self, - _input: &[u8], - _output: &mut [u8], - ) -> Result<(), rustls::compress::DecompressionFailed> { - Err(rustls::compress::DecompressionFailed) - } - - fn algorithm(&self) -> rustls::CertificateCompressionAlgorithm { - rustls::CertificateCompressionAlgorithm::Zlib - } -} - -#[derive(Debug)] -struct IdentityCompressor; - -impl rustls::compress::CertCompressor for IdentityCompressor { - fn compress( - &self, - input: Vec, - _level: rustls::compress::CompressionLevel, - ) -> Result, rustls::compress::CompressionFailed> { - Ok(input.to_vec()) - } - - fn algorithm(&self) -> rustls::CertificateCompressionAlgorithm { - rustls::CertificateCompressionAlgorithm::Zlib - } -} - -struct FakeStream<'a>(&'a [u8]); - -impl io::Read for FakeStream<'_> { - fn read(&mut self, b: &mut [u8]) -> io::Result { - let take = core::cmp::min(b.len(), self.0.len()); - let (taken, remain) = self.0.split_at(take); - b[..take].copy_from_slice(taken); - self.0 = remain; - Ok(take) - } -} - -impl io::Write for FakeStream<'_> { - fn write(&mut self, b: &[u8]) -> io::Result { - Ok(b.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -#[test] -fn test_illegal_server_renegotiation_attempt_after_tls13_handshake() { - let client_config = - make_client_config_with_versions(KeyType::Rsa2048, &[&rustls::version::TLS13]); - let mut server_config = make_server_config(KeyType::Rsa2048); - server_config.enable_secret_extraction = true; - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake(&mut client, &mut server); - - let mut raw_server = RawTls::new_server(server); - - let msg = PlainMessage { - typ: ContentType::Handshake, - version: ProtocolVersion::TLSv1_3, - payload: Payload::new( - HandshakeMessagePayload { - typ: HandshakeType::HelloRequest, - payload: HandshakePayload::HelloRequest, - } - .get_encoding(), - ), - }; - raw_server.encrypt_and_send(&msg, &mut client); - let err = client - .process_new_packets() - .unwrap_err(); - assert_eq!( - err, - Error::InappropriateHandshakeMessage { - expect_types: vec![HandshakeType::NewSessionTicket, HandshakeType::KeyUpdate], - got_type: HandshakeType::HelloRequest - } - ); -} - -#[cfg(feature = "tls12")] -#[test] -fn test_illegal_server_renegotiation_attempt_after_tls12_handshake() { - let client_config = - make_client_config_with_versions(KeyType::Rsa2048, &[&rustls::version::TLS12]); - let mut server_config = make_server_config(KeyType::Rsa2048); - server_config.enable_secret_extraction = true; - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake(&mut client, &mut server); - - let mut raw_server = RawTls::new_server(server); - - let msg = PlainMessage { - typ: ContentType::Handshake, - version: ProtocolVersion::TLSv1_3, - payload: Payload::new( - HandshakeMessagePayload { - typ: HandshakeType::HelloRequest, - payload: HandshakePayload::HelloRequest, - } - .get_encoding(), - ), - }; - - // one is allowed (and elicits a warning alert) - raw_server.encrypt_and_send(&msg, &mut client); - client.process_new_packets().unwrap(); - raw_server.receive_and_decrypt(&mut client, |m| { - assert_eq!(format!("{m:?}"), - "Message { version: TLSv1_2, payload: Alert(AlertMessagePayload { level: Warning, description: NoRenegotiation }) }"); - }); - - // second is fatal - raw_server.encrypt_and_send(&msg, &mut client); - assert_eq!( - client - .process_new_packets() - .unwrap_err(), - Error::PeerMisbehaved(PeerMisbehaved::TooManyRenegotiationRequests) - ); -} - -#[test] -fn test_illegal_client_renegotiation_attempt_after_tls13_handshake() { - let mut client_config = - make_client_config_with_versions(KeyType::Rsa2048, &[&rustls::version::TLS13]); - client_config.enable_secret_extraction = true; - let server_config = make_server_config(KeyType::Rsa2048); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake(&mut client, &mut server); - - let mut raw_client = RawTls::new_client(client); - - let msg = PlainMessage { - typ: ContentType::Handshake, - version: ProtocolVersion::TLSv1_3, - payload: Payload::new( - HandshakeMessagePayload { - typ: HandshakeType::ClientHello, - payload: HandshakePayload::ClientHello(ClientHelloPayload { - client_version: ProtocolVersion::TLSv1_2, - random: Random::from([0u8; 32]), - session_id: SessionId::read_bytes(&[0u8]).unwrap(), - cipher_suites: vec![], - compression_methods: vec![Compression::Null], - extensions: vec![ClientExtension::ExtendedMasterSecretRequest], - }), - } - .get_encoding(), - ), - }; - raw_client.encrypt_and_send(&msg, &mut server); - let err = server - .process_new_packets() - .unwrap_err(); - assert_eq!( - format!("{err:?}"), - "InappropriateHandshakeMessage { expect_types: [KeyUpdate], got_type: ClientHello }" - ); -} - -#[cfg(feature = "tls12")] -#[test] -fn test_illegal_client_renegotiation_attempt_during_tls12_handshake() { - let server_config = make_server_config(KeyType::Rsa2048); - let client_config = - make_client_config_with_versions(KeyType::Rsa2048, &[&rustls::version::TLS12]); - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - - let mut client_hello = vec![]; - client - .write_tls(&mut io::Cursor::new(&mut client_hello)) - .unwrap(); - - server - .read_tls(&mut io::Cursor::new(&client_hello)) - .unwrap(); - server - .read_tls(&mut io::Cursor::new(&client_hello)) - .unwrap(); - assert_eq!( - server - .process_new_packets() - .unwrap_err(), - Error::InappropriateHandshakeMessage { - expect_types: vec![HandshakeType::ClientKeyExchange], - got_type: HandshakeType::ClientHello - } - ); -} - -#[test] -fn test_refresh_traffic_keys_during_handshake() { - let (mut client, mut server) = make_pair(KeyType::Ed25519); - assert_eq!( - client - .refresh_traffic_keys() - .unwrap_err(), - Error::HandshakeNotComplete - ); - assert_eq!( - server - .refresh_traffic_keys() - .unwrap_err(), - Error::HandshakeNotComplete - ); -} - -#[test] -fn test_refresh_traffic_keys() { - let (mut client, mut server) = make_pair(KeyType::Ed25519); - do_handshake(&mut client, &mut server); - - fn check_both_directions(client: &mut ClientConnection, server: &mut ServerConnection) { - client - .writer() - .write_all(b"to-server-1") - .unwrap(); - server - .writer() - .write_all(b"to-client-1") - .unwrap(); - transfer(client, server); - server.process_new_packets().unwrap(); - - transfer(server, client); - client.process_new_packets().unwrap(); - - let mut buf = [0u8; 16]; - let len = server.reader().read(&mut buf).unwrap(); - assert_eq!(&buf[..len], b"to-server-1"); - - let len = client.reader().read(&mut buf).unwrap(); - assert_eq!(&buf[..len], b"to-client-1"); - } - - check_both_directions(&mut client, &mut server); - client.refresh_traffic_keys().unwrap(); - check_both_directions(&mut client, &mut server); - server.refresh_traffic_keys().unwrap(); - check_both_directions(&mut client, &mut server); -} - -#[test] -fn test_automatic_refresh_traffic_keys() { - const fn encrypted_size(body: usize) -> usize { - let padding = 1; - let header = 5; - let tag = 16; - header + body + padding + tag - } - - const KEY_UPDATE_SIZE: usize = encrypted_size(5); - let provider = aes_128_gcm_with_1024_confidentiality_limit(); - - let client_config = finish_client_config( - KeyType::Ed25519, - ClientConfig::builder_with_provider(provider.clone()) - .with_safe_default_protocol_versions() - .unwrap(), - ); - let server_config = finish_server_config( - KeyType::Ed25519, - ServerConfig::builder_with_provider(provider) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake(&mut client, &mut server); - - for i in 0..(CONFIDENTIALITY_LIMIT + 16) { - let message = format!("{i:08}"); - client - .writer() - .write_all(message.as_bytes()) - .unwrap(); - let transferred = transfer(&mut client, &mut server); - println!( - "{}: {} -> {:?}", - i, - transferred, - server.process_new_packets().unwrap() - ); - - // at CONFIDENTIALITY_LIMIT messages, we also have a key_update message sent - assert_eq!( - transferred, - match i { - CONFIDENTIALITY_LIMIT => KEY_UPDATE_SIZE + encrypted_size(message.len()), - _ => encrypted_size(message.len()), - } - ); - - let mut buf = [0u8; 32]; - let recvd = server.reader().read(&mut buf).unwrap(); - assert_eq!(&buf[..recvd], message.as_bytes()); - } - - // finally, server writes and pumps its key_update response - let message = b"finished"; - server - .writer() - .write_all(message) - .unwrap(); - let transferred = transfer(&mut server, &mut client); - - println!( - "F: {} -> {:?}", - transferred, - client.process_new_packets().unwrap() - ); - assert_eq!(transferred, KEY_UPDATE_SIZE + encrypted_size(message.len())); -} - -#[cfg(feature = "tls12")] -#[test] -fn tls12_connection_fails_after_key_reaches_confidentiality_limit() { - let provider = aes_128_gcm_with_1024_confidentiality_limit(); - - let client_config = finish_client_config( - KeyType::Ed25519, - ClientConfig::builder_with_provider(provider.clone()) - .with_protocol_versions(&[&rustls::version::TLS12]) - .unwrap(), - ); - let server_config = finish_server_config( - KeyType::Ed25519, - ServerConfig::builder_with_provider(provider) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake(&mut client, &mut server); - - for i in 0..CONFIDENTIALITY_LIMIT { - let message = format!("{i:08}"); - client - .writer() - .write_all(message.as_bytes()) - .unwrap(); - let transferred = transfer(&mut client, &mut server); - println!( - "{}: {} -> {:?}", - i, - transferred, - server.process_new_packets().unwrap() - ); - - let mut buf = [0u8; 32]; - let recvd = server.reader().read(&mut buf).unwrap(); - - match i { - 1023 => assert_eq!(recvd, 0), - _ => assert_eq!(&buf[..recvd], message.as_bytes()), - } - } -} - -#[test] -fn test_keys_match_for_all_signing_key_types() { - for kt in ALL_KEY_TYPES { - let key = provider::default_provider() - .key_provider - .load_private_key(kt.get_client_key()) - .unwrap(); - let ck = sign::CertifiedKey::new(kt.get_client_chain(), key); - ck.keys_match().unwrap(); - println!("{kt:?} ok"); - } -} - -#[test] -fn tls13_packed_handshake() { - // transcript requires selection of X25519 - if provider_is_fips() { - return; - } - - // regression test for https://github.com/rustls/rustls/issues/2040 - // (did not affect the buffered api) - let client_config = ClientConfig::builder_with_provider(unsafe_plaintext_crypto_provider()) - .with_safe_default_protocol_versions() - .unwrap() - .dangerous() - .with_custom_certificate_verifier(Arc::new(MockServerVerifier::rejects_certificate( - CertificateError::UnknownIssuer.into(), - ))) - .with_no_client_auth(); - - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - - let mut hello = Vec::new(); - client - .write_tls(&mut io::Cursor::new(&mut hello)) - .unwrap(); - - let first_flight = include_bytes!("data/bug2040-message-1.bin"); - client - .read_tls(&mut io::Cursor::new(first_flight)) - .unwrap(); - client.process_new_packets().unwrap(); - - let second_flight = include_bytes!("data/bug2040-message-2.bin"); - client - .read_tls(&mut io::Cursor::new(second_flight)) - .unwrap(); - assert_eq!( - client - .process_new_packets() - .unwrap_err(), - Error::InvalidCertificate(CertificateError::UnknownIssuer), - ); -} - -#[test] -fn large_client_hello() { - let (_, mut server) = make_pair(KeyType::Rsa2048); - let hello = include_bytes!("data/bug2227-clienthello.bin"); - let mut cursor = io::Cursor::new(hello); - loop { - if server.read_tls(&mut cursor).unwrap() == 0 { - break; - } - server.process_new_packets().unwrap(); - } -} - -#[test] -fn large_client_hello_acceptor() { - let mut acceptor = rustls::server::Acceptor::default(); - let hello = include_bytes!("data/bug2227-clienthello.bin"); - let mut cursor = io::Cursor::new(hello); - loop { - acceptor.read_tls(&mut cursor).unwrap(); - - if let Some(accepted) = acceptor.accept().unwrap() { - println!("{accepted:?}"); - break; - } - } -} - -#[test] -fn hybrid_kx_component_share_offered_if_supported_seperately() { - let kt = KeyType::Rsa2048; - let client_config = finish_client_config( - kt, - ClientConfig::builder_with_provider( - CryptoProvider { - kx_groups: vec![&FakeHybrid, provider::kx_group::SECP384R1], - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ); - let server_config = make_server_config(kt); - - let (client, server) = make_pair_for_configs(client_config, server_config); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered( - &mut client, - assert_client_sends_hello_with_two_key_shares, - &mut server, - ); -} - -#[test] -fn hybrid_kx_component_share_not_offered_unless_supported_seperately() { - let kt = KeyType::Rsa2048; - let client_config = finish_client_config( - kt, - ClientConfig::builder_with_provider( - CryptoProvider { - kx_groups: vec![&FakeHybrid], - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ); - let server_config = make_server_config(kt); - - let (client, server) = make_pair_for_configs(client_config, server_config); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered( - &mut client, - assert_client_sends_hello_with_one_hybrid_key_share, - &mut server, - ); -} - -#[test] -fn hybrid_kx_component_share_offered_but_server_chooses_something_else() { - let kt = KeyType::Rsa2048; - let client_config = finish_client_config( - kt, - ClientConfig::builder_with_provider( - CryptoProvider { - kx_groups: vec![&FakeHybrid, provider::kx_group::SECP384R1], - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ); - let server_config = make_server_config(kt); - - let (mut client_1, mut server) = make_pair_for_configs(client_config, server_config); - let (mut client_2, _) = make_pair(kt); - - // client_2 supplies the ClientHello, client_1 receives the ServerHello - transfer(&mut client_2, &mut server); - server.process_new_packets().unwrap(); - transfer(&mut server, &mut client_1); - assert_eq!( - client_1 - .process_new_packets() - .unwrap_err(), - PeerMisbehaved::WrongGroupForKeyShare.into() - ); -} - -#[derive(Debug)] -struct FakeHybrid; - -impl SupportedKxGroup for FakeHybrid { - fn start(&self) -> Result, Error> { - Ok(Box::new(FakeHybridActive)) - } - - fn name(&self) -> NamedGroup { - NamedGroup::from(0x1234) - } -} - -struct FakeHybridActive; - -impl ActiveKeyExchange for FakeHybridActive { - fn complete(self: Box, _peer_pub_key: &[u8]) -> Result { - Err(PeerMisbehaved::InvalidKeyShare.into()) - } - - fn hybrid_component(&self) -> Option<(NamedGroup, &[u8])> { - Some((provider::kx_group::SECP384R1.name(), b"classical")) - } - - fn pub_key(&self) -> &[u8] { - b"hybrid" - } - - fn group(&self) -> NamedGroup { - FakeHybrid.name() - } -} - -fn assert_client_sends_hello_with_two_key_shares(msg: &mut Message) -> Altered { - match &mut msg.payload { - MessagePayload::Handshake { parsed, .. } => match &mut parsed.payload { - HandshakePayload::ClientHello(ch) => { - let keyshares = ch - .keyshare_extension() - .expect("missing key share extension"); - assert_eq!(keyshares.len(), 2); - assert_eq!(keyshares[0].group(), FakeHybrid.name()); - assert_eq!(keyshares[0].get_encoding(), b"\x12\x34\x00\x06hybrid"); - assert_eq!(keyshares[1].group(), NamedGroup::secp384r1); - assert_eq!(keyshares[1].get_encoding(), b"\x00\x18\x00\x09classical"); - } - _ => panic!("unexpected handshake message {parsed:?}"), - }, - _ => panic!("unexpected non-handshake message {msg:?}"), - }; - Altered::InPlace -} - -fn assert_client_sends_hello_with_one_hybrid_key_share(msg: &mut Message) -> Altered { - match &mut msg.payload { - MessagePayload::Handshake { parsed, .. } => match &mut parsed.payload { - HandshakePayload::ClientHello(ch) => { - let keyshares = ch - .keyshare_extension() - .expect("missing key share extension"); - assert_eq!(keyshares.len(), 1); - assert_eq!(keyshares[0].group(), FakeHybrid.name()); - assert_eq!(keyshares[0].get_encoding(), b"\x12\x34\x00\x06hybrid"); - } - _ => panic!("unexpected handshake message {parsed:?}"), - }, - _ => panic!("unexpected non-handshake message {msg:?}"), - }; - Altered::InPlace -} - -const CONFIDENTIALITY_LIMIT: u64 = 1024; diff --git a/rustls/tests/api_ffdhe.rs b/rustls/tests/api_ffdhe.rs deleted file mode 100644 index ddfcbb75793..00000000000 --- a/rustls/tests/api_ffdhe.rs +++ /dev/null @@ -1,484 +0,0 @@ -//! This file contains tests that use the test-only FFDHE KX group (defined in submodule `ffdhe`) - -#![allow(clippy::duplicate_mod)] - -mod common; -use common::*; -use rustls::crypto::CryptoProvider; -use rustls::internal::msgs::base::Payload; -use rustls::internal::msgs::codec::Codec; -use rustls::internal::msgs::handshake::{ClientExtension, HandshakePayload}; -use rustls::internal::msgs::message::{Message, MessagePayload}; -use rustls::version::{TLS12, TLS13}; -use rustls::{CipherSuite, ClientConfig, NamedGroup}; - -use super::*; - -#[test] -fn config_builder_for_client_rejects_cipher_suites_without_compatible_kx_groups() { - let bad_crypto_provider = CryptoProvider { - kx_groups: vec![&ffdhe::FFDHE2048_KX_GROUP], - cipher_suites: vec![ - provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - ], - ..provider::default_provider() - }; - - let build_err = ClientConfig::builder_with_provider(bad_crypto_provider.into()) - .with_safe_default_protocol_versions() - .unwrap_err() - .to_string(); - - // Current expected error: - // Ciphersuite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 requires [ECDHE] key exchange, but no \ - // [ECDHE]-compatible key exchange groups were present in `CryptoProvider`'s `kx_groups` field - assert!(build_err.contains("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")); - assert!(build_err.contains("ECDHE")); - assert!(build_err.contains("key exchange")); -} - -#[test] -fn ffdhe_ciphersuite() { - use provider::cipher_suite; - use rustls::version::{TLS12, TLS13}; - - let test_cases = [ - (&TLS12, ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256), - (&TLS13, cipher_suite::TLS13_CHACHA20_POLY1305_SHA256), - ]; - - for (expected_protocol, expected_cipher_suite) in test_cases { - let client_config = finish_client_config( - KeyType::Rsa2048, - rustls::ClientConfig::builder_with_provider(ffdhe::ffdhe_provider().into()) - .with_protocol_versions(&[expected_protocol]) - .unwrap(), - ); - let server_config = finish_server_config( - KeyType::Rsa2048, - rustls::ServerConfig::builder_with_provider(ffdhe::ffdhe_provider().into()) - .with_safe_default_protocol_versions() - .unwrap(), - ); - do_suite_and_kx_test( - client_config, - server_config, - expected_cipher_suite, - NamedGroup::FFDHE2048, - expected_protocol.version, - ); - } -} - -#[test] -fn server_picks_ffdhe_group_when_clienthello_has_no_ffdhe_group_in_groups_ext() { - fn clear_named_groups_ext(msg: &mut Message) -> Altered { - if let MessagePayload::Handshake { parsed, encoded } = &mut msg.payload { - if let HandshakePayload::ClientHello(ch) = &mut parsed.payload { - for mut ext in ch.extensions.iter_mut() { - if let ClientExtension::NamedGroups(ngs) = &mut ext { - ngs.clear(); - } - } - } - *encoded = Payload::new(parsed.get_encoding()); - } - Altered::InPlace - } - - let client_config = finish_client_config( - KeyType::Rsa2048, - rustls::ClientConfig::builder_with_provider(ffdhe::ffdhe_provider().into()) - .with_protocol_versions(&[&rustls::version::TLS12]) - .unwrap(), - ); - let server_config = finish_server_config( - KeyType::Rsa2048, - rustls::ServerConfig::builder_with_provider(ffdhe::ffdhe_provider().into()) - .with_protocol_versions(&[&rustls::version::TLS12]) - .unwrap(), - ); - - let (client, server) = make_pair_for_configs(client_config, server_config); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered(&mut client, clear_named_groups_ext, &mut server); - assert!(server.process_new_packets().is_ok()); -} - -#[test] -fn server_picks_ffdhe_group_when_clienthello_has_no_groups_ext() { - fn remove_named_groups_ext(msg: &mut Message) -> Altered { - if let MessagePayload::Handshake { parsed, encoded } = &mut msg.payload { - if let HandshakePayload::ClientHello(ch) = &mut parsed.payload { - ch.extensions - .retain(|ext| !matches!(ext, ClientExtension::NamedGroups(_))); - } - *encoded = Payload::new(parsed.get_encoding()); - } - Altered::InPlace - } - - let client_config = finish_client_config( - KeyType::Rsa2048, - rustls::ClientConfig::builder_with_provider(ffdhe::ffdhe_provider().into()) - .with_protocol_versions(&[&rustls::version::TLS12]) - .unwrap(), - ); - let server_config = finish_server_config( - KeyType::Rsa2048, - rustls::ServerConfig::builder_with_provider(ffdhe::ffdhe_provider().into()) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - let (client, server) = make_pair_for_configs(client_config, server_config); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered(&mut client, remove_named_groups_ext, &mut server); - assert!(server.process_new_packets().is_ok()); -} - -#[test] -fn server_avoids_dhe_cipher_suites_when_client_has_no_known_dhe_in_groups_ext() { - use rustls::{CipherSuite, NamedGroup}; - - let client_config = finish_client_config( - KeyType::Rsa2048, - rustls::ClientConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![ - ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - ], - kx_groups: vec![&ffdhe::FFDHE4096_KX_GROUP, provider::kx_group::SECP256R1], - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - let server_config = finish_server_config( - KeyType::Rsa2048, - rustls::ServerConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![ - ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - ], - kx_groups: vec![&ffdhe::FFDHE2048_KX_GROUP, provider::kx_group::SECP256R1], - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - do_handshake(&mut client, &mut server); - assert_eq!( - server - .negotiated_cipher_suite() - .unwrap() - .suite(), - CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - ); - assert_eq!( - server - .negotiated_key_exchange_group() - .unwrap() - .name(), - NamedGroup::secp256r1, - ) -} - -#[test] -fn server_accepts_client_with_no_ecpoints_extension_and_only_ffdhe_cipher_suites() { - fn remove_ecpoints_ext(msg: &mut Message) -> Altered { - if let MessagePayload::Handshake { parsed, encoded } = &mut msg.payload { - if let HandshakePayload::ClientHello(ch) = &mut parsed.payload { - ch.extensions - .retain(|ext| !matches!(ext, ClientExtension::EcPointFormats(_))); - } - *encoded = Payload::new(parsed.get_encoding()); - } - Altered::InPlace - } - - let client_config = finish_client_config( - KeyType::Rsa2048, - rustls::ClientConfig::builder_with_provider(ffdhe::ffdhe_provider().into()) - .with_protocol_versions(&[&rustls::version::TLS12]) - .unwrap(), - ); - let server_config = finish_server_config( - KeyType::Rsa2048, - rustls::ServerConfig::builder_with_provider(ffdhe::ffdhe_provider().into()) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - let (client, server) = make_pair_for_configs(client_config, server_config); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered(&mut client, remove_ecpoints_ext, &mut server); - assert!(server.process_new_packets().is_ok()); -} - -#[test] -fn server_avoids_cipher_suite_with_no_common_kx_groups() { - let server_config = finish_server_config( - KeyType::Rsa2048, - rustls::ServerConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![ - provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - provider::cipher_suite::TLS13_AES_128_GCM_SHA256, - ], - kx_groups: vec![provider::kx_group::SECP256R1, &ffdhe::FFDHE2048_KX_GROUP], - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ) - .into(); - - let test_cases = [ - ( - // TLS 1.2, have common - vec![ - // this matches: - provider::kx_group::SECP256R1, - &ffdhe::FFDHE2048_KX_GROUP, - ], - &TLS12, - CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - Some(NamedGroup::secp256r1), - ), - ( - vec![ - // this matches: - provider::kx_group::SECP256R1, - &ffdhe::FFDHE3072_KX_GROUP, - ], - &TLS12, - CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - Some(NamedGroup::secp256r1), - ), - ( - vec![ - provider::kx_group::SECP384R1, - // this matches: - &ffdhe::FFDHE2048_KX_GROUP, - ], - &TLS12, - CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - Some(NamedGroup::FFDHE2048), - ), - ( - // TLS 1.3, have common - vec![ - // this matches: - provider::kx_group::SECP256R1, - &ffdhe::FFDHE2048_KX_GROUP, - ], - &TLS13, - CipherSuite::TLS13_AES_128_GCM_SHA256, - Some(NamedGroup::secp256r1), - ), - ( - vec![ - // this matches: - provider::kx_group::SECP256R1, - &ffdhe::FFDHE3072_KX_GROUP, - ], - &TLS13, - CipherSuite::TLS13_AES_128_GCM_SHA256, - Some(NamedGroup::secp256r1), - ), - ( - vec![ - provider::kx_group::SECP384R1, - // this matches: - &ffdhe::FFDHE2048_KX_GROUP, - ], - &TLS13, - CipherSuite::TLS13_AES_128_GCM_SHA256, - Some(NamedGroup::FFDHE2048), - ), - ]; - - for (client_kx_groups, protocol_version, expected_cipher_suite, expected_group) in test_cases { - let client_config = finish_client_config( - KeyType::Rsa2048, - rustls::ClientConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![ - provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - provider::cipher_suite::TLS13_AES_128_GCM_SHA256, - ], - kx_groups: client_kx_groups, - ..provider::default_provider() - } - .into(), - ) - .with_protocol_versions(&[protocol_version]) - .unwrap(), - ) - .into(); - - let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); - do_handshake(&mut client, &mut server); - assert_eq!( - server - .negotiated_cipher_suite() - .unwrap() - .suite(), - expected_cipher_suite - ); - assert_eq!(server.protocol_version(), Some(protocol_version.version)); - assert_eq!( - server - .negotiated_key_exchange_group() - .map(|kx| kx.name()), - expected_group, - ); - } -} - -#[test] -fn non_ffdhe_kx_does_not_have_ffdhe_group() { - let non_ffdhe = provider::kx_group::SECP256R1; - assert_eq!(non_ffdhe.ffdhe_group(), None); - let active = non_ffdhe.start().unwrap(); - assert_eq!(active.ffdhe_group(), None); -} - -mod ffdhe { - use num_bigint::BigUint; - use rustls::crypto::{ - ActiveKeyExchange, CipherSuiteCommon, CryptoProvider, KeyExchangeAlgorithm, SharedSecret, - SupportedKxGroup, - }; - use rustls::ffdhe_groups::FfdheGroup; - use rustls::{ffdhe_groups, CipherSuite, NamedGroup, SupportedCipherSuite, Tls12CipherSuite}; - - use super::provider; - - /// A test-only `CryptoProvider`, only supporting FFDHE key exchange - pub fn ffdhe_provider() -> CryptoProvider { - CryptoProvider { - cipher_suites: FFDHE_CIPHER_SUITES.to_vec(), - kx_groups: FFDHE_KX_GROUPS.to_vec(), - ..provider::default_provider() - } - } - - static FFDHE_KX_GROUPS: &[&dyn SupportedKxGroup] = &[&FFDHE2048_KX_GROUP, &FFDHE3072_KX_GROUP]; - - pub const FFDHE2048_KX_GROUP: FfdheKxGroup = - FfdheKxGroup(NamedGroup::FFDHE2048, ffdhe_groups::FFDHE2048); - pub const FFDHE3072_KX_GROUP: FfdheKxGroup = - FfdheKxGroup(NamedGroup::FFDHE3072, ffdhe_groups::FFDHE3072); - pub const FFDHE4096_KX_GROUP: FfdheKxGroup = - FfdheKxGroup(NamedGroup::FFDHE4096, ffdhe_groups::FFDHE4096); - - static FFDHE_CIPHER_SUITES: &[rustls::SupportedCipherSuite] = &[ - TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - provider::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, - ]; - - /// The (test-only) TLS1.2 ciphersuite TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 - pub static TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&TLS12_DHE_RSA_WITH_AES_128_GCM_SHA256); - - static TLS12_DHE_RSA_WITH_AES_128_GCM_SHA256: Tls12CipherSuite = - match &provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 { - SupportedCipherSuite::Tls12(provider) => Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - ..provider.common - }, - kx: KeyExchangeAlgorithm::DHE, - ..**provider - }, - _ => unreachable!(), - }; - - #[derive(Debug)] - pub struct FfdheKxGroup(pub NamedGroup, pub FfdheGroup<'static>); - - impl SupportedKxGroup for FfdheKxGroup { - fn start(&self) -> Result, rustls::Error> { - let mut x = vec![0; 64]; - ffdhe_provider() - .secure_random - .fill(&mut x)?; - let x = BigUint::from_bytes_be(&x); - - let p = BigUint::from_bytes_be(self.1.p); - let g = BigUint::from_bytes_be(self.1.g); - - let x_pub = g.modpow(&x, &p); - let x_pub = to_bytes_be_with_len(x_pub, self.1.p.len()); - - Ok(Box::new(ActiveFfdheKx { - x_pub, - x, - p, - group: self.1, - named_group: self.0, - })) - } - - fn ffdhe_group(&self) -> Option> { - Some(self.1) - } - - fn name(&self) -> NamedGroup { - self.0 - } - } - - struct ActiveFfdheKx { - x_pub: Vec, - x: BigUint, - p: BigUint, - group: FfdheGroup<'static>, - named_group: NamedGroup, - } - - impl ActiveKeyExchange for ActiveFfdheKx { - fn complete(self: Box, peer_pub_key: &[u8]) -> Result { - let peer_pub = BigUint::from_bytes_be(peer_pub_key); - let secret = peer_pub.modpow(&self.x, &self.p); - let secret = to_bytes_be_with_len(secret, self.group.p.len()); - - Ok(SharedSecret::from(&secret[..])) - } - - fn pub_key(&self) -> &[u8] { - &self.x_pub - } - - fn ffdhe_group(&self) -> Option> { - Some(self.group) - } - - fn group(&self) -> NamedGroup { - self.named_group - } - } - - fn to_bytes_be_with_len(n: BigUint, len_bytes: usize) -> Vec { - let mut bytes = n.to_bytes_le(); - bytes.resize(len_bytes, 0); - bytes.reverse(); - bytes - } -} diff --git a/rustls/tests/client_cert_verifier.rs b/rustls/tests/client_cert_verifier.rs deleted file mode 100644 index 5f001a64331..00000000000 --- a/rustls/tests/client_cert_verifier.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! Tests for configuring and using a [`ClientCertVerifier`] for a server. - -#![allow(clippy::duplicate_mod)] - -use super::*; - -mod common; -use std::sync::Arc; - -use common::{ - do_handshake_until_both_error, do_handshake_until_error, make_client_config_with_versions, - make_client_config_with_versions_with_auth, make_pair_for_arc_configs, server_config_builder, - server_name, ErrorFromPeer, KeyType, MockClientVerifier, ALL_KEY_TYPES, -}; -use rustls::server::danger::ClientCertVerified; -use rustls::{ - AlertDescription, ClientConnection, Error, InvalidMessage, ServerConfig, ServerConnection, -}; - -// Client is authorized! -fn ver_ok() -> Result { - Ok(ClientCertVerified::assertion()) -} - -// Use when we shouldn't even attempt verification -fn ver_unreachable() -> Result { - unreachable!() -} - -// Verifier that returns an error that we can expect -fn ver_err() -> Result { - Err(Error::General("test err".to_string())) -} - -fn server_config_with_verifier( - kt: KeyType, - client_cert_verifier: MockClientVerifier, -) -> ServerConfig { - server_config_builder() - .with_client_cert_verifier(Arc::new(client_cert_verifier)) - .with_single_cert(kt.get_chain(), kt.get_key()) - .unwrap() -} - -#[test] -// Happy path, we resolve to a root, it is verified OK, should be able to connect -fn client_verifier_works() { - for kt in ALL_KEY_TYPES.iter() { - let client_verifier = MockClientVerifier::new(ver_ok, *kt); - let server_config = server_config_with_verifier(*kt, client_verifier); - let server_config = Arc::new(server_config); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config.clone()), &server_config); - let err = do_handshake_until_error(&mut client, &mut server); - assert_eq!(err, Ok(())); - } - } -} - -// Server offers no verification schemes -#[test] -fn client_verifier_no_schemes() { - for kt in ALL_KEY_TYPES.iter() { - let mut client_verifier = MockClientVerifier::new(ver_ok, *kt); - client_verifier.offered_schemes = Some(vec![]); - let server_config = server_config_with_verifier(*kt, client_verifier); - let server_config = Arc::new(server_config); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config.clone()), &server_config); - let err = do_handshake_until_error(&mut client, &mut server); - assert_eq!( - err, - Err(ErrorFromPeer::Client(Error::InvalidMessage( - InvalidMessage::NoSignatureSchemes, - ))), - ); - } - } -} - -// If we do have a root, we must do auth -#[test] -fn client_verifier_no_auth_yes_root() { - for kt in ALL_KEY_TYPES.iter() { - let client_verifier = MockClientVerifier::new(ver_unreachable, *kt); - let server_config = server_config_with_verifier(*kt, client_verifier); - let server_config = Arc::new(server_config); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions(*kt, &[version]); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let errs = do_handshake_until_both_error(&mut client, &mut server); - assert_eq!( - errs, - Err(vec![ - ErrorFromPeer::Server(Error::NoCertificatesPresented), - ErrorFromPeer::Client(Error::AlertReceived( - AlertDescription::CertificateRequired - )) - ]) - ); - } - } -} - -#[test] -// Triple checks we propagate the rustls::Error through -fn client_verifier_fails_properly() { - for kt in ALL_KEY_TYPES.iter() { - let client_verifier = MockClientVerifier::new(ver_err, *kt); - let server_config = server_config_with_verifier(*kt, client_verifier); - let server_config = Arc::new(server_config); - - for version in rustls::ALL_VERSIONS { - let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); - let mut client = - ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let err = do_handshake_until_error(&mut client, &mut server); - assert_eq!( - err, - Err(ErrorFromPeer::Server(Error::General("test err".into()))) - ); - } - } -} diff --git a/rustls/tests/common/mod.rs b/rustls/tests/common/mod.rs deleted file mode 100644 index 21cf4e53524..00000000000 --- a/rustls/tests/common/mod.rs +++ /dev/null @@ -1,1466 +0,0 @@ -#![allow(dead_code)] -#![allow(clippy::duplicate_mod)] - -use std::io; -use std::ops::DerefMut; -use std::sync::{Arc, OnceLock}; - -use pki_types::pem::PemObject; -use pki_types::{ - CertificateDer, CertificateRevocationListDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName, - SubjectPublicKeyInfoDer, UnixTime, -}; -use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; -use rustls::client::{ - AlwaysResolvesClientRawPublicKeys, ServerCertVerifierBuilder, WebPkiServerVerifier, -}; -use rustls::crypto::cipher::{InboundOpaqueMessage, MessageDecrypter, MessageEncrypter}; -use rustls::crypto::{verify_tls13_signature_with_raw_key, CryptoProvider}; -use rustls::internal::msgs::codec::{Codec, Reader}; -use rustls::internal::msgs::message::{Message, OutboundOpaqueMessage, PlainMessage}; -use rustls::server::danger::{ClientCertVerified, ClientCertVerifier}; -use rustls::server::{ - AlwaysResolvesServerRawPublicKeys, ClientCertVerifierBuilder, WebPkiClientVerifier, -}; -use rustls::sign::CertifiedKey; -use rustls::{ - ClientConfig, ClientConnection, Connection, ConnectionCommon, ContentType, - DigitallySignedStruct, DistinguishedName, Error, InconsistentKeys, NamedGroup, ProtocolVersion, - RootCertStore, ServerConfig, ServerConnection, SideData, SignatureScheme, SupportedCipherSuite, -}; -use webpki::anchor_from_trusted_cert; - -use super::provider; - -macro_rules! embed_files { - ( - $( - ($name:ident, $keytype:expr, $path:expr); - )+ - ) => { - $( - const $name: &'static [u8] = include_bytes!( - concat!("../../../test-ca/", $keytype, "/", $path)); - )+ - - pub fn bytes_for(keytype: &str, path: &str) -> &'static [u8] { - match (keytype, path) { - $( - ($keytype, $path) => $name, - )+ - _ => panic!("unknown keytype {} with path {}", keytype, path), - } - } - } -} - -embed_files! { - (ECDSA_P256_END_PEM_SPKI, "ecdsa-p256", "end.spki.pem"); - (ECDSA_P256_CLIENT_PEM_SPKI, "ecdsa-p256", "client.spki.pem"); - (ECDSA_P256_CA_CERT, "ecdsa-p256", "ca.cert"); - (ECDSA_P256_CA_DER, "ecdsa-p256", "ca.der"); - (ECDSA_P256_CA_KEY, "ecdsa-p256", "ca.key"); - (ECDSA_P256_CLIENT_CERT, "ecdsa-p256", "client.cert"); - (ECDSA_P256_CLIENT_CHAIN, "ecdsa-p256", "client.chain"); - (ECDSA_P256_CLIENT_FULLCHAIN, "ecdsa-p256", "client.fullchain"); - (ECDSA_P256_CLIENT_KEY, "ecdsa-p256", "client.key"); - (ECDSA_P256_END_CRL_PEM, "ecdsa-p256", "end.revoked.crl.pem"); - (ECDSA_P256_CLIENT_CRL_PEM, "ecdsa-p256", "client.revoked.crl.pem"); - (ECDSA_P256_INTERMEDIATE_CRL_PEM, "ecdsa-p256", "inter.revoked.crl.pem"); - (ECDSA_P256_EXPIRED_CRL_PEM, "ecdsa-p256", "end.expired.crl.pem"); - (ECDSA_P256_END_CERT, "ecdsa-p256", "end.cert"); - (ECDSA_P256_END_CHAIN, "ecdsa-p256", "end.chain"); - (ECDSA_P256_END_FULLCHAIN, "ecdsa-p256", "end.fullchain"); - (ECDSA_P256_END_KEY, "ecdsa-p256", "end.key"); - (ECDSA_P256_INTER_CERT, "ecdsa-p256", "inter.cert"); - (ECDSA_P256_INTER_KEY, "ecdsa-p256", "inter.key"); - - (ECDSA_P384_END_PEM_SPKI, "ecdsa-p384", "end.spki.pem"); - (ECDSA_P384_CLIENT_PEM_SPKI, "ecdsa-p384", "client.spki.pem"); - (ECDSA_P384_CA_CERT, "ecdsa-p384", "ca.cert"); - (ECDSA_P384_CA_DER, "ecdsa-p384", "ca.der"); - (ECDSA_P384_CA_KEY, "ecdsa-p384", "ca.key"); - (ECDSA_P384_CLIENT_CERT, "ecdsa-p384", "client.cert"); - (ECDSA_P384_CLIENT_CHAIN, "ecdsa-p384", "client.chain"); - (ECDSA_P384_CLIENT_FULLCHAIN, "ecdsa-p384", "client.fullchain"); - (ECDSA_P384_CLIENT_KEY, "ecdsa-p384", "client.key"); - (ECDSA_P384_END_CRL_PEM, "ecdsa-p384", "end.revoked.crl.pem"); - (ECDSA_P384_CLIENT_CRL_PEM, "ecdsa-p384", "client.revoked.crl.pem"); - (ECDSA_P384_INTERMEDIATE_CRL_PEM, "ecdsa-p384", "inter.revoked.crl.pem"); - (ECDSA_P384_EXPIRED_CRL_PEM, "ecdsa-p384", "end.expired.crl.pem"); - (ECDSA_P384_END_CERT, "ecdsa-p384", "end.cert"); - (ECDSA_P384_END_CHAIN, "ecdsa-p384", "end.chain"); - (ECDSA_P384_END_FULLCHAIN, "ecdsa-p384", "end.fullchain"); - (ECDSA_P384_END_KEY, "ecdsa-p384", "end.key"); - (ECDSA_P384_INTER_CERT, "ecdsa-p384", "inter.cert"); - (ECDSA_P384_INTER_KEY, "ecdsa-p384", "inter.key"); - - (ECDSA_P521_END_PEM_SPKI, "ecdsa-p521", "end.spki.pem"); - (ECDSA_P521_CLIENT_PEM_SPKI, "ecdsa-p521", "client.spki.pem"); - (ECDSA_P521_CA_CERT, "ecdsa-p521", "ca.cert"); - (ECDSA_P521_CA_DER, "ecdsa-p521", "ca.der"); - (ECDSA_P521_CA_KEY, "ecdsa-p521", "ca.key"); - (ECDSA_P521_CLIENT_CERT, "ecdsa-p521", "client.cert"); - (ECDSA_P521_CLIENT_CHAIN, "ecdsa-p521", "client.chain"); - (ECDSA_P521_CLIENT_FULLCHAIN, "ecdsa-p521", "client.fullchain"); - (ECDSA_P521_CLIENT_KEY, "ecdsa-p521", "client.key"); - (ECDSA_P521_END_CRL_PEM, "ecdsa-p521", "end.revoked.crl.pem"); - (ECDSA_P521_CLIENT_CRL_PEM, "ecdsa-p521", "client.revoked.crl.pem"); - (ECDSA_P521_INTERMEDIATE_CRL_PEM, "ecdsa-p521", "inter.revoked.crl.pem"); - (ECDSA_P521_EXPIRED_CRL_PEM, "ecdsa-p521", "end.expired.crl.pem"); - (ECDSA_P521_END_CERT, "ecdsa-p521", "end.cert"); - (ECDSA_P521_END_CHAIN, "ecdsa-p521", "end.chain"); - (ECDSA_P521_END_FULLCHAIN, "ecdsa-p521", "end.fullchain"); - (ECDSA_P521_END_KEY, "ecdsa-p521", "end.key"); - (ECDSA_P521_INTER_CERT, "ecdsa-p521", "inter.cert"); - (ECDSA_P521_INTER_KEY, "ecdsa-p521", "inter.key"); - - (EDDSA_END_PEM_SPKI, "eddsa", "end.spki.pem"); - (EDDSA_CLIENT_PEM_SPKI, "eddsa", "client.spki.pem"); - (EDDSA_CA_CERT, "eddsa", "ca.cert"); - (EDDSA_CA_DER, "eddsa", "ca.der"); - (EDDSA_CA_KEY, "eddsa", "ca.key"); - (EDDSA_CLIENT_CERT, "eddsa", "client.cert"); - (EDDSA_CLIENT_CHAIN, "eddsa", "client.chain"); - (EDDSA_CLIENT_FULLCHAIN, "eddsa", "client.fullchain"); - (EDDSA_CLIENT_KEY, "eddsa", "client.key"); - (EDDSA_END_CRL_PEM, "eddsa", "end.revoked.crl.pem"); - (EDDSA_CLIENT_CRL_PEM, "eddsa", "client.revoked.crl.pem"); - (EDDSA_INTERMEDIATE_CRL_PEM, "eddsa", "inter.revoked.crl.pem"); - (EDDSA_EXPIRED_CRL_PEM, "eddsa", "end.expired.crl.pem"); - (EDDSA_END_CERT, "eddsa", "end.cert"); - (EDDSA_END_CHAIN, "eddsa", "end.chain"); - (EDDSA_END_FULLCHAIN, "eddsa", "end.fullchain"); - (EDDSA_END_KEY, "eddsa", "end.key"); - (EDDSA_INTER_CERT, "eddsa", "inter.cert"); - (EDDSA_INTER_KEY, "eddsa", "inter.key"); - - (RSA_2048_END_PEM_SPKI, "rsa-2048", "end.spki.pem"); - (RSA_2048_CLIENT_PEM_SPKI, "rsa-2048", "client.spki.pem"); - (RSA_2048_CA_CERT, "rsa-2048", "ca.cert"); - (RSA_2048_CA_DER, "rsa-2048", "ca.der"); - (RSA_2048_CA_KEY, "rsa-2048", "ca.key"); - (RSA_2048_CLIENT_CERT, "rsa-2048", "client.cert"); - (RSA_2048_CLIENT_CHAIN, "rsa-2048", "client.chain"); - (RSA_2048_CLIENT_FULLCHAIN, "rsa-2048", "client.fullchain"); - (RSA_2048_CLIENT_KEY, "rsa-2048", "client.key"); - (RSA_2048_END_CRL_PEM, "rsa-2048", "end.revoked.crl.pem"); - (RSA_2048_CLIENT_CRL_PEM, "rsa-2048", "client.revoked.crl.pem"); - (RSA_2048_INTERMEDIATE_CRL_PEM, "rsa-2048", "inter.revoked.crl.pem"); - (RSA_2048_EXPIRED_CRL_PEM, "rsa-2048", "end.expired.crl.pem"); - (RSA_2048_END_CERT, "rsa-2048", "end.cert"); - (RSA_2048_END_CHAIN, "rsa-2048", "end.chain"); - (RSA_2048_END_FULLCHAIN, "rsa-2048", "end.fullchain"); - (RSA_2048_END_KEY, "rsa-2048", "end.key"); - (RSA_2048_INTER_CERT, "rsa-2048", "inter.cert"); - (RSA_2048_INTER_KEY, "rsa-2048", "inter.key"); - - (RSA_3072_END_PEM_SPKI, "rsa-3072", "end.spki.pem"); - (RSA_3072_CLIENT_PEM_SPKI, "rsa-3072", "client.spki.pem"); - (RSA_3072_CA_CERT, "rsa-3072", "ca.cert"); - (RSA_3072_CA_DER, "rsa-3072", "ca.der"); - (RSA_3072_CA_KEY, "rsa-3072", "ca.key"); - (RSA_3072_CLIENT_CERT, "rsa-3072", "client.cert"); - (RSA_3072_CLIENT_CHAIN, "rsa-3072", "client.chain"); - (RSA_3072_CLIENT_FULLCHAIN, "rsa-3072", "client.fullchain"); - (RSA_3072_CLIENT_KEY, "rsa-3072", "client.key"); - (RSA_3072_END_CRL_PEM, "rsa-3072", "end.revoked.crl.pem"); - (RSA_3072_CLIENT_CRL_PEM, "rsa-3072", "client.revoked.crl.pem"); - (RSA_3072_INTERMEDIATE_CRL_PEM, "rsa-3072", "inter.revoked.crl.pem"); - (RSA_3072_EXPIRED_CRL_PEM, "rsa-3072", "end.expired.crl.pem"); - (RSA_3072_END_CERT, "rsa-3072", "end.cert"); - (RSA_3072_END_CHAIN, "rsa-3072", "end.chain"); - (RSA_3072_END_FULLCHAIN, "rsa-3072", "end.fullchain"); - (RSA_3072_END_KEY, "rsa-3072", "end.key"); - (RSA_3072_INTER_CERT, "rsa-3072", "inter.cert"); - (RSA_3072_INTER_KEY, "rsa-3072", "inter.key"); - - (RSA_4096_END_PEM_SPKI, "rsa-4096", "end.spki.pem"); - (RSA_4096_CLIENT_PEM_SPKI, "rsa-4096", "client.spki.pem"); - (RSA_4096_CA_CERT, "rsa-4096", "ca.cert"); - (RSA_4096_CA_DER, "rsa-4096", "ca.der"); - (RSA_4096_CA_KEY, "rsa-4096", "ca.key"); - (RSA_4096_CLIENT_CERT, "rsa-4096", "client.cert"); - (RSA_4096_CLIENT_CHAIN, "rsa-4096", "client.chain"); - (RSA_4096_CLIENT_FULLCHAIN, "rsa-4096", "client.fullchain"); - (RSA_4096_CLIENT_KEY, "rsa-4096", "client.key"); - (RSA_4096_END_CRL_PEM, "rsa-4096", "end.revoked.crl.pem"); - (RSA_4096_CLIENT_CRL_PEM, "rsa-4096", "client.revoked.crl.pem"); - (RSA_4096_INTERMEDIATE_CRL_PEM, "rsa-4096", "inter.revoked.crl.pem"); - (RSA_4096_EXPIRED_CRL_PEM, "rsa-4096", "end.expired.crl.pem"); - (RSA_4096_END_CERT, "rsa-4096", "end.cert"); - (RSA_4096_END_CHAIN, "rsa-4096", "end.chain"); - (RSA_4096_END_FULLCHAIN, "rsa-4096", "end.fullchain"); - (RSA_4096_END_KEY, "rsa-4096", "end.key"); - (RSA_4096_INTER_CERT, "rsa-4096", "inter.cert"); - (RSA_4096_INTER_KEY, "rsa-4096", "inter.key"); -} - -pub fn transfer( - left: &mut impl DerefMut>, - right: &mut impl DerefMut>, -) -> usize { - let mut buf = [0u8; 262144]; - let mut total = 0; - - while left.wants_write() { - let sz = { - let into_buf: &mut dyn io::Write = &mut &mut buf[..]; - left.write_tls(into_buf).unwrap() - }; - total += sz; - if sz == 0 { - return total; - } - - let mut offs = 0; - loop { - let from_buf: &mut dyn io::Read = &mut &buf[offs..sz]; - offs += right.read_tls(from_buf).unwrap(); - if sz == offs { - break; - } - } - } - - total -} - -pub fn transfer_eof(conn: &mut impl DerefMut>) { - let empty_buf = [0u8; 0]; - let empty_cursor: &mut dyn io::Read = &mut &empty_buf[..]; - let sz = conn.read_tls(empty_cursor).unwrap(); - assert_eq!(sz, 0); -} - -pub enum Altered { - /// message has been edited in-place (or is unchanged) - InPlace, - /// send these raw bytes instead of the message. - Raw(Vec), -} - -pub fn transfer_altered(left: &mut Connection, filter: F, right: &mut Connection) -> usize -where - F: Fn(&mut Message) -> Altered, -{ - let mut buf = [0u8; 262144]; - let mut total = 0; - - while left.wants_write() { - let sz = { - let into_buf: &mut dyn io::Write = &mut &mut buf[..]; - left.write_tls(into_buf).unwrap() - }; - total += sz; - if sz == 0 { - return total; - } - - let mut reader = Reader::init(&buf[..sz]); - while reader.any_left() { - let message = OutboundOpaqueMessage::read(&mut reader).unwrap(); - - // this is a bit of a falsehood: we don't know whether message - // is encrypted. it is quite unlikely that a genuine encrypted - // message can be decoded by `Message::try_from`. - let plain = message.into_plain_message(); - - let message_enc = match Message::try_from(plain.clone()) { - Ok(mut message) => match filter(&mut message) { - Altered::InPlace => PlainMessage::from(message) - .into_unencrypted_opaque() - .encode(), - Altered::Raw(data) => data, - }, - // pass through encrypted/undecodable messages - Err(_) => plain.into_unencrypted_opaque().encode(), - }; - - let message_enc_reader: &mut dyn io::Read = &mut &message_enc[..]; - let len = right - .read_tls(message_enc_reader) - .unwrap(); - assert_eq!(len, message_enc.len()); - } - } - - total -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum KeyType { - Rsa2048, - Rsa3072, - Rsa4096, - EcdsaP256, - EcdsaP384, - EcdsaP521, - Ed25519, -} - -pub static ALL_KEY_TYPES: &[KeyType] = &[ - KeyType::Rsa2048, - KeyType::Rsa3072, - KeyType::Rsa4096, - KeyType::EcdsaP256, - KeyType::EcdsaP384, - #[cfg(all(not(feature = "ring"), feature = "aws_lc_rs"))] - KeyType::EcdsaP521, - KeyType::Ed25519, -]; - -impl KeyType { - fn bytes_for(&self, part: &str) -> &'static [u8] { - match self { - Self::Rsa2048 => bytes_for("rsa-2048", part), - Self::Rsa3072 => bytes_for("rsa-3072", part), - Self::Rsa4096 => bytes_for("rsa-4096", part), - Self::EcdsaP256 => bytes_for("ecdsa-p256", part), - Self::EcdsaP384 => bytes_for("ecdsa-p384", part), - Self::EcdsaP521 => bytes_for("ecdsa-p521", part), - Self::Ed25519 => bytes_for("eddsa", part), - } - } - - pub fn ca_cert(&self) -> CertificateDer<'_> { - self.get_chain() - .into_iter() - .next_back() - .expect("cert chain cannot be empty") - } - - pub fn get_chain(&self) -> Vec> { - CertificateDer::pem_slice_iter(self.bytes_for("end.fullchain")) - .map(|result| result.unwrap()) - .collect() - } - - pub fn get_spki(&self) -> SubjectPublicKeyInfoDer<'static> { - SubjectPublicKeyInfoDer::from_pem_slice(self.bytes_for("end.spki.pem")).unwrap() - } - - pub fn get_key(&self) -> PrivateKeyDer<'static> { - PrivatePkcs8KeyDer::from_pem_slice(self.bytes_for("end.key")) - .unwrap() - .into() - } - - pub fn get_client_chain(&self) -> Vec> { - CertificateDer::pem_slice_iter(self.bytes_for("client.fullchain")) - .map(|result| result.unwrap()) - .collect() - } - - pub fn end_entity_crl(&self) -> CertificateRevocationListDer<'static> { - self.get_crl("end", "revoked") - } - - pub fn client_crl(&self) -> CertificateRevocationListDer<'static> { - self.get_crl("client", "revoked") - } - - pub fn intermediate_crl(&self) -> CertificateRevocationListDer<'static> { - self.get_crl("inter", "revoked") - } - - pub fn end_entity_crl_expired(&self) -> CertificateRevocationListDer<'static> { - self.get_crl("end", "expired") - } - - pub fn get_client_key(&self) -> PrivateKeyDer<'static> { - PrivatePkcs8KeyDer::from_pem_slice(self.bytes_for("client.key")) - .unwrap() - .into() - } - - pub fn get_client_spki(&self) -> SubjectPublicKeyInfoDer<'static> { - SubjectPublicKeyInfoDer::from_pem_slice(self.bytes_for("client.spki.pem")).unwrap() - } - - pub fn get_certified_client_key(&self) -> Result, Error> { - let private_key = provider::default_provider() - .key_provider - .load_private_key(self.get_client_key())?; - let public_key = private_key - .public_key() - .ok_or(Error::InconsistentKeys(InconsistentKeys::Unknown))?; - let public_key_as_cert = CertificateDer::from(public_key.to_vec()); - Ok(Arc::new(CertifiedKey::new( - vec![public_key_as_cert], - private_key, - ))) - } - - pub fn certified_key_with_raw_pub_key(&self) -> Result, Error> { - let private_key = provider::default_provider() - .key_provider - .load_private_key(self.get_key())?; - let public_key = private_key - .public_key() - .ok_or(Error::InconsistentKeys(InconsistentKeys::Unknown))?; - let public_key_as_cert = CertificateDer::from(public_key.to_vec()); - Ok(Arc::new(CertifiedKey::new( - vec![public_key_as_cert], - private_key, - ))) - } - - pub fn certified_key_with_cert_chain(&self) -> Result, Error> { - let private_key = provider::default_provider() - .key_provider - .load_private_key(self.get_key())?; - Ok(Arc::new(CertifiedKey::new(self.get_chain(), private_key))) - } - - fn get_crl(&self, role: &str, r#type: &str) -> CertificateRevocationListDer<'static> { - CertificateRevocationListDer::from_pem_slice( - self.bytes_for(&format!("{role}.{type}.crl.pem")), - ) - .unwrap() - } - - pub fn ca_distinguished_name(&self) -> &'static [u8] { - match self { - KeyType::Rsa2048 => b"0\x1f1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14ponytown RSA 2048 CA", - KeyType::Rsa3072 => b"0\x1f1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14ponytown RSA 3072 CA", - KeyType::Rsa4096 => b"0\x1f1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14ponytown RSA 4096 CA", - KeyType::EcdsaP256 => b"0\x211\x1f0\x1d\x06\x03U\x04\x03\x0c\x16ponytown ECDSA p256 CA", - KeyType::EcdsaP384 => b"0\x211\x1f0\x1d\x06\x03U\x04\x03\x0c\x16ponytown ECDSA p384 CA", - KeyType::EcdsaP521 => b"0\x211\x1f0\x1d\x06\x03U\x04\x03\x0c\x16ponytown ECDSA p521 CA", - KeyType::Ed25519 => b"0\x1c1\x1a0\x18\x06\x03U\x04\x03\x0c\x11ponytown EdDSA CA", - } - } -} - -pub fn server_config_builder() -> rustls::ConfigBuilder { - // ensure `ServerConfig::builder()` is covered, even though it is - // equivalent to `builder_with_provider(provider::provider().into())`. - if exactly_one_provider() { - rustls::ServerConfig::builder() - } else { - rustls::ServerConfig::builder_with_provider(provider::default_provider().into()) - .with_safe_default_protocol_versions() - .unwrap() - } -} - -pub fn server_config_builder_with_versions( - versions: &[&'static rustls::SupportedProtocolVersion], -) -> rustls::ConfigBuilder { - if exactly_one_provider() { - rustls::ServerConfig::builder_with_protocol_versions(versions) - } else { - rustls::ServerConfig::builder_with_provider(provider::default_provider().into()) - .with_protocol_versions(versions) - .unwrap() - } -} - -pub fn client_config_builder() -> rustls::ConfigBuilder { - // ensure `ClientConfig::builder()` is covered, even though it is - // equivalent to `builder_with_provider(provider::provider().into())`. - if exactly_one_provider() { - rustls::ClientConfig::builder() - } else { - rustls::ClientConfig::builder_with_provider(provider::default_provider().into()) - .with_safe_default_protocol_versions() - .unwrap() - } -} - -pub fn client_config_builder_with_versions( - versions: &[&'static rustls::SupportedProtocolVersion], -) -> rustls::ConfigBuilder { - if exactly_one_provider() { - rustls::ClientConfig::builder_with_protocol_versions(versions) - } else { - rustls::ClientConfig::builder_with_provider(provider::default_provider().into()) - .with_protocol_versions(versions) - .unwrap() - } -} - -pub fn finish_server_config( - kt: KeyType, - conf: rustls::ConfigBuilder, -) -> ServerConfig { - conf.with_no_client_auth() - .with_single_cert(kt.get_chain(), kt.get_key()) - .unwrap() -} - -pub fn make_server_config(kt: KeyType) -> ServerConfig { - finish_server_config(kt, server_config_builder()) -} - -pub fn make_server_config_with_versions( - kt: KeyType, - versions: &[&'static rustls::SupportedProtocolVersion], -) -> ServerConfig { - finish_server_config(kt, server_config_builder_with_versions(versions)) -} - -pub fn make_server_config_with_kx_groups( - kt: KeyType, - kx_groups: Vec<&'static dyn rustls::crypto::SupportedKxGroup>, -) -> ServerConfig { - finish_server_config( - kt, - ServerConfig::builder_with_provider( - CryptoProvider { - kx_groups, - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap(), - ) -} - -pub fn get_client_root_store(kt: KeyType) -> Arc { - // The key type's chain file contains the DER encoding of the EE cert, the intermediate cert, - // and the root trust anchor. We want only the trust anchor to build the root cert store. - let chain = kt.get_chain(); - let trust_anchor = chain.last().unwrap(); - RootCertStore { - roots: vec![anchor_from_trusted_cert(trust_anchor) - .unwrap() - .to_owned()], - } - .into() -} - -pub fn make_server_config_with_mandatory_client_auth_crls( - kt: KeyType, - crls: Vec>, -) -> ServerConfig { - make_server_config_with_client_verifier( - kt, - webpki_client_verifier_builder(get_client_root_store(kt)).with_crls(crls), - ) -} - -pub fn make_server_config_with_mandatory_client_auth(kt: KeyType) -> ServerConfig { - make_server_config_with_client_verifier( - kt, - webpki_client_verifier_builder(get_client_root_store(kt)), - ) -} - -pub fn make_server_config_with_optional_client_auth( - kt: KeyType, - crls: Vec>, -) -> ServerConfig { - make_server_config_with_client_verifier( - kt, - webpki_client_verifier_builder(get_client_root_store(kt)) - .with_crls(crls) - .allow_unknown_revocation_status() - .allow_unauthenticated(), - ) -} - -pub fn make_server_config_with_client_verifier( - kt: KeyType, - verifier_builder: ClientCertVerifierBuilder, -) -> ServerConfig { - server_config_builder() - .with_client_cert_verifier(verifier_builder.build().unwrap()) - .with_single_cert(kt.get_chain(), kt.get_key()) - .unwrap() -} - -pub fn make_server_config_with_raw_key_support(kt: KeyType) -> ServerConfig { - let mut client_verifier = MockClientVerifier::new(|| Ok(ClientCertVerified::assertion()), kt); - let server_cert_resolver = Arc::new(AlwaysResolvesServerRawPublicKeys::new( - kt.certified_key_with_raw_pub_key() - .unwrap(), - )); - client_verifier.expect_raw_public_keys = true; - // We don't support tls1.2 for Raw Public Keys, hence the version is hard-coded. - server_config_builder_with_versions(&[&rustls::version::TLS13]) - .with_client_cert_verifier(Arc::new(client_verifier)) - .with_cert_resolver(server_cert_resolver) -} - -pub fn make_client_config_with_raw_key_support(kt: KeyType) -> ClientConfig { - let server_verifier = Arc::new(MockServerVerifier::expects_raw_public_keys()); - let client_cert_resolver = Arc::new(AlwaysResolvesClientRawPublicKeys::new( - kt.get_certified_client_key().unwrap(), - )); - // We don't support tls1.2 for Raw Public Keys, hence the version is hard-coded. - client_config_builder_with_versions(&[&rustls::version::TLS13]) - .dangerous() - .with_custom_certificate_verifier(server_verifier) - .with_client_cert_resolver(client_cert_resolver) -} - -pub fn make_client_config_with_cipher_suite_and_raw_key_support( - kt: KeyType, - cipher_suite: SupportedCipherSuite, -) -> ClientConfig { - let server_verifier = Arc::new(MockServerVerifier::expects_raw_public_keys()); - let client_cert_resolver = Arc::new(AlwaysResolvesClientRawPublicKeys::new( - kt.get_certified_client_key().unwrap(), - )); - ClientConfig::builder_with_provider( - CryptoProvider { - cipher_suites: vec![cipher_suite], - ..provider::default_provider() - } - .into(), - ) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() - .dangerous() - .with_custom_certificate_verifier(server_verifier) - .with_client_cert_resolver(client_cert_resolver) -} - -pub fn finish_client_config( - kt: KeyType, - config: rustls::ConfigBuilder, -) -> ClientConfig { - let mut root_store = RootCertStore::empty(); - root_store.add_parsable_certificates( - CertificateDer::pem_slice_iter(kt.bytes_for("ca.cert")).map(|result| result.unwrap()), - ); - - config - .with_root_certificates(root_store) - .with_no_client_auth() -} - -pub fn finish_client_config_with_creds( - kt: KeyType, - config: rustls::ConfigBuilder, -) -> ClientConfig { - let mut root_store = RootCertStore::empty(); - root_store.add_parsable_certificates( - CertificateDer::pem_slice_iter(kt.bytes_for("ca.cert")).map(|result| result.unwrap()), - ); - - config - .with_root_certificates(root_store) - .with_client_auth_cert(kt.get_client_chain(), kt.get_client_key()) - .unwrap() -} - -pub fn make_client_config(kt: KeyType) -> ClientConfig { - finish_client_config(kt, client_config_builder()) -} - -pub fn make_client_config_with_kx_groups( - kt: KeyType, - kx_groups: Vec<&'static dyn rustls::crypto::SupportedKxGroup>, -) -> ClientConfig { - let builder = ClientConfig::builder_with_provider( - CryptoProvider { - kx_groups, - ..provider::default_provider() - } - .into(), - ) - .with_safe_default_protocol_versions() - .unwrap(); - finish_client_config(kt, builder) -} - -pub fn make_client_config_with_versions( - kt: KeyType, - versions: &[&'static rustls::SupportedProtocolVersion], -) -> ClientConfig { - finish_client_config(kt, client_config_builder_with_versions(versions)) -} - -pub fn make_client_config_with_auth(kt: KeyType) -> ClientConfig { - finish_client_config_with_creds(kt, client_config_builder()) -} - -pub fn make_client_config_with_versions_with_auth( - kt: KeyType, - versions: &[&'static rustls::SupportedProtocolVersion], -) -> ClientConfig { - finish_client_config_with_creds(kt, client_config_builder_with_versions(versions)) -} - -pub fn make_client_config_with_verifier( - versions: &[&'static rustls::SupportedProtocolVersion], - verifier_builder: ServerCertVerifierBuilder, -) -> ClientConfig { - client_config_builder_with_versions(versions) - .dangerous() - .with_custom_certificate_verifier(verifier_builder.build().unwrap()) - .with_no_client_auth() -} - -pub fn webpki_client_verifier_builder(roots: Arc) -> ClientCertVerifierBuilder { - if exactly_one_provider() { - WebPkiClientVerifier::builder(roots) - } else { - WebPkiClientVerifier::builder_with_provider(roots, provider::default_provider().into()) - } -} - -pub fn webpki_server_verifier_builder(roots: Arc) -> ServerCertVerifierBuilder { - if exactly_one_provider() { - WebPkiServerVerifier::builder(roots) - } else { - WebPkiServerVerifier::builder_with_provider(roots, provider::default_provider().into()) - } -} - -pub fn make_pair(kt: KeyType) -> (ClientConnection, ServerConnection) { - make_pair_for_configs(make_client_config(kt), make_server_config(kt)) -} - -pub fn make_pair_for_configs( - client_config: ClientConfig, - server_config: ServerConfig, -) -> (ClientConnection, ServerConnection) { - make_pair_for_arc_configs(&Arc::new(client_config), &Arc::new(server_config)) -} - -pub fn make_pair_for_arc_configs( - client_config: &Arc, - server_config: &Arc, -) -> (ClientConnection, ServerConnection) { - ( - ClientConnection::new(Arc::clone(client_config), server_name("localhost")).unwrap(), - ServerConnection::new(Arc::clone(server_config)).unwrap(), - ) -} - -pub fn do_handshake( - client: &mut impl DerefMut>, - server: &mut impl DerefMut>, -) -> (usize, usize) { - let (mut to_client, mut to_server) = (0, 0); - while server.is_handshaking() || client.is_handshaking() { - to_server += transfer(client, server); - server.process_new_packets().unwrap(); - to_client += transfer(server, client); - client.process_new_packets().unwrap(); - } - (to_server, to_client) -} - -#[derive(PartialEq, Debug)] -pub enum ErrorFromPeer { - Client(Error), - Server(Error), -} - -pub fn do_handshake_until_error( - client: &mut ClientConnection, - server: &mut ServerConnection, -) -> Result<(), ErrorFromPeer> { - while server.is_handshaking() || client.is_handshaking() { - transfer(client, server); - server - .process_new_packets() - .map_err(ErrorFromPeer::Server)?; - transfer(server, client); - client - .process_new_packets() - .map_err(ErrorFromPeer::Client)?; - } - - Ok(()) -} - -pub fn do_handshake_altered( - client: ClientConnection, - alter_server_message: impl Fn(&mut Message) -> Altered, - alter_client_message: impl Fn(&mut Message) -> Altered, - server: ServerConnection, -) -> Result<(), ErrorFromPeer> { - let mut client: Connection = Connection::Client(client); - let mut server: Connection = Connection::Server(server); - - while server.is_handshaking() || client.is_handshaking() { - transfer_altered(&mut client, &alter_client_message, &mut server); - - server - .process_new_packets() - .map_err(ErrorFromPeer::Server)?; - - transfer_altered(&mut server, &alter_server_message, &mut client); - - client - .process_new_packets() - .map_err(ErrorFromPeer::Client)?; - } - - Ok(()) -} - -pub fn do_handshake_until_both_error( - client: &mut ClientConnection, - server: &mut ServerConnection, -) -> Result<(), Vec> { - match do_handshake_until_error(client, server) { - Err(server_err @ ErrorFromPeer::Server(_)) => { - let mut errors = vec![server_err]; - transfer(server, client); - let client_err = client - .process_new_packets() - .map_err(ErrorFromPeer::Client) - .expect_err("client didn't produce error after server error"); - errors.push(client_err); - Err(errors) - } - - Err(client_err @ ErrorFromPeer::Client(_)) => { - let mut errors = vec![client_err]; - transfer(client, server); - let server_err = server - .process_new_packets() - .map_err(ErrorFromPeer::Server) - .expect_err("server didn't produce error after client error"); - errors.push(server_err); - Err(errors) - } - - Ok(()) => Ok(()), - } -} - -pub fn server_name(name: &'static str) -> ServerName<'static> { - name.try_into().unwrap() -} - -pub struct FailsReads { - errkind: io::ErrorKind, -} - -impl FailsReads { - pub fn new(errkind: io::ErrorKind) -> Self { - Self { errkind } - } -} - -impl io::Read for FailsReads { - fn read(&mut self, _b: &mut [u8]) -> io::Result { - Err(io::Error::from(self.errkind)) - } -} - -pub fn do_suite_and_kx_test( - client_config: ClientConfig, - server_config: ServerConfig, - expect_suite: SupportedCipherSuite, - expect_kx: NamedGroup, - expect_version: ProtocolVersion, -) { - println!( - "do_suite_test {:?} {:?}", - expect_version, - expect_suite.suite() - ); - let (mut client, mut server) = make_pair_for_configs(client_config, server_config); - - assert_eq!(None, client.negotiated_cipher_suite()); - assert_eq!(None, server.negotiated_cipher_suite()); - assert!(client - .negotiated_key_exchange_group() - .is_none()); - assert!(server - .negotiated_key_exchange_group() - .is_none()); - assert_eq!(None, client.protocol_version()); - assert_eq!(None, server.protocol_version()); - assert!(client.is_handshaking()); - assert!(server.is_handshaking()); - - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - - assert!(client.is_handshaking()); - assert!(server.is_handshaking()); - assert_eq!(None, client.protocol_version()); - assert_eq!(Some(expect_version), server.protocol_version()); - assert_eq!(None, client.negotiated_cipher_suite()); - assert_eq!(Some(expect_suite), server.negotiated_cipher_suite()); - assert!(client - .negotiated_key_exchange_group() - .is_none()); - if matches!(expect_version, ProtocolVersion::TLSv1_2) { - assert!(server - .negotiated_key_exchange_group() - .is_none()); - } else { - assert_eq!( - expect_kx, - server - .negotiated_key_exchange_group() - .unwrap() - .name() - ); - } - - transfer(&mut server, &mut client); - client.process_new_packets().unwrap(); - - assert_eq!(Some(expect_suite), client.negotiated_cipher_suite()); - assert_eq!(Some(expect_suite), server.negotiated_cipher_suite()); - assert_eq!( - expect_kx, - client - .negotiated_key_exchange_group() - .unwrap() - .name() - ); - if matches!(expect_version, ProtocolVersion::TLSv1_2) { - assert!(server - .negotiated_key_exchange_group() - .is_none()); - } else { - assert_eq!( - expect_kx, - server - .negotiated_key_exchange_group() - .unwrap() - .name() - ); - } - - transfer(&mut client, &mut server); - server.process_new_packets().unwrap(); - transfer(&mut server, &mut client); - client.process_new_packets().unwrap(); - - assert!(!client.is_handshaking()); - assert!(!server.is_handshaking()); - assert_eq!(Some(expect_version), client.protocol_version()); - assert_eq!(Some(expect_version), server.protocol_version()); - assert_eq!(Some(expect_suite), client.negotiated_cipher_suite()); - assert_eq!(Some(expect_suite), server.negotiated_cipher_suite()); - assert_eq!( - expect_kx, - client - .negotiated_key_exchange_group() - .unwrap() - .name() - ); - assert_eq!( - expect_kx, - server - .negotiated_key_exchange_group() - .unwrap() - .name() - ); -} - -fn exactly_one_provider() -> bool { - cfg!(any( - all(feature = "ring", not(feature = "aws_lc_rs")), - all(feature = "aws_lc_rs", not(feature = "ring")) - )) -} - -#[derive(Debug)] -pub struct MockServerVerifier { - cert_rejection_error: Option, - tls12_signature_error: Option, - tls13_signature_error: Option, - signature_schemes: Vec, - expected_ocsp_response: Option>, - requires_raw_public_keys: bool, -} - -impl ServerCertVerifier for MockServerVerifier { - fn verify_server_cert( - &self, - end_entity: &CertificateDer<'_>, - intermediates: &[CertificateDer<'_>], - server_name: &ServerName<'_>, - ocsp_response: &[u8], - now: UnixTime, - ) -> Result { - println!( - "verify_server_cert({:?}, {:?}, {:?}, {:?}, {:?})", - end_entity, intermediates, server_name, ocsp_response, now - ); - if let Some(expected_ocsp) = &self.expected_ocsp_response { - assert_eq!(expected_ocsp, ocsp_response); - } - if let Some(error) = &self.cert_rejection_error { - Err(error.clone()) - } else { - Ok(ServerCertVerified::assertion()) - } - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - println!( - "verify_tls12_signature({:?}, {:?}, {:?})", - message, cert, dss - ); - if let Some(error) = &self.tls12_signature_error { - Err(error.clone()) - } else { - Ok(HandshakeSignatureValid::assertion()) - } - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - println!( - "verify_tls13_signature({:?}, {:?}, {:?})", - message, cert, dss - ); - if let Some(error) = &self.tls13_signature_error { - Err(error.clone()) - } else if self.requires_raw_public_keys { - verify_tls13_signature_with_raw_key( - message, - &SubjectPublicKeyInfoDer::from(cert.as_ref()), - dss, - &provider::default_provider().signature_verification_algorithms, - ) - } else { - Ok(HandshakeSignatureValid::assertion()) - } - } - - fn supported_verify_schemes(&self) -> Vec { - self.signature_schemes.clone() - } - - fn requires_raw_public_keys(&self) -> bool { - self.requires_raw_public_keys - } -} - -impl MockServerVerifier { - pub fn accepts_anything() -> Self { - MockServerVerifier { - cert_rejection_error: None, - ..Default::default() - } - } - - pub fn expects_ocsp_response(response: &[u8]) -> Self { - MockServerVerifier { - expected_ocsp_response: Some(response.to_vec()), - ..Default::default() - } - } - - pub fn rejects_certificate(err: Error) -> Self { - MockServerVerifier { - cert_rejection_error: Some(err), - ..Default::default() - } - } - - pub fn rejects_tls12_signatures(err: Error) -> Self { - MockServerVerifier { - tls12_signature_error: Some(err), - ..Default::default() - } - } - - pub fn rejects_tls13_signatures(err: Error) -> Self { - MockServerVerifier { - tls13_signature_error: Some(err), - ..Default::default() - } - } - - pub fn offers_no_signature_schemes() -> Self { - MockServerVerifier { - signature_schemes: vec![], - ..Default::default() - } - } - - pub fn expects_raw_public_keys() -> Self { - MockServerVerifier { - requires_raw_public_keys: true, - ..Default::default() - } - } -} - -impl Default for MockServerVerifier { - fn default() -> Self { - MockServerVerifier { - cert_rejection_error: None, - tls12_signature_error: None, - tls13_signature_error: None, - signature_schemes: vec![ - SignatureScheme::RSA_PSS_SHA256, - SignatureScheme::RSA_PKCS1_SHA256, - SignatureScheme::ED25519, - SignatureScheme::ECDSA_NISTP256_SHA256, - SignatureScheme::ECDSA_NISTP384_SHA384, - SignatureScheme::ECDSA_NISTP521_SHA512, - ], - expected_ocsp_response: None, - requires_raw_public_keys: false, - } - } -} - -#[derive(Debug)] -pub struct MockClientVerifier { - pub verified: fn() -> Result, - pub subjects: Vec, - pub mandatory: bool, - pub offered_schemes: Option>, - pub expect_raw_public_keys: bool, - parent: Arc, -} - -impl MockClientVerifier { - pub fn new(verified: fn() -> Result, kt: KeyType) -> Self { - Self { - parent: webpki_client_verifier_builder(get_client_root_store(kt)) - .build() - .unwrap(), - verified, - subjects: get_client_root_store(kt).subjects(), - mandatory: true, - offered_schemes: None, - expect_raw_public_keys: false, - } - } -} - -impl ClientCertVerifier for MockClientVerifier { - fn client_auth_mandatory(&self) -> bool { - self.mandatory - } - - fn root_hint_subjects(&self) -> &[DistinguishedName] { - &self.subjects - } - - fn verify_client_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _now: UnixTime, - ) -> Result { - (self.verified)() - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - if self.expect_raw_public_keys { - Ok(HandshakeSignatureValid::assertion()) - } else { - self.parent - .verify_tls12_signature(message, cert, dss) - } - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - if self.expect_raw_public_keys { - verify_tls13_signature_with_raw_key( - message, - &SubjectPublicKeyInfoDer::from(cert.as_ref()), - dss, - &provider::default_provider().signature_verification_algorithms, - ) - } else { - self.parent - .verify_tls13_signature(message, cert, dss) - } - } - - fn supported_verify_schemes(&self) -> Vec { - if let Some(schemes) = &self.offered_schemes { - schemes.clone() - } else { - self.parent.supported_verify_schemes() - } - } - - fn requires_raw_public_keys(&self) -> bool { - self.expect_raw_public_keys - } -} - -/// This allows injection/receipt of raw messages into a post-handshake connection. -/// -/// It consumes one of the peers, extracts its secrets, and then reconstitutes the -/// message encrypter/decrypter. It does not do fragmentation/joining. -pub struct RawTls { - encrypter: Box, - enc_seq: u64, - decrypter: Box, - dec_seq: u64, -} - -impl RawTls { - /// conn must be post-handshake, and must have been created with `enable_secret_extraction` - pub fn new_client(conn: ClientConnection) -> Self { - let suite = conn.negotiated_cipher_suite().unwrap(); - Self::new( - suite, - conn.dangerous_extract_secrets() - .unwrap(), - ) - } - - /// conn must be post-handshake, and must have been created with `enable_secret_extraction` - pub fn new_server(conn: ServerConnection) -> Self { - let suite = conn.negotiated_cipher_suite().unwrap(); - Self::new( - suite, - conn.dangerous_extract_secrets() - .unwrap(), - ) - } - - fn new(suite: SupportedCipherSuite, secrets: rustls::ExtractedSecrets) -> Self { - let rustls::ExtractedSecrets { - tx: (tx_seq, tx_keys), - rx: (rx_seq, rx_keys), - } = secrets; - - let encrypter = match (tx_keys, suite) { - ( - rustls::ConnectionTrafficSecrets::Aes256Gcm { key, iv }, - SupportedCipherSuite::Tls13(tls13), - ) => tls13.aead_alg.encrypter(key, iv), - - ( - rustls::ConnectionTrafficSecrets::Aes256Gcm { key, iv }, - SupportedCipherSuite::Tls12(tls12), - ) => tls12 - .aead_alg - .encrypter(key, &iv.as_ref()[..4], &iv.as_ref()[4..]), - - _ => todo!(), - }; - - let decrypter = match (rx_keys, suite) { - ( - rustls::ConnectionTrafficSecrets::Aes256Gcm { key, iv }, - SupportedCipherSuite::Tls13(tls13), - ) => tls13.aead_alg.decrypter(key, iv), - - ( - rustls::ConnectionTrafficSecrets::Aes256Gcm { key, iv }, - SupportedCipherSuite::Tls12(tls12), - ) => tls12 - .aead_alg - .decrypter(key, &iv.as_ref()[..4]), - - _ => todo!(), - }; - - Self { - encrypter, - enc_seq: tx_seq, - decrypter, - dec_seq: rx_seq, - } - } - - pub fn encrypt_and_send( - &mut self, - msg: &PlainMessage, - peer: &mut impl DerefMut>, - ) { - let data = self - .encrypter - .encrypt(msg.borrow_outbound(), self.enc_seq) - .unwrap() - .encode(); - self.enc_seq += 1; - peer.read_tls(&mut io::Cursor::new(data)) - .unwrap(); - } - - pub fn receive_and_decrypt( - &mut self, - peer: &mut impl DerefMut>, - f: impl Fn(Message), - ) { - let mut data = vec![]; - peer.write_tls(&mut io::Cursor::new(&mut data)) - .unwrap(); - - let mut reader = Reader::init(&data); - let content_type = ContentType::read(&mut reader).unwrap(); - let version = ProtocolVersion::read(&mut reader).unwrap(); - let len = u16::read(&mut reader).unwrap(); - let left = &mut data[5..]; - assert_eq!(len as usize, left.len()); - - let inbound = InboundOpaqueMessage::new(content_type, version, left); - let plain = self - .decrypter - .decrypt(inbound, self.dec_seq) - .unwrap(); - self.dec_seq += 1; - - let msg = Message::try_from(plain).unwrap(); - println!("receive_and_decrypt: {msg:?}"); - - f(msg); - } -} - -pub fn aes_128_gcm_with_1024_confidentiality_limit() -> Arc { - const CONFIDENTIALITY_LIMIT: u64 = 1024; - - // needed to extend lifetime of Tls13CipherSuite to 'static - static TLS13_LIMITED_SUITE: OnceLock = OnceLock::new(); - static TLS12_LIMITED_SUITE: OnceLock = OnceLock::new(); - - let tls13_limited = TLS13_LIMITED_SUITE.get_or_init(|| { - let tls13 = provider::cipher_suite::TLS13_AES_128_GCM_SHA256 - .tls13() - .unwrap(); - - rustls::Tls13CipherSuite { - common: rustls::crypto::CipherSuiteCommon { - confidentiality_limit: CONFIDENTIALITY_LIMIT, - ..tls13.common - }, - ..*tls13 - } - }); - - let tls12_limited = TLS12_LIMITED_SUITE.get_or_init(|| { - let SupportedCipherSuite::Tls12(tls12) = - provider::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - else { - unreachable!(); - }; - - rustls::Tls12CipherSuite { - common: rustls::crypto::CipherSuiteCommon { - confidentiality_limit: CONFIDENTIALITY_LIMIT, - ..tls12.common - }, - ..*tls12 - } - }); - - CryptoProvider { - cipher_suites: vec![ - SupportedCipherSuite::Tls13(tls13_limited), - SupportedCipherSuite::Tls12(tls12_limited), - ], - ..provider::default_provider() - } - .into() -} - -pub fn unsafe_plaintext_crypto_provider() -> Arc { - static TLS13_PLAIN_SUITE: OnceLock = OnceLock::new(); - - let tls13 = TLS13_PLAIN_SUITE.get_or_init(|| { - let tls13 = provider::cipher_suite::TLS13_AES_256_GCM_SHA384 - .tls13() - .unwrap(); - - rustls::Tls13CipherSuite { - aead_alg: &plaintext::Aead, - common: rustls::crypto::CipherSuiteCommon { ..tls13.common }, - ..*tls13 - } - }); - - CryptoProvider { - cipher_suites: vec![SupportedCipherSuite::Tls13(tls13)], - ..provider::default_provider() - } - .into() -} - -mod plaintext { - use rustls::crypto::cipher::{ - AeadKey, InboundOpaqueMessage, InboundPlainMessage, Iv, MessageDecrypter, MessageEncrypter, - OutboundPlainMessage, PrefixedPayload, Tls13AeadAlgorithm, UnsupportedOperationError, - }; - use rustls::ConnectionTrafficSecrets; - - use super::*; - - pub(super) struct Aead; - - impl Tls13AeadAlgorithm for Aead { - fn encrypter(&self, _key: AeadKey, _iv: Iv) -> Box { - Box::new(Encrypter) - } - - fn decrypter(&self, _key: AeadKey, _iv: Iv) -> Box { - Box::new(Decrypter) - } - - fn key_len(&self) -> usize { - 32 - } - - fn extract_keys( - &self, - _key: AeadKey, - _iv: Iv, - ) -> Result { - Err(UnsupportedOperationError) - } - } - - struct Encrypter; - - impl MessageEncrypter for Encrypter { - fn encrypt( - &mut self, - msg: OutboundPlainMessage<'_>, - _seq: u64, - ) -> Result { - let mut payload = PrefixedPayload::with_capacity(msg.payload.len()); - payload.extend_from_chunks(&msg.payload); - - Ok(OutboundOpaqueMessage::new( - ContentType::ApplicationData, - ProtocolVersion::TLSv1_2, - payload, - )) - } - - fn encrypted_payload_len(&self, payload_len: usize) -> usize { - payload_len - } - } - - struct Decrypter; - - impl MessageDecrypter for Decrypter { - fn decrypt<'a>( - &mut self, - msg: InboundOpaqueMessage<'a>, - _seq: u64, - ) -> Result, Error> { - Ok(msg.into_plain_message()) - } - } -} diff --git a/rustls/tests/data/cloudflare-esni-echconfigs.bin b/rustls/tests/data/cloudflare-esni-echconfigs.bin new file mode 100644 index 00000000000..f6d687ef8e7 Binary files /dev/null and b/rustls/tests/data/cloudflare-esni-echconfigs.bin differ diff --git a/rustls/tests/data/localhost-echconfigs.bin b/rustls/tests/data/localhost-echconfigs.bin new file mode 100644 index 00000000000..35a605da9d8 Binary files /dev/null and b/rustls/tests/data/localhost-echconfigs.bin differ diff --git a/rustls/tests/data/unsupported-then-valid-echconfigs.bin b/rustls/tests/data/unsupported-then-valid-echconfigs.bin new file mode 100644 index 00000000000..506a22e7dc2 Binary files /dev/null and b/rustls/tests/data/unsupported-then-valid-echconfigs.bin differ diff --git a/rustls/tests/ech.rs b/rustls/tests/ech.rs deleted file mode 100644 index 444f1e20494..00000000000 --- a/rustls/tests/ech.rs +++ /dev/null @@ -1,126 +0,0 @@ -use base64::prelude::{Engine, BASE64_STANDARD}; -use pki_types::DnsName; -use rustls::internal::msgs::codec::{Codec, Reader}; -use rustls::internal::msgs::enums::{EchVersion, HpkeAead, HpkeKdf, HpkeKem}; -use rustls::internal::msgs::handshake::{ - EchConfigContents, EchConfigPayload, HpkeKeyConfig, HpkeSymmetricCipherSuite, -}; - -#[test] -fn test_decode_config_list() { - fn assert_config(contents: &EchConfigContents, public_name: impl AsRef<[u8]>, max_len: u8) { - assert_eq!(contents.maximum_name_length, max_len); - assert_eq!( - contents.public_name, - DnsName::try_from(public_name.as_ref()).unwrap() - ); - assert!(contents.extensions.is_empty()); - } - - fn assert_key_config( - config: &HpkeKeyConfig, - id: u8, - kem_id: HpkeKem, - cipher_suites: Vec, - ) { - assert_eq!(config.config_id, id); - assert_eq!(config.kem_id, kem_id); - assert_eq!(config.symmetric_cipher_suites, cipher_suites); - } - - let config_list = get_ech_config(BASE64_ECHCONFIG_LIST_LOCALHOST); - assert_eq!(config_list.len(), 1); - let EchConfigPayload::V18(contents) = &config_list[0] else { - panic!("unexpected ECH config version: {:?}", config_list[0]); - }; - assert_config(contents, "localhost", 128); - assert_key_config( - &contents.key_config, - 0, - HpkeKem::DHKEM_X25519_HKDF_SHA256, - vec![ - HpkeSymmetricCipherSuite { - kdf_id: HpkeKdf::HKDF_SHA256, - aead_id: HpkeAead::AES_128_GCM, - }, - HpkeSymmetricCipherSuite { - kdf_id: HpkeKdf::HKDF_SHA256, - aead_id: HpkeAead::CHACHA20_POLY_1305, - }, - ], - ); - - let config_list = get_ech_config(BASE64_ECHCONFIG_LIST_CF); - assert_eq!(config_list.len(), 2); - let EchConfigPayload::V18(contents_a) = &config_list[0] else { - panic!("unexpected ECH config version: {:?}", config_list[0]); - }; - assert_config(contents_a, "cloudflare-esni.com", 37); - assert_key_config( - &contents_a.key_config, - 195, - HpkeKem::DHKEM_X25519_HKDF_SHA256, - vec![HpkeSymmetricCipherSuite { - kdf_id: HpkeKdf::HKDF_SHA256, - aead_id: HpkeAead::AES_128_GCM, - }], - ); - let EchConfigPayload::V18(contents_b) = &config_list[1] else { - panic!("unexpected ECH config version: {:?}", config_list[1]); - }; - assert_config(contents_b, "cloudflare-esni.com", 42); - assert_key_config( - &contents_b.key_config, - 3, - HpkeKem::DHKEM_P256_HKDF_SHA256, - vec![HpkeSymmetricCipherSuite { - kdf_id: HpkeKdf::HKDF_SHA256, - aead_id: HpkeAead::AES_128_GCM, - }], - ); - - let config_list = get_ech_config(BASE64_ECHCONFIG_LIST_WITH_UNSUPPORTED); - assert_eq!(config_list.len(), 4); - // The first config should be unsupported. - assert!(matches!( - config_list[0], - EchConfigPayload::Unknown { - version: EchVersion::Unknown(0xBADD), - .. - } - )); - // The other configs should be recognized. - for config in config_list.iter().skip(1) { - assert!(matches!(config, EchConfigPayload::V18(_))); - } -} - -#[test] -fn test_echconfig_serialization() { - fn assert_round_trip_eq(data: &str) { - let configs = get_ech_config(data); - let mut output = Vec::new(); - configs.encode(&mut output); - assert_eq!(data, BASE64_STANDARD.encode(&output)); - } - - assert_round_trip_eq(BASE64_ECHCONFIG_LIST_LOCALHOST); - assert_round_trip_eq(BASE64_ECHCONFIG_LIST_CF); - assert_round_trip_eq(BASE64_ECHCONFIG_LIST_WITH_UNSUPPORTED); -} - -fn get_ech_config(s: &str) -> Vec { - let bytes = BASE64_STANDARD.decode(s).unwrap(); - Vec::<_>::read(&mut Reader::init(&bytes)).unwrap() -} - -// One EchConfig, with server-name "localhost". -const BASE64_ECHCONFIG_LIST_LOCALHOST: &str = - "AED+DQA8AAAgACAxoIJyV36iDlfFRmqE+ho2PxXE0EISPfUUJYKCy6T8VwAIAAEAAQABAAOACWxvY2FsaG9zdAAA"; - -// Two EchConfigs, both with server-name "cloudflare-esni.com". -const BASE64_ECHCONFIG_LIST_CF: &str = - "AK3+DQBCwwAgACAJ9T5U4FeM6631r2bvAuGtmEd8zQaoTkFAtArTcMl/XQAEAAEAASUTY2xvdWRmbGFyZS1lc25pLmNvbQAA/g0AYwMAEABBBGGbUlGLuGRorUeFwmrgHImkrh9uxoPrnFKpS5bQvnc5grfMS3PvymQ2FYL02WQi1ZzZJg5OsYYdzlaGYnEoJNsABAABAAEqE2Nsb3VkZmxhcmUtZXNuaS5jb20AAA=="; - -// Three EchConfigs, the first one with an unsupported version. -const BASE64_ECHCONFIG_LIST_WITH_UNSUPPORTED: &str = "AQW63QAFBQQDAgH+DQBmAAAQAEEE5itp4r9ln5e+Lx4NlIpM1Zdrt6keDUb73ampHp3culoB59aXqAoY+cPEox5W4nyDSNsWGhz1HX7xlC1Lz3IiwQAMAAEAAQABAAIAAQADQA5wdWJsaWMuZXhhbXBsZQAA/g0APQAAIAAgfWYWFXMCFK7ucFMzZvNqYJ6tZcDCCOYjIjRqtbzY3hwABBERIiJADnB1YmxpYy5leGFtcGxlAAD+DQBNAAAgACCFvWoDJ3wlQntS4mngx3qOtSS6HrPS8TJmLUsKxstzVwAMAAEAAQABAAIAAQADQA5wdWJsaWMuZXhhbXBsZQAIqqoABHRlc3Q="; diff --git a/rustls/tests/process_provider.rs b/rustls/tests/process_provider.rs deleted file mode 100644 index e1ab14199b7..00000000000 --- a/rustls/tests/process_provider.rs +++ /dev/null @@ -1,73 +0,0 @@ -#![cfg(any(feature = "ring", feature = "aws_lc_rs"))] - -//! Note that the default test runner builds each test file into a separate -//! executable, and runs tests in an indeterminate order. That restricts us -//! to doing all the desired tests, in series, in one function. - -#[cfg(all(feature = "aws_lc_rs", not(feature = "ring")))] -use rustls::crypto::aws_lc_rs as provider; -#[cfg(all(feature = "ring", not(feature = "aws_lc_rs")))] -use rustls::crypto::ring as provider; -#[cfg(all(feature = "ring", feature = "aws_lc_rs"))] -use rustls::crypto::ring as provider; -use rustls::crypto::CryptoProvider; -use rustls::ClientConfig; - -mod common; -use crate::common::*; - -#[test] -fn test_process_provider() { - if dbg!(cfg!(all(feature = "ring", feature = "aws_lc_rs"))) { - test_explicit_choice_required(); - } else if dbg!(cfg!(all(feature = "ring", not(feature = "aws_lc_rs")))) { - test_ring_used_as_implicit_provider(); - } else if dbg!(cfg!(all(feature = "aws_lc_rs", not(feature = "ring")))) { - test_aws_lc_rs_used_as_implicit_provider(); - } else { - panic!("fix feature combinations"); - } -} - -fn test_explicit_choice_required() { - assert!(CryptoProvider::get_default().is_none()); - provider::default_provider() - .install_default() - .expect("cannot install"); - CryptoProvider::get_default().expect("provider missing"); - provider::default_provider() - .install_default() - .expect_err("install succeeded a second time"); - CryptoProvider::get_default().expect("provider missing"); - - // does not panic - finish_client_config(KeyType::Rsa2048, ClientConfig::builder()); -} - -fn test_ring_used_as_implicit_provider() { - assert!(CryptoProvider::get_default().is_none()); - - // implicitly installs ring provider - finish_client_config(KeyType::Rsa2048, ClientConfig::builder()); - - let default = CryptoProvider::get_default().expect("provider missing"); - let debug = format!("{default:?}"); - assert!(debug.contains("secure_random: Ring")); - - let builder = ClientConfig::builder(); - assert_eq!(format!("{:?}", builder.crypto_provider()), debug); -} - -fn test_aws_lc_rs_used_as_implicit_provider() { - assert!(CryptoProvider::get_default().is_none()); - - // implicitly installs aws-lc-rs provider - finish_client_config(KeyType::Rsa2048, ClientConfig::builder()); - - let default = CryptoProvider::get_default().expect("provider missing"); - let debug = format!("{default:?}"); - assert!(debug.contains("secure_random: AwsLcRs")); - - let builder = ClientConfig::builder(); - assert_eq!(format!("{:?}", builder.crypto_provider()), debug); -} diff --git a/rustls/tests/runners/api.rs b/rustls/tests/runners/api.rs deleted file mode 100644 index 39bb2cf5ad3..00000000000 --- a/rustls/tests/runners/api.rs +++ /dev/null @@ -1,99 +0,0 @@ -#![cfg_attr(read_buf, feature(read_buf))] -#![cfg_attr(read_buf, feature(core_io_borrowed_buf))] - -use std::cell::RefCell; - -#[macro_use] -mod macros; - -#[cfg(feature = "ring")] -#[path = "."] -mod tests_with_ring { - use super::*; - - provider_ring!(); - - #[path = "../api.rs"] - mod tests; -} - -#[cfg(feature = "aws_lc_rs")] -#[path = "."] -mod tests_with_aws_lc_rs { - use super::*; - - provider_aws_lc_rs!(); - - #[path = "../api.rs"] - mod tests; -} - -// this must be outside tests_with_*, as we want -// one thread_local!, not one per provider. -thread_local!(static COUNTS: RefCell = RefCell::new(LogCounts::new())); - -struct CountingLogger; - -#[allow(dead_code)] -static LOGGER: CountingLogger = CountingLogger; - -#[allow(dead_code)] -impl CountingLogger { - fn install() { - let _ = log::set_logger(&LOGGER); - log::set_max_level(log::LevelFilter::Trace); - } - - fn reset() { - COUNTS.with(|c| { - c.borrow_mut().reset(); - }); - } -} - -impl log::Log for CountingLogger { - fn enabled(&self, _metadata: &log::Metadata) -> bool { - true - } - - fn log(&self, record: &log::Record) { - println!("logging at {:?}: {:?}", record.level(), record.args()); - - COUNTS.with(|c| { - c.borrow_mut().add(record.level()); - }); - } - - fn flush(&self) {} -} - -#[derive(Default, Debug)] -struct LogCounts { - trace: usize, - debug: usize, - info: usize, - warn: usize, - error: usize, -} - -impl LogCounts { - fn new() -> Self { - Self { - ..Default::default() - } - } - - fn reset(&mut self) { - *self = Self::new(); - } - - fn add(&mut self, level: log::Level) { - match level { - log::Level::Trace => self.trace += 1, - log::Level::Debug => self.debug += 1, - log::Level::Info => self.info += 1, - log::Level::Warn => self.warn += 1, - log::Level::Error => self.error += 1, - } - } -} diff --git a/rustls/tests/runners/api_ffdhe.rs b/rustls/tests/runners/api_ffdhe.rs deleted file mode 100644 index 242455e764f..00000000000 --- a/rustls/tests/runners/api_ffdhe.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[macro_use] -mod macros; - -#[cfg(feature = "ring")] -#[path = "."] -mod tests_with_ring { - provider_ring!(); - - #[path = "../api_ffdhe.rs"] - mod tests; -} - -#[cfg(feature = "aws_lc_rs")] -#[path = "."] -mod tests_with_aws_lc_rs { - provider_aws_lc_rs!(); - - #[path = "../api_ffdhe.rs"] - mod tests; -} diff --git a/rustls/tests/runners/client_cert_verifier.rs b/rustls/tests/runners/client_cert_verifier.rs deleted file mode 100644 index 4626270d2c4..00000000000 --- a/rustls/tests/runners/client_cert_verifier.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[macro_use] -mod macros; - -#[cfg(feature = "ring")] -#[path = "."] -mod tests_with_ring { - provider_ring!(); - - #[path = "../client_cert_verifier.rs"] - mod tests; -} - -#[cfg(feature = "aws_lc_rs")] -#[path = "."] -mod tests_with_aws_lc_rs { - provider_aws_lc_rs!(); - - #[path = "../client_cert_verifier.rs"] - mod tests; -} diff --git a/rustls/tests/runners/macros.rs b/rustls/tests/runners/macros.rs deleted file mode 100644 index 3a55977d42b..00000000000 --- a/rustls/tests/runners/macros.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Macros that bring a provider into the current scope. -//! -//! The selected provider module is bound as `provider`; you can rely on this -//! having the union of the public items common to the `rustls::crypto::ring` -//! and `rustls::crypto::aws_lc_rs` modules. - -#[allow(unused_macros)] -macro_rules! provider_ring { - () => { - #[allow(unused_imports)] - use rustls::crypto::ring as provider; - #[allow(dead_code)] - const fn provider_is_aws_lc_rs() -> bool { - false - } - #[allow(dead_code)] - const fn provider_is_ring() -> bool { - true - } - #[allow(dead_code)] - const fn provider_is_fips() -> bool { - false - } - }; -} - -#[allow(unused_macros)] -macro_rules! provider_aws_lc_rs { - () => { - #[allow(unused_imports)] - use rustls::crypto::aws_lc_rs as provider; - #[allow(dead_code)] - const fn provider_is_aws_lc_rs() -> bool { - true - } - #[allow(dead_code)] - const fn provider_is_ring() -> bool { - false - } - #[allow(dead_code)] - const fn provider_is_fips() -> bool { - cfg!(feature = "fips") - } - }; -} diff --git a/rustls/tests/runners/server_cert_verifier.rs b/rustls/tests/runners/server_cert_verifier.rs deleted file mode 100644 index 11dd98990f4..00000000000 --- a/rustls/tests/runners/server_cert_verifier.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[macro_use] -mod macros; - -#[cfg(feature = "ring")] -#[path = "."] -mod tests_with_ring { - provider_ring!(); - - #[path = "../server_cert_verifier.rs"] - mod tests; -} - -#[cfg(feature = "aws_lc_rs")] -#[path = "."] -mod tests_with_aws_lc_rs { - provider_aws_lc_rs!(); - - #[path = "../server_cert_verifier.rs"] - mod tests; -} diff --git a/rustls/tests/runners/unbuffered.rs b/rustls/tests/runners/unbuffered.rs deleted file mode 100644 index 3c1f72048a7..00000000000 --- a/rustls/tests/runners/unbuffered.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[macro_use] -mod macros; - -#[cfg(feature = "ring")] -#[path = "."] -mod tests_with_ring { - provider_ring!(); - - #[path = "../unbuffered.rs"] - mod tests; -} - -#[cfg(feature = "aws_lc_rs")] -#[path = "."] -mod tests_with_aws_lc_rs { - provider_aws_lc_rs!(); - - #[path = "../unbuffered.rs"] - mod tests; -} diff --git a/rustls/tests/server_cert_verifier.rs b/rustls/tests/server_cert_verifier.rs deleted file mode 100644 index 8b5b03a9783..00000000000 --- a/rustls/tests/server_cert_verifier.rs +++ /dev/null @@ -1,383 +0,0 @@ -//! Tests for configuring and using a [`ServerCertVerifier`] for a client. - -#![allow(clippy::duplicate_mod)] - -use super::*; - -mod common; -use std::sync::Arc; - -use common::{ - client_config_builder, client_config_builder_with_versions, do_handshake, - do_handshake_until_both_error, do_handshake_until_error, make_client_config_with_versions, - make_pair_for_arc_configs, make_server_config, server_config_builder, transfer_altered, - Altered, ErrorFromPeer, KeyType, MockServerVerifier, ALL_KEY_TYPES, -}; -use pki_types::{CertificateDer, ServerName}; -use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; -use rustls::client::WebPkiServerVerifier; -use rustls::internal::msgs::handshake::{ClientExtension, HandshakePayload}; -use rustls::internal::msgs::message::{Message, MessagePayload}; -use rustls::server::{ClientHello, ResolvesServerCert}; -use rustls::sign::CertifiedKey; -use rustls::version::{TLS12, TLS13}; -use rustls::{ - AlertDescription, CertificateError, DigitallySignedStruct, DistinguishedName, Error, - InvalidMessage, RootCertStore, -}; -use x509_parser::prelude::FromDer; -use x509_parser::x509::X509Name; - -#[test] -fn client_can_override_certificate_verification() { - for kt in ALL_KEY_TYPES.iter() { - let verifier = Arc::new(MockServerVerifier::accepts_anything()); - - let server_config = Arc::new(make_server_config(*kt)); - - for version in rustls::ALL_VERSIONS { - let mut client_config = make_client_config_with_versions(*kt, &[version]); - client_config - .dangerous() - .set_certificate_verifier(verifier.clone()); - - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - do_handshake(&mut client, &mut server); - } - } -} - -#[test] -fn client_can_override_certificate_verification_and_reject_certificate() { - for kt in ALL_KEY_TYPES.iter() { - let verifier = Arc::new(MockServerVerifier::rejects_certificate( - Error::InvalidMessage(InvalidMessage::HandshakePayloadTooLarge), - )); - - let server_config = Arc::new(make_server_config(*kt)); - - for version in rustls::ALL_VERSIONS { - let mut client_config = make_client_config_with_versions(*kt, &[version]); - client_config - .dangerous() - .set_certificate_verifier(verifier.clone()); - - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - let errs = do_handshake_until_both_error(&mut client, &mut server); - assert_eq!( - errs, - Err(vec![ - ErrorFromPeer::Client(Error::InvalidMessage( - InvalidMessage::HandshakePayloadTooLarge, - )), - ErrorFromPeer::Server(Error::AlertReceived(AlertDescription::HandshakeFailure)), - ]), - ); - } - } -} - -#[cfg(feature = "tls12")] -#[test] -fn client_can_override_certificate_verification_and_reject_tls12_signatures() { - for kt in ALL_KEY_TYPES.iter() { - let mut client_config = make_client_config_with_versions(*kt, &[&rustls::version::TLS12]); - let verifier = Arc::new(MockServerVerifier::rejects_tls12_signatures( - Error::InvalidMessage(InvalidMessage::HandshakePayloadTooLarge), - )); - - client_config - .dangerous() - .set_certificate_verifier(verifier); - - let server_config = Arc::new(make_server_config(*kt)); - - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - let errs = do_handshake_until_both_error(&mut client, &mut server); - assert_eq!( - errs, - Err(vec![ - ErrorFromPeer::Client(Error::InvalidMessage( - InvalidMessage::HandshakePayloadTooLarge, - )), - ErrorFromPeer::Server(Error::AlertReceived(AlertDescription::HandshakeFailure)), - ]), - ); - } -} - -#[test] -fn client_can_override_certificate_verification_and_reject_tls13_signatures() { - for kt in ALL_KEY_TYPES.iter() { - let mut client_config = make_client_config_with_versions(*kt, &[&rustls::version::TLS13]); - let verifier = Arc::new(MockServerVerifier::rejects_tls13_signatures( - Error::InvalidMessage(InvalidMessage::HandshakePayloadTooLarge), - )); - - client_config - .dangerous() - .set_certificate_verifier(verifier); - - let server_config = Arc::new(make_server_config(*kt)); - - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - let errs = do_handshake_until_both_error(&mut client, &mut server); - assert_eq!( - errs, - Err(vec![ - ErrorFromPeer::Client(Error::InvalidMessage( - InvalidMessage::HandshakePayloadTooLarge, - )), - ErrorFromPeer::Server(Error::AlertReceived(AlertDescription::HandshakeFailure)), - ]), - ); - } -} - -#[test] -fn client_can_override_certificate_verification_and_offer_no_signature_schemes() { - for kt in ALL_KEY_TYPES.iter() { - let verifier = Arc::new(MockServerVerifier::offers_no_signature_schemes()); - - let server_config = Arc::new(make_server_config(*kt)); - - for version in rustls::ALL_VERSIONS { - let mut client_config = make_client_config_with_versions(*kt, &[version]); - client_config - .dangerous() - .set_certificate_verifier(verifier.clone()); - - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(client_config), &server_config); - let errs = do_handshake_until_both_error(&mut client, &mut server); - assert_eq!( - errs, - Err(vec![ - ErrorFromPeer::Server(Error::PeerIncompatible( - rustls::PeerIncompatible::NoSignatureSchemesInCommon - )), - ErrorFromPeer::Client(Error::AlertReceived(AlertDescription::HandshakeFailure)), - ]) - ); - } - } -} - -#[test] -fn cas_extension_in_client_hello_if_server_verifier_requests_it() { - let server_config = Arc::new(make_server_config(KeyType::Rsa2048)); - - let mut root_cert_store = RootCertStore::empty(); - root_cert_store - .add(KeyType::Rsa2048.ca_cert()) - .unwrap(); - - let server_verifier = WebPkiServerVerifier::builder_with_provider( - Arc::new(root_cert_store), - Arc::new(provider::default_provider()), - ) - .build() - .unwrap(); - let cas_sending_server_verifier = Arc::new(ServerCertVerifierWithCasExt { - verifier: server_verifier.clone(), - ca_names: vec![KeyType::Rsa2048 - .ca_distinguished_name() - .to_vec() - .into()], - }); - - for (protocol_version, cas_extension_expected) in [(&TLS12, false), (&TLS13, true)] { - let client_config = Arc::new( - client_config_builder_with_versions(&[protocol_version]) - .dangerous() - .with_custom_certificate_verifier(cas_sending_server_verifier.clone()) - .with_no_client_auth(), - ); - - let expect_cas_extension = |msg: &mut Message<'_>| -> Altered { - if let MessagePayload::Handshake { parsed, .. } = &msg.payload { - if let HandshakePayload::ClientHello(ch) = &parsed.payload { - assert_eq!( - ch.extensions - .iter() - .any(|ext| matches!(ext, ClientExtension::AuthorityNames(_))), - cas_extension_expected - ); - println!("cas extension expectation met! cas_extension_expected: {cas_extension_expected}"); - } - } - Altered::InPlace - }; - - let (client, server) = make_pair_for_arc_configs(&client_config, &server_config); - let (mut client, mut server) = (client.into(), server.into()); - transfer_altered(&mut client, expect_cas_extension, &mut server); - } -} - -#[test] -fn client_can_request_certain_trusted_cas() { - // These keys have CAs with different names, which our test needs. - // They also share the same sigalgs, so the server won't pick one over the other based on sigalgs. - let key_types = [KeyType::Rsa2048, KeyType::Rsa3072, KeyType::Rsa4096]; - let cert_resolver = ResolvesCertChainByCaName( - key_types - .iter() - .map(|kt| { - ( - kt.ca_distinguished_name() - .to_vec() - .into(), - kt.certified_key_with_cert_chain() - .unwrap(), - ) - }) - .collect(), - ); - - let server_config = Arc::new( - server_config_builder() - .with_no_client_auth() - .with_cert_resolver(Arc::new(cert_resolver.clone())), - ); - - let mut cas_unaware_error_count = 0; - - for key_type in key_types { - let mut root_store = RootCertStore::empty(); - root_store - .add(key_type.ca_cert()) - .unwrap(); - let server_verifier = WebPkiServerVerifier::builder_with_provider( - Arc::new(root_store), - Arc::new(provider::default_provider()), - ) - .build() - .unwrap(); - - let cas_sending_server_verifier = Arc::new(ServerCertVerifierWithCasExt { - verifier: server_verifier.clone(), - ca_names: vec![DistinguishedName::from( - key_type - .ca_distinguished_name() - .to_vec(), - )], - }); - - let cas_sending_client_config = client_config_builder() - .dangerous() - .with_custom_certificate_verifier(cas_sending_server_verifier) - .with_no_client_auth(); - - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(cas_sending_client_config), &server_config); - do_handshake(&mut client, &mut server); - - let cas_unaware_client_config = client_config_builder() - .dangerous() - .with_custom_certificate_verifier(server_verifier) - .with_no_client_auth(); - - let (mut client, mut server) = - make_pair_for_arc_configs(&Arc::new(cas_unaware_client_config), &server_config); - - cas_unaware_error_count += do_handshake_until_error(&mut client, &mut server) - .inspect_err(|e| { - assert!(matches!( - e, - ErrorFromPeer::Client(Error::InvalidCertificate( - CertificateError::UnknownIssuer - )) - )) - }) - .is_err() as usize; - - println!("key type {key_type:?} success!"); - } - - // For cas_unaware clients, all of them should fail except one that happens to - // have the cert the server sends - assert_eq!(cas_unaware_error_count, key_types.len() - 1); -} - -#[derive(Debug, Clone)] -pub struct ResolvesCertChainByCaName(Vec<(DistinguishedName, Arc)>); - -impl ResolvesServerCert for ResolvesCertChainByCaName { - fn resolve(&self, client_hello: ClientHello<'_>) -> Option> { - let Some(cas_extension) = client_hello.certificate_authorities() else { - println!("ResolvesCertChainByCaName: no CAs extension in ClientHello, returning default cert"); - return Some(self.0[0].1.clone()); - }; - for (name, certified_key) in self.0.iter() { - let name = X509Name::from_der(name.as_ref()) - .unwrap() - .1; - if cas_extension.iter().any(|ca_name| { - X509Name::from_der(ca_name.as_ref()).is_ok_and(|(_, ca_name)| ca_name == name) - }) { - println!("ResolvesCertChainByCaName: found matching CA name: {name}"); - return Some(certified_key.clone()); - } - } - println!("ResolvesCertChainByCaName: no matching CA name found, returning default Cert"); - Some(self.0[0].1.clone()) - } -} - -#[derive(Debug)] -struct ServerCertVerifierWithCasExt { - verifier: Arc, - ca_names: Vec, -} - -impl ServerCertVerifier for ServerCertVerifierWithCasExt { - fn verify_server_cert( - &self, - end_entity: &CertificateDer<'_>, - intermediates: &[CertificateDer<'_>], - server_name: &ServerName<'_>, - ocsp_response: &[u8], - now: pki_types::UnixTime, - ) -> Result { - self.verifier - .verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now) - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - self.verifier - .verify_tls12_signature(message, cert, dss) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - self.verifier - .verify_tls13_signature(message, cert, dss) - } - - fn supported_verify_schemes(&self) -> Vec { - self.verifier.supported_verify_schemes() - } - - fn requires_raw_public_keys(&self) -> bool { - self.verifier.requires_raw_public_keys() - } - - fn root_hint_subjects(&self) -> Option<&[DistinguishedName]> { - println!("ServerCertVerifierWithCasExt::root_hint_subjects() called!"); - Some(&self.ca_names) - } -} diff --git a/rustls/tests/unbuffered.rs b/rustls/tests/unbuffered.rs deleted file mode 100644 index 81311b2540c..00000000000 --- a/rustls/tests/unbuffered.rs +++ /dev/null @@ -1,1424 +0,0 @@ -#![allow(clippy::duplicate_mod)] - -use std::num::NonZeroUsize; -use std::sync::Arc; - -use rustls::client::{ClientConnectionData, EarlyDataError, UnbufferedClientConnection}; -use rustls::server::{ServerConnectionData, UnbufferedServerConnection}; -use rustls::unbuffered::{ - ConnectionState, EncodeError, EncryptError, InsufficientSizeError, UnbufferedConnectionCommon, - UnbufferedStatus, WriteTraffic, -}; -use rustls::version::TLS13; -use rustls::{ - AlertDescription, CertificateError, ClientConfig, Error, InvalidMessage, ServerConfig, SideData, -}; - -use super::*; - -mod common; -use common::*; - -const MAX_ITERATIONS: usize = 100; - -#[test] -fn tls12_handshake() { - let outcome = handshake(&rustls::version::TLS12); - assert_eq!( - outcome.client_transcript, - vec![ - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(WriteTraffic)" - ], - "client transcript mismatch" - ); - assert_eq!( - outcome.server_transcript, - vec![ - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(WriteTraffic)" - ], - "server transcript mismatch" - ); -} - -#[test] -fn tls12_handshake_fragmented() { - let outcome = handshake_config(&rustls::version::TLS12, |client, server| { - client.max_fragment_size = Some(512); - client.cert_decompressors = vec![]; - server.max_fragment_size = Some(512); - }); - assert_eq!( - outcome.client_transcript, - vec![ - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(WriteTraffic)" - ], - "client transcript mismatch" - ); - assert_eq!( - outcome.server_transcript, - vec![ - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(WriteTraffic)" - ], - "server transcript mismatch" - ); -} - -#[test] -fn tls13_handshake() { - let outcome = handshake(&rustls::version::TLS13); - assert_eq!( - outcome.client_transcript, - vec![ - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(WriteTraffic)", - "Ok(WriteTraffic)" - ], - "client transcript mismatch" - ); - assert_eq!( - outcome.server_transcript, - vec![ - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(WriteTraffic)" - ], - "server transcript mismatch" - ); -} - -#[test] -fn tls13_handshake_fragmented() { - let outcome = handshake_config(&rustls::version::TLS13, |client, server| { - client.max_fragment_size = Some(512); - client.cert_decompressors = vec![]; - server.max_fragment_size = Some(512); - }); - assert_eq!( - outcome.client_transcript, - vec![ - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(WriteTraffic)", - "Ok(WriteTraffic)" - ], - "client transcript mismatch" - ); - assert_eq!( - outcome.server_transcript, - vec![ - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(WriteTraffic)" - ], - "server transcript mismatch" - ); -} - -fn handshake(version: &'static rustls::SupportedProtocolVersion) -> Outcome { - handshake_config(version, |_, _| ()) -} - -fn handshake_config( - version: &'static rustls::SupportedProtocolVersion, - editor: impl Fn(&mut ClientConfig, &mut ServerConfig), -) -> Outcome { - let mut server_config = make_server_config_with_versions(KeyType::Rsa2048, &[version]); - let mut client_config = make_client_config(KeyType::Rsa2048); - editor(&mut client_config, &mut server_config); - - run( - Arc::new(client_config), - &mut NO_ACTIONS.clone(), - Arc::new(server_config), - &mut NO_ACTIONS.clone(), - ) -} - -#[test] -fn app_data_client_to_server() { - let expected: &[_] = b"hello"; - for version in rustls::ALL_VERSIONS { - eprintln!("{version:?}"); - let server_config = make_server_config_with_versions(KeyType::Rsa2048, &[version]); - let client_config = make_client_config(KeyType::Rsa2048); - - let mut client_actions = Actions { - app_data_to_send: Some(expected), - ..NO_ACTIONS - }; - - let outcome = run( - Arc::new(client_config), - &mut client_actions, - Arc::new(server_config), - &mut NO_ACTIONS.clone(), - ); - - assert!(client_actions - .app_data_to_send - .is_none()); - assert_eq!( - [expected], - outcome - .server_received_app_data - .as_slice() - ); - } -} - -#[test] -fn app_data_server_to_client() { - let expected: &[_] = b"hello"; - for version in rustls::ALL_VERSIONS { - eprintln!("{version:?}"); - let server_config = make_server_config_with_versions(KeyType::Rsa2048, &[version]); - let client_config = make_client_config(KeyType::Rsa2048); - - let mut server_actions = Actions { - app_data_to_send: Some(expected), - ..NO_ACTIONS - }; - - let outcome = run( - Arc::new(client_config), - &mut NO_ACTIONS.clone(), - Arc::new(server_config), - &mut server_actions, - ); - - assert!(server_actions - .app_data_to_send - .is_none()); - assert_eq!( - [expected], - outcome - .client_received_app_data - .as_slice() - ); - } -} - -#[test] -fn early_data() { - let expected: &[_] = b"hello"; - - let mut server_config = make_server_config(KeyType::Rsa2048); - server_config.max_early_data_size = 128; - let server_config = Arc::new(server_config); - - let mut client_config = make_client_config_with_versions(KeyType::Rsa2048, &[&TLS13]); - client_config.enable_early_data = true; - let client_config = Arc::new(client_config); - - // first handshake allows the second to be a resumption and use 0-RTT - run( - client_config.clone(), - &mut NO_ACTIONS.clone(), - server_config.clone(), - &mut NO_ACTIONS.clone(), - ); - - let mut client_actions = Actions { - early_data_to_send: Some(expected), - ..NO_ACTIONS - }; - - let outcome = run( - client_config.clone(), - &mut client_actions, - server_config.clone(), - &mut NO_ACTIONS.clone(), - ); - - assert_eq!( - outcome.client_transcript, - vec![ - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(BlockedHandshake)", - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(WriteTraffic)", - "Ok(WriteTraffic)" - ] - ); - assert_eq!( - outcome.server_transcript, - vec![ - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(EncodeTlsData)", - "Ok(ReadEarlyData)", - "Ok(TransmitTlsData)", - "Ok(BlockedHandshake)", - "Ok(EncodeTlsData)", - "Ok(TransmitTlsData)", - "Ok(WriteTraffic)" - ] - ); - assert!(client_actions - .early_data_to_send - .is_none()); - assert_eq!( - [expected], - outcome - .server_received_early_data - .as_slice() - ); -} - -fn run( - client_config: Arc, - client_actions: &mut Actions, - server_config: Arc, - server_actions: &mut Actions, -) -> Outcome { - let mut outcome = Outcome::default(); - let mut count = 0; - let mut client_handshake_done = false; - let mut server_handshake_done = false; - - let mut client = - UnbufferedClientConnection::new(client_config.clone(), server_name("localhost")).unwrap(); - let mut server = UnbufferedServerConnection::new(server_config.clone()).unwrap(); - let mut buffers = BothBuffers::default(); - - while !(client_handshake_done - && server_handshake_done - && client_actions.finished() - && server_actions.finished()) - { - match advance_client( - &mut client, - &mut buffers.client, - *client_actions, - &mut outcome.client_transcript, - ) { - State::EncodedTlsData => {} - State::TransmitTlsData { - sent_early_data, - sent_app_data, - sent_close_notify, - } => { - buffers.client_send(); - if sent_app_data { - client_actions.app_data_to_send = None; - } - - if sent_early_data { - client_actions.early_data_to_send = None; - } - - if sent_close_notify { - client_actions.send_close_notify = false; - } - } - State::BlockedHandshake => buffers.server_send(), - State::WriteTraffic { - sent_app_data, - sent_close_notify, - } => { - buffers.client_send(); - - if sent_app_data { - client_actions.app_data_to_send = None; - } - - if sent_close_notify { - client_actions.send_close_notify = false; - } - - client_handshake_done = true; - } - State::ReceivedAppData { records } => { - outcome - .client_received_app_data - .extend(records); - } - State::Closed => { - client_handshake_done = true; - outcome.client_reached_connection_closed_state = true - } - state => unreachable!("{state:?}"), - } - - match advance_server( - &mut server, - &mut buffers.server, - *server_actions, - &mut outcome.server_transcript, - ) { - State::EncodedTlsData => {} - State::TransmitTlsData { - sent_app_data, - sent_close_notify, - .. - } => { - buffers.server_send(); - - if sent_app_data { - server_actions.app_data_to_send = None; - } - - if sent_close_notify { - server_actions.send_close_notify = false; - } - } - State::BlockedHandshake => buffers.client_send(), - State::WriteTraffic { - sent_app_data, - sent_close_notify, - } => { - buffers.server_send(); - - if sent_app_data { - server_actions.app_data_to_send = None; - } - - if sent_close_notify { - server_actions.send_close_notify = false; - } - - server_handshake_done = true; - } - State::ReceivedEarlyData { records } => { - outcome - .server_received_early_data - .extend(records); - } - State::ReceivedAppData { records } => { - outcome - .server_received_app_data - .extend(records); - } - State::Closed => { - server_handshake_done = true; - outcome.server_reached_connection_closed_state = true - } - } - - count += 1; - - assert!(count <= MAX_ITERATIONS, "handshake was not completed"); - } - - println!("finished with:"); - println!( - " client: {:?} {:?} {:?}", - client.protocol_version(), - client.negotiated_cipher_suite(), - client.handshake_kind() - ); - println!( - " server: {:?} {:?} {:?}", - server.protocol_version(), - server.negotiated_cipher_suite(), - server.handshake_kind() - ); - - outcome.server = Some(server); - outcome.client = Some(client); - outcome -} - -#[test] -fn close_notify_client_to_server() { - for version in rustls::ALL_VERSIONS { - eprintln!("{version:?}"); - let server_config = make_server_config_with_versions(KeyType::Rsa2048, &[version]); - let client_config = make_client_config(KeyType::Rsa2048); - - let mut client_actions = Actions { - send_close_notify: true, - ..NO_ACTIONS - }; - - let outcome = run( - Arc::new(client_config), - &mut client_actions, - Arc::new(server_config), - &mut NO_ACTIONS.clone(), - ); - - assert!(!client_actions.send_close_notify); - assert!(outcome.server_reached_connection_closed_state); - } -} - -#[test] -fn close_notify_server_to_client() { - for version in rustls::ALL_VERSIONS { - eprintln!("{version:?}"); - let server_config = make_server_config_with_versions(KeyType::Rsa2048, &[version]); - let client_config = make_client_config(KeyType::Rsa2048); - - let mut server_actions = Actions { - send_close_notify: true, - ..NO_ACTIONS - }; - - let outcome = run( - Arc::new(client_config), - &mut NO_ACTIONS.clone(), - Arc::new(server_config), - &mut server_actions, - ); - - assert!(!server_actions.send_close_notify); - assert!(outcome.client_reached_connection_closed_state); - } -} - -#[test] -fn junk_after_close_notify_received() { - // cf. test_junk_after_close_notify_received in api.rs - let mut outcome = handshake(&rustls::version::TLS13); - let mut client = outcome.client.take().unwrap(); - let mut server = outcome.server.take().unwrap(); - - let mut client_send_buf = [0u8; 128]; - let mut len = dbg!(write_traffic( - client.process_tls_records(&mut []), - |mut wt: WriteTraffic<_>| wt.queue_close_notify(&mut client_send_buf), - ) - .unwrap()); - - client_send_buf[len..len + 4].copy_from_slice(&[0x17, 0x03, 0x03, 0x01]); - len += 4; - - let discard = match dbg!(server.process_tls_records(dbg!(&mut client_send_buf[..len]))) { - UnbufferedStatus { - discard, - state: Ok(ConnectionState::Closed), - } => { - assert_eq!(discard, 24); - discard - } - st => { - panic!("unexpected server state {st:?} (wanted Closed)"); - } - }; - - // further data in client_send_buf is ignored - let UnbufferedStatus { discard, .. } = - server.process_tls_records(dbg!(&mut client_send_buf[discard..len])); - assert_eq!(discard, 0); -} - -#[test] -fn queue_close_notify_is_idempotent() { - let mut outcome = handshake(&rustls::version::TLS13); - let mut client = outcome.client.take().unwrap(); - - let mut client_send_buf = [0u8; 128]; - let (len_first, len_second) = write_traffic( - client.process_tls_records(&mut []), - |mut wt: WriteTraffic<_>| { - ( - wt.queue_close_notify(&mut client_send_buf), - wt.queue_close_notify(&mut client_send_buf), - ) - }, - ); - - assert!(len_first.unwrap() > 0); - assert_eq!(len_second.unwrap(), 0); -} - -#[test] -fn refresh_traffic_keys_on_tls12_connection() { - let mut outcome = handshake(&rustls::version::TLS12); - let mut client = outcome.client.take().unwrap(); - - match client.process_tls_records(&mut []) { - UnbufferedStatus { - discard: 0, - state: Ok(ConnectionState::WriteTraffic(wt)), - } => { - assert_eq!( - wt.refresh_traffic_keys().unwrap_err(), - Error::HandshakeNotComplete, - ); - } - st => { - panic!("unexpected client state {st:?}"); - } - }; -} - -#[test] -fn refresh_traffic_keys_manually() { - let mut outcome = handshake(&rustls::version::TLS13); - let mut client = outcome.client.take().unwrap(); - let mut server = outcome.server.take().unwrap(); - - match client.process_tls_records(&mut []) { - UnbufferedStatus { - discard: 0, - state: Ok(ConnectionState::WriteTraffic(wt)), - } => { - wt.refresh_traffic_keys().unwrap(); - } - st => { - panic!("unexpected client state {st:?}"); - } - }; - - let mut buffer = [0u8; 64]; - let used = match client.process_tls_records(&mut []) { - UnbufferedStatus { - discard: 0, - state: Ok(ConnectionState::EncodeTlsData(mut etd)), - } => { - println!("EncodeTlsData"); - etd.encode(&mut buffer).unwrap() - } - st => { - panic!("unexpected client state {st:?}"); - } - }; - - match client.process_tls_records(&mut []) { - UnbufferedStatus { - discard: 0, - state: Ok(ConnectionState::TransmitTlsData(ttd)), - } => { - ttd.done(); - } - st => { - panic!("unexpected client state {st:?}"); - } - }; - - println!("server WriteTraffic"); - let used = match server.process_tls_records(&mut buffer[..used]) { - UnbufferedStatus { - discard: actual_used, - state: Ok(ConnectionState::WriteTraffic(mut wt)), - } => { - assert_eq!(used, actual_used); - wt.encrypt(b"hello", &mut buffer) - .unwrap() - } - st => { - panic!("unexpected server state {st:?}"); - } - }; - - println!("client recv"); - match client.process_tls_records(&mut buffer[..used]) { - UnbufferedStatus { - discard: actual_used, - state: Ok(ConnectionState::ReadTraffic(mut rt)), - } => { - assert_eq!(used, actual_used); - let app_data = rt.next_record().unwrap().unwrap(); - assert_eq!(app_data.payload, b"hello"); - } - st => { - panic!("unexpected client state {st:?}"); - } - }; - - println!("client reply"); - let used = match client.process_tls_records(&mut []) { - UnbufferedStatus { - discard: 0, - state: Ok(ConnectionState::WriteTraffic(mut wt)), - } => wt - .encrypt(b"world", &mut buffer) - .unwrap(), - st => { - panic!("unexpected client state {st:?}"); - } - }; - - match server.process_tls_records(&mut buffer[..used]) { - UnbufferedStatus { - discard: actual_used, - state: Ok(ConnectionState::ReadTraffic(mut rt)), - } => { - assert_eq!(used, actual_used); - let app_data = rt.next_record().unwrap().unwrap(); - assert_eq!(app_data.payload, b"world"); - } - st => { - panic!("unexpected server state {st:?}"); - } - }; -} - -#[test] -fn refresh_traffic_keys_automatically() { - const fn encrypted_size(body: usize) -> usize { - let padding = 1; - let header = 5; - let tag = 16; - header + body + padding + tag - } - - const KEY_UPDATE_SIZE: usize = encrypted_size(5); - const CONFIDENTIALITY_LIMIT: usize = 1024; - const CONFIDENTIALITY_LIMIT_PLUS_ONE: usize = CONFIDENTIALITY_LIMIT + 1; - - let client_config = finish_client_config( - KeyType::Rsa2048, - ClientConfig::builder_with_provider(aes_128_gcm_with_1024_confidentiality_limit()) - .with_safe_default_protocol_versions() - .unwrap(), - ); - - let server_config = make_server_config(KeyType::Rsa2048); - let mut outcome = run( - Arc::new(client_config), - &mut NO_ACTIONS.clone(), - Arc::new(server_config), - &mut NO_ACTIONS.clone(), - ); - let mut server = outcome.server.take().unwrap(); - let mut client = outcome.client.take().unwrap(); - - match client.process_tls_records(&mut []) { - UnbufferedStatus { - discard: 0, - state: Ok(ConnectionState::WriteTraffic(mut wt)), - } => { - // Must happen on a single `WriteTraffic` instance, to - // validate that handshake messages are included - // in the TLS records returned by `WriteTraffic::encrypt` - for i in 0..(CONFIDENTIALITY_LIMIT + 16) { - let message = format!("{i:08}"); - - let mut buffer = [0u8; 64]; - let used = wt - .encrypt(message.as_bytes(), &mut buffer) - .unwrap(); - - assert_eq!( - used, - match i { - // The key_update message triggered by write N appears in write N+1 - CONFIDENTIALITY_LIMIT_PLUS_ONE => - KEY_UPDATE_SIZE + encrypted_size(message.len()), - _ => encrypted_size(message.len()), - } - ); - - match server.process_tls_records(&mut buffer[..used]) { - UnbufferedStatus { - discard: actual_used, - state: Ok(ConnectionState::ReadTraffic(mut rt)), - } => { - assert_eq!(used, actual_used); - let record = rt.next_record().unwrap().unwrap(); - assert_eq!(record.payload, message.as_bytes()); - } - st => { - panic!("unexpected server state {st:?}"); - } - }; - println!("{i}: wrote {used}"); - } - } - st => { - panic!("unexpected client state {st:?}"); - } - }; -} - -#[test] -fn tls12_connection_fails_after_key_reaches_confidentiality_limit() { - const CONFIDENTIALITY_LIMIT: usize = 1024; - - let client_config = finish_client_config( - KeyType::Ed25519, - ClientConfig::builder_with_provider(aes_128_gcm_with_1024_confidentiality_limit()) - .with_protocol_versions(&[&rustls::version::TLS12]) - .unwrap(), - ); - - let server_config = make_server_config(KeyType::Ed25519); - let mut outcome = run( - Arc::new(client_config), - &mut NO_ACTIONS.clone(), - Arc::new(server_config), - &mut NO_ACTIONS.clone(), - ); - let mut server = outcome.server.take().unwrap(); - let mut client = outcome.client.take().unwrap(); - - match client.process_tls_records(&mut []) { - UnbufferedStatus { - discard: 0, - state: Ok(ConnectionState::WriteTraffic(mut wt)), - } => { - for i in 0..CONFIDENTIALITY_LIMIT { - let message = format!("{i:08}"); - - let mut buffer = [0u8; 64]; - let used = match wt.encrypt(message.as_bytes(), &mut buffer) { - Ok(used) => used, - Err(EncryptError::EncryptExhausted) if i == CONFIDENTIALITY_LIMIT - 1 => { - break; - } - rc @ Err(_) => rc.unwrap(), - }; - - match server.process_tls_records(&mut buffer[..used]) { - UnbufferedStatus { - discard: actual_used, - state: Ok(ConnectionState::ReadTraffic(mut rt)), - } => { - assert_eq!(used, actual_used); - let record = rt.next_record().unwrap().unwrap(); - assert_eq!(record.payload, message.as_bytes()); - } - st => { - panic!("unexpected server state {st:?}"); - } - }; - println!("{i}: wrote {used}"); - } - } - st => { - panic!("unexpected client state {st:?}"); - } - }; - - let (mut data, _) = encode_tls_data(client.process_tls_records(&mut [])); - let data_len = data.len(); - - match server.process_tls_records(&mut data) { - UnbufferedStatus { - discard, - state: Ok(ConnectionState::Closed), - } if discard == data_len => {} - st => panic!("unexpected server state {st:?}"), - } -} - -#[test] -fn tls13_packed_handshake() { - // transcript requires selection of X25519 - if provider_is_fips() { - return; - } - - // regression test for https://github.com/rustls/rustls/issues/2040 - let client_config = ClientConfig::builder_with_provider(unsafe_plaintext_crypto_provider()) - .with_safe_default_protocol_versions() - .unwrap() - .dangerous() - .with_custom_certificate_verifier(Arc::new(MockServerVerifier::rejects_certificate( - CertificateError::UnknownIssuer.into(), - ))) - .with_no_client_auth(); - - let mut client = - UnbufferedClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - - let (_hello, _) = encode_tls_data(client.process_tls_records(&mut [])); - confirm_transmit_tls_data(client.process_tls_records(&mut [])); - - let mut first_flight = include_bytes!("data/bug2040-message-1.bin").to_vec(); - let (_ccs, discard) = encode_tls_data(client.process_tls_records(&mut first_flight[..])); - assert_eq!(discard, first_flight.len()); - - let mut second_flight = include_bytes!("data/bug2040-message-2.bin").to_vec(); - let UnbufferedStatus { state, .. } = client.process_tls_records(&mut second_flight[..]); - assert_eq!( - state.unwrap_err(), - Error::InvalidCertificate(CertificateError::UnknownIssuer) - ); -} - -#[test] -fn rejects_junk() { - let mut server = - UnbufferedServerConnection::new(Arc::new(make_server_config(KeyType::Rsa2048))).unwrap(); - - let mut buf = [0xff; 5]; - let UnbufferedStatus { discard, state } = server.process_tls_records(&mut buf); - assert_eq!(discard, 0); - assert_eq!( - state.unwrap_err(), - Error::InvalidMessage(InvalidMessage::InvalidContentType) - ); - - // sends alert - let (data, _) = encode_tls_data(server.process_tls_records(&mut [])); - assert_eq!( - data, - &[ - 0x15, - 0x03, - 0x03, - 0x00, - 0x02, - 0x02, - u8::from(AlertDescription::DecodeError) - ] - ); - confirm_transmit_tls_data(server.process_tls_records(&mut [])); -} - -fn write_traffic) -> R>( - status: UnbufferedStatus<'_, '_, T>, - mut f: F, -) -> R { - let UnbufferedStatus { discard, state } = status; - assert_eq!(discard, 0); - let state = state.unwrap(); - if let ConnectionState::WriteTraffic(state) = state { - f(state) - } else { - panic!("unexpected client state {state:?} (wanted WriteTraffic)"); - } -} - -fn encode_tls_data(status: UnbufferedStatus<'_, '_, T>) -> (Vec, usize) { - match status { - UnbufferedStatus { - discard, - state: Ok(ConnectionState::EncodeTlsData(mut etd)), - } => { - let len = match etd.encode(&mut []) { - Err(EncodeError::InsufficientSize(InsufficientSizeError { required_size })) => { - required_size - } - e => panic!("unexpected encode {e:?}"), - }; - let mut buf = vec![0u8; len]; - etd.encode(&mut buf).unwrap(); - (buf, discard) - } - _ => { - panic!("unexpected state {status:?} (wanted EncodeTlsData)"); - } - } -} - -fn confirm_transmit_tls_data(status: UnbufferedStatus<'_, '_, T>) { - match status { - UnbufferedStatus { - discard: 0, - state: Ok(ConnectionState::TransmitTlsData(ttd)), - } => { - ttd.done(); - } - _ => { - panic!("unexpected state {status:?} (wanted TransmitTlsData)"); - } - } -} - -#[derive(Debug)] -enum State { - Closed, - EncodedTlsData, - TransmitTlsData { - sent_app_data: bool, - sent_close_notify: bool, - sent_early_data: bool, - }, - BlockedHandshake, - ReceivedAppData { - records: Vec>, - }, - ReceivedEarlyData { - records: Vec>, - }, - WriteTraffic { - sent_app_data: bool, - sent_close_notify: bool, - }, -} - -const NO_ACTIONS: Actions = Actions { - app_data_to_send: None, - early_data_to_send: None, - send_close_notify: false, -}; - -#[derive(Clone, Copy, Debug)] -struct Actions<'a> { - app_data_to_send: Option<&'a [u8]>, - early_data_to_send: Option<&'a [u8]>, - send_close_notify: bool, -} - -impl Actions<'_> { - fn finished(&self) -> bool { - self.app_data_to_send.is_none() - && self.early_data_to_send.is_none() - && !self.send_close_notify - } -} - -#[derive(Default)] -struct Outcome { - server: Option, - server_transcript: Vec, - server_received_early_data: Vec>, - server_received_app_data: Vec>, - server_reached_connection_closed_state: bool, - client: Option, - client_transcript: Vec, - client_received_app_data: Vec>, - client_reached_connection_closed_state: bool, -} - -fn advance_client( - conn: &mut UnbufferedConnectionCommon, - buffers: &mut Buffers, - actions: Actions, - transcript: &mut Vec, -) -> State { - let UnbufferedStatus { discard, state } = conn.process_tls_records(buffers.incoming.filled()); - - transcript.push(format!("{:?}", state)); - - let state = match state.unwrap() { - ConnectionState::TransmitTlsData(mut state) => { - let mut sent_early_data = false; - if let Some(early_data) = actions.early_data_to_send { - if let Some(mut state) = state.may_encrypt_early_data() { - write_with_buffer_size_checks( - |out_buf| state.encrypt(early_data, out_buf), - |e| { - println!("encrypt error: {e}"); - if let EarlyDataError::Encrypt(EncryptError::InsufficientSize(ise)) = e - { - ise - } else { - unreachable!() - } - }, - &mut buffers.outgoing, - ); - sent_early_data = true; - } - } - state.done(); - State::TransmitTlsData { - sent_app_data: false, - sent_close_notify: false, - sent_early_data, - } - } - - state => handle_state(state, &mut buffers.outgoing, actions), - }; - buffers.incoming.discard(discard); - - state -} - -fn advance_server( - conn: &mut UnbufferedConnectionCommon, - buffers: &mut Buffers, - actions: Actions, - transcript: &mut Vec, -) -> State { - let UnbufferedStatus { discard, state } = conn.process_tls_records(buffers.incoming.filled()); - - transcript.push(format!("{:?}", state)); - - let state = match state.unwrap() { - ConnectionState::ReadEarlyData(mut state) => { - let mut records = vec![]; - let mut peeked_len = state.peek_len(); - - while let Some(res) = state.next_record() { - let payload = res.unwrap().payload.to_vec(); - assert_eq!(NonZeroUsize::new(payload.len()), peeked_len); - records.push(payload); - peeked_len = state.peek_len(); - } - - assert_eq!(None, peeked_len); - - State::ReceivedEarlyData { records } - } - - state => handle_state(state, &mut buffers.outgoing, actions), - }; - buffers.incoming.discard(discard); - - state -} - -fn handle_state( - state: ConnectionState<'_, '_, Data>, - outgoing: &mut Buffer, - actions: Actions, -) -> State { - match dbg!(state) { - ConnectionState::EncodeTlsData(mut state) => { - write_with_buffer_size_checks( - |out_buf| state.encode(out_buf), - |e| { - println!("encode error: {e}"); - if let EncodeError::InsufficientSize(ise) = e { - ise - } else { - unreachable!() - } - }, - outgoing, - ); - - assert!(matches!( - state.encode(&mut []).unwrap_err(), - EncodeError::AlreadyEncoded - )); - - State::EncodedTlsData - } - - ConnectionState::TransmitTlsData(mut state) => { - let mut sent_app_data = false; - if let Some(app_data) = actions.app_data_to_send { - if let Some(mut state) = state.may_encrypt_app_data() { - encrypt(&mut state, app_data, outgoing); - sent_app_data = true; - } - } - - let mut sent_close_notify = false; - if let Some(mut state) = state.may_encrypt_app_data() { - if actions.send_close_notify { - queue_close_notify(&mut state, outgoing); - sent_close_notify = true; - } - } - - // this should be called *after* the data has been transmitted but it's easier to - // do it in reverse - state.done(); - State::TransmitTlsData { - sent_app_data, - sent_early_data: false, - sent_close_notify, - } - } - - ConnectionState::BlockedHandshake { .. } => State::BlockedHandshake, - - ConnectionState::WriteTraffic(mut state) => { - let mut sent_app_data = false; - if let Some(app_data) = actions.app_data_to_send { - encrypt(&mut state, app_data, outgoing); - sent_app_data = true; - } - - let mut sent_close_notify = false; - if actions.send_close_notify { - queue_close_notify(&mut state, outgoing); - sent_close_notify = true; - } - - State::WriteTraffic { - sent_app_data, - sent_close_notify, - } - } - - ConnectionState::ReadTraffic(mut state) => { - let mut records = vec![]; - let mut peeked_len = state.peek_len(); - - while let Some(res) = state.next_record() { - let payload = res.unwrap().payload.to_vec(); - assert_eq!(NonZeroUsize::new(payload.len()), peeked_len); - records.push(payload); - peeked_len = state.peek_len(); - } - - assert_eq!(None, peeked_len); - - State::ReceivedAppData { records } - } - - ConnectionState::Closed => State::Closed, - - _ => unreachable!(), - } -} - -fn queue_close_notify(state: &mut WriteTraffic<'_, Data>, outgoing: &mut Buffer) { - write_with_buffer_size_checks( - |out_buf| state.queue_close_notify(out_buf), - map_encrypt_error, - outgoing, - ); -} - -fn encrypt(state: &mut WriteTraffic<'_, Data>, app_data: &[u8], outgoing: &mut Buffer) { - write_with_buffer_size_checks( - |out_buf| state.encrypt(app_data, out_buf), - map_encrypt_error, - outgoing, - ); -} - -fn map_encrypt_error(e: EncryptError) -> InsufficientSizeError { - if let EncryptError::InsufficientSize(ise) = e { - ise - } else { - unreachable!() - } -} - -fn write_with_buffer_size_checks( - mut try_write: impl FnMut(&mut [u8]) -> Result, - map_err: impl FnOnce(E) -> InsufficientSizeError, - outgoing: &mut Buffer, -) { - let required_size = map_err(try_write(&mut []).unwrap_err()).required_size; - let written = try_write(outgoing.unfilled()).unwrap(); - assert_eq!(required_size, written); - outgoing.advance(written); -} - -#[derive(Default)] -struct BothBuffers { - client: Buffers, - server: Buffers, -} - -impl BothBuffers { - fn client_send(&mut self) { - let client_data = self.client.outgoing.filled(); - let num_bytes = client_data.len(); - if num_bytes == 0 { - return; - } - self.server.incoming.append(client_data); - self.client.outgoing.clear(); - eprintln!("client sent {num_bytes}B"); - } - - fn server_send(&mut self) { - let server_data = self.server.outgoing.filled(); - let num_bytes = server_data.len(); - if num_bytes == 0 { - return; - } - self.client.incoming.append(server_data); - self.server.outgoing.clear(); - eprintln!("server sent {num_bytes}B"); - } -} - -#[derive(Default)] -struct Buffers { - incoming: Buffer, - outgoing: Buffer, -} - -struct Buffer { - inner: Vec, - used: usize, -} - -impl Default for Buffer { - fn default() -> Self { - Self { - inner: vec![0; 16 * 1024], - used: 0, - } - } -} - -impl Buffer { - fn advance(&mut self, num_bytes: usize) { - self.used += num_bytes; - } - - fn append(&mut self, bytes: &[u8]) { - let num_bytes = bytes.len(); - self.unfilled()[..num_bytes].copy_from_slice(bytes); - self.advance(num_bytes) - } - - fn clear(&mut self) { - self.used = 0; - } - - fn discard(&mut self, discard: usize) { - if discard != 0 { - assert!(discard <= self.used); - - self.inner - .copy_within(discard..self.used, 0); - self.used -= discard; - } - } - - fn filled(&mut self) -> &mut [u8] { - &mut self.inner[..self.used] - } - - fn unfilled(&mut self) -> &mut [u8] { - &mut self.inner[self.used..] - } -} - -fn make_connection_pair( - version: &'static rustls::SupportedProtocolVersion, -) -> (UnbufferedClientConnection, UnbufferedServerConnection) { - let server_config = make_server_config(KeyType::Rsa2048); - let client_config = make_client_config_with_versions(KeyType::Rsa2048, &[version]); - - let client = - UnbufferedClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let server = UnbufferedServerConnection::new(Arc::new(server_config)).unwrap(); - (client, server) -} - -#[test] -fn server_receives_handshake_byte_by_byte() { - let (mut client, mut server) = make_connection_pair(&TLS13); - - let mut client_hello_buffer = vec![0u8; 1024]; - let UnbufferedStatus { discard, state } = client.process_tls_records(&mut []); - - assert_eq!(discard, 0); - match state.unwrap() { - ConnectionState::EncodeTlsData(mut inner) => { - let wr = inner - .encode(&mut client_hello_buffer) - .expect("client hello too big"); - client_hello_buffer.truncate(wr); - } - _ => panic!("unexpected first client event"), - }; - - println!("client hello: {:?}", client_hello_buffer); - - for prefix in 0..client_hello_buffer.len() - 1 { - let UnbufferedStatus { discard, state } = - server.process_tls_records(&mut client_hello_buffer[..prefix]); - println!("prefix {prefix:?}: ({discard:?}, {state:?}"); - assert!(matches!(state.unwrap(), ConnectionState::BlockedHandshake)); - } - - let UnbufferedStatus { discard, state } = - server.process_tls_records(&mut client_hello_buffer[..]); - - assert!(matches!(state.unwrap(), ConnectionState::EncodeTlsData(_))); - assert_eq!(client_hello_buffer.len(), discard); -} - -#[test] -fn server_receives_incorrect_first_handshake_message() { - let (_, mut server) = make_connection_pair(&TLS13); - - let mut junk_buffer = [0x16, 0x3, 0x1, 0x0, 0x4, 0xff, 0x0, 0x0, 0x0]; - let junk_buffer_len = junk_buffer.len(); - - let UnbufferedStatus { discard, state } = server.process_tls_records(&mut junk_buffer[..]); - - assert_eq!(discard, junk_buffer_len); - assert_eq!( - format!("{state:?}"), - "Err(InappropriateHandshakeMessage { expect_types: [ClientHello], got_type: HandshakeType(0xff) })" - ); - - let UnbufferedStatus { discard, state } = server.process_tls_records(&mut []); - assert_eq!(discard, 0); - - match state.unwrap() { - ConnectionState::EncodeTlsData(mut inner) => { - let mut alert_buffer = [0u8; 7]; - let wr = inner.encode(&mut alert_buffer).unwrap(); - assert_eq!(wr, 7); - assert_eq!(alert_buffer, &[0x15, 0x3, 0x3, 0x0, 0x2, 0x2, 0xa][..]); - } - _ => panic!("unexpected alert sending state"), - }; -} diff --git a/website/config.toml b/website/config.toml index 568b9dac7f2..12288dad488 100644 --- a/website/config.toml +++ b/website/config.toml @@ -7,6 +7,11 @@ compile_sass = false # Whether to build a search index to be used later on by a JavaScript library build_search_index = false +generate_feeds = true +feed_filenames = ["atom.xml", "rss.xml"] + +taxonomies = [{ name = "tags", render = false }] + [markdown] # Whether to do syntax highlighting # Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola diff --git a/website/content/blog/2025-09-03-rustls-and-rust-foundation.md b/website/content/blog/2025-09-03-rustls-and-rust-foundation.md new file mode 100644 index 00000000000..f6b6b65567d --- /dev/null +++ b/website/content/blog/2025-09-03-rustls-and-rust-foundation.md @@ -0,0 +1,35 @@ ++++ +title = "Rustls and the Rust Foundation's Rust Innovation Lab" +date = 2025-09-03 +authors = ["Joe Birr-Pixton", "with", "Dirkjan Ochtman, Daniel McCarney and Josh Aas"] + +[taxonomies] +tags = ["blog"] ++++ + +As you may have seen, [Rustls is the first project in the Rust Foundation's new Rust Innovation Lab program](https://rustfoundation.org/media/rust-foundation-launches-rust-innovation-lab-with-rustls-as-inaugural-project/). + +Rustls is a project I started in May 2016 as I learned Rust. Since then it has grown from a casual project, to having multiple maintainers, hundreds of contributors, funded contributions, and finally multiple funded maintainers. + +As a project it has expanded to incorporate adjacent efforts, such as an [OpenSSL-compatible API](https://github.com/rustls/rustls-openssl-compat), a [C API](https://github.com/rustls/rustls-ffi), [deep integration into platform-specific certificate verifiers](https://github.com/rustls/rustls-platform-verifier) and integrations with important Rust ecosystem crates such as [tokio](http://github.com/rustls/tokio-rustls) and [hyper](http://github.com/rustls/hyper-rustls). + +Now it supports a significant amount of the crates ecosystem and applications with *billions* of users. + +Giving the Rustls project an administrative and legal home is the next step in that development. + +Users will see no change in the project's direction or personnel. If you rely on Rustls in a commercial context we would love to talk about how we can address your needs, and how we can work together to support the project long-term. + +We want to thank ISRG for its [significant and ongoing support](https://www.memorysafety.org/initiative/rustls/), and [Sovereign Tech Agency](https://www.sovereign.tech/tech/rustls) for their recent funding of the project + +# Q+A: +## Why do this now? +We want to make it easier for potential funding sources to support the project. In conversations, it became clear that a clear legal and governance status would make the project more attractive for funders. + +## Why the Rust Foundation? +We feel the Rust Foundation shares our goals in promoting the Rust language and serving its users. + +## Does this mean Rustls is being funded by my organization's membership of the Rust Foundation? +No. Only funding specifically for Rustls will be made available to the Rustls project. This does not affect funding to the Rust Project or other initiatives funded by the Rust Foundation. + +## Does this change Rustls' technical direction or personnel? +It does not change that. The existing maintainers have complete and unequivocal control over the Rustls project and project roadmap. diff --git a/website/content/blog/_index.md b/website/content/blog/_index.md new file mode 100644 index 00000000000..b2a2001c2a9 --- /dev/null +++ b/website/content/blog/_index.md @@ -0,0 +1,6 @@ ++++ +title = "All pages" +sort_by = "date" +template = "pages.html" +page_template = "report-page.html" ++++ diff --git a/website/content/perf/2024-10-18-report.md b/website/content/perf/2024-10-18-report.md index 53a86a94bac..6619b677b7e 100644 --- a/website/content/perf/2024-10-18-report.md +++ b/website/content/perf/2024-10-18-report.md @@ -2,8 +2,11 @@ title = "Benchmarking rustls 0.23.15 vs OpenSSL 3.3.2 vs BoringSSL on x86_64" date = 2024-10-18 +[taxonomies] +tags = ["performance"] + [extra] -headlines = true +headlines = false [extra.version] rustls = "rustls 0.23.15" @@ -132,4 +135,3 @@ AVX-512 support shows up twice in these results: This support was contributed to the respective projects by Intel. TLS1.3 resumption is slower than TLS1.2 resumption because it includes a fresh key exchange. - diff --git a/website/content/perf/2024-10-31-arm64.md b/website/content/perf/2024-10-31-arm64.md index 4e490763987..2deb9cba076 100644 --- a/website/content/perf/2024-10-31-arm64.md +++ b/website/content/perf/2024-10-31-arm64.md @@ -1,6 +1,9 @@ +++ title = "Benchmarking rustls 0.23.15 vs OpenSSL 3.3.2 vs BoringSSL on ARM64" date = 2024-10-31 + +[taxonomies] +tags = ["performance"] +++ ### System configuration @@ -135,4 +138,3 @@ AES-256-GCM 13570.29k 168865.38k 623050.41k 1766296.58k 2320760.83k That is 2268, 2267 and 2264 MB/s for BoringSSL, aws-lc and OpenSSL respectively. Given these project's shared lineage, it would not be surprising if the implementations are the same. - diff --git a/website/content/perf/2024-11-28-threading/index.md b/website/content/perf/2024-11-28-threading/index.md index 6248cfa6f8c..ee63bb208df 100644 --- a/website/content/perf/2024-11-28-threading/index.md +++ b/website/content/perf/2024-11-28-threading/index.md @@ -1,6 +1,9 @@ +++ title = "Measuring and Improving rustls's Multithreaded Performance" date = 2024-11-28 + +[taxonomies] +tags = ["performance"] +++ ### System configuration diff --git a/website/content/perf/2024-12-17-pq-kx/index.md b/website/content/perf/2024-12-17-pq-kx/index.md index 29da8f00f4f..dca09b7c73e 100644 --- a/website/content/perf/2024-12-17-pq-kx/index.md +++ b/website/content/perf/2024-12-17-pq-kx/index.md @@ -1,6 +1,9 @@ +++ title = "Measuring and (slightly) Improving Post-Quantum Handshake Performance" date = 2024-12-17 + +[taxonomies] +tags = ["performance"] +++ To defend against the potential advent of "Cryptographically Relevant Quantum Computers" @@ -78,7 +81,7 @@ It's therefore advantageous for a client to avoid `HelloRetryRequest`s, by: [draft-ietf-tls-key-share-prediction]: https://datatracker.ietf.org/doc/draft-ietf-tls-key-share-prediction/ [X25519]: https://datatracker.ietf.org/doc/html/rfc7748 [ML-KEM]: https://csrc.nist.gov/pubs/fips/203/final -[X25519MLKEM768]: https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem/02/ +[X25519MLKEM768]: https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/ [Xeon E-2386G]: https://www.intel.com/content/www/us/en/products/sku/214806/intel-xeon-e2386g-processor-12m-cache-3-50-ghz/specifications.html [draft-ietf-tls-hybrid-design]: https://www.ietf.org/archive/id/draft-ietf-tls-hybrid-design-11.html#name-transmitting-public-keys-an diff --git a/website/content/perf/2025-07-31-report.md b/website/content/perf/2025-07-31-report.md new file mode 100644 index 00000000000..196d278a151 --- /dev/null +++ b/website/content/perf/2025-07-31-report.md @@ -0,0 +1,154 @@ ++++ +title = "Benchmarking rustls 0.23.31 vs OpenSSL 3.5.15 vs BoringSSL on x86_64" +date = 2025-07-31 + +[taxonomies] +tags = ["performance"] + +[extra] +headlines = false + +[extra.version] +rustls = "rustls 0.23.31" +openssl = "OpenSSL 3.5.1" +boringssl = "BoringSSL 0.20250701.0" + +[extra.transfer] +recv.rustls = 7407.83 +recv.openssl = 6472.3 +recv.boringssl = 6421.36 + +send.rustls = 7628.68 +send.openssl = 6093.27 +send.boringssl = 7739.61 + +[extra.handshake] +full.rustls = 2273.13 +full.openssl = 1714.24 +full.boringssl = 1285.88 + +resume.rustls = 6623.94 +resume.openssl = 3771.28 +resume.boringssl = 5803.03 ++++ + +### System configuration + +We ran the benchmarks on a bare-metal server with the following characteristics: + +- OS: Debian 12 (Bookworm). +- C/C++ toolchains: GCC 12.2.0 and Clang 14.0.6. +- CPU: [Xeon E-2386G](https://www.intel.com/content/www/us/en/products/sku/214806/intel-xeon-e2386g-processor-12m-cache-3-50-ghz/specifications.html) (supporting AVX-512). +- Memory: 32GB. +- Extra configuration: hyper-threading disabled, dynamic frequency scaling disabled, cpu scaling + governor set to performance for all cores. + +### Versions +The benchmarking tool used for both OpenSSL and BoringSSL was [openssl-bench 82b86b22](https://github.com/ctz/openssl-bench/tree/82b86b22). + +This was built from source with its makefile. + +#### BoringSSL +The tested version of BoringSSL is [0.20250701.0](https://github.com/google/boringssl/tree/0.20250701.0), which was the most recent point on master +when we started these measurements. + +BoringSSL was built from source with `CC=clang CXX=clang++ cmake -DCMAKE_BUILD_TYPE=Release`. + +#### OpenSSL +The tested version of OpenSSL is [3.5.1](https://github.com/openssl/openssl/tree/openssl-3.5.1), which was the latest release at the time of writing. + +OpenSSL was built from source with `./Configure ; make -j12`. + +#### Rustls +The tested version of rustls is [0.23.31](https://github.com/rustls/rustls/releases/tag/v%2F0.23.31), which was the latest release at the time of writing. +This was used with aws-lc-rs 1.13.1 / aws-lc-sys 0.29.0. + + +### Measurements +BoringSSL was tested with this command: + +```shell +~/bench/openssl-bench +$ BENCH_MULTIPLIER=16 setarch -R make measure BORINGSSL=1 +``` + +OpenSSL was tested with this command: + +```shell +~/bench/openssl-bench +$ BENCH_MULTIPLIER=16 setarch -R make measure +``` + +rustls was tested with this command: + +```shell +~/bench/rustls +$ BENCH_MULTIPLIER=16 setarch -R make -f admin/bench-measure.mk measure +``` + +## Results + +Transfer measurements are in megabytes per second. +Handshake units are handshakes per second. + +| | BoringSSL 0.20250701.0 | OpenSSL 3.5.1 | rustls 0.23.31 | +| -- | -- | -- | -- | +transfer, 1.2, aes-128-gcm, sending | 8575.27 | 6565.22 | 8074.82 | +transfer, 1.2, aes-128-gcm, receiving | 6986.81 | 7219.67 | 7952.68 | +transfer, 1.3, aes-256-gcm, sending | 7739.61 | 6093.27 | 7628.68 | +transfer, 1.3, aes-256-gcm, receiving | 6421.36 | 6472.3 | 7407.83 | +| | BoringSSL 0.20250701.0 | OpenSSL 3.5.1 | rustls 0.23.31 | +full handshakes, 1.2, rsa, client | 5375.06 | 3251.54 | 8206.33 +full handshakes, 1.2, rsa, server | 1447.33 | 2169 | 2857.81 +full handshakes, 1.2, ecdsa, client | 3454.89 | 2195.55 | 4345.05 +full handshakes, 1.2, ecdsa, server | 9096.44 | 5178.02 | 13618.81 +full handshakes, 1.3, rsa, client | 3125.36 | 2222.21 | 4187.28 +full handshakes, 1.3, rsa, server | 1285.88 | 1714.24 | 2273.13 +full handshakes, 1.3, ecdsa, client | 2344.76 | 1650.56 | 2884.83 +full handshakes, 1.3, ecdsa, server | 5113.83 | 3183.26 | 6229.71 +| | BoringSSL 0.20250701.0 | OpenSSL 3.5.1 | rustls 0.23.31 | +resumed handshakes, 1.2, client | 47,509.5 | 19,936.5 | 65,617.35 +resumed handshakes, 1.2, server | 46,561.8 | 21,043.1 | 74,771.51 +resumed handshakes, 1.3, client | 4695.79 | 3574.86 | 5614.4 +resumed handshakes, 1.3, server | 5803.03 | 3771.28 | 6623.94 + +![graph of transfer speeds](/2025-07-31-transfer.svg) + +![graph of full handshakes](/2025-07-31-full-handshake.svg) + +![graph of resumed handshakes](/2025-07-31-resumed-handshake.svg) + + +### Notable changes since [last time](/perf/2024-10-18-report) + +#### Post-quantum key exchange +OpenSSL and rustls now use X25519MLKEM768 post-quantum key exchange by default. +BoringSSL is configured to do the same. This applies to all TLS1.3 handshakes. + +| | old | | new | +| -- | -- | -- | -- | +| | BoringSSL 76968bb3 | âž¡ï¸ | BoringSSL 0.20250701.0 | +full handshakes, 1.3, rsa, client | 4813.91 hs/s | 1.54x slower | 3125.36 hs/s | +| | OpenSSL 3.3.2 | âž¡ï¸ | OpenSSL 3.5.1 | +full handshakes, 1.3, rsa, client | 2788.76 hs/s | 1.25x slower | 2222.21 hs/s | +| | rustls 0.23.15 | âž¡ï¸ | rustls 0.23.31 | +full handshakes, 1.3, rsa, client | 6803.93 hs/s | 1.62x slower | 4187.28 hs/s | + +#### BoringSSL AVX-512 AES-GCM +BoringSSL now has AVX512-accelerated AES-GCM. Since last time, that looks like: + +| | old | | new | +| -- | -- | -- | -- | +| | BoringSSL 76968bb3 | âž¡ï¸ | BoringSSL 0.20250701.0 | +transfer, 1.2, aes-128-gcm, sending | 5043.04 MB/s | 1.7x faster | 8575.27 MB/s | + +#### rustls extension optimizations +We spent some time improving our internal representation for TLS extensions. This applied +to clients and servers, and all TLS versions. But it's most visible here in TLS1.2 +performance because there aren't any cryptography changes masking it. + +| | old | | new | +| -- | -- | -- | -- | +| | rustls 0.23.15 | âž¡ï¸ | rustls 0.23.31 | +resumed handshakes, 1.2, client | 64,722.55 hs/s | 1.02x faster | 65,617.35 hs/s | +resumed handshakes, 1.2, server | 71,149.91 hs/s | 1.05x faster | 74,771.51 hs/s | diff --git a/website/content/perf/2026-03-07-report.md b/website/content/perf/2026-03-07-report.md new file mode 100644 index 00000000000..e3ec861040c --- /dev/null +++ b/website/content/perf/2026-03-07-report.md @@ -0,0 +1,120 @@ ++++ +title = "Benchmarking rustls 0.23.37 vs OpenSSL 3.6.1 vs BoringSSL on x86_64" +date = 2026-03-07 + +[taxonomies] +tags = ["performance"] + +[extra] +headlines = true + +[extra.version] +rustls = "rustls 0.23.37" +openssl = "OpenSSL 3.6.1" +boringssl = "BoringSSL 30cd935" + +[extra.transfer] +recv.rustls = 7332.91 +recv.openssl = 6237.52 +recv.boringssl = 6217.68 + +send.rustls = 7421.02 +send.openssl = 5844.35 +send.boringssl = 7564.71 + +[extra.handshake] +full.rustls = 2357.36 +full.openssl = 1712.64 +full.boringssl = 1302.03 + +resume.rustls = 7249.08 +resume.openssl = 3780 +resume.boringssl = 5687.32 ++++ + +### System configuration + +We ran the benchmarks on a bare-metal server with the following characteristics: + +- OS: Debian 12 (Bookworm). +- C/C++ toolchains: GCC 12.2.0 and Clang 14.0.6. +- Rust toolchain: 1.94.0 +- CPU: [Xeon E-2386G](https://www.intel.com/content/www/us/en/products/sku/214806/intel-xeon-e2386g-processor-12m-cache-3-50-ghz/specifications.html) (supporting AVX-512). +- Memory: 32GB. +- Extra configuration: hyper-threading disabled, dynamic frequency scaling disabled, cpu scaling + governor set to performance for all cores. + +### Versions +The benchmarking tool used for both OpenSSL and BoringSSL was [openssl-bench 82b86b22](https://github.com/ctz/openssl-bench/tree/82b86b22). + +This was built from source with its makefile. + +#### BoringSSL +The tested version of BoringSSL is [30cd935](https://github.com/google/boringssl/tree/30cd935), which was the most recent point on main +when we started these measurements. + +BoringSSL was built from source with `CC=clang CXX=clang++ cmake -DCMAKE_BUILD_TYPE=Release`. + +#### OpenSSL +The tested version of OpenSSL is [3.6.1](https://github.com/openssl/openssl/tree/openssl-3.6.1), which was the latest release at the time of writing. + +OpenSSL was built from source with `./Configure ; make -j12`. + +#### Rustls +The tested version of rustls is [0.23.37](https://github.com/rustls/rustls/releases/tag/v%2F0.23.37), which was the latest stable release at the time of writing. +This was used with aws-lc-rs 1.16.0 / aws-lc-sys 0.37.1. + + +### Measurements +BoringSSL was tested with this command: + +```shell +~/bench/openssl-bench +$ BENCH_MULTIPLIER=16 setarch -R make measure BORINGSSL=1 +``` + +OpenSSL was tested with this command: + +```shell +~/bench/openssl-bench +$ BENCH_MULTIPLIER=16 setarch -R make measure +``` + +rustls was tested with this command: + +```shell +~/bench/rustls +$ BENCH_MULTIPLIER=16 setarch -R make -f admin/bench-measure.mk measure +``` + +## Results + +Transfer measurements are in megabytes per second. +Handshake units are handshakes per second. + +| | BoringSSL 30cd935 | OpenSSL 3.6.1 | rustls 0.23.37 | +| -- | -- | -- | -- | +transfer, 1.2, aes-128-gcm, sending | 8291.9 | 6610.19 | 8133.85 | +transfer, 1.2, aes-128-gcm, receiving | 6722.26 | 7129.43 | 7946.96 | +transfer, 1.3, aes-256-gcm, sending | 7564.71 | 5844.35 | 7421.02 | +transfer, 1.3, aes-256-gcm, receiving | 6217.68 | 6237.52 | 7332.91 | +| | BoringSSL 30cd935 | OpenSSL 3.6.1 | rustls 0.23.37 | +full handshakes, 1.2, rsa, client | 5657.81 | 3186.87 | 8122.99 +full handshakes, 1.2, rsa, server | 1476.66 | 2154.41 | 2835.45 +full handshakes, 1.2, ecdsa, client | 3545.32 | 2201.16 | 4344.30 +full handshakes, 1.2, ecdsa, server | 9057.57 | 5226.96 | 13,524.99 +full handshakes, 1.3, rsa, client | 3179.48 | 2219.64 | 4766.63 +full handshakes, 1.3, rsa, server | 1302.03 | 1712.64 | 2357.36 +full handshakes, 1.3, ecdsa, client | 2364.8 | 1642.88 | 3160.39 +full handshakes, 1.3, ecdsa, server | 4981.38 | 3176.58 | 6786.26 +| | BoringSSL 30cd935 | OpenSSL 3.6.1 | rustls 0.23.37 | +resumed handshakes, 1.2, client | 45,390.3 | 21,136.9 | 63,870.34 +resumed handshakes, 1.2, server | 44,429 | 22,647.1 | 72,480.52 +resumed handshakes, 1.3, client | 4648.41 | 3594.88 | 6735.88 +resumed handshakes, 1.3, server | 5687.32 | 3780 | 7249.08 + +![graph of transfer speeds](/2026-03-07-transfer.svg) + +![graph of full handshakes](/2026-03-07-full-handshake.svg) + +![graph of resumed handshakes](/2026-03-07-resumed-handshake.svg) diff --git a/website/content/release/_index.md b/website/content/release/_index.md new file mode 100644 index 00000000000..b2a2001c2a9 --- /dev/null +++ b/website/content/release/_index.md @@ -0,0 +1,6 @@ ++++ +title = "All pages" +sort_by = "date" +template = "pages.html" +page_template = "report-page.html" ++++ diff --git a/website/static/2025-07-31-full-handshake.svg b/website/static/2025-07-31-full-handshake.svg new file mode 100644 index 00000000000..4e693d3cf26 --- /dev/null +++ b/website/static/2025-07-31-full-handshake.svg @@ -0,0 +1,599 @@ + + + + + + + + 2025-08-04T08:51:49.926885 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 2000 + + + + + + + + + + + + + 4000 + + + + + + + + + + + + + 6000 + + + + + + + + + + + + + 8000 + + + + + + + + + + + + + 10000 + + + + + + + + + + + + + 12000 + + + + + + + + + + + + + 14000 + + + + full handshakes per second (single core) + + + + + + + + + + + + + + full handshakes, 1.2, rsa, client + + + + + + + + + + full handshakes, 1.2, rsa, server + + + + + + + + + + full handshakes, 1.2, ecdsa, client + + + + + + + + + + full handshakes, 1.2, ecdsa, server + + + + + + + + + + full handshakes, 1.3, rsa, client + + + + + + + + + + full handshakes, 1.3, rsa, server + + + + + + + + + + full handshakes, 1.3, ecdsa, client + + + + + + + + + + full handshakes, 1.3, ecdsa, server + + + + + + + + + + + + + + + + + 5375 + + + 1447 + + + 3454 + + + 9096 + + + 3125 + + + 1285 + + + 2344 + + + 5113 + + + 3251 + + + 2169 + + + 2195 + + + 5178 + + + 2222 + + + 1714 + + + 1650 + + + 3183 + + + 8206 + + + 2857 + + + 4345 + + + 13618 + + + 4187 + + + 2273 + + + 2884 + + + 6229 + + + + + + + + + + BoringSSL 0.20250701.0 + + + + + + OpenSSL 3.5.1 + + + + + + rustls 0.23.31 + + + + + Full handshake speed comparison (handshakes per second) + + + + + + + + diff --git a/website/static/2025-07-31-resumed-handshake.svg b/website/static/2025-07-31-resumed-handshake.svg new file mode 100644 index 00000000000..67d6587cbed --- /dev/null +++ b/website/static/2025-07-31-resumed-handshake.svg @@ -0,0 +1,427 @@ + + + + + + + + 2025-08-04T08:51:49.994856 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 10000 + + + + + + + + + + + + + 20000 + + + + + + + + + + + + + 30000 + + + + + + + + + + + + + 40000 + + + + + + + + + + + + + 50000 + + + + + + + + + + + + + 60000 + + + + + + + + + + + + + 70000 + + + + resumed handshakes per second (single core) + + + + + + + + + + + + + + resumed handshakes, 1.2, client + + + + + + + + + + resumed handshakes, 1.2, server + + + + + + + + + + resumed handshakes, 1.3, client + + + + + + + + + + resumed handshakes, 1.3, server + + + + + + + + + + + + + + + + + 47509 + + + 46561 + + + 4695 + + + 5803 + + + 19936 + + + 21043 + + + 3574 + + + 3771 + + + 65617 + + + 74771 + + + 5614 + + + 6623 + + + + + + + + + + BoringSSL 0.20250701.0 + + + + + + OpenSSL 3.5.1 + + + + + + rustls 0.23.31 + + + + + Resumed handshake speed comparison (handshakes per second) + + + + + + + + diff --git a/website/static/2025-07-31-transfer.svg b/website/static/2025-07-31-transfer.svg new file mode 100644 index 00000000000..861d2e28de7 --- /dev/null +++ b/website/static/2025-07-31-transfer.svg @@ -0,0 +1,382 @@ + + + + + + + + 2025-08-04T08:51:49.855776 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 2000 + + + + + + + + + + + + + 4000 + + + + + + + + + + + + + 6000 + + + + + + + + + + + + + 8000 + + + + megabytes per second (single core) + + + + + + + + + + + + + + transfer, 1.2, aes-128-gcm, sending + + + + + + + + + + transfer, 1.2, aes-128-gcm, receiving + + + + + + + + + + transfer, 1.3, aes-256-gcm, sending + + + + + + + + + + transfer, 1.3, aes-256-gcm, receiving + + + + + + + + + + + + + + + + + 8575 + + + 6986 + + + 7739 + + + 6421 + + + 6565 + + + 7219 + + + 6093 + + + 6472 + + + 8074 + + + 7952 + + + 7628 + + + 7407 + + + + + + + + + + BoringSSL 0.20250701.0 + + + + + + OpenSSL 3.5.1 + + + + + + rustls 0.23.31 + + + + + Bulk throughput speed comparison (megabytes per second) + + + + + + + + diff --git a/website/static/2026-03-07-full-handshake.svg b/website/static/2026-03-07-full-handshake.svg new file mode 100644 index 00000000000..c8904f02263 --- /dev/null +++ b/website/static/2026-03-07-full-handshake.svg @@ -0,0 +1,599 @@ + + + + + + + + 2026-03-07T13:50:57.510425 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 2000 + + + + + + + + + + + + + 4000 + + + + + + + + + + + + + 6000 + + + + + + + + + + + + + 8000 + + + + + + + + + + + + + 10000 + + + + + + + + + + + + + 12000 + + + + + + + + + + + + + 14000 + + + + full handshakes per second (single core) + + + + + + + + + + + + + + full handshakes, 1.2, rsa, client + + + + + + + + + + full handshakes, 1.2, rsa, server + + + + + + + + + + full handshakes, 1.2, ecdsa, client + + + + + + + + + + full handshakes, 1.2, ecdsa, server + + + + + + + + + + full handshakes, 1.3, rsa, client + + + + + + + + + + full handshakes, 1.3, rsa, server + + + + + + + + + + full handshakes, 1.3, ecdsa, client + + + + + + + + + + full handshakes, 1.3, ecdsa, server + + + + + + + + + + + + + + + + + 5657 + + + 1476 + + + 3545 + + + 9057 + + + 3179 + + + 1302 + + + 2364 + + + 4981 + + + 3186 + + + 2154 + + + 2201 + + + 5226 + + + 2219 + + + 1712 + + + 1642 + + + 3176 + + + 8122 + + + 2835 + + + 4344 + + + 13524 + + + 4766 + + + 2357 + + + 3160 + + + 6786 + + + + + + + + + + BoringSSL 30cd935 + + + + + + OpenSSL 3.6.1 + + + + + + rustls 0.23.37 + + + + + Full handshake speed comparison (handshakes per second) + + + + + + + + diff --git a/website/static/2026-03-07-resumed-handshake.svg b/website/static/2026-03-07-resumed-handshake.svg new file mode 100644 index 00000000000..67bf46ab408 --- /dev/null +++ b/website/static/2026-03-07-resumed-handshake.svg @@ -0,0 +1,427 @@ + + + + + + + + 2026-03-07T13:50:57.576300 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 10000 + + + + + + + + + + + + + 20000 + + + + + + + + + + + + + 30000 + + + + + + + + + + + + + 40000 + + + + + + + + + + + + + 50000 + + + + + + + + + + + + + 60000 + + + + + + + + + + + + + 70000 + + + + resumed handshakes per second (single core) + + + + + + + + + + + + + + resumed handshakes, 1.2, client + + + + + + + + + + resumed handshakes, 1.2, server + + + + + + + + + + resumed handshakes, 1.3, client + + + + + + + + + + resumed handshakes, 1.3, server + + + + + + + + + + + + + + + + + 45390 + + + 44429 + + + 4648 + + + 5687 + + + 21136 + + + 22647 + + + 3594 + + + 3780 + + + 63870 + + + 72480 + + + 6735 + + + 7249 + + + + + + + + + + BoringSSL 30cd935 + + + + + + OpenSSL 3.6.1 + + + + + + rustls 0.23.37 + + + + + Resumed handshake speed comparison (handshakes per second) + + + + + + + + diff --git a/website/static/2026-03-07-transfer.svg b/website/static/2026-03-07-transfer.svg new file mode 100644 index 00000000000..645d68cfcd0 --- /dev/null +++ b/website/static/2026-03-07-transfer.svg @@ -0,0 +1,442 @@ + + + + + + + + 2026-03-07T13:50:57.428010 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 1000 + + + + + + + + + + + + + 2000 + + + + + + + + + + + + + 3000 + + + + + + + + + + + + + 4000 + + + + + + + + + + + + + 5000 + + + + + + + + + + + + + 6000 + + + + + + + + + + + + + 7000 + + + + + + + + + + + + + 8000 + + + + megabytes per second (single core) + + + + + + + + + + + + + + transfer, 1.2, aes-128-gcm, sending + + + + + + + + + + transfer, 1.2, aes-128-gcm, receiving + + + + + + + + + + transfer, 1.3, aes-256-gcm, sending + + + + + + + + + + transfer, 1.3, aes-256-gcm, receiving + + + + + + + + + + + + + + + + + 8291 + + + 6722 + + + 7564 + + + 6217 + + + 6610 + + + 7129 + + + 5844 + + + 6237 + + + 8133 + + + 7946 + + + 7421 + + + 7332 + + + + + + + + + + BoringSSL 30cd935 + + + + + + OpenSSL 3.6.1 + + + + + + rustls 0.23.37 + + + + + Bulk throughput speed comparison (megabytes per second) + + + + + + + + diff --git a/website/static/rustls-ferris.png b/website/static/rustls-ferris.png deleted file mode 100644 index be48c92d8e5..00000000000 Binary files a/website/static/rustls-ferris.png and /dev/null differ diff --git a/website/static/rustls-ferris.svg b/website/static/rustls-ferris.svg new file mode 120000 index 00000000000..14db5f753ac --- /dev/null +++ b/website/static/rustls-ferris.svg @@ -0,0 +1 @@ +../../admin/logo/rustls.svg \ No newline at end of file diff --git a/website/static/style.css b/website/static/style.css index de1bf71900e..04df76a29bb 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -100,6 +100,7 @@ section.perf h1 footer { section.perf h2 footer { font-size: 0.45em; font-weight: normal; + padding-top: 0.2em; } footer em { display: block; @@ -175,3 +176,15 @@ div.content img { object-fit: contain; max-width: 100%; } +span.tag { + padding-left: 1em; + padding-right: 1em; + text-transform: uppercase; + font-weight: bold; + font-size: 0.6em; + margin: 0.2em; + color: rgba(255, 255, 255, 0.8); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 5px; + background: rgba(255, 255, 255, 0.1); +} diff --git a/website/templates/base.html b/website/templates/base.html index 938d7484f80..80f4ce93948 100644 --- a/website/templates/base.html +++ b/website/templates/base.html @@ -2,10 +2,13 @@ rustls: {{ page.title | default (value="a modern TLS library written in Rust") }} + + + - + {% block lede %} {% endblock %}

diff --git a/website/templates/index.html b/website/templates/index.html index d35cd9afebd..72202359363 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -2,10 +2,13 @@ rustls: a modern TLS library written in Rust + + + - +
A modern TLS library written in Rust
@@ -40,6 +43,10 @@

Flexible

crates.io
+