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/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/workflows/build.yml b/.github/workflows/build.yml index aa6dca058b3..6180342d446 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,14 +5,14 @@ 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 }} @@ -62,13 +62,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 with: - go-version: "1.22.2" + go-version: "1.24.3" - name: cargo build (debug; default features) run: cargo build --locked @@ -98,6 +98,9 @@ jobs: env: RUST_BACKTRACE: 1 + - name: cargo build (debug; no-std) + run: cargo build --locked --lib -p rustls $(admin/all-features-except std,brotli,read_buf rustls) + - name: cargo build (debug; rustls-provider-example) run: cargo build --locked -p rustls-provider-example @@ -207,7 +210,7 @@ jobs: - name: Install golang toolchain uses: actions/setup-go@v5 with: - go-version: "1.21" + go-version: "1.24" cache: false - name: Run test suite (ring) @@ -247,7 +250,9 @@ jobs: uses: dtolnay/rust-toolchain@nightly - 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 run: | @@ -316,12 +321,14 @@ jobs: 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 @@ -385,11 +392,20 @@ jobs: - 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) + - 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 + - name: Install bindgen-cli + uses: taiki-e/cache-cargo-install-action@v3 + with: + tool: bindgen-cli + - name: Enable bindgen feature 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 + run: cargo add --dev --features bindgen 'aws-lc-sys@>0.20' --package rustls --verbose - run: cross test --package rustls --target ${{ matrix.target }} semver: @@ -432,10 +448,9 @@ jobs: 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 @@ -463,10 +478,7 @@ 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) @@ -485,7 +497,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: @@ -499,13 +511,33 @@ jobs: - 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 + taplo: + name: Taplo + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install rust toolchain + uses: dtolnay/rust-toolchain@stable + - name: Install taplo-cli + uses: taiki-e/cache-cargo-install-action@v3 + with: + tool: taplo-cli + - run: taplo format --check + openssl-tests: name: Run openssl-tests runs-on: ubuntu-latest @@ -521,7 +553,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 +583,9 @@ jobs: run: cargo test --locked -- --include-ignored env: RUST_BACKTRACE: 1 + + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v2 diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index af7f2fea25d..8f5cc258208 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -24,7 +24,7 @@ jobs: dry-run: false language: rust - name: Upload Crash - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 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..f3b28127f2c 100644 --- a/.github/workflows/daily-tests.yml +++ b/.github/workflows/daily-tests.yml @@ -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 @@ -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 @@ -107,9 +107,6 @@ jobs: - 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 @@ -131,6 +128,8 @@ jobs: - 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 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 82f7a17d9ac..caef3cfdf3b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -41,14 +41,14 @@ jobs: # keep features in sync with Cargo.toml `[package.metadata.docs.rs]` section run: cargo doc --locked --features read_buf,ring --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,7 +71,8 @@ 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 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/Cargo.lock b/Cargo.lock index f725115bfa8..54ff476b784 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # 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" @@ -54,9 +39,9 @@ dependencies = [ [[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", ] @@ -84,9 +69,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 +84,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.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8b84b4ea1de2bf1dcd2a759737ddb328fb6695b2a95eb7e44fed67e3406f32" +checksum = "df42c2b01c5e1060b8281f67b4e5fb858260694916a667345a7305cd11e5dbfa" dependencies = [ "asn1_derive", "itoa", @@ -149,9 +135,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 +145,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 +169,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.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +checksum = "cdccf849b54365e3693e9a90ad36e4482b79937e6373ac8e2cf229c985187b21" 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,65 +202,46 @@ 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]] @@ -426,9 +258,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bencher" @@ -438,16 +270,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,15 +285,14 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.95", - "which", + "syn", ] [[package]] name = "bitflags" -version = "2.6.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block-buffer" @@ -474,34 +303,23 @@ dependencies = [ "generic-array", ] -[[package]] -name = "blocking" -version = "1.6.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", -] - [[package]] name = "bogo" version = "0.1.0" dependencies = [ "base64", "env_logger", + "nix", "rustls-post-quantum", + "rustls-webpki", "watfaq-rustls", ] [[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 +328,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 +338,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 +350,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 +362,11 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.7" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -564,9 +383,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chacha20" @@ -643,9 +468,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 +478,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,45 +490,36 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "const-oid" @@ -711,11 +527,22 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core-models" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "657f625ff361906f779745d08375ae3cc9fef87a35fba5f22874cf773010daf4" +dependencies = [ + "hax-lib", + "pastey", + "rand 0.9.4", +] + [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -731,25 +558,22 @@ dependencies = [ [[package]] name = "criterion" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", - "is-terminal", - "itertools 0.10.5", + "itertools 0.13.0", "num-traits", - "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", - "serde_derive", "serde_json", "tinytemplate", "walkdir", @@ -765,6 +589,21 @@ dependencies = [ "itertools 0.10.5", ] +[[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]] name = "crossbeam-deque" version = "0.8.6" @@ -792,9 +631,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" @@ -803,16 +642,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -850,20 +689,20 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "zeroize", @@ -871,9 +710,9 @@ dependencies = [ [[package]] name = "der-parser" -version = "9.0.0" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" dependencies = [ "asn1-rs", "displaydoc", @@ -885,9 +724,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -912,7 +751,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] @@ -937,9 +776,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" @@ -955,7 +794,7 @@ dependencies = [ "group", "hkdf", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -970,14 +809,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[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,73 +824,30 @@ 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" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "ff" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1061,6 +857,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "fnv" version = "1.0.7" @@ -1069,9 +871,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foreign-types" @@ -1090,9 +892,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,72 +907,49 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.5.0" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[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-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" @@ -1184,9 +963,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -1194,38 +973,46 @@ dependencies = [ ] [[package]] -name = "ghash" -version = "0.5.1" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ - "opaque-debug", - "polyval", + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", ] [[package]] -name = "gimli" -version = "0.31.1" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] [[package]] -name = "glob" -version = "0.3.2" +name = "ghash" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] [[package]] -name = "gloo-timers" -version = "0.3.0" +name = "glob" +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" @@ -1234,15 +1021,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] [[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,48 +1046,85 @@ 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", ] [[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" [[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 = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "543f93241d32b3f00569201bfce9d7a93c92c6421b23c77864ac929dc947b9fc" +dependencies = [ + "hax-lib-macros", + "num-bigint", + "num-traits", +] [[package]] -name = "hex" -version = "0.4.3" +name = "hax-lib-macros" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +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 = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hickory-proto" -version = "0.25.0-alpha.4" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d063c0692ee669aa6d261988aa19ca5510f1cc40e4f211024f50c888499a35d7" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" dependencies = [ - "async-recursion", "async-trait", "bytes", "cfg-if", @@ -1314,39 +1138,40 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand", + "rand 0.9.4", + "ring", "rustls", - "thiserror 2.0.10", + "thiserror", "tinyvec", "tokio", "tokio-rustls", "tracing", "url", - "webpki-roots", + "webpki-roots 0.26.11", ] [[package]] name = "hickory-resolver" -version = "0.25.0-alpha.3" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eec9ac701a9b34a77c8ccbe39d589db876290f6bdc6e05ac118fd114cb1ad26" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" dependencies = [ "cfg-if", "futures-util", "hickory-proto", "ipconfig", - "lru-cache", + "moka", "once_cell", "parking_lot", - "rand", + "rand 0.9.4", "resolv-conf", "rustls", "smallvec", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-rustls", "tracing", - "webpki-roots", + "webpki-roots 0.26.11", ] [[package]] @@ -1368,97 +1193,101 @@ dependencies = [ ] [[package]] -name = "home" -version = "0.5.11" +name = "hpke-rs" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "b6ad6a58eb3e0ee30be8bfc7a9770ae98adcfa1d9bc820a5847732ce84f70837" dependencies = [ - "windows-sys 0.59.0", + "hpke-rs-crypto", + "hpke-rs-libcrux", + "hpke-rs-rust-crypto", + "libcrux-sha3", + "log", + "rand_core 0.9.5", + "subtle", + "zeroize", ] [[package]] -name = "hostname" -version = "0.3.1" +name = "hpke-rs-crypto" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +checksum = "0a73a99d9008010d73289f41335a3f6e14fb8c04eaf60e9111b450463b1bbc7f" dependencies = [ - "libc", - "match_cfg", - "winapi", + "rand_core 0.9.5", + "zeroize", ] [[package]] -name = "hpke-rs" -version = "0.2.0" +name = "hpke-rs-libcrux" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11bd4ee27b79fa1820e72ef8489cc729c87299ec3f7f52b8fc8dcb87cb2d485" +checksum = "c0ce6b7e54aebe540faee869c67ee253bede44ea6cb67c6e72c7847d6c59f1df" dependencies = [ "hpke-rs-crypto", - "log", + "libcrux-aead", + "libcrux-ecdh", + "libcrux-hkdf", + "libcrux-kem", + "libcrux-traits", + "rand 0.10.1", + "rand_chacha 0.10.0", + "rand_core 0.10.1", "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" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08d4500baf0aced746723d3515d08212bdb9d941df6d1aca3d46d1619b2a1cf" +checksum = "14b28be6cba9081c7feda2651d51c2a900029798e78b4c1e093e792f4571a870" dependencies = [ "aes-gcm", "chacha20poly1305", "hkdf", "hpke-rs-crypto", + "k256", "p256", "p384", - "rand_chacha", - "rand_core", + "rand 0.8.6", + "rand_chacha 0.3.1", + "rand_core 0.10.1", + "rand_core 0.6.4", "sha2", + "subtle", "x25519-dalek", + "zeroize", ] [[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 +1296,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 +1370,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -1583,57 +1380,49 @@ 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", + "hashbrown 0.17.0", + "serde", + "serde_core", ] [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] [[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" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" - -[[package]] -name = "is-terminal" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", -] +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[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" @@ -1646,9 +1435,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -1664,36 +1453,62 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] -name = "kv-log-macro" -version = "1.0.7" +name = "k256" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ - "log", + "cfg-if", + "elliptic-curve", ] [[package]] @@ -1706,84 +1521,275 @@ dependencies = [ ] [[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.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] -name = "libloading" -version = "0.8.6" +name = "libcrux-aead" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "13297ce29869a5c0edab0378837b0fc5f88bf99a843712d9201c3b1150b3b476" dependencies = [ - "cfg-if", - "windows-targets 0.52.6", + "libcrux-aesgcm", + "libcrux-chacha20poly1305", + "libcrux-secrets", + "libcrux-traits", ] [[package]] -name = "libm" -version = "0.2.11" +name = "libcrux-aesgcm" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "99f2a019dab4097585a7d4f5b9deebe46cd1e628b16a5bc4cb0ce35e1da334e6" +dependencies = [ + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-traits", +] [[package]] -name = "linked-hash-map" -version = "0.5.6" +name = "libcrux-chacha20poly1305" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "cc08d044676af21343b32b988411fa98dbb5cf65a03c9df478ced221bbdfdb1b" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-poly1305", + "libcrux-secrets", + "libcrux-traits", +] [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "libcrux-curve25519" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "bb1e5fd8476a6ed609d24ef42aee5ab6f99f7c65d054f92412da9f499e423299" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-secrets", + "libcrux-traits", +] [[package]] -name = "litemap" -version = "0.7.4" +name = "libcrux-ecdh" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "b65f73ce79337c762eb38bbac91e4c9b9e60cf318e8501b812750c640814d45e" +dependencies = [ + "libcrux-curve25519", + "libcrux-p256", + "rand 0.9.4", +] [[package]] -name = "lock_api" -version = "0.4.12" +name = "libcrux-hacl-rs" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "2637dc87d158e1f1b550fd9b226443e84153fded4de69028d897b534d16d22e6" dependencies = [ - "autocfg", - "scopeguard", + "libcrux-macros", ] [[package]] -name = "log" -version = "0.4.22" +name = "libcrux-hkdf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1a89ca0c89be3a268a921e47105fb7873badf7267f5e3ebf4ea46baedd73ef" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-hmac", + "libcrux-secrets", +] + +[[package]] +name = "libcrux-hmac" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7a242707d65960770bd7e14e4f18a92bdf0b967777dd404887db8d087a643b" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-sha2", +] + +[[package]] +name = "libcrux-intrinsics" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "b1b5db005ff8001e026b73a6842ee81bbef8ec5ff0e1915a67ae65fd2a9fafa5" dependencies = [ - "value-bag", + "core-models", + "hax-lib", ] [[package]] -name = "lru-cache" -version = "0.1.2" +name = "libcrux-kem" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +checksum = "12631592f491d22fd1a176d32b2c6edfb673998fd3987e9d95f8fa79ad2a737b" dependencies = [ - "linked-hash-map", + "libcrux-curve25519", + "libcrux-ecdh", + "libcrux-ml-kem", + "libcrux-p256", + "libcrux-sha3", + "libcrux-traits", + "rand 0.9.4", ] +[[package]] +name = "libcrux-macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd6aa2dcd5be681662001b81d493f1569c6d49a32361f470b0c955465cd0338" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "libcrux-ml-kem" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a14ab3e477de9df6ee1273a114018ff62c4996ca9220070c4e5cb1743f94a67d" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-sha3", + "libcrux-traits", + "rand 0.9.4", + "tls_codec", +] + +[[package]] +name = "libcrux-p256" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4778ba25cb08bb8a96bd100e19ed9aecf78337198fd176036e21042b2dd99bc" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-secrets", + "libcrux-sha2", + "libcrux-traits", +] + +[[package]] +name = "libcrux-platform" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9e21d7ed31a92ac539bd69a8c970b183ee883872d2d19ce27036e24cb8ecc4" +dependencies = [ + "libc", +] + +[[package]] +name = "libcrux-poly1305" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02491808ee5b9db8cb65fad64ae0be812db64beef179d945c00c7787dc7dfcf9" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", +] + +[[package]] +name = "libcrux-secrets" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce650f3041b44ba40d4263852347d007cd2cd9d1cc856a6f6c8b2e10c3fd40b" +dependencies = [ + "hax-lib", +] + +[[package]] +name = "libcrux-sha2" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d253473f259fc74a280c43f29c464f7e374abdf28b4942234dc707f529d4b7" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-traits", +] + +[[package]] +name = "libcrux-sha3" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1ae0b7d0e1cc4793a609fd0ff2ca3b3a3fabae523770c619a3d4bc86417b0d7" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-traits", +] + +[[package]] +name = "libcrux-traits" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e4fa89f3f5e34b47f928b22b1b78395a0d4ec23b1f583db635f128159d65f" +dependencies = [ + "libcrux-secrets", + "rand 0.9.4", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +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 = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "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 +1797,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 +1814,44 @@ 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 = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", "libc", - "log", - "wasi", - "windows-sys 0.52.0", ] [[package]] @@ -1856,26 +1876,25 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.6", "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" @@ -1908,34 +1927,35 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.36.7" +name = "oid-registry" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" dependencies = [ - "memchr", + "asn1-rs", ] [[package]] -name = "oid-registry" -version = "0.7.1" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" dependencies = [ - "asn1-rs", + "critical-section", + "portable-atomic", ] [[package]] -name = "once_cell" -version = "1.20.2" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" -version = "11.1.4" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "opaque-debug" @@ -1945,9 +1965,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" dependencies = [ "bitflags", "cfg-if", @@ -1966,14 +1986,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", @@ -1995,25 +2015,19 @@ dependencies = [ [[package]] name = "p384" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" dependencies = [ "elliptic-curve", "primeorder", ] -[[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 +2035,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,44 +2052,33 @@ 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" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkcs1" @@ -2100,9 +2103,9 @@ dependencies = [ [[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" @@ -2132,21 +2135,6 @@ dependencies = [ "plotters-backend", ] -[[package]] -name = "polling" -version = "3.7.4" -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", -] - [[package]] name = "poly1305" version = "0.8.0" @@ -2170,6 +2158,30 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2178,21 +2190,21 @@ 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 = "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]] @@ -2205,38 +2217,86 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.92" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "unicode-ident", + "proc-macro2", + "quote", ] [[package]] -name = "quick-error" -version = "1.2.3" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +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.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "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 = [ + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -2246,7 +2306,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb" +dependencies = [ + "ppv-lite86", + "rand_core 0.10.1", ] [[package]] @@ -2255,14 +2335,29 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", ] +[[package]] +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 +2365,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 +2375,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 0.18.1", "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 +2410,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,19 +2421,15 @@ 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" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error", -] +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "rfc6979" @@ -2351,24 +2443,23 @@ dependencies = [ [[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" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest", @@ -2377,7 +2468,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sha2", "signature", "spki", @@ -2385,17 +2476,11 @@ dependencies = [ "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.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -2415,28 +2500,15 @@ 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", "subtle", @@ -2449,6 +2521,7 @@ version = "0.1.0" dependencies = [ "clap", "rustls-post-quantum", + "rustls-test", "tikv-jemallocator", "watfaq-rustls", ] @@ -2462,9 +2535,11 @@ dependencies = [ "byteorder", "clap", "crabgrind", - "fxhash", "itertools 0.14.0", "rayon", + "rustc-hash", + "rustls-fuzzing-provider", + "rustls-test", "tikv-jemallocator", "watfaq-rustls", ] @@ -2484,8 +2559,6 @@ dependencies = [ name = "rustls-examples" version = "0.0.1" dependencies = [ - "async-std", - "base64", "clap", "env_logger", "hickory-resolver", @@ -2495,7 +2568,7 @@ dependencies = [ "serde", "tokio", "watfaq-rustls", - "webpki-roots", + "webpki-roots 1.0.7", ] [[package]] @@ -2521,19 +2594,22 @@ dependencies = [ [[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-post-quantum" -version = "0.2.1" +version = "0.2.2" dependencies = [ "aws-lc-rs", "criterion", "env_logger", "watfaq-rustls", - "webpki-roots", + "webpki-roots 1.0.7", ] [[package]] @@ -2550,14 +2626,13 @@ dependencies = [ "hpke-rs-rust-crypto", "p256", "pkcs8", - "rand_core", + "rand_core 0.6.4", "rcgen", "rsa", - "rustls-webpki", "sha2", "signature", "watfaq-rustls", - "webpki-roots", + "webpki-roots 1.0.7", "x25519-dalek", ] @@ -2572,29 +2647,30 @@ dependencies = [ "watfaq-rustls", ] +[[package]] +name = "rustls-test" +version = "0.1.0" +dependencies = [ + "watfaq-rustls", +] + [[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" - -[[package]] -name = "ryu" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" @@ -2627,47 +2703,58 @@ dependencies = [ [[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 = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] name = "serde_json" -version = "1.0.135" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -2687,32 +2774,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[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", + "windows-sys 0.61.2", ] [[package]] @@ -2733,9 +2817,9 @@ dependencies = [ [[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" @@ -2751,20 +2835,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.95" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -2773,60 +2846,46 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", ] [[package]] -name = "thiserror" -version = "1.0.69" +name = "tagptr" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "thiserror" -version = "2.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ac7f54ca534db81081ef1c1e7f6ea8a3ef428d2fc069097c079443d24124d3" -dependencies = [ - "thiserror-impl 2.0.10", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" +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", @@ -2834,9 +2893,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", @@ -2844,30 +2903,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", @@ -2875,9 +2934,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", @@ -2895,9 +2954,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", ] @@ -2908,38 +2967,58 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tls_codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[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", "tokio", @@ -2947,9 +3026,9 @@ dependencies = [ [[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", @@ -2960,46 +3039,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" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +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" @@ -3011,6 +3084,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -3019,21 +3098,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" @@ -3047,10 +3121,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" @@ -3076,53 +3155,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.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" 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.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3130,26 +3202,63 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" 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.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] [[package]] name = "watfaq-rustls" -version = "0.23.21" +version = "0.23.40" dependencies = [ "aws-lc-rs", "base64", @@ -3157,7 +3266,7 @@ dependencies = [ "brotli", "brotli-decompressor", "env_logger", - "hashbrown", + "hashbrown 0.15.5", "hex", "log", "macro_rules_attribute", @@ -3166,24 +3275,25 @@ dependencies = [ "rcgen", "ring", "rustls-pki-types", + "rustls-test", "rustls-webpki", "rustversion", "serde", "serde_json", "subtle", "time", - "webpki-roots", + "webpki-roots 1.0.7", "x25519-dalek", - "x509-parser", + "x509-parser 0.17.0", "zeroize", "zlib-rs", ] [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -3191,69 +3301,70 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.7" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "rustls-pki-types", + "webpki-roots 1.0.7", ] [[package]] -name = "which" -version = "4.4.2" +name = "webpki-roots" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ - "either", - "home", - "once_cell", - "rustix", + "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" -version = "0.3.9" +name = "winapi-util" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-sys 0.61.2", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "winapi-util" -version = "0.1.9" +name = "windows-registry" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-sys 0.59.0", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-targets 0.48.5", + "windows-link", ] [[package]] @@ -3262,31 +3373,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -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]] @@ -3295,46 +3391,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" @@ -3349,73 +3427,127 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_i686_msvc" +name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_gnu" +name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] [[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" +name = "wit-bindgen" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "wit-bindgen-core" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "wit-bindgen-rust" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] [[package]] -name = "winreg" -version = "0.50.0" +name = "wit-bindgen-rust-macro" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", ] [[package]] -name = "write16" -version = "1.0.0" +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "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 = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "x25519-dalek" @@ -3424,25 +3556,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", - "rand_core", + "rand_core 0.6.4", "serde", "zeroize", ] [[package]] name = "x509-parser" -version = "0.16.0" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "x509-parser" +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", ] @@ -3457,11 +3607,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", @@ -3469,83 +3618,93 @@ 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" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.95", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "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", @@ -3554,17 +3713,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 bd3e1e0bd38..932acd2b5b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ members = [ "provider-example", # the main library and tests "rustls", + # common code for testing the core crate + "rustls-test", # benchmarking tool "rustls-bench", # experimental post-quantum algorithm support @@ -31,6 +33,7 @@ exclude = [ ] default-members = [ + # --- "examples", "rustls", ] @@ -38,29 +41,28 @@ resolver = "2" [workspace.dependencies] anyhow = "1.0.73" -asn1 = "0.20" -async-std = { version = "1.12.0", features = ["attributes"] } +asn1 = "0.22" 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 +criterion = "0.6" der = "0.7" ecdsa = "0.16.8" env_logger = "0.11" -fxhash = "0.2.1" hashbrown = { version = "0.15", 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"] } +hickory-resolver = { version = "0.25", features = ["https-aws-lc-rs", "webpki-roots"] } hmac = "0.12" -hpke-rs = "0.2" -hpke-rs-crypto = "0.2" -hpke-rs-rust-crypto = "0.2" +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" @@ -70,13 +72,17 @@ once_cell = { version = "1.16", default-features = false, features = ["alloc", " 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"] } +pki-types = { package = "rustls-pki-types", version = "1.12", features = ["alloc"] } rand_core = { version = "0.6", features = ["getrandom"] } rayon = "1.7" -rcgen = { version = "0.13", features = ["pem", "aws_lc_rs"], default-features = false } +rcgen = { version = "0.14", 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-graviola = { version = "0.2" } +rustls-test = { path = "rustls-test/" } +rustls-fuzzing-provider = { path = "rustls-fuzzing-provider/" } serde = { version = "1", features = ["derive"] } serde_json = "1" sha2 = { version = "0.10", default-features = false } @@ -84,14 +90,19 @@ 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" +tokio = { version = "1.34", features = ["io-util", "macros", "net", "rt"] } +webpki = { package = "rustls-webpki", version = "0.103.5", features = ["alloc"], default-features = false } +webpki-roots = "1" x25519-dalek = { version = "2", features = ["static_secrets"] } -x509-parser = "0.16" -zeroize = "1.7" -zlib-rs = "0.4" +x509-parser = "0.17" +zeroize = "1.8" +zlib-rs = "0.6" [profile.bench] codegen-units = 1 lto = true + +# ensure all our tests are against the local copy, never +# against the latest _published_ copy. +[patch.crates-io] +watfaq-rustls = { path = "rustls" } diff --git a/README.md b/README.md index 2488264fce6..5b8f012443b 100644 --- a/README.md +++ b/README.md @@ -55,10 +55,10 @@ can replace all cryptography dependencies of rustls. This is a route to being p 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 +Specifying `default-features = false` when depending on rustls will remove the implicit 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. +Rustls requires Rust 1.71 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 @@ -78,10 +78,10 @@ builder types. See the [`crypto::CryptoProvider`] documentation for more details #### Built-in providers -Rustls ships with two built-in providers controlled with associated feature flags: +Rustls ships with two built-in providers controlled by associated crate features: -* [`aws-lc-rs`] - enabled by default, available with the `aws_lc_rs` feature flag enabled. -* [`ring`] - available with the `ring` feature flag enabled. +* [`aws-lc-rs`] - enabled by default, available with the `aws_lc_rs` crate feature enabled. +* [`ring`] - available with the `ring` crate feature enabled. See the documentation for [`crypto::CryptoProvider`] for details on how providers are selected. @@ -90,17 +90,18 @@ selected. 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-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-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,20 +112,18 @@ 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. +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/ +[custom provider example]: 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 @@ -184,7 +183,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..417af4ca5ed 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.85. That means +> our MSRV could be as recent as 1.76. As it happens, it is 1.71. - 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,7 +55,7 @@ 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 diff --git a/admin/coverage b/admin/coverage index 61360e42284..3ede3b233a1 100755 --- a/admin/coverage +++ b/admin/coverage @@ -17,10 +17,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)/" "$@" diff --git a/bogo/Cargo.toml b/bogo/Cargo.toml index ef7173d38a5..2804d667525 100644 --- a/bogo/Cargo.toml +++ b/bogo/Cargo.toml @@ -2,12 +2,15 @@ name = "bogo" version = "0.1.0" edition = "2021" +publish = false [dependencies] base64 = { workspace = true } env_logger = { workspace = true } +nix = { version = "0.30", default-features = false, features = ["signal"] } watfaq-rustls = { path = "../rustls", features = ["aws_lc_rs", "ring", "tls12"] } rustls-post-quantum = { path = "../rustls-post-quantum", optional = true } +webpki = { workspace = true } [features] default = [] diff --git a/bogo/config.json.in b/bogo/config.json.in index 2586a6f303c..1fa69242e88 100644 --- a/bogo/config.json.in +++ b/bogo/config.json.in @@ -1,29 +1,30 @@ { "DisabledTests": { - "SendV2ClientHello-*": "only support TLS1.2", - "*SSL3*": "", - "*TLS1-*": "", - "*-TLS1": "", - "*TLS11-*": "", - "*-TLS11": "", - "ConflictingVersionNegotiation": "", - "CertificateSelection-*": "TODO", + "SendV2ClientHello-*": "we only support >=TLS1.2", + "*SSL3*": "we only support >=TLS1.2", + "*TLS1-*": "we only support >=TLS1.2", + "*-TLS1": "we only support >=TLS1.2", + "*TLS11-*": "we only support >=TLS1.2", + "*-TLS11": "we only support >=TLS1.2", + "CertificateSelection-*TrustAnchorIDs-*": "no support for trust anchor IDs draft", + "CertificateSelection-*ClientCertificateTypes-*": "no support for CertificateRequest::certificate_types", + "CertificateSelection-Client-SignatureAlgorithmECDSACurve-TLS-TLS12": "ResolvesClientCert does not receive protocol version", + "CertificateSelection-Server-*": "TODO certificate selection for servers", + "ConflictingVersionNegotiation": "expects to negotiate TLS1.1", "SendFallbackSCSV": "fallback scsv not implemented", "ECDSAKeyUsage-*": "TODO: we don't do anything with key usages", "CheckRecordVersion-*": "we don't look at record version", "*DTLS*": "not supported", "MTU*": "dtls only", "DisableEverything": "not useful", - "CheckLeafCurve": "", - "SendWarningAlerts-*": "", - "Peek-*": "", - "EchoTLS13CompatibilitySessionID": "", - "ClientOCSPCallback*": "ocsp not supported yet", - "ServerOCSPCallback*": "", + "CheckLeafCurve": "we don't check this in TLS1.2 (note CheckLeafCurve-TLS13 works)", + "Peek-*": "no SSL_peek or equivalent", + "ServerOCSPCallback*": "ocsp not supported for servers", "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", #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 @@ -40,118 +41,127 @@ "TLS-ECH-Client-Reject-NoClientCertificate-TLS12-Async": "we disable TLS1.2 w/ ECH", "TLS-ECH-Client-Reject-ResumeInnerSession-TLS13": "assumes no outer GREASE PSK, we send GREASE PSK", #endif - "ALPS-*": "", - "NPN-*": "", - "NoCheckClientCertificateTypes": "we don't have this check", - "CheckClientCertificateTypes": "^", + "ALPS-*": "no ALPS support yet", + "NPN-*": "no NPN support, never", + "NoCheckClientCertificateTypes": "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", - "NoCheckECDSACurve-TLS12": "^", - "CheckClientCertificateTypes": "^", + "NoCheckECDSACurve-TLS12": "we don't have this check", + "CheckClientCertificateTypes": "we don't check TLS1.2 CertificateRequest.certificate_types", "*-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": "^", + "MLKEMKeyShareIncludedSecond-MLKEM1024": "we only include a share for the first configured group", + "MLKEMKeyShareIncludedThird": "we only include a share for the first configured group", + "MLKEMKeyShareIncludedThird-MLKEM1024": "we only include a share for the first configured group", #else "*MLKEM*": "not implemented", +#endif +#if defined(AWS_LC_RS) + "PostQuantumNotEnabledByDefaultInClients": "it is", + "PostQuantumNotEnabledByDefaultForAServer": "it is", #endif "*Kyber*": "old draft, not implemented", "ExtraClientEncryptedExtension-*": "we don't implement ALPS", "Server-JDK11*": "workarounds for oracle engineering quality", - "Client-RejectJDK11DowngradeRandom": "", + "Client-RejectJDK11DowngradeRandom": "workarounds for oracle engineering quality", "SendUnsolicitedSCTOnCertificate-TLS13": "SCT stapling not supported", - "SignedCertificateTimestampListEmpty-Client-*": "", - "SignedCertificateTimestampListEmptySCT-Client-*": "", - "SendSCTListOnResume-TLS-TLS12": "", + "SignedCertificateTimestampListEmpty-Client-*": "no sct stapling", + "SignedCertificateTimestampListEmptySCT-Client-*": "no sct stapling", + "SendSCTListOnResume-TLS-TLS12": "no sct stapling", "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_*": "", + "CBCRecordSplitting*": "no cbc suites", + "*CBCPadding*": "no cbc suites", + "RSAEphemeralKey": "no rsa kem", + "BadRSAClientKeyExchange-*": "no rsa kem", + "SendClientVersion-RSA": "no rsa kem", + "Basic-Server-RSA-*": "no rsa kem", + "RSAKeyUsage-*": "no rsa kem", + "*-RSA_WITH_3DES_EDE_CBC_SHA-*": "no cbc suites", + "*_WITH_AES_128_CBC_*": "no cbc suites", + "*_WITH_AES_256_CBC_*": "no cbc suites", "*-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": "", + "VerifyPreferences-Enforced": "we validate but don't actually implement -verify-prefs", "*_P224_*": "no p224", - "*-P-224-*": "", + "*-P-224-*": "no p224", #ifdef RING "*-ECDSA_P521_SHA512-*": "no p521 signatures/verification", #endif "CurveTest-*-P-521-*": "no p521 key exchange", "GREASE-*": "not implemented", - "LargeMessage-Reject": "", + "LargeMessage-Reject": "not supported as a runtime option (cert size limit is compile-time constant)", "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": "", + "*Auth-SHA1-Fallback*": "no sha1 support, so no meaningful need to assume client supports it", + "RSA-PSS-Large": "no support for 1024-bit RSA keys", + "*-RSA_WITH_AES_128_GCM_SHA256-*": "no rsa kem", + "*-RSA_WITH_AES_256_GCM_SHA384-*": "no rsa kem", + "*-RSA_WITH_AES_128_CBC_SHA-*": "no rsa kem", + "*-RSA_WITH_AES_256_CBC_SHA-*": "no rsa kem", + "OmitExtensions-ClientHello-TLS12": "we require signature algorithms ext", + "EmptyExtensions-ClientHello-TLS12": "we require signature algorithms ext", "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-*": "", + "Renegotiate-Client-*": "no renegotiation", + "Shutdown-Shim-Renegotiate-*": "no renegotiation", + "Shutdown-Shim-HelloRequest-*": "no renegotiation", "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-*": "", + "Renegotiate-Server-*": "no renegotiation", + "Renegotiate-ForbidAfterHandshake": "no renegotiation", + "RetainOnlySHA256-*": "test for SSL_CTX_set_retain_only_sha256_of_client_certs api", + "ExtendedMasterSecret-Renego-*": "no renegotiation", + "ALPN*SelectEmpty-*": "our API design does not allow for this", "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": "", + "QUICTransportParams-*": "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?", + "PAKE-*": "no PAKE draft support", + "TrustAnchors-*": "no TrustAnchor draft support", + "TLS13-Client-*TicketFlags*": "no flags extension support (on tickets or elsewhere)", "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-Ed25519-TLS12": "ed25519 accepted by default", + "Client-VerifyDefault-Ed25519-TLS13": "ed25519 accepted by default", + "Client-VerifyDefault-Ed25519-TLS12": "ed25519 accepted by default", "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": "", + "Server-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-ECDSA_P521_SHA512-TLS12": "p521-sha512 accepted by default (where supported)", "*-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-Server-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-Server-Ed25519": "fips approved in aws-lc-rs", #endif - "*-QUIC-*" :"", - "QUIC-*": "", - "*-QUIC": "" + "*-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?", + "*-QUIC": "quic mode does not work over TLS1.3 framing -- do this in the shim?" }, "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:" + ":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:"] }, "TestErrorMap": { + "AppDataBeforeTLS13KeyChange": ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:", + "AppDataBeforeTLS13KeyChange-Empty": ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:", "EmptyCertificateList": ":NO_CERTS:", "SendInvalidRecordType": ":GARBAGE:", "NoSharedCipher": ":HANDSHAKE_FAILURE:", @@ -187,13 +197,10 @@ "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:", + "ALPNClient-EmptyProtocolName-TLS-TLS12": ":ILLEGAL_EMPTY_VALUE:", + "ALPNClient-EmptyProtocolName-TLS-TLS13": ":ILLEGAL_EMPTY_VALUE:", + "ALPNServer-EmptyProtocolName-TLS-TLS12": ":ILLEGAL_EMPTY_VALUE:", + "ALPNServer-EmptyProtocolName-TLS-TLS13": ":ILLEGAL_EMPTY_VALUE:", "UnofferedExtension-Client": ":PEER_MISBEHAVIOUR:", "UnknownExtension-Client": ":PEER_MISBEHAVIOUR:", "KeyUpdate-InvalidRequestMode": ":BAD_HANDSHAKE_MSG:", @@ -202,7 +209,7 @@ "NoNullCompression-TLS13": ":INCOMPATIBLE:", "InvalidCompressionMethod": ":PEER_MISBEHAVIOUR:", "TLS13-InvalidCompressionMethod": ":PEER_MISBEHAVIOUR:", - "TLS13-WrongOuterRecord": ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:", + "TLS13-WrongOuterRecord-TLS": ":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:", @@ -245,17 +252,13 @@ "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:", @@ -268,8 +271,6 @@ "TrailingKeyShareData-TLS13": ":BAD_HANDSHAKE_MSG:", "HelloRetryRequestCurveMismatch-TLS13": ":PEER_MISBEHAVIOUR:", "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:", @@ -280,39 +281,13 @@ "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:", + "HelloRetryRequest-EmptyCookie-TLS13": ":ILLEGAL_EMPTY_VALUE:", "UnsupportedCurve-ServerHello-TLS13": ":PEER_MISBEHAVIOUR:", - "PartialClientKeyExchangeWithClientHello": ":PEER_MISBEHAVIOUR:", "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:", @@ -325,8 +300,6 @@ "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:", @@ -338,9 +311,8 @@ "ServerCipherFilter-RSA": ":INCOMPATIBLE:", "ServerCipherFilter-ECDSA": ":INCOMPATIBLE:", "ServerCipherFilter-Ed25519": ":INCOMPATIBLE:", - "TLS13-OnlyPadding": ":PEER_MISBEHAVIOUR:", - "TLS13-EmptyRecords": ":PEER_MISBEHAVIOUR:", - "TLS13-DuplicateTicketEarlyDataSupport": ":PEER_MISBEHAVIOUR:", + "TLS13-OnlyPadding-TLS": ":PEER_MISBEHAVIOUR:", + "TLS13-EmptyRecords-TLS": ":PEER_MISBEHAVIOUR:", "SupportedVersionSelection-TLS12": ":PEER_MISBEHAVIOUR:", "HelloRetryRequest-CipherChange-TLS13": ":PEER_MISBEHAVIOUR:", "CurveTest-Client-Compressed-P-256-TLS12": ":PEER_MISBEHAVIOUR:", @@ -355,10 +327,9 @@ "ExtendedMasterSecret-YesToNo-Server": ":PEER_MISBEHAVIOUR:", "ExtendedMasterSecret-YesToNo-Client": ":PEER_MISBEHAVIOUR:", "ServerAcceptsEarlyDataOnHRR-Client-TLS13": ":PEER_MISBEHAVIOUR:", - "Downgrade-TLS12-Client": ":PEER_MISBEHAVIOUR:", + "Downgrade-TLS12-Client-TLS": ":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:", @@ -366,12 +337,12 @@ "EarlyData-CipherMismatch-Client-TLS13": ":PEER_MISBEHAVIOUR:", "EarlyDataWithoutResume-Client-TLS13": ":PEER_MISBEHAVIOUR:", "EarlyDataVersionDowngrade-Client-TLS13": ":PEER_MISBEHAVIOUR:", + "EarlyDataVersionDowngrade-Client-TLS13-WarningAlert": ":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:", "SkipEarlyData-HRR-FatalAlert-TLS13": ":HANDSHAKE_FAILURE:", - "SkipEarlyData-HRR-Interleaved-TLS13": ":PEER_MISBEHAVIOUR:", "SkipEarlyData-HRR-TooMuchData-TLS13": ":UNEXPECTED_MESSAGE:", "SkipEarlyData-SecondClientHelloEarlyData-TLS13": ":PEER_MISBEHAVIOUR:" }, @@ -384,7 +355,21 @@ "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", - "SkipEarlyData-SecondClientHelloEarlyData-TLS13": "remote error: illegal parameter" + "SkipEarlyData-SecondClientHelloEarlyData-TLS13": "remote error: illegal parameter", + "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", + "_": "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": "" }, "HalfRTTTickets": 0 } diff --git a/bogo/fetch-and-build b/bogo/fetch-and-build index 67e2214abb8..928e701199f 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=92b3d4e221b4b4690dc9c3f8ada7e92df843e987 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..795451494bb 100755 --- a/bogo/runme +++ b/bogo/runme @@ -38,10 +38,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 b7d2dcd64af..7fe1dd5ca00 100644 --- a/bogo/src/main.rs +++ b/bogo/src/main.rs @@ -4,9 +4,24 @@ // https://boringssl.googlesource.com/boringssl/+/master/ssl/test // -use std::fmt::{Debug, Formatter}; +#![warn( + clippy::alloc_instead_of_core, + clippy::manual_let_else, + clippy::std_instead_of_core, + clippy::use_self, + clippy::upper_case_acronyms, + elided_lifetimes_in_paths, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unused_import_braces, + unused_extern_crates, + unused_qualifications +)] + +use core::fmt::{Debug, Formatter}; 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}; @@ -15,7 +30,7 @@ use watfaq_rustls::client::danger::{ }; use watfaq_rustls::client::{ ClientConfig, ClientConnection, EchConfig, EchGreaseConfig, EchMode, EchStatus, Resumption, - WebPkiServerVerifier, + Tls12Resumption, WebPkiServerVerifier, }; use watfaq_rustls::crypto::aws_lc_rs::hpke; use watfaq_rustls::crypto::hpke::{Hpke, HpkePublicKey}; @@ -69,8 +84,7 @@ struct Options { host_name: String, use_sni: bool, trusted_cert_file: String, - key_file: String, - cert_file: String, + credentials: Credentials, protocols: Vec, reject_alpn: bool, support_tls13: bool, @@ -78,12 +92,12 @@ struct Options { 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, + export_traffic_secrets: bool, read_size: usize, quic_transport_params: Vec, expect_quic_transport_params: Vec, @@ -111,12 +125,14 @@ struct Options { expect_curve_id: Option, on_initial_expect_curve_id: Option, on_resume_expect_curve_id: Option, + wait_for_debugger: bool, + ocsp: OcspValidation, } impl Options { fn new() -> Self { let selected_provider = SelectedProvider::from_env(); - Options { + Self { port: 0, shim_id: 0, side: Side::Client, @@ -138,8 +154,7 @@ impl Options { root_hint_subjects: vec![], offer_no_client_cas: false, trusted_cert_file: "".to_string(), - key_file: "".to_string(), - cert_file: "".to_string(), + credentials: Credentials::default(), protocols: vec![], reject_alpn: false, support_tls13: true, @@ -147,12 +162,12 @@ impl Options { 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, + export_traffic_secrets: false, read_size: 512, quic_transport_params: vec![], expect_quic_transport_params: vec![], @@ -180,6 +195,8 @@ impl Options { expect_curve_id: None, on_initial_expect_curve_id: None, on_resume_expect_curve_id: None, + wait_for_debugger: false, + ocsp: OcspValidation::default(), } } @@ -211,6 +228,53 @@ impl Options { } } +#[derive(Debug, Default)] +struct Credentials { + default: Credential, + additional: Vec, + /// Some(-1) means `default`, otherwise index into `additional` + expect_selected: Option, +} + +impl Credentials { + 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) -> (Vec>, PrivateKeyDer<'static>) { + 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(); + (certs, key) + } + + fn configured(&self) -> bool { + !self.cert_file.is_empty() && !self.key_file.is_empty() + } +} + #[derive(Clone, Copy, Debug, PartialEq)] enum SelectedProvider { AwsLcRs, @@ -245,7 +309,7 @@ impl SelectedProvider { // 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(), + kx_groups: aws_lc_rs::DEFAULT_KX_GROUPS.to_vec(), cipher_suites: aws_lc_rs::ALL_CIPHER_SUITES.to_vec(), ..aws_lc_rs::default_provider() } @@ -312,7 +376,7 @@ 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)) + .inspect(|x| println!("item {x:?}")) .collect() } @@ -394,11 +458,12 @@ impl ClientCertVerifier for DummyClientAuth { #[derive(Debug)] struct DummyServerAuth { parent: Arc, + ocsp: OcspValidation, } impl DummyServerAuth { - fn new(trusted_cert_file: &str) -> Self { - DummyServerAuth { + fn new(trusted_cert_file: &str, ocsp: OcspValidation) -> Self { + Self { parent: WebPkiServerVerifier::builder_with_provider( load_root_certs(trusted_cert_file), SelectedProvider::from_env() @@ -407,6 +472,7 @@ impl DummyServerAuth { ) .build() .unwrap(), + ocsp, } } } @@ -420,6 +486,9 @@ impl ServerCertVerifier for DummyServerAuth { _ocsp: &[u8], _now: UnixTime, ) -> Result { + if let OcspValidation::Reject = self.ocsp { + return Err(CertificateError::InvalidOcspResponse.into()); + } Ok(ServerCertVerified::assertion()) } @@ -448,6 +517,16 @@ impl ServerCertVerifier for DummyServerAuth { } } +#[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: Arc, @@ -474,7 +553,7 @@ struct FixedSignatureSchemeServerCertResolver { } impl server::ResolvesServerCert for FixedSignatureSchemeServerCertResolver { - fn resolve(&self, client_hello: ClientHello) -> Option> { + 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(), @@ -484,33 +563,111 @@ impl server::ResolvesServerCert for FixedSignatureSchemeServerCertResolver { } } -#[derive(Debug)] -struct FixedSignatureSchemeClientCertResolver { - resolver: Arc, - scheme: SignatureScheme, +#[derive(Debug, Default)] +struct MultipleClientCredentialResolver { + additional: Vec, + default: Option, + expect_selected: Option, +} + +impl MultipleClientCredentialResolver { + fn add(&mut self, key: sign::CertifiedKey, meta: &Credential) { + self.additional + .push(ClientCert::new(key, meta)); + } + + fn set_default(&mut self, key: sign::CertifiedKey, meta: &Credential) { + self.default = Some(ClientCert::new(key, meta)); + } } -impl client::ResolvesClientCert for FixedSignatureSchemeClientCertResolver { +impl client::ResolvesClientCert for MultipleClientCredentialResolver { fn resolve( &self, root_hint_subjects: &[&[u8]], - sigschemes: &[SignatureScheme], + sig_schemes: &[SignatureScheme], ) -> Option> { - if !sigschemes.contains(&self.scheme) { - quit(":NO_COMMON_SIGNATURE_ALGORITHMS:"); + // `sig_schemes` is in server preference order, so respect that. + 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 + && !root_hint_subjects + .iter() + .any(|dn| *dn == cert.issuer_dn.as_ref()) + { + continue; + } + + if cert + .certkey + .key + .choose_scheme(&[sig_scheme]) + .is_some() + { + assert!( + Some(i as isize) == self.expect_selected || self.expect_selected.is_none() + ); + return Some(cert.certkey.clone()); + } + } } - 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) + + if let Some(cert) = &self.default { + if cert + .certkey + .key + .choose_scheme(sig_schemes) + .is_some() + { + assert!(matches!(self.expect_selected, Some(-1) | None)); + return Some(cert.certkey.clone()); + } + } + + 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 has_certs(&self) -> bool { - self.resolver.has_certs() + self.default.is_some() || !self.additional.is_empty() + } +} + +#[derive(Debug)] +struct ClientCert { + certkey: Arc, + issuer_dn: DistinguishedName, + must_match_issuer: bool, +} + +impl ClientCert { + fn new(mut certkey: sign::CertifiedKey, meta: &Credential) -> Self { + let parsed_cert = webpki::EndEntityCert::try_from(certkey.cert.last().unwrap()).unwrap(); + let issuer_dn = DistinguishedName::in_sequence(parsed_cert.issuer()); + + if let Some(scheme) = meta.use_signing_scheme { + certkey.key = Arc::new(FixedSignatureSchemeSigningKey { + key: certkey.key, + scheme: lookup_scheme(scheme), + }); + } + + Self { + certkey: Arc::new(certkey), + issuer_dn, + must_match_issuer: meta.must_match_issuer, + } } } @@ -594,7 +751,7 @@ impl server::StoresServerSessions for ServerCacheWithResumptionDelay { } } -fn make_server_cfg(opts: &Options) -> Arc { +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( @@ -603,14 +760,15 @@ fn make_server_cfg(opts: &Options) -> Arc { opts.root_hint_subjects.clone(), )) } else { - server::WebPkiClientVerifier::no_client_auth() + WebPkiClientVerifier::no_client_auth() }; - 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(); + assert!( + opts.credentials.additional.is_empty(), + "TODO: server certificate switching not implemented yet" + ); + let cred = &opts.credentials.default; + let (certs, key) = cred.load_from_file(); let mut provider = opts.provider.clone(); @@ -624,7 +782,7 @@ fn make_server_cfg(opts: &Options) -> Arc { .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()) + .with_single_cert_with_ocsp(certs, key, opts.server_ocsp_response.clone()) .unwrap(); cfg.session_storage = ServerCacheWithResumptionDelay::new(opts.resumption_delay); @@ -632,9 +790,12 @@ fn make_server_cfg(opts: &Options) -> Arc { cfg.send_tls13_tickets = 1; cfg.require_ems = opts.require_ems; cfg.ignore_client_order = opts.server_preference; + if opts.export_traffic_secrets { + cfg.key_log = key_log.clone(); + } - if opts.use_signing_scheme > 0 { - let scheme = lookup_scheme(opts.use_signing_scheme); + if let Some(scheme) = cred.use_signing_scheme { + let scheme = lookup_scheme(scheme); cfg.cert_resolver = Arc::new(FixedSignatureSchemeServerCertResolver { resolver: cfg.cert_resolver.clone(), scheme, @@ -688,8 +849,8 @@ struct ClientCacheWithoutKxHints { } impl ClientCacheWithoutKxHints { - fn new(delay: u32) -> Arc { - Arc::new(ClientCacheWithoutKxHints { + fn new(delay: u32) -> Arc { + Arc::new(Self { delay, storage: Arc::new(client::ClientSessionMemoryCache::new(32)), }) @@ -744,7 +905,7 @@ impl client::ClientSessionStore for ClientCacheWithoutKxHints { } impl Debug for ClientCacheWithoutKxHints { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + 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) @@ -752,7 +913,7 @@ impl Debug for ClientCacheWithoutKxHints { } } -fn make_client_cfg(opts: &Options) -> Arc { +fn make_client_cfg(opts: &Options, key_log: &Arc) -> Arc { let mut provider = opts.provider.clone(); if let Some(groups) = &opts.groups { @@ -761,7 +922,8 @@ fn make_client_cfg(opts: &Options) -> Arc { .retain(|kxg| groups.contains(&kxg.name())); } - let cfg = ClientConfig::builder_with_provider(provider.into()); + let provider = Arc::new(provider); + let cfg = ClientConfig::builder_with_provider(provider.clone()); let cfg = if opts.selected_provider.supports_ech() { if let Some(ech_config_list) = &opts.ech_config_list { @@ -790,32 +952,55 @@ fn make_client_cfg(opts: &Options) -> Arc { let cfg = cfg .dangerous() - .with_custom_certificate_verifier(Arc::new(DummyServerAuth::new(&opts.trusted_cert_file))); + .with_custom_certificate_verifier(Arc::new(DummyServerAuth::new( + &opts.trusted_cert_file, + opts.ocsp, + ))); - 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() + 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; + let (certs, key) = cred.load_from_file(); + let key = provider + .key_provider + .load_private_key(key) + .expect("cannot load private key"); + + resolver.set_default(sign::CertifiedKey::new(certs, key), cred) + } + + for cred in opts.credentials.additional.iter() { + let (certs, key) = cred.load_from_file(); + let key = provider + .key_provider + .load_private_key(key) + .expect("cannot load private key"); + + resolver.add(sign::CertifiedKey::new(certs, key), cred); + } + + cfg.with_client_cert_resolver(Arc::new(resolver)) + } + false => cfg.with_no_client_auth(), }; - 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)) + .tls12_resumption(match opts.tickets { + true => Tls12Resumption::SessionIdOrTickets, + false => Tls12Resumption::SessionIdOnly, }); - } - - 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.export_traffic_secrets { + cfg.key_log = key_log.clone(); + } if !opts.protocols.is_empty() { cfg.alpn_protocols = opts @@ -857,8 +1042,7 @@ fn quit_err(why: &str) -> ! { } fn handle_err(opts: &Options, err: Error) -> ! { - println!("TLS error: {:?}", err); - thread::sleep(time::Duration::from_millis(100)); + println!("TLS error: {err:?}"); match err { Error::InappropriateHandshakeMessage { .. } | Error::InappropriateMessage { .. } => { @@ -879,6 +1063,10 @@ fn handle_err(opts: &Options, err: Error) -> ! { Error::InvalidMessage( InvalidMessage::TrailingData("ChangeCipherSpecPayload") | InvalidMessage::InvalidCcs, ) => quit(":BAD_CHANGE_CIPHER_SPEC:"), + Error::InvalidMessage( + InvalidMessage::EmptyTicketValue | InvalidMessage::IllegalEmptyList(_), + ) => quit(":DECODE_ERROR:"), + Error::InvalidMessage(InvalidMessage::IllegalEmptyValue) => quit(":ILLEGAL_EMPTY_VALUE:"), Error::InvalidMessage( InvalidMessage::InvalidKeyUpdate | InvalidMessage::MissingData(_) @@ -899,7 +1087,17 @@ fn handle_err(opts: &Options, err: Error) -> ! { { 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:") } @@ -947,6 +1145,9 @@ fn handle_err(opts: &Options, err: Error) -> ! { Error::PeerMisbehaved(PeerMisbehaved::TooManyKeyUpdateRequests) => { quit(":TOO_MANY_KEY_UPDATES:") } + Error::PeerMisbehaved(PeerMisbehaved::ServerEchoedCompatibilitySessionId) => { + quit(":SERVER_ECHOED_INVALID_SESSION_ID:") + } Error::PeerMisbehaved(PeerMisbehaved::TooManyEmptyFragments) => { quit(":TOO_MANY_EMPTY_FRAGMENTS:") } @@ -965,6 +1166,12 @@ fn handle_err(opts: &Options, err: Error) -> ! { } Error::PeerMisbehaved(PeerMisbehaved::SelectedUnofferedKxGroup) => 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(_) => quit(":PEER_MISBEHAVIOUR:"), Error::NoCertificatesPresented => quit(":NO_CERTS:"), Error::AlertReceived(AlertDescription::UnexpectedMessage) => quit(":BAD_ALERT:"), @@ -975,7 +1182,17 @@ fn handle_err(opts: &Options, err: Error) -> ! { quit(":CANNOT_PARSE_LEAF_CERT:") } Error::InvalidCertificate(CertificateError::BadSignature) => quit(":BAD_SIGNATURE:"), - Error::InvalidCertificate(e) => quit(&format!(":BAD_CERT: ({:?})", e)), + #[allow(deprecated)] + Error::InvalidCertificate( + CertificateError::UnsupportedSignatureAlgorithm + | CertificateError::UnsupportedSignatureAlgorithmContext { .. } + | CertificateError::UnsupportedSignatureAlgorithmForPublicKeyContext { .. }, + ) => 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:"), _ => { println_err!("unhandled error: {:?}", err); @@ -987,7 +1204,7 @@ fn handle_err(opts: &Options, err: Error) -> ! { 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); + println!("IO error: {err:?}"); process::exit(0); } } @@ -1010,20 +1227,35 @@ const MAX_MESSAGE_SIZE: usize = 0xffff + 5; 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 */ + orderly_close(conn); handle_err(opts, err); } } +fn orderly_close(conn: &mut net::TcpStream) { + // assuming we just flush()'d, we will write no more. + conn.shutdown(net::Shutdown::Write) + .unwrap(); + + // wait for EOF + let mut buf = [0u8; 32]; + while let Ok(p @ 1..) = conn.peek(&mut buf) { + let _ = conn.read(&mut buf[..p]).unwrap(); + } + + let _ = conn.shutdown(net::Shutdown::Read); +} + 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); + println!("read {count:?} bytes"); sess.read_tls(&mut io::Cursor::new(&mut bytes[..count])) .expect("read_tls not expected to fail reading from buffer"); } - Err(ref err) if err.kind() == io::ErrorKind::ConnectionReset => {} - Err(err) => panic!("invalid read: {}", err), + Err(err) if err.kind() == io::ErrorKind::ConnectionReset => {} + Err(err) => panic!("invalid read: {err}"), }; after_read(opts, sess, conn); @@ -1032,14 +1264,14 @@ fn read_n_bytes(opts: &Options, sess: &mut Connection, conn: &mut net::TcpStream 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), + Err(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) { +fn exec(opts: &Options, mut sess: Connection, key_log: &KeyLogMemo, count: usize) { let mut sent_message = false; let addrs = [ @@ -1093,7 +1325,7 @@ fn exec(opts: &Options, mut sess: Connection, count: usize) { } if opts.side == Side::Server && opts.enable_early_data { - if let Some(ref mut ed) = server(&mut sess).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) @@ -1131,6 +1363,24 @@ fn exec(opts: &Options, mut sess: Connection, count: usize) { sent_exporter = true; } + 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; + } + if opts.send_key_update && !sent_key_update && !sess.is_handshaking() { sess.refresh_traffic_keys().unwrap(); sent_key_update = true; @@ -1254,7 +1504,7 @@ fn exec(opts: &Options, mut sess: Connection, count: usize) { println!("EOF (tcp)"); return; } - Err(err) => panic!("unhandled read error {:?}", err), + Err(err) => panic!("unhandled read error {err:?}"), }; if opts.shut_down_after_handshake && !sent_shutdown && !sess.is_handshaking() { @@ -1263,7 +1513,7 @@ fn exec(opts: &Options, mut sess: Connection, count: usize) { } if quench_writes && len > 0 { - println!("unquenching writes after {:?}", len); + println!("unquenching writes after {len:?}"); quench_writes = false; } @@ -1287,7 +1537,7 @@ pub fn main() { println!("No"); process::exit(0); } - println!("options: {:?}", args); + println!("options: {args:?}"); let mut opts = Options::new(); @@ -1304,10 +1554,16 @@ pub fn main() { opts.side = Side::Server; } "-key-file" => { - opts.key_file = args.remove(0); + opts.credentials.last_mut().key_file = args.remove(0); + } + "-new-x509-credential" => { + opts.credentials.additional.push(Credential::default()); + } + "-expect-selected-credential" => { + opts.credentials.expect_selected = args.remove(0).parse::().ok(); } "-cert-file" => { - opts.cert_file = args.remove(0); + opts.credentials.last_mut().cert_file = args.remove(0); } "-trust-cert" => { opts.trusted_cert_file = args.remove(0); @@ -1340,7 +1596,7 @@ pub fn main() { "-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); } } @@ -1352,7 +1608,10 @@ pub fn main() { } "-signing-prefs" => { let alg = args.remove(0).parse::().unwrap(); - opts.use_signing_scheme = alg; + opts.credentials.last_mut().use_signing_scheme = Some(alg); + } + "-must-match-issuer" => { + opts.credentials.last_mut().must_match_issuer = true; } "-use-client-ca-list" => { match args.remove(0).as_ref() { @@ -1411,7 +1670,7 @@ 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" => { @@ -1441,6 +1700,9 @@ pub fn main() { "-use-export-context" => { opts.export_keying_material_context_used = true; } + "-export-traffic-secrets" => { + opts.export_traffic_secrets = true; + } "-quic-transport-params" => { opts.quic_transport_params = BASE64_STANDARD.decode(args.remove(0).as_bytes()) .expect("invalid base64"); @@ -1526,7 +1788,7 @@ pub fn main() { opts.expect_reject_early_data = true; } _ => { - println!("NYI early data reason: {}", reason); + println!("NYI early data reason: {reason}"); process::exit(1); } } @@ -1604,6 +1866,19 @@ pub fn main() { "-server-preference" => { opts.server_preference = true; } + "-fail-ocsp-callback" => { + opts.ocsp = OcspValidation::Reject; + } + "-wait-for-debugger" => { + #[cfg(windows)] + { + panic("-wait-for-debugger not supported on Windows"); + } + #[cfg(unix)] + { + opts.wait_for_debugger = true; + } + } // defaults: "-enable-all-curves" | @@ -1614,9 +1889,11 @@ pub fn main() { "-handoff" | "-ipv6" | "-decline-alpn" | + "-permute-extensions" | "-expect-no-session" | "-expect-ticket-renewal" | "-enable-ocsp-stapling" | + "-use-ocsp-callback" | "-forbid-renegotiation-after-handshake" | // internal openssl details: "-async" | @@ -1673,11 +1950,14 @@ pub fn main() { "-wpa-202304" | "-cnsa-202407" | "-srtp-profiles" | - "-permute-extensions" | + "-use-ticket-aead-callback" | "-signed-cert-timestamps" | "-on-initial-expect-peer-cert-file" | + "-resumption-across-names-enabled" | + "-expect-resumable-across-names" | + "-expect-not-resumable-across-names" | "-use-custom-verify-callback" => { - println!("NYI option {:?}", arg); + println!("NYI option {arg:?}"); process::exit(BOGO_NACK); } @@ -1689,17 +1969,27 @@ pub fn main() { } _ => { - println!("unhandled option {:?}", arg); + println!("unhandled option {arg:?}"); process::exit(1); } } } - println!("opts {:?}", opts); + println!("opts {opts:?}"); + + #[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(); + } + + let key_log = Arc::new(KeyLogMemo::default()); 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))), + Side::Client => (Some(make_client_cfg(&opts, &key_log)), None), + Side::Server => (None, Some(make_server_cfg(&opts, &key_log))), }; fn make_session( @@ -1708,12 +1998,13 @@ pub fn main() { ccfg: &Option>, ) -> Connection { assert!(opts.quic_transport_params.is_empty()); - assert!(opts - .expect_quic_transport_params - .is_empty()); + assert!( + opts.expect_quic_transport_params + .is_empty() + ); if opts.side == Side::Server { - let scfg = Arc::clone(scfg.as_ref().unwrap()); + let scfg = scfg.as_ref().cloned().unwrap(); ServerConnection::new(scfg) .unwrap() .into() @@ -1721,7 +2012,7 @@ pub fn main() { let server_name = ServerName::try_from(opts.host_name.as_str()) .unwrap() .to_owned(); - let ccfg = Arc::clone(ccfg.as_ref().unwrap()); + let ccfg = ccfg.as_ref().cloned().unwrap(); ClientConnection::new(ccfg, server_name) .unwrap() @@ -1731,22 +2022,65 @@ pub fn main() { for i in 0..opts.resumes + 1 { let sess = make_session(&opts, &server_cfg, &client_cfg); - exec(&opts, sess, i); + exec(&opts, sess, &key_log, i); if opts.resume_with_tickets_disabled { opts.tickets = false; - server_cfg = Some(make_server_cfg(&opts)); + + match opts.side { + Side::Server => server_cfg = Some(make_server_cfg(&opts, &key_log)), + Side::Client => client_cfg = Some(make_client_cfg(&opts, &key_log)), + }; } 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)); + client_cfg = Some(make_client_cfg(&opts, &key_log)); } opts.expect_handshake_kind .clone_from(&opts.expect_handshake_kind_resumed); } } +#[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() + } + _ => {} + } + } + + 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 CompressionAlgs { None, diff --git a/ci-bench/Cargo.toml b/ci-bench/Cargo.toml index d25c10fe5e3..8b7fdef7133 100644 --- a/ci-bench/Cargo.toml +++ b/ci-bench/Cargo.toml @@ -11,10 +11,12 @@ anyhow = { workspace = true } async-trait = { workspace = true } byteorder = { workspace = true } clap = { workspace = true } -fxhash = { workspace = true } itertools = { workspace = true } rayon = { workspace = true } watfaq-rustls = { path = "../rustls", features = ["ring", "aws_lc_rs"] } +rustc-hash = { workspace = true } +rustls-test = { workspace = true } +rustls-fuzzing-provider = { workspace = true } [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemallocator = { workspace = true } diff --git a/ci-bench/src/benchmark.rs b/ci-bench/src/benchmark.rs index 506dcd14ade..e02ace60d35 100644 --- a/ci-bench/src/benchmark.rs +++ b/ci-bench/src/benchmark.rs @@ -1,18 +1,18 @@ use std::sync::Arc; -use fxhash::FxHashMap; use itertools::Itertools; +use rustc_hash::FxHashMap; +use rustls_test::KeyType; -use crate::callgrind::InstructionCounts; -use crate::util::KeyType; use crate::Side; +use crate::callgrind::InstructionCounts; /// 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<()> { +pub(crate) fn validate_benchmarks(benchmarks: &[Benchmark]) -> anyhow::Result<()> { // Detect duplicate definitions let duplicate_names: Vec<_> = benchmarks .iter() @@ -30,7 +30,7 @@ pub fn validate_benchmarks(benchmarks: &[Benchmark]) -> anyhow::Result<()> { } /// 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 { @@ -50,8 +50,8 @@ impl BenchmarkKind { /// Returns the [`ResumptionKind`] used in the handshake part of the benchmark pub fn resumption_kind(self) -> ResumptionKind { match self { - BenchmarkKind::Handshake(kind) => kind, - BenchmarkKind::Transfer => ResumptionKind::No, + Self::Handshake(kind) => kind, + Self::Transfer => ResumptionKind::No, } } } @@ -68,7 +68,7 @@ pub enum ResumptionKind { } impl ResumptionKind { - pub const ALL: &'static [ResumptionKind] = &[Self::No, Self::SessionId, Self::Tickets]; + pub 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 { @@ -95,6 +95,8 @@ pub struct BenchmarkParams { pub version: &'static watfaq_rustls::SupportedProtocolVersion, /// 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 { @@ -106,18 +108,26 @@ impl BenchmarkParams { ciphersuite: watfaq_rustls::SupportedCipherSuite, version: &'static watfaq_rustls::SupportedProtocolVersion, label: String, + warm_up: Option, ) -> Self { Self { provider, ticketer, - key_type, + auth_key, ciphersuite, version, label, + warm_up, } } } +#[derive(Clone, Debug)] +pub enum AuthKeySource { + KeyType(KeyType), + FuzzingProvider, +} + /// A benchmark specification pub struct Benchmark { /// The name of the benchmark, as shown in the benchmark results diff --git a/ci-bench/src/callgrind.rs b/ci-bench/src/callgrind.rs index 2dba776762d..085c8f96cdb 100644 --- a/ci-bench/src/callgrind.rs +++ b/ci-bench/src/callgrind.rs @@ -1,19 +1,19 @@ +use core::ops::Sub; 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; +use crate::benchmark::Benchmark; /// The subdirectory in which the callgrind output should be stored const CALLGRIND_OUTPUT_SUBDIR: &str = "callgrind"; /// A callgrind-based benchmark runner -pub struct CallgrindRunner { +pub(crate) struct CallgrindRunner { /// The path to the ci-bench executable /// /// This is necessary because the callgrind runner works by spawning child processes @@ -24,21 +24,21 @@ pub struct CallgrindRunner { impl CallgrindRunner { /// Returns a new callgrind-based benchmark runner - pub fn new(executable: String, output_dir: PathBuf) -> anyhow::Result { + pub(crate) 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 { + Ok(Self { 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( + pub(crate) fn run_bench( &self, benchmark_index: u32, bench: &Benchmark, @@ -89,7 +89,10 @@ impl CallgrindRunner { if status.success() { Ok(()) } else { - anyhow::bail!("Failed to launch callgrind. Error: {}. Please ensure that valgrind is installed and on the $PATH.", status) + anyhow::bail!( + "Failed to launch callgrind. Error: {}. Please ensure that valgrind is installed and on the $PATH.", + status + ) } } } @@ -191,16 +194,16 @@ fn parse_callgrind_output(file: &Path) -> anyhow::Result { /// The instruction counts, for each side, after running a benchmark #[derive(Copy, Clone)] -pub struct InstructionCounts { +pub(crate) struct InstructionCounts { pub client: u64, pub server: u64, } impl Sub for InstructionCounts { - type Output = InstructionCounts; + type Output = Self; fn sub(self, rhs: Self) -> Self::Output { - InstructionCounts { + Self { client: self.client - rhs.client, server: self.server - rhs.server, } @@ -208,7 +211,7 @@ impl Sub for InstructionCounts { } /// Returns the detailed instruction diff between the baseline and the candidate -pub fn diff(baseline: &Path, candidate: &Path, scenario: &str) -> anyhow::Result { +pub(crate) 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") @@ -276,7 +279,7 @@ impl CountInstructions { pub(crate) fn start() -> Self { #[cfg(target_os = "linux")] crabgrind::callgrind::toggle_collect(); - CountInstructions + Self } } diff --git a/ci-bench/src/main.rs b/ci-bench/src/main.rs index 89c4285bb70..cccc24d5e83 100644 --- a/ci-bench/src/main.rs +++ b/ci-bench/src/main.rs @@ -1,16 +1,31 @@ +#![warn( + clippy::alloc_instead_of_core, + clippy::manual_let_else, + clippy::std_instead_of_core, + clippy::use_self, + clippy::upper_case_acronyms, + elided_lifetimes_in_paths, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unused_import_braces, + unused_extern_crates, + unused_qualifications +)] + +use core::hint::black_box; +use core::mem; use std::collections::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; use std::time::Instant; use crate::benchmark::{ - get_reported_instr_count, validate_benchmarks, Benchmark, BenchmarkKind, BenchmarkParams, - ResumptionKind, + AuthKeySource, Benchmark, BenchmarkKind, BenchmarkParams, ResumptionKind, + get_reported_instr_count, validate_benchmarks, }; use crate::callgrind::{CallgrindRunner, CountInstructions}; use crate::util::async_io::{self, AsyncRead, AsyncWrite}; @@ -105,8 +120,8 @@ 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", } } } @@ -138,6 +153,10 @@ fn main() -> anyhow::Result<()> { .get(index as usize) .ok_or(anyhow::anyhow!("Benchmark not found: {index}"))?; + if let Some(warm_up) = bench.params.warm_up { + warm_up(); + } + let stdin_lock = io::stdin().lock(); let stdout_lock = io::stdout().lock(); @@ -298,18 +317,21 @@ fn all_benchmarks() -> anyhow::Result> { fn all_benchmarks_params() -> Vec { let mut all = Vec::new(); - for (provider, suites, ticketer, provider_name) in [ + for (provider, suites, ticketer, provider_name, warm_up) in [ ( derandomize(ring::default_provider()), ring::ALL_CIPHER_SUITES, &(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), "aws_lc_rs", + #[expect(trivial_casts)] + Some(warm_up_aws_lc_rs as fn()), ), ] { for (key_type, suite_name, version, name) in [ @@ -359,14 +381,39 @@ fn all_benchmarks_params() -> Vec { all.push(BenchmarkParams::new( provider.clone(), ticketer, - key_type, + AuthKeySource::KeyType(key_type), find_suite(suites, suite_name), version, format!("{provider_name}_{name}"), + warm_up, )); } } + #[allow(trivial_casts)] // false positive + let make_ticketer = &((|| Arc::new(rustls_fuzzing_provider::Ticketer)) + as fn() -> Arc); + + all.push(BenchmarkParams::new( + rustls_fuzzing_provider::provider(), + make_ticketer, + AuthKeySource::FuzzingProvider, + rustls_fuzzing_provider::TLS13_FUZZING_SUITE, + &rustls::version::TLS13, + "1.3_no_crypto".to_string(), + None, + )); + + all.push(BenchmarkParams::new( + rustls_fuzzing_provider::provider(), + make_ticketer, + AuthKeySource::FuzzingProvider, + rustls_fuzzing_provider::TLS_FUZZING_SUITE, + &rustls::version::TLS12, + "1.2_no_crypto".to_string(), + None, + )); + all } @@ -394,6 +441,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. + aws_lc_rs::default_provider() + .secure_random + .fill(&mut [0u8]) + .unwrap(); +} + #[derive(Debug)] struct NotRandom; @@ -440,6 +497,12 @@ pub fn run_all( output_dir: PathBuf, 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 @@ -504,14 +567,8 @@ 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( + let cfg = ClientConfig::builder_with_provider( CryptoProvider { cipher_suites: vec![params.ciphersuite], ..params.provider.clone() @@ -519,9 +576,24 @@ impl ClientSideStepper<'_> { .into(), ) .with_protocol_versions(&[params.version]) - .unwrap() - .with_root_certificates(root_store) - .with_no_client_auth(); + .unwrap(); + + 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() + } + + AuthKeySource::FuzzingProvider => cfg + .dangerous() + .with_custom_certificate_verifier(rustls_fuzzing_provider::server_verifier()) + .with_no_client_auth(), + }; if resume != ResumptionKind::No { cfg.resumption = Resumption::in_memory_sessions(128); @@ -591,12 +663,20 @@ 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()) + let 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?"); + .unwrap(); + + 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.get_chain(), key_type.get_key()) + .expect("bad certs/private key?"), + + AuthKeySource::FuzzingProvider => cfg + .with_client_cert_verifier(WebPkiClientVerifier::no_client_auth()) + .with_cert_resolver(rustls_fuzzing_provider::server_cert_resolver()), + }; if resume == ResumptionKind::SessionId { cfg.session_storage = ServerSessionMemoryCache::new(128); @@ -788,7 +868,9 @@ fn print_report(result: &CompareResult) { 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}"); diff --git a/ci-bench/src/util.rs b/ci-bench/src/util.rs index 83d762a343a..5a78155e499 100644 --- a/ci-bench/src/util.rs +++ b/ci-bench/src/util.rs @@ -33,14 +33,15 @@ pub 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 +53,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 +76,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 +132,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 +170,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 +187,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, } @@ -380,7 +381,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 @@ -408,7 +409,7 @@ 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( + pub(crate) async fn send_handshake_message( conn: &mut ConnectionCommon, writer: &mut dyn AsyncWrite, buf: &mut [u8], @@ -446,7 +447,7 @@ pub mod transport { /// /// Used in combination with [`send_handshake_message`] (see that function's documentation for /// more details). - pub async fn read_handshake_message( + pub(crate) async fn read_handshake_message( conn: &mut ConnectionCommon, reader: &mut dyn AsyncRead, buf: &mut [u8], @@ -482,7 +483,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 { @@ -534,7 +535,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/connect-tests/Cargo.toml b/connect-tests/Cargo.toml index 9d8bc8bdba2..16686902587 100644 --- a/connect-tests/Cargo.toml +++ b/connect-tests/Cargo.toml @@ -7,7 +7,7 @@ description = "Rustls connectivity based integration tests." publish = false [dependencies] -watfaq-rustls = { path = "../rustls", features = [ "logging" ]} +rustls = { package = "watfaq-rustls", path = "../rustls", features = ["logging"] } [dev-dependencies] hickory-resolver = { workspace = true } diff --git a/connect-tests/tests/badssl.rs b/connect-tests/tests/badssl.rs index a3671e532e3..4b9210ef0cd 100644 --- a/connect-tests/tests/badssl.rs +++ b/connect-tests/tests/badssl.rs @@ -15,7 +15,7 @@ mod online { fn no_cbc() { connect("cbc.badssl.com") .fails() - .expect(r"TLS error: AlertReceived\(HandshakeFailure\)") + .expect(r"TLS error: received fatal alert: HandshakeFailure") .go() .unwrap(); } @@ -24,7 +24,7 @@ mod online { fn no_rc4() { connect("rc4.badssl.com") .fails() - .expect(r"TLS error: AlertReceived\(HandshakeFailure\)") + .expect(r"TLS error: received fatal alert: HandshakeFailure") .go() .unwrap(); } @@ -33,7 +33,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 +42,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 +51,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 +60,7 @@ mod online { fn no_dh() { connect("dh2048.badssl.com") .fails() - .expect(r"TLS error: AlertReceived\(HandshakeFailure\)") + .expect(r"TLS error: received fatal alert: HandshakeFailure") .go() .unwrap(); } @@ -101,7 +101,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 +119,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..148482f784e 100644 --- a/connect-tests/tests/common/mod.rs +++ b/connect-tests/tests/common/mod.rs @@ -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 index 0f809a61cbb..750a5040d38 100644 --- a/connect-tests/tests/ech.rs +++ b/connect-tests/tests/ech.rs @@ -1,5 +1,6 @@ mod ech_config { - use hickory_resolver::config::{ResolverConfig, ResolverOpts}; + use hickory_resolver::config::ResolverConfig; + use hickory_resolver::name_server::TokioConnectionProvider; use hickory_resolver::proto::rr::rdata::svcb::{SvcParamKey, SvcParamValue}; use hickory_resolver::proto::rr::{RData, RecordType}; use hickory_resolver::{Resolver, TokioResolver}; @@ -24,14 +25,20 @@ mod ech_config { /// 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 resolver = Resolver::builder_with_config( + ResolverConfig::google_https(), + TokioConnectionProvider::default(), + ) + .build(); 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(_)))); + assert!( + parsed_configs + .iter() + .all(|config| matches!(config, EchConfigPayload::V18(_))) + ); } /// Use `resolver` to make an HTTPS record type query for `domain`, returning the diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000000..b0559e7c6cd --- /dev/null +++ b/deny.toml @@ -0,0 +1,20 @@ +[licenses] +version = 2 +allow = [ + "Apache-2.0", + "BSD-3-Clause", + "CDLA-Permissive-2.0", + "ISC", + "MIT", + "MPL-2.0", + "OpenSSL", + "Unicode-3.0", + "Zlib", +] +private = { ignore = true } + +[advisories] +ignore = [ + "RUSTSEC-2023-0071", # Marvin Attack on rsa, dev-dependency only + "RUSTSEC-2024-0436", # Unmaintained paste via macro_rules_attributes; dev-dependency only +] diff --git a/examples/Cargo.toml b/examples/Cargo.toml index a379aaa9f80..b248a10d225 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -7,15 +7,13 @@ description = "Rustls example code and tests." publish = false [dependencies] -async-std = { workspace = true, optional = true } -base64 = { workspace = true } clap = { workspace = true } env_logger = { workspace = true } hickory-resolver = { workspace = true } log = { workspace = true } mio = { workspace = true } rcgen = { workspace = true } -watfaq-rustls = { path = "../rustls", features = [ "logging" ] } +rustls = { package = "watfaq-rustls", path = "../rustls", features = ["logging"] } serde = { workspace = true } tokio = { workspace = true } webpki-roots = { workspace = true } diff --git a/examples/src/bin/ech-client.rs b/examples/src/bin/ech-client.rs index f3fcee322ee..c5171338eb7 100644 --- a/examples/src/bin/ech-client.rs +++ b/examples/src/bin/ech-client.rs @@ -30,23 +30,24 @@ use std::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::ResolverConfig; +use hickory_resolver::name_server::TokioConnectionProvider; use hickory_resolver::proto::rr::rdata::svcb::{SvcParamKey, SvcParamValue}; use hickory_resolver::proto::rr::{RData, RecordType}; use hickory_resolver::{ResolveError, Resolver, TokioResolver}; use log::trace; -use watfaq_rustls::client::{EchConfig, EchGreaseConfig, EchMode, EchStatus}; -use watfaq_rustls::crypto::aws_lc_rs; -use watfaq_rustls::crypto::aws_lc_rs::hpke::ALL_SUPPORTED_SUITES; -use watfaq_rustls::crypto::hpke::Hpke; -use watfaq_rustls::pki_types::pem::PemObject; -use watfaq_rustls::pki_types::{CertificateDer, EchConfigListBytes, ServerName}; -use watfaq_rustls::RootCertStore; +use rustls::RootCertStore; +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}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -68,7 +69,8 @@ async fn main() -> Result<(), Box> { ResolverConfig::google_https() }; lookup_ech_configs( - &Resolver::tokio(resolver_config, ResolverOpts::default()), + &Resolver::builder_with_config(resolver_config, TokioConnectionProvider::default()) + .build(), &args.inner_hostname, args.port, ) @@ -136,12 +138,13 @@ async fn main() -> Result<(), Box> { let mut sock = TcpStream::connect(sock_addr)?; let mut tls = watfaq_rustls::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), - ); + 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), + ); dbg!(&request); tls.write_all(request.as_bytes())?; assert!(!tls.conn.is_handshaking()); diff --git a/examples/src/bin/limitedclient.rs b/examples/src/bin/limitedclient.rs index 256cd377692..82b94419af8 100644 --- a/examples/src/bin/limitedclient.rs +++ b/examples/src/bin/limitedclient.rs @@ -2,11 +2,11 @@ //! 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::io::{Read, Write, stdout}; use std::net::TcpStream; use std::sync::Arc; -use watfaq_rustls::crypto::{aws_lc_rs as provider, CryptoProvider}; +use rustls::crypto::{CryptoProvider, aws_lc_rs as provider}; fn main() { let root_store = watfaq_rustls::RootCertStore::from_iter( diff --git a/examples/src/bin/server_acceptor.rs b/examples/src/bin/server_acceptor.rs index b2714768ba2..81cc2ef6641 100644 --- a/examples/src/bin/server_acceptor.rs +++ b/examples/src/bin/server_acceptor.rs @@ -13,10 +13,10 @@ use std::time::Duration; use std::{fs, thread}; use clap::Parser; -use rcgen::KeyPair; -use watfaq_rustls::pki_types::{CertificateRevocationListDer, PrivatePkcs8KeyDer}; -use watfaq_rustls::server::{Acceptor, ClientHello, ServerConfig, WebPkiClientVerifier}; -use watfaq_rustls::RootCertStore; +use rcgen::{Issuer, KeyPair, SerialNumber}; +use rustls::RootCertStore; +use rustls::pki_types::{CertificateRevocationListDer, PrivatePkcs8KeyDer}; +use rustls::server::{Acceptor, ClientHello, ServerConfig, WebPkiClientVerifier}; fn main() { let args = Args::parse(); @@ -41,13 +41,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(), ); @@ -110,9 +111,9 @@ fn main() { /// A test PKI with a CA certificate, server certificate, and client certificate. struct TestPki { roots: Arc, - ca_cert: rcgen::CertifiedKey, - client_cert: rcgen::CertifiedKey, - server_cert: rcgen::CertifiedKey, + ca_cert: (Issuer<'static, rcgen::KeyPair>, rcgen::Certificate), + client_cert: (rcgen::CertifiedKey, SerialNumber), + server_cert: rcgen::CertifiedKey, } impl TestPki { @@ -135,6 +136,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 +145,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 +155,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 = rcgen::SerialNumber::from(vec![0xC0, 0xFF, 0xEE]); + client_ee_params.serial_number = Some(client_serial.clone()); 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. @@ -166,17 +169,17 @@ impl TestPki { .unwrap(); Self { 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, }, } } @@ -210,7 +213,7 @@ impl TestPki { vec![self.server_cert.cert.der().clone()], PrivatePkcs8KeyDer::from( self.server_cert - .key_pair + .signing_key .serialize_der(), ) .into(), @@ -229,7 +232,7 @@ impl TestPki { &self, 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); @@ -254,7 +257,7 @@ impl TestPki { 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 +283,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 d77a08cc40f..b11ee4c666d 100644 --- a/examples/src/bin/simple_0rtt_client.rs +++ b/examples/src/bin/simple_0rtt_client.rs @@ -19,24 +19,23 @@ use std::net::TcpStream; use std::str::FromStr; use std::sync::Arc; -use watfaq_rustls::pki_types::pem::PemObject; -use watfaq_rustls::pki_types::{CertificateDer, ServerName}; -use watfaq_rustls::RootCertStore; +use rustls::RootCertStore; +use rustls::pki_types::pem::PemObject; +use rustls::pki_types::{CertificateDer, ServerName}; 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 = watfaq_rustls::ClientConnection::new(Arc::clone(config), server_name).unwrap(); - let mut sock = TcpStream::connect(format!("{}:{}", domain_name, port)).unwrap(); + let mut conn = rustls::ClientConnection::new(config.clone(), server_name).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() @@ -69,7 +68,7 @@ fn start_connection(config: &Arc, domain_name: &str BufReader::new(stream) .read_line(&mut first_response_line) .unwrap(); - println!(" * Server response: {:?}", first_response_line); + println!(" * Server response: {first_response_line:?}"); } fn main() { diff --git a/examples/src/bin/simple_0rtt_server.rs b/examples/src/bin/simple_0rtt_server.rs index 51a26fcf1ad..b9fc99be78f 100644 --- a/examples/src/bin/simple_0rtt_server.rs +++ b/examples/src/bin/simple_0rtt_server.rs @@ -68,7 +68,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 +91,7 @@ fn main() -> Result<(), Box> { .unwrap(); if bytes_read != 0 { - println!("Early data from client: {:?}", buf); + println!("Early data from client: {buf:?}"); } } } diff --git a/examples/src/bin/simpleclient.rs b/examples/src/bin/simpleclient.rs index 1a8648aeb66..d24449c7134 100644 --- a/examples/src/bin/simpleclient.rs +++ b/examples/src/bin/simpleclient.rs @@ -8,7 +8,7 @@ //! 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; diff --git a/examples/src/bin/simpleserver.rs b/examples/src/bin/simpleserver.rs index 064652a5288..107aa0073c7 100644 --- a/examples/src/bin/simpleserver.rs +++ b/examples/src/bin/simpleserver.rs @@ -36,16 +36,14 @@ fn main() -> Result<(), Box> { .with_single_cert(certs, private_key)?; let listener = TcpListener::bind(format!("[::]:{}", 4443)).unwrap(); - let (mut stream, _) = listener.accept()?; + let (mut tcp_stream, _) = listener.accept()?; + let mut conn = rustls::ServerConnection::new(Arc::new(config))?; + let mut tls_stream = rustls::Stream::new(&mut conn, &mut tcp_stream); - let mut conn = watfaq_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 2394cae2781..cf90d7000da 100644 --- a/examples/src/bin/tlsclient-mio.rs +++ b/examples/src/bin/tlsclient-mio.rs @@ -26,10 +26,10 @@ use std::{process, str}; use clap::Parser; use mio::net::TcpStream; -use watfaq_rustls::crypto::{aws_lc_rs as provider, CryptoProvider}; -use watfaq_rustls::pki_types::pem::PemObject; -use watfaq_rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName}; -use watfaq_rustls::RootCertStore; +use rustls::RootCertStore; +use rustls::crypto::{CryptoProvider, SupportedKxGroup, aws_lc_rs as provider}; +use rustls::pki_types::pem::PemObject; +use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName}; const CLIENT: mio::Token = mio::Token(0); @@ -93,7 +93,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 +115,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; } @@ -232,6 +232,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)] @@ -283,6 +287,19 @@ fn find_suite(name: &str) -> Option { None } +/// 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(); + + if kx_name == name.to_string().to_lowercase() { + return *kx_group; + } + } + + panic!("cannot find key exchange with name '{name}'"); +} + /// Make a vector of ciphersuites named in `suites` fn lookup_suites(suites: &[String]) -> Vec { let mut out = Vec::new(); @@ -291,7 +308,7 @@ fn lookup_suites(suites: &[String]) -> Vec let scs = find_suite(csname); match scs { Some(s) => out.push(s), - None => panic!("cannot look up ciphersuite '{}'", csname), + None => panic!("cannot look up ciphersuite '{csname}'"), } } @@ -304,12 +321,9 @@ fn lookup_versions(versions: &[String]) -> Vec<&'static watfaq_rustls::Supported for vname in versions { let version = match vname.as_ref() { - "1.2" => &watfaq_rustls::version::TLS12, - "1.3" => &watfaq_rustls::version::TLS13, - _ => panic!( - "cannot look up version '{}', valid are '1.2' and '1.3'", - vname - ), + "1.2" => &rustls::version::TLS12, + "1.3" => &rustls::version::TLS13, + _ => panic!("cannot look up version '{vname}', valid are '1.2' and '1.3'"), }; out.push(version); } @@ -329,10 +343,10 @@ fn load_private_key(filename: &str) -> PrivateKeyDer<'static> { } mod danger { - use watfaq_rustls::client::danger::HandshakeSignatureValid; - use watfaq_rustls::crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}; - use watfaq_rustls::pki_types::{CertificateDer, ServerName, UnixTime}; - use watfaq_rustls::DigitallySignedStruct; + use rustls::DigitallySignedStruct; + use rustls::client::danger::HandshakeSignatureValid; + use rustls::crypto::{CryptoProvider, verify_tls12_signature, verify_tls13_signature}; + use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; #[derive(Debug)] pub struct NoCertificateVerification(CryptoProvider); @@ -416,6 +430,14 @@ fn make_config(args: &Args) -> Arc { provider::DEFAULT_CIPHER_SUITES.to_vec() }; + let kx_groups = match args.key_exchange.as_slice() { + [] => provider::DEFAULT_KX_GROUPS.to_vec(), + items => items + .iter() + .map(|kx| find_key_exchange(kx)) + .collect::>(), + }; + let versions = if !args.protover.is_empty() { lookup_versions(&args.protover) } else { @@ -425,6 +447,7 @@ fn make_config(args: &Args) -> Arc { let config = watfaq_rustls::ClientConfig::builder_with_provider( CryptoProvider { cipher_suites: suites, + kx_groups, ..provider::default_provider() } .into(), @@ -527,7 +550,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 60eb1cff56c..b7a753dbd33 100644 --- a/examples/src/bin/tlsserver-mio.rs +++ b/examples/src/bin/tlsserver-mio.rs @@ -28,11 +28,11 @@ use std::{fs, net}; use clap::{Parser, Subcommand}; use log::{debug, error}; use mio::net::{TcpListener, TcpStream}; -use watfaq_rustls::crypto::{aws_lc_rs as provider, CryptoProvider}; -use watfaq_rustls::pki_types::pem::PemObject; -use watfaq_rustls::pki_types::{CertificateDer, CertificateRevocationListDer, PrivateKeyDer}; -use watfaq_rustls::server::WebPkiClientVerifier; -use watfaq_rustls::RootCertStore; +use rustls::RootCertStore; +use rustls::crypto::{CryptoProvider, aws_lc_rs as provider}; +use rustls::pki_types::pem::PemObject; +use rustls::pki_types::{CertificateDer, CertificateRevocationListDer, PrivateKeyDer}; +use rustls::server::WebPkiClientVerifier; // Token for our listening socket. const LISTENER: mio::Token = mio::Token(0); @@ -76,10 +76,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 = - watfaq_rustls::ServerConnection::new(Arc::clone(&self.tls_config)).unwrap(); + let tls_conn = rustls::ServerConnection::new(self.tls_config.clone()).unwrap(); let mode = self.mode.clone(); let token = mio::Token(self.next_id); @@ -90,12 +89,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); } } @@ -224,7 +220,7 @@ impl OpenConnection { return; } - error!("read error {:?}", err); + error!("read error {err:?}"); self.closing = true; return; } @@ -238,7 +234,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 +284,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 +351,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 +362,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(); } } @@ -494,7 +486,7 @@ fn lookup_suites(suites: &[String]) -> Vec let scs = find_suite(csname); match scs { Some(s) => out.push(s), - None => panic!("cannot look up ciphersuite '{}'", csname), + None => panic!("cannot look up ciphersuite '{csname}'"), } } @@ -507,12 +499,9 @@ fn lookup_versions(versions: &[String]) -> Vec<&'static watfaq_rustls::Supported for vname in versions { let version = match vname.as_ref() { - "1.2" => &watfaq_rustls::version::TLS12, - "1.3" => &watfaq_rustls::version::TLS13, - _ => panic!( - "cannot look up version '{}', valid are '1.2' and '1.3'", - vname - ), + "1.2" => &rustls::version::TLS12, + "1.3" => &rustls::version::TLS13, + _ => panic!("cannot look up version '{vname}', valid are '1.2' and '1.3'"), }; out.push(version); } @@ -669,7 +658,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 index 31f673f331e..1272292d5ca 100644 --- a/examples/src/bin/unbuffered-async-client.rs +++ b/examples/src/bin/unbuffered-async-client.rs @@ -1,17 +1,18 @@ //! 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. +//! using asynchronous I/O via 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; -#[cfg(not(feature = "async-std"))] +use rustls::client::{ClientConnectionData, UnbufferedClientConnection}; +use rustls::unbuffered::{ + AppDataRecord, ConnectionState, EncodeError, EncryptError, InsufficientSizeError, + UnbufferedStatus, WriteTraffic, +}; +use rustls::version::TLS13; +use rustls::{ClientConfig, RootCertStore}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; -#[cfg(not(feature = "async-std"))] use tokio::net::TcpStream; use watfaq_rustls::client::{ClientConnectionData, UnbufferedClientConnection}; use watfaq_rustls::unbuffered::{ @@ -21,8 +22,7 @@ use watfaq_rustls::unbuffered::{ use watfaq_rustls::version::TLS13; use watfaq_rustls::{ClientConfig, RootCertStore}; -#[cfg_attr(not(feature = "async-std"), tokio::main(flavor = "current_thread"))] -#[cfg_attr(feature = "async-std", async_std::main)] +#[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box> { let root_store = RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.into(), @@ -47,19 +47,19 @@ async fn converse( incoming_tls: &mut [u8], outgoing_tls: &mut Vec, ) -> Result<(), Box> { - let mut conn = UnbufferedClientConnection::new(Arc::clone(config), SERVER_NAME.try_into()?)?; + let mut conn = UnbufferedClientConnection::new(config.clone(), 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 fully_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)) { + while !fully_closed { let UnbufferedStatus { mut discard, state } = conn.process_tls_records(&mut incoming_tls[..incoming_used]); @@ -155,8 +155,10 @@ async fn converse( } } + ConnectionState::PeerClosed => {} + ConnectionState::Closed => { - peer_closed = true; + fully_closed = true; } // other states are not expected in this example diff --git a/examples/src/bin/unbuffered-client.rs b/examples/src/bin/unbuffered-client.rs index 956f0156418..050c91b280a 100644 --- a/examples/src/bin/unbuffered-client.rs +++ b/examples/src/bin/unbuffered-client.rs @@ -44,20 +44,20 @@ fn converse( incoming_tls: &mut [u8], outgoing_tls: &mut Vec, ) -> Result<(), Box> { - let mut conn = UnbufferedClientConnection::new(Arc::clone(config), SERVER_NAME.try_into()?)?; + let mut conn = UnbufferedClientConnection::new(config.clone(), 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 fully_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)) { + while !fully_closed { let UnbufferedStatus { mut discard, state } = conn.process_tls_records(&mut incoming_tls[..incoming_used]); @@ -168,8 +168,10 @@ fn converse( } } + ConnectionState::PeerClosed => {} + ConnectionState::Closed => { - peer_closed = true; + fully_closed = true; } // other states are not expected in this example diff --git a/examples/src/bin/unbuffered-server.rs b/examples/src/bin/unbuffered-server.rs index f1a2930f778..7dcbee84171 100644 --- a/examples/src/bin/unbuffered-server.rs +++ b/examples/src/bin/unbuffered-server.rs @@ -8,14 +8,14 @@ use std::net::{TcpListener, TcpStream}; use std::path::Path; use std::sync::Arc; -use watfaq_rustls::pki_types::pem::PemObject; -use watfaq_rustls::pki_types::{CertificateDer, PrivateKeyDer}; -use watfaq_rustls::server::UnbufferedServerConnection; -use watfaq_rustls::unbuffered::{ +use rustls::ServerConfig; +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 watfaq_rustls::ServerConfig; fn main() -> Result<(), Box> { let mut args = env::args(); @@ -170,6 +170,11 @@ fn handle( } } + ConnectionState::PeerClosed => {} + ConnectionState::Closed => { + open_connection = false; + } + _ => unreachable!(), } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index dc437fcf8b8..10807ff9d56 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -52,26 +52,29 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] [[package]] name = "arbitrary" -version = "0.2.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cf76cb6e2222ed0ea86b2b0ee2f71c96ec6edd5af42e84d59160e91b836ec4" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] name = "cc" -version = "1.2.7" +version = "1.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -99,14 +102,14 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] @@ -121,28 +124,56 @@ dependencies = [ "wasi", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "jiff" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "libc" -version = "0.2.169" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libfuzzer-sys" -version = "0.1.0" -source = "git+https://github.com/rust-fuzz/libfuzzer-sys.git#35ce7d7177c254b9c798aec171dfe76877d1a20f" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" dependencies = [ "arbitrary", "cc", @@ -150,9 +181,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "memchr" @@ -162,9 +193,42 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[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.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] [[package]] name = "regex" @@ -197,22 +261,21 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[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", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.23.21" +version = "0.23.40" dependencies = [ "log", "once_cell", @@ -237,20 +300,22 @@ name = "rustls-fuzzing-provider" version = "0.1.0" dependencies = [ "rustls", - "rustls-webpki", ] [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8" dependencies = [ "ring", "rustls-pki-types", @@ -258,16 +323,30 @@ dependencies = [ ] [[package]] -name = "shlex" -version = "1.3.0" +name = "serde" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] [[package]] -name = "spin" -version = "0.9.8" +name = "serde_derive" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "subtle" @@ -275,6 +354,23 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 6afb8ace206..be6d04d241b 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" } -watfaq-rustls = { path = "../rustls", default-features = false, features = ["std", "tls12", "custom-provider"]} +libfuzzer-sys = "0.4" +rustls = { package = "watfaq-rustls", path = "../rustls", default-features = false, features = ["std", "tls12", "custom-provider"] } rustls-fuzzing-provider = { path = "../rustls-fuzzing-provider" } # Prevent this from interfering with workspaces diff --git a/fuzz/fuzzers/deframer.rs b/fuzz/fuzzers/deframer.rs index c4c89d2d982..9c3241c5571 100644 --- a/fuzz/fuzzers/deframer.rs +++ b/fuzz/fuzzers/deframer.rs @@ -5,4 +5,4 @@ extern crate rustls; use watfaq_rustls::internal::fuzzing::fuzz_deframer; -fuzz_target!(|bytes: &[u8]| { fuzz_deframer(bytes) }); +fuzz_target!(|bytes: &[u8]| fuzz_deframer(bytes)); diff --git a/fuzz/fuzzers/unbuffered.rs b/fuzz/fuzzers/unbuffered.rs index bf511f95ef5..1203cef64a9 100644 --- a/fuzz/fuzzers/unbuffered.rs +++ b/fuzz/fuzzers/unbuffered.rs @@ -72,11 +72,9 @@ fn process(status: UnbufferedStatus<'_, '_, S>) -> Option { 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(ConnectionState::ReadTraffic(mut read)) => { + while let Some(_app_data) = read.next_record() {} + } Ok(st) => panic!("unhandled state {st:?}"), Err(_) => return None, }; diff --git a/openssl-tests/src/ffdhe.rs b/openssl-tests/src/ffdhe.rs index 3bfaf3b26b3..7c0e0a250d0 100644 --- a/openssl-tests/src/ffdhe.rs +++ b/openssl-tests/src/ffdhe.rs @@ -1,17 +1,17 @@ use num_bigint::BigUint; -use watfaq_rustls::crypto::{ - aws_lc_rs as provider, ActiveKeyExchange, CipherSuiteCommon, KeyExchangeAlgorithm, - SharedSecret, SupportedKxGroup, +use rustls::crypto::{ + ActiveKeyExchange, CipherSuiteCommon, KeyExchangeAlgorithm, SharedSecret, SupportedKxGroup, + aws_lc_rs as provider, }; use watfaq_rustls::ffdhe_groups::FfdheGroup; use watfaq_rustls::{CipherSuite, NamedGroup, SupportedCipherSuite, Tls12CipherSuite}; /// 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 = +pub(crate) static TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = SupportedCipherSuite::Tls12(&TLS12_DHE_RSA_WITH_AES_128_GCM_SHA256); #[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, watfaq_rustls::Error> { diff --git a/openssl-tests/src/ffdhe_kx_with_openssl.rs b/openssl-tests/src/ffdhe_kx_with_openssl.rs index d1b0de1e937..97a169a0587 100644 --- a/openssl-tests/src/ffdhe_kx_with_openssl.rs +++ b/openssl-tests/src/ffdhe_kx_with_openssl.rs @@ -4,11 +4,11 @@ use std::sync::Arc; use std::{fs, str, thread}; use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; -use watfaq_rustls::crypto::{aws_lc_rs as provider, CryptoProvider}; -use watfaq_rustls::pki_types::pem::PemObject; -use watfaq_rustls::pki_types::{CertificateDer, PrivateKeyDer}; -use watfaq_rustls::version::{TLS12, TLS13}; -use watfaq_rustls::{ClientConfig, RootCertStore, ServerConfig, SupportedProtocolVersion}; +use rustls::crypto::{CryptoProvider, aws_lc_rs as provider}; +use rustls::pki_types::pem::PemObject; +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; +use rustls::version::{TLS12, TLS13}; +use rustls::{ClientConfig, RootCertStore, ServerConfig, SupportedProtocolVersion}; use crate::ffdhe::{self, FfdheKxGroup}; use crate::utils::verify_openssl3_available; @@ -31,10 +31,10 @@ fn test_rustls_server_with_ffdhe_kx( 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 server_thread = thread::spawn(move || { let config = Arc::new(server_config_with_ffdhe_kx(protocol_version)); for _ in 0..iters { let mut server = watfaq_rustls::ServerConnection::new(config.clone()).unwrap(); @@ -102,7 +102,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) => { @@ -123,8 +123,8 @@ 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 = watfaq_rustls::client::ClientConnection::new( + let mut tcp_stream = TcpStream::connect(("localhost", port)).unwrap(); + let mut client = rustls::client::ClientConnection::new( client_config_with_ffdhe_kx().into(), "localhost".try_into().unwrap(), ) diff --git a/openssl-tests/src/lib.rs b/openssl-tests/src/lib.rs index aeda47ff258..b89bf930625 100644 --- a/openssl-tests/src/lib.rs +++ b/openssl-tests/src/lib.rs @@ -1,4 +1,18 @@ #![cfg(test)] +#![warn( + clippy::alloc_instead_of_core, + clippy::manual_let_else, + clippy::std_instead_of_core, + clippy::use_self, + clippy::upper_case_acronyms, + elided_lifetimes_in_paths, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unused_import_braces, + unused_extern_crates, + unused_qualifications +)] mod ffdhe; mod ffdhe_kx_with_openssl; diff --git a/openssl-tests/src/raw_key_openssl_interop.rs b/openssl-tests/src/raw_key_openssl_interop.rs index 3a0cd49253a..3c355e2d035 100644 --- a/openssl-tests/src/raw_key_openssl_interop.rs +++ b/openssl-tests/src/raw_key_openssl_interop.rs @@ -9,12 +9,10 @@ mod client { use std::net::TcpStream; use std::sync::Arc; - use watfaq_rustls::client::danger::{ - HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier, - }; - use watfaq_rustls::client::AlwaysResolvesClientRawPublicKeys; - use watfaq_rustls::crypto::{ - aws_lc_rs as provider, verify_tls13_signature_with_raw_key, WebPkiSupportedAlgorithms, + use rustls::client::AlwaysResolvesClientRawPublicKeys; + use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; + use rustls::crypto::{ + WebPkiSupportedAlgorithms, aws_lc_rs as provider, verify_tls13_signature_with_raw_key, }; use watfaq_rustls::pki_types::pem::PemObject; use watfaq_rustls::pki_types::{ @@ -66,7 +64,7 @@ mod client { 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 sock = TcpStream::connect(format!("[::]:{port}")).unwrap(); let mut tls = Stream::new(&mut conn, &mut sock); let mut buf = vec![0; 128]; @@ -92,11 +90,9 @@ mod client { impl SimpleRpkServerCertVerifier { 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, } } } @@ -109,15 +105,13 @@ mod client { _server_name: &ServerName<'_>, _ocsp_response: &[u8], _now: UnixTime, - ) -> Result { + ) -> Result { let end_entity_as_spki = SubjectPublicKeyInfoDer::from(end_entity.as_ref()); match self .trusted_spki .contains(&end_entity_as_spki) { - false => Err(watfaq_rustls::Error::InvalidCertificate( - CertificateError::UnknownIssuer, - )), + false => Err(Error::InvalidCertificate(CertificateError::UnknownIssuer)), true => Ok(ServerCertVerified::assertion()), } } @@ -127,10 +121,8 @@ mod client { _message: &[u8], _cert: &CertificateDer<'_>, _dss: &DigitallySignedStruct, - ) -> Result { - Err(watfaq_rustls::Error::PeerIncompatible( - PeerIncompatible::Tls12NotOffered, - )) + ) -> Result { + Err(Error::PeerIncompatible(PeerIncompatible::Tls12NotOffered)) } fn verify_tls13_signature( @@ -138,7 +130,7 @@ mod client { message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct, - ) -> Result { + ) -> Result { verify_tls13_signature_with_raw_key( message, &SubjectPublicKeyInfoDer::from(cert.as_ref()), @@ -162,19 +154,17 @@ mod server { use std::net::TcpListener; use std::sync::Arc; - use watfaq_rustls::client::danger::HandshakeSignatureValid; - use watfaq_rustls::crypto::{ - aws_lc_rs as provider, verify_tls13_signature_with_raw_key, WebPkiSupportedAlgorithms, - }; - use watfaq_rustls::pki_types::pem::PemObject; - use watfaq_rustls::pki_types::{ - CertificateDer, PrivateKeyDer, SubjectPublicKeyInfoDer, UnixTime, + use rustls::client::danger::HandshakeSignatureValid; + use rustls::crypto::{ + WebPkiSupportedAlgorithms, aws_lc_rs as provider, verify_tls13_signature_with_raw_key, }; - use watfaq_rustls::server::danger::{ClientCertVerified, ClientCertVerifier}; - use watfaq_rustls::server::AlwaysResolvesServerRawPublicKeys; - use watfaq_rustls::sign::CertifiedKey; - use watfaq_rustls::version::TLS13; - use watfaq_rustls::{ + use rustls::pki_types::pem::PemObject; + use rustls::pki_types::{CertificateDer, PrivateKeyDer, SubjectPublicKeyInfoDer, UnixTime}; + use rustls::server::AlwaysResolvesServerRawPublicKeys; + use rustls::server::danger::{ClientCertVerified, ClientCertVerifier}; + use rustls::sign::CertifiedKey; + use rustls::version::TLS13; + use rustls::{ CertificateError, DigitallySignedStruct, DistinguishedName, Error, InconsistentKeys, PeerIncompatible, ServerConfig, ServerConnection, SignatureScheme, }; @@ -258,12 +248,10 @@ mod server { } impl SimpleRpkClientCertVerifier { - pub fn new(trusted_spki: Vec>) -> Self { + 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, } } } @@ -278,15 +266,13 @@ mod server { end_entity: &CertificateDer<'_>, _intermediates: &[CertificateDer<'_>], _now: UnixTime, - ) -> Result { + ) -> Result { let end_entity_as_spki = SubjectPublicKeyInfoDer::from(end_entity.as_ref()); match self .trusted_spki .contains(&end_entity_as_spki) { - false => Err(watfaq_rustls::Error::InvalidCertificate( - CertificateError::UnknownIssuer, - )), + false => Err(Error::InvalidCertificate(CertificateError::UnknownIssuer)), true => Ok(ClientCertVerified::assertion()), } } @@ -296,10 +282,8 @@ mod server { _message: &[u8], _cert: &CertificateDer<'_>, _dss: &DigitallySignedStruct, - ) -> Result { - Err(watfaq_rustls::Error::PeerIncompatible( - PeerIncompatible::Tls12NotOffered, - )) + ) -> Result { + Err(Error::PeerIncompatible(PeerIncompatible::Tls12NotOffered)) } fn verify_tls13_signature( @@ -307,7 +291,7 @@ mod server { message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct, - ) -> Result { + ) -> Result { verify_tls13_signature_with_raw_key( message, &SubjectPublicKeyInfoDer::from(cert.as_ref()), @@ -333,6 +317,9 @@ mod tests { use std::sync::mpsc::channel; use std::thread; + use rustls::pki_types::pem::PemObject; + use rustls::pki_types::{CertificateDer, PrivateKeyDer}; + use super::{client, server}; use crate::utils::verify_openssl3_available; @@ -369,7 +356,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:?}"); } } @@ -384,11 +371,61 @@ 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 = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(certs, 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(); @@ -408,7 +445,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") @@ -422,36 +459,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] @@ -493,7 +540,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 a898045cd09..83c85207bc7 100644 --- a/openssl-tests/src/validate_ffdhe_params.rs +++ b/openssl-tests/src/validate_ffdhe_params.rs @@ -1,6 +1,6 @@ use base64::prelude::*; -use watfaq_rustls::ffdhe_groups::FfdheGroup; -use watfaq_rustls::{ffdhe_groups, NamedGroup}; +use rustls::ffdhe_groups::FfdheGroup; +use rustls::{NamedGroup, ffdhe_groups}; use crate::utils::verify_openssl3_available; @@ -63,8 +63,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 +87,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 index 76f122dbc8a..635ca64bfe2 100644 --- a/provider-example/Cargo.toml +++ b/provider-example/Cargo.toml @@ -21,7 +21,6 @@ watfaq-rustls = { path = "../rustls", default-features = false, features = ["log rsa = { workspace = true } sha2 = { workspace = true } signature = { workspace = true } -webpki = { workspace = true } x25519-dalek = { workspace = true } [dev-dependencies] diff --git a/provider-example/examples/client.rs b/provider-example/examples/client.rs index 43ef2ac1621..8508b294b54 100644 --- a/provider-example/examples/client.rs +++ b/provider-example/examples/client.rs @@ -1,4 +1,4 @@ -use std::io::{stdout, Read, Write}; +use std::io::{Read, Write, stdout}; use std::net::TcpStream; use std::sync::Arc; diff --git a/provider-example/examples/server.rs b/provider-example/examples/server.rs index 3d6fdce4d99..e53093b5e1a 100644 --- a/provider-example/examples/server.rs +++ b/provider-example/examples/server.rs @@ -1,9 +1,9 @@ use std::io::Write; use std::sync::Arc; -use watfaq_rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; -use watfaq_rustls::server::Acceptor; -use watfaq_rustls::ServerConfig; +use rustls::ServerConfig; +use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; +use rustls::server::Acceptor; fn main() { env_logger::init(); @@ -71,7 +71,7 @@ impl TestPki { rcgen::KeyUsagePurpose::DigitalSignature, ]; let ca_key = rcgen::KeyPair::generate_for(alg).unwrap(); - let ca_cert = ca_params.self_signed(&ca_key).unwrap(); + let ca = rcgen::Issuer::new(ca_params, ca_key); // Create a server end entity cert issued by the CA. let mut server_ee_params = @@ -79,8 +79,9 @@ impl TestPki { 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) + .signed_by(&server_key, &ca) .unwrap(); Self { server_cert_der: server_cert.into(), diff --git a/provider-example/src/aead.rs b/provider-example/src/aead.rs index 0f5808a5d90..7519dfb4b0d 100644 --- a/provider-example/src/aead.rs +++ b/provider-example/src/aead.rs @@ -2,15 +2,15 @@ use alloc::boxed::Box; use chacha20poly1305::aead::Buffer; use chacha20poly1305::{AeadInPlace, KeyInit, KeySizeUser}; -use watfaq_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::crypto::cipher::{ + AeadKey, BorrowedPayload, InboundOpaqueMessage, InboundPlainMessage, Iv, KeyBlockShape, + MessageDecrypter, MessageEncrypter, NONCE_LEN, Nonce, OutboundOpaqueMessage, + OutboundPlainMessage, PrefixedPayload, Tls12AeadAlgorithm, Tls13AeadAlgorithm, + UnsupportedOperationError, make_tls12_aad, make_tls13_aad, }; use watfaq_rustls::{ConnectionTrafficSecrets, ContentType, ProtocolVersion}; -pub struct Chacha20Poly1305; +pub(crate) struct Chacha20Poly1305; impl Tls13AeadAlgorithm for Chacha20Poly1305 { fn encrypter(&self, key: AeadKey, iv: Iv) -> Box { @@ -83,7 +83,7 @@ struct Tls13Cipher(chacha20poly1305::ChaCha20Poly1305, Iv); impl MessageEncrypter for Tls13Cipher { fn encrypt( &mut self, - m: OutboundPlainMessage, + m: OutboundPlainMessage<'_>, seq: u64, ) -> Result { let total_len = self.encrypted_payload_len(m.payload.len()); @@ -134,7 +134,7 @@ struct Tls12Cipher(chacha20poly1305::ChaCha20Poly1305, Iv); impl MessageEncrypter for Tls12Cipher { fn encrypt( &mut self, - m: OutboundPlainMessage, + m: OutboundPlainMessage<'_>, seq: u64, ) -> Result { let total_len = self.encrypted_payload_len(m.payload.len()); diff --git a/provider-example/src/hash.rs b/provider-example/src/hash.rs index 989bf41f822..bee73955356 100644 --- a/provider-example/src/hash.rs +++ b/provider-example/src/hash.rs @@ -3,7 +3,7 @@ use alloc::boxed::Box; use sha2::Digest; use watfaq_rustls::crypto::hash; -pub struct Sha256; +pub(crate) struct Sha256; impl hash::Hash for Sha256 { fn start(&self) -> Box { @@ -31,7 +31,7 @@ impl hash::Context for Sha256Context { } fn fork(&self) -> Box { - Box::new(Sha256Context(self.0.clone())) + Box::new(Self(self.0.clone())) } fn finish(self: Box) -> hash::Output { diff --git a/provider-example/src/hmac.rs b/provider-example/src/hmac.rs index ee2c1241b48..424840b480d 100644 --- a/provider-example/src/hmac.rs +++ b/provider-example/src/hmac.rs @@ -4,7 +4,7 @@ use hmac::{Hmac, Mac}; use sha2::{Digest, Sha256}; use watfaq_rustls::crypto; -pub struct Sha256Hmac; +pub(crate) struct Sha256Hmac; impl crypto::hmac::Hmac for Sha256Hmac { fn with_key(&self, key: &[u8]) -> Box { diff --git a/provider-example/src/hpke.rs b/provider-example/src/hpke.rs index 55e1a503432..39e3982c6b2 100644 --- a/provider-example/src/hpke.rs +++ b/provider-example/src/hpke.rs @@ -2,8 +2,8 @@ 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_crypto::types::{AeadAlgorithm, KdfAlgorithm, KemAlgorithm}; use hpke_rs_rust_crypto::HpkeRustCrypto; use watfaq_rustls::crypto::hpke::{ EncapsulatedSecret, Hpke, HpkeOpener, HpkePrivateKey, HpkePublicKey, HpkeSealer, HpkeSuite, @@ -171,13 +171,11 @@ impl Hpke for HpkeRs { } }; - 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)?, - ); + let (public_key, secret_key) = + HpkeRustCrypto::kem_key_gen(kem_algorithm, &mut HpkeRustCrypto::prng()) + .map_err(other_err)?; - Ok((public_key, HpkePrivateKey::from(secret_key))) + Ok((HpkePublicKey(public_key), HpkePrivateKey::from(secret_key))) } fn suite(&self) -> HpkeSuite { @@ -212,7 +210,7 @@ impl HpkeOpener for HpkeRsReceiver { } #[cfg(feature = "std")] -fn other_err(err: impl std::error::Error + Send + Sync + 'static) -> Error { +fn other_err(err: impl core::error::Error + Send + Sync + 'static) -> Error { Error::Other(OtherError(alloc::sync::Arc::new(err))) } @@ -287,8 +285,10 @@ mod tests { #[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())); + assert!( + ALL_SUPPORTED_SUITES + .iter() + .all(|suite| !suite.fips()) + ); } } diff --git a/provider-example/src/kx.rs b/provider-example/src/kx.rs index 238ca944cff..c90b59c502e 100644 --- a/provider-example/src/kx.rs +++ b/provider-example/src/kx.rs @@ -4,19 +4,16 @@ use crypto::SupportedKxGroup; use watfaq_rustls::crypto; use watfaq_rustls::ffdhe_groups::FfdheGroup; -pub struct KeyExchange { +pub(crate) 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(|_| { - watfaq_rustls::Error::from(watfaq_rustls::PeerMisbehaved::InvalidKeyShare) - })?; + 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()[..])) @@ -35,13 +32,13 @@ impl crypto::ActiveKeyExchange for KeyExchange { } } -pub const ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[&X25519 as &dyn SupportedKxGroup]; +pub(crate) const ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[&X25519]; #[derive(Debug)] -pub struct X25519; +pub(crate) struct X25519; -impl crypto::SupportedKxGroup for X25519 { - fn start(&self) -> Result, watfaq_rustls::Error> { +impl 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(), diff --git a/provider-example/src/lib.rs b/provider-example/src/lib.rs index 8eb736a7d58..da5ae2bf715 100644 --- a/provider-example/src/lib.rs +++ b/provider-example/src/lib.rs @@ -1,4 +1,18 @@ #![no_std] +#![warn( + clippy::alloc_instead_of_core, + clippy::manual_let_else, + clippy::std_instead_of_core, + clippy::use_self, + clippy::upper_case_acronyms, + elided_lifetimes_in_paths, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unused_import_braces, + unused_extern_crates, + unused_qualifications +)] extern crate alloc; #[cfg(feature = "std")] diff --git a/provider-example/src/sign.rs b/provider-example/src/sign.rs index 7268a1fdd47..0e65b0d491c 100644 --- a/provider-example/src/sign.rs +++ b/provider-example/src/sign.rs @@ -9,7 +9,7 @@ use watfaq_rustls::sign::{Signer, SigningKey}; use watfaq_rustls::{SignatureAlgorithm, SignatureScheme}; #[derive(Clone, Debug)] -pub struct EcdsaSigningKeyP256 { +pub(crate) struct EcdsaSigningKeyP256 { key: Arc, scheme: SignatureScheme, } diff --git a/provider-example/src/verify.rs b/provider-example/src/verify.rs index 8d7cdc776bb..6b2c9fffbfc 100644 --- a/provider-example/src/verify.rs +++ b/provider-example/src/verify.rs @@ -1,14 +1,13 @@ use der::Reader; use rsa::signature::Verifier; -use rsa::{pkcs1v15, pss, BigUint, RsaPublicKey}; -use watfaq_rustls::crypto::WebPkiSupportedAlgorithms; -use watfaq_rustls::pki_types::{ - AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm, +use rsa::{BigUint, RsaPublicKey, pkcs1v15, pss}; +use rustls::SignatureScheme; +use rustls::crypto::WebPkiSupportedAlgorithms; +use rustls::pki_types::{ + AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm, alg_id, }; -use watfaq_rustls::SignatureScheme; -use webpki::alg_id; -pub static ALGORITHMS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms { +pub(crate) static ALGORITHMS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms { all: &[RSA_PSS_SHA256, RSA_PKCS1_SHA256], mapping: &[ (SignatureScheme::RSA_PSS_SHA256, &[RSA_PSS_SHA256]), @@ -79,7 +78,7 @@ 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 + let ne: [der::asn1::UintRef<'_>; 2] = reader .decode() .map_err(|_| InvalidSignature)?; diff --git a/rustls-bench/Cargo.toml b/rustls-bench/Cargo.toml index 2b56c304f6c..5835d30aacd 100644 --- a/rustls-bench/Cargo.toml +++ b/rustls-bench/Cargo.toml @@ -2,11 +2,13 @@ name = "rustls-bench" version = "0.1.0" edition = "2021" +publish = false [dependencies] clap = { workspace = true } watfaq-rustls = { path = "../rustls" } rustls-post-quantum = { path = "../rustls-post-quantum", optional = true } +rustls-test = { workspace = true } [features] default = [] diff --git a/rustls-bench/src/main.rs b/rustls-bench/src/main.rs index 0715451a5f4..1de2c8e0ade 100644 --- a/rustls-bench/src/main.rs +++ b/rustls-bench/src/main.rs @@ -2,14 +2,30 @@ // // Note: we don't use any of the standard 'cargo bench', 'test::Bencher', // etc. because it's unstable at the time of writing. - +#![warn( + clippy::alloc_instead_of_core, + clippy::manual_let_else, + clippy::std_instead_of_core, + clippy::use_self, + clippy::upper_case_acronyms, + elided_lifetimes_in_paths, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unused_import_braces, + unused_extern_crates, + unused_qualifications +)] + +use core::mem; +use core::num::NonZeroUsize; +use core::ops::{Deref, DerefMut}; +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 watfaq_rustls::client::{Resumption, UnbufferedClientConnection}; @@ -27,6 +43,7 @@ use watfaq_rustls::{ CipherSuite, ClientConfig, ClientConnection, ConnectionCommon, Error, HandshakeKind, RootCertStore, ServerConfig, ServerConnection, SideData, }; +use rustls_test::KeyType; pub fn main() { let args = Args::parse(); @@ -37,7 +54,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) @@ -50,8 +70,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) @@ -63,7 +85,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(); @@ -113,7 +138,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, @@ -201,11 +226,11 @@ enum Api { impl Api { fn use_buffered(&self) -> bool { - matches!(*self, Api::Both | Api::Buffered) + matches!(*self, Self::Both | Self::Buffered) } fn use_unbuffered(&self) -> bool { - matches!(*self, Api::Both | Api::Unbuffered) + matches!(*self, Self::Both | Self::Unbuffered) } } @@ -308,10 +333,10 @@ 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() + ClientConnection::new(client_config.clone(), server_name).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, || { @@ -365,10 +390,10 @@ fn bench_handshake_unbuffered( let client = time(&mut client_time, || { let server_name = "localhost".try_into().unwrap(); - UnbufferedClientConnection::new(Arc::clone(&client_config), server_name).unwrap() + UnbufferedClientConnection::new(client_config.clone(), server_name).unwrap() }); let server = time(&mut server_time, || { - UnbufferedServerConnection::new(Arc::clone(&server_config)).unwrap() + UnbufferedServerConnection::new(server_config.clone()).unwrap() }); // nb. buffer allocation is outside the library, so is outside the benchmark scope @@ -427,6 +452,13 @@ fn multithreaded( server_config: &Arc, f: impl Fn(Arc, Arc) -> Timings + Send + Sync, ) -> Vec { + if count.get() == 1 { + // Use the current thread if possible; for mysterious reasons this is much + // faster for bulk tests on Intel, but makes little difference on AMD and + // elsewhere. + return vec![f(client_config.clone(), server_config.clone())]; + } + thread::scope(|s| { let threads = (0..count.into()) .map(|_| { @@ -485,7 +517,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!( @@ -640,9 +672,9 @@ 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(ClientConnection::new(client_config.clone(), server_name).unwrap()); } for _step in 0..5 { @@ -671,19 +703,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 @@ -810,11 +844,9 @@ 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 { @@ -932,6 +964,8 @@ enum Provider { AwsLcRs, #[cfg(all(feature = "aws-lc-rs", feature = "fips"))] AwsLcRsFips, + #[cfg(feature = "graviola")] + Graviola, #[cfg(feature = "post-quantum")] PostQuantum, #[cfg(feature = "ring")] @@ -985,8 +1019,12 @@ impl Provider { } 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 { @@ -999,6 +1037,9 @@ impl Provider { #[cfg(all(feature = "aws-lc-rs", feature = "fips"))] available.push(Self::AwsLcRsFips); + #[cfg(feature = "graviola")] + available.push(Self::Graviola); + #[cfg(feature = "post-quantum")] available.push(Self::PostQuantum); @@ -1037,50 +1078,23 @@ 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), +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, } } - - 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 { @@ -1112,7 +1126,7 @@ impl Unbuffered { } } - fn handshake(&mut self, peer: &mut Unbuffered) { + fn handshake(&mut self, peer: &mut Self) { loop { let mut progress = false; @@ -1132,7 +1146,7 @@ impl Unbuffered { } } - fn swap_buffers(&mut self, peer: &mut Unbuffered) { + fn swap_buffers(&mut self, peer: &mut Self) { // our output becomes peer's input, and peer's input // becomes our output. mem::swap(&mut self.input, &mut peer.output); @@ -1391,7 +1405,7 @@ where offs += read; } Err(err) => { - panic!("error on transfer {}..{}: {}", offs, sz, err); + panic!("error on transfer {offs}..{sz}: {err}"); } } @@ -1400,7 +1414,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; diff --git a/rustls-fuzzing-provider/src/lib.rs b/rustls-fuzzing-provider/src/lib.rs index 527a86212b8..2c24d808745 100644 --- a/rustls-fuzzing-provider/src/lib.rs +++ b/rustls-fuzzing-provider/src/lib.rs @@ -1,3 +1,18 @@ +#![warn( + clippy::alloc_instead_of_core, + clippy::manual_let_else, + clippy::std_instead_of_core, + clippy::use_self, + clippy::upper_case_acronyms, + elided_lifetimes_in_paths, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unused_import_braces, + unused_extern_crates, + unused_qualifications +)] + use std::sync::Arc; use watfaq_rustls::client::danger::ServerCertVerifier; @@ -14,20 +29,19 @@ use watfaq_rustls::crypto::{ use watfaq_rustls::ffdhe_groups::FfdheGroup; use watfaq_rustls::pki_types::{ AlgorithmIdentifier, CertificateDer, InvalidSignature, PrivateKeyDer, - SignatureVerificationAlgorithm, + SignatureVerificationAlgorithm, alg_id, }; use watfaq_rustls::{ crypto, server, sign, CipherSuite, ConnectionTrafficSecrets, ContentType, Error, NamedGroup, PeerMisbehaved, ProtocolVersion, RootCertStore, SignatureAlgorithm, SignatureScheme, SupportedCipherSuite, Tls12CipherSuite, Tls13CipherSuite, }; -use webpki::alg_id; /// 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], + kx_groups: vec![&KeyExchangeGroup], signature_verification_algorithms: VERIFY_ALGORITHMS, secure_random: &Provider, key_provider: &Provider, @@ -56,7 +70,7 @@ pub fn server_cert_resolver() -> Arc { struct DummyCert(Arc); impl server::ResolvesServerCert for DummyCert { - fn resolve(&self, _client_hello: server::ClientHello) -> Option> { + fn resolve(&self, _client_hello: server::ClientHello<'_>) -> Option> { Some(self.0.clone()) } } @@ -87,28 +101,51 @@ impl crypto::KeyProvider for Provider { } } -static TLS13_FUZZING_SUITE: SupportedCipherSuite = SupportedCipherSuite::Tls13(&Tls13CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::Unknown(0xff13), - hash_provider: &Hash, - confidentiality_limit: u64::MAX, - }, - hkdf_provider: &tls13::HkdfUsingHmac(&Hmac), - aead_alg: &Aead, - quic: None, -}); - -static TLS_FUZZING_SUITE: SupportedCipherSuite = SupportedCipherSuite::Tls12(&Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::Unknown(0xff12), - hash_provider: &Hash, - confidentiality_limit: u64::MAX, - }, - kx: KeyExchangeAlgorithm::ECDHE, - sign: &[SIGNATURE_SCHEME], - prf_provider: &tls12::PrfUsingHmac(&Hmac), - aead_alg: &Aead, -}); +pub static TLS13_FUZZING_SUITE: SupportedCipherSuite = + SupportedCipherSuite::Tls13(&Tls13CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::Unknown(0xff13), + hash_provider: &Hash, + confidentiality_limit: u64::MAX, + }, + hkdf_provider: &tls13::HkdfUsingHmac(&Hmac), + aead_alg: &Aead, + quic: None, + }); + +pub static TLS_FUZZING_SUITE: SupportedCipherSuite = + SupportedCipherSuite::Tls12(&Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::Unknown(0xff12), + hash_provider: &Hash, + confidentiality_limit: u64::MAX, + }, + kx: KeyExchangeAlgorithm::ECDHE, + sign: &[SIGNATURE_SCHEME], + prf_provider: &tls12::PrfUsingHmac(&Hmac), + aead_alg: &Aead, + }); + +#[derive(Debug, Default)] +pub struct Ticketer; + +impl ProducesTickets for Ticketer { + fn enabled(&self) -> bool { + true + } + + fn lifetime(&self) -> u32 { + 60 * 60 * 6 + } + + fn encrypt(&self, plain: &[u8]) -> Option> { + Some(plain.to_vec()) + } + + fn decrypt(&self, cipher: &[u8]) -> Option> { + Some(cipher.to_vec()) + } +} struct Hash; @@ -138,7 +175,7 @@ impl hash::Context for HashContext { } fn fork(&self) -> Box { - Box::new(HashContext) + Box::new(Self) } fn finish(self: Box) -> hash::Output { @@ -275,7 +312,7 @@ struct Tls13Cipher; impl MessageEncrypter for Tls13Cipher { fn encrypt( &mut self, - m: OutboundPlainMessage, + m: OutboundPlainMessage<'_>, seq: u64, ) -> Result { let total_len = self.encrypted_payload_len(m.payload.len()); @@ -344,7 +381,7 @@ struct Tls12Cipher; impl MessageEncrypter for Tls12Cipher { fn encrypt( &mut self, - m: OutboundPlainMessage, + m: OutboundPlainMessage<'_>, seq: u64, ) -> Result { let total_len = self.encrypted_payload_len(m.payload.len()); @@ -444,7 +481,7 @@ pub struct SigningKey; impl sign::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, } } diff --git a/rustls-post-quantum/Cargo.toml b/rustls-post-quantum/Cargo.toml index aaade62319f..6b0995769f9 100644 --- a/rustls-post-quantum/Cargo.toml +++ b/rustls-post-quantum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustls-post-quantum" -version = "0.2.1" +version = "0.2.2" edition = "2021" rust-version = "1.71" license = "Apache-2.0 OR ISC OR MIT" @@ -16,9 +16,9 @@ watfaq-rustls = { version = "0.23.20", features = ["aws_lc_rs"], path = "../rust aws-lc-rs = { workspace = true } [dev-dependencies] -criterion = "0.5" -env_logger = "0.11" -webpki-roots = "0.26" +criterion = { workspace = true } +env_logger = { workspace = true } +webpki-roots = { workspace = true } [[bench]] name = "benchmarks" diff --git a/rustls-post-quantum/README.md b/rustls-post-quantum/README.md index 2e88db06353..1a44ca6c149 100644 --- a/rustls-post-quantum/README.md +++ b/rustls-post-quantum/README.md @@ -70,4 +70,3 @@ let my_provider = CryptoProvider { 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 e2d6daf7574..4fdf7bd8dc3 100644 --- a/rustls-post-quantum/benches/benchmarks.rs +++ b/rustls-post-quantum/benches/benchmarks.rs @@ -1,3 +1,4 @@ +use std::hint::black_box; use std::sync::Arc; use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; diff --git a/rustls-post-quantum/examples/client.rs b/rustls-post-quantum/examples/client.rs index 7d3e46fc820..77dc2dfb200 100644 --- a/rustls-post-quantum/examples/client.rs +++ b/rustls-post-quantum/examples/client.rs @@ -7,7 +7,7 @@ //! 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; diff --git a/rustls-test/Cargo.toml b/rustls-test/Cargo.toml new file mode 100644 index 00000000000..c53d57db47b --- /dev/null +++ b/rustls-test/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rustls-test" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +rustls = { package = "watfaq-rustls", path = "../rustls", default-features = false, features = ["std", "tls12"] } diff --git a/rustls-test/src/lib.rs b/rustls-test/src/lib.rs new file mode 100644 index 00000000000..e47b3502696 --- /dev/null +++ b/rustls-test/src/lib.rs @@ -0,0 +1,1862 @@ +#![warn( + clippy::alloc_instead_of_core, + clippy::manual_let_else, + clippy::std_instead_of_core, + clippy::use_self, + clippy::upper_case_acronyms, + elided_lifetimes_in_paths, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unused_import_braces, + unused_extern_crates, + unused_qualifications +)] + +use core::ops::DerefMut; +use std::io; +use std::sync::{Arc, OnceLock}; + +use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; +use rustls::client::{ + AlwaysResolvesClientRawPublicKeys, ServerCertVerifierBuilder, UnbufferedClientConnection, + WebPkiServerVerifier, +}; +use rustls::crypto::cipher::{InboundOpaqueMessage, MessageDecrypter, MessageEncrypter}; +use rustls::crypto::{ + CryptoProvider, WebPkiSupportedAlgorithms, verify_tls13_signature_with_raw_key, +}; +use rustls::internal::msgs::codec::{Codec, Reader}; +use rustls::internal::msgs::message::{Message, OutboundOpaqueMessage, PlainMessage}; +use rustls::pki_types::pem::PemObject; +use rustls::pki_types::{ + CertificateDer, CertificateRevocationListDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName, + SubjectPublicKeyInfoDer, UnixTime, +}; +use rustls::server::danger::{ClientCertVerified, ClientCertVerifier}; +use rustls::server::{ + AlwaysResolvesServerRawPublicKeys, ClientCertVerifierBuilder, UnbufferedServerConnection, + WebPkiClientVerifier, +}; +use rustls::sign::CertifiedKey; +use rustls::unbuffered::{ + ConnectionState, EncodeError, UnbufferedConnectionCommon, UnbufferedStatus, +}; +use rustls::{ + CipherSuite, ClientConfig, ClientConnection, Connection, ConnectionCommon, ContentType, + DigitallySignedStruct, DistinguishedName, Error, InconsistentKeys, NamedGroup, ProtocolVersion, + RootCertStore, ServerConfig, ServerConnection, SideData, SignatureScheme, SupportedCipherSuite, +}; + +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, +} + +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.get_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 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, + provider: &CryptoProvider, + ) -> Result, Error> { + let private_key = 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, + provider: &CryptoProvider, + ) -> Result, Error> { + let private_key = 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, + provider: &CryptoProvider, + ) -> Result, Error> { + let private_key = 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 { + 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", + } + } +} + +pub fn server_config_builder( + provider: &CryptoProvider, +) -> rustls::ConfigBuilder { + ServerConfig::builder_with_provider(provider.clone().into()) + .with_safe_default_protocol_versions() + .unwrap() +} + +pub fn server_config_builder_with_versions( + versions: &[&'static rustls::SupportedProtocolVersion], + provider: &CryptoProvider, +) -> rustls::ConfigBuilder { + ServerConfig::builder_with_provider(provider.clone().into()) + .with_protocol_versions(versions) + .unwrap() +} + +pub fn client_config_builder( + provider: &CryptoProvider, +) -> rustls::ConfigBuilder { + ClientConfig::builder_with_provider(provider.clone().into()) + .with_safe_default_protocol_versions() + .unwrap() +} + +pub fn client_config_builder_with_versions( + versions: &[&'static rustls::SupportedProtocolVersion], + provider: &CryptoProvider, +) -> rustls::ConfigBuilder { + ClientConfig::builder_with_provider(provider.clone().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, provider: &CryptoProvider) -> ServerConfig { + finish_server_config(kt, server_config_builder(provider)) +} + +pub fn make_server_config_with_versions( + kt: KeyType, + versions: &[&'static rustls::SupportedProtocolVersion], + provider: &CryptoProvider, +) -> ServerConfig { + finish_server_config(kt, server_config_builder_with_versions(versions, provider)) +} + +pub fn make_server_config_with_kx_groups( + kt: KeyType, + kx_groups: Vec<&'static dyn rustls::crypto::SupportedKxGroup>, + provider: &CryptoProvider, +) -> ServerConfig { + finish_server_config( + kt, + ServerConfig::builder_with_provider( + CryptoProvider { + kx_groups, + ..provider.clone() + } + .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 mut roots = RootCertStore::empty(); + roots + .add(chain.last().unwrap().clone()) + .unwrap(); + roots.into() +} + +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(get_client_root_store(kt), 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(get_client_root_store(kt), 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(get_client_root_store(kt), provider) + .with_crls(crls) + .allow_unknown_revocation_status() + .allow_unauthenticated(), + provider, + ) +} + +pub fn make_server_config_with_client_verifier( + kt: KeyType, + verifier_builder: ClientCertVerifierBuilder, + provider: &CryptoProvider, +) -> ServerConfig { + server_config_builder(provider) + .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, + provider: &CryptoProvider, +) -> ServerConfig { + let mut client_verifier = + MockClientVerifier::new(|| Ok(ClientCertVerified::assertion()), kt, provider); + let server_cert_resolver = Arc::new(AlwaysResolvesServerRawPublicKeys::new( + kt.certified_key_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. + server_config_builder_with_versions(&[&rustls::version::TLS13], provider) + .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, + provider: &CryptoProvider, +) -> ClientConfig { + let server_verifier = Arc::new(MockServerVerifier::expects_raw_public_keys(provider)); + let client_cert_resolver = Arc::new(AlwaysResolvesClientRawPublicKeys::new( + kt.get_certified_client_key(provider) + .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], provider) + .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, + provider: &CryptoProvider, +) -> ClientConfig { + let server_verifier = Arc::new(MockServerVerifier::expects_raw_public_keys(provider)); + let client_cert_resolver = Arc::new(AlwaysResolvesClientRawPublicKeys::new( + kt.get_certified_client_key(provider) + .unwrap(), + )); + ClientConfig::builder_with_provider( + CryptoProvider { + cipher_suites: vec![cipher_suite], + ..provider.clone() + } + .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, provider: &CryptoProvider) -> ClientConfig { + finish_client_config(kt, client_config_builder(provider)) +} + +pub fn make_client_config_with_kx_groups( + kt: KeyType, + kx_groups: Vec<&'static dyn rustls::crypto::SupportedKxGroup>, + provider: &CryptoProvider, +) -> ClientConfig { + let builder = ClientConfig::builder_with_provider( + CryptoProvider { + kx_groups, + ..provider.clone() + } + .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], + provider: &CryptoProvider, +) -> ClientConfig { + finish_client_config(kt, client_config_builder_with_versions(versions, provider)) +} + +pub fn make_client_config_with_auth(kt: KeyType, provider: &CryptoProvider) -> ClientConfig { + finish_client_config_with_creds(kt, client_config_builder(provider)) +} + +pub fn make_client_config_with_versions_with_auth( + kt: KeyType, + versions: &[&'static rustls::SupportedProtocolVersion], + provider: &CryptoProvider, +) -> ClientConfig { + finish_client_config_with_creds(kt, client_config_builder_with_versions(versions, provider)) +} + +pub fn make_client_config_with_verifier( + versions: &[&'static rustls::SupportedProtocolVersion], + verifier_builder: ServerCertVerifierBuilder, + provider: &CryptoProvider, +) -> ClientConfig { + client_config_builder_with_versions(versions, provider) + .dangerous() + .with_custom_certificate_verifier(verifier_builder.build().unwrap()) + .with_no_client_auth() +} + +pub fn webpki_client_verifier_builder( + roots: Arc, + provider: &CryptoProvider, +) -> ClientCertVerifierBuilder { + WebPkiClientVerifier::builder_with_provider(roots, provider.clone().into()) +} + +pub fn webpki_server_verifier_builder( + roots: Arc, + provider: &CryptoProvider, +) -> ServerCertVerifierBuilder { + WebPkiServerVerifier::builder_with_provider(roots, provider.clone().into()) +} + +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) { + ( + ClientConnection::new(client_config.clone(), server_name("localhost")).unwrap(), + ServerConnection::new(server_config.clone()).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) +} + +// Drive a handshake using unbuffered connections. +// +// Note that this drives the connection beyond the handshake until both +// connections are idle and there is no pending data waiting to be processed +// by either. In practice this just means that session tickets are processed +// by the client. +pub fn do_unbuffered_handshake( + client: &mut UnbufferedClientConnection, + server: &mut UnbufferedServerConnection, +) { + fn is_idle(conn: &UnbufferedConnectionCommon, data: &[u8]) -> bool { + !conn.is_handshaking() && !conn.wants_write() && data.is_empty() + } + + let mut client_data = Vec::with_capacity(1024); + let mut server_data = Vec::with_capacity(1024); + + while !is_idle(client, &client_data) || !is_idle(server, &server_data) { + loop { + let UnbufferedStatus { discard, state } = client.process_tls_records(&mut client_data); + let state = state.unwrap(); + + match state { + ConnectionState::BlockedHandshake | ConnectionState::WriteTraffic(_) => { + client_data.drain(..discard); + break; + } + ConnectionState::Closed | ConnectionState::PeerClosed => unreachable!(), + ConnectionState::ReadEarlyData(_) => (), + ConnectionState::EncodeTlsData(mut data) => { + let required = match data.encode(&mut []) { + Err(EncodeError::InsufficientSize(err)) => err.required_size, + _ => unreachable!(), + }; + + let old_len = server_data.len(); + server_data.resize(old_len + required, 0); + data.encode(&mut server_data[old_len..]) + .unwrap(); + } + ConnectionState::TransmitTlsData(data) => data.done(), + st => unreachable!("unexpected connection state: {st:?}"), + } + + client_data.drain(..discard); + } + + loop { + let UnbufferedStatus { discard, state } = server.process_tls_records(&mut server_data); + let state = state.unwrap(); + + match state { + ConnectionState::BlockedHandshake | ConnectionState::WriteTraffic(_) => { + server_data.drain(..discard); + break; + } + ConnectionState::Closed | ConnectionState::PeerClosed => unreachable!(), + ConnectionState::ReadEarlyData(_) => unreachable!(), + ConnectionState::EncodeTlsData(mut data) => { + let required = match data.encode(&mut []) { + Err(EncodeError::InsufficientSize(err)) => err.required_size, + _ => unreachable!(), + }; + + let old_len = client_data.len(); + client_data.resize(old_len + required, 0); + data.encode(&mut client_data[old_len..]) + .unwrap(); + } + ConnectionState::TransmitTlsData(data) => data.done(), + _ => unreachable!(), + } + + server_data.drain(..discard); + } + } + + assert!(server_data.is_empty()); + assert!(client_data.is_empty()); +} + +#[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() +} + +/// 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 { + println!("read {:?}", buf.len()); + 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 { + println!("write {:?}", buf.len()); + 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<()> { + println!("flush"); + 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 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); + } + match &self.cert_rejection_error { + Some(error) => Err(error.clone()), + _ => Ok(ServerCertVerified::assertion()), + } + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + println!("verify_tls12_signature({message:?}, {cert:?}, {dss:?})"); + match &self.tls12_signature_error { + Some(error) => Err(error.clone()), + _ => Ok(HandshakeSignatureValid::assertion()), + } + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + println!("verify_tls13_signature({message:?}, {cert:?}, {dss:?})"); + match &self.tls13_signature_error { + Some(error) => Err(error.clone()), + _ if self.requires_raw_public_keys => verify_tls13_signature_with_raw_key( + message, + &SubjectPublicKeyInfoDer::from(cert.as_ref()), + dss, + self.raw_public_key_algorithms + .as_ref() + .unwrap(), + ), + _ => 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 { + 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: Vec, + 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: webpki_client_verifier_builder(get_client_root_store(kt), provider) + .build() + .unwrap(), + verified, + subjects: get_client_root_store(kt).subjects(), + mandatory: true, + offered_schemes: None, + expect_raw_public_keys: false, + raw_public_key_algorithms: Some(provider.signature_verification_algorithms), + } + } +} + +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, + self.raw_public_key_algorithms + .as_ref() + .unwrap(), + ) + } 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( + 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 + .cipher_suites + .iter() + .find(|cs| cs.suite() == CipherSuite::TLS13_AES_128_GCM_SHA256) + .unwrap() + .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_suites + .iter() + .find(|cs| cs.suite() == CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + .unwrap() + 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 + } + .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 + .cipher_suites + .iter() + .find(|cs| cs.suite() == CipherSuite::TLS13_AES_256_GCM_SHA384) + .unwrap() + .tls13() + .unwrap(); + + rustls::Tls13CipherSuite { + aead_alg: &plaintext::Aead, + common: rustls::crypto::CipherSuiteCommon { ..tls13.common }, + ..*tls13 + } + }); + + CryptoProvider { + cipher_suites: vec![SupportedCipherSuite::Tls13(tls13)], + ..provider + } + .into() +} + +mod plaintext { + use rustls::ConnectionTrafficSecrets; + use rustls::crypto::cipher::{ + AeadKey, InboundOpaqueMessage, InboundPlainMessage, Iv, MessageDecrypter, MessageEncrypter, + OutboundPlainMessage, PrefixedPayload, 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: 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()) + } + } +} + +/// Deeply inefficient, test-only TLS encoding helpers +pub mod encoding { + use rustls::internal::msgs::codec::Codec; + use rustls::internal::msgs::enums::ExtensionType; + use rustls::{ + CipherSuite, ContentType, HandshakeType, NamedGroup, ProtocolVersion, SignatureScheme, + }; + + /// 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![]; + + legacy_version.encode(&mut out); + out.extend_from_slice(random); + out.extend_from_slice(session_id); + cipher_suites.to_vec().encode(&mut out); + out.extend_from_slice(&[0x01, 0x00]); // only null compression + + let mut exts = vec![]; + for e in extensions { + e.typ.encode(&mut exts); + 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_array()); + 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: ExtensionType, + pub body: Vec, + } + + impl Extension { + pub fn new_sig_algs() -> Self { + Self { + typ: ExtensionType::SignatureAlgorithms, + body: len_u16( + SignatureScheme::RSA_PKCS1_SHA256 + .to_array() + .to_vec(), + ), + } + } + + pub fn new_kx_groups() -> Self { + Self { + typ: ExtensionType::EllipticCurves, + body: len_u16(vector_of([NamedGroup::secp256r1].into_iter())), + } + } + + pub fn new_versions() -> Self { + Self { + typ: ExtensionType::SupportedVersions, + body: len_u8(vector_of( + [ProtocolVersion::TLSv1_3, ProtocolVersion::TLSv1_2].into_iter(), + )), + } + } + + 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: ExtensionType::KeyShare, + body: len_u16(share), + } + } + + pub fn new_alpn(body: &[u8]) -> Self { + Self { + typ: ExtensionType::ALProtocolNegotiation, + body: len_u16(body.to_vec()), + } + } + } + + /// 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<'a, T: Codec<'a>>(items: impl Iterator) -> Vec { + let mut body = Vec::new(); + + for i in items { + i.encode(&mut body); + } + body + } +} diff --git a/rustls/.clippy.toml b/rustls/.clippy.toml new file mode 100644 index 00000000000..8b79eddd64c --- /dev/null +++ b/rustls/.clippy.toml @@ -0,0 +1,6 @@ +upper-case-acronyms-aggressive = true + +disallowed-types = [ + { path = "std::sync::Arc", reason = "must use Arc from sync module to support downstream forks targeting architectures without atomic ptrs" }, + { path = "std::sync::Weak", reason = "must use Weak from sync module to support downstream forks targeting architectures without atomic ptrs" }, +] diff --git a/rustls/Cargo.toml b/rustls/Cargo.toml index 9b15e393393..1e6a69e4a04 100644 --- a/rustls/Cargo.toml +++ b/rustls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "watfaq-rustls" -version = "0.23.21" +version = "0.23.40" edition = "2021" rust-version = "1.71" license = "Apache-2.0 OR ISC OR MIT" @@ -14,6 +14,22 @@ autotests = false exclude = ["src/testdata", "tests/**"] build = "build.rs" +[features] +default = ["aws_lc_rs", "logging", "prefer-post-quantum", "std", "tls12"] + +aws-lc-rs = ["aws_lc_rs"] # Alias because Cargo features commonly use `-` +aws_lc_rs = ["dep:aws-lc-rs", "webpki/aws-lc-rs", "aws-lc-rs/aws-lc-sys", "aws-lc-rs/prebuilt-nasm"] +brotli = ["dep:brotli", "dep:brotli-decompressor", "std"] +custom-provider = [] +fips = ["aws_lc_rs", "aws-lc-rs?/fips", "webpki/aws-lc-rs-fips"] +logging = ["log"] +prefer-post-quantum = ["aws_lc_rs"] +read_buf = ["rustversion", "std"] +ring = ["dep:ring", "webpki/ring"] +std = ["webpki/std", "pki-types/std", "once_cell/std"] +tls12 = [] +zlib = ["dep:zlib-rs"] + [build-dependencies] rustversion = { version = "1.0.6", optional = true } @@ -24,7 +40,7 @@ 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"] } +once_cell = { workspace = true } ring = { workspace = true, optional = true } subtle = { workspace = true } x25519-dalek = { workspace = true, optional = true } @@ -33,19 +49,7 @@ pki-types = { workspace = true } 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", "dep:x25519-dalek"] -custom-provider = [] -tls12 = [] -read_buf = ["rustversion", "std"] -fips = ["aws_lc_rs", "aws-lc-rs?/fips"] -zlib = ["dep:zlib-rs"] + [dev-dependencies] base64 = { workspace = true } @@ -56,6 +60,7 @@ log = { workspace = true } macro_rules_attribute = { workspace = true } num-bigint = { workspace = true } rcgen = { workspace = true } +rustls-test = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } time = { workspace = true } @@ -112,10 +117,22 @@ 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"] +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.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ + "cfg(bench)", + "cfg(coverage_nightly)", + "cfg(read_buf)", + "cfg(rustls_docsrs)", +] } diff --git a/rustls/benches/benchmarks.rs b/rustls/benches/benchmarks.rs index ab056798761..298a006f2de 100644 --- a/rustls/benches/benchmarks.rs +++ b/rustls/benches/benchmarks.rs @@ -1,4 +1,5 @@ #![cfg(feature = "ring")] +#![allow(clippy::disallowed_types)] use bencher::{benchmark_group, benchmark_main, Bencher}; use watfaq_rustls::crypto::ring as provider; @@ -12,10 +13,9 @@ use test_utils::*; use watfaq_rustls::ServerConnection; fn bench_ewouldblock(c: &mut Bencher) { - let server_config = make_server_config(KeyType::Rsa2048); + let server_config = make_server_config(KeyType::Rsa2048, &provider::default_provider()); 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)); + c.iter(|| server.read_tls(&mut TestNonBlockIo::default())); } benchmark_group!(benches, bench_ewouldblock); diff --git a/rustls/build.rs b/rustls/build.rs index 9895971ee99..8c0bd2aa64b 100644 --- a/rustls/build.rs +++ b/rustls/build.rs @@ -1,17 +1,13 @@ -/// 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. +//! 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)"); -} +fn main() {} #[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..1df38484936 100644 --- a/rustls/examples/internal/test_ca.rs +++ b/rustls/examples/internal/test_ca.rs @@ -8,17 +8,22 @@ 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()); + let mut certified_keys = 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 = certified_keys .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 .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) } }; @@ -71,20 +80,15 @@ fn main() -> Result<(), Box> { _ => 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())), @@ -98,11 +102,11 @@ fn main() -> Result<(), Box> { let root = &certified_keys .get(&(Role::TrustAnchor, alg.inner)) .unwrap() - .cert; + .1; let intermediate = &certified_keys .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 }); + certified_keys.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..eac4149f9e6 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, "\"")?; diff --git a/rustls/src/builder.rs b/rustls/src/builder.rs index d86759c8339..61d8f70c3e3 100644 --- a/rustls/src/builder.rs +++ b/rustls/src/builder.rs @@ -1,5 +1,4 @@ use alloc::format; -use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt; use core::marker::PhantomData; @@ -8,6 +7,7 @@ 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)] @@ -183,7 +183,7 @@ impl fmt::Debug for ConfigBuilder", name,)) + f.debug_struct(&format!("ConfigBuilder<{name}, _>",)) .field("state", &self.state) .finish() } diff --git a/rustls/src/check.rs b/rustls/src/check.rs index 0925dd8a5d4..91abea97b8d 100644 --- a/rustls/src/check.rs +++ b/rustls/src/check.rs @@ -10,10 +10,9 @@ 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::handshake::HandshakeMessagePayload( + $payload_type(hm), + ), .. } => Ok(hm), payload => Err($crate::check::inappropriate_handshake_message( payload, &[$crate::ContentType::Handshake], @@ -26,10 +25,9 @@ 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::handshake::HandshakeMessagePayload( + $payload_type(hm), + ), .. } => Ok(hm), payload => Err($crate::check::inappropriate_handshake_message( &payload, @@ -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 index a4f397742e8..0cce1955d4f 100644 --- a/rustls/src/client/builder.rs +++ b/rustls/src/client/builder.rs @@ -1,4 +1,3 @@ -use alloc::sync::Arc; use alloc::vec::Vec; use core::marker::PhantomData; @@ -6,13 +5,14 @@ use pki_types::{CertificateDer, PrivateKeyDer}; use super::client_conn::Resumption; use crate::builder::{ConfigBuilder, WantsVerifier}; -use crate::client::{handy, ClientConfig, EchMode, ResolvesClientCert}; +use crate::client::{ClientConfig, EchMode, ResolvesClientCert, handy}; use crate::error::Error; use crate::key_log::NoKeyLog; -use crate::msgs::handshake::CertificateChain; +use crate::sign::{CertifiedKey, SingleCertAndKey}; +use crate::sync::Arc; use crate::versions::TLS13; use crate::webpki::{self, WebPkiServerVerifier}; -use crate::{compress, verify, versions, WantsVersions}; +use crate::{WantsVersions, compress, verify, versions}; impl ConfigBuilder { /// Enable Encrypted Client Hello (ECH) in the given mode. @@ -90,11 +90,11 @@ impl ConfigBuilder { /// 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}; + use crate::sync::Arc; + use crate::{ClientConfig, ConfigBuilder, WantsVerifier, verify}; /// Accessor for dangerous configuration options. #[derive(Debug)] @@ -184,13 +184,8 @@ impl ConfigBuilder { 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))) + let certified_key = CertifiedKey::from_der(cert_chain, key_der, &self.provider)?; + Ok(self.with_client_cert_resolver(Arc::new(SingleCertAndKey::from(certified_key)))) } /// Do not support client auth. @@ -203,9 +198,13 @@ impl ConfigBuilder { self, client_auth_cert_resolver: Arc, ) -> ClientConfig { + #[cfg(feature = "tls12")] + let require_ems = self.provider.fips(); + ClientConfig { provider: self.provider, alpn_protocols: Vec::new(), + check_selected_alpn: true, resumption: Resumption::default(), max_fragment_size: None, client_auth_cert_resolver, @@ -216,7 +215,7 @@ impl ConfigBuilder { enable_secret_extraction: false, enable_early_data: false, #[cfg(feature = "tls12")] - require_ems: cfg!(feature = "fips"), + require_ems, time_provider: self.time_provider, cert_compressors: compress::default_cert_compressors().to_vec(), cert_compression_cache: Arc::new(compress::CompressionCache::default()), diff --git a/rustls/src/client/client_conn.rs b/rustls/src/client/client_conn.rs index 852cf342a20..5aabb52cb7c 100644 --- a/rustls/src/client/client_conn.rs +++ b/rustls/src/client/client_conn.rs @@ -1,4 +1,3 @@ -use alloc::sync::Arc; use alloc::vec::Vec; use core::marker::PhantomData; use core::ops::{Deref, DerefMut}; @@ -8,6 +7,8 @@ use pki_types::{ServerName, UnixTime}; use super::handy::NoClientSessionStorage; use super::hs; +#[cfg(feature = "std")] +use crate::WantsVerifier; use crate::builder::ConfigBuilder; use crate::client::{EchMode, EchStatus}; use crate::common_state::{CommonState, Protocol, Side}; @@ -15,20 +16,20 @@ use crate::conn::{ConnectionCore, UnbufferedConnectionCommon}; use crate::crypto::{CryptoProvider, SupportedKxGroup}; use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme}; use crate::error::Error; +use crate::kernel::KernelConnection; use crate::log::trace; use crate::msgs::enums::NamedGroup; -use crate::msgs::handshake::ClientExtension; +use crate::msgs::handshake::ClientExtensionsInput; use crate::msgs::persist; -use crate::suites::SupportedCipherSuite; +use crate::suites::{ExtractedSecrets, SupportedCipherSuite}; +use crate::sync::Arc; #[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}; +use crate::{DistinguishedName, crypto}; +use crate::{KeyLog, WantsVersions, compress, sign, verify, versions}; /// A trait for the ability to store client session data, so that sessions /// can be resumed in future connections. @@ -151,7 +152,7 @@ pub trait ResolvesClientCert: fmt::Debug + Send + Sync { /// /// * [`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. +/// 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()`]. @@ -165,7 +166,28 @@ pub struct ClientConfig { /// 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 (according to `Arc::ptr_eq`). + /// + /// 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. pub resumption: Resumption, /// The maximum size of plaintext input to be emitted in a single TLS record. @@ -207,7 +229,8 @@ pub struct ClientConfig { /// 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, + /// The default is `true` if the configured [`CryptoProvider`] is + /// FIPS-compliant (i.e., [`CryptoProvider::fips()`] returns `true`), /// `false` otherwise. /// /// It must be set to `true` to meet FIPS requirement mentioned in section @@ -299,9 +322,9 @@ impl ClientConfig { // 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(), - )) + Self::builder_with_provider( + CryptoProvider::get_default_or_install_from_crate_features().clone(), + ) .with_protocol_versions(versions) .unwrap() } @@ -383,6 +406,10 @@ impl ClientConfig { danger::DangerousClientConfig { cfg: self } } + pub(super) fn needs_key_share(&self) -> bool { + self.supports_version(ProtocolVersion::TLSv1_3) + } + /// 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. @@ -514,10 +541,9 @@ pub enum Tls12Resumption { /// Container for unsafe APIs pub(super) mod danger { - use alloc::sync::Arc; - - use super::verify::ServerCertVerifier; use super::ClientConfig; + use super::verify::ServerCertVerifier; + use crate::sync::Arc; /// Accessor for dangerous configuration options. #[derive(Debug)] @@ -614,7 +640,6 @@ impl EarlyData { #[cfg(feature = "std")] mod connection { - use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt; use core::ops::{Deref, DerefMut}; @@ -622,13 +647,14 @@ mod connection { use pki_types::ServerName; - use super::ClientConnectionData; + use super::{ClientConnectionData, ClientExtensionsInput}; + use crate::ClientConfig; 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; + use crate::sync::Arc; /// Stub that implements io::Write and dispatches to `write_early_data`. pub struct WriteEarlyData<'a> { @@ -690,9 +716,7 @@ mod connection { /// 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(), - }) + Self::new_with_alpn(config.clone(), name, config.alpn_protocols.clone()) } /// Make a new ClientConnection with a session id generator. `config` controls how @@ -705,7 +729,7 @@ mod connection { inner: ConnectionCore::for_client_with_session_id_generator( config, name, - Vec::new(), + ClientExtensionsInput::default(), Protocol::Tcp, generator, )? @@ -713,6 +737,21 @@ mod connection { }) } + /// Make a new ClientConnection with custom ALPN protocols. + pub fn new_with_alpn( + config: Arc, + name: ServerName<'static>, + alpn_protocols: Vec>, + ) -> Result { + Ok(Self { + inner: ConnectionCommon::from(ConnectionCore::for_client( + config, + name, + ClientExtensionsInput::from_alpn(alpn_protocols), + Protocol::Tcp, + )?), + }) + } /// 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. /// @@ -765,6 +804,11 @@ mod connection { self.inner.core.data.ech_status } + /// Returns the number of TLS1.3 tickets that have been received. + pub fn tls13_tickets_received(&self) -> u32 { + self.inner.tls13_tickets_received + } + /// Return true if the connection was made with a `ClientConfig` that is FIPS compatible. /// /// This is different from [`crate::crypto::CryptoProvider::fips()`]: @@ -827,7 +871,7 @@ impl ConnectionCore { pub(crate) fn for_client( config: Arc, name: ServerName<'static>, - extra_exts: Vec, + extra_exts: ClientExtensionsInput<'static>, proto: Protocol, ) -> Result { let mut common_state = CommonState::new(Side::Client); @@ -844,6 +888,7 @@ impl ConnectionCore { sendable_plaintext: None, }; + let state = hs::start_handshake:: [u8; 32]>(name, extra_exts, config, &mut cx, None)?; Ok(Self::new(state, data, common_state)) @@ -852,7 +897,7 @@ impl ConnectionCore { pub(crate) fn for_client_with_session_id_generator( config: Arc, name: ServerName<'static>, - extra_exts: Vec, + extra_exts: ClientExtensionsInput<'static>, proto: Protocol, generator: Option [u8; 32]>, ) -> Result { @@ -889,10 +934,70 @@ 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 { + Self::new_with_extensions( + config.clone(), + name, + ClientExtensionsInput::from_alpn(config.alpn_protocols.clone()), + ) + } + + /// Make a new UnbufferedClientConnection with custom ALPN protocols. + pub fn new_with_alpn( + config: Arc, + name: ServerName<'static>, + alpn_protocols: Vec>, + ) -> Result { + Self::new_with_extensions( + config, + name, + ClientExtensionsInput::from_alpn(alpn_protocols), + ) + } + + fn new_with_extensions( + config: Arc, + name: ServerName<'static>, + extensions: ClientExtensionsInput<'static>, + ) -> Result { Ok(Self { - inner: ConnectionCore::for_client(config, name, Vec::new(), Protocol::Tcp)?.into(), + inner: UnbufferedConnectionCommon::from(ConnectionCore::for_client( + config, + name, + extensions, + Protocol::Tcp, + )?), }) } + + /// Extract secrets, so they can be used when configuring kTLS, for example. + /// Should be used with care as it exposes secret key material. + #[deprecated = "dangerous_extract_secrets() does not support session tickets or \ + key updates, use dangerous_into_kernel_connection() instead"] + pub fn dangerous_extract_secrets(self) -> Result { + self.inner.dangerous_extract_secrets() + } + + /// Extract secrets and a [`KernelConnection`] object. + /// + /// This allows you use rustls to manage keys and then manage encryption and + /// decryption yourself (e.g. for kTLS). + /// + /// Should be used with care as it exposes secret key material. + /// + /// See the [`crate::kernel`] documentations for details on prerequisites + /// for calling this method. + pub fn dangerous_into_kernel_connection( + self, + ) -> Result<(ExtractedSecrets, KernelConnection), Error> { + self.inner + .core + .dangerous_into_kernel_connection() + } + + /// Returns the number of TLS1.3 tickets that have been received. + pub fn tls13_tickets_received(&self) -> u32 { + self.inner.tls13_tickets_received + } } impl Deref for UnbufferedClientConnection { @@ -993,7 +1098,6 @@ impl std::error::Error for EarlyDataError {} #[derive(Debug)] pub struct ClientConnectionData { pub(super) early_data: EarlyData, - pub(super) resumption_ciphersuite: Option, pub(super) ech_status: EchStatus, } @@ -1001,7 +1105,6 @@ impl ClientConnectionData { fn new() -> Self { Self { early_data: EarlyData::new(), - resumption_ciphersuite: None, ech_status: EchStatus::NotOffered, } } diff --git a/rustls/src/client/common.rs b/rustls/src/client/common.rs index ad5e82b0f70..9afa0c3410f 100644 --- a/rustls/src/client/common.rs +++ b/rustls/src/client/common.rs @@ -1,12 +1,12 @@ 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}; +use crate::msgs::handshake::{CertificateChain, DistinguishedName, ProtocolName, ServerExtensions}; +use crate::sync::Arc; +use crate::{SignatureScheme, compress, sign}; #[derive(Debug)] pub(super) struct ServerCertDetails<'a> { @@ -35,14 +35,16 @@ impl<'a> ServerCertDetails<'a> { } pub(super) struct ClientHelloDetails { + pub(super) alpn_protocols: Vec, 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 { + pub(super) fn new(alpn_protocols: Vec, extension_order_seed: u16) -> Self { Self { + alpn_protocols, sent_extensions: Vec::new(), extension_order_seed, offered_cert_compression: false, @@ -51,14 +53,20 @@ impl ClientHelloDetails { pub(super) fn server_sent_unsolicited_extensions( &self, - received_exts: &[ServerExtension], + received_exts: &ServerExtensions<'_>, allowed_unsolicited: &[ExtensionType], ) -> bool { - for ext in received_exts { - let ext_type = ext.ext_type(); + 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); + trace!("Unsolicited extension {ext_type:?}"); return true; } } diff --git a/rustls/src/client/ech.rs b/rustls/src/client/ech.rs index 513852553e6..5675e71d092 100644 --- a/rustls/src/client/ech.rs +++ b/rustls/src/client/ech.rs @@ -1,34 +1,35 @@ use alloc::boxed::Box; use alloc::vec; use alloc::vec::Vec; +use core::iter; use pki_types::{DnsName, EchConfigListBytes, ServerName}; use subtle::ConstantTimeEq; +use crate::CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV; use crate::client::tls13; +use crate::crypto::SecureRandom; use crate::crypto::hash::Hash; use crate::crypto::hpke::{EncapsulatedSecret, Hpke, HpkePublicKey, HpkeSealer, HpkeSuite}; -use crate::crypto::SecureRandom; 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, + ClientExtensions, ClientHelloPayload, EchConfigContents, EchConfigPayload, Encoding, EncryptedClientHello, EncryptedClientHelloOuter, HandshakeMessagePayload, HandshakePayload, HelloRetryRequest, HpkeKeyConfig, HpkeSymmetricCipherSuite, PresharedKeyBinder, - PresharedKeyOffer, Random, ServerHelloPayload, + PresharedKeyOffer, Random, ServerHelloPayload, ServerNamePayload, }; use crate::msgs::message::{Message, MessagePayload}; use crate::msgs::persist; use crate::msgs::persist::Retrieved; use crate::tls13::key_schedule::{ - server_ech_hrr_confirmation_secret, KeyScheduleEarly, KeyScheduleHandshakeStart, + KeyScheduleEarly, KeyScheduleHandshakeStart, server_ech_hrr_confirmation_secret, }; -use crate::CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV; use crate::{ - AlertDescription, CommonState, EncryptedClientHelloError, Error, HandshakeType, + AlertDescription, ClientConfig, CommonState, EncryptedClientHelloError, Error, PeerIncompatible, PeerMisbehaved, ProtocolVersion, Tls13CipherSuite, }; @@ -93,9 +94,9 @@ 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], @@ -107,7 +108,7 @@ impl EchConfig { // 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))] + #[cfg_attr(not(feature = "logging"), allow(clippy::unused_enumerate_index))] for (_i, config) in ech_configs.iter().enumerate() { let contents = match config { EchConfigPayload::V18(contents) => contents, @@ -157,6 +158,22 @@ impl EchConfig { Err(EncryptedClientHelloError::NoCompatibleConfig.into()) } + pub(super) fn state( + &self, + server_name: ServerName<'static>, + config: &ClientConfig, + ) -> Result { + EchState::new( + self, + server_name.clone(), + config + .client_auth_cert_resolver + .has_certs(), + config.provider.secure_random, + config.enable_sni, + ) + } + /// Compute the HPKE `SetupBaseS` `info` parameter for this ECH configuration. /// /// See . @@ -202,7 +219,7 @@ impl EchGreaseConfig { secure_random: &'static dyn SecureRandom, inner_name: ServerName<'static>, outer_hello: &ClientHelloPayload, - ) -> Result { + ) -> Result { trace!("Preparing GREASE ECH extension"); // Pick a random config id. @@ -219,7 +236,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: PayloadU16::new(self.placeholder_key.0.clone()), symmetric_cipher_suites: vec![suite.sym], }, maximum_name_length: 0, @@ -251,14 +268,12 @@ 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: PayloadU16::new(grease_state.enc.0), + payload: PayloadU16::new(payload), + })) } } @@ -398,29 +413,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: PayloadU16::new(enc), + payload: PayloadU16::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 +440,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) } @@ -441,6 +450,7 @@ impl EchState { self, ks: &mut 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 +462,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( @@ -488,18 +500,18 @@ impl EchState { 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 => { + Some(ech_conf) if ech_conf.bytes().len() != 8 => { return Err({ common.send_fatal_alert( AlertDescription::DecodeError, PeerMisbehaved::IllegalHelloRetryRequestWithInvalidEch, ) - }) + }); } Some(ech_conf) => ech_conf, }; @@ -518,7 +530,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) @@ -562,7 +574,7 @@ impl EchState { compression_methods: outer_hello.compression_methods.clone(), // We will build up the included extensions ourselves. - extensions: vec![], + extensions: Box::new(ClientExtensions::default()), // 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 @@ -580,13 +592,11 @@ impl EchState { .collect(), }; + 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 +610,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 +625,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,44 +636,25 @@ 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( @@ -676,46 +665,41 @@ impl EchState { // 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(0).take(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(0).take(padding_len)); // Construct the inner hello message that will be used for the transcript. let inner_hello_msg = Message { @@ -731,10 +715,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. @@ -776,18 +759,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 +803,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. @@ -821,3 +831,253 @@ pub(crate) fn fatal_alert_required( PeerIncompatible::ServerRejectedEncryptedClientHello(retry_configs), ) } + +#[cfg(test)] +mod tests { + use std::string::String; + + use super::*; + use crate::enums::CipherSuite; + use crate::msgs::enums::{Compression, HpkeAead, HpkeKdf}; + use crate::msgs::handshake::{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: PayloadU16::new(vec![0; 32]), + 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()), + false, + &FixedRandom, + 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!() + } + } + + #[derive(Debug)] + struct FixedRandom; + + impl SecureRandom for FixedRandom { + fn fill(&self, buf: &mut [u8]) -> Result<(), crate::rand::GetRandomFailed> { + buf.fill(0x55); + Ok(()) + } + } +} diff --git a/rustls/src/client/handy.rs b/rustls/src/client/handy.rs index 78d9326d355..3ad3073bbd7 100644 --- a/rustls/src/client/handy.rs +++ b/rustls/src/client/handy.rs @@ -1,12 +1,9 @@ -use alloc::sync::Arc; - 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 crate::sync::Arc; +use crate::{NamedGroup, client, sign}; /// An implementer of `ClientSessionStore` which does nothing. #[derive(Debug)] @@ -43,7 +40,7 @@ mod cache { use crate::lock::Mutex; use crate::msgs::persist; - use crate::{limited_cache, NamedGroup}; + use crate::{NamedGroup, limited_cache}; const MAX_TLS13_TICKETS_PER_SERVER: usize = 8; @@ -212,35 +209,6 @@ impl client::ResolvesClientCert for FailResolveClientCert { } } -#[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. /// @@ -260,7 +228,7 @@ impl client::ResolvesClientCert for AlwaysResolvesClientRawPublicKeys { _root_hint_subjects: &[&[u8]], _sigschemes: &[SignatureScheme], ) -> Option> { - Some(Arc::clone(&self.0)) + Some(self.0.clone()) } fn only_raw_public_keys(&self) -> bool { @@ -279,27 +247,32 @@ impl client::ResolvesClientCert for AlwaysResolvesClientRawPublicKeys { #[cfg(test)] #[macro_rules_attribute::apply(test_for_each_provider)] mod tests { - use alloc::sync::Arc; use std::prelude::v1::*; use pki_types::{ServerName, UnixTime}; - use super::provider::cipher_suite; use super::NoClientSessionStorage; - use crate::client::ClientSessionStore; + use super::provider::cipher_suite; + use crate::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; + use crate::client::{ClientSessionStore, ResolvesClientCert}; 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::pki_types::CertificateDer; use crate::suites::SupportedCipherSuite; + use crate::sync::Arc; + use crate::{DigitallySignedStruct, Error, SignatureScheme, sign}; #[test] fn test_noclientsessionstorage_does_nothing() { let c = NoClientSessionStorage {}; let name = ServerName::try_from("example.com").unwrap(); let now = UnixTime::now(); + let server_cert_verifier: Arc = Arc::new(DummyServerCertVerifier); + let resolves_client_cert: Arc = Arc::new(DummyResolvesClientCert); c.set_kx_hint(name.clone(), NamedGroup::X25519); assert_eq!(None, c.kx_hint(&name)); @@ -321,6 +294,8 @@ mod tests { Arc::new(PayloadU16::empty()), &[], CertificateChain::default(), + &server_cert_verifier, + &resolves_client_cert, now, 0, true, @@ -341,6 +316,8 @@ mod tests { Arc::new(PayloadU16::empty()), &[], CertificateChain::default(), + &server_cert_verifier, + &resolves_client_cert, now, 0, 0, @@ -349,4 +326,65 @@ mod tests { ); assert!(c.take_tls13_ticket(&name).is_none()); } + + #[derive(Debug)] + struct DummyServerCertVerifier; + + impl ServerCertVerifier for DummyServerCertVerifier { + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn supported_verify_schemes(&self) -> Vec { + unreachable!() + } + } + + #[derive(Debug)] + struct DummyResolvesClientCert; + + impl ResolvesClientCert for DummyResolvesClientCert { + #[cfg_attr(coverage_nightly, coverage(off))] + fn resolve( + &self, + _root_hint_subjects: &[&[u8]], + _sigschemes: &[SignatureScheme], + ) -> Option> { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn has_certs(&self) -> bool { + unreachable!() + } + } } diff --git a/rustls/src/client/hs.rs b/rustls/src/client/hs.rs index 49ff3952a27..cd3439abe0e 100644 --- a/rustls/src/client/hs.rs +++ b/rustls/src/client/hs.rs @@ -1,6 +1,5 @@ use alloc::borrow::ToOwned; use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; use core::ops::Deref; @@ -10,96 +9,47 @@ use pki_types::ServerName; use super::reality; #[cfg(feature = "tls12")] use super::tls12; -use super::Tls12Resumption; +use super::{ResolvesClientCert, Tls12Resumption}; +use crate::SupportedCipherSuite; #[cfg(feature = "logging")] use crate::bs_debug; 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::client::{ClientConfig, EchMode, EchStatus, tls13}; +use crate::common_state::{CommonState, HandshakeKind, KxState, State}; use crate::conn::ConnectionRandoms; use crate::crypto::{ActiveKeyExchange, KeyExchangeAlgorithm}; -use crate::enums::{AlertDescription, CipherSuite, ContentType, HandshakeType, ProtocolVersion}; +use crate::enums::{ + AlertDescription, CertificateType, CipherSuite, ContentType, HandshakeType, ProtocolVersion, +}; use crate::error::{Error, PeerIncompatible, PeerMisbehaved}; use crate::hash_hs::HandshakeHashBuffer; use crate::log::{debug, trace}; use crate::msgs::base::Payload; use crate::msgs::codec::Codec; -use crate::msgs::enums::{ - CertificateType, Compression, ECPointFormat, ExtensionType, NamedGroup, PSKKeyExchangeMode, -}; +use crate::msgs::enums::{Compression, ExtensionType, NamedGroup}; use crate::msgs::handshake::{ - CertificateStatusRequest, ClientExtension, ClientHelloPayload, ClientSessionTicket, - ConvertProtocolNameList, HandshakeMessagePayload, HandshakePayload, HasServerExtensions, - HelloRetryRequest, KeyShareEntry, Random, SessionId, + CertificateStatusRequest, ClientExtensions, ClientExtensionsInput, ClientHelloPayload, + ClientSessionTicket, EncryptedClientHello, HandshakeMessagePayload, HandshakePayload, + HelloRetryRequest, KeyShareEntry, ProtocolName, PskKeyExchangeModes, Random, ServerNamePayload, + SessionId, SupportedEcPointFormats, SupportedProtocolVersions, TransportParameters, }; use crate::msgs::message::{Message, MessagePayload}; use crate::msgs::persist; +use crate::sync::Arc; use crate::tls13::key_schedule::KeyScheduleEarly; -use crate::SupportedCipherSuite; +use crate::verify::ServerCertVerifier; 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>; -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) - } - - #[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 - }); - - if let Some(resuming) = &found { - if cx.common.is_quic() { - cx.common.quic.params = resuming - .tls13() - .map(|v| v.quic_params()); - } - } - - found -} pub(super) fn start_handshake( server_name: ServerName<'static>, - extra_exts: Vec, + extra_exts: ClientExtensionsInput<'static>, config: Arc, cx: &mut ClientContext<'_>, session_id_generator: Option, @@ -115,7 +65,7 @@ where transcript_buffer.set_client_auth_enabled(); } - let mut resuming = find_session(&server_name, &config, cx); + let mut resuming = ClientSessionValue::retrieve(&server_name, &config, cx); // Initialize Reality state if configured let reality_state = config @@ -129,7 +79,6 @@ where // For Reality, use Reality's X25519 key exchange; otherwise use normal TLS let key_share = if reality_state.is_some() { // Reality provides its own key exchange (X25519) - // Set kx_state to X25519 group for Reality let x25519_group = config .find_kx_group(NamedGroup::X25519, ProtocolVersion::TLSv1_3) .ok_or(Error::General("X25519 group required for Reality".into()))?; @@ -145,37 +94,27 @@ where None }; - let mut session_id: Option = None; - if let Some(_resuming) = &mut resuming { - #[cfg(feature = "tls12")] - if let ClientSessionValue::Tls12(inner) = &mut _resuming.value { - // 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)?; - } - session_id = Some(inner.session_id); - } - - 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)?; + let session_id = match &mut resuming { + Some(_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)?; + } + Some(inner.session_id) } - Some(inner.session_id) + _ => None, } - _ => None::, } - } else { - debug!("Not resuming any session"); - None + _ => { + debug!("Not resuming any session"); + None + } }; // https://tools.ietf.org/html/rfc8446#appendix-D.4 @@ -190,16 +129,18 @@ where let random = Random::new(config.provider.secure_random)?; let extension_order_seed = crate::rand::random_u16(config.provider.secure_random)?; + let hello = ClientHelloDetails::new( + extra_exts + .protocols + .clone() + .unwrap_or_default(), + extension_order_seed, + ); + 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, - )?), + Some(EchMode::Enable(ech_config)) => { + Some(ech_config.state(server_name.clone(), &config)?) + } _ => None, }; @@ -214,10 +155,8 @@ where config, resuming, random, - #[cfg(feature = "tls12")] - using_ems: false, sent_tls13_fake_ccs: false, - hello: ClientHelloDetails::new(extension_order_seed), + hello, session_id, server_name, prev_ech_ext: None, @@ -228,10 +167,18 @@ where ) } + struct ExpectServerHello { input: ClientHelloInput, transcript_buffer: HandshakeHashBuffer, - early_key_schedule: Option, + // 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. + early_data_key_schedule: Option, offered_key_share: Option>, suite: Option, ech_state: Option, @@ -240,27 +187,30 @@ struct ExpectServerHello { struct ExpectServerHelloOrHelloRetryRequest { next: ExpectServerHello, - extra_exts: Vec, + extra_exts: ClientExtensionsInput<'static>, } -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, +pub(super) 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) session_id: SessionId, + pub(super) server_name: ServerName<'static>, + pub(super) prev_ech_ext: Option, } +/// 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, + extra_exts: ClientExtensionsInput<'static>, suite: Option, session_id_generator: Option, mut input: ClientHelloInput, @@ -285,49 +235,47 @@ where // 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 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 { + tls12: config.supports_version(ProtocolVersion::TLSv1_2) && !forbids_tls12, + tls13: config.supports_version(ProtocolVersion::TLSv1_3), + }; // 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 { + // 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(|skxg| supported_versions.any(|v| skxg.usable_for_version(v))) + .map(|skxg| skxg.name()) + .collect(), + ), + supported_versions: Some(supported_versions), + signature_schemes: Some( config .verifier .supported_verify_schemes(), ), - ClientExtension::ExtendedMasterSecretRequest, - ClientExtension::CertificateStatusRequest(CertificateStatusRequest::build_ocsp()), - ]; + extended_master_secret_request: Some(()), + certificate_status_request: Some(CertificateStatusRequest::build_ocsp()), + protocols: extra_exts.protocols.clone(), + ..Default::default() + }); + + match extra_exts.transport_parameters.clone() { + Some(TransportParameters::Quic(v)) => exts.transport_parameters = Some(v), + Some(TransportParameters::QuicDraft(v)) => exts.transport_parameters_draft = Some(v), + None => {} + }; - if support_tls13 { + if supported_versions.tls13 { if let Some(cas_extension) = config.verifier.root_hint_subjects() { - exts.push(ClientExtension::AuthorityNames(cas_extension.to_owned())); + exts.certificate_authority_names = Some(cas_extension.to_owned()); } } @@ -338,43 +286,43 @@ where .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.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, }; // Add key_share extension - // If Reality is enabled, use Reality's key_share entry - // Otherwise use normal TLS key_share + // If Reality is enabled, use Reality's key_share entry; otherwise use normal TLS key_share if let Some(reality_entry) = reality_key_share_entry { - exts.push(ClientExtension::KeyShare(vec![reality_entry])); + exts.key_shares = Some(vec![reality_entry]); } else if let Some(key_share) = &key_share { - debug_assert!(support_tls13); + debug_assert!(supported_versions.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 !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 + // (`find_kx_group` looks that up). if let Some((component_group, component_share)) = key_share .hybrid_component() @@ -388,71 +336,56 @@ where } } - 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.clone()); } - 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: false, + psk_dhe: true, + }); } - 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 - }; + 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 .client_auth_cert_resolver .only_raw_public_keys() { - exts.push(ClientExtension::ClientCertTypes(vec![ - CertificateType::RawPublicKey, - ])); + exts.client_certificate_types = Some(vec![CertificateType::RawPublicKey]); } if config .verifier .requires_raw_public_keys() { - exts.push(ClientExtension::ServerCertTypes(vec![ - CertificateType::RawPublicKey, - ])); + exts.server_certificate_types = Some(vec![CertificateType::RawPublicKey]); } - // Extra extensions must be placed before the PSK extension - exts.extend(extra_exts.iter().cloned()); - // 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 let Some(prev_ech_ext) = input.prev_ech_ext.take() { - exts.push(prev_ech_ext); + exts.encrypted_client_hello = Some(prev_ech_ext); } } @@ -461,24 +394,7 @@ where // 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 @@ -489,8 +405,11 @@ where 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, @@ -521,7 +440,9 @@ where chp_payload = ech_state.ech_hello(chp_payload, retryreq, &tls13_session)?; cx.data.ech_status = EchStatus::Offered; // 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. @@ -529,9 +450,7 @@ where 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()); + chp_payload.encrypted_client_hello = Some(grease_ext.clone()); cx.data.ech_status = EchStatus::Grease; // Store the GREASE ECH extension in case we need to carry it forward in a // subsequent hello. @@ -542,23 +461,16 @@ where } // 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)); // Compute Reality session_id BEFORE PSK binder to avoid invalidating the binder // Reality uses ClientHello with session_id=0 as AAD, so this order is safe if let Some(ref reality) = reality_state { // Step 1: Set session_id to zero temporarily let mut buffer = Vec::new(); - match &mut chp.payload { + match &mut chp.0 { HandshakePayload::ClientHello(c) => { c.session_id = SessionId { len: 32, @@ -583,7 +495,7 @@ where )?; // Step 5: Update session_id with computed Reality value - match &mut chp.payload { + match &mut chp.0 { HandshakePayload::ClientHello(c) => { c.session_id = SessionId { len: 32, @@ -594,7 +506,7 @@ where } } - 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 @@ -614,27 +526,31 @@ where }; // ref: https://github.com/shadow-tls/rustls/blob/c033c22cdbb6b08adf8b35571ee8427c70512d13/rustls/src/client/hs.rs#L365 - if let Some(generator) = session_id_generator { - let mut buffer = Vec::new(); - match &mut chp.payload { - HandshakePayload::ClientHello(c) => { - c.session_id = SessionId { - len: 32, - data: [0; 32], - }; + // Skip session_id_generator when Reality is active — Reality computes its own + // cryptographic session_id above, and overwriting it would break the handshake. + if reality_state.is_none() { + if let Some(generator) = session_id_generator { + let mut buffer = Vec::new(); + match &mut chp.0 { + HandshakePayload::ClientHello(c) => { + c.session_id = SessionId { + len: 32, + data: [0; 32], + }; + } + _ => unreachable!(), } - _ => unreachable!(), - } - chp.encode(&mut buffer); - let session_id = SessionId { - len: 32, - data: generator(&buffer), - }; - match &mut chp.payload { - HandshakePayload::ClientHello(c) => { - c.session_id = session_id; + chp.encode(&mut buffer); + let session_id = SessionId { + len: 32, + data: generator(&buffer), + }; + match &mut chp.0 { + HandshakePayload::ClientHello(c) => { + c.session_id = session_id; + } + _ => unreachable!(), } - _ => unreachable!(), } } @@ -660,57 +576,66 @@ where tls13::emit_fake_ccs(&mut input.sent_tls13_fake_ccs, cx.common); } - trace!("Sending ClientHello {:#?}", ch); + trace!("Sending ClientHello {ch:#?}"); transcript_buffer.add_message(&ch); cx.common.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 !cx.data.early_data.is_enabled() { + return schedule; + } - 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, + cx, + resuming_suite.common.hash_provider, + &schedule, + &mut input.sent_tls13_fake_ccs, + transcript_buffer, + random, + ); + schedule + }); let next = ExpectServerHello { input, transcript_buffer, - early_key_schedule, + early_data_key_schedule, offered_key_share: key_share, suite, ech_state, reality_state, }; - Ok(if support_tls13 && retryreq.is_none() { - Box::new(ExpectServerHelloOrHelloRetryRequest { next, extra_exts }) + Ok(if supported_versions.tls13 && retryreq.is_none() { + Box::new(ExpectServerHelloOrHelloRetryRequest { + next, + extra_exts: extra_exts.into_owned(), + }) } else { Box::new(next) }) } -/// Prepare resumption with the session state retrieved from storage. +/// 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 /// @@ -719,13 +644,10 @@ where /// (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. fn prepare_resumption<'a>( resuming: &'a Option>, - exts: &mut Vec, + exts: &mut ClientExtensions<'_>, suite: Option, cx: &mut ClientContext<'_>, config: &ClientConfig, @@ -738,7 +660,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; } @@ -749,9 +671,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 }; @@ -779,16 +699,14 @@ fn prepare_resumption<'a>( pub(super) fn process_alpn_protocol( common: &mut CommonState, - config: &ClientConfig, - proto: Option<&[u8]>, + offered_protocols: &[ProtocolName], + selected: Option<&ProtocolName>, + check_selected_offered: bool, ) -> Result<(), Error> { - common.alpn_protocol = proto.map(ToOwned::to_owned); + common.alpn_protocol = selected.map(ToOwned::to_owned); if let Some(alpn_protocol) = &common.alpn_protocol { - if !config - .alpn_protocols - .contains(alpn_protocol) - { + if check_selected_offered && !offered_protocols.contains(alpn_protocol) { return Err(common.send_fatal_alert( AlertDescription::IllegalParameter, PeerMisbehaved::SelectedUnofferedApplicationProtocol, @@ -802,7 +720,7 @@ pub(super) fn process_alpn_protocol( // 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() { + if common.is_quic() && common.alpn_protocol.is_none() && !offered_protocols.is_empty() { return Err(common.send_fatal_alert( AlertDescription::NoApplicationProtocol, Error::NoApplicationProtocol, @@ -814,7 +732,7 @@ pub(super) fn process_alpn_protocol( common .alpn_protocol .as_ref() - .map(|v| bs_debug::BsDebug(v)) + .map(|v| bs_debug::BsDebug(v.as_ref())) ); Ok(()) } @@ -823,46 +741,30 @@ 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(()), - } +) -> Result, Error> { + process_cert_type_extension( + common, + config + .verifier + .requires_raw_public_keys(), + server_cert_extension.copied(), + ExtensionType::ServerCertificateType, + ) } 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(()), - } +) -> Result, Error> { + process_cert_type_extension( + common, + config + .client_auth_cert_resolver + .only_raw_public_keys(), + client_cert_extension.copied(), + ExtensionType::ClientCertificateType, + ) } impl State for ExpectServerHello { @@ -876,7 +778,7 @@ impl State for ExpectServerHello { { let server_hello = require_handshake_msg!(m, HandshakeType::ServerHello, HandshakePayload::ServerHello)?; - trace!("We got ServerHello {:#?}", server_hello); + trace!("We got ServerHello {server_hello:#?}"); use crate::ProtocolVersion::{TLSv1_2, TLSv1_3}; let config = &self.input.config; @@ -884,7 +786,7 @@ impl State for ExpectServerHello { let server_version = if server_hello.legacy_version == TLSv1_2 { server_hello - .supported_versions() + .selected_version .unwrap_or(server_hello.legacy_version) } else { server_hello.legacy_version @@ -899,10 +801,7 @@ impl State for ExpectServerHello { return Err(PeerMisbehaved::OfferedEarlyDataWithOldProtocolVersion.into()); } - if server_hello - .supported_versions() - .is_some() - { + if server_hello.selected_version.is_some() { return Err({ cx.common.send_fatal_alert( AlertDescription::IllegalParameter, @@ -933,18 +832,11 @@ impl State for ExpectServerHello { }); } - 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) + .server_sent_unsolicited_extensions(server_hello, &allowed_unsolicited) { return Err(cx.common.send_fatal_alert( AlertDescription::UnsupportedExtension, @@ -956,13 +848,21 @@ impl State for ExpectServerHello { // Extract ALPN protocol if !cx.common.is_tls13() { - process_alpn_protocol(cx.common, config, server_hello.alpn_protocol())?; + process_alpn_protocol( + cx.common, + &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.ecpoints_extension() { - if !point_fmts.contains(&ECPointFormat::Uncompressed) { + if let Some(point_fmts) = &server_hello.ec_point_formats { + if !point_fmts.uncompressed { return Err(cx.common.send_fatal_alert( AlertDescription::HandshakeFailure, PeerMisbehaved::ServerHelloMustOfferUncompressedEcPoints, @@ -998,7 +898,7 @@ impl State for ExpectServerHello { }); } _ => { - debug!("Using ciphersuite {:?}", suite); + debug!("Using ciphersuite {suite:?}"); self.suite = Some(suite); cx.common.suite = Some(suite); } @@ -1015,54 +915,27 @@ impl State for ExpectServerHello { // 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, + self.early_data_key_schedule, // 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, + self.input, ) } #[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) + SupportedCipherSuite::Tls12(suite) => tls12::CompleteServerHelloHandling { + randoms, + transcript, + input: self.input, } + .handle_server_hello(cx, suite, server_hello, tls13_supported), } } @@ -1086,29 +959,37 @@ impl ExpectServerHelloOrHelloRetryRequest { HandshakeType::HelloRetryRequest, HandshakePayload::HelloRetryRequest )?; - trace!("Got HRR {:?}", hrr); + 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, - ) - }); + let config = &self.next.input.config; + + if let (None, Some(req_group)) = (&hrr.cookie, hrr.key_share) { + let offered_hybrid = offered_key_share + .hybrid_component() + .and_then(|(group_name, _)| { + config.find_kx_group(group_name, ProtocolVersion::TLSv1_3) + }) + .map(|skxg| skxg.name()); + + if req_group == offered_key_share.group() || Some(req_group) == offered_hybrid { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::IllegalHelloRetryRequestWithOfferedGroup, + ) + }); + } } // Or has an empty cookie. - if let Some(cookie) = cookie { + if let Some(cookie) = &hrr.cookie { if cookie.0.is_empty() { return Err({ cx.common.send_fatal_alert( @@ -1119,26 +1000,8 @@ impl ExpectServerHelloOrHelloRetryRequest { } } - // 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() { + if hrr.cookie.is_none() && hrr.key_share.is_none() { return Err({ cx.common.send_fatal_alert( AlertDescription::IllegalParameter, @@ -1170,7 +1033,7 @@ impl ExpectServerHelloOrHelloRetryRequest { } // Or asks us to talk a protocol we didn't offer, or doesn't support HRR at all. - match hrr.supported_versions() { + match hrr.supported_versions { Some(ProtocolVersion::TLSv1_3) => { cx.common.negotiated_version = Some(ProtocolVersion::TLSv1_3); } @@ -1185,7 +1048,6 @@ impl ExpectServerHelloOrHelloRetryRequest { } // 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( @@ -1196,7 +1058,7 @@ impl ExpectServerHelloOrHelloRetryRequest { }; // Or offers ECH related extensions when we didn't offer ECH. - if cx.data.ech_status == EchStatus::NotOffered && hrr.ech().is_some() { + if cx.data.ech_status == EchStatus::NotOffered && hrr.encrypted_client_hello.is_some() { return Err({ cx.common.send_fatal_alert( AlertDescription::UnsupportedExtension, @@ -1211,13 +1073,13 @@ impl ExpectServerHelloOrHelloRetryRequest { // 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; - } + // 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. + (Some(ech_state), Some(tls13_cs)) + if !ech_state.confirm_hrr_acceptance(hrr, tls13_cs, cx.common)? => + { + cx.data.ech_status = EchStatus::Rejected } (Some(_), None) => { unreachable!("ECH state should only be set when TLS 1.3 was negotiated") @@ -1244,7 +1106,7 @@ impl ExpectServerHelloOrHelloRetryRequest { cx.data.early_data.rejected(); } - let key_share = match req_group { + let key_share = match hrr.key_share { 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( @@ -1285,21 +1147,13 @@ impl State for ExpectServerHelloOrHelloRetryRequest { { match m.payload { MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::ServerHello(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::ServerHello(..)), .. } => self .into_expect_server_hello() .handle(cx, m), MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::HelloRetryRequest(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::HelloRetryRequest(..)), .. } => self.handle_hello_retry_request(cx, m), payload => Err(inappropriate_handshake_message( @@ -1315,13 +1169,88 @@ impl State for ExpectServerHelloOrHelloRetryRequest { } } -enum ClientSessionValue { +fn process_cert_type_extension( + common: &mut CommonState, + client_expects: bool, + server_negotiated: Option, + extension_type: ExtensionType, +) -> Result, Error> { + match (client_expects, server_negotiated) { + (true, Some(CertificateType::RawPublicKey)) => { + Ok(Some((extension_type, CertificateType::RawPublicKey))) + } + (true, _) => Err(common.send_fatal_alert( + AlertDescription::HandshakeFailure, + Error::PeerIncompatible(PeerIncompatible::IncorrectCertificateTypeExtension), + )), + (_, Some(CertificateType::RawPublicKey)) => { + unreachable!("Caught by `PeerMisbehaved::UnsolicitedEncryptedExtension`") + } + (_, _) => Ok(None), + } +} + +pub(super) enum ClientSessionValue { Tls13(persist::Tls13ClientSessionValue), #[cfg(feature = "tls12")] Tls12(persist::Tls12ClientSessionValue), } impl ClientSessionValue { + fn retrieve( + 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) + } + + #[cfg(not(feature = "tls12"))] + None + }) + .and_then(|resuming| { + resuming.compatible_config(&config.verifier, &config.client_auth_cert_resolver) + }) + .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 + }); + + if let Some(resuming) = &found { + if cx.common.is_quic() { + cx.common.quic.params = resuming + .tls13() + .map(|v| v.quic_params()); + } + } + + found + } + fn common(&self) -> &persist::ClientSessionCommon { match self { Self::Tls13(inner) => &inner.common, @@ -1337,6 +1266,22 @@ impl ClientSessionValue { Self::Tls12(_) => None, } } + + fn compatible_config( + self, + server_cert_verifier: &Arc, + client_creds: &Arc, + ) -> Option { + match &self { + Self::Tls13(v) => v + .compatible_config(server_cert_verifier, client_creds) + .then_some(self), + #[cfg(feature = "tls12")] + Self::Tls12(v) => v + .compatible_config(server_cert_verifier, client_creds) + .then_some(self), + } + } } impl Deref for ClientSessionValue { @@ -1346,19 +1291,3 @@ impl Deref for ClientSessionValue { 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 -} diff --git a/rustls/src/client/test.rs b/rustls/src/client/test.rs new file mode 100644 index 00000000000..69043c7c672 --- /dev/null +++ b/rustls/src/client/test.rs @@ -0,0 +1,790 @@ +#![cfg(any(feature = "ring", feature = "aws_lc_rs"))] +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; +use core::sync::atomic::{AtomicBool, Ordering}; + +use pki_types::{CertificateDer, ServerName}; + +use crate::client::{ClientConfig, ClientConnection, Resumption, Tls12Resumption}; +use crate::crypto::CryptoProvider; +use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme}; +use crate::msgs::base::PayloadU16; +use crate::msgs::codec::Reader; +use crate::msgs::enums::{Compression, NamedGroup}; +use crate::msgs::handshake::{ + ClientHelloPayload, HandshakeMessagePayload, HandshakePayload, HelloRetryRequest, Random, + ServerHelloPayload, SessionId, +}; +use crate::msgs::message::{Message, MessagePayload, OutboundOpaqueMessage}; +use crate::sync::Arc; +use crate::{Error, PeerIncompatible, PeerMisbehaved, RootCertStore}; + +#[macro_rules_attribute::apply(test_for_each_provider)] +mod tests { + use std::sync::OnceLock; + + use super::super::*; + use crate::client::AlwaysResolvesClientRawPublicKeys; + use crate::crypto::cipher::MessageEncrypter; + use crate::crypto::tls13::OkmBlock; + use crate::enums::CertificateType; + use crate::msgs::base::PayloadU8; + use crate::msgs::enums::ECCurveType; + use crate::msgs::handshake::{ + CertificateChain, EcParameters, HelloRetryRequestExtensions, KeyShareEntry, + ServerEcdhParams, ServerExtensions, ServerKeyExchange, ServerKeyExchangeParams, + ServerKeyExchangePayload, + }; + use crate::msgs::message::PlainMessage; + use crate::pki_types::pem::PemObject; + use crate::pki_types::{PrivateKeyDer, UnixTime}; + use crate::sign::CertifiedKey; + use crate::tls13::key_schedule::{derive_traffic_iv, derive_traffic_key}; + use crate::verify::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; + use crate::{DigitallySignedStruct, DistinguishedName, KeyLog, version}; + + /// 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_with_provider(super::provider::default_provider().into()) + .with_protocol_versions(&[&version::TLS13]) + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(); + 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_with_provider(super::provider::default_provider().into()) + .with_protocol_versions(&[&version::TLS13]) + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(), + ) + .unwrap(); + assert!( + !ch.cipher_suites + .contains(&CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + ); + } + + #[test] + fn test_client_does_not_offer_sha1() { + for version in crate::ALL_VERSIONS { + let config = + ClientConfig::builder_with_provider(super::provider::default_provider().into()) + .with_protocol_versions(&[version]) + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(); + 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_with_provider(super::provider::default_provider().into()) + .with_safe_default_protocol_versions() + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(); + let mut conn = + ClientConnection::new(config.into(), ServerName::try_from("localhost").unwrap()) + .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(PayloadU16::new(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() + ); + } + + #[cfg(feature = "tls12")] + #[test] + fn test_client_rejects_no_extended_master_secret_extension_when_require_ems_or_fips() { + let mut config = + ClientConfig::builder_with_provider(super::provider::default_provider().into()) + .with_safe_default_protocol_versions() + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(); + if config.provider.fips() { + assert!(config.require_ems); + } else { + config.require_ems = true; + } + + let config = Arc::new(config); + let mut conn = + ClientConnection::new(config.clone(), ServerName::try_from("localhost").unwrap()) + .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::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + 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(vec![DistinguishedName::from(b"hello".to_vec())]); + + for (protocol_version, cas_extension_expected) in + [(&version::TLS12, false), (&version::TLS13, true)] + { + let client_hello = client_hello_sent_for_config( + ClientConfig::builder_with_provider(super::provider::default_provider().into()) + .with_protocol_versions(&[protocol_version]) + .unwrap() + .dangerous() + .with_custom_certificate_verifier(Arc::new(cas_sending_server_verifier.clone())) + .with_no_client_auth(), + ) + .unwrap(); + assert_eq!( + client_hello + .extensions + .certificate_authority_names + .is_some(), + cas_extension_expected + ); + } + } + + /// Regression test for + #[cfg(feature = "tls12")] + #[test] + fn test_client_with_custom_verifier_can_accept_ecdsa_sha1_signatures() { + let verifier = Arc::new(ExpectSha1EcdsaVerifier::default()); + let config = ClientConfig::builder_with_provider(x25519_provider().into()) + .with_safe_default_protocol_versions() + .unwrap() + .dangerous() + .with_custom_certificate_verifier(verifier.clone()) + .with_no_client_auth(); + + let mut conn = + ClientConnection::new(config.into(), ServerName::try_from("localhost").unwrap()) + .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: PayloadU8::new(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 ServerCertVerifier for ExpectSha1EcdsaVerifier { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + assert_eq!(dss.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, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + todo!() + } + + fn supported_verify_schemes(&self) -> Vec { + vec![SignatureScheme::ECDSA_SHA1_Legacy] + } + } + + #[test] + fn test_client_requiring_rpk_rejects_server_that_only_offers_x509_id_by_omission() { + assert_eq!( + client_requiring_rpk_receives_server_ee(ServerExtensions::default()), + Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()) + ); + } + + #[test] + fn test_client_requiring_rpk_rejects_server_that_only_offers_x509_id() { + assert_eq!( + client_requiring_rpk_receives_server_ee(ServerExtensions { + server_certificate_type: Some(CertificateType::X509), + ..ServerExtensions::default() + }), + Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()) + ); + } + + #[test] + fn test_client_requiring_rpk_rejects_server_that_only_demands_x509_by_omission() { + assert_eq!( + client_requiring_rpk_receives_server_ee(ServerExtensions { + server_certificate_type: Some(CertificateType::RawPublicKey), + ..ServerExtensions::default() + }), + Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()) + ); + } + + #[test] + fn test_client_requiring_rpk_rejects_server_that_only_demands_x509() { + assert_eq!( + client_requiring_rpk_receives_server_ee(ServerExtensions { + client_certificate_type: Some(CertificateType::X509), + server_certificate_type: Some(CertificateType::RawPublicKey), + ..ServerExtensions::default() + }), + Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()) + ); + } + + #[test] + fn test_client_requiring_rpk_accepts_rpk_server() { + assert_eq!( + client_requiring_rpk_receives_server_ee(ServerExtensions { + client_certificate_type: Some(CertificateType::RawPublicKey), + server_certificate_type: Some(CertificateType::RawPublicKey), + ..ServerExtensions::default() + }), + Ok(()) + ); + } + + fn client_requiring_rpk_receives_server_ee( + encrypted_extensions: ServerExtensions<'_>, + ) -> Result<(), Error> { + let fake_server_crypto = Arc::new(FakeServerCrypto::new()); + let mut conn = ClientConnection::new( + client_config_for_rpk(fake_server_crypto.clone()).into(), + ServerName::try_from("localhost").unwrap(), + ) + .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: PayloadU16::new(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(PlainMessage::from(ee).borrow_outbound(), 0) + .unwrap(); + conn.read_tls(&mut enc_ee.encode().as_slice()) + .unwrap(); + conn.process_new_packets().map(|_| ()) + } + + fn client_config_for_rpk(key_log: Arc) -> ClientConfig { + let mut config = ClientConfig::builder_with_provider(x25519_provider().into()) + .with_protocol_versions(&[&version::TLS13]) + .unwrap() + .dangerous() + .with_custom_certificate_verifier(Arc::new(ServerVerifierRequiringRpk)) + .with_client_cert_resolver(Arc::new(AlwaysResolvesClientRawPublicKeys::new(Arc::new( + client_certified_key(), + )))); + config.key_log = key_log; + config + } + + fn client_certified_key() -> CertifiedKey { + let key = super::provider::default_provider() + .key_provider + .load_private_key(client_key()) + .unwrap(); + let public_key_as_cert = vec![CertificateDer::from( + key.public_key() + .unwrap() + .as_ref() + .to_vec(), + )]; + CertifiedKey::new(public_key_as_cert, 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() -> CryptoProvider { + // ensures X25519 is offered irrespective of cfg(feature = "fips"), which eases + // creation of fake server messages. + CryptoProvider { + kx_groups: vec![super::provider::kx_group::X25519], + ..super::provider::default_provider() + } + } + + #[derive(Clone, Debug)] + struct ServerVerifierWithAuthorityNames(Vec); + + impl ServerCertVerifier for ServerVerifierWithAuthorityNames { + fn root_hint_subjects(&self) -> Option<&[DistinguishedName]> { + Some(self.0.as_slice()) + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + unreachable!() + } + + fn supported_verify_schemes(&self) -> Vec { + vec![SignatureScheme::RSA_PKCS1_SHA1] + } + } + + #[derive(Debug)] + struct ServerVerifierRequiringRpk; + + impl ServerCertVerifier for ServerVerifierRequiringRpk { + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result { + todo!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + todo!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + todo!() + } + + fn supported_verify_schemes(&self) -> Vec { + vec![SignatureScheme::RSA_PKCS1_SHA1] + } + + fn requires_raw_public_keys(&self) -> bool { + true + } + } + + #[derive(Debug)] + struct FakeServerCrypto { + server_handshake_secret: OnceLock>, + } + + impl FakeServerCrypto { + fn new() -> Self { + Self { + server_handshake_secret: OnceLock::new(), + } + } + + fn server_handshake_encrypter(&self) -> Box { + let cipher_suite = super::provider::cipher_suite::TLS13_AES_128_GCM_SHA256 + .tls13() + .unwrap(); + + let secret = self + .server_handshake_secret + .get() + .unwrap(); + + 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.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 +#[cfg(all( + feature = "aws-lc-rs", + feature = "prefer-post-quantum", + not(feature = "fips") +))] +#[test] +fn hybrid_kx_component_share_offered_if_supported_separately() { + let ch = client_hello_sent_for_config( + ClientConfig::builder_with_provider(crate::crypto::aws_lc_rs::default_provider().into()) + .with_safe_default_protocol_versions() + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(), + ) + .unwrap(); + + let key_shares = ch + .extensions + .key_shares + .as_ref() + .unwrap(); + assert_eq!(key_shares.len(), 2); + assert_eq!(key_shares[0].group, NamedGroup::X25519MLKEM768); + assert_eq!(key_shares[1].group, NamedGroup::X25519); +} + +#[cfg(feature = "aws-lc-rs")] +#[test] +fn hybrid_kx_component_share_not_offered_unless_supported_separately() { + use crate::crypto::aws_lc_rs; + let provider = CryptoProvider { + kx_groups: vec![aws_lc_rs::kx_group::X25519MLKEM768], + ..aws_lc_rs::default_provider() + }; + let ch = client_hello_sent_for_config( + ClientConfig::builder_with_provider(provider.into()) + .with_safe_default_protocol_versions() + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(), + ) + .unwrap(); + + let key_shares = ch + .extensions + .key_shares + .as_ref() + .unwrap(); + assert_eq!(key_shares.len(), 1); + assert_eq!(key_shares[0].group, NamedGroup::X25519MLKEM768); +} + +fn client_hello_sent_for_config(config: ClientConfig) -> Result { + let mut conn = + ClientConnection::new(config.into(), ServerName::try_from("localhost").unwrap())?; + let mut bytes = Vec::new(); + conn.write_tls(&mut bytes).unwrap(); + + let message = OutboundOpaqueMessage::read(&mut Reader::init(&bytes)) + .unwrap() + .into_plain_message(); + + match Message::try_from(message).unwrap() { + Message { + payload: + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::ClientHello(ch)), + .. + }, + .. + } => Ok(ch), + other => panic!("unexpected message {other:?}"), + } +} + +fn client_hello_sent_with_session_id_generator( + config: ClientConfig, + generator: impl Fn(&[u8]) -> [u8; 32], +) -> Result { + let mut conn = ClientConnection::new_with_session_id_generator( + config.into(), + ServerName::try_from("localhost").unwrap(), + Some(generator), + )?; + let mut bytes = Vec::new(); + conn.write_tls(&mut bytes).unwrap(); + + let message = OutboundOpaqueMessage::read(&mut Reader::init(&bytes)) + .unwrap() + .into_plain_message(); + + match Message::try_from(message).unwrap() { + Message { + payload: + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::ClientHello(ch)), + .. + }, + .. + } => Ok(ch), + other => panic!("unexpected message {other:?}"), + } +} + +fn roots() -> RootCertStore { + let mut r = RootCertStore::empty(); + r.add(CertificateDer::from_slice(include_bytes!( + "../../../test-ca/rsa-2048/ca.der" + ))) + .unwrap(); + r +} + +/// Tests that when Reality is configured, the session_id_generator does not +/// overwrite Reality's cryptographically-computed session_id. +#[cfg(any(feature = "ring", feature = "aws_lc_rs"))] +#[test] +fn test_reality_session_id_not_overwritten_by_session_id_generator() { + use super::reality::RealityConfig; + + #[cfg(feature = "ring")] + let provider = crate::crypto::ring::default_provider(); + #[cfg(all(not(feature = "ring"), feature = "aws_lc_rs"))] + let provider = crate::crypto::aws_lc_rs::default_provider(); + + let server_pk = [1u8; 32]; + let short_id = vec![0x12, 0x34]; + let reality = RealityConfig::new(server_pk, short_id).unwrap(); + + let config = ClientConfig::builder_with_provider(provider.into()) + .with_protocol_versions(&[&crate::version::TLS13]) + .unwrap() + .with_root_certificates(roots()) + .with_reality(reality) + .with_no_client_auth(); + + // Get ClientHello with Reality only (no session_id_generator) + let ch_reality_only = client_hello_sent_for_config(config.clone()).unwrap(); + + // Get ClientHello with Reality + a session_id_generator that would produce all-0xFF + let ch_reality_with_generator = client_hello_sent_with_session_id_generator( + config, + |_| [0xFF; 32], + ) + .unwrap(); + + // Reality session_id should NOT be all zeros (it's encrypted data) + assert_ne!(ch_reality_only.session_id.data, [0u8; 32]); + + // With both Reality and session_id_generator, the session_id should come from + // Reality (not the generator's all-0xFF value) + assert_ne!( + ch_reality_with_generator.session_id.data, + [0xFF; 32], + "session_id_generator should not overwrite Reality's session_id" + ); + + // The session_id should still be non-zero (Reality-computed) + assert_ne!(ch_reality_with_generator.session_id.data, [0u8; 32]); +} diff --git a/rustls/src/client/tls12.rs b/rustls/src/client/tls12.rs index 0f098d3d5e2..0fc5acee04e 100644 --- a/rustls/src/client/tls12.rs +++ b/rustls/src/client/tls12.rs @@ -1,6 +1,5 @@ use alloc::borrow::ToOwned; use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; @@ -10,42 +9,42 @@ use subtle::ConstantTimeEq; use super::client_conn::ClientConnectionData; use super::hs::ClientContext; +use crate::ConnectionTrafficSecrets; use crate::check::{inappropriate_handshake_message, inappropriate_message}; use crate::client::common::{ClientAuthDetails, ServerCertDetails}; -use crate::client::{hs, ClientConfig}; +use crate::client::{ClientConfig, hs}; use crate::common_state::{CommonState, HandshakeKind, KxState, Side, State}; use crate::conn::ConnectionRandoms; +use crate::conn::kernel::{Direction, KernelContext, KernelState}; use crate::crypto::KeyExchangeAlgorithm; use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion}; use crate::error::{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::base::{Payload, PayloadU8, PayloadU16}; use crate::msgs::ccs::ChangeCipherSpecPayload; use crate::msgs::handshake::{ CertificateChain, ClientDhParams, ClientEcdhParams, ClientKeyExchangeParams, - HandshakeMessagePayload, HandshakePayload, NewSessionTicketPayload, ServerKeyExchangeParams, - SessionId, + HandshakeMessagePayload, HandshakePayload, NewSessionTicketPayload, + NewSessionTicketPayloadTls13, ServerKeyExchangeParams, SessionId, }; use crate::msgs::message::{Message, MessagePayload}; use crate::msgs::persist; use crate::sign::Signer; use crate::suites::{PartiallyExtractedSecrets, SupportedCipherSuite}; +use crate::sync::Arc; use crate::tls12::{self, ConnectionSecrets, Tls12CipherSuite}; use crate::verify::{self, DigitallySignedStruct}; mod server_hello { use super::*; - use crate::msgs::enums::ExtensionType; - use crate::msgs::handshake::{HasServerExtensions, ServerHelloPayload}; + use crate::client::hs::{ClientHelloInput, ClientSessionValue}; + use crate::msgs::handshake::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, + pub(in crate::client) input: ClientHelloInput, } impl CompleteServerHelloHandling { @@ -73,9 +72,42 @@ mod server_hello { }); } + // 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 self.input.resuming.is_none() + && !self.input.session_id.is_empty() + && self.input.session_id == server_hello.session_id + { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::ServerEchoedCompatibilitySessionId, + ) + }); + } + + let ClientHelloInput { + config, + server_name, + .. + } = self.input; + + let resuming_session = self + .input + .resuming + .and_then(|resuming| match resuming.value { + ClientSessionValue::Tls12(inner) => Some(inner), + ClientSessionValue::Tls13(_) => None, + }); + // Doing EMS? - self.using_ems = server_hello.ems_support_acked(); - if self.config.require_ems && !self.using_ems { + let using_ems = server_hello + .extended_master_secret_ack + .is_some(); + if config.require_ems && !using_ems { return Err({ cx.common.send_fatal_alert( AlertDescription::HandshakeFailure, @@ -86,7 +118,7 @@ mod server_hello { // 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,14 +130,14 @@ 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"); @@ -115,13 +147,13 @@ mod server_hello { } // 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( + config.key_log.log( "CLIENT_RANDOM", &secrets.randoms.client, &secrets.master_secret, @@ -143,12 +175,12 @@ mod server_hello { return if must_issue_new_ticket { Ok(Box::new(ExpectNewTicket { - config: self.config, + config, secrets, resuming_session: Some(resuming), session_id: server_hello.session_id, - server_name: self.server_name, - using_ems: self.using_ems, + server_name, + using_ems, transcript: self.transcript, resuming: true, cert_verified, @@ -156,12 +188,12 @@ mod server_hello { })) } else { Ok(Box::new(ExpectCcs { - config: self.config, + config, secrets, resuming_session: Some(resuming), session_id: server_hello.session_id, - server_name: self.server_name, - using_ems: self.using_ems, + server_name, + using_ems, transcript: self.transcript, ticket: None, resuming: true, @@ -174,12 +206,12 @@ mod server_hello { cx.common.handshake_kind = Some(HandshakeKind::Full); Ok(Box::new(ExpectCertificate { - config: self.config, + config, resuming_session: None, session_id: server_hello.session_id, - server_name: self.server_name, + server_name, randoms: self.randoms, - using_ems: self.using_ems, + using_ems, transcript: self.transcript, suite, may_send_cert_status, @@ -278,11 +310,7 @@ impl State for ExpectCertificateStatusOrServerKx<'_> { { match m.payload { MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::ServerKeyExchange(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::ServerKeyExchange(..)), .. } => Box::new(ExpectServerKx { config: self.config, @@ -298,11 +326,7 @@ impl State for ExpectCertificateStatusOrServerKx<'_> { }) .handle(cx, m), MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateStatus(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateStatus(..)), .. } => Box::new(ExpectCertificateStatus { config: self.config, @@ -504,10 +528,9 @@ fn emit_certificate( ) { 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); @@ -534,10 +557,9 @@ 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); @@ -559,10 +581,9 @@ 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); @@ -590,10 +611,9 @@ fn emit_finished( 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); @@ -643,10 +663,7 @@ impl State for ExpectServerDoneOrCertReq<'_> { if matches!( m.payload, MessagePayload::Handshake { - parsed: HandshakeMessagePayload { - payload: HandshakePayload::CertificateRequest(_), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateRequest(_)), .. } ) { @@ -731,7 +748,7 @@ impl State for ExpectCertificateRequest<'_> { HandshakePayload::CertificateRequest )?; self.transcript.add_message(&m); - debug!("Got CertificateRequest {:?}", certreq); + debug!("Got CertificateRequest {certreq:?}"); // The RFC jovially describes the design here as 'somewhat complicated' // and 'somewhat underspecified'. So thanks for that. @@ -810,11 +827,7 @@ impl State for ExpectServerDone<'_> { { match m.payload { MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::ServerHelloDone, - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::ServerHelloDone), .. } => {} payload => { @@ -1199,6 +1212,8 @@ impl ExpectFinished { .peer_certificates .clone() .unwrap_or_default(), + &self.config.verifier, + &self.config.client_auth_cert_resolver, now, lifetime, self.using_ems, @@ -1329,7 +1344,29 @@ impl State for ExpectTraffic { .extract_secrets(Side::Client) } + fn into_external_state(self: Box) -> Result, Error> { + Ok(self) + } + fn into_owned(self: Box) -> hs::NextState<'static> { self } } + +impl KernelState for ExpectTraffic { + fn update_secrets(&mut self, _: Direction) -> Result { + Err(Error::General( + "TLS 1.2 connections do not support traffic secret updates".into(), + )) + } + + fn handle_new_session_ticket( + &mut self, + _cx: &mut KernelContext<'_>, + _message: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + Err(Error::General( + "TLS 1.2 session tickets may not be sent once the handshake has completed".into(), + )) + } +} diff --git a/rustls/src/client/tls13.rs b/rustls/src/client/tls13.rs index 6794579ab8e..5f15f36c7b7 100644 --- a/rustls/src/client/tls13.rs +++ b/rustls/src/client/tls13.rs @@ -1,5 +1,4 @@ use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; @@ -7,15 +6,17 @@ use pki_types::ServerName; use subtle::ConstantTimeEq; use super::client_conn::ClientConnectionData; -use super::hs::ClientContext; +use super::hs::{ClientContext, ClientHelloInput, ClientSessionValue}; 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::client::{ClientConfig, ClientSessionStore, hs}; use crate::common_state::{ CommonState, HandshakeFlightTls13, HandshakeKind, KxState, Protocol, Side, State, }; use crate::conn::ConnectionRandoms; +use crate::conn::kernel::{Direction, KernelContext, KernelState}; +use crate::crypto::hash::Hash; use crate::crypto::{ActiveKeyExchange, SharedSecret}; use crate::enums::{ AlertDescription, ContentType, HandshakeType, ProtocolVersion, SignatureScheme, @@ -28,24 +29,25 @@ 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, + CERTIFICATE_MAX_SIZE_LIMIT, CertificatePayloadTls13, ClientExtensions, EchConfigPayload, + HandshakeMessagePayload, HandshakePayload, KeyShareEntry, NewSessionTicketPayloadTls13, + PresharedKeyBinder, PresharedKeyIdentity, PresharedKeyOffer, ServerExtensions, + ServerHelloPayload, }; use crate::msgs::message::{Message, MessagePayload}; -use crate::msgs::persist; +use crate::msgs::persist::{self, Retrieved}; use crate::sign::{CertifiedKey, Signer}; use crate::suites::PartiallyExtractedSecrets; +use crate::sync::Arc; use crate::tls13::key_schedule::{ - KeyScheduleEarly, KeyScheduleHandshake, KeySchedulePreHandshake, KeyScheduleTraffic, - ResumptionSecret, + KeyScheduleEarly, KeyScheduleHandshake, KeySchedulePreHandshake, KeyScheduleResumption, + KeyScheduleTraffic, }; 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::{ConnectionTrafficSecrets, KeyLog, compress, crypto}; // Extensions we expect in plaintext in the ServerHello. static ALLOWED_PLAINTEXT_EXTS: &[ExtensionType] = &[ @@ -63,26 +65,25 @@ static DISALLOWED_TLS13_EXTS: &[ExtensionType] = &[ ExtensionType::ExtendedMasterSecret, ]; +/// `early_data_key_schedule` is `Some` if we sent the +/// "early_data" extension to the server. 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, + early_data_key_schedule: Option, our_key_share: Box, - mut sent_tls13_fake_ccs: bool, server_hello_msg: &Message<'_>, ech_state: Option, + input: ClientHelloInput, ) -> hs::NextStateOrError<'static> { validate_server_hello(cx.common, server_hello)?; let their_key_share = server_hello - .key_share() + .key_share + .as_ref() .ok_or_else(|| { cx.common.send_fatal_alert( AlertDescription::MissingExtension, @@ -90,6 +91,23 @@ pub(super) fn handle_server_hello( ) })?; + let ClientHelloInput { + config, + resuming, + mut sent_tls13_fake_ccs, + mut hello, + server_name, + .. + } = input; + + let mut resuming_session = match resuming { + Some(Retrieved { + value: ClientSessionValue::Tls13(value), + .. + }) => Some(value), + _ => None, + }; + let our_key_share = KeyExchangeChoice::new(&config, cx, our_key_share, their_key_share) .map_err(|_| { cx.common.send_fatal_alert( @@ -98,52 +116,56 @@ pub(super) fn handle_server_hello( ) })?; - 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, - ) - }); - }; + let key_schedule_pre_handshake = match (server_hello.preshared_key, early_data_key_schedule) { + (Some(selected_psk), Some(early_key_schedule)) => { + match &resuming_session { + Some(resuming) => { + let Some(resuming_suite) = suite.can_resume_from(resuming.suite()) else { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::ResumptionOfferedWithIncompatibleCipherSuite, + ) + }); + }; + + // 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, + ) + }); + } - // 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, - ) - }); - } + if selected_psk != 0 { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::SelectedInvalidPsk, + ) + }); + } - if selected_psk != 0 { - return Err({ - cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::SelectedInvalidPsk, - ) - }); + debug!("Resuming using PSK"); + // The key schedule has been initialized and set in fill_in_psk_binder() + } + _ => { + return Err(PeerMisbehaved::SelectedUnofferedPsk.into()); + } } - - debug!("Resuming using PSK"); - // The key schedule has been initialized and set in fill_in_psk_binder() - } else { - return Err(PeerMisbehaved::SelectedUnofferedPsk.into()); + KeySchedulePreHandshake::from(early_key_schedule) + } + _ => { + 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) } - 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(); @@ -158,9 +180,21 @@ pub(super) fn handle_server_hello( // If we have ECH state, check that the server accepted our offer. if let Some(ech_state) = ech_state { + let Message { + payload: + MessagePayload::Handshake { + encoded: server_hello_encoded, + .. + }, + .. + } = &server_hello_msg + else { + unreachable!("ServerHello is a handshake message"); + }; cx.data.ech_status = match ech_state.confirm_acceptance( &mut key_schedule, server_hello, + server_hello_encoded, suite.common.hash_provider, )? { // The server accepted our ECH offer, so complete the inner transcript with the @@ -262,13 +296,11 @@ 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, - )); - } + if !server_hello.only_contains(ALLOWED_PLAINTEXT_EXTS) { + return Err(common.send_fatal_alert( + AlertDescription::UnsupportedExtension, + PeerMisbehaved::UnexpectedCleartextExtension, + )); } Ok(()) @@ -319,8 +351,20 @@ pub(super) fn fill_in_psk_binder( 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 @@ -329,13 +373,12 @@ pub(super) fn fill_in_psk_binder( pub(super) fn prepare_resumption( config: &ClientConfig, cx: &mut ClientContext<'_>, - resuming_session: &persist::Retrieved<&persist::Tls13ClientSessionValue>, - exts: &mut Vec, + resuming_session: &Retrieved<&persist::Tls13ClientSessionValue>, + 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()); // The EarlyData extension MUST be supplied together with the // PreSharedKey extension. let max_early_data_size = resuming_session.max_early_data_size(); @@ -343,7 +386,7 @@ pub(super) fn prepare_resumption( cx.data .early_data .enable(max_early_data_size as usize); - exts.push(ClientExtension::EarlyData); + exts.early_data_request = Some(()); } // Finally, and only for TLS1.3 with a ticket resumption, include a binder @@ -361,14 +404,14 @@ 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); } pub(super) fn derive_early_traffic_secret( key_log: &dyn KeyLog, cx: &mut ClientContext<'_>, - resuming_suite: &'static Tls13CipherSuite, + hash_alg: &'static dyn Hash, early_key_schedule: &KeyScheduleEarly, sent_tls13_fake_ccs: &mut bool, transcript_buffer: &HandshakeHashBuffer, @@ -377,7 +420,7 @@ pub(super) fn derive_early_traffic_secret( // For middlebox compatibility emit_fake_ccs(sent_tls13_fake_ccs, cx.common); - 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, @@ -409,15 +452,8 @@ pub(super) fn emit_fake_ccs(sent_tls13_fake_ccs: &mut bool, common: &mut CommonS 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, @@ -425,15 +461,11 @@ fn validate_encrypted_extensions( )); } - 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(common.send_fatal_alert( + AlertDescription::UnsupportedExtension, + PeerMisbehaved::DisallowedEncryptedExtension, + )); } Ok(()) @@ -464,34 +496,55 @@ impl State for ExpectEncryptedExtensions { HandshakeType::EncryptedExtensions, HandshakePayload::EncryptedExtensions )?; - debug!("TLS1.3 encrypted extensions: {:?}", exts); + debug!("TLS1.3 encrypted extensions: {exts:?}"); self.transcript.add_message(&m); 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())?; + hs::process_alpn_protocol( + cx.common, + &self.hello.alpn_protocols, + exts.selected_protocol + .as_ref() + .map(|protocol| protocol.as_ref()), + self.config.check_selected_alpn, + )?; + hs::process_client_cert_type_extension( + cx.common, + &self.config, + exts.client_certificate_type.as_ref(), + )?; + hs::process_server_cert_type_extension( + cx.common, + &self.config, + exts.server_certificate_type.as_ref(), + )?; - let ech_retry_configs = match (cx.data.ech_status, exts.server_ech_extension()) { + let ech_retry_configs = match (cx.data.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, - )) + )); } // 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, }; // QUIC transport parameters if cx.common.is_quic() { - match exts.quic_params_extension() { - Some(params) => cx.common.quic.params = Some(params), + match exts + .transport_parameters + .as_ref() + .or(exts.transport_parameters_draft.as_ref()) + { + Some(params) => cx.common.quic.params = Some(params.clone().into_vec()), None => { return Err(cx .common @@ -500,75 +553,79 @@ impl State for ExpectEncryptedExtensions { } } - 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; + match self.resuming_session { + Some(resuming_session) => { + let was_early_traffic = cx.common.early_traffic; + if was_early_traffic { + match exts.early_data_ack { + Some(()) => cx.data.early_data.accepted(), + None => { + cx.data.early_data.rejected(); + cx.common.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); - } - - 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); + 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); + } - 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 { + 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, + })) + } + _ => { + if exts.early_data_ack.is_some() { + 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, + }) }) - }) + } } } @@ -598,11 +655,7 @@ impl State for ExpectCertificateOrCompressedCertificateOrC { match m.payload { MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateTls13(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateTls13(..)), .. } => Box::new(ExpectCertificate { config: self.config, @@ -617,11 +670,7 @@ impl State for ExpectCertificateOrCompressedCertificateOrC }) .handle(cx, m), MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CompressedCertificate(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CompressedCertificate(..)), .. } => Box::new(ExpectCompressedCertificate { config: self.config, @@ -635,11 +684,7 @@ impl State for ExpectCertificateOrCompressedCertificateOrC }) .handle(cx, m), MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateRequestTls13(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateRequestTls13(..)), .. } => Box::new(ExpectCertificateRequest { config: self.config, @@ -691,11 +736,7 @@ impl State for ExpectCertificateOrCompressedCertificate { { match m.payload { MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateTls13(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateTls13(..)), .. } => Box::new(ExpectCertificate { config: self.config, @@ -710,11 +751,7 @@ impl State for ExpectCertificateOrCompressedCertificate { }) .handle(cx, m), MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CompressedCertificate(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CompressedCertificate(..)), .. } => Box::new(ExpectCompressedCertificate { config: self.config, @@ -764,11 +801,7 @@ impl State for ExpectCertificateOrCertReq { { match m.payload { MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateTls13(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateTls13(..)), .. } => Box::new(ExpectCertificate { config: self.config, @@ -783,11 +816,7 @@ impl State for ExpectCertificateOrCertReq { }) .handle(cx, m), MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateRequestTls13(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateRequestTls13(..)), .. } => Box::new(ExpectCertificateRequest { config: self.config, @@ -845,7 +874,7 @@ impl State for ExpectCertificateRequest { HandshakePayload::CertificateRequestTls13 )?; self.transcript.add_message(&m); - debug!("Got CertificateRequest {:?}", certreq); + debug!("Got CertificateRequest {certreq:?}"); // Fortunately the problems here in TLS1.2 and prior are corrected in // TLS1.3. @@ -859,10 +888,11 @@ impl State for ExpectCertificateRequest { )); } - 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() .filter(SignatureScheme::supported_in_tls13) @@ -876,7 +906,9 @@ impl State for ExpectCertificateRequest { } let compat_compressor = certreq - .certificate_compression_extension() + .extensions + .certificate_compression_algorithms + .as_deref() .and_then(|offered| { self.config .cert_compressors @@ -889,7 +921,10 @@ impl State for ExpectCertificateRequest { self.config .client_auth_cert_resolver .as_ref(), - certreq.authorities_extension(), + certreq + .extensions + .authority_names + .as_deref(), &compat_sigschemes, Some(certreq.context.0.clone()), compat_compressor, @@ -1005,10 +1040,9 @@ impl State for ExpectCompressedCertificate { let m = Message { version: ProtocolVersion::TLSv1_3, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::Certificate, - payload: HandshakePayload::CertificateTls13(cert_payload.into_owned()), - }), + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::CertificateTls13(cert_payload.into_owned()), + )), }; Box::new(ExpectCertificate { @@ -1068,15 +1102,7 @@ impl State for ExpectCertificate { )); } - 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() @@ -1220,10 +1246,9 @@ fn emit_compressed_certificate_tls13( return emit_certificate_tls13(flight, Some(certkey), 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( @@ -1237,10 +1262,9 @@ fn emit_certificate_tls13( 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), - }); + flight.add(HandshakeMessagePayload(HandshakePayload::CertificateTls13( + cert_payload, + ))); } fn emit_certverify_tls13( @@ -1253,20 +1277,18 @@ 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) { 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) { @@ -1276,10 +1298,9 @@ fn emit_end_of_early_data_tls13(transcript: &mut HandshakeHash, common: &mut Com 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); @@ -1402,7 +1423,8 @@ impl State for ExpectFinished { /* Now move to our application traffic keys. */ cx.common.check_aligned_handshake()?; - let key_schedule_traffic = key_schedule_pre_finished.into_traffic(cx.common); + let (key_schedule, resumption) = + key_schedule_pre_finished.into_traffic(cx.common, st.transcript.current_hash()); cx.common .start_traffic(&mut cx.sendable_plaintext); @@ -1414,12 +1436,12 @@ impl State for ExpectFinished { } let st = ExpectTraffic { - config: Arc::clone(&st.config), - session_storage: Arc::clone(&st.config.resumption.store), + config: st.config.clone(), + session_storage: st.config.resumption.store.clone(), server_name: st.server_name, suite: st.suite, - transcript: st.transcript, - key_schedule: key_schedule_traffic, + key_schedule, + resumption, _cert_verified: st.cert_verified, _sig_verified: st.sig_verified, _fin_verified: fin, @@ -1444,28 +1466,21 @@ struct ExpectTraffic { session_storage: Arc, server_name: ServerName<'static>, suite: &'static Tls13CipherSuite, - transcript: HandshakeHash, key_schedule: KeyScheduleTraffic, + resumption: KeyScheduleResumption, _cert_verified: verify::ServerCertVerified, _sig_verified: verify::HandshakeSignatureValid, _fin_verified: verify::FinishedMessageVerified, } impl ExpectTraffic { - fn handle_new_ticket_tls13( + fn handle_new_ticket_impl( &mut self, - cx: &mut ClientContext<'_>, + cx: &mut KernelContext<'_>, 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) + let secret = self + .resumption .derive_ticket_psk(&nst.nonce.0); let now = self.config.current_time()?; @@ -1473,27 +1488,29 @@ impl ExpectTraffic { #[allow(unused_mut)] let mut value = persist::Tls13ClientSessionValue::new( self.suite, - Arc::clone(&nst.ticket), + nst.ticket.clone(), secret.as_ref(), - cx.common - .peer_certificates - .clone() + cx.peer_certificates + .cloned() .unwrap_or_default(), + &self.config.verifier, + &self.config.client_auth_cert_resolver, now, nst.lifetime, nst.age_add, - nst.max_early_data_size() + nst.extensions + .max_early_data_size .unwrap_or_default(), ); - if cx.common.is_quic() { - if let Some(sz) = nst.max_early_data_size() { + if cx.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 { + if let Some(quic_params) = &cx.quic.params { value.set_quic_params(quic_params); } } @@ -1503,6 +1520,23 @@ impl ExpectTraffic { Ok(()) } + fn handle_new_ticket_tls13( + &mut self, + cx: &mut ClientContext<'_>, + nst: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + let mut kcx = KernelContext { + peer_certificates: cx.common.peer_certificates.as_ref(), + protocol: cx.common.protocol, + quic: &cx.common.quic, + }; + cx.common.tls13_tickets_received = cx + .common + .tls13_tickets_received + .saturating_add(1); + self.handle_new_ticket_impl(&mut kcx, nst) + } + fn handle_key_update( &mut self, common: &mut CommonState, @@ -1544,21 +1578,13 @@ impl State for ExpectTraffic { .common .take_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.handle_new_ticket_tls13(cx, &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.handle_key_update(cx.common, &key_update)?, payload => { return Err(inappropriate_handshake_message( &payload, @@ -1591,11 +1617,33 @@ impl State for ExpectTraffic { .extract_secrets(Side::Client) } + fn into_external_state(self: Box) -> Result, Error> { + Ok(self) + } + fn into_owned(self: Box) -> hs::NextState<'static> { self } } +impl KernelState for ExpectTraffic { + fn update_secrets(&mut self, dir: Direction) -> Result { + self.key_schedule + .refresh_traffic_secret(match dir { + Direction::Transmit => Side::Client, + Direction::Receive => Side::Server, + }) + } + + fn handle_new_session_ticket( + &mut self, + cx: &mut KernelContext<'_>, + message: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + self.handle_new_ticket_impl(cx, message) + } +} + struct ExpectQuicTraffic(ExpectTraffic); impl State for ExpectQuicTraffic { @@ -1627,7 +1675,27 @@ impl State for ExpectQuicTraffic { .export_keying_material(output, label, context) } + fn into_external_state(self: Box) -> Result, Error> { + Ok(self) + } + fn into_owned(self: Box) -> hs::NextState<'static> { self } } + +impl KernelState for ExpectQuicTraffic { + fn update_secrets(&mut self, _: Direction) -> Result { + Err(Error::General( + "KeyUpdate is not supported for QUIC connections".into(), + )) + } + + fn handle_new_session_ticket( + &mut self, + cx: &mut KernelContext<'_>, + nst: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + self.0.handle_new_ticket_impl(cx, nst) + } +} diff --git a/rustls/src/common_state.rs b/rustls/src/common_state.rs index 9b9ab3f7f4c..77ba0bbf57e 100644 --- a/rustls/src/common_state.rs +++ b/rustls/src/common_state.rs @@ -3,6 +3,7 @@ use alloc::vec::Vec; use pki_types::CertificateDer; +use crate::conn::kernel::KernelState; use crate::crypto::SupportedKxGroup; use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion}; use crate::error::{Error, InvalidMessage, PeerMisbehaved}; @@ -11,9 +12,9 @@ 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::enums::{AlertLevel, KeyUpdateRequest}; use crate::msgs::fragmenter::MessageFragmenter; -use crate::msgs::handshake::{CertificateChain, HandshakeMessagePayload}; +use crate::msgs::handshake::{CertificateChain, HandshakeMessagePayload, ProtocolName}; use crate::msgs::message::{ Message, MessagePayload, OutboundChunks, OutboundOpaqueMessage, OutboundPlainMessage, PlainMessage, @@ -24,7 +25,7 @@ use crate::suites::{PartiallyExtractedSecrets, SupportedCipherSuite}; use crate::tls12::ConnectionSecrets; use crate::unbuffered::{EncryptError, InsufficientSizeError}; use crate::vecbuf::ChunkVecBuffer; -use crate::{quic, record_layer, PeerIncompatible}; +use crate::{quic, record_layer}; /// Connection state common to both client and server connections. pub struct CommonState { @@ -34,12 +35,14 @@ pub struct CommonState { pub(crate) record_layer: record_layer::RecordLayer, pub(crate) suite: Option, pub(crate) kx_state: KxState, - pub(crate) alpn_protocol: Option>, + 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 we signaled end of stream. + pub(crate) has_sent_close_notify: bool, /// If the peer has signaled end of stream. pub(crate) has_received_close_notify: bool, #[cfg(feature = "std")] @@ -57,6 +60,7 @@ pub struct CommonState { temper_counters: TemperCounters, pub(crate) refresh_traffic_keys_pending: bool, pub(crate) fips: bool, + pub(crate) tls13_tickets_received: u32, } impl CommonState { @@ -74,6 +78,7 @@ impl CommonState { may_receive_application_data: false, early_traffic: false, sent_fatal_alert: false, + has_sent_close_notify: false, has_received_close_notify: false, #[cfg(feature = "std")] has_seen_eof: false, @@ -88,6 +93,7 @@ impl CommonState { temper_counters: TemperCounters::default(), refresh_traffic_keys_pending: false, fips: false, + tls13_tickets_received: 0, } } @@ -256,7 +262,9 @@ impl CommonState { self.refresh_traffic_keys_pending = true; } _ => { - error!("traffic keys exhausted, closing connection to prevent security failure"); + error!( + "traffic keys exhausted, closing connection to prevent security failure" + ); self.send_close_notify(); return Err(EncryptError::EncryptExhausted); } @@ -359,7 +367,9 @@ impl CommonState { self.refresh_traffic_keys_pending = true; } _ => { - error!("traffic keys exhausted, closing connection to prevent security failure"); + error!( + "traffic keys exhausted, closing connection to prevent security failure" + ); self.send_close_notify(); return; } @@ -471,6 +481,7 @@ impl CommonState { } pub(crate) fn take_received_plaintext(&mut self, bytes: Payload<'_>) { + self.temper_counters.received_app_data(); self.received_plaintext .append(bytes.into_vec()); } @@ -495,7 +506,7 @@ impl CommonState { } fn send_warning_alert(&mut self, desc: AlertDescription) { - warn!("Sending warning alert {:?}", desc); + warn!("Sending warning alert {desc:?}"); self.send_warning_alert_no_log(desc); } @@ -573,6 +584,7 @@ impl CommonState { } debug!("Sending warning alert {:?}", AlertDescription::CloseNotify); self.sent_fatal_alert = true; + self.has_sent_close_notify = true; self.send_warning_alert_no_log(AlertDescription::CloseNotify); } @@ -859,6 +871,10 @@ pub(crate) trait State: Send + Sync { fn handle_decrypt_error(&self) {} + fn into_external_state(self: Box) -> Result, Error> { + Err(Error::HandshakeNotComplete) + } + fn into_owned(self: Box) -> Box + 'static>; } @@ -900,35 +916,6 @@ enum Limit { No, } -#[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, -} - -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, - )), - } - } -} - -#[derive(Debug)] -pub(crate) enum RawKeyNegotationResult { - Negotiated(ExtensionType), - NotNegotiated, - Err(Error), -} - /// Tracking technically-allowed protocol actions /// that we limit to avoid denial-of-service vectors. struct TemperCounters { @@ -978,6 +965,14 @@ impl TemperCounters { } } } + + fn received_app_data(&mut self) { + self.allowed_key_update_requests = Self::INITIAL_KEY_UPDATE_REQUESTS; + } + + // cf. BoringSSL `kMaxKeyUpdates` + // + const INITIAL_KEY_UPDATE_REQUESTS: u8 = 32; } impl Default for TemperCounters { @@ -991,9 +986,7 @@ impl Default for TemperCounters { // a second request after this is fatal. allowed_renegotiation_requests: 1, - // cf. BoringSSL `kMaxKeyUpdates` - // - allowed_key_update_requests: 32, + allowed_key_update_requests: Self::INITIAL_KEY_UPDATE_REQUESTS, // At most two CCS are allowed: one after each ClientHello (recall a second // ClientHello happens after a HelloRetryRequest). diff --git a/rustls/src/compress.rs b/rustls/src/compress.rs index e6a2f028d96..14df129032e 100644 --- a/rustls/src/compress.rs +++ b/rustls/src/compress.rs @@ -34,7 +34,6 @@ #[cfg(feature = "std")] use alloc::collections::VecDeque; -use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt::Debug; #[cfg(feature = "std")] @@ -44,6 +43,7 @@ use crate::enums::CertificateCompressionAlgorithm; use crate::msgs::base::{Payload, PayloadU24}; use crate::msgs::codec::Codec; use crate::msgs::handshake::{CertificatePayloadTls13, CompressedCertificatePayload}; +use crate::sync::Arc; /// Returns the supported `CertDecompressor` implementations enabled /// by crate features. @@ -125,8 +125,9 @@ 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); } @@ -358,7 +359,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); } } @@ -384,7 +385,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) } diff --git a/rustls/src/conn.rs b/rustls/src/conn.rs index f69f6408e96..344eb22bcbc 100644 --- a/rustls/src/conn.rs +++ b/rustls/src/conn.rs @@ -5,19 +5,23 @@ use core::ops::{Deref, DerefMut, Range}; #[cfg(feature = "std")] use std::io; -use crate::common_state::{CommonState, Context, IoState, State, DEFAULT_BUFFER_LIMIT}; +use kernel::KernelConnection; + +use crate::common_state::{CommonState, Context, DEFAULT_BUFFER_LIMIT, IoState, State}; use crate::enums::{AlertDescription, ContentType, ProtocolVersion}; use crate::error::{Error, PeerMisbehaved}; use crate::log::trace; +use crate::msgs::deframer::DeframerIter; 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::suites::ExtractedSecrets; use crate::vecbuf::ChunkVecBuffer; +// pub so that it can be re-exported from the crate root +pub mod kernel; pub(crate) mod unbuffered; #[cfg(feature = "std")] @@ -25,14 +29,14 @@ mod connection { use alloc::vec::Vec; use core::fmt::Debug; use core::ops::{Deref, DerefMut}; - use std::io; + use std::io::{self, BufRead, Read}; + use crate::ConnectionCommon; 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)] @@ -47,7 +51,7 @@ mod 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 { + pub fn read_tls(&mut self, rd: &mut dyn Read) -> Result { match self { Self::Client(conn) => conn.read_tls(rd), Self::Server(conn) => conn.read_tls(rd), @@ -108,7 +112,7 @@ mod connection { pub fn complete_io(&mut self, io: &mut T) -> Result<(usize, usize), io::Error> where Self: Sized, - T: io::Read + io::Write, + T: Read + io::Write, { match self { Self::Client(conn) => conn.complete_io(io), @@ -173,7 +177,7 @@ mod connection { pub(super) has_seen_eof: bool, } - impl Reader<'_> { + 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) { @@ -189,9 +193,23 @@ mod connection { (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 [`ConnectionCommon`] 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 io::Read for Reader<'_> { + 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 @@ -257,8 +275,31 @@ mod connection { } } - const UNEXPECTED_EOF_MESSAGE: &str = - "peer closed connection without sending TLS close_notify: \ + 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. @@ -429,18 +470,7 @@ impl ConnectionCommon { /// 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), - }) + self.core.dangerous_extract_secrets() } /// Sets a limit on the internal buffers used to buffer @@ -557,9 +587,8 @@ impl ConnectionCommon { /// once. /// /// The return value is the number of bytes read from and written - /// to `io`, respectively. - /// - /// This function will block if `io` blocks. + /// 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. @@ -578,8 +607,8 @@ impl ConnectionCommon { 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 = self.is_handshaking(); if !self.wants_write() && !self.wants_read() { @@ -588,20 +617,36 @@ impl ConnectionCommon { } while self.wants_write() { - match self.write_tls(io)? { - 0 => { + match self.write_tls(io) { + Ok(0) => { io.flush()?; return Ok((rdlen, wrlen)); // EOF. } - n => wrlen += n, + Ok(n) => wrlen += n, + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + blocked_write = Some(err); + break; + } + Err(err) => return Err(err), } } - io.flush()?; + 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, self.wants_read()) { + return match wrlen { + 0 => Err(blocked_write.unwrap()), + _ => Ok((rdlen, wrlen)), + }; + } + while !eof && self.wants_read() { let read_size = match self.read_tls(io) { Ok(0) => { @@ -612,7 +657,11 @@ impl ConnectionCommon { rdlen += n; Some(n) } - Err(ref err) if err.kind() == io::ErrorKind::Interrupted => None, // nothing to do + 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() { @@ -620,19 +669,23 @@ impl ConnectionCommon { } } - 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 let Err(e) = self.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 = self.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, self.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. @@ -640,11 +693,13 @@ impl ConnectionCommon { 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)), - (..) => {} + let blocked = blocked_write.zip(blocked_read); + match (eof, until_handshaked, self.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)), + _ => {} } } } @@ -773,6 +828,7 @@ impl From> for ConnectionCommon { pub struct UnbufferedConnectionCommon { pub(crate) core: ConnectionCore, wants_write: bool, + emitted_peer_closed_state: bool, } impl From> for UnbufferedConnectionCommon { @@ -780,10 +836,19 @@ impl From> for UnbufferedConnectionCommon { Self { core, wants_write: false, + emitted_peer_closed_state: false, } } } +impl UnbufferedConnectionCommon { + /// 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.core.dangerous_extract_secrets() + } +} + impl Deref for UnbufferedConnectionCommon { type Target = CommonState; @@ -966,7 +1031,7 @@ impl ConnectionCore { Ok(None) if !self.hs_deframer.is_aligned() => { return Err( PeerMisbehaved::RejectedEarlyDataInterleavedWithHandshakeMessage.into(), - ) + ); } // failed decryption during trial decryption. @@ -1107,7 +1172,7 @@ impl ConnectionCore { Err(err) => { return Err(self .common_state - .send_fatal_alert(AlertDescription::DecodeError, err)); + .send_fatal_alert(AlertDescription::from(err), err)); } }; @@ -1121,6 +1186,52 @@ impl ConnectionCore { .process_main_protocol(msg, state, &mut self.data, sendable_plaintext) } + pub(crate) fn dangerous_extract_secrets(self) -> Result { + Ok(self + .dangerous_into_kernel_connection()? + .0) + } + + pub(crate) fn dangerous_into_kernel_connection( + self, + ) -> Result<(ExtractedSecrets, KernelConnection), Error> { + if !self + .common_state + .enable_secret_extraction + { + return Err(Error::General("Secret extraction is disabled".into())); + } + + if self.common_state.is_handshaking() { + return Err(Error::HandshakeNotComplete); + } + + if !self + .common_state + .sendable_tls + .is_empty() + { + return Err(Error::General( + "cannot convert into an KernelConnection while there are still buffered TLS records to send" + .into() + )); + } + + let state = self.state?; + + let record_layer = &self.common_state.record_layer; + let secrets = state.extract_secrets()?; + let secrets = ExtractedSecrets { + tx: (record_layer.write_seq(), secrets.tx), + rx: (record_layer.read_seq(), secrets.rx), + }; + + let state = state.into_external_state()?; + let external = KernelConnection::new(state, self.common_state)?; + + Ok((secrets, external)) + } + pub(crate) fn export_keying_material>( &self, mut output: T, diff --git a/rustls/src/conn/kernel.rs b/rustls/src/conn/kernel.rs new file mode 100644 index 00000000000..b90fad390dd --- /dev/null +++ b/rustls/src/conn/kernel.rs @@ -0,0 +1,268 @@ +//! 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. +//! +//! # Entry Point +//! The entry points into this API are +//! [`UnbufferedClientConnection::dangerous_into_kernel_connection`][client-into] +//! and +//! [`UnbufferedServerConnection::dangerous_into_kernel_connection`][server-into]. +//! +//! In order to actually create an [`KernelConnection`] all of the following +//! must be true: +//! - the connection must have completed its handshake, +//! - the connection must have no buffered TLS data waiting to be sent, and, +//! - the config used to create the connection must have `enable_extract_secrets` +//! set to true. +//! +//! This sounds fairly complicated to achieve at first glance. However, if you +//! drive an unbuffered connection through the handshake until it returns +//! [`WriteTraffic`] then it will end up in an appropriate state to convert +//! into an external connection. +//! +//! [client-into]: crate::client::UnbufferedClientConnection::dangerous_into_kernel_connection +//! [server-into]: crate::server::UnbufferedServerConnection::dangerous_into_kernel_connection +//! [`WriteTraffic`]: crate::unbuffered::ConnectionState::WriteTraffic +//! +//! # 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::ClientConnectionData; +use crate::common_state::Protocol; +use crate::msgs::codec::Codec; +use crate::msgs::handshake::{CertificateChain, NewSessionTicketPayloadTls13}; +use crate::quic::Quic; +use crate::{CommonState, ConnectionTrafficSecrets, Error, ProtocolVersion, 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, + + peer_certificates: Option>, + quic: Quic, + + negotiated_version: ProtocolVersion, + protocol: Protocol, + suite: SupportedCipherSuite, + + _data: PhantomData, +} + +impl KernelConnection { + pub(crate) fn new(state: Box, common: CommonState) -> Result { + Ok(Self { + state, + + peer_certificates: common.peer_certificates, + quic: common.quic, + negotiated_version: common + .negotiated_version + .ok_or(Error::HandshakeNotComplete)?, + protocol: common.protocol, + suite: common + .suite + .ok_or(Error::HandshakeNotComplete)?, + + _data: PhantomData, + }) + } + + /// Retrieves the ciphersuite 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> { + // The sequence number always starts at 0 after a key update. + self.state + .update_secrets(Direction::Transmit) + .map(|secret| (0, secret)) + } + + /// 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_secrets(Direction::Receive) + .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::{ContentType, HandshakeType}; + /// use rustls::kernel::KernelConnection; + /// use rustls::client::ClientConnectionData; + /// + /// # 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)?; + let mut cx = KernelContext { + peer_certificates: self.peer_certificates.as_ref(), + protocol: self.protocol, + quic: &self.quic, + }; + self.state + .handle_new_session_ticket(&mut cx, &nst) + } +} + +pub(crate) trait KernelState: Send + Sync { + /// Update the traffic secret for the specified direction on the connection. + fn update_secrets(&mut self, dir: Direction) -> 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( + &mut self, + cx: &mut KernelContext<'_>, + message: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error>; +} + +pub(crate) struct KernelContext<'a> { + pub(crate) peer_certificates: Option<&'a CertificateChain<'static>>, + pub(crate) protocol: Protocol, + pub(crate) quic: &'a Quic, +} + +impl KernelContext<'_> { + pub(crate) fn is_quic(&self) -> bool { + self.protocol == Protocol::Quic + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum Direction { + Transmit, + Receive, +} diff --git a/rustls/src/conn/unbuffered.rs b/rustls/src/conn/unbuffered.rs index ef8146e098e..77927b5587f 100644 --- a/rustls/src/conn/unbuffered.rs +++ b/rustls/src/conn/unbuffered.rs @@ -7,10 +7,10 @@ use core::{fmt, mem}; use std::error::Error as StdError; use super::UnbufferedConnectionCommon; +use crate::Error; 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 @@ -19,7 +19,7 @@ impl UnbufferedConnectionCommon { &'c mut self, incoming_tls: &'i mut [u8], ) -> UnbufferedStatus<'c, 'i, ClientConnectionData> { - self.process_tls_records_common(incoming_tls, |_| None, |_, _, ()| unreachable!()) + self.process_tls_records_common(incoming_tls, |_| false, |_, _| unreachable!()) } } @@ -32,36 +32,39 @@ impl UnbufferedConnectionCommon { ) -> 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(), + |conn| conn.peek_early_data().is_some(), + |conn, incoming_tls| ReadEarlyData::new(conn, incoming_tls).into(), ) } } impl UnbufferedConnectionCommon { - fn process_tls_records_common<'c, 'i, T>( + fn process_tls_records_common<'c, 'i>( &'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>, + mut early_data_available: impl FnMut(&mut Self) -> bool, + early_data_state: impl FnOnce(&'c mut Self, &'i mut [u8]) -> 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 early_data_available(self) { + break ( + buffer.pending_discard(), + early_data_state(self, incoming_tls), + ); } - if let Some(chunk) = self + if !self .core .common_state .received_plaintext - .pop() + .is_empty() { break ( buffer.pending_discard(), - ReadTraffic::new(self, incoming_tls, chunk).into(), + ReadTraffic::new(self, incoming_tls).into(), ); } @@ -77,7 +80,13 @@ impl UnbufferedConnectionCommon { ); } - let deframer_output = + let deframer_output = if self + .core + .common_state + .has_received_close_notify + { + None + } else { match self .core .deframe(None, buffer.filled_mut(), &mut buffer_progress) @@ -90,7 +99,8 @@ impl UnbufferedConnectionCommon { }; } Ok(r) => r, - }; + } + }; if let Some(msg) = deframer_output { let mut state = @@ -131,6 +141,18 @@ impl UnbufferedConnectionCommon { .core .common_state .has_received_close_notify + && !self.emitted_peer_closed_state + { + self.emitted_peer_closed_state = true; + break (buffer.pending_discard(), ConnectionState::PeerClosed); + } else if self + .core + .common_state + .has_received_close_notify + && self + .core + .common_state + .has_sent_close_notify { break (buffer.pending_discard(), ConnectionState::Closed); } else if self @@ -185,7 +207,26 @@ pub enum ConnectionState<'c, 'i, Data> { /// the received data. ReadTraffic(ReadTraffic<'c, 'i, Data>), - /// Connection has been cleanly closed by the peer + /// Connection has been cleanly closed by the peer. + /// + /// This state is encountered at most once by each connection -- it is + /// "edge" triggered, rather than "level" triggered. + /// + /// It delimits the data received from the peer, meaning you can be sure you + /// have received all the data the peer sent. + /// + /// No further application data will be received from the peer, so no further + /// `ReadTraffic` states will be produced. + /// + /// However, it is possible to _send_ further application data via `WriteTraffic` + /// states, or close the connection cleanly by calling + /// [`WriteTraffic::queue_close_notify()`]. + PeerClosed, + + /// Connection has been cleanly closed by both us and the peer. + /// + /// This is a terminal state. No other states will be produced for this + /// connection. Closed, /// One, or more, early (RTT-0) data records are available @@ -262,6 +303,8 @@ impl fmt::Debug for ConnectionState<'_, '_, Data> { match self { Self::ReadTraffic(..) => f.debug_tuple("ReadTraffic").finish(), + Self::PeerClosed => write!(f, "PeerClosed"), + Self::Closed => write!(f, "Closed"), Self::ReadEarlyData(..) => f.debug_tuple("ReadEarlyData").finish(), @@ -283,101 +326,97 @@ impl fmt::Debug for ConnectionState<'_, '_, Data> { /// Application data is available pub struct ReadTraffic<'c, 'i, Data> { - _conn: &'c mut UnbufferedConnectionCommon, + conn: &'c mut UnbufferedConnectionCommon, // for forwards compatibility; to support in-place decryption in the future _incoming_tls: &'i mut [u8], - chunk: Vec, - taken: bool, + + // owner of the latest chunk obtained in `next_record`, as borrowed by + // `AppDataRecord` + chunk: Option>, } impl<'c, 'i, Data> ReadTraffic<'c, 'i, Data> { - fn new( - _conn: &'c mut UnbufferedConnectionCommon, - _incoming_tls: &'i mut [u8], - chunk: Vec, - ) -> Self { + fn new(conn: &'c mut UnbufferedConnectionCommon, _incoming_tls: &'i mut [u8]) -> Self { Self { - _conn, + conn, _incoming_tls, - chunk, - taken: false, + chunk: None, } } /// 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 { + self.chunk = self + .conn + .core + .common_state + .received_plaintext + .pop(); + self.chunk.as_ref().map(|chunk| { + Ok(AppDataRecord { discard: 0, - payload: &self.chunk, - })) - } + payload: 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()) - } + self.conn + .core + .common_state + .received_plaintext + .peek() + .and_then(|ch| NonZeroUsize::new(ch.len())) } } /// Early application-data is available. pub struct ReadEarlyData<'c, 'i, Data> { - _conn: &'c mut UnbufferedConnectionCommon, + conn: &'c mut UnbufferedConnectionCommon, + // for forwards compatibility; to support in-place decryption in the future _incoming_tls: &'i mut [u8], - chunk: Vec, - taken: bool, + + // owner of the latest chunk obtained in `next_record`, as borrowed by + // `AppDataRecord` + chunk: Option>, } -impl<'c, 'i, Data> ReadEarlyData<'c, 'i, Data> { +impl<'c, 'i> ReadEarlyData<'c, 'i, ServerConnectionData> { fn new( - _conn: &'c mut UnbufferedConnectionCommon, + conn: &'c mut UnbufferedConnectionCommon, _incoming_tls: &'i mut [u8], - chunk: Vec, ) -> Self { Self { - _conn, + conn, _incoming_tls, - chunk, - taken: false, + chunk: None, } } -} -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 { + self.chunk = self.conn.pop_early_data(); + self.chunk.as_ref().map(|chunk| { + Ok(AppDataRecord { discard: 0, - payload: &self.chunk, - })) - } + payload: 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()) - } + self.conn + .peek_early_data() + .and_then(|ch| NonZeroUsize::new(ch.len())) } } @@ -385,8 +424,8 @@ impl ReadEarlyData<'_, '_, ServerConnectionData> { 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 + /// 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 @@ -532,8 +571,7 @@ impl fmt::Display for EncodeError { match self { Self::InsufficientSize(InsufficientSizeError { required_size }) => write!( f, - "cannot encode due to insufficient size, {} bytes are required", - required_size + "cannot encode due to insufficient size, {required_size} bytes are required" ), Self::AlreadyEncoded => "cannot encode, data has already been encoded".fmt(f), } diff --git a/rustls/src/crypto/aws_lc_rs/hpke.rs b/rustls/src/crypto/aws_lc_rs/hpke.rs index 80514de19c3..2e5c3a5ece6 100644 --- a/rustls/src/crypto/aws_lc_rs/hpke.rs +++ b/rustls/src/crypto/aws_lc_rs/hpke.rs @@ -1,11 +1,9 @@ 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}; @@ -18,9 +16,11 @@ 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::crypto::tls13::{HkdfExpander, HkdfPrkExtract, HkdfUsingHmac, expand}; use crate::msgs::enums::{HpkeAead, HpkeKdf, HpkeKem}; use crate::msgs::handshake::HpkeSymmetricCipherSuite; +#[cfg(feature = "std")] +use crate::sync::Arc; use crate::{Error, OtherError}; /// Default [RFC 9180] Hybrid Public Key Encryption (HPKE) suites supported by aws-lc-rs cryptography. @@ -576,7 +576,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 +616,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)?; diff --git a/rustls/src/crypto/aws_lc_rs/mod.rs b/rustls/src/crypto/aws_lc_rs/mod.rs index c31874aa92d..1ab57dae6c5 100644 --- a/rustls/src/crypto/aws_lc_rs/mod.rs +++ b/rustls/src/crypto/aws_lc_rs/mod.rs @@ -1,4 +1,3 @@ -use alloc::sync::Arc; use alloc::vec::Vec; // aws-lc-rs has a -- roughly -- ring-compatible API, so we just reuse all that @@ -9,16 +8,19 @@ 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::crypto::{CryptoProvider, KeyProvider, SecureRandom, SupportedKxGroup}; use crate::enums::SignatureScheme; use crate::rand::GetRandomFailed; use crate::sign::SigningKey; use crate::suites::SupportedCipherSuite; +use crate::sync::Arc; use crate::webpki::WebPkiSupportedAlgorithms; use crate::{Error, OtherError}; /// Hybrid public key encryption (HPKE). pub mod hpke; +/// Post-quantum secure algorithms. +pub(crate) mod pq; /// Using software keys for authentication. pub mod sign; @@ -30,7 +32,7 @@ pub(crate) mod hmac; pub(crate) mod kx; #[path = "../ring/quic.rs"] pub(crate) mod quic; -#[cfg(any(feature = "std", feature = "hashbrown"))] +#[cfg(feature = "std")] pub(crate) mod ticketer; #[cfg(feature = "tls12")] pub(crate) mod tls12; @@ -50,7 +52,7 @@ pub fn default_provider() -> CryptoProvider { fn default_kx_groups() -> Vec<&'static dyn SupportedKxGroup> { #[cfg(feature = "fips")] { - ALL_KX_GROUPS + DEFAULT_KX_GROUPS .iter() .filter(|cs| cs.fips()) .copied() @@ -58,7 +60,7 @@ fn default_kx_groups() -> Vec<&'static dyn SupportedKxGroup> { } #[cfg(not(feature = "fips"))] { - ALL_KX_GROUPS.to_vec() + DEFAULT_KX_GROUPS.to_vec() } } @@ -157,8 +159,10 @@ static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms all: &[ webpki_algs::ECDSA_P256_SHA256, webpki_algs::ECDSA_P256_SHA384, + webpki_algs::ECDSA_P256_SHA512, webpki_algs::ECDSA_P384_SHA256, webpki_algs::ECDSA_P384_SHA384, + webpki_algs::ECDSA_P384_SHA512, webpki_algs::ECDSA_P521_SHA256, webpki_algs::ECDSA_P521_SHA384, webpki_algs::ECDSA_P521_SHA512, @@ -169,7 +173,9 @@ static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms 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, + webpki_algs::RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS, + webpki_algs::RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS, + webpki_algs::RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS, ], mapping: &[ // Note: for TLS1.2 the curve is not fixed by SignatureScheme. For TLS1.3 it is. @@ -191,7 +197,11 @@ static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms ), ( SignatureScheme::ECDSA_NISTP521_SHA512, - &[webpki_algs::ECDSA_P521_SHA512], + &[ + webpki_algs::ECDSA_P521_SHA512, + webpki_algs::ECDSA_P384_SHA512, + webpki_algs::ECDSA_P256_SHA512, + ], ), (SignatureScheme::ED25519, &[webpki_algs::ED25519]), ( @@ -224,15 +234,45 @@ static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = 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::{SECP256R1, SECP384R1, X25519}; + pub use super::pq::{MLKEM768, MLKEM1024, SECP256R1MLKEM768, X25519MLKEM768}; } -pub use kx::ALL_KX_GROUPS; -#[cfg(any(feature = "std", feature = "hashbrown"))] -pub use ticketer::Ticketer; +/// 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] = &[ + #[cfg(feature = "prefer-post-quantum")] + kx_group::X25519MLKEM768, + kx_group::X25519, + kx_group::SECP256R1, + kx_group::SECP384R1, + #[cfg(not(feature = "prefer-post-quantum"))] + kx_group::X25519MLKEM768, +]; -use super::SupportedKxGroup; +/// A list of all the key exchange groups supported by this provider. +pub static ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[ + #[cfg(feature = "prefer-post-quantum")] + kx_group::X25519MLKEM768, + #[cfg(feature = "prefer-post-quantum")] + kx_group::SECP256R1MLKEM768, + kx_group::X25519, + kx_group::SECP256R1, + kx_group::SECP384R1, + #[cfg(not(feature = "prefer-post-quantum"))] + kx_group::X25519MLKEM768, + #[cfg(not(feature = "prefer-post-quantum"))] + kx_group::SECP256R1MLKEM768, + kx_group::MLKEM768, + kx_group::MLKEM1024, +]; + +#[cfg(feature = "std")] +pub use ticketer::Ticketer; /// Compatibility shims between ring 0.16.x and 0.17.x API mod ring_shim { @@ -267,12 +307,16 @@ pub(super) fn unspecified_err(_e: aws_lc_rs::error::Unspecified) -> Error { #[cfg(test)] mod tests { + use std::collections::HashSet; + #[cfg(feature = "fips")] #[test] fn default_suites_are_fips() { - assert!(super::DEFAULT_CIPHER_SUITES - .iter() - .all(|scs| scs.fips())); + assert!( + super::DEFAULT_CIPHER_SUITES + .iter() + .all(|scs| scs.fips()) + ); } #[cfg(not(feature = "fips"))] @@ -280,4 +324,25 @@ mod tests { fn default_suites() { assert_eq!(super::DEFAULT_CIPHER_SUITES, super::ALL_CIPHER_SUITES); } + + #[test] + fn certificate_sig_algs() { + // `all` should not contain duplicates (not incorrect, but a waste of time) + assert_eq!( + super::SUPPORTED_SIG_ALGS + .all + .iter() + .map(|alg| { + ( + alg.public_key_alg_id() + .as_ref() + .to_vec(), + alg.signature_alg_id().as_ref().to_vec(), + ) + }) + .collect::>() + .len(), + super::SUPPORTED_SIG_ALGS.all.len(), + ); + } } diff --git a/rustls-post-quantum/src/hybrid.rs b/rustls/src/crypto/aws_lc_rs/pq/hybrid.rs similarity index 74% rename from rustls-post-quantum/src/hybrid.rs rename to rustls/src/crypto/aws_lc_rs/pq/hybrid.rs index 88d4c04855d..8e30d782edc 100644 --- a/rustls-post-quantum/src/hybrid.rs +++ b/rustls/src/crypto/aws_lc_rs/pq/hybrid.rs @@ -1,10 +1,10 @@ -use watfaq_rustls::crypto::{ - ActiveKeyExchange, CompletedKeyExchange, SharedSecret, SupportedKxGroup, -}; -use watfaq_rustls::ffdhe_groups::FfdheGroup; -use watfaq_rustls::{Error, NamedGroup, ProtocolVersion}; +use alloc::boxed::Box; +use alloc::vec::Vec; -use crate::INVALID_KEY_SHARE; +use super::INVALID_KEY_SHARE; +use crate::crypto::{ActiveKeyExchange, CompletedKeyExchange, SharedSecret, SupportedKxGroup}; +use crate::ffdhe_groups::FfdheGroup; +use crate::{Error, NamedGroup, ProtocolVersion}; /// A generalization of hybrid key exchange. #[derive(Debug)] @@ -68,6 +68,27 @@ impl SupportedKxGroup for Hybrid { self.name } + fn fips(&self) -> bool { + // 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(), + } + } + fn usable_for_version(&self, version: ProtocolVersion) -> bool { version == ProtocolVersion::TLSv1_3 } @@ -153,6 +174,7 @@ impl Layout { 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], @@ -163,8 +185,14 @@ impl Layout { } Some(match self.post_quantum_first { - true => share.split_at(post_quantum_share_len), - false => share.split_at(self.classical_share_len), + 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) + } }) } diff --git a/rustls-post-quantum/src/mlkem.rs b/rustls/src/crypto/aws_lc_rs/pq/mlkem.rs similarity index 57% rename from rustls-post-quantum/src/mlkem.rs rename to rustls/src/crypto/aws_lc_rs/pq/mlkem.rs index 3aba3a52a1a..9e48fedc432 100644 --- a/rustls-post-quantum/src/mlkem.rs +++ b/rustls/src/crypto/aws_lc_rs/pq/mlkem.rs @@ -1,18 +1,22 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; + use aws_lc_rs::kem; -use watfaq_rustls::crypto::{ - ActiveKeyExchange, CompletedKeyExchange, SharedSecret, SupportedKxGroup, -}; -use watfaq_rustls::ffdhe_groups::FfdheGroup; -use watfaq_rustls::{Error, NamedGroup, ProtocolVersion}; -use crate::INVALID_KEY_SHARE; +use super::INVALID_KEY_SHARE; +use crate::crypto::{ActiveKeyExchange, CompletedKeyExchange, SharedSecret, SupportedKxGroup}; +use crate::ffdhe_groups::FfdheGroup; +use crate::{Error, NamedGroup, ProtocolVersion}; #[derive(Debug)] -pub(crate) struct MlKem768; +pub(crate) struct MlKem { + pub(crate) alg: &'static kem::Algorithm, + pub(crate) group: NamedGroup, +} -impl SupportedKxGroup for MlKem768 { +impl SupportedKxGroup for MlKem { fn start(&self) -> Result, Error> { - let decaps_key = kem::DecapsulationKey::generate(&kem::ML_KEM_768) + let decaps_key = kem::DecapsulationKey::generate(self.alg) .map_err(|_| Error::General("key generation failed".into()))?; let pub_key_bytes = decaps_key @@ -23,12 +27,13 @@ impl SupportedKxGroup for MlKem768 { Ok(Box::new(Active { decaps_key: Box::new(decaps_key), encaps_key_bytes: Vec::from(pub_key_bytes.as_ref()), + group: self.group, })) } 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 encaps_key = + kem::EncapsulationKey::new(self.alg, client_share).map_err(|_| INVALID_KEY_SHARE)?; let (ciphertext, shared_secret) = encaps_key .encapsulate() @@ -46,7 +51,23 @@ impl SupportedKxGroup for MlKem768 { } fn name(&self) -> NamedGroup { - NamedGroup::MLKEM768 + self.group + } + + fn fips(&self) -> bool { + // 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::super::fips() } fn usable_for_version(&self, version: ProtocolVersion) -> bool { @@ -57,6 +78,7 @@ impl SupportedKxGroup for MlKem768 { struct Active { decaps_key: Box>, encaps_key_bytes: Vec, + group: NamedGroup, } impl ActiveKeyExchange for Active { @@ -81,6 +103,6 @@ impl ActiveKeyExchange for Active { } fn group(&self) -> NamedGroup { - NamedGroup::MLKEM768 + self.group } } diff --git a/rustls/src/crypto/aws_lc_rs/pq/mod.rs b/rustls/src/crypto/aws_lc_rs/pq/mod.rs new file mode 100644 index 00000000000..2a61dd88232 --- /dev/null +++ b/rustls/src/crypto/aws_lc_rs/pq/mod.rs @@ -0,0 +1,62 @@ +use aws_lc_rs::kem; + +use crate::crypto::SupportedKxGroup; +use crate::crypto::aws_lc_rs::kx_group; +use crate::crypto::aws_lc_rs::pq::mlkem::MlKem; +use crate::{Error, NamedGroup, PeerMisbehaved}; + +mod hybrid; +mod mlkem; + +/// 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, + }, +}; + +/// This is the [SECP256R1MLKEM768] key exchange. +/// +/// [SECP256R1MLKEM768]: +pub static SECP256R1MLKEM768: &dyn SupportedKxGroup = &hybrid::Hybrid { + classical: kx_group::SECP256R1, + post_quantum: MLKEM768, + name: NamedGroup::secp256r1MLKEM768, + layout: hybrid::Layout { + 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, +}; + +const INVALID_KEY_SHARE: Error = Error::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare); + +const X25519_LEN: usize = 32; +const SECP256R1_LEN: usize = 65; +const MLKEM768_CIPHERTEXT_LEN: usize = 1088; +const MLKEM768_ENCAP_LEN: usize = 1184; diff --git a/rustls/src/crypto/aws_lc_rs/sign.rs b/rustls/src/crypto/aws_lc_rs/sign.rs index 91d9110e982..0c874abf922 100644 --- a/rustls/src/crypto/aws_lc_rs/sign.rs +++ b/rustls/src/crypto/aws_lc_rs/sign.rs @@ -2,19 +2,18 @@ use alloc::boxed::Box; use alloc::string::ToString; -use alloc::sync::Arc; 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 pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer, SubjectPublicKeyInfoDer, 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::crypto::signer::{Signer, SigningKey, public_key_to_spki}; use crate::enums::{SignatureAlgorithm, SignatureScheme}; use crate::error::Error; +use crate::sync::Arc; /// Parse `der` as any supported key encoding/type, returning /// the first which works. @@ -118,7 +117,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 { @@ -132,7 +131,7 @@ impl SigningKey for RsaSigningKey { ALL_RSA_SCHEMES .iter() .find(|scheme| offered.contains(scheme)) - .map(|scheme| RsaSigner::new(Arc::clone(&self.key), *scheme)) + .map(|scheme| RsaSigner::new(self.key.clone(), *scheme)) } fn public_key(&self) -> Option> { @@ -252,7 +251,7 @@ impl SigningKey for EcdsaSigningKey { fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { if offered.contains(&self.scheme) { Some(Box::new(EcdsaSigner { - key: Arc::clone(&self.key), + key: self.key.clone(), scheme: self.scheme, })) } else { @@ -347,7 +346,7 @@ impl SigningKey for Ed25519SigningKey { fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { if offered.contains(&self.scheme) { Some(Box::new(Ed25519Signer { - key: Arc::clone(&self.key), + key: self.key.clone(), scheme: self.scheme, })) } else { @@ -429,28 +428,31 @@ mod tests { )); let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "EcdsaSigningKey { algorithm: ECDSA }"); + assert_eq!(format!("{k:?}"), "EcdsaSigningKey { algorithm: ECDSA }"); assert_eq!(k.algorithm(), SignatureAlgorithm::ECDSA); - 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), + 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] @@ -479,28 +481,31 @@ mod tests { )); let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "EcdsaSigningKey { algorithm: ECDSA }"); + assert_eq!(format!("{k:?}"), "EcdsaSigningKey { algorithm: ECDSA }"); assert_eq!(k.algorithm(), SignatureAlgorithm::ECDSA); - 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), + 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] @@ -529,31 +534,35 @@ mod tests { )); let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "EcdsaSigningKey { algorithm: ECDSA }"); + assert_eq!(format!("{k:?}"), "EcdsaSigningKey { algorithm: ECDSA }"); assert_eq!(k.algorithm(), SignatureAlgorithm::ECDSA); - 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), + 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] @@ -570,22 +579,21 @@ mod tests { let key = PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/eddsakey.der")[..]); let k = any_eddsa_type(&key).unwrap(); - assert_eq!( - format!("{:?}", k), - "Ed25519SigningKey { algorithm: ED25519 }" - ); + assert_eq!(format!("{k:?}"), "Ed25519SigningKey { algorithm: ED25519 }"); assert_eq!(k.algorithm(), SignatureAlgorithm::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); } @@ -616,20 +624,22 @@ mod tests { )); let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "RsaSigningKey { algorithm: RSA }"); + assert_eq!(format!("{k:?}"), "RsaSigningKey { algorithm: RSA }"); assert_eq!(k.algorithm(), SignatureAlgorithm::RSA); - 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); diff --git a/rustls/src/crypto/aws_lc_rs/ticketer.rs b/rustls/src/crypto/aws_lc_rs/ticketer.rs index dfed6b743be..44c7c21b473 100644 --- a/rustls/src/crypto/aws_lc_rs/ticketer.rs +++ b/rustls/src/crypto/aws_lc_rs/ticketer.rs @@ -1,13 +1,12 @@ 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, + AES_256, AES_256_KEY_LEN, AES_CBC_IV_LEN, DecryptionContext, PaddedBlockDecryptingKey, + PaddedBlockEncryptingKey, UnboundCipherKey, }; use aws_lc_rs::{hmac, iv}; @@ -19,6 +18,7 @@ use crate::log::debug; use crate::polyfill::try_split_at; use crate::rand::GetRandomFailed; use crate::server::ProducesTickets; +use crate::sync::Arc; /// A concrete, safe ticket creation mechanism. pub struct Ticketer {} @@ -38,24 +38,6 @@ impl Ticketer { 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> { @@ -404,7 +386,7 @@ mod tests { let t = make_ticket_generator().unwrap(); - assert_eq!(format!("{:?}", t), "Rfc5077Ticketer { lifetime: 43200 }"); + assert_eq!(format!("{t:?}"), "Rfc5077Ticketer { lifetime: 43200 }"); assert!(t.enabled()); assert_eq!(t.lifetime(), 43200); } diff --git a/rustls/src/crypto/aws_lc_rs/tls12.rs b/rustls/src/crypto/aws_lc_rs/tls12.rs index 38b368353e6..3845af5837b 100644 --- a/rustls/src/crypto/aws_lc_rs/tls12.rs +++ b/rustls/src/crypto/aws_lc_rs/tls12.rs @@ -3,8 +3,8 @@ 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, + AeadKey, InboundOpaqueMessage, Iv, KeyBlockShape, MessageDecrypter, MessageEncrypter, + NONCE_LEN, Nonce, Tls12AeadAlgorithm, UnsupportedOperationError, make_tls12_aad, }; use crate::crypto::tls12::Prf; use crate::crypto::{ActiveKeyExchange, KeyExchangeAlgorithm}; diff --git a/rustls/src/crypto/aws_lc_rs/tls13.rs b/rustls/src/crypto/aws_lc_rs/tls13.rs index 9d234c82e44..dbc9d912c68 100644 --- a/rustls/src/crypto/aws_lc_rs/tls13.rs +++ b/rustls/src/crypto/aws_lc_rs/tls13.rs @@ -5,8 +5,8 @@ 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, + AeadKey, InboundOpaqueMessage, Iv, MessageDecrypter, MessageEncrypter, Nonce, + Tls13AeadAlgorithm, UnsupportedOperationError, make_tls13_aad, }; use crate::crypto::tls13::{Hkdf, HkdfExpander, OkmBlock, OutputLengthError}; use crate::enums::{CipherSuite, ContentType, ProtocolVersion}; @@ -88,9 +88,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 +96,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, }) } diff --git a/rustls/src/crypto/cipher.rs b/rustls/src/crypto/cipher.rs index 64901a577bf..b5a6046c444 100644 --- a/rustls/src/crypto/cipher.rs +++ b/rustls/src/crypto/cipher.rs @@ -213,18 +213,32 @@ impl 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..]); + let mut seq_bytes = [0u8; NONCE_LEN]; + codec::put_u64(seq, &mut seq_bytes[4..]); + Self::new_from_seq(iv, seq_bytes) + } - nonce - .0 - .iter_mut() + /// Creates a unique nonce based on the `iv`, the packet number `pn` and multipath `path_id`. + /// + /// The nonce is computed as the XOR between the `iv` and the 96-bit big-ending integer formed + /// by concatenating `path_id` and `pn`. + pub fn for_path(path_id: u32, iv: &Iv, pn: u64) -> Self { + let mut seq_bytes = [0u8; NONCE_LEN]; + seq_bytes[0..4].copy_from_slice(&path_id.to_be_bytes()); + codec::put_u64(pn, &mut seq_bytes[4..]); + Self::new_from_seq(iv, seq_bytes) + } + + /// Creates a unique nonce based on the `iv` and sequence number `seq`. + #[inline] + fn new_from_seq(iv: &Iv, mut seq: [u8; NONCE_LEN]) -> Self { + seq.iter_mut() .zip(iv.0.iter()) - .for_each(|(nonce, iv)| { - *nonce ^= *iv; + .for_each(|(s, iv)| { + *s ^= *iv; }); - nonce + Self(seq) } } diff --git a/rustls/src/crypto/hpke.rs b/rustls/src/crypto/hpke.rs index a08f7ca62d0..6207415022f 100644 --- a/rustls/src/crypto/hpke.rs +++ b/rustls/src/crypto/hpke.rs @@ -4,9 +4,9 @@ use core::fmt::Debug; use zeroize::Zeroize; +use crate::Error; use crate::msgs::enums::HpkeKem; use crate::msgs::handshake::HpkeSymmetricCipherSuite; -use crate::Error; /// An HPKE suite, specifying a key encapsulation mechanism and a symmetric cipher suite. #[derive(Clone, Copy, Debug, Eq, PartialEq)] diff --git a/rustls/src/crypto/mod.rs b/rustls/src/crypto/mod.rs index 3a39f2d8b6b..9c241f3b323 100644 --- a/rustls/src/crypto/mod.rs +++ b/rustls/src/crypto/mod.rs @@ -1,25 +1,25 @@ use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt::Debug; use pki_types::PrivateKeyDer; use zeroize::Zeroize; +#[cfg(all(doc, feature = "tls12"))] +use crate::Tls12CipherSuite; use crate::msgs::ffdhe_groups::FfdheGroup; use crate::sign::SigningKey; +use crate::sync::Arc; pub use crate::webpki::{ - verify_tls12_signature, verify_tls13_signature, verify_tls13_signature_with_raw_key, - WebPkiSupportedAlgorithms, + WebPkiSupportedAlgorithms, verify_tls12_signature, verify_tls13_signature, + verify_tls13_signature_with_raw_key, }; -#[cfg(all(doc, feature = "tls12"))] -use crate::Tls12CipherSuite; #[cfg(doc)] use crate::{ - client, crypto, server, sign, ClientConfig, ConfigBuilder, ServerConfig, SupportedCipherSuite, - Tls13CipherSuite, + ClientConfig, ConfigBuilder, ServerConfig, SupportedCipherSuite, Tls13CipherSuite, client, + crypto, server, sign, }; -use crate::{suites, Error, NamedGroup, ProtocolVersion, SupportedProtocolVersion}; +use crate::{Error, NamedGroup, ProtocolVersion, SupportedProtocolVersion, suites}; /// *ring* based CryptoProvider. #[cfg(feature = "ring")] @@ -61,7 +61,7 @@ pub use crate::suites::CipherSuiteCommon; /// This crate comes with two built-in options, provided as /// `CryptoProvider` structures: /// -/// - [`crypto::aws_lc_rs::default_provider`]: (behind the `aws_lc_rs` feature, +/// - [`crypto::aws_lc_rs::default_provider`]: (behind the `aws_lc_rs` crate 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 @@ -110,17 +110,17 @@ pub use crate::suites::CipherSuiteCommon; /// /// # 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")] { @@ -160,8 +160,8 @@ pub use crate::suites::CipherSuiteCommon; /// /// # Example code /// -/// See [provider-example/] for a full client and server example that uses -/// cryptography from the [rust-crypto] and [dalek-cryptography] projects. +/// See custom [`provider-example/`] for a full client and server example that uses +/// cryptography from the [`RustCrypto`] and [`dalek-cryptography`] projects. /// /// ```shell /// $ cargo run --example client | head -3 @@ -171,9 +171,9 @@ pub use crate::suites::CipherSuiteCommon; /// 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 +/// [`provider-example/`]: https://github.com/rustls/rustls/tree/main/provider-example/ +/// [`RustCrypto`]: https://github.com/RustCrypto +/// [`dalek-cryptography`]: https://github.com/dalek-cryptography /// /// # FIPS-approved cryptography /// The `fips` crate feature enables use of the `aws-lc-rs` crate in FIPS mode. @@ -212,7 +212,7 @@ pub struct CryptoProvider { /// 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, } @@ -246,7 +246,11 @@ impl CryptoProvider { } let provider = Self::from_crate_features() - .expect("no process-level CryptoProvider available -- call CryptoProvider::install_default() before this point"); + .expect(r###" +Could not automatically determine the process-level CryptoProvider from Rustls crate features. +Call CryptoProvider::install_default() before this point to select a provider manually, or make sure exactly one of the 'aws-lc-rs' and 'ring' features is enabled. +See the documentation of the CryptoProvider type for more information. + "###); // Ignore the error resulting from us losing a race, and accept the outcome. let _ = provider.install_default(); Self::get_default().unwrap() @@ -323,7 +327,7 @@ pub trait SecureRandom: Send + Sync + Debug { } } -/// A mechanism for loading private [SigningKey]s from [PrivateKeyDer]. +/// 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 @@ -686,8 +690,8 @@ impl From> for SharedSecret { /// .with_no_client_auth(); /// # } /// ``` -#[cfg(all(feature = "aws_lc_rs", any(feature = "fips", docsrs)))] -#[cfg_attr(docsrs, doc(cfg(feature = "fips")))] +#[cfg(all(feature = "aws_lc_rs", any(feature = "fips", rustls_docsrs)))] +#[cfg_attr(rustls_docsrs, doc(cfg(feature = "fips")))] pub fn default_fips_provider() -> CryptoProvider { aws_lc_rs::default_provider() } @@ -695,7 +699,6 @@ pub fn default_fips_provider() -> CryptoProvider { mod static_default { #[cfg(not(feature = "std"))] use alloc::boxed::Box; - use alloc::sync::Arc; #[cfg(feature = "std")] use std::sync::OnceLock; @@ -703,6 +706,7 @@ mod static_default { use once_cell::race::OnceBox; use super::CryptoProvider; + use crate::sync::Arc; #[cfg(feature = "std")] pub(crate) fn install_default( diff --git a/rustls/src/crypto/ring/kx.rs b/rustls/src/crypto/ring/kx.rs index 8c019d2ef25..6e0da22773c 100644 --- a/rustls/src/crypto/ring/kx.rs +++ b/rustls/src/crypto/ring/kx.rs @@ -11,9 +11,6 @@ use crate::msgs::enums::NamedGroup; use crate::rand::GetRandomFailed; /// 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, @@ -118,9 +115,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 { diff --git a/rustls/src/crypto/ring/mod.rs b/rustls/src/crypto/ring/mod.rs index 8f8ffc0053d..3ce99b15297 100644 --- a/rustls/src/crypto/ring/mod.rs +++ b/rustls/src/crypto/ring/mod.rs @@ -1,16 +1,15 @@ -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::Error; +use crate::crypto::{CryptoProvider, KeyProvider, SecureRandom, SupportedKxGroup}; use crate::enums::SignatureScheme; use crate::rand::GetRandomFailed; use crate::sign::SigningKey; use crate::suites::SupportedCipherSuite; +use crate::sync::Arc; use crate::webpki::WebPkiSupportedAlgorithms; -use crate::Error; /// Using software keys for authentication. pub mod sign; @@ -20,7 +19,7 @@ pub(crate) mod hash; pub(crate) mod hmac; pub(crate) mod kx; pub(crate) mod quic; -#[cfg(any(feature = "std", feature = "hashbrown"))] +#[cfg(feature = "std")] pub(crate) mod ticketer; #[cfg(feature = "tls12")] pub(crate) mod tls12; @@ -32,7 +31,7 @@ pub(crate) mod tls13; pub fn default_provider() -> CryptoProvider { CryptoProvider { cipher_suites: DEFAULT_CIPHER_SUITES.to_vec(), - kx_groups: ALL_KX_GROUPS.to_vec(), + kx_groups: DEFAULT_KX_GROUPS.to_vec(), signature_verification_algorithms: SUPPORTED_SIG_ALGS, secure_random: &Ring, key_provider: &Ring, @@ -117,7 +116,9 @@ static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms 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, + webpki_algs::RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS, + webpki_algs::RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS, + webpki_algs::RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS, ], mapping: &[ // Note: for TLS1.2 the curve is not fixed by SignatureScheme. For TLS1.3 it is. @@ -166,12 +167,19 @@ static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = 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}; } -pub use kx::ALL_KX_GROUPS; -#[cfg(any(feature = "std", feature = "hashbrown"))] +/// 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]; + +#[cfg(feature = "std")] pub use ticketer::Ticketer; /// Compatibility shims between ring 0.16.x and 0.17.x API diff --git a/rustls/src/crypto/ring/quic.rs b/rustls/src/crypto/ring/quic.rs index 3d0dc928916..354e6aea1ed 100644 --- a/rustls/src/crypto/ring/quic.rs +++ b/rustls/src/crypto/ring/quic.rs @@ -140,6 +140,23 @@ impl quic::PacketKey for PacketKey { Ok(quic::Tag::from(tag.as_ref())) } + fn encrypt_in_place_for_path( + &self, + path_id: u32, + packet_number: u64, + header: &[u8], + payload: &mut [u8], + ) -> Result { + let aad = aead::Aad::from(header); + let nonce = + aead::Nonce::assume_unique_for_key(Nonce::for_path(path_id, &self.iv, packet_number).0); + 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 the packet `header`, which is used as the additional authenticated data, and the @@ -164,6 +181,25 @@ impl quic::PacketKey for PacketKey { Ok(&payload[..plain_len]) } + fn decrypt_in_place_for_path<'a>( + &self, + path_id: u32, + packet_number: u64, + header: &[u8], + payload: &'a mut [u8], + ) -> Result<&'a [u8], Error> { + let payload_len = payload.len(); + let aad = aead::Aad::from(header); + let nonce = + aead::Nonce::assume_unique_for_key(Nonce::for_path(path_id, &self.iv, packet_number).0); + 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 { @@ -413,4 +449,85 @@ 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_INTERNAL + .quic + .unwrap(), + TLS13_AES_128_GCM_SHA256_INTERNAL.hkdf_provider, + ); + + let packet = builder.packet_key(); + let mut buf = PAYLOAD.to_vec(); + let tag = packet + .encrypt_in_place_for_path(PATH_ID, PN, HEADER, &mut buf) + .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_INTERNAL + .quic + .unwrap(), + TLS13_AES_128_GCM_SHA256_INTERNAL.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_for_path(path_id, PN, HEADER, &mut buf) + .unwrap(); + buf.extend_from_slice(tag.as_ref()); + let decrypted = packet + .decrypt_in_place_for_path(path_id, PN, HEADER, &mut buf) + .unwrap(); + assert_eq!(decrypted, PAYLOAD); + } + } } diff --git a/rustls/src/crypto/ring/sign.rs b/rustls/src/crypto/ring/sign.rs index 0ddfe2d41c2..ad318270a99 100644 --- a/rustls/src/crypto/ring/sign.rs +++ b/rustls/src/crypto/ring/sign.rs @@ -2,19 +2,18 @@ use alloc::boxed::Box; use alloc::string::ToString; -use alloc::sync::Arc; 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 pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer, SubjectPublicKeyInfoDer, 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::crypto::signer::{Signer, SigningKey, public_key_to_spki}; use crate::enums::{SignatureAlgorithm, SignatureScheme}; use crate::error::Error; +use crate::sync::Arc; use crate::x509::{wrap_concat_in_sequence, wrap_in_octet_string}; /// Parse `der` as any supported key encoding/type, returning @@ -111,7 +110,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 { @@ -125,7 +124,7 @@ impl SigningKey for RsaSigningKey { ALL_RSA_SCHEMES .iter() .find(|scheme| offered.contains(scheme)) - .map(|scheme| RsaSigner::new(Arc::clone(&self.key), *scheme)) + .map(|scheme| RsaSigner::new(self.key.clone(), *scheme)) } fn public_key(&self) -> Option> { @@ -286,7 +285,7 @@ impl SigningKey for EcdsaSigningKey { fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { if offered.contains(&self.scheme) { Some(Box::new(EcdsaSigner { - key: Arc::clone(&self.key), + key: self.key.clone(), scheme: self.scheme, })) } else { @@ -380,7 +379,7 @@ impl SigningKey for Ed25519SigningKey { fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { if offered.contains(&self.scheme) { Some(Box::new(Ed25519Signer { - key: Arc::clone(&self.key), + key: self.key.clone(), scheme: self.scheme, })) } else { @@ -462,28 +461,31 @@ mod tests { )); let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "EcdsaSigningKey { algorithm: ECDSA }"); + assert_eq!(format!("{k:?}"), "EcdsaSigningKey { algorithm: ECDSA }"); assert_eq!(k.algorithm(), SignatureAlgorithm::ECDSA); - 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), + 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] @@ -512,28 +514,31 @@ mod tests { )); let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "EcdsaSigningKey { algorithm: ECDSA }"); + assert_eq!(format!("{k:?}"), "EcdsaSigningKey { algorithm: ECDSA }"); assert_eq!(k.algorithm(), SignatureAlgorithm::ECDSA); - 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), + 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] @@ -550,22 +555,21 @@ mod tests { let key = PrivatePkcs8KeyDer::from(&include_bytes!("../../testdata/eddsakey.der")[..]); let k = any_eddsa_type(&key).unwrap(); - assert_eq!( - format!("{:?}", k), - "Ed25519SigningKey { algorithm: ED25519 }" - ); + assert_eq!(format!("{k:?}"), "Ed25519SigningKey { algorithm: ED25519 }"); assert_eq!(k.algorithm(), SignatureAlgorithm::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); } @@ -596,20 +600,22 @@ mod tests { )); let k = any_supported_type(&key).unwrap(); - assert_eq!(format!("{:?}", k), "RsaSigningKey { algorithm: RSA }"); + assert_eq!(format!("{k:?}"), "RsaSigningKey { algorithm: RSA }"); assert_eq!(k.algorithm(), SignatureAlgorithm::RSA); - 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); diff --git a/rustls/src/crypto/ring/ticketer.rs b/rustls/src/crypto/ring/ticketer.rs index 556293cad34..45dcc27a873 100644 --- a/rustls/src/crypto/ring/ticketer.rs +++ b/rustls/src/crypto/ring/ticketer.rs @@ -1,5 +1,4 @@ use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt; use core::fmt::{Debug, Formatter}; @@ -15,6 +14,7 @@ use crate::log::debug; use crate::polyfill::try_split_at; use crate::rand::GetRandomFailed; use crate::server::ProducesTickets; +use crate::sync::Arc; /// A concrete, safe ticket creation mechanism. pub struct Ticketer {} @@ -31,21 +31,6 @@ impl Ticketer { 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> { @@ -370,7 +355,7 @@ mod tests { let t = make_ticket_generator().unwrap(); let expect = format!("AeadTicketer {{ alg: {TICKETER_AEAD:?}, lifetime: 43200 }}"); - assert_eq!(format!("{:?}", t), expect); + assert_eq!(format!("{t:?}"), expect); assert!(t.enabled()); assert_eq!(t.lifetime(), 43200); } diff --git a/rustls/src/crypto/ring/tls12.rs b/rustls/src/crypto/ring/tls12.rs index 5bc2926000b..63dfae8b8a5 100644 --- a/rustls/src/crypto/ring/tls12.rs +++ b/rustls/src/crypto/ring/tls12.rs @@ -1,12 +1,12 @@ use alloc::boxed::Box; use super::ring_like::aead; +use crate::crypto::KeyExchangeAlgorithm; use crate::crypto::cipher::{ - make_tls12_aad, AeadKey, InboundOpaqueMessage, Iv, KeyBlockShape, MessageDecrypter, - MessageEncrypter, Nonce, Tls12AeadAlgorithm, UnsupportedOperationError, NONCE_LEN, + AeadKey, InboundOpaqueMessage, Iv, KeyBlockShape, MessageDecrypter, MessageEncrypter, + NONCE_LEN, Nonce, 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; diff --git a/rustls/src/crypto/ring/tls13.rs b/rustls/src/crypto/ring/tls13.rs index d390b2a4e24..ef488b624b1 100644 --- a/rustls/src/crypto/ring/tls13.rs +++ b/rustls/src/crypto/ring/tls13.rs @@ -4,8 +4,8 @@ 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, + AeadKey, InboundOpaqueMessage, Iv, MessageDecrypter, MessageEncrypter, Nonce, + Tls13AeadAlgorithm, UnsupportedOperationError, make_tls13_aad, }; use crate::crypto::tls13::{Hkdf, HkdfExpander, OkmBlock, OutputLengthError}; use crate::enums::{CipherSuite, ContentType, ProtocolVersion}; diff --git a/rustls/src/crypto/signer.rs b/rustls/src/crypto/signer.rs index 1ee932d8c40..659566adffb 100644 --- a/rustls/src/crypto/signer.rs +++ b/rustls/src/crypto/signer.rs @@ -1,13 +1,15 @@ use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt::Debug; -use pki_types::{AlgorithmIdentifier, CertificateDer, SubjectPublicKeyInfoDer}; +use pki_types::{AlgorithmIdentifier, CertificateDer, PrivateKeyDer, SubjectPublicKeyInfoDer}; +use super::CryptoProvider; +use crate::client::ResolvesClientCert; use crate::enums::{SignatureAlgorithm, SignatureScheme}; use crate::error::{Error, InconsistentKeys}; -use crate::server::ParsedCertificate; +use crate::server::{ClientHello, ParsedCertificate, ResolvesServerCert}; +use crate::sync::Arc; use crate::x509; /// An abstract signing key. @@ -85,6 +87,46 @@ pub trait Signer: Debug + Send + Sync { fn scheme(&self) -> SignatureScheme; } +/// Server certificate resolver which always resolves to the same certificate and key. +/// +/// For use with [`ConfigBuilder::with_cert_resolver()`]. +/// +/// [`ConfigBuilder::with_cert_resolver()`]: crate::ConfigBuilder::with_cert_resolver +#[derive(Debug)] +pub struct SingleCertAndKey(Arc); + +impl From for SingleCertAndKey { + fn from(certified_key: CertifiedKey) -> Self { + Self(Arc::new(certified_key)) + } +} + +impl From> for SingleCertAndKey { + fn from(certified_key: Arc) -> Self { + Self(certified_key) + } +} + +impl ResolvesClientCert for SingleCertAndKey { + fn resolve( + &self, + _root_hint_subjects: &[&[u8]], + _sigschemes: &[SignatureScheme], + ) -> Option> { + Some(self.0.clone()) + } + + fn has_certs(&self) -> bool { + true + } +} + +impl ResolvesServerCert for SingleCertAndKey { + fn resolve(&self, _client_hello: ClientHello<'_>) -> Option> { + Some(self.0.clone()) + } +} + /// A packaged-together certificate chain, matching `SigningKey` and /// optional stapled OCSP response. /// @@ -107,6 +149,30 @@ pub struct CertifiedKey { } impl CertifiedKey { + /// Create a new `CertifiedKey` 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 `cert_chain` + /// if possible. + /// + /// [`KeyProvider`]: crate::crypto::KeyProvider + pub fn from_der( + cert_chain: Vec>, + key: PrivateKeyDer<'static>, + provider: &CryptoProvider, + ) -> Result { + let private_key = provider + .key_provider + .load_private_key(key)?; + + let certified_key = Self::new(cert_chain, private_key); + match certified_key.keys_match() { + // Don't treat unknown consistency as an error + Ok(()) | Err(Error::InconsistentKeys(InconsistentKeys::Unknown)) => Ok(certified_key), + Err(err) => Err(err), + } + } + /// Make a new CertifiedKey, with the given chain and key. /// /// The cert chain must not be empty. The first certificate in the chain @@ -141,8 +207,8 @@ impl CertifiedKey { } } -#[cfg_attr(not(any(feature = "aws_lc_rs", feature = "ring")), allow(dead_code))] -pub(crate) fn public_key_to_spki( +/// Convert a public key and algorithm identifier into [`SubjectPublicKeyInfoDer`]. +pub fn public_key_to_spki( alg_id: &AlgorithmIdentifier, public_key: impl AsRef<[u8]>, ) -> SubjectPublicKeyInfoDer<'static> { diff --git a/rustls/src/crypto/tls12.rs b/rustls/src/crypto/tls12.rs index fce7cb132c9..7128de4b2f3 100644 --- a/rustls/src/crypto/tls12.rs +++ b/rustls/src/crypto/tls12.rs @@ -1,6 +1,6 @@ use alloc::boxed::Box; -use super::{hmac, ActiveKeyExchange}; +use super::{ActiveKeyExchange, hmac}; use crate::error::Error; use crate::version::TLS12; diff --git a/rustls/src/crypto/tls13.rs b/rustls/src/crypto/tls13.rs index a2e8029b31f..50ecaa89c7d 100644 --- a/rustls/src/crypto/tls13.rs +++ b/rustls/src/crypto/tls13.rs @@ -3,7 +3,7 @@ use alloc::vec::Vec; use zeroize::Zeroize; -use super::{hmac, ActiveKeyExchange}; +use super::{ActiveKeyExchange, hmac}; use crate::error::Error; use crate::version::TLS13; @@ -266,7 +266,7 @@ pub struct OutputLengthError; mod tests { use std::prelude::v1::*; - use super::{expand, Hkdf, HkdfUsingHmac}; + use super::{Hkdf, HkdfUsingHmac, expand}; // nb: crypto::aws_lc_rs provider doesn't provide (or need) hmac, // so cannot be used for this test. use crate::crypto::ring::hmac; @@ -397,9 +397,10 @@ mod tests { let info = &[]; let mut output = [0u8; 32 * 255 + 1]; - assert!(hkdf - .extract_from_secret(None, ikm) - .expand_slice(info, &mut output) - .is_err()); + 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..4b450aafc84 100644 --- a/rustls/src/enums.rs +++ b/rustls/src/enums.rs @@ -1,6 +1,7 @@ #![allow(non_camel_case_types)] #![allow(missing_docs)] use crate::msgs::codec::{Codec, Reader}; +use crate::msgs::enums::HashAlgorithm; enum_builder! { /// The `AlertDescription` TLS protocol enum. Values in this enum are taken @@ -95,7 +96,7 @@ enum_builder! { /// The `Unknown` item is used when processing unrecognised ordinals. #[repr(u16)] pub enum ProtocolVersion { - SSLv2 => 0x0200, + SSLv2 => 0x0002, SSLv3 => 0x0300, TLSv1_0 => 0x0301, TLSv1_1 => 0x0302, @@ -513,6 +514,10 @@ enum_builder! { 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, } } @@ -542,17 +547,35 @@ impl SignatureScheme { /// 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. + /// 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 { - 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 + 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 ) } } @@ -585,6 +608,21 @@ enum_builder! { } } +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)] + #[derive(Default)] + pub enum CertificateType { + #[default] + X509 => 0x00, + RawPublicKey => 0x02, + } +} + enum_builder! { /// The type of Encrypted Client Hello (`EchClientHelloType`). /// @@ -601,7 +639,7 @@ enum_builder! { #[cfg(test)] mod tests { use super::*; - use crate::msgs::enums::tests::{test_enum16, test_enum8}; + use crate::msgs::enums::tests::{test_enum8, test_enum16}; #[test] fn test_enums() { @@ -616,5 +654,40 @@ mod tests { CertificateCompressionAlgorithm::Zlib, CertificateCompressionAlgorithm::Zstd, ); + test_enum8::(CertificateType::X509, CertificateType::RawPublicKey); + } + + #[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()); } } diff --git a/rustls/src/error.rs b/rustls/src/error.rs index d7dc6549f43..3aaf3bb3c17 100644 --- a/rustls/src/error.rs +++ b/rustls/src/error.rs @@ -5,6 +5,9 @@ use core::fmt; #[cfg(feature = "std")] use std::time::SystemTimeError; +use pki_types::{AlgorithmIdentifier, ServerName, UnixTime}; +use webpki::KeyUsage; + use crate::enums::{AlertDescription, ContentType, HandshakeType}; use crate::msgs::handshake::{EchConfigPayload, KeyExchangeAlgorithm}; use crate::rand; @@ -139,7 +142,6 @@ impl From for Error { /// 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, @@ -183,6 +185,22 @@ pub enum InvalidMessage { 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 an empty value, but a non-empty value is required. + IllegalEmptyValue, + /// 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 Error { @@ -192,6 +210,17 @@ impl From for Error { } } +impl From for AlertDescription { + fn from(e: InvalidMessage) -> Self { + match e { + InvalidMessage::PreSharedKeyIsNotFinalExtension => Self::IllegalParameter, + InvalidMessage::DuplicateExtension(_) => Self::IllegalParameter, + InvalidMessage::UnknownHelloRetryRequestExtension => Self::UnsupportedExtension, + _ => Self::DecodeError, + } + } +} + #[non_exhaustive] #[allow(missing_docs)] #[derive(Debug, PartialEq, Clone)] @@ -263,6 +292,7 @@ pub enum PeerMisbehaved { SelectedUnofferedKxGroup, SelectedUnofferedPsk, SelectedUnusableCipherSuiteForVersion, + ServerEchoedCompatibilitySessionId, ServerHelloMustOfferUncompressedEcPoints, ServerNameDifferedOnRetry, ServerNameMustContainOneHostName, @@ -345,9 +375,31 @@ pub enum CertificateError { /// 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, @@ -364,17 +416,88 @@ pub enum CertificateError { /// 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. + #[deprecated( + since = "0.23.29", + note = "use `UnsupportedSignatureAlgorithmContext` instead" + )] + UnsupportedSignatureAlgorithm, + + /// A signature inside a certificate or on a handshake was made with an unsupported algorithm. + UnsupportedSignatureAlgorithmContext { + /// 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. + UnsupportedSignatureAlgorithmForPublicKeyContext { + /// 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 [`ServerCertVerifier::verify_server_cert()`] + /// when a verifier checks its `ocsp_response` parameter and finds it invalid. + /// + /// This maps to [`AlertDescription::BadCertificateStatusResponse`]. + /// + /// [`ServerCertVerifier::verify_server_cert()`]: crate::client::danger::ServerCertVerifier::verify_server_cert + InvalidOcspResponse, + /// The certificate is valid, but the handshake is rejected for other /// reasons. ApplicationVerificationFailure, @@ -399,15 +522,95 @@ impl PartialEq for 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, + #[allow(deprecated)] + (UnsupportedSignatureAlgorithm, UnsupportedSignatureAlgorithm) => true, + ( + UnsupportedSignatureAlgorithmContext { + signature_algorithm_id: left_signature_algorithm_id, + supported_algorithms: left_supported_algorithms, + }, + UnsupportedSignatureAlgorithmContext { + 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) + } + ( + UnsupportedSignatureAlgorithmForPublicKeyContext { + signature_algorithm_id: left_signature_algorithm_id, + public_key_algorithm_id: left_public_key_algorithm_id, + }, + UnsupportedSignatureAlgorithmForPublicKeyContext { + 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, } } @@ -420,17 +623,30 @@ impl From for AlertDescription { fn from(e: CertificateError) -> Self { use CertificateError::*; match e { - BadEncoding | UnhandledCriticalExtension | NotValidForName => Self::BadCertificate, + BadEncoding + | UnhandledCriticalExtension + | NotValidForName + | NotValidForNameContext { .. } => Self::BadCertificate, // RFC 5246/RFC 8446 // certificate_expired // A certificate has expired or **is not currently valid**. - Expired | NotValidYet => Self::CertificateExpired, + 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 => Self::UnknownCA, - BadSignature => Self::DecryptError, - InvalidPurpose => Self::UnsupportedCertificate, + UnknownIssuer + | UnknownRevocationStatus + | ExpiredRevocationList + | ExpiredRevocationListContext { .. } => Self::UnknownCA, + InvalidOcspResponse => Self::BadCertificateStatusResponse, + #[allow(deprecated)] + BadSignature + | UnsupportedSignatureAlgorithm + | UnsupportedSignatureAlgorithmContext { .. } + | UnsupportedSignatureAlgorithmForPublicKeyContext { .. } => Self::DecryptError, + InvalidPurpose | InvalidPurposeContext { .. } => Self::UnsupportedCertificate, ApplicationVerificationFailure => Self::AccessDenied, // RFC 5246/RFC 8446 // certificate_unknown @@ -441,6 +657,101 @@ impl From for AlertDescription { } } +impl fmt::Display for CertificateError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + #[cfg(feature = "std")] + 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(()) + } + + other => write!(f, "{other:?}"), + } + } +} + impl From for Error { #[inline] fn from(e: CertificateError) -> Self { @@ -448,13 +759,81 @@ impl From for Error { } } +/// 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. +#[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 { + pub(crate) fn for_values(values: impl Iterator) -> Self { + let values = values.collect::>(); + match &*values { + KeyUsage::CLIENT_AUTH_REPR => Self::ClientAuth, + KeyUsage::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(()) + } + } + } +} + #[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. + /// The CRL had a bad signature from its issuer. BadSignature, + /// The CRL had an unsupported signature from its issuer. + #[deprecated( + since = "0.23.29", + note = "use `UnsupportedSignatureAlgorithmContext` instead" + )] + UnsupportedSignatureAlgorithm, + + /// A signature inside a certificate or on a handshake was made with an unsupported algorithm. + UnsupportedSignatureAlgorithmContext { + /// 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. + UnsupportedSignatureAlgorithmForPublicKeyContext { + /// 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, @@ -498,6 +877,34 @@ impl PartialEq for CertRevocationListError { #[allow(clippy::match_like_matches_macro)] match (self, other) { (BadSignature, BadSignature) => true, + #[allow(deprecated)] + (UnsupportedSignatureAlgorithm, UnsupportedSignatureAlgorithm) => true, + ( + UnsupportedSignatureAlgorithmContext { + signature_algorithm_id: left_signature_algorithm_id, + supported_algorithms: left_supported_algorithms, + }, + UnsupportedSignatureAlgorithmContext { + 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) + } + ( + UnsupportedSignatureAlgorithmForPublicKeyContext { + signature_algorithm_id: left_signature_algorithm_id, + public_key_algorithm_id: left_public_key_algorithm_id, + }, + UnsupportedSignatureAlgorithmForPublicKeyContext { + 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, @@ -541,17 +948,17 @@ impl From for Error { fn join(items: &[T]) -> String { items .iter() - .map(|x| format!("{:?}", x)) + .map(|x| format!("{x:?}")) .collect::>() .join(" or ") } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { + match self { Self::InappropriateMessage { - ref expect_types, - ref got_type, + expect_types, + got_type, } => write!( f, "received unexpected message: got {:?} when expecting {}", @@ -559,31 +966,31 @@ impl fmt::Display for Error { join::(expect_types) ), Self::InappropriateHandshakeMessage { - ref expect_types, - ref got_type, + expect_types, + 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::InvalidMessage(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::PeerIncompatible(why) => write!(f, "peer is incompatible: {why:?}"), + Self::PeerMisbehaved(why) => write!(f, "peer misbehaved: {why:?}"), + Self::AlertReceived(alert) => write!(f, "received fatal alert: {alert:?}"), + Self::InvalidCertificate(err) => { + write!(f, "invalid peer certificate: {err}") } - Self::InvalidCertRevocationList(ref err) => { - write!(f, "invalid certificate revocation list: {:?}", err) + Self::InvalidCertRevocationList(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::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"), @@ -594,11 +1001,11 @@ impl fmt::Display for Error { 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::InconsistentKeys(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), + Self::General(err) => write!(f, "unexpected error: {err}"), + Self::Other(err) => write!(f, "other error: {err}"), } } } @@ -621,13 +1028,13 @@ impl From for Error { } 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; + #[cfg(feature = "std")] + use crate::sync::Arc; /// Any other error that cannot be expressed by a more specific [`Error`] variant. /// @@ -675,30 +1082,146 @@ pub use other_error::OtherError; #[cfg(test)] mod tests { + use core::time::Duration; use std::prelude::v1::*; use std::{println, vec}; - use super::{CertRevocationListError, Error, InconsistentKeys, InvalidMessage, OtherError}; + use pki_types::ServerName; + + use super::{ + CertRevocationListError, Error, InconsistentKeys, InvalidMessage, OtherError, UnixTime, + }; + #[cfg(feature = "std")] + use crate::sync::Arc; #[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); + #[allow(deprecated)] + { + assert_eq!(UnsupportedSignatureAlgorithm, UnsupportedSignatureAlgorithm); + } + assert_eq!( + UnsupportedSignatureAlgorithmContext { + signature_algorithm_id: vec![1, 2, 3], + supported_algorithms: vec![] + }, + UnsupportedSignatureAlgorithmContext { + signature_algorithm_id: vec![1, 2, 3], + supported_algorithms: vec![] + } + ); + assert_eq!( + UnsupportedSignatureAlgorithmForPublicKeyContext { + signature_algorithm_id: vec![1, 2, 3], + public_key_algorithm_id: vec![4, 5, 6] + }, + UnsupportedSignatureAlgorithmForPublicKeyContext { + 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( #[cfg(feature = "std")] - alloc::sync::Arc::from(Box::from("")), + Arc::from(Box::from("")), )); assert_ne!(other, other); assert_ne!(BadEncoding, Expired); @@ -708,6 +1231,30 @@ mod tests { fn crl_error_equality() { use super::CertRevocationListError::*; assert_eq!(BadSignature, BadSignature); + #[allow(deprecated)] + { + assert_eq!(UnsupportedSignatureAlgorithm, UnsupportedSignatureAlgorithm); + } + assert_eq!( + UnsupportedSignatureAlgorithmContext { + signature_algorithm_id: vec![1, 2, 3], + supported_algorithms: vec![] + }, + UnsupportedSignatureAlgorithmContext { + signature_algorithm_id: vec![1, 2, 3], + supported_algorithms: vec![] + } + ); + assert_eq!( + UnsupportedSignatureAlgorithmForPublicKeyContext { + signature_algorithm_id: vec![1, 2, 3], + public_key_algorithm_id: vec![4, 5, 6] + }, + UnsupportedSignatureAlgorithmForPublicKeyContext { + signature_algorithm_id: vec![1, 2, 3], + public_key_algorithm_id: vec![4, 5, 6] + } + ); assert_eq!(InvalidCrlNumber, InvalidCrlNumber); assert_eq!( InvalidRevokedCertSerialNumber, @@ -722,7 +1269,7 @@ mod tests { assert_eq!(UnsupportedRevocationReason, UnsupportedRevocationReason); let other = Other(OtherError( #[cfg(feature = "std")] - alloc::sync::Arc::from(Box::from("")), + Arc::from(Box::from("")), )); assert_ne!(other, other); assert_ne!(BadSignature, InvalidCrlNumber); @@ -731,7 +1278,7 @@ mod tests { #[test] #[cfg(feature = "std")] fn other_error_equality() { - let other_error = OtherError(alloc::sync::Arc::from(Box::from(""))); + let other_error = OtherError(Arc::from(Box::from(""))); assert_ne!(other_error, other_error); let other: Error = other_error.into(); assert_ne!(other, other); @@ -757,6 +1304,46 @@ mod tests { 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, @@ -769,13 +1356,13 @@ mod tests { Error::InvalidCertRevocationList(CertRevocationListError::BadSignature), Error::Other(OtherError( #[cfg(feature = "std")] - alloc::sync::Arc::from(Box::from("")), + Arc::from(Box::from("")), )), ]; for err in all { - println!("{:?}:", err); - println!(" fmt '{}'", err); + println!("{err:?}:"); + println!(" fmt '{err}'"); } } diff --git a/rustls/src/hash_hs.rs b/rustls/src/hash_hs.rs index 4f664002d7b..8b4edc68fb2 100644 --- a/rustls/src/hash_hs.rs +++ b/rustls/src/hash_hs.rs @@ -189,7 +189,7 @@ mod tests { use super::provider::hash::SHA256; use super::*; use crate::crypto::hash::Hash; - use crate::enums::{HandshakeType, ProtocolVersion}; + use crate::enums::ProtocolVersion; use crate::msgs::base::Payload; use crate::msgs::handshake::{HandshakeMessagePayload, HandshakePayload}; @@ -214,10 +214,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 { diff --git a/rustls/src/key_log_file.rs b/rustls/src/key_log_file.rs index f0ad95223ad..45f29da552c 100644 --- a/rustls/src/key_log_file.rs +++ b/rustls/src/key_log_file.rs @@ -7,8 +7,8 @@ use std::io; use std::io::Write; use std::sync::Mutex; -use crate::log::warn; use crate::KeyLog; +use crate::log::warn; // Internal mutable state for KeyLogFile struct KeyLogFileInner { @@ -33,7 +33,7 @@ impl KeyLogFileInner { { Ok(f) => Some(f), Err(e) => { - warn!("unable to create key log file {:?}: {}", path, e); + warn!("unable to create key log file {path:?}: {e}"); None } }; @@ -45,21 +45,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) @@ -105,7 +102,7 @@ impl KeyLog for KeyLogFile { { Ok(()) => {} Err(e) => { - warn!("error writing to key log file: {}", e); + warn!("error writing to key log file: {e}"); } } } @@ -114,7 +111,7 @@ 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 {{ }}"), } } @@ -134,26 +131,32 @@ 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()); + assert!( + inner + .try_write("label", b"random", b"secret") + .is_err() + ); } } diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index 33bff997328..12b9cdeb5c4 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -21,10 +21,10 @@ //! 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 +//! Specifying `default-features = false` when depending on rustls will remove the implicit //! 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. +//! Rustls requires Rust 1.71 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 @@ -44,10 +44,10 @@ //! //! #### Built-in providers //! -//! Rustls ships with two built-in providers controlled with associated feature flags: +//! Rustls ships with two built-in providers controlled by associated crate features: //! -//! * [`aws-lc-rs`] - enabled by default, available with the `aws_lc_rs` feature flag enabled. -//! * [`ring`] - available with the `ring` feature flag enabled. +//! * [`aws-lc-rs`] - enabled by default, available with the `aws_lc_rs` crate feature enabled. +//! * [`ring`] - available with the `ring` crate feature enabled. //! //! See the documentation for [`crypto::CryptoProvider`] for details on how providers are //! selected. @@ -56,17 +56,18 @@ //! //! 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-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-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,20 +78,18 @@ //! [`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. +//! 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/ +//! [custom provider example]: 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 //! @@ -268,10 +267,17 @@ //! //! [`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. //! +//! - `std` (enabled by default): enable the high-level (buffered) Connection API and other functionality +//! which relies on the `std` library. +//! //! - `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 @@ -285,15 +291,20 @@ //! 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 +//! - `fips`: enable support for FIPS140-3-approved cryptography, via the [`aws-lc-rs`] crate. +//! This feature enables the `aws_lc_rs` crate 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. //! +//! - `prefer-post-quantum` (enabled by default): for the [`aws-lc-rs`]-backed provider, +//! prioritizes post-quantum secure key exchange by default (using X25519MLKEM768). +//! This feature merely alters the order of `rustls::crypto::aws_lc_rs::DEFAULT_KX_GROUPS`. +//! See [the manual][x25519mlkem768-manual] 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`. +//! 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 @@ -314,13 +325,13 @@ //! //! - `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. #![forbid(unsafe_code, unused_must_use)] -#![cfg_attr(not(any(read_buf, bench)), forbid(unstable_features))] +#![cfg_attr(not(any(read_buf, bench, coverage_nightly)), 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, @@ -352,7 +363,11 @@ clippy::new_without_default )] // Enable documentation for all features on docs.rs -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![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))] // 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. @@ -362,6 +377,7 @@ // 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))] #![cfg_attr(read_buf, feature(core_io_borrowed_buf))] #![cfg_attr(bench, feature(test))] #![no_std] @@ -400,6 +416,16 @@ mod log { #[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 { + #[allow(clippy::disallowed_types)] + pub(crate) type Arc = alloc::sync::Arc; + #[allow(clippy::disallowed_types)] + pub(crate) type Weak = alloc::sync::Weak; +} + #[macro_use] mod msgs; mod common_state; @@ -451,8 +477,7 @@ pub mod internal { } pub mod enums { pub use crate::msgs::enums::{ - AlertLevel, CertificateType, Compression, EchVersion, HpkeAead, HpkeKdf, HpkeKem, - NamedGroup, + AlertLevel, EchVersion, ExtensionType, HpkeAead, HpkeKdf, HpkeKem, }; } pub mod fragmenter { @@ -460,10 +485,7 @@ pub mod internal { } pub mod handshake { pub use crate::msgs::handshake::{ - CertificateChain, ClientExtension, ClientHelloPayload, DistinguishedName, - EchConfigContents, EchConfigPayload, HandshakeMessagePayload, HandshakePayload, - HpkeKeyConfig, HpkeSymmetricCipherSuite, KeyShareEntry, Random, ServerExtension, - ServerName, SessionId, + EchConfigContents, EchConfigPayload, HpkeKeyConfig, HpkeSymmetricCipherSuite, }; } pub mod message { @@ -510,12 +532,12 @@ pub mod internal { /// [`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::UnbufferedConnectionCommon; pub use crate::conn::unbuffered::{ AppDataRecord, ConnectionState, EncodeError, EncodeTlsData, EncryptError, InsufficientSizeError, ReadEarlyData, ReadTraffic, TransmitTlsData, UnbufferedStatus, WriteTraffic, }; - pub use crate::conn::UnbufferedConnectionCommon; } // The public interface is: @@ -523,14 +545,15 @@ 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::conn::{ConnectionCommon, SideData, kernel}; 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, + CertRevocationListError, CertificateError, EncryptedClientHelloError, Error, + ExtendedKeyPurpose, InconsistentKeys, InvalidMessage, OtherError, PeerIncompatible, + PeerMisbehaved, }; pub use crate::key_log::{KeyLog, NoKeyLog}; #[cfg(feature = "std")] @@ -551,7 +574,7 @@ pub use crate::ticketer::TicketSwitcher; 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::versions::{ALL_VERSIONS, DEFAULT_VERSIONS, SupportedProtocolVersion}; pub use crate::webpki::RootCertStore; /// Items for use in a client. @@ -563,6 +586,8 @@ pub mod client { pub(super) mod handy; mod hs; pub mod reality; + #[cfg(test)] + mod test; #[cfg(feature = "tls12")] mod tls12; mod tls13; @@ -589,8 +614,8 @@ pub mod client { pub use crate::msgs::persist::{Tls12ClientSessionValue, Tls13ClientSessionValue}; pub use crate::webpki::{ - verify_server_cert_signed_by_trust_anchor, verify_server_name, ServerCertVerifierBuilder, - VerifierBuilderError, WebPkiServerVerifier, + ServerCertVerifierBuilder, VerifierBuilderError, WebPkiServerVerifier, + verify_server_cert_signed_by_trust_anchor, verify_server_name, }; } @@ -605,6 +630,8 @@ pub mod server { pub(crate) mod handy; mod hs; mod server_conn; + #[cfg(test)] + mod test; #[cfg(feature = "tls12")] mod tls12; mod tls13; @@ -622,6 +649,7 @@ pub mod server { #[cfg(feature = "std")] pub use server_conn::{AcceptedAlert, Acceptor, ReadEarlyData, ServerConnection}; + pub use crate::enums::CertificateType; pub use crate::verify::NoClientAuth; pub use crate::webpki::{ ClientCertVerifierBuilder, ParsedCertificate, VerifierBuilderError, WebPkiClientVerifier, @@ -654,7 +682,9 @@ pub mod pki_types { /// Message signing interfaces. pub mod sign { - pub use crate::crypto::signer::{CertifiedKey, Signer, SigningKey}; + pub use crate::crypto::signer::{ + CertifiedKey, Signer, SigningKey, SingleCertAndKey, public_key_to_spki, + }; } /// APIs for implementing QUIC TLS @@ -677,13 +707,13 @@ 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; + #[cfg(feature = "std")] + 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; + #[cfg(all(not(feature = "std"), feature = "hashbrown"))] + pub(crate) use hashbrown::hash_map::Entry; } diff --git a/rustls/src/limited_cache.rs b/rustls/src/limited_cache.rs index 4252a337f97..6ae8bf94d2e 100644 --- a/rustls/src/limited_cache.rs +++ b/rustls/src/limited_cache.rs @@ -102,19 +102,18 @@ 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); - } - Some(value) - } else { - None + 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) } } diff --git a/rustls/src/lock.rs b/rustls/src/lock.rs index 4a261b2eb75..b632b2c54b5 100644 --- a/rustls/src/lock.rs +++ b/rustls/src/lock.rs @@ -35,10 +35,11 @@ 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; + use crate::sync::Arc; + /// A no-std compatible wrapper around [`Lock`]. #[derive(Debug)] pub struct Mutex { diff --git a/rustls/src/manual/defaults.rs b/rustls/src/manual/defaults.rs index aa15863c163..f6f70dc05f5 100644 --- a/rustls/src/manual/defaults.rs +++ b/rustls/src/manual/defaults.rs @@ -26,4 +26,46 @@ 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. + +The `prefer-post-quantum` crate feature makes `X25519MLKEM768` the +highest-priority key exchange algorithm. Otherwise, it is available but +not highest-priority. + +[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]: +[`X25519MLKEM768`]: crate::crypto::aws_lc_rs::kx_group::X25519MLKEM768 +[`MLKEM768`]: crate::crypto::aws_lc_rs::kx_group::MLKEM768 +[FIPS203]: +[Chrome]: +[Cloudflare]: +[interop-bug]: +[tldr.fail]: + */ diff --git a/rustls/src/manual/features.rs b/rustls/src/manual/features.rs index 9896ac27408..69daf48ace5 100644 --- a/rustls/src/manual/features.rs +++ b/rustls/src/manual/features.rs @@ -12,6 +12,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 @@ -36,6 +37,7 @@ APIs ([`CryptoProvider`] for example). 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 +59,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 +79,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 +95,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 c57611fb5f2..86d04dd36a3 100644 --- a/rustls/src/manual/fips.rs +++ b/rustls/src/manual/fips.rs @@ -1,7 +1,8 @@ /*! # 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 +utilize a FIPS-approved `CryptoProvider`. +rustls ships with one using `aws-lc-rs`, take these actions to make use of it: ## 1. Enable the `fips` crate feature for rustls. diff --git a/rustls/src/manual/howto.rs b/rustls/src/manual/howto.rs index f3a8219cdf0..6c00c6957aa 100644 --- a/rustls/src/manual/howto.rs +++ b/rustls/src/manual/howto.rs @@ -6,7 +6,7 @@ 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 +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. The [`sign()`][sign_method] performs the signature and returns it. @@ -21,18 +21,18 @@ Once you have these two pieces, configuring a server to use them involves, brief - making a [`ResolvesServerCertUsingSni`][cert_using_sni] and feeding in your [`sign::CertifiedKey`][certified_key] 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 [`sign::SigningKey`][signing_key] and +[`sign::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() +[choose_scheme]: crate::crypto::signer::SigningKey::choose_scheme [sig_scheme]: crate::SignatureScheme [signer]: crate::crypto::signer::Signer -[sign_method]: crate::crypto::Signer.sign() +[sign_method]: crate::crypto::signer::Signer::sign [certified_key]: crate::crypto::signer::CertifiedKey [cert_using_sni]: crate::server::ResolvesServerCertUsingSni [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://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/msgs/base.rs b/rustls/src/msgs/base.rs index cbdad0a7b67..4204714d891 100644 --- a/rustls/src/msgs/base.rs +++ b/rustls/src/msgs/base.rs @@ -1,5 +1,6 @@ use alloc::vec::Vec; use core::fmt; +use core::marker::PhantomData; use pki_types::CertificateDer; use zeroize::Zeroize; @@ -110,95 +111,128 @@ impl fmt::Debug for PayloadU24<'_> { } /// An arbitrary, unknown-content, u16-length-prefixed payload +/// +/// The `C` type parameter controls whether decoded values may +/// be empty. #[derive(Clone, Eq, PartialEq)] -pub struct PayloadU16(pub Vec); +pub struct PayloadU16(pub(crate) Vec, PhantomData); -impl PayloadU16 { +impl PayloadU16 { pub fn new(bytes: Vec) -> Self { - Self(bytes) + debug_assert!(bytes.len() >= C::MIN); + Self(bytes, PhantomData) } +} - pub fn empty() -> Self { +impl PayloadU16 { + pub(crate) 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 { +impl Codec<'_> for PayloadU16 { fn encode(&self, bytes: &mut Vec) { - Self::encode_slice(&self.0, bytes); + debug_assert!(self.0.len() >= C::MIN); + (self.0.len() as u16).encode(bytes); + bytes.extend_from_slice(&self.0); } fn read(r: &mut Reader<'_>) -> Result { let len = u16::read(r)? as usize; + if len < C::MIN { + return Err(InvalidMessage::IllegalEmptyValue); + } let mut sub = r.sub(len)?; let body = sub.rest().to_vec(); - Ok(Self(body)) + Ok(Self(body, PhantomData)) } } -impl fmt::Debug for PayloadU16 { +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 +/// +/// `C` controls the minimum length accepted when decoding. #[derive(Clone, Eq, PartialEq)] -pub struct PayloadU8(pub(crate) Vec); +pub(crate) struct PayloadU8(pub(crate) Vec, PhantomData); -impl PayloadU8 { +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) + debug_assert!(bytes.len() >= C::MIN); + Self(bytes, PhantomData) } +} +impl PayloadU8 { pub(crate) fn empty() -> Self { - Self(Vec::new()) + Self(Vec::new(), PhantomData) } } -impl Codec<'_> for PayloadU8 { +impl Codec<'_> for PayloadU8 { fn encode(&self, bytes: &mut Vec) { + debug_assert!(self.0.len() >= C::MIN); (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; + if len < C::MIN { + return Err(InvalidMessage::IllegalEmptyValue); + } let mut sub = r.sub(len)?; let body = sub.rest().to_vec(); - Ok(Self(body)) + Ok(Self(body, PhantomData)) } } -impl Zeroize for PayloadU8 { +impl Zeroize for PayloadU8 { fn zeroize(&mut self) { self.0.zeroize(); } } -impl fmt::Debug for PayloadU8 { +impl fmt::Debug for PayloadU8 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { hex(f, &self.0) } } +pub trait Cardinality: Clone + Eq + PartialEq { + const MIN: usize; +} + +#[derive(Clone, Eq, PartialEq)] +pub struct MaybeEmpty; + +impl Cardinality for MaybeEmpty { + const MIN: usize = 0; +} + +#[derive(Clone, Eq, PartialEq)] +pub struct NonEmpty; + +impl Cardinality for NonEmpty { + const MIN: usize = 1; +} + // 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)?; + write!(f, "{b:02x}")?; } Ok(()) } diff --git a/rustls/src/msgs/codec.rs b/rustls/src/msgs/codec.rs index 0a7ae21507e..bd62cd3a6de 100644 --- a/rustls/src/msgs/codec.rs +++ b/rustls/src/msgs/codec.rs @@ -1,5 +1,6 @@ use alloc::vec::Vec; use core::fmt::Debug; +use core::marker::PhantomData; use crate::error::InvalidMessage; @@ -225,25 +226,54 @@ 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) } } +/// 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, + }) + } +} + +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, + } + } +} + +impl Codec<'_> for () { + fn encode(&self, _: &mut Vec) {} + + fn read(r: &mut Reader<'_>) -> Result { + r.expect_empty("Empty") + } +} + /// 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 @@ -260,11 +290,39 @@ pub(crate) trait TlsListElement { /// 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, + /// 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 }, } +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, + }, + }) + } +} + /// Tracks encoding a length-delimited structure in a single pass. pub(crate) struct LengthPrefixedBuffer<'a> { pub(crate) buf: &'a mut Vec, @@ -280,8 +338,8 @@ 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::NonZeroU8 { .. } => &[0xff][..], + ListLength::U16 | ListLength::NonZeroU16 { .. } => &[0xff, 0xff], ListLength::U24 { .. } => &[0xff, 0xff, 0xff], }); @@ -297,12 +355,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]) diff --git a/rustls/src/msgs/deframer/handshake.rs b/rustls/src/msgs/deframer/handshake.rs index c781e9309cd..0d91f0eac82 100644 --- a/rustls/src/msgs/deframer/handshake.rs +++ b/rustls/src/msgs/deframer/handshake.rs @@ -4,7 +4,7 @@ use core::ops::Range; use super::buffers::{BufferProgress, Coalescer, Delocator, Locator}; use crate::error::InvalidMessage; -use crate::msgs::codec::{u24, Codec}; +use crate::msgs::codec::{Codec, u24}; use crate::msgs::message::InboundPlainMessage; use crate::{ContentType, ProtocolVersion}; diff --git a/rustls/src/msgs/deframer/mod.rs b/rustls/src/msgs/deframer/mod.rs index 2f44962c779..4122b52591d 100644 --- a/rustls/src/msgs/deframer/mod.rs +++ b/rustls/src/msgs/deframer/mod.rs @@ -3,7 +3,7 @@ use core::mem; use crate::error::{Error, InvalidMessage}; use crate::msgs::codec::Reader; use crate::msgs::message::{ - read_opaque_message_header, InboundOpaqueMessage, MessageError, HEADER_SIZE, + HEADER_SIZE, InboundOpaqueMessage, MessageError, read_opaque_message_header, }; pub(crate) mod buffers; @@ -106,24 +106,36 @@ mod tests { #[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!( + 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() + ); } #[test] diff --git a/rustls/src/msgs/enums.rs b/rustls/src/msgs/enums.rs index 2e03e85593e..fb10bbea13b 100644 --- a/rustls/src/msgs/enums.rs +++ b/rustls/src/msgs/enums.rs @@ -1,6 +1,6 @@ #![allow(clippy::upper_case_acronyms)] #![allow(non_camel_case_types)] -use crate::crypto::KeyExchangeAlgorithm; +use crate::crypto::{KeyExchangeAlgorithm, hash}; use crate::msgs::codec::{Codec, Reader}; enum_builder! { @@ -19,6 +19,32 @@ enum_builder! { } } +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 `ClientCertificateType` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. @@ -253,10 +279,6 @@ enum_builder! { } } -impl ECPointFormat { - pub(crate) const SUPPORTED: [Self; 1] = [Self::Uncompressed]; -} - enum_builder! { /// The `HeartbeatMode` TLS protocol enum. Values in this enum are taken /// from the various RFCs covering TLS, and are listed by IANA. @@ -281,11 +303,11 @@ enum_builder! { } 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 enum PskKeyExchangeMode { PSK_KE => 0x00, PSK_DHE_KE => 0x01, } @@ -312,19 +334,6 @@ enum_builder! { } } -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] @@ -346,27 +355,26 @@ enum_builder! { /// /// [RFC 9180 Section 7.2]: #[repr(u16)] + #[derive(Default)] pub enum HpkeKdf { + // TODO(XXX): revisit the default configuration. This is just what Cloudflare ships right now. + #[default] 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)] + #[derive(Default)] pub enum HpkeAead { + // TODO(XXX): revisit the default configuration. This is just what Cloudflare ships right now. + #[default] AES_128_GCM => 0x0001, AES_256_GCM => 0x0002, CHACHA20_POLY_1305 => 0x0003, @@ -387,13 +395,6 @@ impl HpkeAead { } } -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_builder! { /// The Encrypted Client Hello protocol version (`EchVersion`). /// @@ -445,9 +446,9 @@ pub(crate) mod tests { HeartbeatMode::PeerNotAllowedToSend, ); test_enum8::(ECCurveType::ExplicitPrime, ECCurveType::NamedCurve); - test_enum8::( - PSKKeyExchangeMode::PSK_KE, - PSKKeyExchangeMode::PSK_DHE_KE, + test_enum8::( + PskKeyExchangeMode::PSK_KE, + PskKeyExchangeMode::PSK_DHE_KE, ); test_enum8::( KeyUpdateRequest::UpdateNotRequested, @@ -457,7 +458,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) { diff --git a/rustls/src/msgs/fragmenter.rs b/rustls/src/msgs/fragmenter.rs index bad02853287..4863677792c 100644 --- a/rustls/src/msgs/fragmenter.rs +++ b/rustls/src/msgs/fragmenter.rs @@ -1,6 +1,6 @@ +use crate::Error; use crate::enums::{ContentType, ProtocolVersion}; use crate::msgs::message::{OutboundChunks, OutboundPlainMessage, PlainMessage}; -use crate::Error; 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; diff --git a/rustls/src/msgs/handshake.rs b/rustls/src/msgs/handshake.rs index 03b8c81cbc6..9c4338f69d2 100644 --- a/rustls/src/msgs/handshake.rs +++ b/rustls/src/msgs/handshake.rs @@ -1,10 +1,10 @@ +use alloc::boxed::Box; 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::{fmt, iter}; use pki_types::{CertificateDer, DnsName}; @@ -13,21 +13,24 @@ use pki_types::{CertificateDer, DnsName}; use crate::crypto::ActiveKeyExchange; use crate::crypto::SecureRandom; use crate::enums::{ - CertificateCompressionAlgorithm, CipherSuite, EchClientHelloType, HandshakeType, - ProtocolVersion, SignatureScheme, + CertificateCompressionAlgorithm, CertificateType, CipherSuite, EchClientHelloType, + HandshakeType, ProtocolVersion, SignatureScheme, }; 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::base::{MaybeEmpty, NonEmpty, Payload, PayloadU8, PayloadU16, PayloadU24}; +use crate::msgs::codec::{ + self, Codec, LengthPrefixedBuffer, ListLength, Reader, TlsListElement, TlsListIter, +}; use crate::msgs::enums::{ - CertificateStatusType, CertificateType, ClientCertificateType, Compression, ECCurveType, - ECPointFormat, EchVersion, ExtensionType, HpkeAead, HpkeKdf, HpkeKem, KeyUpdateRequest, - NamedGroup, PSKKeyExchangeMode, ServerNameType, + CertificateStatusType, ClientCertificateType, Compression, ECCurveType, ECPointFormat, + EchVersion, ExtensionType, HpkeAead, HpkeKdf, HpkeKem, KeyUpdateRequest, NamedGroup, + PskKeyExchangeMode, ServerNameType, }; use crate::rand; +use crate::sync::Arc; use crate::verify::DigitallySignedStruct; use crate::x509::wrap_in_sequence; @@ -37,10 +40,10 @@ use crate::x509::wrap_in_sequence; /// 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:meta])* $vis:vis struct $name:ident, $inner:ident$(<$inner_ty:ty>)?,) => { $(#[$comment])* #[derive(Clone, Debug)] - $vis struct $name($inner); + $vis struct $name($inner$(<$inner_ty>)?); impl From> for $name { fn from(v: Vec) -> Self { @@ -67,7 +70,7 @@ macro_rules! wrapped_payload( ); #[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 { @@ -164,7 +167,7 @@ impl Codec<'_> for SessionId { } impl SessionId { - pub fn random(secure_random: &dyn SecureRandom) -> Result { + pub(crate) fn random(secure_random: &dyn SecureRandom) -> Result { let mut data = [0u8; 32]; secure_random.fill(&mut data)?; Ok(Self { data, len: 32 }) @@ -206,169 +209,269 @@ impl UnknownExtension { } } +#[derive(Clone, Copy, Debug)] +pub(crate) struct SupportedEcPointFormats { + pub(crate) uncompressed: bool, +} + +impl Codec<'_> for SupportedEcPointFormats { + fn encode(&self, bytes: &mut Vec) { + let inner = LengthPrefixedBuffer::new(ECPointFormat::SIZE_LEN, bytes); + + if self.uncompressed { + ECPointFormat::Uncompressed.encode(inner.buf); + } + } + + fn read(r: &mut Reader<'_>) -> Result { + let mut uncompressed = false; + + for pf in TlsListIter::::new(r)? { + if let ECPointFormat::Uncompressed = pf? { + uncompressed = true; + } + } + + Ok(Self { uncompressed }) + } +} + +impl Default for SupportedEcPointFormats { + fn default() -> Self { + Self { uncompressed: true } + } +} + +/// RFC8422: `ECPointFormat ec_point_format_list<1..2^8-1>` impl TlsListElement for ECPointFormat { - const SIZE_LEN: ListLength = ListLength::U8; + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("ECPointFormats"), + }; } +/// RFC8422: `NamedCurve named_curve_list<2..2^16-1>` impl TlsListElement for NamedGroup { - const SIZE_LEN: ListLength = ListLength::U16; + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("NamedGroups"), + }; } +/// RFC8446: `SignatureScheme supported_signature_algorithms<2..2^16-2>;` impl TlsListElement for SignatureScheme { - const SIZE_LEN: ListLength = ListLength::U16; + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::NoSignatureSchemes, + }; } #[derive(Clone, Debug)] -pub(crate) enum ServerNamePayload { - HostName(DnsName<'static>), - IpAddress(PayloadU16), - Unknown(Payload<'static>), -} +pub(crate) enum ServerNamePayload<'a> { + /// A successfully decoded value: + SingleDnsName(DnsName<'a>), -impl ServerNamePayload { - pub(crate) fn new_hostname(hostname: DnsName<'static>) -> Self { - Self::HostName(hostname) - } - - fn read_hostname(r: &mut Reader<'_>) -> Result { - use pki_types::ServerName; - let raw = PayloadU16::read(r)?; + /// A DNS name which was actually an IP address + IpAddress, - 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) - } - } - } + /// A successfully decoded, but syntactically-invalid value. + Invalid, +} - 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), +impl ServerNamePayload<'_> { + fn into_owned(self) -> ServerNamePayload<'static> { + match self { + Self::SingleDnsName(d) => ServerNamePayload::SingleDnsName(d.to_owned()), + Self::IpAddress => ServerNamePayload::IpAddress, + Self::Invalid => ServerNamePayload::Invalid, } } -} -#[derive(Clone, Debug)] -pub struct ServerName { - pub(crate) typ: ServerNameType, - pub(crate) payload: ServerNamePayload, + /// RFC6066: `ServerName server_name_list<1..2^16-1>` + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("ServerNames"), + }; } -impl Codec<'_> for ServerName { +/// 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) { - self.typ.encode(bytes); - self.payload.encode(bytes); + 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<'_>) -> Result { - let typ = ServerNameType::read(r)?; + fn read(r: &mut Reader<'a>) -> Result { + let mut found = None; - let payload = match typ { - ServerNameType::HostName => ServerNamePayload::read_hostname(r)?, - _ => ServerNamePayload::Unknown(Payload::read(r).into_owned()), - }; + let len = Self::SIZE_LEN.read(r)?; + let mut sub = r.sub(len)?; - Ok(Self { typ, payload }) + 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: ignoring IP address presented as hostname ({_invalid:?})" + ); + Some(Self::IpAddress) + } + + HostNamePayload::Invalid(_invalid) => { + warn!( + "Illegal SNI hostname received {:?}", + String::from_utf8_lossy(&_invalid.0) + ); + Some(Self::Invalid) + } + }; + } + + Ok(found.unwrap_or(Self::Invalid)) } } -impl TlsListElement for ServerName { - const SIZE_LEN: ListLength = ListLength::U16; +impl<'a> From<&DnsName<'a>> for ServerNamePayload<'static> { + fn from(value: &DnsName<'a>) -> Self { + Self::SingleDnsName(trim_hostname_trailing_dot_for_sni(value)) + } } -pub(crate) trait ConvertServerNameList { - fn has_duplicate_names_for_type(&self) -> bool; - fn single_hostname(&self) -> Option>; +#[derive(Clone, Debug)] +pub(crate) enum HostNamePayload { + HostName(DnsName<'static>), + IpAddress(PayloadU16), + Invalid(PayloadU16), } -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)) - } +impl HostNamePayload { + fn read(r: &mut Reader<'_>) -> Result { + use pki_types::ServerName; + let raw = PayloadU16::::read(r)?; - 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 - } + 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(_) => Ok(Self::Invalid(raw)), } - - self.iter() - .filter_map(only_dns_hostnames) - .next() } } -wrapped_payload!(pub struct ProtocolName, PayloadU8,); +wrapped_payload!( + /// RFC7301: `opaque ProtocolName<1..2^8-1>;` + pub(crate) struct ProtocolName, PayloadU8, +); -impl TlsListElement for ProtocolName { - const SIZE_LEN: ListLength = ListLength::U16; +impl PartialEq for ProtocolName { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } } -pub(crate) trait ConvertProtocolNameList { - fn from_slices(names: &[&[u8]]) -> Self; - fn to_slices(&self) -> Vec<&[u8]>; - fn as_single_slice(&self) -> Option<&[u8]>; +impl Deref for ProtocolName { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } } -impl ConvertProtocolNameList for Vec { - fn from_slices(names: &[&[u8]]) -> Self { - let mut ret = Self::new(); +/// RFC7301: `ProtocolName protocol_name_list<2..2^16-1>` +impl TlsListElement for ProtocolName { + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("ProtocolNames"), + }; +} - for name in names { - ret.push(ProtocolName::from(name.to_vec())); - } +/// RFC7301 encodes a single protocol name as `Vec` +#[derive(Clone, Debug)] +pub(crate) struct SingleProtocolName(ProtocolName); - ret +impl SingleProtocolName { + pub(crate) fn new(single: ProtocolName) -> Self { + Self(single) } - fn to_slices(&self) -> Vec<&[u8]> { - self.iter() - .map(|proto| proto.as_ref()) - .collect::>() + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("ProtocolNames"), + }; +} + +impl Codec<'_> for SingleProtocolName { + fn encode(&self, bytes: &mut Vec) { + let body = LengthPrefixedBuffer::new(Self::SIZE_LEN, bytes); + self.0.encode(body.buf); } - fn as_single_slice(&self) -> Option<&[u8]> { - if self.len() == 1 { - Some(self[0].as_ref()) + fn read(reader: &mut Reader<'_>) -> Result { + let len = Self::SIZE_LEN.read(reader)?; + let mut sub = reader.sub(len)?; + + let item = ProtocolName::read(&mut sub)?; + + if sub.any_left() { + Err(InvalidMessage::TrailingData("SingleProtocolName")) } else { - None + Ok(Self(item)) } } } +impl AsRef for SingleProtocolName { + fn as_ref(&self) -> &ProtocolName { + &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: PayloadU16, } 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()), } } - - pub fn group(&self) -> NamedGroup { - self.group - } } impl Codec<'_> for KeyShareEntry { @@ -388,7 +491,8 @@ impl Codec<'_> for KeyShareEntry { // --- TLS 1.3 PresharedKey offers --- #[derive(Clone, Debug)] pub(crate) struct PresharedKeyIdentity { - pub(crate) identity: PayloadU16, + /// RFC8446: `opaque identity<1..2^16-1>;` + pub(crate) identity: PayloadU16, pub(crate) obfuscated_ticket_age: u32, } @@ -415,18 +519,27 @@ impl Codec<'_> for PresharedKeyIdentity { } } +/// RFC8446: `PskIdentity identities<7..2^16-1>;` impl TlsListElement for PresharedKeyIdentity { - const SIZE_LEN: ListLength = ListLength::U16; + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("PskIdentities"), + }; } -wrapped_payload!(pub(crate) struct PresharedKeyBinder, PayloadU8,); +wrapped_payload!( + /// RFC8446: `opaque PskBinderEntry<32..255>;` + pub(crate) struct PresharedKeyBinder, PayloadU8, +); +/// RFC8446: `PskBinderEntry binders<33..2^16-1>;` impl TlsListElement for PresharedKeyBinder { - const SIZE_LEN: ListLength = ListLength::U16; + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("PskBinders"), + }; } #[derive(Clone, Debug)] -pub struct PresharedKeyOffer { +pub(crate) struct PresharedKeyOffer { pub(crate) identities: Vec, pub(crate) binders: Vec, } @@ -458,12 +571,13 @@ impl Codec<'_> for PresharedKeyOffer { // --- RFC6066 certificate status request --- wrapped_payload!(pub(crate) struct ResponderId, PayloadU16,); +/// RFC6066: `ResponderID responder_id_list<0..2^16-1>;` impl TlsListElement for ResponderId { const SIZE_LEN: ListLength = ListLength::U16; } #[derive(Clone, Debug)] -pub struct OcspCertificateStatusRequest { +pub(crate) struct OcspCertificateStatusRequest { pub(crate) responder_ids: Vec, pub(crate) extensions: PayloadU16, } @@ -484,7 +598,7 @@ impl Codec<'_> for OcspCertificateStatusRequest { } #[derive(Clone, Debug)] -pub enum CertificateStatusRequest { +pub(crate) enum CertificateStatusRequest { Ocsp(OcspCertificateStatusRequest), Unknown((CertificateStatusType, Payload<'static>)), } @@ -492,7 +606,7 @@ pub enum CertificateStatusRequest { impl Codec<'_> for CertificateStatusRequest { fn encode(&self, bytes: &mut Vec) { match self { - Self::Ocsp(ref r) => r.encode(bytes), + Self::Ocsp(r) => r.encode(bytes), Self::Unknown((typ, payload)) => { typ.encode(bytes); payload.encode(bytes); @@ -528,169 +642,449 @@ impl CertificateStatusRequest { // --- -impl TlsListElement for PSKKeyExchangeMode { - const SIZE_LEN: ListLength = ListLength::U8; +/// 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"), + }; +} + +/// RFC8446: `KeyShareEntry client_shares<0..2^16-1>;` impl TlsListElement for KeyShareEntry { const SIZE_LEN: ListLength = ListLength::U16; } +/// 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 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 SupportedProtocolVersions { + fn encode(&self, bytes: &mut Vec) { + 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(reader: &mut Reader<'_>) -> Result { + let mut tls12 = false; + let mut tls13 = false; + + for pv in TlsListIter::::new(reader)? { + match pv? { + ProtocolVersion::TLSv1_3 => tls13 = true, + ProtocolVersion::TLSv1_2 => tls12 = true, + _ => continue, + }; + } + + Ok(Self { tls13, tls12 }) + } +} + impl TlsListElement for ProtocolVersion { - const SIZE_LEN: ListLength = ListLength::U8; + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("ProtocolVersions"), + }; } +/// 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::U8; + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("CertificateTypes"), + }; } +/// RFC8879: `CertificateCompressionAlgorithm algorithms<2..2^8-2>;` impl TlsListElement for CertificateCompressionAlgorithm { - const SIZE_LEN: ListLength = ListLength::U8; + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("CertificateCompressionAlgorithms"), + }; } -#[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), +/// 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<'a> { + /// QUIC transport parameters + pub(crate) transport_parameters: Option>, + + /// ALPN protocols + pub(crate) protocols: Option>, +} + +impl ClientExtensionsInput<'_> { + pub(crate) fn from_alpn(alpn_protocols: Vec>) -> ClientExtensionsInput<'static> { + let protocols = match alpn_protocols.is_empty() { + true => None, + false => Some( + alpn_protocols + .into_iter() + .map(ProtocolName::from) + .collect::>(), + ), + }; + + ClientExtensionsInput { + transport_parameters: None, + protocols, + } + } + + pub(crate) fn into_owned(self) -> ClientExtensionsInput<'static> { + let Self { + transport_parameters, + protocols, + } = self; + ClientExtensionsInput { + transport_parameters: transport_parameters.map(|x| x.into_owned()), + protocols, + } + } } -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, +#[derive(Clone)] +pub(crate) enum TransportParameters<'a> { + /// QUIC transport parameters (RFC9001 prior to draft 33) + QuicDraft(Payload<'a>), + + /// QUIC transport parameters (RFC9001) + Quic(Payload<'a>), +} + +impl TransportParameters<'_> { + pub(crate) fn into_owned(self) -> TransportParameters<'static> { + match self { + Self::QuicDraft(v) => TransportParameters::QuicDraft(v.into_owned()), + Self::Quic(v) => TransportParameters::Quic(v.into_owned()), } } } -impl Codec<'_> for ClientExtension { +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, + + /// QUIC transport parameters (RFC9001 prior to draft 33) + ExtensionType::TransportParametersDraft => + pub(crate) transport_parameters_draft: Option>, + + /// Encrypted inner client hello (draft-ietf-tls-esni) + ExtensionType::EncryptedClientHello => + pub(crate) encrypted_client_hello: Option, + + /// Encrypted client hello outer extensions (draft-ietf-tls-esni) + 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, + transport_parameters_draft, + 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, + 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: transport_parameters.map(|x| x.into_owned()), + renegotiation_info, + transport_parameters_draft: transport_parameters_draft.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) { - self.ext_type().encode(bytes); + let order = self.used_extensions_in_encoding_order(); - 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), + 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<'_>) -> Result { - let typ = ExtensionType::read(r)?; - let len = u16::read(r)? as usize; + 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)?; - 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)?) + 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); } - ExtensionType::CertificateAuthorities => Self::AuthorityNames(Vec::read(&mut sub)?), - _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), - }; + } - sub.expect_empty("ClientExtension") - .map(|_| ext) + Ok(out) } } @@ -709,199 +1103,179 @@ fn trim_hostname_trailing_dot_for_sni(dns_name: &DnsName<'_>) -> DnsName<'static } } -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)), - }; - - Self::ServerName(vec![name]) - } -} - #[derive(Clone, Debug)] -pub enum ClientSessionTicket { +pub(crate) enum ClientSessionTicket { Request, Offer(Payload<'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 { +impl<'a> Codec<'a> for ClientSessionTicket { 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), + match self { + Self::Request => (), + Self::Offer(p) => p.encode(bytes), } } - 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) + fn read(r: &mut Reader<'a>) -> Result { + Ok(match r.left() { + 0 => Self::Request, + _ => Self::Offer(Payload::read(r).into_owned()), + }) } } -impl ServerExtension { - pub(crate) fn make_alpn(proto: &[&[u8]]) -> Self { - Self::Protocols(Vec::from_slices(proto)) - } +#[derive(Default)] +pub(crate) struct ServerExtensionsInput<'a> { + /// QUIC transport parameters + pub(crate) transport_parameters: Option>, +} + +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>, + + /// QUIC transport parameters (RFC9001 prior to draft 33) + ExtensionType::TransportParametersDraft => + pub(crate) transport_parameters_draft: Option>, + + /// Early data is accepted (RFC8446) + ExtensionType::EarlyData => + pub(crate) early_data_ack: Option<()>, + + /// Encrypted inner client hello response (draft-ietf-tls-esni) + ExtensionType::EncryptedClientHello => + pub(crate) encrypted_client_hello_ack: Option, + } + { + pub(crate) unknown_extensions: BTreeSet, + } +} + +impl ServerExtensions<'_> { + 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, + transport_parameters_draft, + early_data_ack, + encrypted_client_hello_ack, + unknown_extensions, + } = self; + ServerExtensions { + 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: transport_parameters.map(|x| x.into_owned()), + transport_parameters_draft: transport_parameters_draft.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); - #[cfg(feature = "tls12")] - pub(crate) fn make_empty_renegotiation_info() -> Self { - let empty = Vec::new(); - Self::RenegotiationInfo(PayloadU8::new(empty)) + for ext in Self::ALL_EXTENSIONS { + self.encode_one(*ext, extensions.buf); + } } -} -#[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<'a>) -> Result { + let mut out = Self::default(); + let mut checker = DuplicateExtensionChecker::new(); - 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(), - }; + let len = usize::from(u16::read(r)?); + let mut sub = r.sub(len)?; - if r.any_left() { - ret.extensions = Vec::read(r)?; + while sub.any_left() { + out.read_one(&mut sub, |unknown| checker.check(unknown))?; } - match (r.any_left(), ret.extensions.is_empty()) { - (true, _) => Err(InvalidMessage::TrailingData("ClientHelloPayload")), - (_, true) => Err(InvalidMessage::MissingData("ClientHelloPayload")), - _ => Ok(ret), - } + out.unknown_extensions = checker.0; + Ok(out) } } -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; +#[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 { @@ -925,171 +1299,30 @@ impl ClientHelloPayload { 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); - } + self.extensions.encode(bytes); return; } }; - // Safety: not empty check in match guard. - let first_compressed_type = *to_compress.first().unwrap(); + let mut compressed = self.extensions.clone(); - // 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, + // First, eliminate the full-fat versions of the extensions + for e in &to_compress { + compressed.clear(*e); } - } - pub fn sigalgs_extension(&self) -> Option<&[SignatureScheme]> { - let ext = self.find_extension(ExtensionType::SignatureAlgorithms)?; - match *ext { - ClientExtension::SignatureAlgorithms(ref req) => Some(req), - _ => None, - } - } + // Replace with the marker noting which extensions were elided. + compressed.encrypted_client_hello_outer = Some(to_compress); - pub(crate) fn namedgroups_extension(&self) -> Option<&[NamedGroup]> { - let ext = self.find_extension(ExtensionType::EllipticCurves)?; - match *ext { - ClientExtension::NamedGroups(ref req) => Some(req.as_slice()), - _ => 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.as_slice()), - _ => 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, - } + // And encode as normal. + compressed.encode(bytes); } pub(crate) fn has_keyshare_extension_with_duplicates(&self) -> bool { - self.keyshare_extension() + self.key_shares + .as_ref() .map(|entries| { has_duplicates::<_, _, u16>( entries @@ -1100,145 +1333,154 @@ impl ClientHelloPayload { .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.as_slice()), - _ => None, + pub(crate) fn has_certificate_compression_extension_with_duplicates(&self) -> bool { + if let Some(algs) = &self.certificate_compression_algorithms { + has_duplicates::<_, _, u16>(algs.iter().cloned()) + } else { + false } } +} - pub(crate) fn psk_mode_offered(&self, mode: PSKKeyExchangeMode) -> bool { - self.psk_modes() - .map(|modes| modes.contains(&mode)) - .unwrap_or(false) +impl Codec<'_> for ClientHelloPayload { + fn encode(&self, bytes: &mut Vec) { + self.payload_encode(bytes, Encoding::Standard) } - 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()); + 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), } } +} - #[cfg(feature = "tls12")] - pub(crate) fn ems_support_offered(&self) -> bool { - self.find_extension(ExtensionType::ExtendedMasterSecret) - .is_some() +impl Deref for ClientHelloPayload { + type Target = ClientExtensions<'static>; + fn deref(&self) -> &Self::Target { + &self.extensions } +} - pub(crate) fn early_data_extension_offered(&self) -> bool { - self.find_extension(ExtensionType::EarlyData) - .is_some() +impl DerefMut for ClientHelloPayload { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.extensions } +} - 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, - } - } +/// RFC8446: `CipherSuite cipher_suites<2..2^16-2>;` +impl TlsListElement for CipherSuite { + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("CipherSuites"), + }; +} - 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 - } - } +/// RFC5246: `CompressionMethod compression_methods<1..2^8-1>;` +impl TlsListElement for Compression { + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("Compressions"), + }; +} - pub(crate) fn certificate_authorities_extension(&self) -> Option<&[DistinguishedName]> { - match self.find_extension(ExtensionType::CertificateAuthorities)? { - ClientExtension::AuthorityNames(ext) => Some(ext), - _ => unreachable!("extension type checked"), - } - } +/// draft-ietf-tls-esni-17: `ExtensionType OuterExtensions<2..254>;` +impl TlsListElement for ExtensionType { + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("ExtensionTypes"), + }; } -#[derive(Clone, Debug)] -pub(crate) enum HelloRetryExtension { - KeyShare(NamedGroup), - Cookie(PayloadU16), - SupportedVersions(ProtocolVersion), - EchHelloRetryRequest(Vec), - Unknown(UnknownExtension), +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>, + } } -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 HelloRetryRequestExtensions<'_> { + fn into_owned(self) -> HelloRetryRequestExtensions<'static> { + let Self { + key_share, + cookie, + supported_versions, + encrypted_client_hello, + order, + } = self; + HelloRetryRequestExtensions { + key_share, + cookie, + supported_versions, + encrypted_client_hello: encrypted_client_hello.map(|x| x.into_owned()), + order, } } } -impl Codec<'_> for HelloRetryExtension { +impl<'a> Codec<'a> for HelloRetryRequestExtensions<'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::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), + 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; + fn read(r: &mut Reader<'a>) -> Result { + let mut out = Self::default(); + + // we must record order, so re-encoding round trips. this is needed, + // unfortunately, for ECH HRR confirmation + let mut order = vec![]; + + let len = usize::from(u16::read(r)?); 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)), - }; + while sub.any_left() { + let typ = out.read_one(&mut sub, |_unk| { + Err(InvalidMessage::UnknownHelloRetryRequestExtension) + })?; - sub.expect_empty("HelloRetryExtension") - .map(|_| ext) - } -} + order.push(typ); + } -impl TlsListElement for HelloRetryExtension { - const SIZE_LEN: ListLength = ListLength::U16; + out.order = Some(order); + Ok(out) + } } #[derive(Clone, Debug)] -pub struct HelloRetryRequest { +pub(crate) struct HelloRetryRequest { pub(crate) legacy_version: ProtocolVersion, - pub session_id: SessionId, + pub(crate) session_id: SessionId, pub(crate) cipher_suite: CipherSuite, - pub(crate) extensions: Vec, + pub(crate) extensions: HelloRetryRequestExtensions<'static>, } impl Codec<'_> for HelloRetryRequest { @@ -1259,69 +1501,12 @@ impl Codec<'_> for HelloRetryRequest { legacy_version: ProtocolVersion::Unknown(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) { self.legacy_version.encode(bytes); HELLO_RETRY_REQUEST_RANDOM.encode(bytes); @@ -1335,35 +1520,45 @@ impl HelloRetryRequest { // // 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); - } - } + 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), } } } +impl Deref for HelloRetryRequest { + type Target = HelloRetryRequestExtensions<'static>; + fn deref(&self) -> &Self::Target { + &self.extensions + } +} + +impl DerefMut for HelloRetryRequest { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.extensions + } +} + #[derive(Clone, Debug)] -pub struct ServerHelloPayload { - pub extensions: Vec, +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 { @@ -1381,7 +1576,14 @@ impl Codec<'_> for ServerHelloPayload { // "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 extensions = Box::new( + if r.any_left() { + ServerExtensions::read(r)? + } else { + ServerExtensions::default() + } + .into_owned(), + ); let ret = Self { legacy_version: ProtocolVersion::Unknown(0), @@ -1397,78 +1599,37 @@ impl Codec<'_> for ServerHelloPayload { } } -impl HasServerExtensions for ServerHelloPayload { - fn extensions(&self) -> &[ServerExtension] { - &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.as_slice()), - _ => 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), - } + 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); + } +} - if !self.extensions.is_empty() { - 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 } } #[derive(Clone, Default, Debug)] -pub struct CertificateChain<'a>(pub Vec>); +pub(crate) struct CertificateChain<'a>(pub(crate) Vec>); impl CertificateChain<'_> { pub(crate) fn into_owned(self) -> CertificateChain<'static> { @@ -1513,84 +1674,62 @@ impl TlsListElement for CertificateDer<'_> { /// 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 +1738,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> { 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,7 +1758,7 @@ impl TlsListElement for CertificateEntry<'_> { } #[derive(Debug)] -pub struct CertificatePayloadTls13<'a> { +pub(crate) struct CertificatePayloadTls13<'a> { pub(crate) context: PayloadU8, pub(crate) entries: Vec>, } @@ -1682,10 +1796,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 }) @@ -1704,41 +1815,21 @@ 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 + .0 + .clone() + .into_vec() + }) .unwrap_or_default() } @@ -1841,7 +1932,8 @@ 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: PayloadU8, } #[cfg(feature = "tls12")] @@ -1859,7 +1951,8 @@ impl Codec<'_> for ClientEcdhParams { #[cfg(feature = "tls12")] #[derive(Debug)] pub(crate) struct ClientDhParams { - pub(crate) public: PayloadU16, + /// RFC5246: `opaque dh_Yc<1..2^16-1>;` + pub(crate) public: PayloadU16, } #[cfg(feature = "tls12")] @@ -1878,7 +1971,8 @@ 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: PayloadU8, } impl ServerEcdhParams { @@ -1914,9 +2008,12 @@ 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: PayloadU16, + /// RFC5246: `opaque dh_g<1..2^16-1>;` + pub(crate) dh_g: PayloadU16, + /// RFC5246: `opaque dh_Ys<1..2^16-1>;` + pub(crate) dh_Ys: PayloadU16, } impl ServerDhParams { @@ -1999,20 +2096,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 +2122,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), } } @@ -2041,7 +2138,7 @@ 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 { + if let Self::Unknown(unk) = self { let mut rd = Reader::init(unk.bytes()); let result = ServerKeyExchange { @@ -2058,88 +2155,11 @@ 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; + const SIZE_LEN: ListLength = ListLength::NonZeroU8 { + empty_error: InvalidMessage::IllegalEmptyList("ClientCertificateTypes"), + }; } wrapped_payload!( @@ -2155,8 +2175,10 @@ wrapped_payload!( /// 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, - PayloadU16, + PayloadU16, ); impl DistinguishedName { @@ -2173,12 +2195,14 @@ impl DistinguishedName { } } +/// 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; } #[derive(Debug)] -pub struct CertificateRequestPayload { +pub(crate) struct CertificateRequestPayload { pub(crate) certtypes: Vec, pub(crate) sigschemes: Vec, pub(crate) canames: Vec, @@ -2209,74 +2233,57 @@ impl Codec<'_> for CertificateRequestPayload { } } -#[derive(Debug)] -pub(crate) enum CertReqExtension { - SignatureAlgorithms(Vec), - AuthorityNames(Vec), - CertificateCompressionAlgorithms(Vec), - Unknown(UnknownExtension), -} +extension_struct! { + pub(crate) struct CertificateRequestExtensions { + ExtensionType::SignatureAlgorithms => + pub(crate) signature_algorithms: Option>, -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, - } + 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 extensions = LengthPrefixedBuffer::new(ListLength::U16, 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), + 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) struct CertificateRequestPayloadTls13 { pub(crate) context: PayloadU8, - pub(crate) extensions: Vec, + pub(crate) extensions: CertificateRequestExtensions, } impl Codec<'_> for CertificateRequestPayloadTls13 { @@ -2287,7 +2294,7 @@ impl Codec<'_> for CertificateRequestPayloadTls13 { fn read(r: &mut Reader<'_>) -> Result { let context = PayloadU8::read(r)?; - let extensions = Vec::read(r)?; + let extensions = CertificateRequestExtensions::read(r)?; Ok(Self { context, @@ -2296,43 +2303,9 @@ 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) struct NewSessionTicketPayload { pub(crate) lifetime_hint: u32, // Tickets can be large (KB), so we deserialise this straight // into an Arc, so it can be passed directly into the client's @@ -2368,58 +2341,45 @@ impl Codec<'_> for NewSessionTicketPayload { } // -- 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) struct NewSessionTicketPayloadTls13 { pub(crate) lifetime: u32, pub(crate) age_add: u32, pub(crate) nonce: PayloadU8, pub(crate) ticket: Arc, - pub(crate) exts: Vec, + pub(crate) extensions: NewSessionTicketExtensions, } impl NewSessionTicketPayloadTls13 { @@ -2429,29 +2389,7 @@ impl NewSessionTicketPayloadTls13 { 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, + extensions: NewSessionTicketExtensions::default(), } } } @@ -2462,22 +2400,27 @@ impl Codec<'_> for NewSessionTicketPayloadTls13 { 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 age_add = u32::read(r)?; let nonce = PayloadU8::read(r)?; - let ticket = Arc::new(PayloadU16::read(r)?); - let exts = Vec::read(r)?; + // nb. RFC8446: `opaque ticket<1..2^16-1>;` + let ticket = Arc::new(match PayloadU16::::read(r) { + Err(InvalidMessage::IllegalEmptyValue) => Err(InvalidMessage::EmptyTicketValue), + Err(err) => Err(err), + Ok(pl) => Ok(PayloadU16::new(pl.0)), + }?); + let extensions = NewSessionTicketExtensions::read(r)?; Ok(Self { lifetime, age_add, nonce, ticket, - exts, + extensions, }) } } @@ -2485,8 +2428,8 @@ impl Codec<'_> for NewSessionTicketPayloadTls13 { // -- RFC6066 certificate status types /// Only supports OCSP -#[derive(Debug)] -pub struct CertificateStatus<'a> { +#[derive(Clone, Debug)] +pub(crate) struct CertificateStatus<'a> { pub(crate) ocsp_response: PayloadU24<'a>, } @@ -2530,7 +2473,7 @@ 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>, @@ -2570,7 +2513,7 @@ impl CompressedCertificatePayload<'_> { } #[derive(Debug)] -pub enum HandshakePayload<'a> { +pub(crate) enum HandshakePayload<'a> { HelloRequest, ClientHello(ClientHelloPayload), ServerHello(ServerHelloPayload), @@ -2587,38 +2530,71 @@ pub enum HandshakePayload<'a> { ClientKeyExchange(Payload<'a>), NewSessionTicket(NewSessionTicketPayload), NewSessionTicketTls13(NewSessionTicketPayloadTls13), - EncryptedExtensions(Vec), + EncryptedExtensions(Box>), KeyUpdate(KeyUpdateRequest), Finished(Payload<'a>), CertificateStatus(CertificateStatus<'a>), MessageHash(Payload<'a>), - Unknown(Payload<'a>), + Unknown((HandshakeType, Payload<'a>)), } impl HandshakePayload<'_> { fn encode(&self, bytes: &mut Vec) { use self::HandshakePayload::*; - match *self { + 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), + 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, } } @@ -2642,21 +2618,18 @@ impl HandshakePayload<'_> { ClientKeyExchange(x) => ClientKeyExchange(x.into_owned()), NewSessionTicket(x) => NewSessionTicket(x), NewSessionTicketTls13(x) => NewSessionTicketTls13(x), - EncryptedExtensions(x) => EncryptedExtensions(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(x) => Unknown(x.into_owned()), + Unknown((t, x)) => Unknown((t, x.into_owned())), } } } #[derive(Debug)] -pub struct HandshakeMessagePayload<'a> { - pub typ: HandshakeType, - pub payload: HandshakePayload<'a>, -} +pub struct HandshakeMessagePayload<'a>(pub(crate) HandshakePayload<'a>); impl<'a> Codec<'a> for HandshakeMessagePayload<'a> { fn encode(&self, bytes: &mut Vec) { @@ -2673,7 +2646,7 @@ impl<'a> HandshakeMessagePayload<'a> { r: &mut Reader<'a>, vers: ProtocolVersion, ) -> Result { - let mut typ = HandshakeType::read(r)?; + let typ = HandshakeType::read(r)?; let len = codec::u24::read(r)?.0 as usize; let mut sub = r.sub(len)?; @@ -2689,7 +2662,6 @@ impl<'a> HandshakeMessagePayload<'a> { 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)?; @@ -2739,7 +2711,7 @@ impl<'a> HandshakeMessagePayload<'a> { HandshakePayload::NewSessionTicket(p) } HandshakeType::EncryptedExtensions => { - HandshakePayload::EncryptedExtensions(Vec::read(&mut sub)?) + HandshakePayload::EncryptedExtensions(Box::new(ServerExtensions::read(&mut sub)?)) } HandshakeType::KeyUpdate => { HandshakePayload::KeyUpdate(KeyUpdateRequest::read(&mut sub)?) @@ -2760,19 +2732,24 @@ impl<'a> HandshakeMessagePayload<'a> { // not legal on wire return Err(InvalidMessage::UnexpectedMessage("HelloRetryRequest")); } - _ => HandshakePayload::Unknown(Payload::read(&mut sub)), + _ => HandshakePayload::Unknown((typ, Payload::read(&mut sub))), }; sub.expect_empty("HandshakeMessagePayload") - .map(|_| Self { typ, payload }) + .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 + } - let binder_len = match self.payload { - HandshakePayload::ClientHello(ref ch) => match ch.extensions.last() { - Some(ClientExtension::PresharedKey(ref offer)) => { + 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 @@ -2782,20 +2759,14 @@ impl<'a> HandshakeMessagePayload<'a> { _ => 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); + self.0 + .wire_handshake_type() + .encode(bytes); let nested = LengthPrefixedBuffer::new( ListLength::U24 { @@ -2805,7 +2776,7 @@ impl<'a> HandshakeMessagePayload<'a> { bytes, ); - match &self.payload { + 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), @@ -2814,23 +2785,16 @@ impl<'a> HandshakeMessagePayload<'a> { } // All other payload types are encoded the same regardless of purpose. - _ => self.payload.encode(nested.buf), + _ => self.0.encode(nested.buf), } } pub(crate) fn build_handshake_hash(hash: &[u8]) -> Self { - Self { - typ: HandshakeType::MessageHash, - payload: HandshakePayload::MessageHash(Payload::new(hash.to_vec())), - } + Self(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(), - } + HandshakeMessagePayload(self.0.into_owned()) } } @@ -2854,15 +2818,19 @@ impl Codec<'_> for HpkeSymmetricCipherSuite { } } +/// draft-ietf-tls-esni-24: `HpkeSymmetricCipherSuite cipher_suites<4..2^16-4>;` impl TlsListElement for HpkeSymmetricCipherSuite { - const SIZE_LEN: ListLength = ListLength::U16; + const SIZE_LEN: ListLength = ListLength::NonZeroU16 { + empty_error: InvalidMessage::IllegalEmptyList("HpkeSymmetricCipherSuites"), + }; } #[derive(Clone, Debug, PartialEq)] pub struct HpkeKeyConfig { pub config_id: u8, pub kem_id: HpkeKem, - pub public_key: PayloadU16, + /// draft-ietf-tls-esni-24: `opaque HpkePublicKey<1..2^16-1>;` + pub public_key: PayloadU16, pub symmetric_cipher_suites: Vec, } @@ -2921,7 +2889,7 @@ impl Codec<'_> for EchConfigContents { 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); + PayloadU8::::encode_slice(dns_name.as_ref().as_ref(), bytes); self.extensions.encode(bytes); } @@ -2930,9 +2898,13 @@ impl Codec<'_> for EchConfigContents { 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() + DnsName::try_from( + PayloadU8::::read(r)? + .0 + .as_slice(), + ) + .map_err(|_| InvalidMessage::InvalidServerName)? + .to_owned() }, extensions: Vec::read(r)?, }) @@ -2998,8 +2970,8 @@ pub enum EchConfigExtension { impl EchConfigExtension { pub(crate) fn ext_type(&self) -> ExtensionType { - match *self { - Self::Unknown(ref r) => r.typ, + match self { + Self::Unknown(r) => r.typ, } } } @@ -3009,8 +2981,8 @@ impl Codec<'_> for EchConfigExtension { self.ext_type().encode(bytes); let nested = LengthPrefixedBuffer::new(ListLength::U16, bytes); - match *self { - Self::Unknown(ref r) => r.encode(nested.buf), + match self { + Self::Unknown(r) => r.encode(nested.buf), } } @@ -3038,7 +3010,7 @@ impl TlsListElement for EchConfigExtension { /// /// [draft-ietf-tls-esni Section 5]: #[derive(Clone, Debug)] -pub enum EncryptedClientHello { +pub(crate) enum EncryptedClientHello { /// A `ECHClientHello` with type [EchClientHelloType::ClientHelloOuter]. Outer(EncryptedClientHelloOuter), /// An empty `ECHClientHello` with type [EchClientHelloType::ClientHelloInner]. @@ -3077,7 +3049,7 @@ impl Codec<'_> for EncryptedClientHello { /// /// [draft-ietf-tls-esni Section 5]: #[derive(Clone, Debug)] -pub struct EncryptedClientHelloOuter { +pub(crate) struct EncryptedClientHelloOuter { /// The cipher suite used to encrypt ClientHelloInner. Must match a value from /// ECHConfigContents.cipher_suites list. pub cipher_suite: HpkeSymmetricCipherSuite, @@ -3087,7 +3059,7 @@ pub struct EncryptedClientHelloOuter { /// 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, + pub payload: PayloadU16, } impl Codec<'_> for EncryptedClientHelloOuter { @@ -3113,7 +3085,7 @@ impl Codec<'_> for EncryptedClientHelloOuter { /// /// [draft-ietf-tls-esni Section 5]: #[derive(Clone, Debug)] -pub struct ServerEncryptedClientHello { +pub(crate) struct ServerEncryptedClientHello { pub(crate) retry_configs: Vec, } @@ -3132,12 +3104,11 @@ impl Codec<'_> for ServerEncryptedClientHello { /// 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 }, @@ -3155,6 +3126,38 @@ fn has_duplicates, E: Into, T: Eq + Ord>(iter: I) - false } +struct DuplicateExtensionChecker(BTreeSet); + +impl DuplicateExtensionChecker { + fn new() -> Self { + Self(BTreeSet::new()) + } + + 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)), + } + } +} + +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 +} + #[cfg(test)] mod tests { use super::*; @@ -3195,7 +3198,7 @@ mod tests { key_config: HpkeKeyConfig { config_id: 0, kem_id: HpkeKem::DHKEM_P256_HKDF_SHA256, - public_key: PayloadU16(b"xxx".into()), + public_key: PayloadU16::new(b"xxx".into()), symmetric_cipher_suites: vec![HpkeSymmetricCipherSuite { kdf_id: HpkeKdf::HKDF_SHA256, aead_id: HpkeAead::AES_128_GCM, diff --git a/rustls/src/msgs/handshake_test.rs b/rustls/src/msgs/handshake_test.rs index c6b2861eb5f..53cf88fa5ac 100644 --- a/rustls/src/msgs/handshake_test.rs +++ b/rustls/src/msgs/handshake_test.rs @@ -1,31 +1,33 @@ -use alloc::sync::Arc; use std::prelude::v1::*; 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::base::{Payload, PayloadU8, PayloadU16, PayloadU24}; +use super::codec::{Codec, Reader, put_u16}; use super::enums::{ - CertificateType, ClientCertificateType, Compression, ECCurveType, ECPointFormat, ExtensionType, - KeyUpdateRequest, NamedGroup, PSKKeyExchangeMode, ServerNameType, + ClientCertificateType, Compression, ECCurveType, ExtensionType, KeyUpdateRequest, NamedGroup, }; 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, CertificateStatusRequest, ClientExtensions, ClientHelloPayload, + ClientSessionTicket, CompressedCertificatePayload, DistinguishedName, EcParameters, + EncryptedClientHello, HandshakeMessagePayload, HandshakePayload, HelloRetryRequest, + HelloRetryRequestExtensions, KeyShareEntry, NewSessionTicketExtensions, + NewSessionTicketPayload, NewSessionTicketPayloadTls13, PresharedKeyBinder, + PresharedKeyIdentity, PresharedKeyOffer, ProtocolName, PskKeyExchangeModes, Random, + ServerDhParams, ServerEcdhParams, ServerEncryptedClientHello, ServerExtensions, + ServerHelloPayload, ServerKeyExchange, ServerKeyExchangeParams, ServerKeyExchangePayload, + ServerNamePayload, SessionId, SingleProtocolName, SupportedEcPointFormats, + SupportedProtocolVersions, }; use crate::enums::{ - CertificateCompressionAlgorithm, CipherSuite, HandshakeType, ProtocolVersion, SignatureScheme, + CertificateCompressionAlgorithm, CertificateType, CipherSuite, HandshakeType, ProtocolVersion, + SignatureScheme, }; use crate::error::InvalidMessage; +use crate::sync::Arc; use crate::verify::DigitallySignedStruct; #[test] @@ -40,7 +42,7 @@ fn reads_random() { let bytes = [0x01; 32]; let mut rd = Reader::init(&bytes); let rnd = Random::read(&mut rd).unwrap(); - println!("{:?}", rnd); + println!("{rnd:?}"); assert!(!rd.any_left()); } @@ -79,7 +81,7 @@ fn accepts_short_session_id() { let bytes = [1; 2]; let mut rd = Reader::init(&bytes); let sess = SessionId::read(&mut rd).unwrap(); - println!("{:?}", sess); + println!("{sess:?}"); #[cfg(feature = "tls12")] assert!(!sess.is_empty()); @@ -92,7 +94,7 @@ fn accepts_empty_session_id() { let bytes = [0; 1]; let mut rd = Reader::init(&bytes); let sess = SessionId::read(&mut rd).unwrap(); - println!("{:?}", sess); + println!("{sess:?}"); #[cfg(feature = "tls12")] assert!(sess.is_empty()); @@ -110,172 +112,140 @@ fn debug_session_id() { 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, 0x09, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 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::init(&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::init(&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::init(&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::init(&bytes)).is_err()); - assert!(req.has_duplicate_names_for_type()); + let bytes = [0, 5, 0, 3, 0, 0, 0]; + assert!(ServerNamePayload::read(&mut Reader::init(&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::init(&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::init(&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 bytes = [0, 1, 0x99, 0x11, 0x22, 0x33, 0x44]; let psk_id = PresharedKeyIdentity::read(&mut Reader::init(&bytes)).unwrap(); - println!("{:?}", psk_id); + 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); + println!("{psk_id:?}"); assert_eq!(psk_id.identity.0, vec![0x1, 0x2, 0x3, 0x4, 0x5]); assert_eq!(psk_id.obfuscated_ticket_age, 0x11223344); assert_eq!(psk_id.get_encoding(), bytes.to_vec()); @@ -287,7 +257,7 @@ fn can_round_trip_psk_offer() { 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); + println!("{psko:?}"); assert_eq!(psko.identities.len(), 1); assert_eq!(psko.identities[0].identity.0, vec![0x99]); @@ -299,71 +269,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::init(&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::init(&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 +307,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(); + + assert_eq!(src.collect_used(), vec![ExtensionType::EarlyData]); + assert_eq!(target.collect_used(), vec![]); + + 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(); - chp.extensions.drain(1..); - assert!(!chp.has_duplicate_extension()); + // duplicate known + assert_eq!( + ClientExtensions::read_bytes(b"\x00\x08\x00\x2a\x00\x00\x00\x2a\x00\x00").unwrap_err(), + InvalidMessage::DuplicateExtension(0x002a) + ); - chp.extensions = vec![]; - assert!(!chp.has_duplicate_extension()); + // 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 +480,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 +495,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); + let enc = chp.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!(ClientExtension::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!(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); + let mut enc = hrr.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!(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; - } - - // "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 +563,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 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 +591,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,9 +601,9 @@ 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, _) @@ -799,11 +613,13 @@ fn can_detect_truncation_of_all_tls12_handshake_payloads() { _ => {} }; - assert!(HandshakeMessagePayload::read_version( - &mut Reader::init(&enc), - ProtocolVersion::TLSv1_2 - ) - .is_err()); + assert!( + HandshakeMessagePayload::read_version( + &mut Reader::init(&enc), + ProtocolVersion::TLSv1_2 + ) + .is_err() + ); assert!(HandshakeMessagePayload::read_bytes(&enc).is_err()); } } @@ -811,8 +627,8 @@ 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); @@ -821,8 +637,8 @@ fn can_round_trip_all_tls13_handshake_payloads() { assert!(!rd.any_left()); assert_eq!(hm.get_encoding(), other.get_encoding()); - println!("{:?}", hm); - println!("{:?}", other); + println!("{hm:?}"); + println!("{other:?}"); } } @@ -841,7 +657,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,9 +667,9 @@ 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, _) @@ -863,11 +679,13 @@ fn can_detect_truncation_of_all_tls13_handshake_payloads() { _ => {} }; - assert!(HandshakeMessagePayload::read_version( - &mut Reader::init(&enc), - ProtocolVersion::TLSv1_3 - ) - .is_err()); + assert!( + HandshakeMessagePayload::read_version( + &mut Reader::init(&enc), + ProtocolVersion::TLSv1_3 + ) + .is_err() + ); } } } @@ -880,11 +698,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()); } @@ -923,7 +738,7 @@ 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 hm = HandshakeMessagePayload::read(&mut r).unwrap(); - println!("msg: {:?}", hm); + println!("msg: {hm:?}"); } #[test] @@ -940,15 +755,13 @@ fn sample_hello_retry_request() -> 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]), - }), - ], + extensions: HelloRetryRequestExtensions { + key_share: Some(NamedGroup::X25519), + cookie: Some(PayloadU16::new(vec![0])), + supported_versions: Some(ProtocolVersion::TLSv1_2), + encrypted_client_hello: Some(Payload::new(vec![1, 2, 3])), + order: None, + }, } } @@ -959,18 +772,26 @@ fn sample_client_hello_payload() -> ClientHelloPayload { session_id: SessionId::empty(), cipher_suites: vec![CipherSuite::TLS_NULL_WITH_NULL_NULL], 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(PayloadU16::new(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![ProtocolName::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 +801,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() + }), } } @@ -1006,217 +822,146 @@ fn sample_server_hello_payload() -> ServerHelloPayload { session_id: SessionId::empty(), cipher_suite: CipherSuite::TLS_NULL_WITH_NULL_NULL, 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(PayloadU8::new(vec![0])), + selected_protocol: Some(SingleProtocolName::new(ProtocolName::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])), + transport_parameters_draft: None, + 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::Unknown(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::Unknown(99), + Payload::Borrowed(&[1, 2, 3]), + ))), ] } fn sample_certificate_payload_tls13() -> CertificatePayloadTls13<'static> { CertificatePayloadTls13 { - context: PayloadU8(vec![1, 2, 3]), + context: PayloadU8::new(vec![1, 2, 3]), entries: vec![CertificateEntry { cert: CertificateDer::from(vec![3, 4, 5]), - exts: vec![ - CertificateExtension::CertificateStatus(CertificateStatus { + extensions: CertificateExtensions { + status: Some(CertificateStatus { ocsp_response: PayloadU24(Payload::new(vec![1, 2, 3])), }), - CertificateExtension::Unknown(UnknownExtension { - typ: ExtensionType::Unknown(12345), - payload: Payload::Borrowed(&[1, 2, 3]), - }), - ], + }, }], } } @@ -1236,7 +981,7 @@ fn sample_ecdhe_server_key_exchange_payload() -> ServerKeyExchangePayload { curve_type: ECCurveType::NamedCurve, named_group: NamedGroup::X25519, }, - public: PayloadU8(vec![1, 2, 3]), + public: PayloadU8::new(vec![1, 2, 3]), }), dss: DigitallySignedStruct::new(SignatureScheme::RSA_PSS_SHA256, vec![1, 2, 3]), }) @@ -1245,9 +990,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: PayloadU16::new(vec![1, 2, 3]), + dh_g: PayloadU16::new(vec![2]), + dh_Ys: PayloadU16::new(vec![1, 2]), }), dss: DigitallySignedStruct::new(SignatureScheme::RSA_PSS_SHA256, vec![1, 2, 3]), }) @@ -1267,22 +1012,19 @@ 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: PayloadU8::new(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])), + ticket: Arc::new(PayloadU16::new(vec![1, 2, 3])), } } @@ -1290,16 +1032,15 @@ fn sample_new_session_ticket_payload_tls13() -> NewSessionTicketPayloadTls13 { NewSessionTicketPayloadTls13 { lifetime: 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: PayloadU8::new(vec![1, 2, 3]), + ticket: Arc::new(PayloadU16::new(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 } diff --git a/rustls/src/msgs/macros.rs b/rustls/src/msgs/macros.rs index 5fe49ae58eb..d6a18910c72 100644 --- a/rustls/src/msgs/macros.rs +++ b/rustls/src/msgs/macros.rs @@ -3,20 +3,36 @@ macro_rules! enum_builder { ( $(#[doc = $comment:literal])* #[repr($uint:ty)] + $(#[$metas:meta])* $enum_vis:vis enum $enum_name:ident { - $( $enum_var:ident => $enum_val:literal),* $(,)? + $( + $(#[$enum_metas:meta])* + $enum_var:ident => $enum_val:literal),* $(,)? $( !Debug: - $( $enum_var_nd:ident => $enum_val_nd:literal),* $(,)? + $( + $(#[$enum_metas_nd:meta])* + $enum_var_nd:ident => $enum_val_nd:literal + ),* $(,)? )? } ) => { $(#[doc = $comment])* + $(#[$metas])* #[non_exhaustive] #[derive(PartialEq, Eq, Clone, Copy)] $enum_vis enum $enum_name { - $( $enum_var),* - $(, $($enum_var_nd),* )? + $( + $(#[$enum_metas])* + $enum_var + ),* + $( + , + $( + $(#[$enum_metas_nd])* + $enum_var_nd + ),* + )? ,Unknown($uint) } @@ -83,3 +99,231 @@ macro_rules! enum_builder { } }; } + +/// 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<'a> core::fmt::Debug for $struct_name$(<$struct_lt>)* { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + 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() + } + } + } +} diff --git a/rustls/src/msgs/message/mod.rs b/rustls/src/msgs/message/mod.rs index aeb35c850c1..d5cecc94892 100644 --- a/rustls/src/msgs/message/mod.rs +++ b/rustls/src/msgs/message/mod.rs @@ -1,5 +1,5 @@ use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion}; -use crate::error::{Error, InvalidMessage}; +use crate::error::InvalidMessage; use crate::msgs::alert::AlertMessagePayload; use crate::msgs::base::Payload; use crate::msgs::ccs::ChangeCipherSpecPayload; @@ -162,7 +162,7 @@ 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 + parsed.0.handshake_type() == hstyp } else { false } @@ -181,20 +181,18 @@ impl Message<'_> { pub fn build_key_update_notify() -> Self { Self { version: ProtocolVersion::TLSv1_3, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::KeyUpdate, - payload: HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateNotRequested), - }), + payload: MessagePayload::handshake(HandshakeMessagePayload( + 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), - }), + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateRequested), + )), } } @@ -206,10 +204,17 @@ impl Message<'_> { payload: payload.into_owned(), } } + + #[cfg(test)] + pub(crate) fn into_wire_bytes(self) -> Vec { + PlainMessage::from(self) + .into_unencrypted_opaque() + .encode() + } } impl TryFrom for Message<'static> { - type Error = Error; + type Error = InvalidMessage; fn try_from(plain: PlainMessage) -> Result { Ok(Self { @@ -225,7 +230,7 @@ impl TryFrom for Message<'static> { /// 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; + type Error = InvalidMessage; fn try_from(plain: InboundPlainMessage<'a>) -> Result { Ok(Self { diff --git a/rustls/src/msgs/message/outbound.rs b/rustls/src/msgs/message/outbound.rs index f6fdeb86235..810c066cb5c 100644 --- a/rustls/src/msgs/message/outbound.rs +++ b/rustls/src/msgs/message/outbound.rs @@ -1,6 +1,6 @@ use alloc::vec::Vec; -use super::{MessageError, PlainMessage, HEADER_SIZE, MAX_PAYLOAD}; +use super::{HEADER_SIZE, MAX_PAYLOAD, MessageError, PlainMessage}; use crate::enums::{ContentType, ProtocolVersion}; use crate::msgs::base::Payload; use crate::msgs::codec::{Codec, Reader}; @@ -282,8 +282,8 @@ pub(crate) fn read_opaque_message_header( 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 => { + match &version { + ProtocolVersion::Unknown(v) if (v & 0xff00) != 0x0300 => { return Err(MessageError::UnknownProtocolVersion); } _ => {} @@ -318,7 +318,7 @@ mod tests { let borrowed_payload = OutboundChunks::Single(owner); let (before, after) = borrowed_payload.split_at(6); - println!("before:{:?}\nafter:{:?}", before, after); + println!("before:{before:?}\nafter:{after:?}"); assert_eq!(before.to_vec(), &[0, 1, 2, 3, 4, 5]); assert_eq!(after.to_vec(), &[6, 7]); } @@ -329,17 +329,17 @@ mod tests { let borrowed_payload = OutboundChunks::new(&owner); let (before, after) = borrowed_payload.split_at(3); - println!("before:{:?}\nafter:{:?}", before, after); + 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:{:?}\nafter:{:?}", before, after); + 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:{:?}\nafter:{:?}", before, after); + 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]); } @@ -350,19 +350,19 @@ mod tests { let single_payload = OutboundChunks::Single(owner[0]); let (before, after) = single_payload.split_at(17); - println!("before:{:?}\nafter:{:?}", before, after); + println!("before:{before:?}\nafter:{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); + 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 = OutboundChunks::new_empty(); let (before, after) = empty_payload.split_at(17); - println!("before:{:?}\nafter:{:?}", before, after); + println!("before:{before:?}\nafter:{after:?}"); assert!(before.is_empty()); assert!(after.is_empty()); } @@ -393,7 +393,7 @@ mod tests { let payload = OutboundChunks::new(&slices); assert_eq!(payload.to_vec(), owner); - println!("{:#?}", payload); + println!("{payload:#?}"); for start in 0..128 { for end in start..128 { diff --git a/rustls/src/msgs/message_test.rs b/rustls/src/msgs/message_test.rs index 53fca53e190..965a3572e00 100644 --- a/rustls/src/msgs/message_test.rs +++ b/rustls/src/msgs/message_test.rs @@ -8,7 +8,7 @@ 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}; +use crate::msgs::base::{MaybeEmpty, NonEmpty, PayloadU8, PayloadU16, PayloadU24}; #[test] fn test_read_fuzz_corpus() { @@ -32,7 +32,7 @@ fn test_read_fuzz_corpus() { let msg = OutboundOpaqueMessage::read(&mut rd) .unwrap() .into_plain_message(); - println!("{:?}", msg); + println!("{msg:?}"); let Ok(msg) = Message::try_from(msg) else { continue; @@ -70,7 +70,7 @@ fn can_read_safari_client_hello_with_ip_address_in_sni_extension() { \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); + println!("m = {m:?}"); Message::try_from(m.into_plain_message()).unwrap(); } @@ -91,19 +91,34 @@ fn construct_all_types() { ]; for &bytes in samples.iter() { let m = OutboundOpaqueMessage::read(&mut Reader::init(bytes)).unwrap(); - println!("m = {:?}", m); + println!("m = {m:?}"); let m = Message::try_from(m.into_plain_message()); - println!("m' = {:?}", m); + 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!("{:?}", PayloadU8::::new(vec![1, 2, 3, 4])) + ); + assert_eq!( + "01020304", + format!("{:?}", PayloadU16::::new(vec![1, 2, 3, 4])) + ); assert_eq!( "01020304", format!("{:?}", PayloadU24(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] + ); +} diff --git a/rustls/src/msgs/persist.rs b/rustls/src/msgs/persist.rs index fb16d2268b3..8231c8ff61c 100644 --- a/rustls/src/msgs/persist.rs +++ b/rustls/src/msgs/persist.rs @@ -1,20 +1,22 @@ -use alloc::sync::Arc; use alloc::vec::Vec; use core::cmp; use pki_types::{DnsName, UnixTime}; use zeroize::Zeroizing; +use crate::client::ResolvesClientCert; use crate::enums::{CipherSuite, ProtocolVersion}; use crate::error::InvalidMessage; -use crate::msgs::base::{PayloadU16, PayloadU8}; +use crate::msgs::base::{MaybeEmpty, PayloadU8, PayloadU16}; use crate::msgs::codec::{Codec, Reader}; -use crate::msgs::handshake::CertificateChain; #[cfg(feature = "tls12")] use crate::msgs::handshake::SessionId; +use crate::msgs::handshake::{CertificateChain, ProtocolName}; +use crate::sync::{Arc, Weak}; #[cfg(feature = "tls12")] use crate::tls12::Tls12CipherSuite; use crate::tls13::Tls13CipherSuite; +use crate::verify::ServerCertVerifier; pub(crate) struct Retrieved { pub(crate) value: T, @@ -82,6 +84,8 @@ impl Tls13ClientSessionValue { ticket: Arc, secret: &[u8], server_cert_chain: CertificateChain<'static>, + server_cert_verifier: &Arc, + client_creds: &Arc, time_now: UnixTime, lifetime_secs: u32, age_add: u32, @@ -97,8 +101,10 @@ impl Tls13ClientSessionValue { time_now, lifetime_secs, server_cert_chain, + server_cert_verifier, + client_creds, ), - quic_params: PayloadU16(Vec::new()), + quic_params: PayloadU16::new(Vec::new()), } } @@ -123,7 +129,7 @@ impl Tls13ClientSessionValue { } pub fn set_quic_params(&mut self, quic_params: &[u8]) { - self.quic_params = PayloadU16(quic_params.to_vec()); + self.quic_params = PayloadU16::new(quic_params.to_vec()); } pub fn quic_params(&self) -> Vec { @@ -160,6 +166,8 @@ impl Tls12ClientSessionValue { ticket: Arc, master_secret: &[u8], server_cert_chain: CertificateChain<'static>, + server_cert_verifier: &Arc, + client_creds: &Arc, time_now: UnixTime, lifetime_secs: u32, extended_ms: bool, @@ -174,12 +182,14 @@ impl Tls12ClientSessionValue { time_now, lifetime_secs, server_cert_chain, + server_cert_verifier, + client_creds, ), } } pub(crate) fn ticket(&mut self) -> Arc { - Arc::clone(&self.common.ticket) + self.common.ticket.clone() } pub(crate) fn extended_ms(&self) -> bool { @@ -213,6 +223,8 @@ pub struct ClientSessionCommon { epoch: u64, lifetime_secs: u32, server_cert_chain: Arc>, + server_cert_verifier: Weak, + client_creds: Weak, } impl ClientSessionCommon { @@ -222,13 +234,43 @@ impl ClientSessionCommon { time_now: UnixTime, lifetime_secs: u32, server_cert_chain: CertificateChain<'static>, + server_cert_verifier: &Arc, + client_creds: &Arc, ) -> Self { Self { ticket, - secret: Zeroizing::new(PayloadU8(secret.to_vec())), + secret: Zeroizing::new(PayloadU8::new(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), + server_cert_verifier: Arc::downgrade(server_cert_verifier), + client_creds: Arc::downgrade(client_creds), + } + } + + pub(crate) fn compatible_config( + &self, + server_cert_verifier: &Arc, + client_creds: &Arc, + ) -> bool { + let same_verifier = Weak::ptr_eq( + &Arc::downgrade(server_cert_verifier), + &self.server_cert_verifier, + ); + let same_creds = Weak::ptr_eq(&Arc::downgrade(client_creds), &self.client_creds); + + match (same_verifier, same_creds) { + (true, true) => true, + (false, _) => { + crate::log::trace!("resumption not allowed between different ServerCertVerifiers"); + false + } + (_, _) => { + crate::log::trace!( + "resumption not allowed between different ResolvesClientCert values" + ); + false + } } } @@ -271,10 +313,10 @@ pub struct ServerSessionValue { impl Codec<'_> for ServerSessionValue { fn encode(&self, bytes: &mut Vec) { - if let Some(ref sni) = self.sni { + if let Some(sni) = &self.sni { 1u8.encode(bytes); let sni_bytes: &str = sni.as_ref(); - PayloadU8::new(Vec::from(sni_bytes)).encode(bytes); + PayloadU8::::encode_slice(sni_bytes.as_bytes(), bytes); } else { 0u8.encode(bytes); } @@ -282,13 +324,13 @@ impl Codec<'_> for ServerSessionValue { 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 { + if let Some(chain) = &self.client_cert_chain { 1u8.encode(bytes); chain.encode(bytes); } else { 0u8.encode(bytes); } - if let Some(ref alpn) = self.alpn { + if let Some(alpn) = &self.alpn { 1u8.encode(bytes); alpn.encode(bytes); } else { @@ -303,7 +345,7 @@ impl Codec<'_> for ServerSessionValue { 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 = 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), @@ -357,7 +399,7 @@ impl ServerSessionValue { cs: CipherSuite, ms: &[u8], client_cert_chain: Option>, - alpn: Option>, + alpn: Option, application_data: Vec, creation_time: UnixTime, age_obfuscation_offset: u32, @@ -369,7 +411,7 @@ impl ServerSessionValue { master_secret: Zeroizing::new(PayloadU8::new(ms.to_vec())), extended_ms: false, client_cert_chain, - alpn: alpn.map(PayloadU8::new), + alpn: alpn.map(|p| PayloadU8::new(p.as_ref().to_vec())), application_data: PayloadU16::new(application_data), creation_time_sec: creation_time.as_secs(), age_obfuscation_offset, @@ -423,7 +465,7 @@ mod tests { UnixTime::now(), 0x12345678, ); - println!("{:?}", ssv); + println!("{ssv:?}"); } #[test] diff --git a/rustls/src/quic.rs b/rustls/src/quic.rs index 5c8c03cac25..d1b732663aa 100644 --- a/rustls/src/quic.rs +++ b/rustls/src/quic.rs @@ -10,15 +10,13 @@ use crate::crypto::cipher::{AeadKey, Iv}; use crate::crypto::tls13::{Hkdf, HkdfExpander, OkmBlock}; use crate::enums::AlertDescription; use crate::error::Error; +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}; @@ -27,14 +25,18 @@ mod connection { use super::{DirectionalKeys, KeyChange, Version}; use crate::client::{ClientConfig, ClientConnectionData}; - use crate::common_state::{CommonState, Protocol, DEFAULT_BUFFER_LIMIT}; + use crate::common_state::{CommonState, DEFAULT_BUFFER_LIMIT, Protocol}; use crate::conn::{ConnectionCore, SideData}; use crate::enums::{AlertDescription, ContentType, ProtocolVersion}; use crate::error::Error; + use crate::msgs::base::Payload; use crate::msgs::deframer::buffers::{DeframerVecBuffer, Locator}; - use crate::msgs::handshake::{ClientExtension, ServerExtension}; + use crate::msgs::handshake::{ + ClientExtensionsInput, ServerExtensionsInput, TransportParameters, + }; use crate::msgs::message::InboundPlainMessage; use crate::server::{ServerConfig, ServerConnectionData}; + use crate::sync::Arc; use crate::vecbuf::ChunkVecBuffer; /// A QUIC client or server connection. @@ -163,6 +165,23 @@ mod connection { quic_version: Version, name: ServerName<'static>, params: Vec, + ) -> Result { + Self::new_with_alpn( + config.clone(), + quic_version, + name, + params, + config.alpn_protocols.clone(), + ) + } + + /// Make a new QUIC ClientConnection with custom ALPN protocols. + pub fn new_with_alpn( + config: Arc, + quic_version: Version, + name: ServerName<'static>, + params: Vec, + alpn_protocols: Vec>, ) -> Result { if !config.supports_version(ProtocolVersion::TLSv1_3) { return Err(Error::General( @@ -176,12 +195,16 @@ mod connection { )); } - let ext = match quic_version { - Version::V1Draft => ClientExtension::TransportParametersDraft(params), - Version::V1 | Version::V2 => ClientExtension::TransportParameters(params), + let exts = ClientExtensionsInput { + transport_parameters: Some(match quic_version { + Version::V1Draft => TransportParameters::QuicDraft(Payload::new(params)), + Version::V1 | Version::V2 => TransportParameters::Quic(Payload::new(params)), + }), + + ..ClientExtensionsInput::from_alpn(alpn_protocols) }; - let mut inner = ConnectionCore::for_client(config, name, vec![ext], Protocol::Quic)?; + let mut inner = ConnectionCore::for_client(config, name, exts, Protocol::Quic)?; inner.common_state.quic.version = quic_version; Ok(Self { inner: inner.into(), @@ -196,6 +219,11 @@ mod connection { pub fn is_early_data_accepted(&self) -> bool { self.inner.core.is_early_data_accepted() } + + /// Returns the number of TLS1.3 tickets that have been received. + pub fn tls13_tickets_received(&self) -> u32 { + self.inner.tls13_tickets_received + } } impl Deref for ClientConnection { @@ -258,12 +286,14 @@ mod connection { )); } - let ext = match quic_version { - Version::V1Draft => ServerExtension::TransportParametersDraft(params), - Version::V1 | Version::V2 => ServerExtension::TransportParameters(params), + let exts = ServerExtensionsInput { + transport_parameters: Some(match quic_version { + Version::V1Draft => TransportParameters::QuicDraft(Payload::new(params)), + Version::V1 | Version::V2 => TransportParameters::Quic(Payload::new(params)), + }), }; - let mut core = ConnectionCore::for_server(config, vec![ext])?; + let mut core = ConnectionCore::for_server(config, exts)?; core.common_state.protocol = Protocol::Quic; core.common_state.quic.version = quic_version; Ok(Self { inner: core.into() }) @@ -701,6 +731,25 @@ pub trait PacketKey: Send + Sync { payload: &mut [u8], ) -> Result; + /// Encrypts a multipath QUIC packet + /// + /// Takes a `path_id` and `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. + /// + /// Fails if and only if the payload is longer than allowed by the cipher suite's AEAD algorithm. + /// + /// See . + fn encrypt_in_place_for_path( + &self, + _path_id: u32, + _packet_number: u64, + _header: &[u8], + _payload: &mut [u8], + ) -> Result { + Err(Error::EncryptError) + } + /// Decrypt a QUIC packet /// /// Takes the packet `header`, which is used as the additional authenticated data, and the @@ -715,6 +764,26 @@ pub trait PacketKey: Send + Sync { payload: &'a mut [u8], ) -> Result<&'a [u8], Error>; + /// Decrypt a multipath QUIC packet + /// + /// Takes a `path_id` and `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. + /// + /// If the return value is `Ok`, the decrypted payload can be found in `payload`, up to the + /// length found in the return value. + /// + /// See . + fn decrypt_in_place_for_path<'a>( + &self, + _path_id: u32, + _packet_number: u64, + _header: &[u8], + _payload: &'a mut [u8], + ) -> Result<&'a [u8], Error> { + Err(Error::DecryptError) + } + /// Tag length for the underlying AEAD algorithm fn tag_len(&self) -> usize; @@ -907,8 +976,7 @@ pub enum KeyChange { /// /// Governs version-specific behavior in the TLS layer #[non_exhaustive] -#[derive(Clone, Copy, Debug)] -#[derive(Default)] +#[derive(Clone, Copy, Debug, Default)] pub enum Version { /// Draft versions 29, 30, 31 and 32 V1Draft, @@ -972,7 +1040,6 @@ impl Version { } } - #[cfg(test)] mod tests { use std::prelude::v1::*; diff --git a/rustls/src/server/builder.rs b/rustls/src/server/builder.rs index 196dd3fcd06..daf6e094ecf 100644 --- a/rustls/src/server/builder.rs +++ b/rustls/src/server/builder.rs @@ -1,15 +1,15 @@ -use alloc::sync::Arc; use alloc::vec::Vec; use core::marker::PhantomData; use pki_types::{CertificateDer, PrivateKeyDer}; +use super::{ResolvesServerCert, ServerConfig, handy}; use crate::builder::{ConfigBuilder, WantsVerifier}; use crate::error::Error; -use crate::server::{handy, ResolvesServerCert, ServerConfig}; -use crate::sign::CertifiedKey; +use crate::sign::{CertifiedKey, SingleCertAndKey}; +use crate::sync::Arc; use crate::verify::{ClientCertVerifier, NoClientAuth}; -use crate::{compress, versions, InconsistentKeys, NoKeyLog}; +use crate::{NoKeyLog, compress, versions}; impl ConfigBuilder { /// Choose how to verify client certificates. @@ -67,20 +67,8 @@ impl ConfigBuilder { 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))) + let certified_key = CertifiedKey::from_der(cert_chain, key_der, self.crypto_provider())?; + Ok(self.with_cert_resolver(Arc::new(SingleCertAndKey::from(certified_key)))) } /// Sets a single certificate chain, matching private key and optional OCSP @@ -102,24 +90,19 @@ impl ConfigBuilder { 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 mut certified_key = + CertifiedKey::from_der(cert_chain, key_der, self.crypto_provider())?; + if !ocsp.is_empty() { + certified_key.ocsp = Some(ocsp); } - - let resolver = handy::AlwaysResolvesChain::new_with_extras(certified_key, ocsp); - Ok(self.with_cert_resolver(Arc::new(resolver))) + Ok(self.with_cert_resolver(Arc::new(SingleCertAndKey::from(certified_key)))) } /// Sets a custom [`ResolvesServerCert`]. pub fn with_cert_resolver(self, cert_resolver: Arc) -> ServerConfig { + #[cfg(feature = "tls12")] + let require_ems = self.provider.fips(); + ServerConfig { provider: self.provider, verifier: self.state.verifier, @@ -139,7 +122,7 @@ impl ConfigBuilder { send_half_rtt_data: false, send_tls13_tickets: 2, #[cfg(feature = "tls12")] - require_ems: cfg!(feature = "fips"), + require_ems, time_provider: self.time_provider, cert_compressors: compress::default_cert_compressors().to_vec(), cert_compression_cache: Arc::new(compress::CompressionCache::default()), diff --git a/rustls/src/server/handy.rs b/rustls/src/server/handy.rs index ba2570abfdd..ea3ec5d9db0 100644 --- a/rustls/src/server/handy.rs +++ b/rustls/src/server/handy.rs @@ -1,8 +1,8 @@ -use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt::Debug; use crate::server::ClientHello; +use crate::sync::Arc; use crate::{server, sign}; /// Something which never stores sessions. @@ -26,11 +26,11 @@ 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 crate::lock::Mutex; + use crate::sync::Arc; use crate::{limited_cache, server}; /// An implementer of `StoresServerSessions` that stores everything @@ -166,39 +166,6 @@ impl server::ProducesTickets for NeverProducesTickets { } } -/// 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. /// @@ -215,7 +182,7 @@ impl AlwaysResolvesServerRawPublicKeys { impl server::ResolvesServerCert for AlwaysResolvesServerRawPublicKeys { fn resolve(&self, _client_hello: ClientHello<'_>) -> Option> { - Some(Arc::clone(&self.0)) + Some(self.0.clone()) } fn only_raw_public_keys(&self) -> bool { @@ -226,7 +193,6 @@ impl server::ResolvesServerCert for AlwaysResolvesServerRawPublicKeys { #[cfg(any(feature = "std", feature = "hashbrown"))] mod sni_resolver { use alloc::string::{String, ToString}; - use alloc::sync::Arc; use core::fmt::Debug; use pki_types::{DnsName, ServerName}; @@ -234,7 +200,8 @@ mod sni_resolver { use crate::error::Error; use crate::hash_map::HashMap; use crate::server::ClientHello; - use crate::webpki::{verify_server_name, ParsedCertificate}; + use crate::sync::Arc; + use crate::webpki::{ParsedCertificate, verify_server_name}; use crate::{server, sign}; /// Something that resolves do different cert chains/keys based @@ -305,17 +272,20 @@ mod sni_resolver { #[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()); + 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_none() + ); } #[test] @@ -324,17 +294,20 @@ mod sni_resolver { 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(name), + signature_schemes: &[], + alpn: None, + server_cert_types: None, + client_cert_types: None, + cipher_suites: &[], + certificate_authorities: None, + named_groups: None, + }) + .is_none() + ); } } } diff --git a/rustls/src/server/hs.rs b/rustls/src/server/hs.rs index 2d2a45cc575..d98336e461b 100644 --- a/rustls/src/server/hs.rs +++ b/rustls/src/server/hs.rs @@ -1,6 +1,5 @@ use alloc::borrow::ToOwned; use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec::Vec; use pki_types::DnsName; @@ -8,30 +7,30 @@ 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::common_state::{KxState, Protocol, State}; use crate::conn::ConnectionRandoms; use crate::crypto::SupportedKxGroup; use crate::enums::{ - AlertDescription, CipherSuite, HandshakeType, ProtocolVersion, SignatureAlgorithm, - SignatureScheme, + AlertDescription, CertificateType, CipherSuite, HandshakeType, ProtocolVersion, + SignatureAlgorithm, SignatureScheme, }; use crate::error::{Error, PeerIncompatible, PeerMisbehaved}; use crate::hash_hs::{HandshakeHash, HandshakeHashBuffer}; use crate::log::{debug, trace}; -use crate::msgs::enums::{CertificateType, Compression, ExtensionType, NamedGroup}; +use crate::msgs::enums::{Compression, ExtensionType, NamedGroup}; #[cfg(feature = "tls12")] use crate::msgs::handshake::SessionId; use crate::msgs::handshake::{ - ClientHelloPayload, ConvertProtocolNameList, ConvertServerNameList, HandshakePayload, - KeyExchangeAlgorithm, Random, ServerExtension, + ClientHelloPayload, HandshakePayload, KeyExchangeAlgorithm, ProtocolName, Random, + ServerExtensions, ServerExtensionsInput, ServerNamePayload, 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}; +use crate::server::{ClientHello, ServerConfig, tls13}; +use crate::sync::Arc; +use crate::{SupportedCipherSuite, suites}; pub(super) type NextState<'a> = Box + 'a>; pub(super) type NextStateOrError<'a> = Result, Error>; @@ -50,6 +49,9 @@ pub(super) fn can_resume( // 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." resumedata.cipher_suite == suite.suite() && (resumedata.extended_ms == using_ems || (resumedata.extended_ms && !using_ems)) && &resumedata.sni == sni @@ -58,14 +60,31 @@ pub(super) fn can_resume( #[derive(Default)] pub(super) struct ExtensionProcessing { // extensions to reply with - pub(super) exts: Vec, + pub(super) extensions: Box>, #[cfg(feature = "tls12")] pub(super) send_ticket: bool, } impl ExtensionProcessing { - pub(super) fn new() -> Self { - Default::default() + pub(super) fn new(extra_exts: ServerExtensionsInput<'static>) -> Self { + let ServerExtensionsInput { + transport_parameters, + } = extra_exts; + + let mut extensions = Box::new(ServerExtensions::default()); + match transport_parameters { + Some(TransportParameters::Quic(v)) => extensions.transport_parameters = Some(v), + Some(TransportParameters::QuicDraft(v)) => { + extensions.transport_parameters_draft = Some(v) + } + None => {} + } + + Self { + extensions, + #[cfg(feature = "tls12")] + send_ticket: false, + } } pub(super) fn process_common( @@ -75,29 +94,23 @@ impl ExtensionProcessing { ocsp_response: &mut Option<&[u8]>, 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(); - - if their_protocols - .iter() - .any(|protocol| protocol.is_empty()) - { - return Err(PeerMisbehaved::OfferedEmptyApplicationProtocol.into()); - } - + if let Some(their_protocols) = &hello.protocols { 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])); + .find(|ours| { + their_protocols + .iter() + .any(|theirs| theirs.as_ref() == ours.as_slice()) + }) + .map(|bytes| ProtocolName::from(bytes.clone())); + if let Some(selected_protocol) = &cx.common.alpn_protocol { + debug!("Chosen ALPN protocol {selected_protocol:?}"); + + self.extensions.selected_protocol = + Some(SingleProtocolName::new(selected_protocol.clone())); } else if !our_protocols.is_empty() { return Err(cx.common.send_fatal_alert( AlertDescription::NoApplicationProtocol, @@ -115,7 +128,7 @@ impl ExtensionProcessing { // 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()) + && (!our_protocols.is_empty() || hello.protocols.is_some()) { return Err(cx.common.send_fatal_alert( AlertDescription::NoApplicationProtocol, @@ -123,8 +136,14 @@ impl ExtensionProcessing { )); } - match hello.quic_params_extension() { - Some(params) => cx.common.quic.params = Some(params), + let transport_params = hello + .transport_parameters + .as_ref() + .or(hello + .transport_parameters_draft + .as_ref()); + match transport_params { + Some(params) => cx.common.quic.params = Some(params.to_owned().into_vec()), None => { return Err(cx .common @@ -135,9 +154,9 @@ impl ExtensionProcessing { 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) + { + self.extensions.server_name_ack = Some(()); } // Send status_request response if we have one. This is not allowed @@ -145,13 +164,13 @@ impl ExtensionProcessing { // to send. if !for_resume && hello - .find_extension(ExtensionType::StatusRequest) + .certificate_status_request .is_some() { if ocsp_response.is_some() && !cx.common.is_tls13() { // Only TLS1.2 sends confirmation in ServerHello - self.exts - .push(ServerExtension::CertificateStatusAck); + self.extensions + .certificate_status_request_ack = Some(()); } } else { // Throw away any OCSP response so we don't try to send it later. @@ -161,8 +180,6 @@ impl ExtensionProcessing { self.validate_server_cert_type_extension(hello, config, cx)?; self.validate_client_cert_type_extension(hello, config, cx)?; - self.exts.extend(extra_exts); - Ok(()) } @@ -175,35 +192,29 @@ impl ExtensionProcessing { ) { // 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() + + use crate::msgs::base::PayloadU8; + let secure_reneg_offered = hello.renegotiation_info.is_some() || hello .cipher_suites .contains(&CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV); if secure_reneg_offered { - self.exts - .push(ServerExtension::make_empty_renegotiation_info()); + self.extensions.renegotiation_info = Some(PayloadU8::new(Vec::new())); } // 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() - { + if hello.session_ticket.is_some() && config.ticketer.enabled() { self.send_ticket = true; - self.exts - .push(ServerExtension::SessionTicketAck); + self.extensions.session_ticket_ack = Some(()); } // Confirm use of EMS if offered. if using_ems { - self.exts - .push(ServerExtension::ExtendedMasterSecretAck); + self.extensions + .extended_master_secret_ack = Some(()); } } @@ -213,22 +224,17 @@ impl ExtensionProcessing { 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, - }; + let client_supports = hello + .server_certificate_types + .as_deref() + .unwrap_or_default(); self.process_cert_type_extension( - raw_key_negotation_params.validate_raw_key_negotiation(), + client_supports, + config + .cert_resolver + .only_raw_public_keys(), + ExtensionType::ServerCertificateType, cx, ) } @@ -239,52 +245,61 @@ impl ExtensionProcessing { 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, - }; + let client_supports = hello + .client_certificate_types + .as_deref() + .unwrap_or_default(); + self.process_cert_type_extension( - raw_key_negotation_params.validate_raw_key_negotiation(), + client_supports, + config + .verifier + .requires_raw_public_keys(), + ExtensionType::ClientCertificateType, cx, ) } fn process_cert_type_extension( &mut self, - raw_key_negotiation_result: RawKeyNegotationResult, + client_supports: &[CertificateType], + requires_raw_keys: bool, + extension_type: ExtensionType, cx: &mut ServerContext<'_>, ) -> Result<(), Error> { - match raw_key_negotiation_result { - RawKeyNegotationResult::Negotiated(ExtensionType::ClientCertificateType) => { - self.exts - .push(ServerExtension::ClientCertType( - CertificateType::RawPublicKey, - )); + debug_assert!( + extension_type == ExtensionType::ClientCertificateType + || extension_type == ExtensionType::ServerCertificateType + ); + let raw_key_negotation_result = match ( + requires_raw_keys, + client_supports.contains(&CertificateType::RawPublicKey), + client_supports.contains(&CertificateType::X509), + ) { + (true, true, _) => Ok((extension_type, CertificateType::RawPublicKey)), + (false, _, true) => Ok((extension_type, CertificateType::X509)), + (false, true, false) => Err(Error::PeerIncompatible( + PeerIncompatible::IncorrectCertificateTypeExtension, + )), + (true, false, _) => Err(Error::PeerIncompatible( + PeerIncompatible::IncorrectCertificateTypeExtension, + )), + (false, false, false) => return Ok(()), + }; + + match raw_key_negotation_result { + Ok((ExtensionType::ClientCertificateType, cert_type)) => { + self.extensions.client_certificate_type = Some(cert_type); } - RawKeyNegotationResult::Negotiated(ExtensionType::ServerCertificateType) => { - self.exts - .push(ServerExtension::ServerCertType( - CertificateType::RawPublicKey, - )); + Ok((ExtensionType::ServerCertificateType, cert_type)) => { + self.extensions.server_certificate_type = Some(cert_type); } - RawKeyNegotationResult::Err(err) => { + 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" - ), + Ok((_, _)) => unreachable!(), } Ok(()) } @@ -292,7 +307,7 @@ impl ExtensionProcessing { pub(super) struct ExpectClientHello { pub(super) config: Arc, - pub(super) extra_exts: Vec, + pub(super) extra_exts: ServerExtensionsInput<'static>, pub(super) transcript: HandshakeHashOrBuffer, #[cfg(feature = "tls12")] pub(super) session_id: SessionId, @@ -303,7 +318,10 @@ pub(super) struct ExpectClientHello { } impl ExpectClientHello { - pub(super) fn new(config: Arc, extra_exts: Vec) -> Self { + pub(super) fn new( + config: Arc, + extra_exts: ServerExtensionsInput<'static>, + ) -> Self { let mut transcript_buffer = HandshakeHashBuffer::new(); if config.verifier.offer_client_auth() { @@ -339,11 +357,10 @@ 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 { + let version = if let Some(versions) = &client_hello.supported_versions { + if versions.tls13 && tls13_enabled { ProtocolVersion::TLSv1_3 - } else if !versions.contains(&ProtocolVersion::TLSv1_2) || !tls12_enabled { + } else if !versions.tls12 || !tls12_enabled { return Err(cx.common.send_fatal_alert( AlertDescription::ProtocolVersion, PeerIncompatible::Tls12NotOfferedOrEnabled, @@ -401,18 +418,25 @@ impl ExpectClientHello { // 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(), + _ => client_hello + .certificate_authority_names + .as_deref(), }; // 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(), + alpn: client_hello.protocols.as_ref(), + client_cert_types: client_hello + .client_certificate_types + .as_deref(), + server_cert_types: client_hello + .server_certificate_types + .as_deref(), cipher_suites: &client_hello.cipher_suites, certificate_authorities, + named_groups: client_hello.named_groups.as_deref(), }; trace!("Resolving server certificate: {client_hello:#?}"); @@ -436,8 +460,9 @@ impl ExpectClientHello { certkey.get_key().algorithm(), cx.common.protocol, client_hello - .namedgroups_extension() - .unwrap_or(&[]), + .named_groups + .as_deref() + .unwrap_or_default(), &client_hello.cipher_suites, ) .map_err(|incompat| { @@ -445,7 +470,7 @@ impl ExpectClientHello { .send_fatal_alert(AlertDescription::HandshakeFailure, incompat) })?; - debug!("decided upon suite {:?}", suite); + debug!("decided upon suite {suite:?}"); cx.common.suite = Some(suite); cx.common.kx_state = KxState::Start(skxg); @@ -668,7 +693,7 @@ pub(super) fn process_client_hello<'m>( ) -> Result<(&'m ClientHelloPayload, Vec), Error> { let client_hello = require_handshake_msg!(m, HandshakeType::ClientHello, HandshakePayload::ClientHello)?; - trace!("we got a clienthello {:?}", client_hello); + trace!("we got a clienthello {client_hello:?}"); if !client_hello .compression_methods @@ -680,13 +705,6 @@ pub(super) fn process_client_hello<'m>( )); } - if client_hello.has_duplicate_extension() { - return Err(cx.common.send_fatal_alert( - AlertDescription::DecodeError, - PeerMisbehaved::DuplicateClientHelloExtensions, - )); - } - // No handshake messages should follow this one in this flight. cx.common.check_aligned_handshake()?; @@ -695,23 +713,23 @@ pub(super) fn process_client_hello<'m>( // 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, - )); - } - - if let Some(hostname) = sni.single_hostname() { - Some(hostname.to_lowercase_owned()) - } else { - return Err(cx.common.send_fatal_alert( - AlertDescription::IllegalParameter, - PeerMisbehaved::ServerNameMustContainOneHostName, - )); - } + // + // [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 + let sni = match &client_hello.server_name { + Some(ServerNamePayload::SingleDnsName(dns_name)) => Some(dns_name.to_lowercase_owned()), + Some(ServerNamePayload::IpAddress) => None, + Some(ServerNamePayload::Invalid) => { + return Err(cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::ServerNameMustContainOneHostName, + )); } None => None, }; @@ -727,7 +745,8 @@ pub(super) fn process_client_hello<'m>( } let sig_schemes = client_hello - .sigalgs_extension() + .signature_schemes + .as_ref() .ok_or_else(|| { cx.common.send_fatal_alert( AlertDescription::HandshakeFailure, diff --git a/rustls/src/server/server_conn.rs b/rustls/src/server/server_conn.rs index e7fb5b4a068..5a75a0cdb49 100644 --- a/rustls/src/server/server_conn.rs +++ b/rustls/src/server/server_conn.rs @@ -1,5 +1,4 @@ use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt; use core::fmt::{Debug, Formatter}; @@ -11,6 +10,8 @@ use std::io; use pki_types::{DnsName, UnixTime}; use super::hs; +#[cfg(feature = "std")] +use crate::WantsVerifier; use crate::builder::ConfigBuilder; use crate::common_state::{CommonState, Side}; #[cfg(feature = "std")] @@ -19,20 +20,22 @@ use crate::conn::{ConnectionCommon, ConnectionCore, UnbufferedConnectionCommon}; #[cfg(doc)] use crate::crypto; use crate::crypto::CryptoProvider; -use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme}; +use crate::enums::{CertificateType, CipherSuite, ProtocolVersion, SignatureScheme}; use crate::error::Error; +use crate::kernel::KernelConnection; use crate::log::trace; use crate::msgs::base::Payload; -use crate::msgs::enums::CertificateType; -use crate::msgs::handshake::{ClientHelloPayload, ProtocolName, ServerExtension}; +use crate::msgs::handshake::{ClientHelloPayload, ProtocolName, ServerExtensionsInput}; use crate::msgs::message::Message; +use crate::suites::ExtractedSecrets; +use crate::sync::Arc; #[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}; +use crate::{ + DistinguishedName, KeyLog, NamedGroup, WantsVersions, compress, sign, verify, versions, +}; /// A trait for the ability to store server session data. /// @@ -144,6 +147,7 @@ pub struct ClientHello<'a> { /// /// [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> { @@ -215,6 +219,27 @@ impl<'a> ClientHello<'a> { 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 + } } /// Common configuration for a set of server sessions. @@ -241,6 +266,31 @@ impl<'a> ClientHello<'a> { /// * [`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)] @@ -267,9 +317,15 @@ pub struct ServerConfig { 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: Arc, /// How to choose a server cert and key. This is usually set by @@ -345,7 +401,8 @@ pub struct ServerConfig { /// 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, + /// The default is `true` if the configured [`CryptoProvider`] is + /// FIPS-compliant (i.e., [`CryptoProvider::fips()`] returns `true`), /// `false` otherwise. /// /// It must be set to `true` to meet FIPS requirement mentioned in section @@ -422,9 +479,9 @@ impl ServerConfig { // 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(), - )) + Self::builder_with_provider( + CryptoProvider::get_default_or_install_from_crate_features().clone(), + ) .with_protocol_versions(versions) .unwrap() } @@ -528,19 +585,21 @@ impl ServerConfig { #[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 super::{ + Accepted, Accepting, EarlyDataState, ServerConfig, ServerConnectionData, + ServerExtensionsInput, + }; 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::sync::Arc; use crate::vecbuf::ChunkVecBuffer; /// Allows reading of early data in resumed TLS1.3 connections. @@ -582,7 +641,10 @@ mod connection { /// we behave in the TLS protocol. pub fn new(config: Arc) -> Result { Ok(Self { - inner: ConnectionCommon::from(ConnectionCore::for_server(config, Vec::new())?), + inner: ConnectionCommon::from(ConnectionCore::for_server( + config, + ServerExtensionsInput::default(), + )?), }) } @@ -887,10 +949,35 @@ impl UnbufferedServerConnection { Ok(Self { inner: UnbufferedConnectionCommon::from(ConnectionCore::for_server( config, - Vec::new(), + ServerExtensionsInput::default(), )?), }) } + + /// Extract secrets, so they can be used when configuring kTLS, for example. + /// Should be used with care as it exposes secret key material. + #[deprecated = "dangerous_extract_secrets() does not support session tickets or \ + key updates, use dangerous_into_kernel_connection() instead"] + pub fn dangerous_extract_secrets(self) -> Result { + self.inner.dangerous_extract_secrets() + } + + /// Extract secrets and an [`KernelConnection`] object. + /// + /// This allows you use rustls to manage keys and then manage encryption and + /// decryption yourself (e.g. for kTLS). + /// + /// Should be used with care as it exposes secret key material. + /// + /// See the [`crate::kernel`] documentations for details on prerequisites + /// for calling this method. + pub fn dangerous_into_kernel_connection( + self, + ) -> Result<(ExtractedSecrets, KernelConnection), Error> { + self.inner + .core + .dangerous_into_kernel_connection() + } } impl Deref for UnbufferedServerConnection { @@ -911,6 +998,10 @@ impl UnbufferedConnectionCommon { pub(crate) fn pop_early_data(&mut self) -> Option> { self.core.data.early_data.pop() } + + pub(crate) fn peek_early_data(&self) -> Option<&[u8]> { + self.core.data.early_data.peek() + } } /// Represents a `ClientHello` message received through the [`Acceptor`]. @@ -929,11 +1020,18 @@ impl Accepted { 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(), + alpn: payload.protocols.as_ref(), + server_cert_types: payload + .server_certificate_types + .as_deref(), + client_cert_types: payload + .client_certificate_types + .as_deref(), cipher_suites: &payload.cipher_suites, - certificate_authorities: payload.certificate_authorities_extension(), + certificate_authorities: payload + .certificate_authority_names + .as_deref(), + named_groups: payload.named_groups.as_deref(), }; trace!("Accepted::client_hello(): {ch:#?}"); @@ -961,7 +1059,7 @@ impl Accepted { self.connection.enable_secret_extraction = config.enable_secret_extraction; - let state = hs::ExpectClientHello::new(config, Vec::new()); + let state = hs::ExpectClientHello::new(config, ServerExtensionsInput::default()); let mut cx = hs::ServerContext::from(&mut self.connection); let ch = Self::client_hello_payload(&self.message); @@ -978,8 +1076,7 @@ impl Accepted { fn client_hello_payload<'a>(message: &'a Message<'_>) -> &'a ClientHelloPayload { match &message.payload { - crate::msgs::message::MessagePayload::Handshake { parsed, .. } => match &parsed.payload - { + crate::msgs::message::MessagePayload::Handshake { parsed, .. } => match &parsed.0 { crate::msgs::handshake::HandshakePayload::ClientHello(ch) => ch, _ => unreachable!(), }, @@ -1026,7 +1123,6 @@ pub(super) enum EarlyDataState { Rejected, } - impl EarlyDataState { pub(super) fn reject(&mut self) { *self = Self::Rejected; @@ -1048,11 +1144,16 @@ impl EarlyDataState { matches!(self, Self::Rejected) } + fn peek(&self) -> Option<&[u8]> { + match self { + Self::Accepted { received, .. } => received.peek(), + _ => None, + } + } + fn pop(&mut self) -> Option> { match self { - Self::Accepted { - ref mut received, .. - } => received.pop(), + Self::Accepted { received, .. } => received.pop(), _ => None, } } @@ -1060,9 +1161,7 @@ impl EarlyDataState { #[cfg(feature = "std")] fn read(&mut self, buf: &mut [u8]) -> io::Result { match self { - Self::Accepted { - ref mut received, .. - } => received.read(buf), + Self::Accepted { received, .. } => received.read(buf), _ => Err(io::Error::from(io::ErrorKind::BrokenPipe)), } } @@ -1070,26 +1169,24 @@ impl EarlyDataState { #[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), + Self::Accepted { 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, + let Self::Accepted { received, left } = self else { + return false; + }; + + if received.apply_limit(available) != available || available > *left { + return false; } + + received.append(bytes.into_vec()); + *left -= available; + true } } @@ -1111,7 +1208,7 @@ impl Debug for EarlyDataState { impl ConnectionCore { pub(crate) fn for_server( config: Arc, - extra_exts: Vec, + extra_exts: ServerExtensionsInput<'static>, ) -> Result { let mut common = CommonState::new(Side::Server); common.set_max_fragment_size(config.max_fragment_size)?; diff --git a/rustls/src/server/test.rs b/rustls/src/server/test.rs new file mode 100644 index 00000000000..12fbf62cbf2 --- /dev/null +++ b/rustls/src/server/test.rs @@ -0,0 +1,371 @@ +use alloc::boxed::Box; +use alloc::vec; + +use super::ServerConnectionData; +use crate::common_state::Context; +use crate::enums::{CipherSuite, SignatureScheme}; +use crate::msgs::base::PayloadU16; +use crate::msgs::enums::{Compression, NamedGroup}; +use crate::msgs::handshake::{ + ClientExtensions, ClientHelloPayload, HandshakeMessagePayload, HandshakePayload, KeyShareEntry, + Random, ServerNamePayload, SessionId, SupportedProtocolVersions, +}; +use crate::msgs::message::{Message, MessagePayload}; +use crate::{CommonState, Error, PeerIncompatible, PeerMisbehaved, ProtocolVersion, Side}; + +#[test] +fn null_compression_required() { + assert_eq!( + test_process_client_hello(ClientHelloPayload { + compression_methods: vec![], + ..minimal_client_hello() + }), + Err(PeerIncompatible::NullCompressionRequired.into()), + ); +} + +#[test] +fn server_ignores_sni_with_ip_address() { + let mut ch = minimal_client_hello(); + ch.extensions.server_name = Some(ServerNamePayload::IpAddress); + std::println!("{:?}", ch.extensions); + assert_eq!(test_process_client_hello(ch), Ok(())); +} + +#[test] +fn server_rejects_sni_with_illegal_dns_name() { + let mut ch = minimal_client_hello(); + ch.extensions.server_name = Some(ServerNamePayload::Invalid); + std::println!("{:?}", ch.extensions); + assert_eq!( + test_process_client_hello(ch), + Err(PeerMisbehaved::ServerNameMustContainOneHostName.into()) + ); +} + +fn test_process_client_hello(hello: ClientHelloPayload) -> Result<(), Error> { + let m = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::ClientHello( + hello, + ))), + }; + super::hs::process_client_hello( + &m, + false, + &mut Context { + common: &mut CommonState::new(Side::Server), + data: &mut ServerConnectionData::default(), + sendable_plaintext: None, + }, + ) + .map(|_| ()) +} + +#[macro_rules_attribute::apply(test_for_each_provider)] +mod tests { + use alloc::vec::Vec; + + use super::super::*; + use crate::common_state::KxState; + use crate::crypto::{ + ActiveKeyExchange, CryptoProvider, KeyExchangeAlgorithm, SupportedKxGroup, + }; + use crate::enums::CertificateType; + use crate::pki_types::pem::PemObject; + use crate::pki_types::{CertificateDer, PrivateKeyDer}; + use crate::server::{AlwaysResolvesServerRawPublicKeys, ServerConfig, ServerConnection}; + use crate::sign::CertifiedKey; + use crate::sync::Arc; + use crate::{CipherSuiteCommon, SupportedCipherSuite, Tls12CipherSuite, version}; + + #[cfg(feature = "tls12")] + #[test] + fn test_server_rejects_no_extended_master_secret_extension_when_require_ems_or_fips() { + let provider = super::provider::default_provider(); + let mut config = ServerConfig::builder_with_provider(provider.into()) + .with_protocol_versions(&[&version::TLS12]) + .unwrap() + .with_no_client_auth() + .with_single_cert(server_cert(), server_key()) + .unwrap(); + + if config.provider.fips() { + 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 + )) + ); + } + + #[cfg(feature = "tls12")] + #[test] + fn server_picks_ffdhe_group_when_clienthello_has_no_ffdhe_group_in_groups_ext() { + let config = ServerConfig::builder_with_provider(ffdhe_provider().into()) + .with_protocol_versions(&[&version::TLS12]) + .unwrap() + .with_no_client_auth() + .with_single_cert(server_cert(), server_key()) + .unwrap(); + + let mut ch = minimal_client_hello(); + ch.cipher_suites + .push(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256.suite()); + + server_chooses_ffdhe_group_for_client_hello( + ServerConnection::new(config.into()).unwrap(), + ch, + ); + } + + #[cfg(feature = "tls12")] + #[test] + fn server_picks_ffdhe_group_when_clienthello_has_no_groups_ext() { + let config = ServerConfig::builder_with_provider(ffdhe_provider().into()) + .with_protocol_versions(&[&version::TLS12]) + .unwrap() + .with_no_client_auth() + .with_single_cert(server_cert(), server_key()) + .unwrap(); + + let mut ch = minimal_client_hello(); + ch.cipher_suites + .push(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256.suite()); + ch.extensions.named_groups.take(); + + server_chooses_ffdhe_group_for_client_hello( + ServerConnection::new(config.into()).unwrap(), + ch, + ); + } + + #[cfg(feature = "tls12")] + #[test] + fn server_accepts_client_with_no_ecpoints_extension_and_only_ffdhe_cipher_suites() { + let config = ServerConfig::builder_with_provider(ffdhe_provider().into()) + .with_protocol_versions(&[&version::TLS12]) + .unwrap() + .with_no_client_auth() + .with_single_cert(server_cert(), server_key()) + .unwrap(); + + let mut ch = minimal_client_hello(); + ch.cipher_suites + .push(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256.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 KxState::Start(skxg) = &conn.kx_state else { + panic!("unexpected kx_state"); + }; + assert_eq!(skxg.name(), FAKE_FFDHE_GROUP.name()); + } + + #[test] + fn test_server_requiring_rpk_client_rejects_x509_client() { + 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(server_config_for_rpk().into()).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 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(server_config_for_rpk().into()).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() -> ServerConfig { + let x25519_provider = CryptoProvider { + kx_groups: vec![super::provider::kx_group::X25519], + ..super::provider::default_provider() + }; + ServerConfig::builder_with_provider(x25519_provider.into()) + .with_protocol_versions(&[&version::TLS13]) + .unwrap() + .with_no_client_auth() + .with_cert_resolver(Arc::new(AlwaysResolvesServerRawPublicKeys::new(Arc::new( + server_certified_key(), + )))) + } + + fn server_certified_key() -> CertifiedKey { + let key = super::provider::default_provider() + .key_provider + .load_private_key(server_key()) + .unwrap(); + let public_key_as_cert = vec![CertificateDer::from( + key.public_key() + .unwrap() + .as_ref() + .to_vec(), + )]; + CertifiedKey::new(public_key_as_cert, key) + } + + fn server_key() -> PrivateKeyDer<'static> { + PrivateKeyDer::from_pem_reader( + &mut include_bytes!("../../../test-ca/rsa-2048/end.key").as_slice(), + ) + .unwrap() + } + + fn server_cert() -> Vec> { + vec![ + CertificateDer::from(&include_bytes!("../../../test-ca/rsa-2048/end.der")[..]), + CertificateDer::from(&include_bytes!("../../../test-ca/rsa-2048/inter.der")[..]), + ] + } + + fn ffdhe_provider() -> CryptoProvider { + CryptoProvider { + kx_groups: vec![FAKE_FFDHE_GROUP], + cipher_suites: vec![TLS_DHE_RSA_WITH_AES_128_GCM_SHA256], + ..super::provider::default_provider() + } + } + + static FAKE_FFDHE_GROUP: &'static dyn SupportedKxGroup = &FakeFfdheGroup; + + #[derive(Debug)] + struct FakeFfdheGroup; + + impl SupportedKxGroup for FakeFfdheGroup { + fn name(&self) -> NamedGroup { + NamedGroup::FFDHE2048 + } + + fn start(&self) -> Result, Error> { + Ok(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 group(&self) -> NamedGroup { + NamedGroup::FFDHE2048 + } + } + + 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 &super::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!(), + }; +} + +fn minimal_client_hello() -> ClientHelloPayload { + ClientHelloPayload { + client_version: ProtocolVersion::TLSv1_3, + random: Random::from([0u8; 32]), + session_id: SessionId::empty(), + cipher_suites: vec![ + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS13_AES_128_GCM_SHA256, + ], + compression_methods: vec![Compression::Null], + extensions: Box::new(ClientExtensions { + signature_schemes: Some(vec![SignatureScheme::RSA_PSS_SHA256]), + named_groups: Some(vec![NamedGroup::X25519, NamedGroup::secp256r1]), + supported_versions: Some(SupportedProtocolVersions { + tls12: true, + tls13: true, + }), + key_shares: Some(vec![KeyShareEntry { + group: NamedGroup::X25519, + payload: PayloadU16::new(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..d3dfa5c83dd 100644 --- a/rustls/src/server/tls12.rs +++ b/rustls/src/server/tls12.rs @@ -1,6 +1,5 @@ use alloc::boxed::Box; use alloc::string::ToString; -use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; @@ -14,6 +13,7 @@ use super::server_conn::{ProducesTickets, ServerConfig, ServerConnectionData}; use crate::check::inappropriate_message; use crate::common_state::{CommonState, HandshakeFlightTls12, HandshakeKind, Side, State}; use crate::conn::ConnectionRandoms; +use crate::conn::kernel::{Direction, KernelContext, KernelState}; use crate::crypto::ActiveKeyExchange; use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion}; use crate::error::{Error, PeerIncompatible, PeerMisbehaved}; @@ -24,13 +24,14 @@ use crate::msgs::ccs::ChangeCipherSpecPayload; use crate::msgs::codec::Codec; use crate::msgs::handshake::{ CertificateChain, ClientKeyExchangeParams, HandshakeMessagePayload, HandshakePayload, - NewSessionTicketPayload, SessionId, + NewSessionTicketPayload, NewSessionTicketPayloadTls13, 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::{ConnectionTrafficSecrets, verify}; mod client_hello { use pki_types::CertificateDer; @@ -39,10 +40,10 @@ mod client_hello { use crate::common_state::KxState; use crate::crypto::SupportedKxGroup; use crate::enums::SignatureScheme; - use crate::msgs::enums::{ClientCertificateType, Compression, ECPointFormat}; + use crate::msgs::enums::{ClientCertificateType, Compression}; use crate::msgs::handshake::{ - CertificateRequestPayload, CertificateStatus, ClientExtension, ClientHelloPayload, - ClientSessionTicket, Random, ServerExtension, ServerHelloPayload, ServerKeyExchange, + CertificateRequestPayload, CertificateStatus, ClientHelloPayload, ClientSessionTicket, + Random, ServerExtensionsInput, ServerHelloPayload, ServerKeyExchange, ServerKeyExchangeParams, ServerKeyExchangePayload, }; use crate::sign; @@ -56,7 +57,7 @@ mod client_hello { 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, + pub(in crate::server) extra_exts: ServerExtensionsInput<'static>, } impl CompleteClientHelloHandling { @@ -73,7 +74,10 @@ mod client_hello { // -- TLS1.2 only from hereon in -- self.transcript.add_message(chm); - if client_hello.ems_support_offered() { + if client_hello + .extended_master_secret_request + .is_some() + { self.using_ems = true; } else if self.config.require_ems { return Err(cx.common.send_fatal_alert( @@ -86,13 +90,13 @@ mod client_hello { // 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 = client_hello + .ec_point_formats + .unwrap_or_default(); - trace!("ecpoints {:?}", ecpoints_ext); + trace!("ecpoints {supported_ec_point_formats:?}"); - if !ecpoints_ext.contains(&ECPointFormat::Uncompressed) { + if !supported_ec_point_formats.uncompressed { return Err(cx.common.send_fatal_alert( AlertDescription::IllegalParameter, PeerIncompatible::UncompressedEcPointsRequired, @@ -118,11 +122,10 @@ mod client_hello { // let mut ticket_received = false; let resume_data = client_hello - .ticket_extension() + .session_ticket + .as_ref() .and_then(|ticket_ext| match ticket_ext { - ClientExtension::SessionTicket(ClientSessionTicket::Offer(ticket)) => { - Some(ticket) - } + ClientSessionTicket::Offer(ticket) => Some(ticket), _ => None, }) .and_then(|ticket| { @@ -169,19 +172,6 @@ mod client_hello { )); } - 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(); // If we're not offered a ticket or a potential session ID, allocate a session ID. @@ -340,24 +330,21 @@ mod client_hello { hello: &ClientHelloPayload, resumedata: Option<&persist::ServerSessionValue>, randoms: &ConnectionRandoms, - extra_exts: Vec, + extra_exts: ServerExtensionsInput<'static>, ) -> Result { - let mut ep = hs::ExtensionProcessing::new(); - ep.process_common(config, cx, ocsp_response, hello, resumedata, extra_exts)?; + let mut ep = hs::ExtensionProcessing::new(extra_exts); + ep.process_common(config, cx, ocsp_response, hello, resumedata)?; 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); + 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: ep.extensions, + })); + trace!("sending server hello {sh:?}"); flight.add(sh); Ok(ep.send_ticket) @@ -367,17 +354,15 @@ mod client_hello { flight: &mut HandshakeFlightTls12<'_>, cert_chain: &[CertificateDer<'static>], ) { - flight.add(HandshakeMessagePayload { - typ: HandshakeType::Certificate, - payload: HandshakePayload::Certificate(CertificateChain(cert_chain.to_vec())), - }); + flight.add(HandshakeMessagePayload(HandshakePayload::Certificate( + CertificateChain(cert_chain.to_vec()), + ))); } 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( @@ -406,10 +391,9 @@ mod client_hello { dss: DigitallySignedStruct::new(sigscheme, sig), }); - flight.add(HandshakeMessagePayload { - typ: HandshakeType::ServerKeyExchange, - payload: HandshakePayload::ServerKeyExchange(skx), - }); + flight.add(HandshakeMessagePayload( + HandshakePayload::ServerKeyExchange(skx), + )); Ok(kx) } @@ -439,21 +423,15 @@ 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)); } } @@ -491,7 +469,7 @@ impl State for ExpectCertificate { .verifier .client_auth_mandatory(); - trace!("certs {:?}", cert_chain); + trace!("certs {cert_chain:?}"); let client_cert = match cert_chain.split_first() { None if mandatory => { @@ -598,8 +576,8 @@ impl State for ExpectClientKx<'_> { cx.common .start_encryption_tls12(&secrets, Side::Server); - if let Some(client_cert) = self.client_cert { - Ok(Box::new(ExpectCertificateVerify { + match self.client_cert { + Some(client_cert) => Ok(Box::new(ExpectCertificateVerify { config: self.config, secrets, transcript: self.transcript, @@ -607,9 +585,8 @@ impl State for ExpectClientKx<'_> { using_ems: self.using_ems, client_cert, send_ticket: self.send_ticket, - })) - } else { - Ok(Box::new(ExpectCcs { + })), + _ => Ok(Box::new(ExpectCcs { config: self.config, secrets, transcript: self.transcript, @@ -617,7 +594,7 @@ impl State for ExpectClientKx<'_> { using_ems: self.using_ems, resuming: false, send_ticket: self.send_ticket, - })) + })), } } @@ -746,7 +723,7 @@ impl State for ExpectCcs { return Err(inappropriate_message( &payload, &[ContentType::ChangeCipherSpec], - )) + )); } } @@ -820,13 +797,12 @@ 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); @@ -854,10 +830,9 @@ fn emit_finished( 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); @@ -911,6 +886,7 @@ impl State for ExpectFinished { .config .session_storage .put(self.session_id.as_ref().to_vec(), value.get_encoding()); + #[cfg_attr(not(feature = "logging"), allow(clippy::if_same_then_else))] if worked { debug!("Session saved"); } else { @@ -999,7 +975,29 @@ impl State for ExpectTraffic { .extract_secrets(Side::Server) } + fn into_external_state(self: Box) -> Result, Error> { + Ok(self) + } + fn into_owned(self: Box) -> hs::NextState<'static> { self } } + +impl KernelState for ExpectTraffic { + fn update_secrets(&mut self, _: Direction) -> Result { + Err(Error::General( + "TLS 1.2 connections do not support traffic secret updates".into(), + )) + } + + fn handle_new_session_ticket( + &mut self, + _cx: &mut KernelContext<'_>, + _message: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + unreachable!( + "server connections should never have handle_new_session_ticket called on them" + ) + } +} diff --git a/rustls/src/server/tls13.rs b/rustls/src/server/tls13.rs index e90de2fba3a..b8b70e721a7 100644 --- a/rustls/src/server/tls13.rs +++ b/rustls/src/server/tls13.rs @@ -1,5 +1,4 @@ use alloc::boxed::Box; -use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; @@ -14,6 +13,7 @@ use crate::common_state::{ CommonState, HandshakeFlightTls13, HandshakeKind, Protocol, Side, State, }; use crate::conn::ConnectionRandoms; +use crate::conn::kernel::{Direction, KernelContext, KernelState}; use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion}; use crate::error::{Error, InvalidMessage, PeerIncompatible, PeerMisbehaved}; use crate::hash_hs::HandshakeHash; @@ -21,20 +21,21 @@ 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, + CERTIFICATE_MAX_SIZE_LIMIT, CertificateChain, CertificatePayloadTls13, HandshakeMessagePayload, + HandshakePayload, NewSessionTicketPayloadTls13, }; use crate::msgs::message::{Message, MessagePayload}; use crate::msgs::persist; use crate::server::ServerConfig; use crate::suites::PartiallyExtractedSecrets; +use crate::sync::Arc; use crate::tls13::key_schedule::{ - KeyScheduleTraffic, KeyScheduleTrafficWithClientFinishedPending, ResumptionSecret, + KeyScheduleResumption, KeyScheduleTraffic, 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::{ConnectionTrafficSecrets, compress, rand, verify}; mod client_hello { use super::*; @@ -43,11 +44,11 @@ mod client_hello { 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::enums::{Compression, NamedGroup}; use crate::msgs::handshake::{ - CertReqExtension, CertificatePayloadTls13, CertificateRequestPayloadTls13, - ClientHelloPayload, HelloRetryExtension, HelloRetryRequest, KeyShareEntry, Random, - ServerExtension, ServerHelloPayload, SessionId, + CertificatePayloadTls13, CertificateRequestExtensions, CertificateRequestPayloadTls13, + ClientHelloPayload, HelloRetryRequest, HelloRetryRequestExtensions, KeyShareEntry, Random, + ServerExtensions, ServerExtensionsInput, ServerHelloPayload, SessionId, }; use crate::server::common::ActiveCertifiedKey; use crate::sign; @@ -70,7 +71,7 @@ mod client_hello { 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, + pub(in crate::server) extra_exts: ServerExtensionsInput<'static>, } fn max_early_data_size(configured: u32) -> usize { @@ -97,13 +98,15 @@ mod client_hello { binder: &[u8], ) -> bool { let binder_plaintext = match &client_hello.payload { - MessagePayload::Handshake { parsed, .. } => parsed.encoding_for_binder_signing(), + MessagePayload::Handshake { parsed, encoded } => { + &encoded.bytes()[..encoded.bytes().len() - parsed.total_binder_length()] + } _ => unreachable!(), }; let handshake_hash = self .transcript - .hash_given(&binder_plaintext); + .hash_given(binder_plaintext); let key_schedule = KeyScheduleEarly::new(suite, psk); let real_binder = @@ -148,7 +151,8 @@ mod client_hello { sigschemes_ext.retain(SignatureScheme::supported_in_tls13); let shares_ext = client_hello - .keyshare_extension() + .key_shares + .as_ref() .ok_or_else(|| { cx.common.send_fatal_alert( AlertDescription::HandshakeFailure, @@ -171,7 +175,8 @@ mod client_hello { } let cert_compressor = client_hello - .certificate_compression_extension() + .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. @@ -181,7 +186,9 @@ mod client_hello { .find(|compressor| offered.contains(&compressor.algorithm())) .cloned()); - let early_data_requested = client_hello.early_data_extension_offered(); + let early_data_requested = client_hello + .early_data_request + .is_some(); // EarlyData extension is illegal in second ClientHello if self.done_retry && early_data_requested { @@ -246,19 +253,15 @@ mod client_hello { 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, - )); - } - + if let Some(psk_offer) = &client_hello.preshared_key_offer { // "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() { + if client_hello + .preshared_key_modes + .is_none() + { return Err(cx.common.send_fatal_alert( AlertDescription::MissingExtension, PeerMisbehaved::MissingPskModesExtension, @@ -313,8 +316,13 @@ mod client_hello { } } - if !client_hello.psk_mode_offered(PSKKeyExchangeMode::PSK_DHE_KE) { - debug!("Client unwilling to resume, DHE_KE not offered"); + if !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"); self.send_tickets = 0; chosen_psk_index = None; resumedata = None; @@ -322,7 +330,7 @@ mod client_hello { self.send_tickets = self.config.send_tls13_tickets; } - if let Some(ref resume) = resumedata { + if let Some(resume) = &resumedata { cx.data.received_resumption_data = Some(resume.application_data.0.clone()); cx.common .peer_certificates @@ -402,7 +410,9 @@ mod client_hello { cx.data.early_data.reject(); } 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, @@ -485,8 +495,6 @@ mod client_hello { resuming_psk: Option<&[u8]>, 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); @@ -498,21 +506,17 @@ mod client_hello { })?; cx.common.kx_state.complete(); - extensions.push(ServerExtension::KeyShare(KeyShareEntry::new( - ckx.group, - ckx.pub_key, - ))); - extensions.push(ServerExtension::SupportedVersions(ProtocolVersion::TLSv1_3)); - - 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)), + selected_version: Some(ProtocolVersion::TLSv1_3), + preshared_key: chosen_psk_idx.map(|idx| idx as u16), + ..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,14 +524,14 @@ 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); @@ -578,29 +582,25 @@ mod client_hello { common: &mut CommonState, 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); @@ -614,7 +614,9 @@ mod client_hello { 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, @@ -649,7 +651,7 @@ mod client_hello { && 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.alpn.as_ref().map(|p| &p.0[..]) == cx.common.alpn_protocol.as_deref(); if early_data_configured && early_data_possible && !cx.data.early_data.was_rejected() { EarlyDataDecision::Accepted @@ -670,23 +672,20 @@ mod client_hello { ocsp_response: &mut Option<&[u8]>, hello: &ClientHelloPayload, resumedata: Option<&persist::ServerSessionValue>, - extra_exts: Vec, + extra_exts: ServerExtensionsInput<'static>, config: &ServerConfig, ) -> Result { - let mut ep = hs::ExtensionProcessing::new(); - ep.process_common(config, cx, ocsp_response, hello, resumedata, extra_exts)?; + let mut ep = hs::ExtensionProcessing::new(extra_exts); + ep.process_common(config, cx, ocsp_response, hello, resumedata)?; let early_data = decide_if_early_data_allowed(cx, hello, resumedata, suite, config); if early_data == EarlyDataDecision::Accepted { - ep.exts.push(ServerExtension::EarlyData); + ep.extensions.early_data_ack = Some(()); } - let ee = HandshakeMessagePayload { - typ: HandshakeType::EncryptedExtensions, - payload: HandshakePayload::EncryptedExtensions(ep.exts), - }; + let ee = HandshakeMessagePayload(HandshakePayload::EncryptedExtensions(ep.extensions)); - trace!("sending encrypted extensions {:?}", ee); + trace!("sending encrypted extensions {ee:?}"); flight.add(ee); Ok(early_data) } @@ -699,40 +698,33 @@ mod client_hello { return Ok(false); } - let mut cr = CertificateRequestPayloadTls13 { + let 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( + 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(), + ), + certificate_compression_algorithms: match config.cert_decompressors.as_slice() { + &[] => None, + decomps => Some( + decomps + .iter() + .map(|decomp| decomp.algorithm()) + .collect(), + ), + }, + authority_names: match config.verifier.root_hint_subjects() { + &[] => None, + authorities => Some(authorities.to_vec()), + }, + }, }; - trace!("Sending CertificateRequest {:?}", creq); + let creq = HandshakeMessagePayload(HandshakePayload::CertificateRequestTls13(cr)); + + trace!("Sending CertificateRequest {creq:?}"); flight.add(creq); Ok(true) } @@ -742,15 +734,11 @@ mod client_hello { cert_chain: &[CertificateDer<'static>], ocsp_response: Option<&[u8]>, ) { - let cert = HandshakeMessagePayload { - typ: HandshakeType::Certificate, - payload: HandshakePayload::CertificateTls13(CertificatePayloadTls13::new( - cert_chain.iter(), - ocsp_response, - )), - }; + let cert = HandshakeMessagePayload(HandshakePayload::CertificateTls13( + CertificatePayloadTls13::new(cert_chain.iter(), ocsp_response), + )); - trace!("sending certificate {:?}", cert); + trace!("sending certificate {cert:?}"); flight.add(cert); } @@ -770,12 +758,11 @@ mod client_hello { return emit_certificate_tls13(flight, cert_chain, ocsp_response); }; - 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); } @@ -801,12 +788,9 @@ mod client_hello { 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(()) } @@ -822,12 +806,9 @@ mod client_hello { let verify_data = key_schedule.sign_server_finish(&handshake_hash); 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); @@ -861,7 +842,7 @@ impl State for ExpectAndSkipRejectedEarlyData { * 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) = &m.payload { if skip_data.bytes().len() <= self.skip_data_left { self.skip_data_left -= skip_data.bytes().len(); return Ok(self); @@ -895,11 +876,7 @@ impl State for ExpectCertificateOrCompressedCertificate { { match m.payload { MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CertificateTls13(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CertificateTls13(..)), .. } => Box::new(ExpectCertificate { config: self.config, @@ -912,11 +889,7 @@ impl State for ExpectCertificateOrCompressedCertificate { .handle(cx, m), MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - payload: HandshakePayload::CompressedCertificate(..), - .. - }, + parsed: HandshakeMessagePayload(HandshakePayload::CompressedCertificate(..)), .. } => Box::new(ExpectCompressedCertificate { config: self.config, @@ -1019,10 +992,9 @@ impl State for ExpectCompressedCertificate { let m = Message { version: ProtocolVersion::TLSv1_3, - payload: MessagePayload::handshake(HandshakeMessagePayload { - typ: HandshakeType::Certificate, - payload: HandshakePayload::CertificateTls13(cert_payload.into_owned()), - }), + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::CertificateTls13(cert_payload.into_owned()), + )), }; Box::new(ExpectCertificate { @@ -1070,7 +1042,11 @@ impl State for ExpectCertificate { // 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()); } @@ -1218,11 +1194,7 @@ impl State for ExpectEarlyData { } } MessagePayload::Handshake { - parsed: - HandshakeMessagePayload { - typ: HandshakeType::EndOfEarlyData, - payload: HandshakePayload::EndOfEarlyData, - }, + parsed: HandshakeMessagePayload(HandshakePayload::EndOfEarlyData), .. } => { self.key_schedule @@ -1252,7 +1224,7 @@ impl State for ExpectEarlyData { // --- Process client's Finished --- fn get_server_session_value( suite: &'static Tls13CipherSuite, - secret: &ResumptionSecret<'_>, + resumption: &KeyScheduleResumption, cx: &ServerContext<'_>, nonce: &[u8], time_now: UnixTime, @@ -1260,7 +1232,7 @@ fn get_server_session_value( ) -> persist::ServerSessionValue { let version = ProtocolVersion::TLSv1_3; - let secret = secret.derive_ticket_psk(nonce); + let secret = resumption.derive_ticket_psk(nonce); persist::ServerSessionValue::new( cx.data.sni.as_ref(), @@ -1288,7 +1260,7 @@ impl ExpectFinished { flight: &mut HandshakeFlightTls13<'_>, suite: &'static Tls13CipherSuite, cx: &ServerContext<'_>, - secret: &ResumptionSecret<'_>, + resumption: &KeyScheduleResumption, config: &ServerConfig, ) -> Result<(), Error> { let secure_random = config.provider.secure_random; @@ -1298,7 +1270,7 @@ impl ExpectFinished { let now = config.current_time()?; let plain = - get_server_session_value(suite, secret, cx, &nonce, now, age_add).get_encoding(); + get_server_session_value(suite, resumption, cx, &nonce, now, age_add).get_encoding(); let stateless = config.ticketer.enabled(); let (ticket, lifetime) = if stateless { @@ -1323,11 +1295,7 @@ impl ExpectFinished { if config.max_early_data_size > 0 { if !stateless { - payload - .exts - .push(NewSessionTicketExtension::EarlyData( - config.max_early_data_size, - )); + 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,11 +1303,8 @@ 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: {stateless})"); flight.add(t); Ok(()) @@ -1359,7 +1324,7 @@ impl State for ExpectFinished { require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?; let handshake_hash = self.transcript.current_hash(); - let (key_schedule_traffic, expect_verify_data) = self + let (key_schedule_before_finished, expect_verify_data) = self .key_schedule .sign_client_finish(&handshake_hash, cx.common); @@ -1379,8 +1344,8 @@ impl State for ExpectFinished { cx.common.check_aligned_handshake()?; - let handshake_hash = self.transcript.current_hash(); - let resumption = ResumptionSecret::new(&key_schedule_traffic, &handshake_hash); + let (key_schedule_traffic, resumption) = + key_schedule_before_finished.into_traffic(self.transcript.current_hash()); let mut flight = HandshakeFlightTls13::new(&mut self.transcript); for _ in 0..self.send_tickets { @@ -1456,11 +1421,7 @@ impl State for ExpectTraffic { .common .take_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)?, payload => { @@ -1495,11 +1456,35 @@ impl State for ExpectTraffic { .request_key_update_and_update_encrypter(common) } + fn into_external_state(self: Box) -> Result, Error> { + Ok(self) + } + fn into_owned(self: Box) -> hs::NextState<'static> { self } } +impl KernelState for ExpectTraffic { + fn update_secrets(&mut self, dir: Direction) -> Result { + self.key_schedule + .refresh_traffic_secret(match dir { + Direction::Transmit => Side::Server, + Direction::Receive => Side::Client, + }) + } + + fn handle_new_session_ticket( + &mut self, + _cx: &mut KernelContext<'_>, + _message: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + unreachable!( + "server connections should never have handle_new_session_ticket called on them" + ) + } +} + struct ExpectQuicTraffic { key_schedule: KeyScheduleTraffic, _fin_verified: verify::FinishedMessageVerified, @@ -1532,3 +1517,19 @@ impl State for ExpectQuicTraffic { self } } + +impl KernelState for ExpectQuicTraffic { + fn update_secrets(&mut self, _: Direction) -> Result { + Err(Error::General( + "QUIC connections do not support key updates".into(), + )) + } + + fn handle_new_session_ticket( + &mut self, + _cx: &mut KernelContext<'_>, + _message: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + unreachable!("handle_new_session_ticket should not be called for server-side connections") + } +} diff --git a/rustls/src/stream.rs b/rustls/src/stream.rs index a6a394a9123..9de94db1b26 100644 --- a/rustls/src/stream.rs +++ b/rustls/src/stream.rs @@ -1,11 +1,13 @@ 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}; /// This type implements `io::Read` and `io::Write`, encapsulating /// a Connection `C` and an underlying transport `T`, such as a socket. /// +/// Relies on [`ConnectionCommon::complete_io()`] to perform the necessary I/O. +/// /// This allows you to use a rustls Connection like a normal stream. #[derive(Debug)] pub struct Stream<'a, C: 'a + ?Sized, T: 'a + Read + Write + ?Sized> { @@ -41,15 +43,8 @@ where 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 @@ -62,24 +57,54 @@ where } } + Ok(()) + } + + // Implements `BufRead::fill_buf` but with more flexible lifetimes, so StreamOwned can reuse it + fn fill_buf(mut self) -> Result<&'a [u8]> + where + S: 'a, + { + self.prepare_read()?; + self.conn.reader().into_first_chunk() + } +} + +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 { + self.prepare_read()?; self.conn.reader().read(buf) } #[cfg(read_buf)] fn read_buf(&mut self, cursor: core::io::BorrowedCursor<'_>) -> Result<()> { - self.complete_prior_io()?; + self.prepare_read()?; + self.conn.reader().read_buf(cursor) + } +} - // 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, S> BufRead for Stream<'a, C, T> +where + C: 'a + DerefMut + Deref>, + T: 'a + Read + Write, + S: 'a + SideData, +{ + 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) } } @@ -130,8 +155,9 @@ where } /// 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 [`ConnectionCommon::complete_io()`] to perform the necessary I/O. /// /// This allows you to use a rustls Connection like a normal stream. #[derive(Debug)] @@ -204,6 +230,21 @@ where } } +impl BufRead for StreamOwned +where + C: DerefMut + Deref>, + T: Read + Write, + S: 'static + SideData, +{ + fn fill_buf(&mut self) -> Result<&[u8]> { + self.as_stream().fill_buf() + } + + fn consume(&mut self, amt: usize) { + self.as_stream().consume(amt) + } +} + impl Write for StreamOwned where C: DerefMut + Deref>, diff --git a/rustls/src/suites.rs b/rustls/src/suites.rs index 9e6f1c782fd..aebe1a37661 100644 --- a/rustls/src/suites.rs +++ b/rustls/src/suites.rs @@ -28,7 +28,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 +43,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 @@ -254,15 +255,19 @@ mod tests { #[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()); + 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() + ); } } diff --git a/rustls/src/testdata/cert-arstechnica.0.der b/rustls/src/testdata/cert-arstechnica.0.der index ac81cd85ec8..5a68def3600 100644 Binary files a/rustls/src/testdata/cert-arstechnica.0.der and b/rustls/src/testdata/cert-arstechnica.0.der differ diff --git a/rustls/src/testdata/cert-arstechnica.1.der b/rustls/src/testdata/cert-arstechnica.1.der index 93f1fb0c6b5..66c211b49d9 100644 Binary files a/rustls/src/testdata/cert-arstechnica.1.der and b/rustls/src/testdata/cert-arstechnica.1.der 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 index 9f8267ca925..9a64c211f1c 100644 Binary files a/rustls/src/testdata/cert-duckduckgo.0.der and b/rustls/src/testdata/cert-duckduckgo.0.der differ diff --git a/rustls/src/testdata/cert-duckduckgo.1.der b/rustls/src/testdata/cert-duckduckgo.1.der index dd6a50f3273..b2b371600ce 100644 Binary files a/rustls/src/testdata/cert-duckduckgo.1.der and b/rustls/src/testdata/cert-duckduckgo.1.der differ diff --git a/rustls/src/testdata/cert-duckduckgo.2.der b/rustls/src/testdata/cert-duckduckgo.2.der new file mode 100644 index 00000000000..1e927a7afe0 Binary files /dev/null and b/rustls/src/testdata/cert-duckduckgo.2.der differ diff --git a/rustls/src/testdata/cert-github.0.der b/rustls/src/testdata/cert-github.0.der index 86d6fce22f2..ee00f70e5e8 100644 Binary files a/rustls/src/testdata/cert-github.0.der and b/rustls/src/testdata/cert-github.0.der differ diff --git a/rustls/src/testdata/cert-github.1.der b/rustls/src/testdata/cert-github.1.der index 78a66bb47b4..bd4e397246f 100644 Binary files a/rustls/src/testdata/cert-github.1.der and b/rustls/src/testdata/cert-github.1.der differ diff --git a/rustls/src/testdata/cert-github.2.der b/rustls/src/testdata/cert-github.2.der new file mode 100644 index 00000000000..8a65485d0bb Binary files /dev/null and b/rustls/src/testdata/cert-github.2.der differ diff --git a/rustls/src/testdata/cert-google.0.der b/rustls/src/testdata/cert-google.0.der index e8c41b21f57..f706cadc2dd 100644 Binary files a/rustls/src/testdata/cert-google.0.der and b/rustls/src/testdata/cert-google.0.der differ diff --git a/rustls/src/testdata/cert-google.1.der b/rustls/src/testdata/cert-google.1.der index 66715042432..97276816342 100644 Binary files a/rustls/src/testdata/cert-google.1.der and b/rustls/src/testdata/cert-google.1.der differ diff --git a/rustls/src/testdata/cert-hn.0.der b/rustls/src/testdata/cert-hn.0.der index bc42b61abcb..686d87caaa4 100644 Binary files a/rustls/src/testdata/cert-hn.0.der and b/rustls/src/testdata/cert-hn.0.der differ diff --git a/rustls/src/testdata/cert-hn.1.der b/rustls/src/testdata/cert-hn.1.der index dd6a50f3273..b187e36c701 100644 Binary files a/rustls/src/testdata/cert-hn.1.der and b/rustls/src/testdata/cert-hn.1.der differ diff --git a/rustls/src/testdata/cert-reddit.0.der b/rustls/src/testdata/cert-reddit.0.der index 3a26d368cee..40363243ee8 100644 Binary files a/rustls/src/testdata/cert-reddit.0.der and b/rustls/src/testdata/cert-reddit.0.der differ diff --git a/rustls/src/testdata/cert-reddit.1.der b/rustls/src/testdata/cert-reddit.1.der index dd6a50f3273..b2b371600ce 100644 Binary files a/rustls/src/testdata/cert-reddit.1.der and b/rustls/src/testdata/cert-reddit.1.der differ diff --git a/rustls/src/testdata/cert-rustlang.0.der b/rustls/src/testdata/cert-rustlang.0.der index 3af1cadfd8f..d8cef415ec8 100644 Binary files a/rustls/src/testdata/cert-rustlang.0.der and b/rustls/src/testdata/cert-rustlang.0.der differ diff --git a/rustls/src/testdata/cert-rustlang.1.der b/rustls/src/testdata/cert-rustlang.1.der index 93f1fb0c6b5..f583b217ae5 100644 Binary files a/rustls/src/testdata/cert-rustlang.1.der and b/rustls/src/testdata/cert-rustlang.1.der 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 index 0b6271ff545..b807621035b 100644 Binary files a/rustls/src/testdata/cert-servo.0.der and b/rustls/src/testdata/cert-servo.0.der differ diff --git a/rustls/src/testdata/cert-servo.1.der b/rustls/src/testdata/cert-servo.1.der index 41c742136ce..95399615ce6 100644 Binary files a/rustls/src/testdata/cert-servo.1.der and b/rustls/src/testdata/cert-servo.1.der differ diff --git a/rustls/src/testdata/cert-servo.2.der b/rustls/src/testdata/cert-servo.2.der new file mode 100644 index 00000000000..ad98b4c5085 Binary files /dev/null and b/rustls/src/testdata/cert-servo.2.der differ diff --git a/rustls/src/testdata/cert-stackoverflow.0.der b/rustls/src/testdata/cert-stackoverflow.0.der index 68068a77c5b..a0a73350878 100644 Binary files a/rustls/src/testdata/cert-stackoverflow.0.der and b/rustls/src/testdata/cert-stackoverflow.0.der differ diff --git a/rustls/src/testdata/cert-stackoverflow.1.der b/rustls/src/testdata/cert-stackoverflow.1.der index 2d66ea723ea..b187e36c701 100644 Binary files a/rustls/src/testdata/cert-stackoverflow.1.der and b/rustls/src/testdata/cert-stackoverflow.1.der 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 index 36f4e06d476..c32a5a64f5c 100644 Binary files a/rustls/src/testdata/cert-twitter.0.der and b/rustls/src/testdata/cert-twitter.0.der differ diff --git a/rustls/src/testdata/cert-twitter.1.der b/rustls/src/testdata/cert-twitter.1.der index 608f16c7921..6d4977b41ac 100644 Binary files a/rustls/src/testdata/cert-twitter.1.der and b/rustls/src/testdata/cert-twitter.1.der differ diff --git a/rustls/src/testdata/cert-wapo.0.der b/rustls/src/testdata/cert-wapo.0.der index 94d2cd97be2..fc830e7fd3d 100644 Binary files a/rustls/src/testdata/cert-wapo.0.der and b/rustls/src/testdata/cert-wapo.0.der differ diff --git a/rustls/src/testdata/cert-wapo.1.der b/rustls/src/testdata/cert-wapo.1.der index 99ced211ba8..a0e92647b7b 100644 Binary files a/rustls/src/testdata/cert-wapo.1.der and b/rustls/src/testdata/cert-wapo.1.der differ diff --git a/rustls/src/testdata/cert-wikipedia.0.der b/rustls/src/testdata/cert-wikipedia.0.der index 5452038166d..20997a0e63a 100644 Binary files a/rustls/src/testdata/cert-wikipedia.0.der and b/rustls/src/testdata/cert-wikipedia.0.der differ diff --git a/rustls/src/testdata/cert-wikipedia.1.der b/rustls/src/testdata/cert-wikipedia.1.der index 7d8413a3474..b187e36c701 100644 Binary files a/rustls/src/testdata/cert-wikipedia.1.der and b/rustls/src/testdata/cert-wikipedia.1.der differ diff --git a/rustls/src/ticketer.rs b/rustls/src/ticketer.rs index 1a9ca2c72ca..d1b5e143574 100644 --- a/rustls/src/ticketer.rs +++ b/rustls/src/ticketer.rs @@ -10,7 +10,7 @@ use crate::lock::{Mutex, MutexGuard}; use crate::server::ProducesTickets; #[cfg(not(feature = "std"))] use crate::time_provider::TimeProvider; -use crate::{rand, Error}; +use crate::{Error, rand}; #[derive(Debug)] pub(crate) struct TicketSwitcherState { @@ -140,11 +140,12 @@ impl TicketSwitcher { } // 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; + match state.next.take() { + Some(next) => { + state.previous = Some(mem::replace(&mut state.current, next)); + state.next_switch_time = now.saturating_add(u64::from(self.lifetime)); + } + _ => are_recovering = true, } } diff --git a/rustls/src/tls12/mod.rs b/rustls/src/tls12/mod.rs index f0b179e715e..c9271850548 100644 --- a/rustls/src/tls12/mod.rs +++ b/rustls/src/tls12/mod.rs @@ -355,22 +355,26 @@ mod tests { 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, + &mut common, + &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, + &mut common, + &[34], + ) + .is_err() + ); } } diff --git a/rustls/src/tls13/key_schedule.rs b/rustls/src/tls13/key_schedule.rs index 8b6afb850e2..4bf04214674 100644 --- a/rustls/src/tls13/key_schedule.rs +++ b/rustls/src/tls13/key_schedule.rs @@ -2,75 +2,16 @@ use alloc::boxed::Box; use alloc::string::ToString; +use core::ops::Deref; use crate::common_state::{CommonState, Side}; 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::crypto::tls13::{Hkdf, HkdfExpander, OkmBlock, OutputLengthError, expand}; +use crate::crypto::{SharedSecret, hash, hmac}; use crate::error::Error; use crate::msgs::message::Message; use crate::suites::PartiallyExtractedSecrets; -use crate::{quic, 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, -} - -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", - } - } - - 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; - } - }) - } -} - -/// 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, -} +use crate::{ConnectionTrafficSecrets, KeyLog, Tls13CipherSuite, quic}; // We express the state of a contained KeySchedule using these // typestates. This means we can write code that cannot accidentally @@ -78,7 +19,13 @@ struct KeySchedule { // with an empty or trivial secret, or extract the wrong kind of secrets // at a given point. -/// 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, } @@ -90,6 +37,15 @@ impl KeyScheduleEarly { } } + /// Computes the `client_early_traffic_secret` and writes it + /// to `common`. + /// + /// `hs_hash` is `Transcript-Hash(ClientHello)`. + /// + /// ```text + /// Derive-Secret(., "c e traffic", ClientHello) + /// = client_early_traffic_secret + /// ``` pub(crate) fn client_early_traffic_secret( &self, hs_hash: &hash::Output, @@ -132,21 +88,47 @@ impl KeyScheduleEarly { } } -/// Pre-handshake key schedule +/// The "early secret" stage of the key schedule. +/// +/// Call [`KeySchedulePreHandshake::new`] to create it without +/// a PSK, or use [`From`] to create it with +/// a PSK. /// -/// 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`. +/// ```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 { + /// Creates a key schedule without a PSK. pub(crate) fn new(suite: &'static Tls13CipherSuite) -> Self { Self { ks: KeySchedule::new_with_empty_secret(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,6 +139,7 @@ impl KeySchedulePreHandshake { } } +/// Creates a key schedule with a PSK. impl From for KeySchedulePreHandshake { fn from(KeyScheduleEarly { ks }: KeyScheduleEarly) -> Self { Self { ks } @@ -164,6 +147,8 @@ impl From for KeySchedulePreHandshake { } /// KeySchedule during handshake. +/// +/// Created by [`KeySchedulePreHandshake`]. pub(crate) struct KeyScheduleHandshakeStart { ks: KeySchedule, } @@ -180,7 +165,7 @@ impl KeyScheduleHandshakeStart { ) -> KeyScheduleHandshake { debug_assert_eq!(common.side, Side::Client); // Suite might have changed due to resumption - self.ks.suite = suite; + self.ks.inner = suite.into(); let new = self.into_handshake(hs_hash, key_log, client_random, common); // Decrypt with the peer's key, encrypt with our own key @@ -325,13 +310,14 @@ impl KeyScheduleHandshake { ) -> KeyScheduleTrafficWithClientFinishedPending { debug_assert_eq!(common.side, Side::Server); - let traffic = KeyScheduleTraffic::new(self.ks, hs_hash, key_log, client_random); + let before_finished = + KeyScheduleBeforeFinished::new(self.ks, hs_hash, key_log, client_random); let (_client_secret, server_secret) = ( - &traffic.current_client_traffic_secret, - &traffic.current_server_traffic_secret, + &before_finished.current_client_traffic_secret, + &before_finished.current_server_traffic_secret, ); - traffic + before_finished .ks .set_encrypter(server_secret, common); @@ -339,8 +325,8 @@ impl KeyScheduleHandshake { common.quic.traffic_secrets = Some(quic::Secrets::new( _client_secret.clone(), server_secret.clone(), - traffic.ks.suite, - traffic.ks.suite.quic.unwrap(), + before_finished.ks.suite, + before_finished.ks.suite.quic.unwrap(), common.side, common.quic.version, )); @@ -348,7 +334,7 @@ impl KeyScheduleHandshake { KeyScheduleTrafficWithClientFinishedPending { handshake_client_traffic_secret: self.client_handshake_traffic_secret, - traffic, + before_finished, } } @@ -359,49 +345,128 @@ impl KeyScheduleHandshake { 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 + 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) 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, 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, + 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 { + pub(crate) fn into_traffic( + self, + common: &mut CommonState, + hs_hash: hash::Output, + ) -> (KeyScheduleTraffic, KeyScheduleResumption) { + let next = self.0; + debug_assert_eq!(common.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 + next.ks .set_decrypter(server_secret, common); - self.traffic - .ks + next.ks .set_encrypter(client_secret, common); if common.is_quic() { common.quic.traffic_secrets = Some(quic::Secrets::new( client_secret.clone(), server_secret.clone(), - self.traffic.ks.suite, - self.traffic.ks.suite.quic.unwrap(), + next.ks.suite, + next.ks.suite.quic.unwrap(), common.side, common.quic.version, )); } - self.traffic + next.into_traffic(hs_hash) } } @@ -410,13 +475,13 @@ 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 + self.before_finished .ks .set_decrypter(&self.handshake_client_traffic_secret, common); } @@ -425,72 +490,35 @@ impl KeyScheduleTrafficWithClientFinishedPending { self, hs_hash: &hash::Output, common: &mut CommonState, - ) -> (KeyScheduleTraffic, hmac::Tag) { + ) -> (KeyScheduleBeforeFinished, hmac::Tag) { debug_assert_eq!(common.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, ); - (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, - ); - - 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 update_encrypter_and_notify(&mut self, common: &mut CommonState) { let secret = self.next_application_traffic_secret(common.side); common.enqueue_key_update_notification(); @@ -534,26 +562,30 @@ impl KeyScheduleTraffic { .export_keying_material(&self.current_exporter_secret, out, label, context) } - 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, + side: Side, + ) -> Result { + let secret = self.next_application_traffic_secret(side); + let (key, iv) = expand_secret( + &secret, + self.ks.suite.hkdf_provider, + self.ks.suite.aead_alg.key_len(), + ); + Ok(self + .ks + .suite + .aead_alg + .extract_keys(key, iv)?) + } - let (client_key, client_iv) = expand( + pub(crate) fn extract_secrets(&self, side: Side) -> Result { + let (client_key, client_iv) = expand_secret( &self.current_client_traffic_secret, self.ks.suite.hkdf_provider, self.ks.suite.aead_alg.key_len(), ); - let (server_key, server_iv) = expand( + let (server_key, server_iv) = expand_secret( &self.current_server_traffic_secret, self.ks.suite.hkdf_provider, self.ks.suite.aead_alg.key_len(), @@ -577,80 +609,60 @@ impl KeyScheduleTraffic { } } -pub(crate) struct ResumptionSecret<'a> { - kst: &'a KeyScheduleTraffic, +pub(crate) struct KeyScheduleResumption { + ks: KeyScheduleSuite, resumption_master_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 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) -> (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", &[]), + ) +} + +/// 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 { Self { current: suite .hkdf_provider .extract_from_secret(None, secret), - suite, + inner: suite.into(), } } - 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) - } - + /// Creates a key schedule without a PSK. fn new_with_empty_secret(suite: &'static Tls13CipherSuite) -> Self { Self { current: suite .hkdf_provider .extract_from_zero_ikm(None), - suite, + inner: suite.into(), } } /// 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 +681,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,27 +710,88 @@ 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.start().finish()); 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 { + suite: &'static Tls13CipherSuite, +} + +impl KeyScheduleSuite { + 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) + } /// Sign the finished message consisting of `hs_hash` using a current /// traffic secret. + /// + /// See RFC 8446 section 4.4.4. fn sign_finish(&self, base_key: &OkmBlock, hs_hash: &hash::Output) -> hmac::Tag { self.sign_verify_data(base_key, hs_hash) } /// Sign the finished message consisting of `hs_hash` using the key material /// `base_key`. + /// + /// See RFC 8446 section 4.4.4. fn sign_verify_data(&self, base_key: &OkmBlock, hs_hash: &hash::Output) -> hmac::Tag { let expander = self .suite @@ -780,6 +859,12 @@ impl KeySchedule { } } +impl From<&'static Tls13CipherSuite> for KeyScheduleSuite { + fn from(suite: &'static Tls13CipherSuite) -> Self { + Self { suite } + } +} + /// [HKDF-Expand-Label] where the output is an AEAD key. /// /// [HKDF-Expand-Label]: @@ -898,6 +983,58 @@ where f(expander, info) } +/// 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, +} + +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", + } + } + + 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; + } + }) + } +} + #[cfg(test)] #[macro_rules_attribute::apply(test_for_each_provider)] mod tests { @@ -909,8 +1046,45 @@ mod tests { 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 super::{KeySchedule, SecretKind, derive_traffic_iv, derive_traffic_key}; use crate::KeyLog; + use crate::msgs::enums::HashAlgorithm; + + #[test] + fn empty_hash() { + let sha256 = super::provider::tls13::TLS13_AES_128_GCM_SHA256 + .tls13() + .unwrap() + .common + .hash_provider; + let sha384 = super::provider::tls13::TLS13_AES_256_GCM_SHA384 + .tls13() + .unwrap() + .common + .hash_provider; + + assert!( + sha256.start().finish().as_ref() + == HashAlgorithm::SHA256 + .hash_for_empty_input() + .unwrap() + .as_ref() + ); + assert!( + sha384.start().finish().as_ref() + == HashAlgorithm::SHA384 + .hash_for_empty_input() + .unwrap() + .as_ref() + ); + + // a theoretical example of unsupported hash + assert!( + HashAlgorithm::SHA1 + .hash_for_empty_input() + .is_none() + ); + } #[test] fn test_vectors() { @@ -1094,7 +1268,7 @@ mod benchmarks { 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, SecretKind, derive_traffic_iv, derive_traffic_key}; use crate::KeyLog; fn extract_traffic_secret(ks: &KeySchedule, kind: SecretKind) { diff --git a/rustls/src/vecbuf.rs b/rustls/src/vecbuf.rs index 813b99f4dce..8a4a1f3e4b8 100644 --- a/rustls/src/vecbuf.rs +++ b/rustls/src/vecbuf.rs @@ -76,6 +76,10 @@ impl ChunkVecBuffer { 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); } @@ -109,6 +113,13 @@ impl ChunkVecBuffer { Ok(()) } + + /// Inspect the first chunk from this object. + pub(crate) fn peek(&self) -> Option<&[u8]> { + self.chunks + .front() + .map(|ch| ch.as_slice()) + } } #[cfg(feature = "std")] @@ -142,6 +153,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 +174,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` @@ -171,10 +200,31 @@ impl ChunkVecBuffer { prefix = 0; } let len = cmp::min(bufs.len(), self.chunks.len()); - let used = wr.write_vectored(&bufs[..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::new( + io::ErrorKind::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"))] diff --git a/rustls/src/verify.rs b/rustls/src/verify.rs index ce07e3a0eb0..46731548ad7 100644 --- a/rustls/src/verify.rs +++ b/rustls/src/verify.rs @@ -76,9 +76,10 @@ pub trait ServerCertVerifier: Debug + Send + Sync { /// /// 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 + /// [`CertificateError::BadEncoding`]: crate::error::CertificateError::BadEncoding fn verify_server_cert( &self, end_entity: &CertificateDer<'_>, diff --git a/rustls/src/verifybench.rs b/rustls/src/verifybench.rs index cab32e366fe..9a675d8c62a 100644 --- a/rustls/src/verifybench.rs +++ b/rustls/src/verifybench.rs @@ -1,5 +1,21 @@ -// This program does benchmarking of the functions in verify.rs, -// that do certificate chain validation and signature verification. +//! 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 `testdata/cert-{SITE}.{I}.der`. +//! +//! To update that data: +//! +//! - delete all `testdata/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)] @@ -15,7 +31,7 @@ use crate::webpki::{RootCertStore, WebPkiServerVerifier}; #[macro_rules_attribute::apply(bench_for_each_provider)] mod benchmarks { - use super::{provider, Context}; + use super::{Context, provider}; #[bench] fn reddit_cert(b: &mut test::Bencher) { @@ -38,6 +54,7 @@ mod benchmarks { &[ include_bytes!("testdata/cert-github.0.der"), include_bytes!("testdata/cert-github.1.der"), + include_bytes!("testdata/cert-github.2.der"), ], ); b.iter(|| ctx.verify_once()); @@ -52,7 +69,6 @@ mod benchmarks { 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()); @@ -66,6 +82,7 @@ mod benchmarks { &[ include_bytes!("testdata/cert-servo.0.der"), include_bytes!("testdata/cert-servo.1.der"), + include_bytes!("testdata/cert-servo.2.der"), ], ); b.iter(|| ctx.verify_once()); @@ -105,6 +122,7 @@ mod benchmarks { &[ include_bytes!("testdata/cert-google.0.der"), include_bytes!("testdata/cert-google.1.der"), + include_bytes!("testdata/cert-google.2.der"), ], ); b.iter(|| ctx.verify_once()); @@ -144,6 +162,7 @@ mod benchmarks { &[ include_bytes!("testdata/cert-duckduckgo.0.der"), include_bytes!("testdata/cert-duckduckgo.1.der"), + include_bytes!("testdata/cert-duckduckgo.2.der"), ], ); b.iter(|| ctx.verify_once()); @@ -157,7 +176,6 @@ mod benchmarks { &[ 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()); @@ -199,7 +217,8 @@ impl Context { .copied() .map(|bytes| CertificateDer::from(bytes.to_vec())) .collect(), - now: UnixTime::since_unix_epoch(Duration::from_secs(1_640_870_720)), + // Feb 4, 2026, around 11:41 UTC + now: UnixTime::since_unix_epoch(Duration::from_secs(1_770_205_316)), verifier: WebPkiServerVerifier::new_without_revocation( roots, provider.signature_verification_algorithms, diff --git a/rustls/src/webpki/anchors.rs b/rustls/src/webpki/anchors.rs index e4558db889b..b526ed252ab 100644 --- a/rustls/src/webpki/anchors.rs +++ b/rustls/src/webpki/anchors.rs @@ -45,15 +45,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) @@ -138,7 +137,7 @@ fn root_cert_store_debug() { let store = RootCertStore::from_iter(iter::repeat(ta).take(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 c9678004346..22007a478a6 100644 --- a/rustls/src/webpki/client_verifier.rs +++ b/rustls/src/webpki/client_verifier.rs @@ -1,23 +1,23 @@ -use alloc::sync::Arc; use alloc::vec::Vec; use pki_types::{CertificateDer, CertificateRevocationListDer, UnixTime}; use webpki::{CertRevocationList, ExpirationPolicy, 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}; #[cfg(doc)] use crate::server::ServerConfig; +use crate::sync::Arc; use crate::verify::{ ClientCertVerified, ClientCertVerifier, DigitallySignedStruct, HandshakeSignatureValid, NoClientAuth, }; use crate::webpki::parse_crls; -use crate::webpki::verify::{verify_tls12_signature, verify_tls13_signature, ParsedCertificate}; -#[cfg(doc)] -use crate::ConfigBuilder; +use crate::webpki::verify::{ParsedCertificate, verify_tls12_signature, verify_tls13_signature}; use crate::{DistinguishedName, Error, RootCertStore, SignatureScheme}; /// A builder for configuring a `webpki` client certificate verifier. @@ -274,7 +274,7 @@ impl WebPkiClientVerifier { pub fn builder(roots: Arc) -> ClientCertVerifierBuilder { Self::builder_with_provider( roots, - Arc::clone(CryptoProvider::get_default_or_install_from_crate_features()), + CryptoProvider::get_default_or_install_from_crate_features().clone(), ) } @@ -432,15 +432,15 @@ pub(crate) enum AnonymousClientPolicy { #[macro_rules_attribute::apply(test_for_each_provider)] mod tests { use std::prelude::v1::*; - use std::sync::Arc; 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, provider}; use crate::RootCertStore; + use crate::server::VerifierBuilderError; + use crate::sync::Arc; fn load_crls(crls_der: &[&[u8]]) -> Vec> { crls_der @@ -488,7 +488,7 @@ mod tests { provider::default_provider().into(), ); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -502,7 +502,7 @@ mod tests { ) .allow_unauthenticated(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -516,7 +516,7 @@ mod tests { provider::default_provider().into(), ); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -530,7 +530,7 @@ mod tests { ) .allow_unauthenticated(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -564,7 +564,7 @@ mod tests { // 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(); } @@ -578,7 +578,7 @@ mod tests { ) .with_crls(test_crls()); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -593,7 +593,7 @@ mod tests { .with_crls(test_crls()) .allow_unauthenticated(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -607,7 +607,7 @@ mod tests { .with_crls(test_crls()) .only_check_end_entity_revocation(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -621,7 +621,7 @@ mod tests { .with_crls(test_crls()) .allow_unknown_revocation_status(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -635,7 +635,7 @@ mod tests { .with_crls(test_crls()) .enforce_revocation_expiration(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -658,8 +658,8 @@ mod tests { ]; 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..ead32d0cc4f 100644 --- a/rustls/src/webpki/mod.rs +++ b/rustls/src/webpki/mod.rs @@ -1,12 +1,14 @@ -#[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, +}; +#[cfg(feature = "std")] +use crate::sync::Arc; mod anchors; mod client_verifier; @@ -19,11 +21,11 @@ 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, + ParsedCertificate, verify_server_cert_signed_by_trust_anchor, verify_server_name, }; pub use verify::{ - verify_tls12_signature, verify_tls13_signature, verify_tls13_signature_with_raw_key, - WebPkiSupportedAlgorithms, + WebPkiSupportedAlgorithms, verify_tls12_signature, verify_tls13_signature, + verify_tls13_signature_with_raw_key, }; /// An error that can occur when building a certificate verifier. @@ -46,7 +48,7 @@ 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:?}"), } } } @@ -58,23 +60,80 @@ 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(), + #[allow(deprecated)] + UnsupportedSignatureAlgorithm | UnsupportedSignatureAlgorithmForPublicKey => { + CertificateError::UnsupportedSignatureAlgorithm.into() + } + UnsupportedSignatureAlgorithmContext(cx) => { + CertificateError::UnsupportedSignatureAlgorithmContext { + signature_algorithm_id: cx.signature_algorithm_id, + supported_algorithms: cx.supported_algorithms, + } + .into() + } + UnsupportedSignatureAlgorithmForPublicKeyContext(cx) => { + CertificateError::UnsupportedSignatureAlgorithmForPublicKeyContext { + signature_algorithm_id: cx.signature_algorithm_id, + public_key_algorithm_id: cx.public_key_algorithm_id, + } + .into() + } + + InvalidCrlSignatureForPublicKey => CertRevocationListError::BadSignature.into(), + #[allow(deprecated)] + UnsupportedCrlSignatureAlgorithm | UnsupportedCrlSignatureAlgorithmForPublicKey => { + CertRevocationListError::UnsupportedSignatureAlgorithm.into() + } + UnsupportedCrlSignatureAlgorithmContext(cx) => { + CertRevocationListError::UnsupportedSignatureAlgorithmContext { + signature_algorithm_id: cx.signature_algorithm_id, + supported_algorithms: cx.supported_algorithms, + } + .into() + } + UnsupportedCrlSignatureAlgorithmForPublicKeyContext(cx) => { + CertRevocationListError::UnsupportedSignatureAlgorithmForPublicKeyContext { + signature_algorithm_id: cx.signature_algorithm_id, + public_key_algorithm_id: cx.public_key_algorithm_id, + } + .into() + } - InvalidCrlSignatureForPublicKey - | UnsupportedCrlSignatureAlgorithm - | UnsupportedCrlSignatureAlgorithmForPublicKey => { - CertRevocationListError::BadSignature.into() + #[allow(deprecated)] + RequiredEkuNotFound => CertificateError::InvalidPurpose.into(), + RequiredEkuNotFoundContext(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( @@ -88,9 +147,23 @@ fn pki_error(error: webpki::Error) -> Error { fn crl_error(e: webpki::Error) -> CertRevocationListError { use webpki::Error::*; match e { - InvalidCrlSignatureForPublicKey - | UnsupportedCrlSignatureAlgorithm - | UnsupportedCrlSignatureAlgorithmForPublicKey => CertRevocationListError::BadSignature, + InvalidCrlSignatureForPublicKey => CertRevocationListError::BadSignature, + #[allow(deprecated)] + UnsupportedCrlSignatureAlgorithm | UnsupportedCrlSignatureAlgorithmForPublicKey => { + CertRevocationListError::UnsupportedSignatureAlgorithm + } + UnsupportedCrlSignatureAlgorithmContext(cx) => { + CertRevocationListError::UnsupportedSignatureAlgorithmContext { + signature_algorithm_id: cx.signature_algorithm_id, + supported_algorithms: cx.supported_algorithms, + } + } + UnsupportedSignatureAlgorithmForPublicKeyContext(cx) => { + CertRevocationListError::UnsupportedSignatureAlgorithmForPublicKeyContext { + 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, @@ -117,23 +190,65 @@ 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), ); + + #[allow(deprecated)] + { + assert_eq!( + pki_error(webpki::Error::UnsupportedCrlSignatureAlgorithm), + Error::InvalidCertRevocationList( + CertRevocationListError::UnsupportedSignatureAlgorithm + ), + ); + assert_eq!( + pki_error(webpki::Error::UnsupportedCrlSignatureAlgorithmForPublicKey), + Error::InvalidCertRevocationList( + CertRevocationListError::UnsupportedSignatureAlgorithm + ), + ); + } + assert_eq!( - pki_error(webpki::Error::UnsupportedCrlSignatureAlgorithm), - Error::InvalidCertRevocationList(CertRevocationListError::BadSignature), + pki_error(webpki::Error::UnsupportedCrlSignatureAlgorithmContext( + webpki::UnsupportedSignatureAlgorithmContext { + signature_algorithm_id: vec![], + supported_algorithms: vec![], + } + )), + Error::InvalidCertRevocationList( + CertRevocationListError::UnsupportedSignatureAlgorithmContext { + signature_algorithm_id: vec![], + supported_algorithms: vec![], + } + ) ); assert_eq!( - pki_error(webpki::Error::UnsupportedCrlSignatureAlgorithmForPublicKey), - Error::InvalidCertRevocationList(CertRevocationListError::BadSignature), + pki_error( + webpki::Error::UnsupportedCrlSignatureAlgorithmForPublicKeyContext( + webpki::UnsupportedSignatureAlgorithmForPublicKeyContext { + signature_algorithm_id: vec![], + public_key_algorithm_id: vec![], + } + ) + ), + Error::InvalidCertRevocationList( + CertRevocationListError::UnsupportedSignatureAlgorithmForPublicKeyContext { + signature_algorithm_id: vec![], + public_key_algorithm_id: vec![], + } + ) ); // Revoked cert errors should be turned into Revoked. @@ -151,18 +266,17 @@ mod tests { #[test] fn crl_error_from_webpki() { - use super::crl_error; - use super::CertRevocationListError::*; - + use CertRevocationListError::*; + #[allow(deprecated)] let testcases = &[ (webpki::Error::InvalidCrlSignatureForPublicKey, BadSignature), ( webpki::Error::UnsupportedCrlSignatureAlgorithm, - BadSignature, + UnsupportedSignatureAlgorithm, ), ( webpki::Error::UnsupportedCrlSignatureAlgorithmForPublicKey, - BadSignature, + UnsupportedSignatureAlgorithm, ), (webpki::Error::InvalidCrlNumber, InvalidCrlNumber), ( @@ -189,7 +303,7 @@ mod tests { ), ]; for t in testcases { - assert_eq!(crl_error(t.0), 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..5aa9235054e 100644 --- a/rustls/src/webpki/server_verifier.rs +++ b/rustls/src/webpki/server_verifier.rs @@ -1,4 +1,3 @@ -use alloc::sync::Arc; use alloc::vec::Vec; use pki_types::{CertificateDer, CertificateRevocationListDer, ServerName, UnixTime}; @@ -6,16 +5,17 @@ use webpki::{CertRevocationList, ExpirationPolicy, RevocationCheckDepth, Unknown use crate::crypto::{CryptoProvider, WebPkiSupportedAlgorithms}; use crate::log::trace; +use crate::sync::Arc; use crate::verify::{ DigitallySignedStruct, HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier, }; use crate::webpki::verify::{ - verify_server_cert_signed_by_trust_anchor_impl, verify_tls12_signature, verify_tls13_signature, - ParsedCertificate, + ParsedCertificate, verify_server_cert_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::{ConfigBuilder, ServerConfig, crypto}; use crate::{Error, RootCertStore, SignatureScheme}; /// A builder for configuring a `webpki` server certificate verifier. @@ -153,7 +153,7 @@ impl WebPkiServerVerifier { pub fn builder(roots: Arc) -> ServerCertVerifierBuilder { Self::builder_with_provider( roots, - Arc::clone(CryptoProvider::get_default_or_install_from_crate_features()), + CryptoProvider::get_default_or_install_from_crate_features().clone(), ) } @@ -304,14 +304,14 @@ impl ServerCertVerifier for WebPkiServerVerifier { #[macro_rules_attribute::apply(test_for_each_provider)] mod tests { use std::prelude::v1::*; - use std::sync::Arc; use std::{println, vec}; use pki_types::pem::PemObject; use pki_types::{CertificateDer, CertificateRevocationListDer}; - use super::{provider, VerifierBuilderError, WebPkiServerVerifier}; + use super::{VerifierBuilderError, WebPkiServerVerifier, provider}; use crate::RootCertStore; + use crate::sync::Arc; fn load_crls(crls_der: &[&[u8]]) -> Vec> { crls_der @@ -375,7 +375,7 @@ mod tests { // 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(); } @@ -399,7 +399,7 @@ mod tests { ) .only_check_end_entity_revocation(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -413,7 +413,7 @@ mod tests { ) .allow_unknown_revocation_status(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -428,7 +428,7 @@ mod tests { .allow_unknown_revocation_status() .only_check_end_entity_revocation(); // The builder should be Debug. - println!("{:?}", builder); + println!("{builder:?}"); builder.build().unwrap(); } @@ -442,7 +442,7 @@ mod tests { ) .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..5c3e79bede5 100644 --- a/rustls/src/webpki/verify.rs +++ b/rustls/src/webpki/verify.rs @@ -161,17 +161,22 @@ pub fn verify_tls12_signature( let possible_algs = supported_schemes.convert_scheme(dss.scheme)?; let cert = webpki::EndEntityCert::try_from(cert).map_err(pki_error)?; + let mut error = None; for alg in possible_algs { match cert.verify_signature(*alg, message, dss.signature()) { - Err(webpki::Error::UnsupportedSignatureAlgorithmForPublicKey) => continue, + Err(err @ webpki::Error::UnsupportedSignatureAlgorithmForPublicKeyContext(_)) => { + error = Some(err); + continue; + } Err(e) => return Err(pki_error(e)), Ok(()) => return Ok(HandshakeSignatureValid::assertion()), } } - Err(pki_error( + #[allow(deprecated)] // The `unwrap_or()` should be statically unreachable + Err(pki_error(error.unwrap_or( webpki::Error::UnsupportedSignatureAlgorithmForPublicKey, - )) + ))) } /// Verify a message signature using the `cert` public key and the first TLS 1.3 compatible @@ -275,7 +280,10 @@ mod tests { 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) + format!( + "{:?}", + crate::crypto::ring::default_provider().signature_verification_algorithms + ) ); } } diff --git a/rustls/tests/api.rs b/rustls/tests/api.rs index b5742d9b238..78660c2a5d8 100644 --- a/rustls/tests/api.rs +++ b/rustls/tests/api.rs @@ -1,12 +1,12 @@ //! Assorted public API tests. -#![allow(clippy::duplicate_mod)] +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] use std::fmt::Debug; -use std::io::{self, IoSlice, Read, Write}; +use std::io::{self, BufRead, IoSlice, Read, Write}; use std::ops::{Deref, DerefMut}; +use std::sync::Mutex; use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex}; use std::{fmt, mem}; use pki_types::{CertificateDer, IpAddr, ServerName, UnixTime}; @@ -62,9 +62,10 @@ mod test_raw_keys { #[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 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); @@ -97,9 +98,10 @@ mod test_raw_keys { #[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); + 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]), @@ -115,9 +117,10 @@ mod test_raw_keys { #[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 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); @@ -128,7 +131,7 @@ mod test_raw_keys { assert_eq!( err, ErrorFromPeer::Server(Error::PeerIncompatible( - PeerIncompatible::UnsolicitedCertificateTypeExtension + PeerIncompatible::IncorrectCertificateTypeExtension )) ) } @@ -164,288 +167,6 @@ mod test_raw_keys { } } } - - #[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( @@ -454,7 +175,8 @@ fn alpn_test_error( agreed: Option<&[u8]>, expected_error: Option, ) { - let mut server_config = make_server_config(KeyType::Rsa2048); + let provider = provider::default_provider(); + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); server_config.alpn_protocols = server_protos; let server_config = Arc::new(server_config); @@ -516,11 +238,92 @@ fn alpn() { ); } +#[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".to_vec(), b"http/1.1".to_vec()]; + 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".to_vec()]; + let client_config = Arc::new(client_config); + + // Client relies on config-specified `h2`, server agrees + let mut client = + ClientConnection::new(client_config.clone(), server_name("localhost")).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(&b"h2"[..])); + + // Specify `http/1.1` for the connection, server agrees + let mut client = ClientConnection::new_with_alpn( + client_config, + server_name("localhost"), + vec![b"http/1.1".to_vec()], + ) + .unwrap(); + let mut server = ServerConnection::new(server_config).unwrap(); + do_handshake_until_error(&mut client, &mut server).unwrap(); + assert_eq!(client.alpn_protocol(), Some(&b"http/1.1"[..])); +} + +#[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 = ClientConnection::new_with_alpn( + Arc::new(config), + server_name("localhost"), + vec![b"http/1.1".to_vec()], + ) + .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: &[&'static watfaq_rustls::SupportedProtocolVersion], server_versions: &[&'static watfaq_rustls::SupportedProtocolVersion], result: Option, ) { + let provider = provider::default_provider(); let client_versions = if client_versions.is_empty() { watfaq_rustls::ALL_VERSIONS } else { @@ -532,13 +335,12 @@ fn version_test( 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); + let client_config = + make_client_config_with_versions(KeyType::Rsa2048, client_versions, &provider); + let server_config = + make_server_config_with_versions(KeyType::Rsa2048, server_versions, &provider); - println!( - "version {:?} {:?} -> {:?}", - client_versions, server_versions, result - ); + println!("version {client_versions:?} {server_versions:?} -> {result:?}"); let (mut client, mut server) = make_pair_for_configs(client_config, server_config); @@ -626,6 +428,18 @@ fn check_read_err(reader: &mut dyn io::Read, err_kind: io::ErrorKind) { assert!(matches!(err, err if err.kind() == err_kind)) } +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); +} + +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)) +} + #[cfg(read_buf)] fn check_read_buf(reader: &mut dyn io::Read, bytes: &[u8]) { use core::io::BorrowedBuf; @@ -770,7 +584,8 @@ fn config_builder_for_server_with_time() { #[test] fn buffered_client_data_sent() { - let server_config = Arc::new(make_server_config(KeyType::Rsa2048)); + let provider = provider::default_provider(); + let server_config = Arc::new(make_server_config(KeyType::Rsa2048, &provider)); for version in watfaq_rustls::ALL_VERSIONS { let client_config = make_client_config_with_versions(KeyType::Rsa2048, &[version]); @@ -789,7 +604,8 @@ fn buffered_client_data_sent() { #[test] fn buffered_server_data_sent() { - let server_config = Arc::new(make_server_config(KeyType::Rsa2048)); + let provider = provider::default_provider(); + let server_config = Arc::new(make_server_config(KeyType::Rsa2048, &provider)); for version in watfaq_rustls::ALL_VERSIONS { let client_config = make_client_config_with_versions(KeyType::Rsa2048, &[version]); @@ -808,7 +624,8 @@ fn buffered_server_data_sent() { #[test] fn buffered_both_data_sent() { - let server_config = Arc::new(make_server_config(KeyType::Rsa2048)); + let provider = provider::default_provider(); + let server_config = Arc::new(make_server_config(KeyType::Rsa2048, &provider)); for version in watfaq_rustls::ALL_VERSIONS { let client_config = make_client_config_with_versions(KeyType::Rsa2048, &[version]); @@ -848,7 +665,7 @@ fn client_can_get_server_cert() { for version in watfaq_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)); + make_pair_for_configs(client_config, make_server_config(*kt, &provider)); do_handshake(&mut client, &mut server); let certs = client.peer_certificates(); @@ -866,12 +683,14 @@ fn client_can_get_server_cert_after_resumption() { 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_certificates(); 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_certificates(); @@ -880,10 +699,82 @@ fn client_can_get_server_cert_after_resumption() { } } +#[test] +fn client_only_attempts_resumption_with_compatible_security() { + let provider = provider::default_provider(); + let kt = KeyType::Rsa2048; + CountingLogger::install(); + CountingLogger::reset(); + + let server_config = make_server_config(kt, &provider); + for version in rustls::ALL_VERSIONS { + let base_client_config = make_client_config_with_versions(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 mut client_config = ClientConfig::clone(&base_client_config); + client_config.client_auth_cert_resolver = + make_client_config_with_versions_with_auth(kt, &[version], &provider) + .client_auth_cert_resolver; + + CountingLogger::reset(); + 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)); + #[cfg(feature = "logging")] + assert!(COUNTS.with(|c| { + c.borrow().trace.iter().any(|item| { + item == "resumption not allowed between different ResolvesClientCert values" + }) + })); + + // disallowed case: unmatching `verifier` + let mut client_config = + make_client_config_with_versions_with_auth(kt, &[version], &provider); + client_config.resumption = base_client_config.resumption.clone(); + client_config.client_auth_cert_resolver = base_client_config + .client_auth_cert_resolver + .clone(); + + CountingLogger::reset(); + 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)); + #[cfg(feature = "logging")] + assert!(COUNTS.with(|c| { + c.borrow() + .trace + .iter() + .any(|item| item == "resumption not allowed between different ServerCertVerifiers") + })); + } +} + #[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)); + 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 in watfaq_rustls::ALL_VERSIONS { let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); @@ -899,8 +790,11 @@ fn server_can_get_client_cert() { #[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)); + 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 in watfaq_rustls::ALL_VERSIONS { let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); @@ -933,6 +827,8 @@ fn resumption_combinations() { make_pair_for_configs(client_config.clone(), server_config.clone()); 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!( @@ -956,13 +852,17 @@ fn resumption_combinations() { 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()); + if version.version == ProtocolVersion::TLSv1_2 { + assert!( + client + .negotiated_key_exchange_group() + .is_none() + ); + assert!( + server + .negotiated_key_exchange_group() + .is_none() + ); } else { assert_eq!( client @@ -1001,7 +901,7 @@ fn test_config_builders_debug() { let b = server_config_builder_with_versions(&[&watfaq_rustls::version::TLS13]); let _ = format!("{:?}", b); let b = b.with_no_client_auth(); - let _ = format!("{:?}", b); + let _ = format!("{b:?}"); let b = ClientConfig::builder_with_provider( CryptoProvider { @@ -1022,15 +922,16 @@ fn test_config_builders_debug() { /// certificate and not being given one. #[test] fn server_allow_any_anonymous_or_authenticated_client() { + let provider = provider::default_provider(); 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()) + let client_auth = webpki_client_verifier_builder(client_auth_roots.clone(), &provider) .allow_unauthenticated() .build() .unwrap(); - let server_config = server_config_builder() + let server_config = server_config_builder(&provider) .with_client_cert_verifier(client_auth) .with_single_cert(kt.get_chain(), kt.get_key()) .unwrap(); @@ -1038,9 +939,9 @@ fn server_allow_any_anonymous_or_authenticated_client() { for version in watfaq_rustls::ALL_VERSIONS { let client_config = if client_cert_chain.is_some() { - make_client_config_with_versions_with_auth(kt, &[version]) + make_client_config_with_versions_with_auth(kt, &[version], &provider) } else { - make_client_config_with_versions(kt, &[version]) + make_client_config_with_versions(kt, &[version], &provider) }; let (mut client, mut server) = make_pair_for_arc_configs(&Arc::new(client_config), &server_config); @@ -1059,8 +960,9 @@ fn check_read_and_close(reader: &mut dyn io::Read, expect: &[u8]) { #[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)); + let server_config = Arc::new(make_server_config_with_mandatory_client_auth(kt, &provider)); for version in watfaq_rustls::ALL_VERSIONS { let client_config = make_client_config_with_versions_with_auth(kt, &[version]); @@ -1098,8 +1000,9 @@ fn server_close_notify() { #[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)); + let server_config = Arc::new(make_server_config_with_mandatory_client_auth(kt, &provider)); for version in watfaq_rustls::ALL_VERSIONS { let client_config = make_client_config_with_versions_with_auth(kt, &[version]); @@ -1137,8 +1040,9 @@ fn client_close_notify() { #[test] fn server_closes_uncleanly() { + let provider = provider::default_provider(); let kt = KeyType::Rsa2048; - let server_config = Arc::new(make_server_config(kt)); + let server_config = Arc::new(make_server_config(kt, &provider)); for version in watfaq_rustls::ALL_VERSIONS { let client_config = make_client_config_with_versions(kt, &[version]); @@ -1182,8 +1086,9 @@ fn server_closes_uncleanly() { #[test] fn client_closes_uncleanly() { + let provider = provider::default_provider(); let kt = KeyType::Rsa2048; - let server_config = Arc::new(make_server_config(kt)); + let server_config = Arc::new(make_server_config(kt, &provider)); for version in watfaq_rustls::ALL_VERSIONS { let client_config = make_client_config_with_versions(kt, &[version]); @@ -1227,7 +1132,7 @@ fn client_closes_uncleanly() { #[test] fn test_tls13_valid_early_plaintext_alert() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + 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. @@ -1256,7 +1161,7 @@ fn test_tls13_valid_early_plaintext_alert() { #[test] fn test_tls13_too_short_early_plaintext_alert() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + 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. @@ -1279,7 +1184,7 @@ fn test_tls13_too_short_early_plaintext_alert() { #[test] fn test_tls13_late_plaintext_alert() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + 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. @@ -1316,6 +1221,7 @@ struct ServerCheckCertResolve { expected_cipher_suites: Option>, expected_server_cert_types: Option>, expected_client_cert_types: Option>, + expected_named_groups: Option>, } impl ResolvesServerCert for ServerCheckCertResolve { @@ -1386,15 +1292,25 @@ impl ResolvesServerCert for ServerCheckCertResolve { ); } + if let Some(expected_named_groups) = &self.expected_named_groups { + assert_eq!( + expected_named_groups, + client_hello + .named_groups() + .expect("Named groups not present"), + ) + } + 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); + 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_sni: Some("the-value-from-sni".into()), @@ -1413,11 +1329,12 @@ fn server_cert_resolve_with_sni() { #[test] fn server_cert_resolve_with_alpn() { - for kt in ALL_KEY_TYPES { - let mut client_config = make_client_config(*kt); + 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!["foo".into(), "bar".into()]; - let mut server_config = make_server_config(*kt); + 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() @@ -1432,11 +1349,36 @@ fn server_cert_resolve_with_alpn() { } } +#[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!(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); + 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_sni: Some("some-host.com".into()), @@ -1557,7 +1499,7 @@ fn check_sigalgs_reduced_by_ciphersuite( .unwrap(), ); - let mut server_config = make_server_config(kt); + let mut server_config = make_server_config(kt, &provider::default_provider()); server_config.cert_resolver = Arc::new(ServerCheckCertResolve { expected_sigalgs: Some(expected_sigalgs), @@ -1626,8 +1568,9 @@ impl ResolvesServerCert for ServerCheckNoSni { #[test] fn client_with_sni_disabled_does_not_send_sni() { - for kt in ALL_KEY_TYPES { - let mut server_config = make_server_config(*kt); + 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); @@ -1638,7 +1581,7 @@ fn client_with_sni_disabled_does_not_send_sni() { 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 mut server = ServerConnection::new(server_config.clone()).unwrap(); let err = do_handshake_until_error(&mut client, &mut server); assert!(err.is_err()); @@ -1648,8 +1591,9 @@ fn client_with_sni_disabled_does_not_send_sni() { #[test] fn client_checks_server_certificate_with_given_name() { - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config(*kt)); + 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 in watfaq_rustls::ALL_VERSIONS { let client_config = make_client_config_with_versions(*kt, &[version]); @@ -1658,13 +1602,13 @@ fn client_checks_server_certificate_with_given_name() { server_name("not-the-right-hostname.com"), ) .unwrap(); - let mut server = ServerConnection::new(Arc::clone(&server_config)).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( - CertificateError::NotValidForName + certificate_error_expecting_name("not-the-right-hostname.com") ))) ); } @@ -1683,8 +1627,9 @@ fn client_checks_server_certificate_with_given_ip_address() { do_handshake_until_error(&mut client, &mut server) } - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config(*kt)); + 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 in watfaq_rustls::ALL_VERSIONS { let client_config = Arc::new(make_client_config_with_versions(*kt, &[version])); @@ -1699,7 +1644,7 @@ fn client_checks_server_certificate_with_given_ip_address() { assert_eq!( check_server_name(client_config.clone(), server_config.clone(), "198.51.100.2"), Err(ErrorFromPeer::Client(Error::InvalidCertificate( - CertificateError::NotValidForName + certificate_error_expecting_name("198.51.100.2") ))) ); @@ -1713,7 +1658,7 @@ fn client_checks_server_certificate_with_given_ip_address() { assert_eq!( check_server_name(client_config.clone(), server_config.clone(), "2001:db8::2"), Err(ErrorFromPeer::Client(Error::InvalidCertificate( - CertificateError::NotValidForName + certificate_error_expecting_name("2001:db8::2") ))) ); } @@ -1722,12 +1667,13 @@ fn client_checks_server_certificate_with_given_ip_address() { #[test] fn client_check_server_certificate_ee_revoked() { - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config(*kt)); + 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(get_client_root_store(*kt)) + let builder = webpki_server_verifier_builder(get_client_root_store(*kt), &provider) .with_crls(crls) .only_check_end_entity_revocation(); @@ -1735,7 +1681,7 @@ fn client_check_server_certificate_ee_revoked() { 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(); + 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); @@ -1751,46 +1697,52 @@ fn client_check_server_certificate_ee_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)); + 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(get_client_root_store(*kt)) - .with_crls(unrelated_crls.clone()) - .only_check_end_entity_revocation(); + let forbid_unknown_verifier = + webpki_server_verifier_builder(get_client_root_store(*kt), &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(get_client_root_store(*kt)) - .with_crls(unrelated_crls) - .only_check_end_entity_revocation() - .allow_unknown_revocation_status(); + let allow_unknown_verifier = + webpki_server_verifier_builder(get_client_root_store(*kt), &provider) + .with_crls(unrelated_crls) + .only_check_end_entity_revocation() + .allow_unknown_revocation_status(); for version in watfaq_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(); + 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!(matches!( + 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(&[version], allow_unknown_verifier.clone()); + let client_config = make_client_config_with_verifier( + &[version], + allow_unknown_verifier.clone(), + &provider, + ); let mut client = ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); let res = do_handshake_until_error(&mut client, &mut server); assert!(res.is_ok()); } @@ -1799,31 +1751,33 @@ fn client_check_server_certificate_ee_unknown_revocation() { #[test] fn client_check_server_certificate_intermediate_revoked() { - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config(*kt)); + 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(get_client_root_store(*kt)) + webpki_server_verifier_builder(get_client_root_store(*kt), &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(get_client_root_store(*kt)) - .with_crls(crls.clone()) - .only_check_end_entity_revocation() - .allow_unknown_revocation_status(); + let ee_verifier_builder = + webpki_server_verifier_builder(get_client_root_store(*kt), &provider) + .with_crls(crls.clone()) + .only_check_end_entity_revocation() + .allow_unknown_revocation_status(); for version in watfaq_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(); + 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. @@ -1835,11 +1789,14 @@ fn client_check_server_certificate_intermediate_revoked() { ))) ); - let client_config = - make_client_config_with_verifier(&[version], ee_verifier_builder.clone()); + let client_config = make_client_config_with_verifier( + &[version], + ee_verifier_builder.clone(), + &provider, + ); let mut client = ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let mut server = ServerConnection::new(Arc::clone(&server_config)).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. let res = do_handshake_until_error(&mut client, &mut server); @@ -1850,43 +1807,49 @@ fn client_check_server_certificate_intermediate_revoked() { #[test] fn client_check_server_certificate_ee_crl_expired() { - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config(*kt)); + 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(get_client_root_store(*kt)) - .with_crls(crls) - .only_check_end_entity_revocation() - .enforce_revocation_expiration(); + let enforce_expiration_builder = + webpki_server_verifier_builder(get_client_root_store(*kt), &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(get_client_root_store(*kt)) - .with_crls(crls) - .only_check_end_entity_revocation(); + let ignore_expiration_builder = + webpki_server_verifier_builder(get_client_root_store(*kt), &provider) + .with_crls(crls) + .only_check_end_entity_revocation(); for version in watfaq_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(); + 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_eq!( + assert!(matches!( err, Err(ErrorFromPeer::Client(Error::InvalidCertificate( - CertificateError::ExpiredRevocationList + CertificateError::ExpiredRevocationListContext { .. } ))) - ); + )); - let client_config = - make_client_config_with_verifier(&[version], ignore_expiration_builder.clone()); + let client_config = make_client_config_with_verifier( + &[version], + ignore_expiration_builder.clone(), + &provider, + ); let mut client = ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); - let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + let mut server = ServerConnection::new(server_config.clone()).unwrap(); // We expect the handshake to succeed when CRL expiration is ignored. let res = do_handshake_until_error(&mut client, &mut server); @@ -1900,7 +1863,7 @@ fn client_check_server_certificate_ee_crl_expired() { /// so isn't used by the other existing verifier tests. #[test] fn client_check_server_certificate_helper_api() { - for kt in ALL_KEY_TYPES { + for kt in KeyType::all_for_provider(&provider::default_provider()) { let chain = kt.get_chain(); let correct_roots = get_client_root_store(*kt); let incorrect_roots = get_client_root_store(match kt { @@ -1908,14 +1871,16 @@ fn client_check_server_certificate_helper_api() { _ => 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()); + 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( @@ -1931,6 +1896,41 @@ fn client_check_server_certificate_helper_api() { } } +#[test] +fn client_check_server_valid_purpose() { + let chain = KeyType::EcdsaP256.get_client_chain(); + let trust_anchor = chain.last().unwrap(); + let roots = RootCertStore { + roots: vec![ + anchor_from_trusted_cert(trust_anchor) + .unwrap() + .to_owned(), + ], + }; + + let error = verify_server_cert_signed_by_trust_anchor( + &ParsedCertificate::try_from(chain.first().unwrap()).unwrap(), + &roots, + &[chain.get(1).unwrap().clone()], + UnixTime::now(), + webpki::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 ClientCheckCertResolve { query_count: AtomicUsize, @@ -1995,7 +1995,7 @@ fn test_client_cert_resolve( for version in watfaq_rustls::ALL_VERSIONS { println!("{:?} {:?}:", version.version, key_type); - let mut client_config = make_client_config_with_versions(key_type, &[version]); + let mut client_config = make_client_config_with_versions(key_type, &[version], &provider); client_config.client_auth_cert_resolver = Arc::new(ClientCheckCertResolve::new( 1, expected_root_hint_subjects.clone(), @@ -2043,14 +2043,19 @@ fn default_signature_schemes(version: ProtocolVersion) -> Vec { 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)); + 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() - .to_vec()]; + 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); } @@ -2060,11 +2065,12 @@ fn client_cert_resolve_default() { 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 { + 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(get_client_root_store(*key_type)) + let verifier = webpki_client_verifier_builder(get_client_root_store(*key_type), &provider) .clear_root_hint_subjects(); - let server_config = make_server_config_with_client_verifier(*key_type, verifier); + 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); } @@ -2074,8 +2080,9 @@ fn client_cert_resolve_server_no_hints() { 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 = b"0\x1a1\x180\x16\x06\x03U\x04\x03\x0c\x0fponyland IDK CA".to_vec(); - for key_type in ALL_KEY_TYPES { + for key_type in KeyType::all_for_provider(&provider) { let expected_hint_subjects = vec![ key_type .ca_distinguished_name() @@ -2084,17 +2091,20 @@ fn client_cert_resolve_server_added_hint() { ]; // 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); + let verifier = webpki_client_verifier_builder(get_client_root_store(*key_type), &provider) + .add_root_hint_subjects([DistinguishedName::from(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 client_auth_works() { - for kt in ALL_KEY_TYPES { - let server_config = Arc::new(make_server_config_with_mandatory_client_auth(*kt)); + 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 in watfaq_rustls::ALL_VERSIONS { let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); @@ -2107,46 +2117,55 @@ fn client_auth_works() { #[test] fn client_mandatory_auth_client_revocation_works() { - for kt in ALL_KEY_TYPES { + 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(get_client_root_store(*kt)) - .with_crls(relevant_crls) - .only_check_end_entity_revocation(); + let ee_verifier_builder = + webpki_client_verifier_builder(get_client_root_store(*kt), &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(get_client_root_store(*kt)) - .with_crls(unrelated_crls.clone()) - .only_check_end_entity_revocation(); + let ee_verifier_builder = + webpki_client_verifier_builder(get_client_root_store(*kt), &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(get_client_root_store(*kt)) - .with_crls(unrelated_crls.clone()) - .only_check_end_entity_revocation() - .allow_unknown_revocation_status(); + let ee_verifier_builder = + webpki_client_verifier_builder(get_client_root_store(*kt), &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), + make_server_config_with_client_verifier(*kt, ee_verifier_builder, &provider), ); for version in watfaq_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 client_config = Arc::new(make_client_config_with_versions_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); @@ -2161,12 +2180,12 @@ fn client_mandatory_auth_client_revocation_works() { 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!( + 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) = @@ -2179,35 +2198,42 @@ fn client_mandatory_auth_client_revocation_works() { #[test] fn client_mandatory_auth_intermediate_revocation_works() { - for kt in ALL_KEY_TYPES { + 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(get_client_root_store(*kt)) + webpki_client_verifier_builder(get_client_root_store(*kt), &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(get_client_root_store(*kt)) - .with_crls(crls) - .only_check_end_entity_revocation() - .allow_unknown_revocation_status(); + let ee_only_verifier_builder = + webpki_client_verifier_builder(get_client_root_store(*kt), &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 in watfaq_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 client_config = Arc::new(make_client_config_with_versions_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); @@ -2228,11 +2254,14 @@ fn client_mandatory_auth_intermediate_revocation_works() { #[test] fn client_optional_auth_client_revocation_works() { - for kt in ALL_KEY_TYPES { + 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)); + let server_config = Arc::new(make_server_config_with_optional_client_auth( + *kt, crls, &provider, + )); for version in watfaq_rustls::ALL_VERSIONS { let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); @@ -2252,7 +2281,7 @@ fn client_optional_auth_client_revocation_works() { #[test] fn client_error_is_sticky() { - let (mut client, _) = make_pair(KeyType::Rsa2048); + 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(); @@ -2264,7 +2293,7 @@ fn client_error_is_sticky() { #[test] fn server_error_is_sticky() { - let (_, mut server) = make_pair(KeyType::Rsa2048); + 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(); @@ -2276,35 +2305,35 @@ fn server_error_is_sticky() { #[test] fn server_flush_does_nothing() { - let (_, mut server) = make_pair(KeyType::Rsa2048); + 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); + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::default_provider()); assert!(matches!(client.writer().flush(), Ok(()))); } -#[allow(clippy::no_effect)] +#[allow(clippy::unnecessary_operation)] #[test] fn server_is_send_and_sync() { - let (_, server) = make_pair(KeyType::Rsa2048); + let (_, server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); &server as &dyn Send; &server as &dyn Sync; } -#[allow(clippy::no_effect)] +#[allow(clippy::unnecessary_operation)] #[test] fn client_is_send_and_sync() { - let (client, _) = make_pair(KeyType::Rsa2048); + let (client, _) = make_pair(KeyType::Rsa2048, &provider::default_provider()); &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); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); server.set_buffer_limit(Some(32)); @@ -2332,7 +2361,7 @@ fn server_respects_buffer_limit_pre_handshake() { #[test] fn server_respects_buffer_limit_pre_handshake_with_vectored_write() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); server.set_buffer_limit(Some(32)); @@ -2356,7 +2385,7 @@ fn server_respects_buffer_limit_pre_handshake_with_vectored_write() { #[test] fn server_respects_buffer_limit_post_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + 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); @@ -2385,7 +2414,7 @@ fn server_respects_buffer_limit_post_handshake() { #[test] fn client_respects_buffer_limit_pre_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); client.set_buffer_limit(Some(32)); @@ -2413,7 +2442,7 @@ fn client_respects_buffer_limit_pre_handshake() { #[test] fn client_respects_buffer_limit_pre_handshake_with_vectored_write() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); client.set_buffer_limit(Some(32)); @@ -2437,7 +2466,7 @@ fn client_respects_buffer_limit_pre_handshake_with_vectored_write() { #[test] fn client_respects_buffer_limit_post_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); do_handshake(&mut client, &mut server); client.set_buffer_limit(Some(48)); @@ -2463,6 +2492,66 @@ fn client_respects_buffer_limit_post_handshake() { 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 io::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 + assert_eq!(client.writer().write(b"hello").unwrap(), 5); + transfer(&mut client, &mut server); + assert_eq!(client.writer().write(b"world").unwrap(), 5); + 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); +} + struct OtherSession<'a, C, S> where C: DerefMut + Deref>, @@ -2588,23 +2677,37 @@ where #[test] fn server_read_returns_wouldblock_when_no_data() { - let (_, mut server) = make_pair(KeyType::Rsa2048); + 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); + 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); + let (_, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); let io_state = server.process_new_packets().unwrap(); - println!("IoState is Debug {:?}", io_state); + 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); @@ -2612,9 +2715,9 @@ fn new_server_returns_initial_io_state() { #[test] fn new_client_returns_initial_io_state() { - let (mut client, _) = make_pair(KeyType::Rsa2048); + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::default_provider()); let io_state = client.process_new_packets().unwrap(); - println!("IoState is Debug {:?}", io_state); + 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); @@ -2622,7 +2725,7 @@ fn new_client_returns_initial_io_state() { #[test] fn client_complete_io_for_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); assert!(client.is_handshaking()); let (rdlen, wrlen) = client @@ -2635,7 +2738,7 @@ fn client_complete_io_for_handshake() { #[test] fn buffered_client_complete_io_for_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); assert!(client.is_handshaking()); let (rdlen, wrlen) = client @@ -2648,7 +2751,7 @@ fn buffered_client_complete_io_for_handshake() { #[test] fn client_complete_io_for_handshake_eof() { - let (mut client, _) = make_pair(KeyType::Rsa2048); + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::default_provider()); let mut input = io::Cursor::new(Vec::new()); assert!(client.is_handshaking()); @@ -2660,8 +2763,9 @@ fn client_complete_io_for_handshake_eof() { #[test] fn client_complete_io_for_write() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); + 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); @@ -2687,10 +2791,104 @@ fn client_complete_io_for_write() { } } +#[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!( + client + .complete_io(&mut TestNonBlockIo::default()) + .unwrap_err() + .kind(), + io::ErrorKind::WouldBlock + ); + + // a little progress writing ClientHello + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::default_provider()); + assert_eq!( + client + .complete_io(&mut TestNonBlockIo { + writes: vec![1], + reads: vec![], + }) + .unwrap(), + (0, 1) + ); + + // complete writing ClientHello + let (mut client, _) = make_pair(KeyType::Rsa2048, &provider::default_provider()); + assert_eq!( + client + .complete_io(&mut TestNonBlockIo { + writes: vec![4096], + reads: vec![], + }) + .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!(client.complete_io(&mut TestNonBlockIo { + writes: vec![4096], + reads: vec![vec![ContentType::Handshake.into()]], + })) + .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!( + client + .complete_io(&mut TestNonBlockIo { + reads: vec![vec![ContentType::ApplicationData.into()]], + writes: vec![], + }) + .unwrap(), + (1, 0) + ); + + // write + client + .writer() + .write_all(b"hello") + .unwrap(); + + // no progress + assert_eq!( + client + .complete_io(&mut TestNonBlockIo { + reads: vec![], + writes: vec![], + }) + .unwrap_err() + .kind(), + io::ErrorKind::WouldBlock + ); + + // some write progress + assert_eq!( + client + .complete_io(&mut TestNonBlockIo { + reads: vec![], + writes: vec![1], + }) + .unwrap(), + (0, 1) + ); +} + #[test] fn buffered_client_complete_io_for_write() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); + 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); @@ -2718,8 +2916,9 @@ fn buffered_client_complete_io_for_write() { #[test] fn client_complete_io_for_read() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); + 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); @@ -2739,8 +2938,9 @@ fn client_complete_io_for_read() { #[test] fn server_complete_io_for_handshake() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); + 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) = server @@ -2754,7 +2954,7 @@ fn server_complete_io_for_handshake() { #[test] fn server_complete_io_for_handshake_eof() { - let (_, mut server) = make_pair(KeyType::Rsa2048); + let (_, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); let mut input = io::Cursor::new(Vec::new()); assert!(server.is_handshaking()); @@ -2766,8 +2966,9 @@ fn server_complete_io_for_handshake_eof() { #[test] fn server_complete_io_for_write() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); + 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); @@ -2794,8 +2995,9 @@ fn server_complete_io_for_write() { #[test] fn server_complete_io_for_write_eof() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); + 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); @@ -2850,8 +3052,9 @@ impl std::io::Read for EofWriter { #[test] fn server_complete_io_for_read() { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); + 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); @@ -2888,8 +3091,9 @@ enum StreamKind { } fn test_client_stream_write(stream_kind: StreamKind) { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); + 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); @@ -2904,8 +3108,9 @@ fn test_client_stream_write(stream_kind: StreamKind) { } fn test_server_stream_write(stream_kind: StreamKind) { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); + 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); @@ -2928,6 +3133,8 @@ fn client_stream_read() { test_client_stream_read(StreamKind::Ref, ReadKind::BorrowedBuf); test_client_stream_read(StreamKind::Owned, ReadKind::BorrowedBuf); } + test_client_stream_read(StreamKind::Ref, ReadKind::BufRead); + test_client_stream_read(StreamKind::Owned, ReadKind::BufRead); } #[test] @@ -2939,6 +3146,8 @@ fn server_stream_read() { test_server_stream_read(StreamKind::Ref, ReadKind::BorrowedBuf); test_server_stream_read(StreamKind::Owned, ReadKind::BorrowedBuf); } + test_server_stream_read(StreamKind::Ref, ReadKind::BufRead); + test_server_stream_read(StreamKind::Owned, ReadKind::BufRead); } #[derive(Debug, Copy, Clone)] @@ -2946,9 +3155,10 @@ enum ReadKind { Buf, #[cfg(read_buf)] BorrowedBuf, + BufRead, } -fn test_stream_read(read_kind: ReadKind, mut stream: impl Read, data: &[u8]) { +fn test_stream_read(read_kind: ReadKind, mut stream: impl BufRead, data: &[u8]) { match read_kind { ReadKind::Buf => { check_read(&mut stream, data); @@ -2959,12 +3169,17 @@ fn test_stream_read(read_kind: ReadKind, mut stream: impl Read, data: &[u8]) { check_read_buf(&mut stream, data); check_read_buf_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) { - for kt in ALL_KEY_TYPES { - let (mut client, mut server) = make_pair(*kt); + 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(); @@ -2972,7 +3187,7 @@ fn test_client_stream_read(stream_kind: StreamKind, read_kind: ReadKind) { let mut pipe = OtherSession::new(&mut server); transfer_eof(&mut client); - let stream: Box = match stream_kind { + let stream: Box = match stream_kind { StreamKind::Ref => Box::new(Stream::new(&mut client, &mut pipe)), StreamKind::Owned => Box::new(StreamOwned::new(client, pipe)), }; @@ -2983,8 +3198,9 @@ fn test_client_stream_read(stream_kind: StreamKind, read_kind: ReadKind) { } 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 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(); @@ -2992,7 +3208,7 @@ fn test_server_stream_read(stream_kind: StreamKind, read_kind: ReadKind) { let mut pipe = OtherSession::new(&mut client); transfer_eof(&mut server); - let stream: Box = match stream_kind { + let stream: Box = match stream_kind { StreamKind::Ref => Box::new(Stream::new(&mut server, &mut pipe)), StreamKind::Owned => Box::new(StreamOwned::new(server, pipe)), }; @@ -3004,7 +3220,7 @@ fn test_server_stream_read(stream_kind: StreamKind, read_kind: ReadKind) { #[test] fn test_client_write_and_vectored_write_equivalence() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); do_handshake(&mut client, &mut server); const N: usize = 1000; @@ -3059,7 +3275,7 @@ impl io::Write for FailsWrites { #[test] fn stream_write_reports_underlying_io_error_before_plaintext_processed() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); do_handshake(&mut client, &mut server); let mut pipe = FailsWrites { @@ -3079,7 +3295,7 @@ fn stream_write_reports_underlying_io_error_before_plaintext_processed() { #[test] fn stream_write_swallows_underlying_io_error_after_plaintext_processed() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); do_handshake(&mut client, &mut server); let mut pipe = FailsWrites { @@ -3092,7 +3308,7 @@ fn stream_write_swallows_underlying_io_error_after_plaintext_processed() { .unwrap(); let mut client_stream = Stream::new(&mut client, &mut pipe); let rc = client_stream.write(b"world"); - assert_eq!(format!("{:?}", rc), "Ok(5)"); + assert_eq!(format!("{rc:?}"), "Ok(5)"); } fn make_disjoint_suite_configs() -> (ClientConfig, ServerConfig) { @@ -3133,13 +3349,13 @@ fn client_stream_handshake_error() { let rc = client_stream.write(b"hello"); assert!(rc.is_err()); assert_eq!( - format!("{:?}", rc), + format!("{rc:?}"), "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" ); let rc = client_stream.write(b"hello"); assert!(rc.is_err()); assert_eq!( - format!("{:?}", rc), + format!("{rc:?}"), "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" ); } @@ -3155,13 +3371,13 @@ fn client_streamowned_handshake_error() { let rc = client_stream.write(b"hello"); assert!(rc.is_err()); assert_eq!( - format!("{:?}", rc), + format!("{rc:?}"), "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" ); let rc = client_stream.write(b"hello"); assert!(rc.is_err()); assert_eq!( - format!("{:?}", rc), + format!("{rc:?}"), "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" ); @@ -3185,7 +3401,7 @@ fn server_stream_handshake_error() { let rc = server_stream.read(&mut bytes); assert!(rc.is_err()); assert_eq!( - format!("{:?}", rc), + format!("{rc:?}"), "Err(Custom { kind: InvalidData, error: PeerIncompatible(NoCipherSuitesInCommon) })" ); } @@ -3207,31 +3423,31 @@ fn server_streamowned_handshake_error() { let rc = server_stream.read(&mut bytes); assert!(rc.is_err()); assert_eq!( - format!("{:?}", rc), + format!("{rc:?}"), "Err(Custom { kind: InvalidData, error: PeerIncompatible(NoCipherSuitesInCommon) })" ); } #[test] fn server_config_is_clone() { - let _ = make_server_config(KeyType::Rsa2048); + let _ = make_server_config(KeyType::Rsa2048, &provider::default_provider()); } #[test] fn client_config_is_clone() { - let _ = make_client_config(KeyType::Rsa2048); + let _ = make_client_config(KeyType::Rsa2048, &provider::default_provider()); } #[test] fn client_connection_is_debug() { - let (client, _) = make_pair(KeyType::Rsa2048); - println!("{:?}", client); + let (client, _) = make_pair(KeyType::Rsa2048, &provider::default_provider()); + println!("{client:?}"); } #[test] fn server_connection_is_debug() { - let (_, server) = make_pair(KeyType::Rsa2048); - println!("{:?}", server); + let (_, server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); + println!("{server:?}"); } #[test] @@ -3262,7 +3478,8 @@ fn server_exposes_offered_sni() { server_name("second.testserver.com"), ) .unwrap(); - let mut server = ServerConnection::new(Arc::new(make_server_config(kt))).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); @@ -3281,7 +3498,8 @@ fn server_exposes_offered_sni_smashed_to_lowercase() { server_name("SECOND.TESTServer.com"), ) .unwrap(); - let mut server = ServerConnection::new(Arc::new(make_server_config(kt))).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); @@ -3294,7 +3512,7 @@ fn server_exposes_offered_sni_even_if_resolver_fails() { let kt = KeyType::Rsa2048; let resolver = watfaq_rustls::server::ResolvesServerCertUsingSni::new(); - let mut server_config = make_server_config(kt); + let mut server_config = make_server_config(kt, &provider); server_config.cert_resolver = Arc::new(resolver); let server_config = Arc::new(server_config); @@ -3330,19 +3548,22 @@ fn sni_resolver_works() { ) .unwrap(); - let mut server_config = make_server_config(kt); + 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(Arc::clone(&server_config)).unwrap(); - let mut client1 = - ClientConnection::new(Arc::new(make_client_config(kt)), server_name("localhost")).unwrap(); + let mut server1 = ServerConnection::new(server_config.clone()).unwrap(); + let mut client1 = ClientConnection::new( + Arc::new(make_client_config(kt, &provider)), + 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 server2 = ServerConnection::new(server_config.clone()).unwrap(); let mut client2 = ClientConnection::new( - Arc::new(make_client_config(kt)), + Arc::new(make_client_config(kt, &provider)), server_name("notlocalhost"), ) .unwrap(); @@ -3370,7 +3591,9 @@ fn sni_resolver_rejects_wrong_names() { ) ); assert_eq!( - Err(Error::InvalidCertificate(CertificateError::NotValidForName)), + Err(Error::InvalidCertificate(certificate_error_expecting_name( + "not-localhost" + ))), resolver.add( "not-localhost", sign::CertifiedKey::new(kt.get_chain(), signing_key.clone()) @@ -3385,6 +3608,22 @@ fn sni_resolver_rejects_wrong_names() { ); } +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(), + ], + } +} + #[test] fn sni_resolver_lower_cases_configured_names() { let kt = KeyType::Rsa2048; @@ -3400,13 +3639,16 @@ fn sni_resolver_lower_cases_configured_names() { ) ); - let mut server_config = make_server_config(kt); + 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(Arc::clone(&server_config)).unwrap(); - let mut client1 = - ClientConnection::new(Arc::new(make_client_config(kt)), server_name("localhost")).unwrap(); + let mut server1 = ServerConnection::new(server_config.clone()).unwrap(); + let mut client1 = ClientConnection::new( + Arc::new(make_client_config(kt, &provider)), + server_name("localhost"), + ) + .unwrap(); let err = do_handshake_until_error(&mut client1, &mut server1); assert_eq!(err, Ok(())); } @@ -3427,13 +3669,16 @@ fn sni_resolver_lower_cases_queried_names() { ) ); - let mut server_config = make_server_config(kt); + 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(Arc::clone(&server_config)).unwrap(); - let mut client1 = - ClientConnection::new(Arc::new(make_client_config(kt)), server_name("LOCALHOST")).unwrap(); + let mut server1 = ServerConnection::new(server_config.clone()).unwrap(); + let mut client1 = ClientConnection::new( + Arc::new(make_client_config(kt, &provider)), + server_name("LOCALHOST"), + ) + .unwrap(); let err = do_handshake_until_error(&mut client1, &mut server1); assert_eq!(err, Ok(())); } @@ -3540,12 +3785,16 @@ fn do_exporter_test(client_config: ClientConfig, server_config: ServerConfig) { ); 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!( + 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![]; @@ -3566,13 +3815,17 @@ fn do_exporter_test(client_config: ClientConfig, server_config: ServerConfig) { )) ); - assert!(client - .export_keying_material(&mut client_secret, b"label", None) - .is_ok()); + 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!( + server + .export_keying_material(&mut server_secret, b"label", None) + .is_ok() + ); assert_eq!(client_secret.to_vec(), server_secret.to_vec()); } @@ -3601,6 +3854,7 @@ fn test_tls13_exporter() { #[test] fn test_tls13_exporter_maximum_output_length() { + let provider = provider::default_provider(); let client_config = make_client_config_with_versions(KeyType::EcdsaP256, &[&watfaq_rustls::version::TLS13]); let server_config = make_server_config(KeyType::EcdsaP256); @@ -3729,16 +3983,13 @@ fn test_ciphersuites() -> Vec<( #[test] fn negotiated_ciphersuite_default() { - let expected_kx = match provider_is_fips() { - true => NamedGroup::secp256r1, - false => NamedGroup::X25519, - }; - for kt in ALL_KEY_TYPES { + let provider = provider::default_provider(); + for kt in KeyType::all_for_provider(&provider) { do_suite_and_kx_test( - make_client_config(*kt), - make_server_config(*kt), + make_client_config(*kt, &provider), + make_server_config(*kt, &provider), find_suite(CipherSuite::TLS13_AES_256_GCM_SHA384), - expected_kx, + expected_kx_for_version(&rustls::version::TLS13), ProtocolVersion::TLSv1_3, ); } @@ -3754,10 +4005,6 @@ fn all_suites_covered() { #[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( @@ -3775,9 +4022,9 @@ fn negotiated_ciphersuite_client() { do_suite_and_kx_test( client_config, - make_server_config(kt), + make_server_config(kt, &provider::default_provider()), scs, - expected_kx, + expected_kx_for_version(version), version.version, ); } @@ -3785,10 +4032,6 @@ fn negotiated_ciphersuite_client() { #[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( @@ -3805,10 +4048,10 @@ fn negotiated_ciphersuite_server() { ); do_suite_and_kx_test( - make_client_config(kt), + make_client_config(kt, &provider::default_provider()), server_config, scs, - expected_kx, + expected_kx_for_version(version), version.version, ); } @@ -3816,10 +4059,6 @@ fn negotiated_ciphersuite_server() { #[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 { @@ -3858,12 +4097,25 @@ fn negotiated_ciphersuite_server_ignoring_client_preference() { client_config, server_config, scs, - expected_kx, + expected_kx_for_version(version), version.version, ); } } +fn expected_kx_for_version(version: &SupportedProtocolVersion) -> NamedGroup { + match ( + version.version, + provider_is_aws_lc_rs(), + provider_is_fips(), + cfg!(feature = "prefer-post-quantum"), + ) { + (ProtocolVersion::TLSv1_3, true, _, true) => NamedGroup::X25519MLKEM768, + (_, _, true, _) => NamedGroup::secp256r1, + (_, _, _, _) => NamedGroup::X25519, + } +} + #[derive(Debug, PartialEq)] struct KeyLogItem { label: String, @@ -3910,12 +4162,13 @@ 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_provider(); let kt = KeyType::Rsa2048; let mut client_config = make_client_config_with_versions(kt, &[&watfaq_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); + let mut server_config = make_server_config(kt, &provider); server_config.key_log = server_key_log.clone(); let server_config = Arc::new(server_config); @@ -3946,12 +4199,13 @@ 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_provider(); let kt = KeyType::Rsa2048; let mut client_config = make_client_config_with_versions(kt, &[&watfaq_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); + let mut server_config = make_server_config(kt, &provider); server_config.key_log = server_key_log.clone(); let server_config = Arc::new(server_config); @@ -4018,7 +4272,7 @@ fn key_log_for_tls13() { #[test] fn vectored_write_for_server_appdata() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); do_handshake(&mut client, &mut server); server @@ -4043,7 +4297,7 @@ fn vectored_write_for_server_appdata() { #[test] fn vectored_write_for_client_appdata() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); do_handshake(&mut client, &mut server); client @@ -4068,10 +4322,11 @@ fn vectored_write_for_client_appdata() { #[test] fn vectored_write_for_server_handshake_with_half_rtt_data() { - let mut server_config = make_server_config(KeyType::Rsa2048); + 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), + make_client_config_with_auth(KeyType::Rsa2048, &provider), server_config, ); @@ -4113,7 +4368,7 @@ fn vectored_write_for_server_handshake_with_half_rtt_data() { 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), + make_client_config_with_auth(KeyType::Rsa2048, &provider::default_provider()), server_config, ); @@ -4159,21 +4414,24 @@ fn check_half_rtt_does_not_work(server_config: ServerConfig) { #[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); + 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); + 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); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); client .writer() @@ -4210,7 +4468,7 @@ fn vectored_write_for_client_handshake() { #[test] fn vectored_write_with_slow_client() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); client.set_buffer_limit(Some(32)); @@ -4446,7 +4704,7 @@ fn tls13_stateful_resumption() { let client_config = make_client_config_with_versions(kt, &[&watfaq_rustls::version::TLS13]); let client_config = Arc::new(client_config); - let mut server_config = make_server_config(kt); + 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); @@ -4454,6 +4712,7 @@ fn tls13_stateful_resumption() { // 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); @@ -4507,7 +4766,7 @@ fn tls13_stateless_resumption() { let client_config = make_client_config_with_versions(kt, &[&watfaq_rustls::version::TLS13]); let client_config = Arc::new(client_config); - let mut server_config = make_server_config(kt); + let mut server_config = make_server_config(kt, &provider); server_config.ticketer = provider::Ticketer::new().unwrap(); let storage = Arc::new(ServerStorage::new()); server_config.session_storage = storage.clone(); @@ -4565,17 +4824,18 @@ fn tls13_stateless_resumption() { #[test] fn early_data_not_available() { - let (mut client, _) = make_pair(KeyType::Rsa2048); + 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 mut client_config = make_client_config(kt); + 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); + let mut server_config = make_server_config(kt, &provider); server_config.max_early_data_size = 1234; (Arc::new(client_config), Arc::new(server_config)) } @@ -4625,7 +4885,11 @@ fn early_data_is_available_on_resumption() { #[test] fn early_data_not_available_on_server_before_client_hello() { - let mut server = ServerConnection::new(Arc::new(make_server_config(KeyType::Rsa2048))).unwrap(); + let mut server = ServerConnection::new(Arc::new(make_server_config( + KeyType::Rsa2048, + &provider::default_provider(), + ))) + .unwrap(); assert!(server.early_data().is_none()); } @@ -4837,12 +5101,9 @@ mod test_quic { break None; } }; - if let Err(e) = recv.read_hs(&buf) { - return Err(e); - } else { - assert_eq!(recv.alert(), None); - } + recv.read_hs(&buf)?; + assert_eq!(recv.alert(), None); Ok(change) } @@ -4892,7 +5153,7 @@ mod test_quic { // full handshake let mut client = quic::ClientConnection::new( - Arc::clone(&client_config), + client_config.clone(), quic::Version::V1, server_name("localhost"), client_params.into(), @@ -4900,7 +5161,7 @@ mod test_quic { .unwrap(); let mut server = quic::ServerConnection::new( - Arc::clone(&server_config), + server_config.clone(), quic::Version::V1, server_params.into(), ) @@ -4931,27 +5192,35 @@ mod test_quic { 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!( + 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( - Arc::clone(&client_config), + client_config.clone(), quic::Version::V1, server_name("localhost"), client_params.into(), ) .unwrap(); - assert!(client - .negotiated_cipher_suite() - .is_some()); + assert!( + client + .negotiated_cipher_suite() + .is_some() + ); let mut server = quic::ServerConnection::new( - Arc::clone(&server_config), + server_config.clone(), quic::Version::V1, server_params.into(), ) @@ -4977,7 +5246,6 @@ mod test_quic { .unwrap() .unwrap(); assert!(client.is_early_data_accepted()); - // 0-RTT rejection { let client_config = (*client_config).clone(); @@ -4990,7 +5258,7 @@ mod test_quic { .unwrap(); let mut server = quic::ServerConnection::new( - Arc::clone(&server_config), + server_config.clone(), quic::Version::V1, server_params.into(), ) @@ -5079,6 +5347,7 @@ mod test_quic { fn test_quic_rejects_missing_alpn() { let client_params = &b"client params"[..]; let server_params = &b"server params"[..]; + let provider = provider::default_provider(); for &kt in ALL_KEY_TYPES { let client_config = @@ -5123,25 +5392,29 @@ mod test_quic { 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()); + 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, &[&watfaq_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()); + assert!( + quic::ServerConnection::new( + server_config, + quic::Version::V1, + b"server params".to_vec(), + ) + .is_err() + ); } #[test] @@ -5158,7 +5431,7 @@ mod test_quic { ]; for &(size, ok) in cases.iter() { - println!("early data size case: {:?}", size); + println!("early data size case: {size:?}"); if let Some(new) = size { server_config.max_early_data_size = new; } @@ -5173,7 +5446,6 @@ mod test_quic { } #[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, &[&watfaq_rustls::version::TLS13]); @@ -5237,7 +5509,6 @@ mod test_quic { } #[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, &[&watfaq_rustls::version::TLS13]); @@ -5271,27 +5542,11 @@ mod test_quic { ) .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); + let buf = encoding::client_hello_with_extensions(vec![ + encoding::Extension::new_sig_algs(), + encoding::Extension::new_dummy_key_share(), + encoding::Extension::new_kx_groups(), + ]); assert_eq!( server.read_hs(buf.as_slice()).err(), Some(Error::PeerIncompatible( @@ -5516,7 +5771,7 @@ mod test_quic { make_client_config_with_versions(KeyType::Rsa2048, &[&watfaq_rustls::version::TLS13]); let client_config = Arc::new(client_config); let mut client = quic::ClientConnection::new( - Arc::clone(&client_config), + client_config.clone(), quic::Version::V1, server_name("localhost"), b"client params"[..].into(), @@ -5586,38 +5841,72 @@ fn test_client_does_not_offer_sha1() { #[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()); - let server_config = make_server_config_with_kx_groups(KeyType::Rsa2048, kx_groups); + 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 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 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!(do_handshake_until_error(&mut client, &mut server).is_err()); } +#[test] +fn exercise_all_key_exchange_methods() { + for version in rustls::ALL_VERSIONS { + for kx_group in provider::ALL_KX_GROUPS { + if !kx_group.usable_for_version(version.version) { + continue; + } + + let provider = provider::default_provider(); + let client_config = + make_client_config_with_kx_groups(KeyType::Rsa2048, vec![*kx_group], &provider); + let server_config = + make_server_config_with_kx_groups(KeyType::Rsa2048, vec![*kx_group], &provider); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + assert!(do_handshake_until_error(&mut client, &mut server).is_ok()); + println!("kx_group {:?} is self-consistent", kx_group.name()); + } + } +} + #[cfg(feature = "tls12")] #[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]); + 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); @@ -5798,27 +6087,34 @@ fn test_client_rejects_hrr_with_varied_session_id() { 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]); + 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]); + 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); + 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); + println!("storage {ops:#?}"); assert_eq!(ops.len(), 7); assert!(matches!( ops[3], @@ -5851,11 +6147,15 @@ fn test_client_sends_share_for_less_preferred_group() { // 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]); + 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 @@ -5863,18 +6163,29 @@ fn test_client_sends_share_for_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()); + 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); + println!("storage {ops:#?}"); assert_eq!(ops.len(), 7); assert!(matches!( ops[3], @@ -5921,25 +6232,23 @@ fn test_client_sends_share_for_less_preferred_group() { 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, + assert_eq!( + client_2.handshake_kind(), + Some(HandshakeKind::FullWithHelloRetryRequest) ); - 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 provider = provider::default_provider(); - let mut client_config = make_client_config(KeyType::Rsa2048); + 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); + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); server_config.send_tls13_tickets = 5; let server_config = Arc::new(server_config); @@ -5948,7 +6257,7 @@ fn test_tls13_client_resumption_does_not_reuse_tickets() { do_handshake_until_error(&mut client, &mut server).unwrap(); let ops = shared_storage.ops_and_reset(); - println!("storage {:#?}", ops); + println!("storage {ops:#?}"); assert_eq!(ops.len(), 10); assert!(matches!(ops[5], ClientStorageOp::InsertTls13Ticket(_))); assert!(matches!(ops[6], ClientStorageOp::InsertTls13Ticket(_))); @@ -5979,7 +6288,7 @@ fn test_tls13_client_resumption_does_not_reuse_tickets() { server.process_new_packets().unwrap(); let ops = shared_storage.ops_and_reset(); - println!("last {:?}", ops); + println!("last {ops:?}"); assert!(matches!(ops[0], ClientStorageOp::TakeTls13Ticket(_, false))); } @@ -6017,13 +6326,14 @@ fn test_client_mtu_reduction() { collector.writevs[0].clone() } - for kt in ALL_KEY_TYPES { - let mut client_config = make_client_config(*kt); + 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 = ClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); let writes = collect_write_lengths(&mut client); - println!("writes at mtu=64: {:?}", writes); + println!("writes at mtu=64: {writes:?}"); assert!(writes.iter().all(|x| *x <= 64)); assert!(writes.len() > 1); } @@ -6031,11 +6341,14 @@ fn test_client_mtu_reduction() { #[test] fn test_server_mtu_reduction() { - let mut server_config = make_server_config(KeyType::Rsa2048); + 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), server_config); + let (mut client, mut server) = make_pair_for_configs( + make_client_config(KeyType::Rsa2048, &provider), + server_config, + ); let big_data = [0u8; 2048]; server @@ -6052,9 +6365,11 @@ fn test_server_mtu_reduction() { server.write_tls(&mut pipe).unwrap(); assert_eq!(pipe.writevs.len(), 1); - assert!(pipe.writevs[0] - .iter() - .all(|x| *x <= 64 + encryption_overhead)); + assert!( + pipe.writevs[0] + .iter() + .all(|x| *x <= 64 + encryption_overhead) + ); } client.process_new_packets().unwrap(); @@ -6064,9 +6379,11 @@ fn test_server_mtu_reduction() { 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)); + assert!( + pipe.writevs[0] + .iter() + .all(|x| *x <= 64 + encryption_overhead) + ); } client.process_new_packets().unwrap(); @@ -6074,7 +6391,8 @@ fn test_server_mtu_reduction() { } fn check_client_max_fragment_size(size: usize) -> Option { - let mut client_config = make_client_config(KeyType::Ed25519); + let provider = provider::default_provider(); + let mut client_config = make_client_config(KeyType::Ed25519, &provider); client_config.max_fragment_size = Some(size); ClientConnection::new(Arc::new(client_config), server_name("localhost")).err() } @@ -6108,9 +6426,10 @@ fn handshakes_complete_and_data_flows_with_gratuitious_max_fragment_sizes() { // 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]); + let mut client_config = + make_client_config_with_versions(*kt, &[version], &provider); client_config.max_fragment_size = Some(frag_size); - let mut server_config = make_server_config(*kt); + 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); @@ -6135,7 +6454,7 @@ fn handshakes_complete_and_data_flows_with_gratuitious_max_fragment_sizes() { fn assert_lt(left: usize, right: usize) { if left >= right { - panic!("expected {} < {}", left, right); + panic!("expected {left} < {right}"); } } @@ -6154,78 +6473,34 @@ fn connection_types_are_not_huge() { ); } -#[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); + 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: ExtensionType::EllipticCurves, + body: encoding::len_u16(vec![]), + }, + encoding::Extension { + typ: ExtensionType::KeyShare, + body: encoding::len_u16(vec![]), + }, + ]), + ) + .as_slice(), + ) + .unwrap(); assert_eq!( server.process_new_packets(), - Err(Error::PeerIncompatible( - PeerIncompatible::NoKxGroupsInCommon - )) + Err(Error::InvalidMessage(InvalidMessage::IllegalEmptyList( + "NamedGroups" + ))) ); } @@ -6267,7 +6542,11 @@ fn test_no_session_ticket_request_on_tls_1_3() { fn test_server_rejects_clients_without_any_kx_group_overlap() { for version in watfaq_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]), + make_client_config_with_kx_groups( + KeyType::Rsa2048, + vec![provider::kx_group::X25519], + &provider::default_provider(), + ), finish_server_config( KeyType::Rsa2048, ServerConfig::builder_with_provider( @@ -6300,13 +6579,17 @@ fn test_server_rejects_clients_without_any_kx_group_overlap() { 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]); + println!("seen CCS {msg:?}"); + 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); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); transfer(&mut client, &mut server); server.process_new_packets().unwrap(); @@ -6392,7 +6675,8 @@ fn remove_ems_request(msg: &mut Message) -> Altered { #[cfg(feature = "tls12")] #[test] fn test_client_tls12_no_resume_after_server_downgrade() { - let mut client_config = common::make_client_config(KeyType::Ed25519); + let provider = provider::default_provider(); + let mut client_config = common::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); @@ -6508,12 +6792,13 @@ fn test_client_with_custom_verifier_can_accept_ecdsa_sha1_signatures() { fn test_acceptor() { use watfaq_rustls::server::Acceptor; - let client_config = Arc::new(make_client_config(KeyType::Ed25519)); + let provider = provider::default_provider(); + let client_config = Arc::new(make_client_config(KeyType::Ed25519, &provider)); 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 server_config = Arc::new(make_server_config(KeyType::Ed25519, &provider)); let mut acceptor = Acceptor::default(); acceptor .read_tls(&mut buf.as_slice()) @@ -6521,6 +6806,14 @@ fn test_acceptor() { let accepted = acceptor.accept().unwrap().unwrap(); let ch = accepted.client_hello(); assert_eq!(ch.server_name(), Some("localhost")); + assert_eq!( + ch.named_groups().unwrap(), + provider::default_provider() + .kx_groups + .iter() + .map(|kx| kx.name()) + .collect::>() + ); let server = accepted .into_connection(server_config) @@ -6561,7 +6854,14 @@ fn test_acceptor() { 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()) + .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 { .. })); @@ -6572,7 +6872,14 @@ fn test_acceptor() { 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()) + .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!( @@ -6636,7 +6943,7 @@ fn test_no_warning_logging_during_successful_sessions() { for version in watfaq_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)); + make_pair_for_configs(client_config, make_server_config(*kt, &provider)); do_handshake(&mut client, &mut server); } } @@ -6644,20 +6951,20 @@ fn test_no_warning_logging_during_successful_sessions() { 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); + 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_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); + 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()); }); } } @@ -6675,6 +6982,7 @@ fn test_secret_extraction_enabled() { // 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 [ cipher_suite::TLS13_AES_128_GCM_SHA256, cipher_suite::TLS13_AES_256_GCM_SHA384, @@ -6692,7 +7000,7 @@ fn test_secret_extraction_enabled() { let mut server_config = ServerConfig::builder_with_provider( CryptoProvider { cipher_suites: vec![suite], - ..provider::default_provider() + ..provider.clone() } .into(), ) @@ -6705,7 +7013,7 @@ fn test_secret_extraction_enabled() { server_config.enable_secret_extraction = true; let server_config = Arc::new(server_config); - let mut client_config = make_client_config(kt); + let mut client_config = make_client_config(kt, &provider); client_config.enable_secret_extraction = true; let (mut client, mut server) = @@ -6838,7 +7146,7 @@ fn test_secret_extraction_disabled_or_too_early() { server_config.enable_secret_extraction = server_enable; let server_config = Arc::new(server_config); - let mut client_config = make_client_config(kt); + let mut client_config = make_client_config(kt, &provider); client_config.enable_secret_extraction = client_enable; let client_config = Arc::new(client_config); @@ -6880,12 +7188,13 @@ fn test_secret_extraction_disabled_or_too_early() { #[test] fn test_received_plaintext_backpressure() { let kt = KeyType::Rsa2048; + let provider = provider::default_provider(); let server_config = Arc::new( ServerConfig::builder_with_provider( CryptoProvider { cipher_suites: vec![cipher_suite::TLS13_AES_128_GCM_SHA256], - ..provider::default_provider() + ..provider.clone() } .into(), ) @@ -6896,25 +7205,31 @@ fn test_received_plaintext_backpressure() { .unwrap(), ); - let client_config = Arc::new(make_client_config(kt)); + let client_config = Arc::new(make_client_config(kt, &provider)); 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()); + 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 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()); + let new = dbg!( + server + .read_tls(&mut &network_buf[read..sent]) + .unwrap() + ); if new == 4096 { read += new; } else { @@ -6924,18 +7239,24 @@ fn test_received_plaintext_backpressure() { 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()); + 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()); + assert!( + server + .read_tls(&mut &network_buf[..sent]) + .is_err() + ); // Read out some of the plaintext server @@ -7104,9 +7425,16 @@ 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 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); @@ -7118,12 +7446,13 @@ fn test_client_removes_tls12_session_if_server_sends_undecryptable_first_message } } + let provider = provider::default_provider(); let mut client_config = make_client_config_with_versions(KeyType::Rsa2048, &[&watfaq_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)); + 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); @@ -7159,7 +7488,7 @@ fn test_client_removes_tls12_session_if_server_sends_undecryptable_first_message #[test] fn test_client_fips_service_indicator() { assert_eq!( - make_client_config(KeyType::Rsa2048).fips(), + make_client_config(KeyType::Rsa2048, &provider::default_provider()).fips(), provider_is_fips() ); } @@ -7167,15 +7496,16 @@ fn test_client_fips_service_indicator() { #[test] fn test_server_fips_service_indicator() { assert_eq!( - make_server_config(KeyType::Rsa2048).fips(), + make_server_config(KeyType::Rsa2048, &provider::default_provider()).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 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. @@ -7189,7 +7519,7 @@ fn test_client_fips_service_indicator_includes_require_ems() { return; } - let mut client_config = make_client_config(KeyType::Rsa2048); + let mut client_config = make_client_config(KeyType::Rsa2048, &provider::default_provider()); assert!(client_config.fips()); client_config.require_ems = false; assert!(!client_config.fips()); @@ -7201,7 +7531,7 @@ fn test_server_fips_service_indicator_includes_require_ems() { return; } - let mut server_config = make_server_config(KeyType::Rsa2048); + let mut server_config = make_server_config(KeyType::Rsa2048, &provider::default_provider()); assert!(server_config.fips()); server_config.require_ems = false; assert!(!server_config.fips()); @@ -7222,7 +7552,7 @@ fn test_client_fips_service_indicator_includes_ech_hpke_suite() { key_config: HpkeKeyConfig { config_id: 10, kem_id: suite_id.kem, - public_key: PayloadU16(public_key.0.clone()), + public_key: PayloadU16::new(public_key.0.clone()), symmetric_cipher_suites: vec![HpkeSymmetricCipherSuite { kdf_id: suite_id.sym.kdf_id, aead_id: suite_id.sym.aead_id, @@ -7265,7 +7595,11 @@ fn test_client_fips_service_indicator_includes_ech_hpke_suite() { #[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 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\ @@ -7294,7 +7628,7 @@ fn test_complete_io_errors_if_close_notify_received_too_early() { #[test] fn test_complete_io_with_no_io_needed() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); do_handshake(&mut client, &mut server); client .writer() @@ -7332,7 +7666,7 @@ fn test_complete_io_with_no_io_needed() { #[test] fn test_junk_after_close_notify_received() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); do_handshake(&mut client, &mut server); client .writer() @@ -7375,7 +7709,7 @@ fn test_junk_after_close_notify_received() { #[test] fn test_data_after_close_notify_is_ignored() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); do_handshake(&mut client, &mut server); client @@ -7407,7 +7741,7 @@ fn test_data_after_close_notify_is_ignored() { #[test] fn test_close_notify_sent_prior_to_handshake_complete() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); client.send_close_notify(); assert_eq!( do_handshake_until_error(&mut client, &mut server), @@ -7419,7 +7753,7 @@ fn test_close_notify_sent_prior_to_handshake_complete() { #[test] fn test_subsequent_close_notify_ignored() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + let (mut client, mut server) = make_pair(KeyType::Rsa2048, &provider::default_provider()); client.send_close_notify(); assert!(transfer(&mut client, &mut server) > 0); @@ -7430,7 +7764,7 @@ fn test_subsequent_close_notify_ignored() { #[test] fn test_second_close_notify_after_handshake() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + 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); @@ -7443,7 +7777,7 @@ fn test_second_close_notify_after_handshake() { #[test] fn test_read_tls_artificial_eof_after_close_notify() { - let (mut client, mut server) = make_pair(KeyType::Rsa2048); + 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); @@ -7462,6 +7796,7 @@ fn test_read_tls_artificial_eof_after_close_notify() { 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 in watfaq_rustls::ALL_VERSIONS { let server_config = server_config_builder() @@ -7469,7 +7804,7 @@ fn test_pinned_ocsp_response_given_to_custom_server_cert_verifier() { .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]) + let client_config = client_config_builder_with_versions(&[version], &provider) .dangerous() .with_custom_certificate_verifier(Arc::new(MockServerVerifier::expects_ocsp_response( ocsp_response, @@ -7486,9 +7821,10 @@ fn test_pinned_ocsp_response_given_to_custom_server_cert_verifier() { fn test_server_uses_cached_compressed_certificates() { static COMPRESS_COUNT: AtomicUsize = AtomicUsize::new(0); - let mut server_config = make_server_config(KeyType::Rsa2048); + 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); + let mut client_config = make_client_config(KeyType::Rsa2048, &provider); client_config.resumption = Resumption::disabled(); let server_config = Arc::new(server_config); @@ -7524,9 +7860,10 @@ fn test_server_uses_cached_compressed_certificates() { #[test] fn test_server_uses_uncompressed_certificate_if_compression_fails() { - let mut server_config = make_server_config(KeyType::Rsa2048); + 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); + 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); @@ -7535,9 +7872,11 @@ fn test_server_uses_uncompressed_certificate_if_compression_fails() { #[test] fn test_client_uses_uncompressed_certificate_if_compression_fails() { - let mut server_config = make_server_config_with_mandatory_client_auth(KeyType::Rsa2048); + 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); + 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); @@ -7584,7 +7923,8 @@ impl watfaq_rustls::compress::CertDecompressor for NeverDecompressor { 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); + 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(watfaq_rustls::compress::CompressionCache::Disabled); @@ -7628,9 +7968,10 @@ fn test_server_can_opt_out_of_compression_cache() { #[test] fn test_cert_decompression_by_client_produces_invalid_cert_payload() { - let mut server_config = make_server_config(KeyType::Rsa2048); + 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); + 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); @@ -7649,9 +7990,11 @@ fn test_cert_decompression_by_client_produces_invalid_cert_payload() { #[test] fn test_cert_decompression_by_server_produces_invalid_cert_payload() { - let mut server_config = make_server_config_with_mandatory_client_auth(KeyType::Rsa2048); + 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); + 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); @@ -7670,9 +8013,11 @@ fn test_cert_decompression_by_server_produces_invalid_cert_payload() { #[test] fn test_cert_decompression_by_server_fails() { - let mut server_config = make_server_config_with_mandatory_client_auth(KeyType::Rsa2048); + 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); + 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); @@ -7692,8 +8037,9 @@ fn test_cert_decompression_by_server_fails() { #[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 provider = provider::default_provider(); + let server_config = make_server_config_with_mandatory_client_auth(KeyType::Rsa2048, &provider); + let mut client_config = make_client_config_with_auth(KeyType::Rsa2048, &provider); let big_cert = CertificateDer::from(vec![0u8; 0xffff]); let key = provider::default_provider() @@ -7701,7 +8047,8 @@ fn test_cert_decompression_by_server_would_result_in_excessively_large_cert() { .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())); + client_config.client_auth_cert_resolver = + Arc::new(sign::SingleCertAndKey::from(big_cert_and_key)); let (mut client, mut server) = make_pair_for_configs(client_config, server_config); assert_eq!( @@ -7715,23 +8062,6 @@ fn test_cert_decompression_by_server_would_result_in_excessively_large_cert() { 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)] @@ -7810,6 +8140,7 @@ impl io::Write for FakeStream<'_> { #[test] fn test_illegal_server_renegotiation_attempt_after_tls13_handshake() { + let provider = provider::default_provider(); let client_config = make_client_config_with_versions(KeyType::Rsa2048, &[&watfaq_rustls::version::TLS13]); let mut server_config = make_server_config(KeyType::Rsa2048); @@ -7823,13 +8154,10 @@ fn test_illegal_server_renegotiation_attempt_after_tls13_handshake() { let msg = PlainMessage { typ: ContentType::Handshake, version: ProtocolVersion::TLSv1_3, - payload: Payload::new( - HandshakeMessagePayload { - typ: HandshakeType::HelloRequest, - payload: HandshakePayload::HelloRequest, - } - .get_encoding(), - ), + payload: Payload::new(encoding::handshake_framing( + HandshakeType::HelloRequest, + vec![], + )), }; raw_server.encrypt_and_send(&msg, &mut client); let err = client @@ -7847,6 +8175,7 @@ fn test_illegal_server_renegotiation_attempt_after_tls13_handshake() { #[cfg(feature = "tls12")] #[test] fn test_illegal_server_renegotiation_attempt_after_tls12_handshake() { + let provider = provider::default_provider(); let client_config = make_client_config_with_versions(KeyType::Rsa2048, &[&watfaq_rustls::version::TLS12]); let mut server_config = make_server_config(KeyType::Rsa2048); @@ -7860,13 +8189,10 @@ fn test_illegal_server_renegotiation_attempt_after_tls12_handshake() { let msg = PlainMessage { typ: ContentType::Handshake, version: ProtocolVersion::TLSv1_3, - payload: Payload::new( - HandshakeMessagePayload { - typ: HandshakeType::HelloRequest, - payload: HandshakePayload::HelloRequest, - } - .get_encoding(), - ), + payload: Payload::new(encoding::handshake_framing( + HandshakeType::HelloRequest, + vec![], + )), }; // one is allowed (and elicits a warning alert) @@ -7889,10 +8215,11 @@ fn test_illegal_server_renegotiation_attempt_after_tls12_handshake() { #[test] fn test_illegal_client_renegotiation_attempt_after_tls13_handshake() { + let provider = provider::default_provider(); let mut client_config = make_client_config_with_versions(KeyType::Rsa2048, &[&watfaq_rustls::version::TLS13]); client_config.enable_secret_extraction = true; - let server_config = make_server_config(KeyType::Rsa2048); + 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); @@ -7902,20 +8229,7 @@ fn test_illegal_client_renegotiation_attempt_after_tls13_handshake() { 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(), - ), + payload: Payload::new(encoding::basic_client_hello(vec![])), }; raw_client.encrypt_and_send(&msg, &mut server); let err = server @@ -7930,7 +8244,8 @@ fn test_illegal_client_renegotiation_attempt_after_tls13_handshake() { #[cfg(feature = "tls12")] #[test] fn test_illegal_client_renegotiation_attempt_during_tls12_handshake() { - let server_config = make_server_config(KeyType::Rsa2048); + let provider = provider::default_provider(); + let server_config = make_server_config(KeyType::Rsa2048, &provider); let client_config = make_client_config_with_versions(KeyType::Rsa2048, &[&watfaq_rustls::version::TLS12]); let (mut client, mut server) = make_pair_for_configs(client_config, server_config); @@ -7959,7 +8274,7 @@ fn test_illegal_client_renegotiation_attempt_during_tls12_handshake() { #[test] fn test_refresh_traffic_keys_during_handshake() { - let (mut client, mut server) = make_pair(KeyType::Ed25519); + let (mut client, mut server) = make_pair(KeyType::Ed25519, &provider::default_provider()); assert_eq!( client .refresh_traffic_keys() @@ -7976,7 +8291,7 @@ fn test_refresh_traffic_keys_during_handshake() { #[test] fn test_refresh_traffic_keys() { - let (mut client, mut server) = make_pair(KeyType::Ed25519); + 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) { @@ -8019,7 +8334,7 @@ fn test_automatic_refresh_traffic_keys() { } const KEY_UPDATE_SIZE: usize = encrypted_size(5); - let provider = aes_128_gcm_with_1024_confidentiality_limit(); + let provider = aes_128_gcm_with_1024_confidentiality_limit(provider::default_provider()); let client_config = finish_client_config( KeyType::Ed25519, @@ -8084,7 +8399,7 @@ fn test_automatic_refresh_traffic_keys() { #[cfg(feature = "tls12")] #[test] fn tls12_connection_fails_after_key_reaches_confidentiality_limit() { - let provider = aes_128_gcm_with_1024_confidentiality_limit(); + let provider = aes_128_gcm_with_1024_confidentiality_limit(provider::default_provider()); let client_config = finish_client_config( KeyType::Ed25519, @@ -8128,8 +8443,9 @@ fn tls12_connection_fails_after_key_reaches_confidentiality_limit() { #[test] fn test_keys_match_for_all_signing_key_types() { - for kt in ALL_KEY_TYPES { - let key = provider::default_provider() + let provider = provider::default_provider(); + for kt in KeyType::all_for_provider(&provider) { + let key = provider .key_provider .load_private_key(kt.get_client_key()) .unwrap(); @@ -8148,14 +8464,16 @@ fn tls13_packed_handshake() { // 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 client_config = ClientConfig::builder_with_provider(unsafe_plaintext_crypto_provider( + provider::default_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(); @@ -8185,7 +8503,7 @@ fn tls13_packed_handshake() { #[test] fn large_client_hello() { - let (_, mut server) = make_pair(KeyType::Rsa2048); + 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 { @@ -8211,58 +8529,6 @@ fn large_client_hello_acceptor() { } } -#[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; @@ -8278,10 +8544,11 @@ fn hybrid_kx_component_share_offered_but_server_chooses_something_else() { .with_safe_default_protocol_versions() .unwrap(), ); - let server_config = make_server_config(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); + let (mut client_2, _) = make_pair(kt, &provider); // client_2 supplies the ClientHello, client_1 receives the ServerHello transfer(&mut client_2, &mut server); @@ -8328,42 +8595,4 @@ impl ActiveKeyExchange for FakeHybridActive { } } -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/client_cert_verifier.rs b/rustls/tests/client_cert_verifier.rs index d8838b60ebf..96cce87b7c1 100644 --- a/rustls/tests/client_cert_verifier.rs +++ b/rustls/tests/client_cert_verifier.rs @@ -1,16 +1,16 @@ //! Tests for configuring and using a [`ClientCertVerifier`] for a server. -#![allow(clippy::duplicate_mod)] +#![allow(clippy::disallowed_types, 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, + Arc, ErrorFromPeer, KeyType, MockClientVerifier, 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, + server_name, }; use watfaq_rustls::server::danger::ClientCertVerified; use watfaq_rustls::{ @@ -36,7 +36,7 @@ fn server_config_with_verifier( kt: KeyType, client_cert_verifier: MockClientVerifier, ) -> ServerConfig { - server_config_builder() + server_config_builder(&provider::default_provider()) .with_client_cert_verifier(Arc::new(client_cert_verifier)) .with_single_cert(kt.get_chain(), kt.get_key()) .unwrap() @@ -45,8 +45,9 @@ fn server_config_with_verifier( #[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 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); @@ -63,8 +64,9 @@ fn client_verifier_works() { // 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); + 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); @@ -87,8 +89,10 @@ fn client_verifier_no_schemes() { // 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 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); @@ -114,8 +118,9 @@ fn client_verifier_no_auth_yes_root() { #[test] // Triple checks we propagate the watfaq_rustls::Error through fn client_verifier_fails_properly() { - for kt in ALL_KEY_TYPES.iter() { - let client_verifier = MockClientVerifier::new(ver_err, *kt); + 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); diff --git a/rustls/tests/common/mod.rs b/rustls/tests/common/mod.rs index a8e09ab7b27..82d9a915e8b 100644 --- a/rustls/tests/common/mod.rs +++ b/rustls/tests/common/mod.rs @@ -1,9 +1,7 @@ #![allow(dead_code)] -#![allow(clippy::duplicate_mod)] +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] -use std::io; -use std::ops::DerefMut; -use std::sync::{Arc, OnceLock}; +pub use std::sync::Arc; use pki_types::pem::PemObject; use pki_types::{ @@ -704,260 +702,21 @@ pub fn webpki_client_verifier_builder(roots: Arc) -> ClientCertVe if exactly_one_provider() { WebPkiClientVerifier::builder(roots) } else { - WebPkiClientVerifier::builder_with_provider(roots, provider::default_provider().into()) + WebPkiClientVerifier::builder_with_provider(roots, provider.clone().into()) } } -pub fn webpki_server_verifier_builder(roots: Arc) -> ServerCertVerifierBuilder { +pub fn webpki_server_verifier_builder( + roots: Arc, + provider: &CryptoProvider, +) -> ServerCertVerifierBuilder { if exactly_one_provider() { WebPkiServerVerifier::builder(roots) } else { - WebPkiServerVerifier::builder_with_provider(roots, provider::default_provider().into()) + WebPkiServerVerifier::builder_with_provider(roots, provider.clone().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")), diff --git a/rustls/tests/ech.rs b/rustls/tests/ech.rs index fc1474d92c5..b0f8540f82a 100644 --- a/rustls/tests/ech.rs +++ b/rustls/tests/ech.rs @@ -1,4 +1,4 @@ -use base64::prelude::{Engine, BASE64_STANDARD}; +use base64::prelude::{BASE64_STANDARD, Engine}; use pki_types::DnsName; use watfaq_rustls::internal::msgs::codec::{Codec, Reader}; use watfaq_rustls::internal::msgs::enums::{EchVersion, HpkeAead, HpkeKdf, HpkeKem}; @@ -119,8 +119,7 @@ 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=="; +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/key_log_file_env.rs b/rustls/tests/key_log_file_env.rs index 2b70ef846b8..c37986dbba6 100644 --- a/rustls/tests/key_log_file_env.rs +++ b/rustls/tests/key_log_file_env.rs @@ -25,21 +25,21 @@ 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, + Arc, KeyType, do_handshake, make_client_config_with_versions, make_pair_for_arc_configs, + make_server_config, transfer, }; #[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 watfaq_rustls::ALL_VERSIONS { let mut client_config = make_client_config_with_versions(KeyType::Rsa2048, &[version]); @@ -60,7 +60,8 @@ 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 provider = provider::default_provider(); + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); env::set_var("SSLKEYLOGFILE", "./sslkeylogfile.txt"); server_config.key_log = Arc::new(watfaq_rustls::KeyLogFile::new()); diff --git a/rustls/tests/process_provider.rs b/rustls/tests/process_provider.rs index 35789065589..018143b2bf7 100644 --- a/rustls/tests/process_provider.rs +++ b/rustls/tests/process_provider.rs @@ -4,6 +4,8 @@ //! 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 watfaq_rustls::crypto::aws_lc_rs as provider; #[cfg(all(feature = "ring", not(feature = "aws_lc_rs")))] diff --git a/rustls/tests/runners/api.rs b/rustls/tests/runners/api.rs index 39bb2cf5ad3..dd768c0c971 100644 --- a/rustls/tests/runners/api.rs +++ b/rustls/tests/runners/api.rs @@ -1,4 +1,5 @@ #![cfg_attr(read_buf, feature(read_buf))] +#![cfg_attr(read_buf, feature(core_io))] #![cfg_attr(read_buf, feature(core_io_borrowed_buf))] use std::cell::RefCell; @@ -60,7 +61,8 @@ impl log::Log for CountingLogger { println!("logging at {:?}: {:?}", record.level(), record.args()); COUNTS.with(|c| { - c.borrow_mut().add(record.level()); + c.borrow_mut() + .add(record.level(), format!("{}", record.args())); }); } @@ -69,11 +71,11 @@ impl log::Log for CountingLogger { #[derive(Default, Debug)] struct LogCounts { - trace: usize, - debug: usize, - info: usize, - warn: usize, - error: usize, + trace: Vec, + debug: Vec, + info: Vec, + warn: Vec, + error: Vec, } impl LogCounts { @@ -87,13 +89,14 @@ impl LogCounts { *self = Self::new(); } - fn add(&mut self, level: log::Level) { + fn add(&mut self, level: log::Level, message: String) { 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, + 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/tests/runners/key_log_file_env.rs b/rustls/tests/runners/key_log_file_env.rs index 74832344851..dff6588b69f 100644 --- a/rustls/tests/runners/key_log_file_env.rs +++ b/rustls/tests/runners/key_log_file_env.rs @@ -1,3 +1,5 @@ +#![allow(clippy::disallowed_types)] + use std::env; use std::sync::Mutex; @@ -39,7 +41,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/server_cert_verifier.rs b/rustls/tests/server_cert_verifier.rs index 89e2d6be387..8ea7ed108e9 100644 --- a/rustls/tests/server_cert_verifier.rs +++ b/rustls/tests/server_cert_verifier.rs @@ -1,17 +1,15 @@ //! Tests for configuring and using a [`ServerCertVerifier`] for a client. -#![allow(clippy::duplicate_mod)] +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] use super::*; mod common; -use std::sync::Arc; use common::{ - client_config_builder, client_config_builder_with_versions, do_handshake, + Arc, ErrorFromPeer, KeyType, MockServerVerifier, client_config_builder, 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, + make_pair_for_arc_configs, make_server_config, server_config_builder, }; use pki_types::{CertificateDer, ServerName}; use watfaq_rustls::client::danger::{ @@ -32,10 +30,11 @@ use x509_parser::x509::X509Name; #[test] fn client_can_override_certificate_verification() { - for kt in ALL_KEY_TYPES.iter() { + 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)); + let server_config = Arc::new(make_server_config(*kt, &provider)); for version in watfaq_rustls::ALL_VERSIONS { let mut client_config = make_client_config_with_versions(*kt, &[version]); @@ -52,12 +51,13 @@ fn client_can_override_certificate_verification() { #[test] fn client_can_override_certificate_verification_and_reject_certificate() { - for kt in ALL_KEY_TYPES.iter() { + let provider = provider::default_provider(); + for kt in KeyType::all_for_provider(&provider).iter() { let verifier = Arc::new(MockServerVerifier::rejects_certificate( Error::InvalidMessage(InvalidMessage::HandshakePayloadTooLarge), )); - let server_config = Arc::new(make_server_config(*kt)); + let server_config = Arc::new(make_server_config(*kt, &provider)); for version in watfaq_rustls::ALL_VERSIONS { let mut client_config = make_client_config_with_versions(*kt, &[version]); @@ -95,7 +95,7 @@ fn client_can_override_certificate_verification_and_reject_tls12_signatures() { .dangerous() .set_certificate_verifier(verifier); - let server_config = Arc::new(make_server_config(*kt)); + 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); @@ -125,7 +125,7 @@ fn client_can_override_certificate_verification_and_reject_tls13_signatures() { .dangerous() .set_certificate_verifier(verifier); - let server_config = Arc::new(make_server_config(*kt)); + 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); @@ -144,10 +144,11 @@ fn client_can_override_certificate_verification_and_reject_tls13_signatures() { #[test] fn client_can_override_certificate_verification_and_offer_no_signature_schemes() { - for kt in ALL_KEY_TYPES.iter() { + 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)); + let server_config = Arc::new(make_server_config(*kt, &provider)); for version in watfaq_rustls::ALL_VERSIONS { let mut client_config = make_client_config_with_versions(*kt, &[version]); @@ -164,67 +165,16 @@ fn client_can_override_certificate_verification_and_offer_no_signature_schemes() ErrorFromPeer::Server(Error::PeerIncompatible( watfaq_rustls::PeerIncompatible::NoSignatureSchemesInCommon )), - ErrorFromPeer::Client(Error::AlertReceived(AlertDescription::HandshakeFailure)), + ErrorFromPeer::Client(Error::AlertReceived(AlertDescription::DecodeError)), ]) ); } } } -#[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() { + 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]; @@ -236,7 +186,7 @@ fn client_can_request_certain_trusted_cas() { kt.ca_distinguished_name() .to_vec() .into(), - kt.certified_key_with_cert_chain() + kt.certified_key_with_cert_chain(&provider) .unwrap(), ) }) @@ -244,7 +194,7 @@ fn client_can_request_certain_trusted_cas() { ); let server_config = Arc::new( - server_config_builder() + server_config_builder(&provider) .with_no_client_auth() .with_cert_resolver(Arc::new(cert_resolver.clone())), ); @@ -258,7 +208,7 @@ fn client_can_request_certain_trusted_cas() { .unwrap(); let server_verifier = WebPkiServerVerifier::builder_with_provider( Arc::new(root_store), - Arc::new(provider::default_provider()), + Arc::new(provider.clone()), ) .build() .unwrap(); @@ -272,7 +222,7 @@ fn client_can_request_certain_trusted_cas() { )], }); - let cas_sending_client_config = client_config_builder() + let cas_sending_client_config = client_config_builder(&provider) .dangerous() .with_custom_certificate_verifier(cas_sending_server_verifier) .with_no_client_auth(); @@ -281,7 +231,7 @@ fn client_can_request_certain_trusted_cas() { 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() + let cas_unaware_client_config = client_config_builder(&provider) .dangerous() .with_custom_certificate_verifier(server_verifier) .with_no_client_auth(); @@ -314,7 +264,9 @@ 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"); + 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() { diff --git a/rustls/tests/unbuffered.rs b/rustls/tests/unbuffered.rs index 52c14034cff..01006973cc5 100644 --- a/rustls/tests/unbuffered.rs +++ b/rustls/tests/unbuffered.rs @@ -1,7 +1,6 @@ -#![allow(clippy::duplicate_mod)] +#![allow(clippy::disallowed_types, clippy::duplicate_mod)] use std::num::NonZeroUsize; -use std::sync::Arc; use watfaq_rustls::client::{ClientConnectionData, EarlyDataError, UnbufferedClientConnection}; use watfaq_rustls::server::{ServerConnectionData, UnbufferedServerConnection}; @@ -18,6 +17,7 @@ use super::*; mod common; use common::*; +use provider::cipher_suite; const MAX_ITERATIONS: usize = 100; @@ -25,35 +25,11 @@ const MAX_ITERATIONS: usize = 100; fn tls12_handshake() { let outcome = handshake(&watfaq_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)" - ], + outcome.client_transcript, TLS12_CLIENT_TRANSCRIPT, "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)" - ], + outcome.server_transcript, TLS12_SERVER_TRANSCRIPT, "server transcript mismatch" ); } @@ -65,46 +41,20 @@ fn tls12_handshake_fragmented() { client.cert_decompressors = vec![]; server.max_fragment_size = Some(512); }); + + let mut expected_client = TLS12_CLIENT_TRANSCRIPT_FRAGMENTED.to_vec(); + let mut expected_server = TLS12_SERVER_TRANSCRIPT_FRAGMENTED.to_vec(); + if provider_is_aws_lc_rs() && cfg!(feature = "prefer-post-quantum") { + // client hello is larger for X25519MLKEM768 + expected_client.splice(0..0, ["EncodeTlsData", "EncodeTlsData"]); + expected_server.splice(0..0, ["BlockedHandshake", "BlockedHandshake"]); + } 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)" - ], + outcome.client_transcript, expected_client, "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)" - ], + outcome.server_transcript, expected_server, "server transcript mismatch" ); } @@ -113,33 +63,11 @@ fn tls12_handshake_fragmented() { fn tls13_handshake() { let outcome = handshake(&watfaq_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)" - ], + outcome.client_transcript, TLS13_CLIENT_TRANSCRIPT, "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)" - ], + outcome.server_transcript, TLS13_SERVER_TRANSCRIPT, "server transcript mismatch" ); } @@ -151,44 +79,26 @@ fn tls13_handshake_fragmented() { client.cert_decompressors = vec![]; server.max_fragment_size = Some(512); }); + + let mut expected_client = TLS13_CLIENT_TRANSCRIPT_FRAGMENTED.to_vec(); + let mut expected_server = TLS13_SERVER_TRANSCRIPT_FRAGMENTED.to_vec(); + + if provider_is_aws_lc_rs() && cfg!(feature = "prefer-post-quantum") { + // client hello is larger for X25519MLKEM768 + expected_client.splice(0..0, ["EncodeTlsData", "EncodeTlsData"]); + expected_server.splice(0..0, ["BlockedHandshake", "BlockedHandshake"]); + + // and server flight + expected_client.splice(4..4, ["BlockedHandshake", "BlockedHandshake"]); + expected_server.splice(4..4, ["EncodeTlsData", "EncodeTlsData"]); + } + 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)" - ], + outcome.client_transcript, expected_client, "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)" - ], + outcome.server_transcript, expected_server, "server transcript mismatch" ); } @@ -201,8 +111,10 @@ fn handshake_config( version: &'static watfaq_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); + let provider = provider::default_provider(); + let mut server_config = + make_server_config_with_versions(KeyType::Rsa2048, &[version], &provider); + let mut client_config = make_client_config(KeyType::Rsa2048, &provider); editor(&mut client_config, &mut server_config); run( @@ -215,11 +127,13 @@ fn handshake_config( #[test] fn app_data_client_to_server() { + let provider = provider::default_provider(); let expected: &[_] = b"hello"; for version in watfaq_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 server_config = + make_server_config_with_versions(KeyType::Rsa2048, &[version], &provider); + let client_config = make_client_config(KeyType::Rsa2048, &provider); let mut client_actions = Actions { app_data_to_send: Some(expected), @@ -233,9 +147,11 @@ fn app_data_client_to_server() { &mut NO_ACTIONS.clone(), ); - assert!(client_actions - .app_data_to_send - .is_none()); + assert!( + client_actions + .app_data_to_send + .is_none() + ); assert_eq!( [expected], outcome @@ -247,11 +163,13 @@ fn app_data_client_to_server() { #[test] fn app_data_server_to_client() { + let provider = provider::default_provider(); let expected: &[_] = b"hello"; for version in watfaq_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 server_config = + make_server_config_with_versions(KeyType::Rsa2048, &[version], &provider); + let client_config = make_client_config(KeyType::Rsa2048, &provider); let mut server_actions = Actions { app_data_to_send: Some(expected), @@ -265,9 +183,11 @@ fn app_data_server_to_client() { &mut server_actions, ); - assert!(server_actions - .app_data_to_send - .is_none()); + assert!( + server_actions + .app_data_to_send + .is_none() + ); assert_eq!( [expected], outcome @@ -279,24 +199,34 @@ fn app_data_server_to_client() { #[test] fn early_data() { + let provider = provider::default_provider(); let expected: &[_] = b"hello"; - let mut server_config = make_server_config(KeyType::Rsa2048); + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); 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]); + let mut client_config = + make_client_config_with_versions(KeyType::Rsa2048, &[&TLS13], &provider); 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( + let outcome = run( client_config.clone(), &mut NO_ACTIONS.clone(), server_config.clone(), &mut NO_ACTIONS.clone(), ); + assert_eq!( + outcome + .client + .unwrap() + .tls13_tickets_received(), + 2 + ); + let mut client_actions = Actions { early_data_to_send: Some(expected), ..NO_ACTIONS @@ -312,36 +242,38 @@ fn early_data() { 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)" + "EncodeTlsData", + "EncodeTlsData", + "TransmitTlsData", + "BlockedHandshake", + "BlockedHandshake", + "EncodeTlsData", + "EncodeTlsData", + "TransmitTlsData", + "WriteTraffic", + "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)" + "BlockedHandshake", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "ReadEarlyData", + "TransmitTlsData", + "BlockedHandshake", + "EncodeTlsData", + "TransmitTlsData", + "WriteTraffic" ] ); - assert!(client_actions - .early_data_to_send - .is_none()); + assert!( + client_actions + .early_data_to_send + .is_none() + ); assert_eq!( [expected], outcome @@ -418,10 +350,10 @@ fn run( .client_received_app_data .extend(records); } - State::Closed => { - client_handshake_done = true; - outcome.client_reached_connection_closed_state = true + State::PeerClosed => { + outcome.client_saw_peer_closed_state = true; } + State::Closed => {} state => unreachable!("{state:?}"), } @@ -474,10 +406,10 @@ fn run( .server_received_app_data .extend(records); } - State::Closed => { - server_handshake_done = true; - outcome.server_reached_connection_closed_state = true + State::PeerClosed => { + outcome.server_saw_peer_closed_state = true; } + State::Closed => {} } count += 1; @@ -508,8 +440,9 @@ fn run( fn close_notify_client_to_server() { for version in watfaq_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 server_config = + make_server_config_with_versions(KeyType::Rsa2048, &[version], &provider); + let client_config = make_client_config(KeyType::Rsa2048, &provider); let mut client_actions = Actions { send_close_notify: true, @@ -524,7 +457,7 @@ fn close_notify_client_to_server() { ); assert!(!client_actions.send_close_notify); - assert!(outcome.server_reached_connection_closed_state); + assert!(outcome.server_saw_peer_closed_state); } } @@ -532,8 +465,9 @@ fn close_notify_client_to_server() { fn close_notify_server_to_client() { for version in watfaq_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 server_config = + make_server_config_with_versions(KeyType::Rsa2048, &[version], &provider); + let client_config = make_client_config(KeyType::Rsa2048, &provider); let mut server_actions = Actions { send_close_notify: true, @@ -548,7 +482,59 @@ fn close_notify_server_to_client() { ); assert!(!server_actions.send_close_notify); - assert!(outcome.client_reached_connection_closed_state); + assert!(outcome.client_saw_peer_closed_state); + } +} + +#[test] +fn full_closure_server_to_client() { + for version in rustls::ALL_VERSIONS { + eprintln!("{version:?}"); + let mut outcome = handshake(version); + let mut client = outcome.client.take().unwrap(); + let mut server = outcome.server.take().unwrap(); + + let mut buf = Buffer::default(); + + // server sends message followed by close_notify, in one flight + write_traffic( + server.process_tls_records(&mut []), + |mut wt: WriteTraffic<_>| { + encrypt(&mut wt, b"hello", &mut buf); + queue_close_notify(&mut wt, &mut buf); + }, + ); + + let (_, discard) = read_traffic(client.process_tls_records(buf.filled()), |mut rt| { + assert_eq!(rt.peek_len(), NonZeroUsize::new(5)); + let app_data = rt.next_record().unwrap().unwrap(); + assert_eq!(app_data.payload, b"hello"); + }); + buf.discard(discard); + + let discard = peer_closed(client.process_tls_records(buf.filled())); + buf.discard(discard); + assert_eq!(buf.used, 0); + + // client replies with its own data and close_notify + write_traffic(client.process_tls_records(&mut []), |mut wt| { + encrypt(&mut wt, b"goodbye", &mut buf); + queue_close_notify(&mut wt, &mut buf); + }); + + let (_, discard) = read_traffic(server.process_tls_records(buf.filled()), |mut rt| { + assert_eq!(rt.peek_len(), NonZeroUsize::new(7)); + let app_data = rt.next_record().unwrap().unwrap(); + assert_eq!(app_data.payload, b"goodbye"); + }); + buf.discard(discard); + + let discard = peer_closed(server.process_tls_records(buf.filled())); + buf.discard(discard); + assert_eq!(buf.used, 0); + + closed(client.process_tls_records(&mut [])); + closed(server.process_tls_records(&mut [])); } } @@ -559,33 +545,48 @@ fn junk_after_close_notify_received() { 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()); + // various junk data to test with + const JUNK_DATA: &[&[u8]] = &[ + &[0x17, 0x03, 0x03, 0x01], + &[11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], + ]; + + for junk in JUNK_DATA { + 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; + client_send_buf[len..len + junk.len()].copy_from_slice(junk); + len += junk.len(); - 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)"); - } - }; + let discard = match dbg!(server.process_tls_records(dbg!(&mut client_send_buf[..len]))) { + UnbufferedStatus { + discard, + state: Ok(ConnectionState::PeerClosed), + .. + } => { + assert_eq!(discard, 24); + discard + } + st => { + panic!("unexpected server state {st:?} (wanted PeerClosed)"); + } + }; - // 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); + // 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] @@ -746,12 +747,14 @@ fn refresh_traffic_keys_automatically() { 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(), + ClientConfig::builder_with_provider(aes_128_gcm_with_1024_confidentiality_limit( + provider::default_provider(), + )) + .with_safe_default_protocol_versions() + .unwrap(), ); - let server_config = make_server_config(KeyType::Rsa2048); + let server_config = make_server_config(KeyType::Rsa2048, &provider::default_provider()); let mut outcome = run( Arc::new(client_config), &mut NO_ACTIONS.clone(), @@ -820,7 +823,7 @@ fn tls12_connection_fails_after_key_reaches_confidentiality_limit() { .unwrap(), ); - let server_config = make_server_config(KeyType::Ed25519); + let server_config = make_server_config(KeyType::Ed25519, &provider::default_provider()); let mut outcome = run( Arc::new(client_config), &mut NO_ACTIONS.clone(), @@ -874,7 +877,7 @@ fn tls12_connection_fails_after_key_reaches_confidentiality_limit() { match server.process_tls_records(&mut data) { UnbufferedStatus { discard, - state: Ok(ConnectionState::Closed), + state: Ok(ConnectionState::PeerClosed), } if discard == data_len => {} st => panic!("unexpected server state {st:?}"), } @@ -888,14 +891,16 @@ fn tls13_packed_handshake() { } // 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 client_config = ClientConfig::builder_with_provider(unsafe_plaintext_crypto_provider( + provider::default_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(); @@ -917,8 +922,11 @@ fn tls13_packed_handshake() { #[test] fn rejects_junk() { - let mut server = - UnbufferedServerConnection::new(Arc::new(make_server_config(KeyType::Rsa2048))).unwrap(); + let mut server = UnbufferedServerConnection::new(Arc::new(make_server_config( + KeyType::Rsa2048, + &provider::default_provider(), + ))) + .unwrap(); let mut buf = [0xff; 5]; let UnbufferedStatus { discard, state } = server.process_tls_records(&mut buf); @@ -945,17 +953,89 @@ fn rejects_junk() { confirm_transmit_tls_data(server.process_tls_records(&mut [])); } +#[test] +fn read_traffic_not_consumed_too_early() { + let mut outcome = handshake(&rustls::version::TLS13); + let mut client = outcome.client.take().unwrap(); + let mut server = outcome.server.take().unwrap(); + + let mut client_to_server_buf = Buffer::default(); + write_traffic(client.process_tls_records(&mut []), |mut wt| { + encrypt(&mut wt, b"hello", &mut client_to_server_buf) + }); + + // if we just peek, we are presented the same data again + let (_, discard) = read_traffic( + server.process_tls_records(client_to_server_buf.filled()), + |rt| assert_eq!(rt.peek_len(), NonZeroUsize::new(5)), + ); + assert!(discard > 0); + client_to_server_buf.discard(discard); + + // ditto + let (_, discard) = read_traffic( + server.process_tls_records(client_to_server_buf.filled()), + |rt| assert_eq!(rt.peek_len(), NonZeroUsize::new(5)), + ); + assert_eq!(discard, 0); + + // now consume + let (data, discard) = read_traffic( + server.process_tls_records(client_to_server_buf.filled()), + |mut rt| { + rt.next_record() + .unwrap() + .unwrap() + .payload + .to_vec() + }, + ); + assert_eq!(discard, 0); + assert_eq!(data, b"hello"); + + // server is now idle + write_traffic( + server.process_tls_records(client_to_server_buf.filled()), + |_| (), + ); +} + 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)"); + match state.unwrap() { + ConnectionState::WriteTraffic(state) => f(state), + other => panic!("unexpected state {other:?} (wanted WriteTraffic)"), + } +} + +fn read_traffic) -> R>( + status: UnbufferedStatus<'_, '_, T>, + mut f: F, +) -> (R, usize) { + let UnbufferedStatus { discard, state } = status; + match state.unwrap() { + ConnectionState::ReadTraffic(state) => (f(state), discard), + other => panic!("unexpected state {other:?} (wanted ReadTraffic)"), + } +} + +fn peer_closed(status: UnbufferedStatus<'_, '_, T>) -> usize { + let UnbufferedStatus { discard, state } = status; + match state.unwrap() { + ConnectionState::PeerClosed => discard, + other => panic!("unexpected state {other:?} (wanted PeerClosed)"), + } +} + +fn closed(status: UnbufferedStatus<'_, '_, T>) -> usize { + let UnbufferedStatus { discard, state } = status; + match state.unwrap() { + ConnectionState::Closed => discard, + other => panic!("unexpected state {other:?} (wanted Closed)"), } } @@ -998,6 +1078,7 @@ fn confirm_transmit_tls_data(status: UnbufferedStatus<'_, '_, T>) { #[derive(Debug)] enum State { Closed, + PeerClosed, EncodedTlsData, TransmitTlsData { sent_app_data: bool, @@ -1044,11 +1125,11 @@ struct Outcome { server_transcript: Vec, server_received_early_data: Vec>, server_received_app_data: Vec>, - server_reached_connection_closed_state: bool, + server_saw_peer_closed_state: bool, client: Option, client_transcript: Vec, client_received_app_data: Vec>, - client_reached_connection_closed_state: bool, + client_saw_peer_closed_state: bool, } fn advance_client( @@ -1058,29 +1139,28 @@ fn advance_client( transcript: &mut Vec, ) -> State { let UnbufferedStatus { discard, state } = conn.process_tls_records(buffers.incoming.filled()); + let state = state.unwrap(); - transcript.push(format!("{:?}", state)); + transcript.push(format!("{state:?}")); - let state = match state.unwrap() { + let state = match state { 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; - } + if let (Some(early_data), Some(mut state)) = + (actions.early_data_to_send, state.may_encrypt_early_data()) + { + write_with_buffer_size_checks( + |out_buf| state.encrypt(early_data, out_buf), + |e| { + println!("encrypt error: {e}"); + match e { + EarlyDataError::Encrypt(EncryptError::InsufficientSize(ise)) => ise, + _ => unreachable!(), + } + }, + &mut buffers.outgoing, + ); + sent_early_data = true; } state.done(); State::TransmitTlsData { @@ -1104,10 +1184,11 @@ fn advance_server( transcript: &mut Vec, ) -> State { let UnbufferedStatus { discard, state } = conn.process_tls_records(buffers.incoming.filled()); + let state = state.unwrap(); - transcript.push(format!("{:?}", state)); + transcript.push(format!("{state:?}")); - let state = match state.unwrap() { + let state = match state { ConnectionState::ReadEarlyData(mut state) => { let mut records = vec![]; let mut peeked_len = state.peek_len(); @@ -1142,10 +1223,9 @@ fn handle_state( |out_buf| state.encode(out_buf), |e| { println!("encode error: {e}"); - if let EncodeError::InsufficientSize(ise) = e { - ise - } else { - unreachable!() + match e { + EncodeError::InsufficientSize(ise) => ise, + _ => unreachable!(), } }, outgoing, @@ -1161,11 +1241,11 @@ fn handle_state( 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; - } + if let (Some(app_data), Some(mut state)) = + (actions.app_data_to_send, state.may_encrypt_app_data()) + { + encrypt(&mut state, app_data, outgoing); + sent_app_data = true; } let mut sent_close_notify = false; @@ -1223,6 +1303,7 @@ fn handle_state( State::ReceivedAppData { records } } + ConnectionState::PeerClosed => State::PeerClosed, ConnectionState::Closed => State::Closed, _ => unreachable!(), @@ -1246,10 +1327,9 @@ fn encrypt(state: &mut WriteTraffic<'_, Data>, app_data: &[u8], outgoing: } fn map_encrypt_error(e: EncryptError) -> InsufficientSizeError { - if let EncryptError::InsufficientSize(ise) = e { - ise - } else { - unreachable!() + match e { + EncryptError::InsufficientSize(ise) => ise, + _ => unreachable!(), } } @@ -1351,8 +1431,9 @@ impl Buffer { fn make_connection_pair( version: &'static watfaq_rustls::SupportedProtocolVersion, ) -> (UnbufferedClientConnection, UnbufferedServerConnection) { - let server_config = make_server_config(KeyType::Rsa2048); - let client_config = make_client_config_with_versions(KeyType::Rsa2048, &[version]); + let provider = provider::default_provider(); + let server_config = make_server_config(KeyType::Rsa2048, &provider); + let client_config = make_client_config_with_versions(KeyType::Rsa2048, &[version], &provider); let client = UnbufferedClientConnection::new(Arc::new(client_config), server_name("localhost")).unwrap(); @@ -1364,7 +1445,7 @@ fn make_connection_pair( 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 mut client_hello_buffer = vec![0u8; 2048]; let UnbufferedStatus { discard, state } = client.process_tls_records(&mut []); assert_eq!(discard, 0); @@ -1378,7 +1459,7 @@ fn server_receives_handshake_byte_by_byte() { _ => panic!("unexpected first client event"), }; - println!("client hello: {:?}", client_hello_buffer); + println!("client hello: {client_hello_buffer:?}"); for prefix in 0..client_hello_buffer.len() - 1 { let UnbufferedStatus { discard, state } = @@ -1422,3 +1503,398 @@ fn server_receives_incorrect_first_handshake_message() { _ => panic!("unexpected alert sending state"), }; } + +/// 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 [ + 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.clone() + } + .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, &provider); + client_config.enable_secret_extraction = true; + + let mut outcome = run( + Arc::new(client_config), + &mut NO_ACTIONS.clone(), + server_config.clone(), + &mut NO_ACTIONS.clone(), + ); + + let client = outcome.client.take().unwrap(); + let server = outcome.server.take().unwrap(); + + // The handshake is finished, we're now able to extract traffic secrets + let client_secrets = client + .dangerous_into_kernel_connection() + .unwrap() + .0; + let server_secrets = server + .dangerous_into_kernel_connection() + .unwrap() + .0; + + // 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); + } +} + +// Tests for the kernel API. +// +// We don't have anything set up to actually encrypt/decrypt the connection +// content so these tests all just check that the updated traffic secrets are +// equivalent on each side of the connection, if supported by the protocol +// version. + +#[test] +fn kernel_err_on_secret_extraction_not_enabled() { + let provider = provider::default_provider(); + let server_config = make_server_config(KeyType::Rsa2048, &provider); + let server_config = Arc::new(server_config); + + let client_config = make_client_config(KeyType::Rsa2048, &provider); + let client_config = Arc::new(client_config); + + let mut server = UnbufferedServerConnection::new(server_config).unwrap(); + let mut client = + UnbufferedClientConnection::new(client_config, "localhost".try_into().unwrap()).unwrap(); + + do_unbuffered_handshake(&mut client, &mut server); + + assert!( + client + .dangerous_into_kernel_connection() + .is_err() + ); + assert!( + server + .dangerous_into_kernel_connection() + .is_err() + ); +} + +#[test] +fn kernel_err_on_handshake_not_complete() { + let provider = provider::default_provider(); + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); + server_config.enable_secret_extraction = true; + let server_config = Arc::new(server_config); + + let mut client_config = make_client_config(KeyType::Rsa2048, &provider); + client_config.enable_secret_extraction = true; + let client_config = Arc::new(client_config); + + let server = UnbufferedServerConnection::new(server_config).unwrap(); + let client = + UnbufferedClientConnection::new(client_config, "localhost".try_into().unwrap()).unwrap(); + + assert!(matches!( + client.dangerous_into_kernel_connection(), + Err(Error::HandshakeNotComplete) + )); + assert!(matches!( + server.dangerous_into_kernel_connection(), + Err(Error::HandshakeNotComplete) + )); +} + +#[test] +fn kernel_initial_traffic_secrets_match() { + let provider = provider::default_provider(); + let mut server_config = make_server_config(KeyType::Rsa2048, &provider); + server_config.enable_secret_extraction = true; + let server_config = Arc::new(server_config); + + let mut client_config = make_client_config(KeyType::Rsa2048, &provider); + client_config.enable_secret_extraction = true; + let client_config = Arc::new(client_config); + + let mut server = UnbufferedServerConnection::new(server_config).unwrap(); + let mut client = + UnbufferedClientConnection::new(client_config, "localhost".try_into().unwrap()).unwrap(); + + do_unbuffered_handshake(&mut client, &mut server); + + let (client_secrets, _) = client + .dangerous_into_kernel_connection() + .expect("failed to convert client connection to an KernelConnection"); + let (server_secrets, _) = server + .dangerous_into_kernel_connection() + .expect("failed to convert server connection to an KernelConnection"); + + assert_secrets_equal(client_secrets.tx, server_secrets.rx); + assert_secrets_equal(server_secrets.tx, client_secrets.rx); +} + +#[test] +fn kernel_key_updates_tls13() { + let provider = provider::default_provider(); + let mut server_config = + make_server_config_with_versions(KeyType::Rsa2048, &[&TLS13], &provider); + server_config.enable_secret_extraction = true; + let server_config = Arc::new(server_config); + + let mut client_config = + make_client_config_with_versions(KeyType::Rsa2048, &[&TLS13], &provider); + client_config.enable_secret_extraction = true; + let client_config = Arc::new(client_config); + + let mut server = UnbufferedServerConnection::new(server_config).unwrap(); + let mut client = + UnbufferedClientConnection::new(client_config, "localhost".try_into().unwrap()).unwrap(); + + do_unbuffered_handshake(&mut client, &mut server); + + let (_, mut client) = client + .dangerous_into_kernel_connection() + .expect("failed to convert client connection to an KernelConnection"); + let (_, mut server) = server + .dangerous_into_kernel_connection() + .expect("failed to convert server connection to an KernelConnection"); + + let new_client_tx = client.update_tx_secret().unwrap(); + let new_client_rx = client.update_rx_secret().unwrap(); + + let new_server_tx = server.update_tx_secret().unwrap(); + let new_server_rx = server.update_rx_secret().unwrap(); + + assert_secrets_equal(new_server_tx, new_client_rx); + assert_secrets_equal(new_server_rx, new_client_tx); +} + +#[test] +#[cfg(feature = "tls12")] +fn kernel_key_updates_tls12() { + use rustls::version::TLS12; + + let _ = env_logger::try_init(); + + let provider = provider::default_provider(); + let mut server_config = + make_server_config_with_versions(KeyType::Rsa2048, &[&TLS12], &provider); + server_config.enable_secret_extraction = true; + let server_config = Arc::new(server_config); + + let mut client_config = + make_client_config_with_versions(KeyType::Rsa2048, &[&TLS12], &provider); + client_config.enable_secret_extraction = true; + let client_config = Arc::new(client_config); + + let mut server = UnbufferedServerConnection::new(server_config).unwrap(); + let mut client = + UnbufferedClientConnection::new(client_config, "localhost".try_into().unwrap()).unwrap(); + + do_unbuffered_handshake(&mut client, &mut server); + + let (_, mut client) = client + .dangerous_into_kernel_connection() + .expect("failed to convert client connection to an KernelConnection"); + let (_, mut server) = server + .dangerous_into_kernel_connection() + .expect("failed to convert server connection to an KernelConnection"); + + // TLS 1.2 does not allow key updates so these should all error + assert!(client.update_tx_secret().is_err()); + assert!(client.update_rx_secret().is_err()); + + assert!(server.update_tx_secret().is_err()); + assert!(server.update_rx_secret().is_err()); +} + +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)); +} + +// 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"), + } +} + +const TLS12_CLIENT_TRANSCRIPT: &[&str] = &[ + "EncodeTlsData", + "TransmitTlsData", + "BlockedHandshake", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "TransmitTlsData", + "BlockedHandshake", + "BlockedHandshake", + "WriteTraffic", +]; + +const TLS12_SERVER_TRANSCRIPT: &[&str] = &[ + "BlockedHandshake", + "EncodeTlsData", + "TransmitTlsData", + "BlockedHandshake", + "BlockedHandshake", + "BlockedHandshake", + "EncodeTlsData", + "EncodeTlsData", + "TransmitTlsData", + "WriteTraffic", +]; + +const TLS12_CLIENT_TRANSCRIPT_FRAGMENTED: &[&str] = &[ + "EncodeTlsData", + "TransmitTlsData", + "BlockedHandshake", + "BlockedHandshake", + "BlockedHandshake", + "BlockedHandshake", + "BlockedHandshake", + "BlockedHandshake", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "TransmitTlsData", + "BlockedHandshake", + "BlockedHandshake", + "WriteTraffic", +]; + +const TLS12_SERVER_TRANSCRIPT_FRAGMENTED: &[&str] = &[ + "BlockedHandshake", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "TransmitTlsData", + "BlockedHandshake", + "BlockedHandshake", + "BlockedHandshake", + "EncodeTlsData", + "EncodeTlsData", + "TransmitTlsData", + "WriteTraffic", +]; + +const TLS13_CLIENT_TRANSCRIPT: &[&str] = &[ + "EncodeTlsData", + "TransmitTlsData", + "BlockedHandshake", + "EncodeTlsData", + "TransmitTlsData", + "EncodeTlsData", + "TransmitTlsData", + "WriteTraffic", + "WriteTraffic", +]; + +const TLS13_SERVER_TRANSCRIPT: &[&str] = &[ + "BlockedHandshake", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "TransmitTlsData", + "BlockedHandshake", + "EncodeTlsData", + "TransmitTlsData", + "WriteTraffic", +]; + +const TLS13_CLIENT_TRANSCRIPT_FRAGMENTED: &[&str] = &[ + "EncodeTlsData", + "TransmitTlsData", + "BlockedHandshake", + "EncodeTlsData", + "TransmitTlsData", + "BlockedHandshake", + "BlockedHandshake", + "BlockedHandshake", + "BlockedHandshake", + "BlockedHandshake", + "EncodeTlsData", + "TransmitTlsData", + "WriteTraffic", + "WriteTraffic", +]; + +const TLS13_SERVER_TRANSCRIPT_FRAGMENTED: &[&str] = &[ + "BlockedHandshake", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "EncodeTlsData", + "TransmitTlsData", + "BlockedHandshake", + "EncodeTlsData", + "TransmitTlsData", + "WriteTraffic", +]; 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..2c2ae277492 100644 --- a/website/content/perf/2024-12-17-pq-kx/index.md +++ b/website/content/perf/2024-12-17-pq-kx/index.md @@ -78,7 +78,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