diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..a7bce3105 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.toml text eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a697cf64..b0545fc47 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,10 +8,12 @@ on: push: branches: - main - - v4.x + env: RUSTFLAGS: -Dwarnings RUST_BACKTRACE: 1 + CARGO_INCREMENTAL: 0 + CARGO_PROFILE_DEV_DEBUG: 0 jobs: rustfmt: @@ -27,44 +29,41 @@ jobs: clippy: name: clippy runs-on: ubuntu-latest + env: + CARGO_HOME: ${{ github.workspace }}/.cache/cargo steps: - uses: actions/checkout@v4 with: submodules: 'recursive' - name: Install Rust - run: rustup update --no-self-update stable && rustup default stable && rustup component add clippy + run: rustup toolchain add stable --no-self-update --component clippy && rustup default stable - name: Get rust version id: rust-version + shell: bash run: | echo "version=$(rustc --version)" >> $GITHUB_OUTPUT - name: Cache cargo index uses: actions/cache@v4 with: - path: ~/.cargo/registry/index - key: index-${{ runner.os }}-${{ github.run_number }} - restore-keys: | - index-${{ runner.os }}- - - name: Create lockfile - run: cargo generate-lockfile - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry/cache - key: registry-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }} + path: | + .cache/cargo/registry/index + .cache/cargo/registry/cache + key: index-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.toml') }} + enableCrossOsArchive: true - name: Fetch dependencies run: cargo fetch - name: Cache target directory uses: actions/cache@v4 with: path: target - key: clippy-target-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }} + key: clippy-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }} - name: Run clippy run: cargo clippy --all --all-targets - name: Check docs - run: cargo doc --no-deps -p boring -p boring-sys --features rpk,pq-experimental,underscore-wildcards + run: cargo doc --no-deps -p boring -p boring-sys -p hyper-boring -p tokio-boring --features rpk,underscore-wildcards env: CARGO_BUILD_RUSTDOCFLAGS: "--cfg=docsrs" - RUST_BOOTSTRAP: 1 + RUSTC_BOOTSTRAP: 1 DOCS_RS: 1 - name: Cargo.toml boring versions consistency shell: bash @@ -82,6 +81,7 @@ jobs: matrix: thing: - stable + - i686-mingw - arm-android - arm64-android - i686-android @@ -121,6 +121,8 @@ jobs: rust: stable os: ubuntu-latest check_only: true + custom_env: + CXXFLAGS: -msse2 - thing: x86_64-android target: x86_64-linux-android rust: stable @@ -151,6 +153,8 @@ jobs: rust: stable os: ubuntu-latest apt_packages: gcc-multilib g++-multilib + custom_env: + CXXFLAGS: -msse2 - thing: arm-linux target: arm-unknown-linux-gnueabi rust: stable @@ -158,8 +162,8 @@ jobs: apt_packages: gcc-arm-linux-gnueabi g++-arm-linux-gnueabi check_only: true custom_env: - CC_arm-unknown-linux-gnueabi: arm-linux-gnueabi-gcc - CXX_arm-unknown-linux-gnueabi: arm-linux-gnueabi-g++ + CC: arm-linux-gnueabi-gcc + CXX: arm-linux-gnueabi-g++ CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER: arm-linux-gnueabi-g++ - thing: aarch64-linux target: aarch64-unknown-linux-gnu @@ -168,8 +172,8 @@ jobs: apt_packages: crossbuild-essential-arm64 check_only: true custom_env: - CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc - CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++ + CC: aarch64-linux-gnu-gcc + CXX: aarch64-linux-gnu-g++ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-g++ - thing: arm64-macos target: aarch64-apple-darwin @@ -191,29 +195,64 @@ jobs: C_INCLUDE_PATH: "C:\\msys64\\usr\\include" CPLUS_INCLUDE_PATH: "C:\\msys64\\usr\\include" LIBRARY_PATH: "C:\\msys64\\usr\\lib" + RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness + # CI's Windows doesn't have required root certs + extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness + - thing: i686-mingw + target: i686-pc-windows-gnu + rust: stable + os: windows-latest + check_only: true + custom_env: + RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness + CMAKE_GENERATOR: "MinGW Makefiles" + COLLECT_GCC: null # CI's Windows doesn't have required root certs - extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring + extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness - thing: i686-msvc target: i686-pc-windows-msvc rust: stable-x86_64-msvc os: windows-latest + custom_env: + RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness + CXXFLAGS: -msse2 # CI's Windows doesn't have required root certs - extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring + extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness - thing: x86_64-msvc target: x86_64-pc-windows-msvc rust: stable-x86_64-msvc os: windows-latest + custom_env: + RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness # CI's Windows doesn't have required root certs - extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring - + extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness + env: + CARGO_HOME: ${{ github.workspace }}/.cache/cargo + CARGO_BUILD_BUILD_DIR: ${{ github.workspace }}/.cache/build-dir steps: - uses: actions/checkout@v4 with: submodules: 'recursive' - name: Install Rust (rustup) - run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} + run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} && rustup target add ${{ matrix.target }} shell: bash - - run: rustup target add ${{ matrix.target }} + - name: Get rust version + id: rust-version + shell: bash + run: | + echo "version=$(rustc --version)" >> $GITHUB_OUTPUT + - name: Prepopulate cargo index + uses: actions/cache/restore@v4 + with: + path: | + .cache/cargo/registry/index + .cache/cargo/registry/cache + key: index-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.toml') }} + enableCrossOsArchive: true + - name: Install golang + uses: actions/setup-go@v5 + with: + go-version: '>=1.22.0' - name: Install target-specific APT dependencies if: "matrix.apt_packages != ''" run: sudo apt update && sudo apt install -y ${{ matrix.apt_packages }} @@ -222,6 +261,32 @@ jobs: if: startsWith(matrix.os, 'windows') run: choco install nasm shell: cmd + - name: Setup 32-bit MSYS2 + if: matrix.thing == 'i686-mingw' + uses: msys2/setup-msys2@v2 + id: msys2 + with: + msystem: MINGW32 + path-type: inherit + install: >- + mingw-w64-i686-gcc + mingw-w64-i686-cmake + - name: Setup 32-bit MSYS2 Env vars + if: matrix.thing == 'i686-mingw' + shell: bash + run: | + MSYS_ROOT='${{ steps.msys2.outputs.msys2-location }}' + test -d "$MSYS_ROOT\\mingw32\\bin" + echo >> $GITHUB_PATH "$MSYS_ROOT\\mingw32\\bin" + echo >> $GITHUB_PATH "$MSYS_ROOT\\usr\\bin" + echo >> $GITHUB_ENV CC="$MSYS_ROOT\\mingw32\\bin\\gcc" + echo >> $GITHUB_ENV CXX="$MSYS_ROOT\\mingw32\\bin\\g++" + echo >> $GITHUB_ENV AR="$MSYS_ROOT\\mingw32\\bin\\ar" + echo >> $GITHUB_ENV CFLAGS="-mlong-double-64 -I$MSYS_ROOT\\mingw32\\include" + echo >> $GITHUB_ENV CXXFLAGS="-mlong-double-64 -I$MSYS_ROOT\\mingw32\\include" + echo >> $GITHUB_ENV BINDGEN_EXTRA_CLANG_ARGS="-mlong-double-64 -I$MSYS_ROOT\\mingw32\\include" + echo >> $GITHUB_ENV LIBRARY_PATH="$MSYS_ROOT\\mingw32\\lib" + echo >> $GITHUB_ENV LDFLAGS="-L$MSYS_ROOT\\mingw32\\lib" - name: Install LLVM and Clang if: startsWith(matrix.os, 'windows') uses: KyleMayes/install-llvm-action@v1 @@ -234,12 +299,31 @@ jobs: - name: Set Android Linker path if: endsWith(matrix.thing, '-android') run: echo "CARGO_TARGET_$(echo ${{ matrix.target }} | tr \\-a-z _A-Z)_LINKER=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/$(echo ${{ matrix.target }} | sed s/armv7/armv7a/)21-clang++" >> "$GITHUB_ENV" + - name: Fetch deps + run: cargo fetch --target ${{ matrix.target }} + shell: bash + env: ${{ matrix.custom_env }} + # Windows builds are the slowest + - name: Cache deps in Windows tests + if: startsWith(matrix.os, 'windows') + uses: actions/cache/restore@v4 + id: test-cache-restore + with: + path: .cache/build-dir + key: wintest-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }} - name: Build tests # We `build` because we want the linker to verify we are cross-compiling correctly for check-only targets. - run: cargo build --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }} + run: cargo build -vv --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }} shell: bash env: ${{ matrix.custom_env }} - - name: Run tests + # By default it'd be saved after later cargo calls, which already invalidated the cache + - name: Cache deps in Windows tests + if: startsWith(matrix.os, 'windows') + uses: actions/cache/save@v4 + with: + path: .cache/build-dir + key: ${{ steps.test-cache-restore.outputs.cache-primary-key }} + - name: Run tests (skip=${{ matrix.check_only }}) if: "!matrix.check_only" run: cargo test --target ${{ matrix.target }} ${{ matrix.extra_test_args }} shell: bash @@ -255,7 +339,9 @@ jobs: # # Both of these may no longer be the case after updating the BoringSSL # submodules to a new revision, so it's important to test this on CI. - run: cargo publish --dry-run -p boring-sys + run: cargo publish --dry-run --target ${{ matrix.target }} -p boring-sys + shell: bash + env: ${{ matrix.custom_env }} test-fips: name: Test FIPS integration @@ -267,18 +353,10 @@ jobs: - name: Install Rust (rustup) run: rustup update stable --no-self-update && rustup default stable shell: bash - - name: Install Clang-12 - uses: KyleMayes/install-llvm-action@v1 - with: - version: "12.0.0" - directory: ${{ runner.temp }}/llvm - name: Install golang uses: actions/setup-go@v5 with: go-version: '>=1.22.0' - - name: Add clang++-12 link - working-directory: ${{ runner.temp }}/llvm/bin - run: ln -s clang clang++-12 - name: Run tests run: cargo test --features fips - name: Test boring-sys cargo publish (FIPS) @@ -306,8 +384,12 @@ jobs: with: submodules: 'recursive' - name: Install Rust (rustup) - run: rustup update stable --no-self-update && rustup default stable && rustup target add ${{ matrix.target }} + run: rustup toolchain install stable --no-self-update --profile minimal --target ${{ matrix.target }} && rustup default stable shell: bash + - name: Install golang + uses: actions/setup-go@v5 + with: + go-version: '>=1.22.0' - name: Install ${{ matrix.target }} toolchain run: brew tap messense/macos-cross-toolchains && brew install ${{ matrix.target }} - name: Set BORING_BSSL_SYSROOT @@ -315,12 +397,17 @@ jobs: shell: bash - name: Set CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER run: echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=${{ matrix.target }}-gcc" >> $GITHUB_ENV + shell: bash + - name: Set CXXFLAGS + run: echo "CXXFLAGS=-D__STDC_FORMAT_MACROS" >> $GITHUB_ENV - name: Build for ${{ matrix.target }} run: cargo build --target ${{ matrix.target }} --all-targets test-features: name: Test features runs-on: ubuntu-latest + env: + CARGO_INCREMENTAL: 1 steps: - uses: actions/checkout@v4 with: @@ -330,19 +417,7 @@ jobs: shell: bash - run: cargo test --features rpk name: Run `rpk` tests - - run: cargo test --features pq-experimental - name: Run `pq-experimental` tests - run: cargo test --features underscore-wildcards name: Run `underscore-wildcards` tests - - run: cargo test --features pq-experimental,rpk - name: Run `pq-experimental,rpk` tests - - run: cargo test --features kx-safe-default,pq-experimental - name: Run `kx-safe-default` tests - - run: cargo test --features pq-experimental,underscore-wildcards - name: Run `pq-experimental,underscore-wildcards` tests - run: cargo test --features rpk,underscore-wildcards name: Run `rpk,underscore-wildcards` tests - - run: cargo test --features pq-experimental,rpk,underscore-wildcards - name: Run `pq-experimental,rpk,underscore-wildcards` tests - - run: cargo test -p hyper-boring --features hyper1 - name: Run hyper 1.0 tests for hyper-boring diff --git a/.gitmodules b/.gitmodules index 93bfb089f..c1e5a527e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,6 +2,3 @@ path = boring-sys/deps/boringssl url = https://github.com/google/boringssl.git ignore = dirty -[submodule "boring-sys/deps/boringssl-fips"] - path = boring-sys/deps/boringssl-fips - url = https://github.com/google/boringssl.git diff --git a/Cargo.toml b/Cargo.toml index afa530053..80b803228 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,8 @@ members = [ resolver = "2" [workspace.package] -version = "4.21.1" +version = "5.0.0" +rust-version = "1.85" repository = "https://github.com/cloudflare/boring" edition = "2021" @@ -19,9 +20,9 @@ tag-prefix = "" publish = false [workspace.dependencies] -boring-sys = { version = "4.21.1", path = "./boring-sys" } -boring = { version = "4.21.1", path = "./boring" } -tokio-boring = { version = "4.21.1", path = "./tokio-boring" } +boring-sys = { version = "5.0.0", path = "./boring-sys" } +boring = { version = "5.0.0", path = "./boring" } +tokio-boring = { version = "5.0.0", path = "./tokio-boring" } bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] } bitflags = "2.9" @@ -40,10 +41,8 @@ anyhow = "1" antidote = "1.0.0" http = "1" http-body-util = "0.1.2" -http_old = { package = "http", version = "0.2" } hyper = "1" hyper-util = "0.1.6" -hyper_old = { package = "hyper", version = "0.14", default-features = false } linked_hash_set = "0.1" openssl-macros = "0.1.1" tower = "0.4" diff --git a/README.md b/README.md index 37ebcc633..9f948242b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ [![crates.io](https://img.shields.io/crates/v/boring.svg)](https://crates.io/crates/boring) -BoringSSL bindings for the Rust programming language and TLS adapters for [tokio](https://github.com/tokio-rs/tokio) +[BoringSSL](https://boringssl.googlesource.com/boringssl) is Google's fork of OpenSSL for Chrome/Chromium and Android. + +This crate provides safe bindings for the Rust programming language and TLS adapters for [tokio](https://github.com/tokio-rs/tokio) and [hyper](https://github.com/hyperium/hyper) built on top of it. ## Documentation @@ -11,6 +13,19 @@ and [hyper](https://github.com/hyperium/hyper) built on top of it. - hyper HTTPS connector: - FFI bindings: +# Upgrading from `boring` v4 + + * First update to boring 4.21 and ensure it builds without any deprecation warnings. + * `pq-experimental` Cargo feature is no longer needed. Post-quantum crypto is enabled by default. + * `fips-precompiled` Cargo feature has been merged into `fips`. Set `BORING_BSSL_FIPS_PATH` env var to use a precompiled library. + * `fips-compat` Cargo feature has been renamed to `legacy-compat-deprecated` (4cb7e260a85b7) + * `SslCurve` and `SslCurveNid` have been removed. Curve names are more stable and portable identifiers. Use `curve_name()` and `set_curves_list()`. + * `Ssl::new_from_ref` -> `Ssl::new()`. + * `X509Builder::append_extension2` -> `X509Builder::append_extension`. + * `X509Store` is now cheaply cloneable, but immutable. `SslContextBuilder.cert_store_mut()` can't be used after `.set_cert_store()`. If you need `.cert_store_mut()`, either don't overwrite the default store, or use `.set_cert_store_builder()`. + * `X509StoreBuilder::add_cert` takes a reference. + * `hyper` 0.x support has been removed. Use `hyper` 1.x. + ## Contribution Unless you explicitly state otherwise, any contribution intentionally diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 923c57880..df122b521 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,5 +1,27 @@ -4.21.0 +5.0.0 +- 2025-12-19 Update vendored boring to a newer version (2023.11 to 2025.11) +- 2025-12-20 Rework RPK/SslMethod (c2f063cf4711f15b8b417b6926496fbf1c2a03ac) +- 2025-09-29 Remove `SslCurve` API +- 2025-09-30 Remove the "kx-*" features +- 2025-09-25 Remove legacy FIPS options (they're controlled via `BORING_BSSL_` env vars instead) +- 2026-01-05 Remove deprecated X509CheckFlags flag +- 2025-09-30 Remove "pq-experimental" Cargo feature, apply PQ patch by default + P256Kyber768Draft00 +- 2026-01-05 Safe clone for X509Store +- 2025-03-08 Add set_ticket_key_callback (SSL_CTX_set_tlsext_ticket_key_cb) +- 2025-09-30 Add SslRef::curve_name() +- 2025-09-30 Expose a safe Rust interface for the session resumption callback +- 2026-01-05 Fix leaky set_ex_data() API +- 2025-12-12 Add boring specific api set_strict_cipher_list to SslContextBuilder +- 2025-11-20 Introduce SslCipherRef::protocol_id +- 2023-05-11 fix: BIO_set_retry_write when BIO_CTRL_FLUSH to allow writer returns WouldBlock on flush +- 2025-11-14 Remove blanket Eq from FFI types +- 2025-12-20 Never use the debug CRT on Windows +- 2025-02-19 X509Builder::append_extension2 -> X509Builder::append_extension +- 2025-02-19 `Ssl::new_from_ref` -> `Ssl::new()` +- 2025-02-19 Align SslStream APIs with upstream +- 2025-09-26 Remove support for Hyper v0 +4.21.0 - 2026-01-05 Warn about set_curves() removal - 2026-01-05 Deprecate set_ex_data() - 2026-01-05 Fix build with --no-default-features diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index 166d62f55..72845c85e 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -12,49 +12,38 @@ links = "boringssl" build = "build/main.rs" readme = "README.md" categories = ["cryptography", "external-ffi-bindings"] +keywords = ["tls", "boringssl", "openssl", "fips", "ml-kem"] edition = { workspace = true } -rust-version = "1.77" +rust-version = { workspace = true } include = [ "/*.md", "/*.toml", "/LICENSE-MIT", "/cmake/*.cmake", - # boringssl (non-FIPS) - "/deps/boringssl/src/util/32-bit-toolchain.cmake", "/deps/boringssl/**/*.[chS]", + "/deps/boringssl/**/*.inc", "/deps/boringssl/**/*.asm", - "/deps/boringssl/sources.json", - "/deps/boringssl/src/crypto/obj/obj_mac.num", - "/deps/boringssl/src/crypto/obj/objects.txt", + "/deps/boringssl/**/*.pl", + "/deps/boringssl/**/*.go", + "/deps/boringssl/**/*.cmake", + "/deps/boringssl/**/go.mod", + "/deps/boringssl/**/go.sum", + "/deps/boringssl/crypto/obj/obj_mac.num", + "/deps/boringssl/crypto/obj/objects.txt", + "/deps/boringssl/crypto/err/*.errordata", "/deps/boringssl/**/*.bzl", - "/deps/boringssl/src/**/*.cc", + "/deps/boringssl/**/*.cc", "/deps/boringssl/**/CMakeLists.txt", "/deps/boringssl/**/sources.cmake", + "/deps/boringssl/**/util/go_tests.txt", "/deps/boringssl/LICENSE", - # boringssl (FIPS) - "/deps/boringssl-fips/src/util/32-bit-toolchain.cmake", - "/deps/boringssl-fips/**/*.[chS]", - "/deps/boringssl-fips/**/*.asm", - "/deps/boringssl-fips/**/*.pl", - "/deps/boringssl-fips/**/*.go", - "/deps/boringssl-fips/**/go.mod", - "/deps/boringssl-fips/**/go.sum", - "/deps/boringssl-fips/sources.json", - "/deps/boringssl-fips/crypto/obj/obj_mac.num", - "/deps/boringssl-fips/crypto/obj/objects.txt", - "/deps/boringssl-fips/crypto/err/*.errordata", - "/deps/boringssl-fips/**/*.bzl", - "/deps/boringssl-fips/**/*.cc", - "/deps/boringssl-fips/**/CMakeLists.txt", - "/deps/boringssl-fips/**/sources.cmake", - "/deps/boringssl-fips/LICENSE", "/build/*", "/src", "/patches", ] [package.metadata.docs.rs] -features = ["rpk", "pq-experimental", "underscore-wildcards"] +features = ["rpk", "underscore-wildcards"] rustdoc-args = ["--cfg", "docsrs"] [features] @@ -66,27 +55,15 @@ rustdoc-args = ["--cfg", "docsrs"] # for instructions and more details on the boringssl FIPS flag. fips = [] -# Use a precompiled FIPS-validated version of BoringSSL. Meant to be used with -# FIPS-20230428 or newer. Users must set `BORING_BSSL_FIPS_PATH` to use this -# feature, or else the build will fail. -fips-precompiled = [] - -# Link with precompiled FIPS-validated `bcm.o` module. -fips-link-precompiled = [] - # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) rpk = [] -# Applies a patch (`patches/boring-pq.patch`) to the boringSSL source code that -# enables support for PQ key exchange. This feature is necessary in order to -# compile the bindings for the default branch of boringSSL (`deps/boringssl`). -# Alternatively, a version of boringSSL that implements the same feature set -# can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH`. -pq-experimental = [] - # Applies a patch (`patches/underscore-wildcards.patch`) to enable -# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. Same caveats as -# those for `pq-experimental` feature apply. +# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. This feature is necessary in +# order to compile the bindings for the default branch of boringSSL +# (`deps/boringssl`). Alternatively, a version of boringSSL that implements the +# same feature set can be provided by setting +# `BORING_BSSL{,_FIPS}_SOURCE_PATH`. underscore-wildcards = [] [build-dependencies] diff --git a/boring-sys/README.md b/boring-sys/README.md deleted file mode 120000 index 32d46ee88..000000000 --- a/boring-sys/README.md +++ /dev/null @@ -1 +0,0 @@ -../README.md \ No newline at end of file diff --git a/boring-sys/README.md b/boring-sys/README.md new file mode 100644 index 000000000..fe4d7db20 --- /dev/null +++ b/boring-sys/README.md @@ -0,0 +1,15 @@ +# Low-level bindings to BoringSSL + +[BoringSSL](https://boringssl.googlesource.com/boringssl) is Google's fork of OpenSSL for Chrome/Chromium and Android. + +This crate builds the BoringSSL library (or optionally links a pre-built version) and generates FFI bindings for it. +It supports FIPS-compatible builds of BoringSSL, as well as Post-Quantum crypto and Raw Public Key features. + +To use BoringSSL from Rust, prefer the [higher-level safe API](https://docs.rs/boring). + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed under the terms of both the Apache License, +Version 2.0 and the MIT license without any additional terms or conditions. diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index 5d93eda4a..a95416b31 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -10,15 +10,15 @@ pub(crate) struct Config { pub(crate) target: String, pub(crate) target_arch: String, pub(crate) target_os: String, + pub(crate) unix: bool, + pub(crate) target_env: String, + pub(crate) target_features: Vec, pub(crate) features: Features, pub(crate) env: Env, } pub(crate) struct Features { pub(crate) fips: bool, - pub(crate) fips_precompiled: bool, - pub(crate) fips_link_precompiled: bool, - pub(crate) pq_experimental: bool, pub(crate) rpk: bool, pub(crate) underscore_wildcards: bool, } @@ -27,7 +27,6 @@ pub(crate) struct Env { pub(crate) path: Option, pub(crate) include_path: Option, pub(crate) source_path: Option, - pub(crate) precompiled_bcm_o: Option, pub(crate) assume_patched: bool, pub(crate) sysroot: Option, pub(crate) compiler_external_toolchain: Option, @@ -36,9 +35,6 @@ pub(crate) struct Env { pub(crate) android_ndk_home: Option, pub(crate) cmake_toolchain_file: Option, pub(crate) cpp_runtime_lib: Option, - /// C compiler (ignored if using FIPS) - pub(crate) cc: Option, - pub(crate) cxx: Option, pub(crate) docs_rs: bool, } @@ -50,6 +46,14 @@ impl Config { let target = env::var("TARGET").unwrap(); let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap(); + let unix = env::var("CARGO_CFG_UNIX").is_ok(); + + let target_features = env::var("CARGO_CFG_TARGET_FEATURE") + .unwrap() + .split(',') + .map(|s| s.to_owned()) + .collect(); let features = Features::from_env(); let env = Env::from_env(&host, &target, features.is_fips_like()); @@ -67,6 +71,9 @@ impl Config { target, target_arch, target_os, + unix, + target_env, + target_features, features, env, }; @@ -81,10 +88,6 @@ impl Config { panic!("`fips` and `rpk` features are mutually exclusive"); } - if self.features.fips_precompiled && self.features.rpk { - panic!("`fips-precompiled` and `rpk` features are mutually exclusive"); - } - let is_precompiled_native_lib = self.env.path.is_some(); let is_external_native_lib_source = !is_precompiled_native_lib && self.env.source_path.is_none(); @@ -96,9 +99,7 @@ impl Config { ); } - let features_with_patches_enabled = self.features.rpk - || self.features.pq_experimental - || self.features.underscore_wildcards; + let features_with_patches_enabled = self.features.rpk || self.features.underscore_wildcards; let patches_required = features_with_patches_enabled && !self.env.assume_patched; @@ -107,88 +108,77 @@ impl Config { "cargo:warning=precompiled BoringSSL was provided, so patches will be ignored" ); } - - // todo(rmehra): should this even be a restriction? why not let people link a custom bcm.o? - // precompiled boringssl will include libcrypto.a - if is_precompiled_native_lib && self.features.fips_link_precompiled { - panic!("precompiled BoringSSL was provided, so FIPS configuration can't be applied"); - } - - if !is_precompiled_native_lib && self.features.fips_precompiled { - panic!("`fips-precompiled` feature requires `BORING_BSSL_FIPS_PATH` to be set"); - } } } impl Features { fn from_env() -> Self { let fips = env::var_os("CARGO_FEATURE_FIPS").is_some(); - let fips_precompiled = env::var_os("CARGO_FEATURE_FIPS_PRECOMPILED").is_some(); - let fips_link_precompiled = env::var_os("CARGO_FEATURE_FIPS_LINK_PRECOMPILED").is_some(); - let pq_experimental = env::var_os("CARGO_FEATURE_PQ_EXPERIMENTAL").is_some(); let rpk = env::var_os("CARGO_FEATURE_RPK").is_some(); let underscore_wildcards = env::var_os("CARGO_FEATURE_UNDERSCORE_WILDCARDS").is_some(); Self { fips, - fips_precompiled, - fips_link_precompiled, - pq_experimental, rpk, underscore_wildcards, } } pub(crate) fn is_fips_like(&self) -> bool { - self.fips || self.fips_precompiled || self.fips_link_precompiled + self.fips } } impl Env { fn from_env(host: &str, target: &str, is_fips_like: bool) -> Self { - const NORMAL_PREFIX: &str = "BORING_BSSL"; - const FIPS_PREFIX: &str = "BORING_BSSL_FIPS"; - - let var_prefix = if host == target { "HOST" } else { "TARGET" }; let target_with_underscores = target.replace('-', "_"); - let target_only_var = |name: &str| { + // Logic stolen from cmake-rs. + let target_var = |name: &str| { + let kind = if host == target { "HOST" } else { "TARGET" }; + + // TODO(rmehra): look for just `name` first, as most people just set that var(&format!("{name}_{target}")) .or_else(|| var(&format!("{name}_{target_with_underscores}"))) - .or_else(|| var(&format!("{var_prefix}_{name}"))) + .or_else(|| var(&format!("{kind}_{name}"))) + .or_else(|| var(name)) }; - let target_var = |name: &str| target_only_var(name).or_else(|| var(name)); let boringssl_var = |name: &str| { + const BORING_BSSL_PREFIX: &str = "BORING_BSSL_"; + const BORING_BSSL_FIPS_PREFIX: &str = "BORING_BSSL_FIPS_"; + // The passed name is the non-fips version of the environment variable, // to help look for them in the repository. - assert!(name.starts_with(NORMAL_PREFIX)); + assert!(name.starts_with(BORING_BSSL_PREFIX)); + let non_fips = target_var(name); if is_fips_like { - target_var(&name.replace(NORMAL_PREFIX, FIPS_PREFIX)) + let fips_name = name.replace(BORING_BSSL_PREFIX, BORING_BSSL_FIPS_PREFIX); + let fips = target_var(&fips_name); + if fips.is_none() && non_fips.is_some() { + println!("cargo:warning=env var {name} ignored, because FIPS is enabled. Set {fips_name} instead."); + } + fips } else { - target_var(name) + non_fips } }; Self { - path: boringssl_var("BORING_BSSL_PATH").map(PathBuf::from), - include_path: boringssl_var("BORING_BSSL_INCLUDE_PATH").map(PathBuf::from), - source_path: boringssl_var("BORING_BSSL_SOURCE_PATH").map(PathBuf::from), - precompiled_bcm_o: boringssl_var("BORING_BSSL_PRECOMPILED_BCM_O").map(PathBuf::from), - assume_patched: boringssl_var("BORING_BSSL_ASSUME_PATCHED") + path: boringssl_var("BORING_BSSL_PATH").map(PathBuf::from), // gets BORING_BSSL_FIPS_PATH if fips is enabled + include_path: boringssl_var("BORING_BSSL_INCLUDE_PATH").map(PathBuf::from), // gets BORING_BSSL_FIPS_INCLUDE_PATH if fips is enabled + source_path: boringssl_var("BORING_BSSL_SOURCE_PATH").map(PathBuf::from), // gets BORING_BSSL_FIPS_SOURCE_PATH if fips is enabled + assume_patched: boringssl_var("BORING_BSSL_ASSUME_PATCHED") // gets BORING_BSSL_FIPS_ASSUME_PATCHED if fips is enabled .is_some_and(|v| !v.is_empty()), - sysroot: boringssl_var("BORING_BSSL_SYSROOT").map(PathBuf::from), - compiler_external_toolchain: boringssl_var("BORING_BSSL_COMPILER_EXTERNAL_TOOLCHAIN") + sysroot: boringssl_var("BORING_BSSL_SYSROOT").map(PathBuf::from), // gets BORING_BSSL_FIPS_SYSROOT if fips is enabled + compiler_external_toolchain: boringssl_var("BORING_BSSL_COMPILER_EXTERNAL_TOOLCHAIN") // gets BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN if fips is enabled .map(PathBuf::from), debug: target_var("DEBUG"), opt_level: target_var("OPT_LEVEL"), android_ndk_home: target_var("ANDROID_NDK_HOME").map(Into::into), cmake_toolchain_file: target_var("CMAKE_TOOLCHAIN_FILE").map(Into::into), cpp_runtime_lib: target_var("BORING_BSSL_RUST_CPPLIB"), - // matches the `cc` crate - cc: target_only_var("CC"), - cxx: target_only_var("CXX"), docs_rs: var("DOCS_RS").is_some(), } } diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 5879ede4b..c034fb931 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -1,5 +1,4 @@ use fslock::LockFile; -use std::env; use std::ffi::OsString; use std::fs; use std::io; @@ -50,6 +49,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[ &[ ("CMAKE_OSX_ARCHITECTURES", "arm64"), ("CMAKE_OSX_SYSROOT", "iphoneos"), + ("CMAKE_MACOSX_BUNDLE", "OFF"), ], ), ( @@ -57,6 +57,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[ &[ ("CMAKE_OSX_ARCHITECTURES", "arm64"), ("CMAKE_OSX_SYSROOT", "iphonesimulator"), + ("CMAKE_MACOSX_BUNDLE", "OFF"), ], ), ( @@ -64,6 +65,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[ &[ ("CMAKE_OSX_ARCHITECTURES", "x86_64"), ("CMAKE_OSX_SYSROOT", "iphonesimulator"), + ("CMAKE_MACOSX_BUNDLE", "OFF"), ], ), // macOS @@ -106,19 +108,21 @@ fn get_apple_sdk_name(config: &Config) -> &'static str { } /// Returns an absolute path to the BoringSSL source. -fn get_boringssl_source_path(config: &Config) -> &PathBuf { - if let Some(src_path) = &config.env.source_path { - return src_path; - } - +fn get_boringssl_source_path(config: &Config) -> &Path { static SOURCE_PATH: OnceLock = OnceLock::new(); SOURCE_PATH.get_or_init(|| { - let submodule_dir = if config.features.fips { - "boringssl-fips" - } else { - "boringssl" - }; + if let Some(src_path) = &config.env.source_path { + if !src_path.exists() { + println!( + "cargo:warning=boringssl source path doesn't exist: {}", + src_path.display() + ); + } + return src_path.into(); + } + + let submodule_dir = "boringssl"; let src_path = config.out_dir.join(submodule_dir); @@ -132,11 +136,15 @@ fn get_boringssl_source_path(config: &Config) -> &PathBuf { .args(["submodule", "update", "--init", "--recursive"]) .arg(&submodule_path), ) - .unwrap(); + .expect("git submodule update"); } let _ = fs::remove_dir_all(&src_path); - fs_extra::dir::copy(submodule_path, &config.out_dir, &Default::default()).unwrap(); + fs_extra::dir::copy(submodule_path, &config.out_dir, &Default::default()) + .inspect_err(|_| { + let _ = fs::remove_dir_all(&config.out_dir); + }) + .expect("copying failed. Try running `cargo clean`"); // NOTE: .git can be both file and dir, depening on whether it was copied from a submodule // or created by the patches code. @@ -200,11 +208,23 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { let src_path = get_boringssl_source_path(config); let mut boringssl_cmake = cmake::Config::new(src_path); - if config.host == config.target { + if config.env.cmake_toolchain_file.is_some() { return boringssl_cmake; } - if config.env.cmake_toolchain_file.is_some() { + if config.target_os == "windows" { + // Explicitly use the non-debug CRT. + // This is required now because newest BoringSSL requires CMake 3.22 which + // uses the new logic with CMAKE_MSVC_RUNTIME_LIBRARY introduced in CMake 3.15. + // https://github.com/rust-lang/cmake-rs/pull/30#issuecomment-2969758499 + if config.target_features.iter().any(|f| f == "crt-static") { + boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded"); + } else { + boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreadedDLL"); + } + } + + if config.host == config.target { return boringssl_cmake; } @@ -216,15 +236,6 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { .define("CMAKE_ASM_COMPILER_TARGET", &config.target); } - if !config.features.fips { - if let Some(cc) = &config.env.cc { - boringssl_cmake.define("CMAKE_C_COMPILER", cc); - } - if let Some(cxx) = &config.env.cxx { - boringssl_cmake.define("CMAKE_CXX_COMPILER", cxx); - } - } - if let Some(sysroot) = &config.env.sysroot { boringssl_cmake.define("CMAKE_SYSROOT", sysroot); } @@ -255,8 +266,8 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { boringssl_cmake.define("CMAKE_TOOLCHAIN_FILE", toolchain_file); // 21 is the minimum level tested. You can give higher value. - boringssl_cmake.define("ANDROID_NATIVE_API_LEVEL", "21"); - boringssl_cmake.define("ANDROID_STL", "c++_shared"); + boringssl_cmake.define("CMAKE_SYSTEM_VERSION", "21"); + boringssl_cmake.define("CMAKE_ANDROID_STL_TYPE", "c++_shared"); } "macos" => { @@ -304,7 +315,7 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { config .manifest_dir .join(src_path) - .join("src/util/32-bit-toolchain.cmake") + .join("util/32-bit-toolchain.cmake") .as_os_str(), ); } @@ -340,55 +351,6 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { boringssl_cmake } -/// Verify that the toolchains match -/// See "Installation Instructions" under section 12.1. -// TODO: maybe this should also verify the Go and Ninja versions? But those haven't been an issue in practice ... -fn verify_fips_clang_version() -> (&'static str, &'static str) { - fn version(tool: &str) -> Option { - let output = match Command::new(tool).arg("--version").output() { - Ok(o) => o, - Err(e) => { - println!("cargo:warning=missing {tool}, trying other compilers: {e}"); - // NOTE: hard-codes that the loop below checks the version - return None; - } - }; - if !output.status.success() { - return Some(String::new()); - } - let output = std::str::from_utf8(&output.stdout).expect("invalid utf8 output"); - Some(output.lines().next().expect("empty output").to_string()) - } - - const REQUIRED_CLANG_VERSION: &str = "12.0.0"; - for (cc, cxx) in [ - ("clang-12", "clang++-12"), - ("clang", "clang++"), - ("cc", "c++"), - ] { - let (Some(cc_version), Some(cxx_version)) = (version(cc), version(cxx)) else { - continue; - }; - - if cc_version.contains(REQUIRED_CLANG_VERSION) { - assert!( - cxx_version.contains(REQUIRED_CLANG_VERSION), - "mismatched versions of cc and c++" - ); - return (cc, cxx); - } else if cc == "cc" { - panic!( - "unsupported clang version \"{cc_version}\": FIPS requires clang {REQUIRED_CLANG_VERSION}" - ); - } else if !cc_version.is_empty() { - println!( - "cargo:warning=FIPS requires clang version {REQUIRED_CLANG_VERSION}, skipping incompatible version \"{cc_version}\"" - ); - } - } - unreachable!() -} - fn pick_best_android_ndk_toolchain(toolchains_dir: &Path) -> std::io::Result { let toolchains = std::fs::read_dir(toolchains_dir)?.collect::, _>>()?; // First look for one of the toolchains that Google has documented. @@ -420,31 +382,23 @@ fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec { let mut params = Vec::new(); // Add platform-specific parameters. - #[allow(clippy::single_match)] match &*config.target_os { "ios" | "macos" => { // When cross-compiling for Apple targets, tell bindgen to use SDK sysroot, // and *don't* use system headers of the host macOS. let sdk = get_apple_sdk_name(config); - let output = std::process::Command::new("xcrun") - .args(["--show-sdk-path", "--sdk", sdk]) - .output() - .unwrap(); - if !output.status.success() { - if let Some(exit_code) = output.status.code() { - println!("cargo:warning=xcrun failed: exit code {exit_code}"); - } else { - println!("cargo:warning=xcrun failed: killed"); + match run_command(Command::new("xcrun").args(["--show-sdk-path", "--sdk", sdk])) { + Ok(output) => { + let sysroot = std::str::from_utf8(&output.stdout).expect("xcrun output"); + params.push("-isysroot".to_string()); + // There is typically a newline at the end which confuses clang. + params.push(sysroot.trim_end().to_string()); + } + Err(e) => { + println!("cargo:warning={e}"); + // Uh... let's try anyway, I guess? } - std::io::stderr().write_all(&output.stderr).unwrap(); - // Uh... let's try anyway, I guess? - return params; } - let mut sysroot = String::from_utf8(output.stdout).unwrap(); - // There is typically a newline at the end which confuses clang. - sysroot.truncate(sysroot.trim_end().len()); - params.push("-isysroot".to_string()); - params.push(sysroot); } "android" => { let mut android_sysroot = config @@ -455,20 +409,18 @@ fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec { android_sysroot.extend(["toolchains", "llvm", "prebuilt"]); - let toolchain = match pick_best_android_ndk_toolchain(&android_sysroot) { - Ok(toolchain) => toolchain, + match pick_best_android_ndk_toolchain(&android_sysroot) { + Ok(toolchain) => { + android_sysroot.push(toolchain); + android_sysroot.push("sysroot"); + params.push("--sysroot".to_string()); + params.push(android_sysroot.into_os_string().into_string().unwrap()); + } Err(e) => { - println!( - "cargo:warning=failed to find prebuilt Android NDK toolchain for bindgen: {e}" - ); + println!("cargo:warning=failed to find prebuilt Android NDK toolchain for bindgen: {e}"); // Uh... let's try anyway, I guess? - return params; } - }; - android_sysroot.push(toolchain); - android_sysroot.push("sysroot"); - params.push("--sysroot".to_string()); - params.push(android_sysroot.into_os_string().into_string().unwrap()); + } } _ => {} } @@ -484,14 +436,12 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> { ); return Ok(()); } else if config.env.source_path.is_some() - && (config.features.rpk - || config.features.pq_experimental - || config.features.underscore_wildcards) + && (config.features.rpk || config.features.underscore_wildcards) { panic!( "BORING_BSSL_ASSUME_PATCHED must be set when setting BORING_BSSL_SOURCE_PATH and using any of the following - features: rpk, pq-experimental, underscore-wildcards" + features: rpk, underscore-wildcards" ); } @@ -506,10 +456,8 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> { run_command(Command::new("git").arg("init").current_dir(src_path))?; } - if config.features.pq_experimental { - println!("cargo:warning=applying experimental post quantum crypto patch to boringssl"); - apply_patch(config, "boring-pq.patch")?; - } + println!("cargo:warning=applying post quantum crypto patch to boringssl"); + apply_patch(config, "boring-pq.patch")?; if config.features.rpk { println!("cargo:warning=applying RPK patch to boringssl"); @@ -554,10 +502,18 @@ fn apply_patch(config: &Config, patch_name: &str) -> io::Result<()> { } fn run_command(command: &mut Command) -> io::Result { - let out = command.output()?; - - println!("{}", std::str::from_utf8(&out.stdout).unwrap()); - eprintln!("{}", std::str::from_utf8(&out.stderr).unwrap()); + let out = command.output().map_err(|e| { + io::Error::new( + e.kind(), + format!( + "can't run {}: {e}\n{command:?} failed", + command.get_program().to_string_lossy(), + ), + ) + })?; + + std::io::stderr().write_all(&out.stderr)?; + std::io::stdout().write_all(&out.stdout)?; if !out.status.success() { let err = match out.status.code() { @@ -572,13 +528,16 @@ fn run_command(command: &mut Command) -> io::Result { } fn built_boring_source_path(config: &Config) -> &PathBuf { - if let Some(path) = &config.env.path { - return path; - } - static BUILD_SOURCE_PATH: OnceLock = OnceLock::new(); BUILD_SOURCE_PATH.get_or_init(|| { + if let Some(path) = &config.env.path { + if !path.exists() { + println!("cargo:warning=built path doesn't exist: {}", path.display()); + } + return path.into(); + } + let mut cfg = get_boringssl_cmake_config(config); let num_jobs = std::env::var("NUM_JOBS").ok().or_else(|| { @@ -591,79 +550,27 @@ fn built_boring_source_path(config: &Config) -> &PathBuf { } if config.features.fips { - let (clang, clangxx) = verify_fips_clang_version(); - cfg.define("CMAKE_C_COMPILER", clang) - .define("CMAKE_CXX_COMPILER", clangxx) - .define("CMAKE_ASM_COMPILER", clang) + cfg.define("CMAKE_C_COMPILER", "clang") + .define("CMAKE_CXX_COMPILER", "clang++") + .define("CMAKE_ASM_COMPILER", "clang") .define("FIPS", "1"); } - if config.features.fips_link_precompiled { - cfg.define("FIPS", "1"); - } - cfg.build_target("ssl").build(); cfg.build_target("crypto").build() }) } -fn link_in_precompiled_bcm_o(config: &Config) { - println!("cargo:warning=linking in precompiled `bcm.o` module"); - - let bssl_dir = built_boring_source_path(config); - let bcm_o_src_path = config.env.precompiled_bcm_o.as_ref() - .expect("`fips-link-precompiled` requires `BORING_BSSL_FIPS_PRECOMPILED_BCM_O` env variable to be specified"); - - let libcrypto_path = bssl_dir - .join("build/crypto/libcrypto.a") - .canonicalize() - .unwrap(); - - let bcm_o_dst_path = bssl_dir.join("build/bcm-fips.o"); - - fs::copy(bcm_o_src_path, &bcm_o_dst_path).unwrap(); - - // check that fips module is named as expected - let out = run_command( - Command::new("ar") - .arg("t") - .arg(&libcrypto_path) - .arg("bcm.o"), - ) - .unwrap(); - - assert_eq!( - String::from_utf8(out.stdout).unwrap().trim(), - "bcm.o", - "failed to verify FIPS module name" - ); - - // insert fips bcm.o before bcm.o into libcrypto.a, - // so for all duplicate symbols the older fips bcm.o is used - // (this causes the need for extra linker flags to deal with duplicate symbols) - // (as long as the newer module does not define new symbols, one may also remove it, - // but once there are new symbols it would cause missing symbols at linking stage) - run_command( - Command::new("ar") - .args(["rb", "bcm.o"]) - .args([&libcrypto_path, &bcm_o_dst_path]), - ) - .unwrap(); -} - fn get_cpp_runtime_lib(config: &Config) -> Option { if let Some(ref cpp_lib) = config.env.cpp_runtime_lib { return cpp_lib.clone().into_string().ok(); } - // TODO(rmehra): figure out how to do this for windows - if env::var_os("CARGO_CFG_UNIX").is_some() { - match env::var("CARGO_CFG_TARGET_OS").unwrap().as_ref() { - "macos" | "ios" | "freebsd" => Some("c++".into()), - _ => Some("stdc++".into()), - } - } else { - None + match &*config.target_os { + "macos" | "ios" | "freebsd" | "openbsd" | "android" => Some("c++".into()), + _ if config.unix || config.target_env == "gnu" => Some("stdc++".into()), + // TODO(rmehra): figure out how to do this for windows + _ => None, } } @@ -709,10 +616,6 @@ fn emit_link_directives(config: &Config) { ); } - if config.features.fips_link_precompiled { - link_in_precompiled_bcm_o(config); - } - if let Some(cpp_lib) = get_cpp_runtime_lib(config) { println!("cargo:rustc-link-lib={cpp_lib}"); } @@ -725,20 +628,33 @@ fn emit_link_directives(config: &Config) { } } +fn check_include_path(path: PathBuf) -> Result { + if path.join("openssl").join("x509v3.h").exists() { + Ok(path) + } else { + Err(format!( + "Include path {} {}", + path.display(), + if !path.exists() { + "does not exist" + } else { + "does not have expected openssl/x509v3.h" + } + )) + } +} + fn generate_bindings(config: &Config) { let include_path = config.env.include_path.clone().unwrap_or_else(|| { if let Some(bssl_path) = &config.env.path { - return bssl_path.join("include"); + return check_include_path(bssl_path.join("include")) + .expect("config has invalid include path"); } let src_path = get_boringssl_source_path(config); - let candidate = src_path.join("include"); - - if candidate.exists() { - candidate - } else { - src_path.join("src").join("include") - } + check_include_path(src_path.join("include")) + .or_else(|_| check_include_path(src_path.join("src").join("include"))) + .expect("can't find usable include path") }); let target_rust_version = @@ -749,7 +665,8 @@ fn generate_bindings(config: &Config) { .derive_copy(true) .derive_debug(true) .derive_default(true) - .derive_eq(true) + .derive_eq(false) + .derive_partialeq(false) .default_enum_style(bindgen::EnumVariation::NewType { is_bitfield: false, is_global: false, @@ -769,6 +686,17 @@ fn generate_bindings(config: &Config) { builder = builder .clang_arg("--sysroot") .clang_arg(sysroot.display().to_string()); + + // we need to add special platform header file with env for support cross building + let target_include_dir = sysroot.join(format!( + "usr/include/{}-{}-{}", + config.target_arch, config.target_os, config.target_env + )); + if target_include_dir.is_dir() { + builder = builder + .clang_arg("-I") + .clang_arg(target_include_dir.display().to_string()); + } } let headers = [ @@ -786,12 +714,12 @@ fn generate_bindings(config: &Config) { "dtls1.h", "err.h", "hkdf.h", - #[cfg(not(feature = "fips"))] "hpke.h", "hmac.h", "hrss.h", "md4.h", "md5.h", + "mlkem.h", "obj_mac.h", "objects.h", "opensslv.h", @@ -807,7 +735,14 @@ fn generate_bindings(config: &Config) { "x509v3.h", ]; for header in &headers { - builder = builder.header(include_path.join("openssl").join(header).to_str().unwrap()); + let header_path = include_path.join("openssl").join(header); + assert!( + header_path.exists(), + "{} is missing. Is {} correct? run `cargo clean`", + header_path.display(), + include_path.display() + ); + builder = builder.header(header_path.to_str().unwrap()); } let bindings = builder.generate().expect("Unable to generate bindings"); @@ -824,7 +759,7 @@ fn ensure_err_lib_enum_is_named(source_code: &mut Vec) { let src = String::from_utf8_lossy(source_code); let enum_type = src .split_once("ERR_LIB_SSL:") - .and_then(|(_, def)| Some(def.split_once("=")?.0)) + .and_then(|(_, def)| Some(def.split_once('=')?.0)) .unwrap_or("_bindgen_ty_1"); source_code.extend_from_slice( diff --git a/boring-sys/deps/boringssl b/boring-sys/deps/boringssl index 44b3df6f0..91a66a59b 160000 --- a/boring-sys/deps/boringssl +++ b/boring-sys/deps/boringssl @@ -1 +1 @@ -Subproject commit 44b3df6f03d85c901767250329c571db405122d5 +Subproject commit 91a66a59b6c1435120ff83e245d7719411294386 diff --git a/boring-sys/deps/boringssl-fips b/boring-sys/deps/boringssl-fips deleted file mode 160000 index 853ca1ea1..000000000 --- a/boring-sys/deps/boringssl-fips +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 853ca1ea1168dff08011e5d42d94609cc0ca2e27 diff --git a/boring-sys/patches/boring-pq.patch b/boring-sys/patches/boring-pq.patch index b3afacf66..f0aa1ea26 100644 --- a/boring-sys/patches/boring-pq.patch +++ b/boring-sys/patches/boring-pq.patch @@ -1,5138 +1,706 @@ -From b98d803dbecc9d6848d8cbffa62b5c943fb75f70 Mon Sep 17 00:00:00 2001 -From: Bas Westerbaan -Date: Fri, 22 Jul 2022 16:43:48 +0200 +From cb5689e091f515fc8a42ceaff08d702333e505ed Mon Sep 17 00:00:00 2001 +From: Anthony Ramine +Date: Wed, 3 Dec 2025 11:10:16 +0100 Subject: [PATCH] Add additional post-quantum key agreements -BoringSSL upstream has supported the temporary post-quantum -key agreement X25519Kyber768Draft00 (0x6399) for a while. -At the time of writing X25519Kyber768Draft00 is widely deployed by browsers. - -Recent BoringSSL adds support for X25519MLKEM768 (0x11ec), -which will be the long term post-quantum key agreement of choice, -and many browsers are expected to switch to it before the end of 2024. - -This patch adds: - -1. Support for MLKEM768X25519 under the codepoint 0x11ec. The version - of BoringSSL we patch against did not support it yet. - -2. Supports for P256Kyber768Draft00 under 0xfe32, which we temporarily - need for compliance reasons. (Note that this is not the codepoint - allocated for that exchange in the IANA table.) - It also enables it in FIPS mode. - -3. Support for X25519Kyber768Draft00 under the old codepoint 0xfe31. - -4. Support for X25519Kyber512Draft00 under the codepoint 0xfe30. This - key agreement should only be used for testing: to see if the smaller - keyshare makes a difference. - -The patch also replaces Google's implementation of Kyber, by the -portable reference implementation, so as to support Kyber512. - -Cf RTG-2076 RTG-2051 RTG-2508 RTG-2707 RTG-2607 RTG-3239 ---- - BUILD.generated.bzl | 5 +- - BUILD.generated_tests.bzl | 4 - - CMakeLists.txt | 4 +- - sources.json | 9 +- - src/crypto/CMakeLists.txt | 5 +- - src/crypto/kyber/internal.h | 91 - - src/crypto/kyber/keccak.c | 204 -- - src/crypto/kyber/keccak_tests.txt | 3071 ----------------------------- - src/crypto/kyber/kyber.c | 3011 +++++++++++++++++++++------- - src/crypto/kyber/kyber512.c | 5 + - src/crypto/kyber/kyber768.c | 4 + - src/crypto/kyber/kyber_test.cc | 229 --- - src/crypto/kyber/kyber_tests.txt | 905 --------- - src/crypto/obj/obj_dat.h | 17 +- - src/crypto/obj/obj_mac.num | 4 + - src/crypto/obj/objects.txt | 6 +- - src/include/openssl/kyber.h | 203 +- - src/include/openssl/nid.h | 12 + - src/include/openssl/ssl.h | 4 + - src/sources.cmake | 2 - - src/ssl/extensions.cc | 4 + - src/ssl/ssl_key_share.cc | 525 ++++- - src/ssl/ssl_lib.cc | 2 +- - src/ssl/ssl_test.cc | 29 +- - src/tool/speed.cc | 162 +- - 26 files changed, 3088 insertions(+), 5433 deletions(-) - delete mode 100644 src/crypto/kyber/internal.h - delete mode 100644 src/crypto/kyber/keccak.c - delete mode 100644 src/crypto/kyber/keccak_tests.txt - create mode 100644 src/crypto/kyber/kyber512.c - create mode 100644 src/crypto/kyber/kyber768.c - delete mode 100644 src/crypto/kyber/kyber_test.cc - delete mode 100644 src/crypto/kyber/kyber_tests.txt - -diff --git a/BUILD.generated.bzl b/BUILD.generated.bzl -index 738e1055f..9466757a2 100644 ---- a/BUILD.generated.bzl -+++ b/BUILD.generated.bzl -@@ -253,7 +253,6 @@ crypto_internal_headers = [ - "src/crypto/fipsmodule/tls/internal.h", - "src/crypto/hrss/internal.h", - "src/crypto/internal.h", -- "src/crypto/kyber/internal.h", - "src/crypto/lhash/internal.h", - "src/crypto/obj/obj_dat.h", - "src/crypto/pkcs7/internal.h", -@@ -382,8 +381,8 @@ crypto_sources = [ - "src/crypto/fipsmodule/fips_shared_support.c", - "src/crypto/hpke/hpke.c", - "src/crypto/hrss/hrss.c", -- "src/crypto/kyber/keccak.c", -- "src/crypto/kyber/kyber.c", -+ "src/crypto/kyber/kyber512.c", -+ "src/crypto/kyber/kyber768.c", - "src/crypto/lhash/lhash.c", - "src/crypto/mem.c", - "src/crypto/obj/obj.c", -diff --git a/BUILD.generated_tests.bzl b/BUILD.generated_tests.bzl -index 92dec1e01..8f70dedc0 100644 ---- a/BUILD.generated_tests.bzl -+++ b/BUILD.generated_tests.bzl -@@ -40,7 +40,6 @@ test_support_sources = [ - "src/crypto/fipsmodule/tls/internal.h", - "src/crypto/hrss/internal.h", - "src/crypto/internal.h", -- "src/crypto/kyber/internal.h", - "src/crypto/lhash/internal.h", - "src/crypto/obj/obj_dat.h", - "src/crypto/pkcs7/internal.h", -@@ -124,7 +123,6 @@ crypto_test_sources = [ - "src/crypto/hpke/hpke_test.cc", - "src/crypto/hrss/hrss_test.cc", - "src/crypto/impl_dispatch_test.cc", -- "src/crypto/kyber/kyber_test.cc", - "src/crypto/lhash/lhash_test.cc", - "src/crypto/obj/obj_test.cc", - "src/crypto/pem/pem_test.cc", -@@ -218,8 +216,6 @@ crypto_test_data = [ - "src/crypto/fipsmodule/rand/ctrdrbg_vectors.txt", - "src/crypto/hmac_extra/hmac_tests.txt", - "src/crypto/hpke/hpke_test_vectors.txt", -- "src/crypto/kyber/keccak_tests.txt", -- "src/crypto/kyber/kyber_tests.txt", - "src/crypto/pkcs8/test/empty_password.p12", - "src/crypto/pkcs8/test/no_encryption.p12", - "src/crypto/pkcs8/test/nss.p12", -diff --git a/CMakeLists.txt b/CMakeLists.txt -index faed2befa..931c0e3a8 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -375,8 +375,8 @@ add_library( - src/crypto/fipsmodule/fips_shared_support.c - src/crypto/hpke/hpke.c - src/crypto/hrss/hrss.c -- src/crypto/kyber/keccak.c -- src/crypto/kyber/kyber.c -+ src/crypto/kyber/kyber512.c -+ src/crypto/kyber/kyber768.c - src/crypto/lhash/lhash.c - src/crypto/mem.c - src/crypto/obj/obj.c -diff --git a/sources.json b/sources.json -index 4c0048e1d..f6ea5c40f 100644 ---- a/sources.json -+++ b/sources.json -@@ -111,8 +111,8 @@ - "src/crypto/fipsmodule/fips_shared_support.c", - "src/crypto/hpke/hpke.c", - "src/crypto/hrss/hrss.c", -- "src/crypto/kyber/keccak.c", -- "src/crypto/kyber/kyber.c", -+ "src/crypto/kyber/kyber512.c", -+ "src/crypto/kyber/kyber768.c", - "src/crypto/lhash/lhash.c", - "src/crypto/mem.c", - "src/crypto/obj/obj.c", -@@ -549,7 +549,6 @@ - "src/crypto/hpke/hpke_test.cc", - "src/crypto/hrss/hrss_test.cc", - "src/crypto/impl_dispatch_test.cc", -- "src/crypto/kyber/kyber_test.cc", - "src/crypto/lhash/lhash_test.cc", - "src/crypto/obj/obj_test.cc", - "src/crypto/pem/pem_test.cc", -@@ -634,8 +633,6 @@ - "src/crypto/fipsmodule/rand/ctrdrbg_vectors.txt", - "src/crypto/hmac_extra/hmac_tests.txt", - "src/crypto/hpke/hpke_test_vectors.txt", -- "src/crypto/kyber/keccak_tests.txt", -- "src/crypto/kyber/kyber_tests.txt", - "src/crypto/pkcs8/test/empty_password.p12", - "src/crypto/pkcs8/test/no_encryption.p12", - "src/crypto/pkcs8/test/nss.p12", -@@ -1060,4 +1057,4 @@ - "urandom_test": [ - "src/crypto/fipsmodule/rand/urandom_test.cc" - ] --} -\ No newline at end of file -+} -diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt -index cdb5ddca1..2052fa791 100644 ---- a/src/crypto/CMakeLists.txt -+++ b/src/crypto/CMakeLists.txt -@@ -170,8 +170,8 @@ add_library( - ex_data.c - hpke/hpke.c - hrss/hrss.c -- kyber/keccak.c -- kyber/kyber.c -+ kyber/kyber512.c -+ kyber/kyber768.c - lhash/lhash.c - mem.c - obj/obj.c -@@ -400,7 +400,6 @@ add_executable( - hmac_extra/hmac_test.cc - hrss/hrss_test.cc - impl_dispatch_test.cc -- kyber/kyber_test.cc - lhash/lhash_test.cc - obj/obj_test.cc - pem/pem_test.cc -diff --git a/src/crypto/kyber/internal.h b/src/crypto/kyber/internal.h -deleted file mode 100644 -index b3bfa86b8..000000000 ---- a/src/crypto/kyber/internal.h -+++ /dev/null -@@ -1,91 +0,0 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -- --#ifndef OPENSSL_HEADER_CRYPTO_KYBER_INTERNAL_H --#define OPENSSL_HEADER_CRYPTO_KYBER_INTERNAL_H -- --#include --#include -- --#if defined(__cplusplus) --extern "C" { --#endif -- -- --// KYBER_ENCAP_ENTROPY is the number of bytes of uniformly random entropy --// necessary to encapsulate a secret. The entropy will be leaked to the --// decapsulating party. --#define KYBER_ENCAP_ENTROPY 32 -- --// KYBER_GENERATE_KEY_ENTROPY is the number of bytes of uniformly random entropy --// necessary to generate a key. --#define KYBER_GENERATE_KEY_ENTROPY 64 -- --struct BORINGSSL_keccak_st { -- uint64_t state[25]; -- size_t rate_bytes; -- size_t offset; --}; -- --enum boringssl_keccak_config_t { -- boringssl_sha3_256, -- boringssl_sha3_512, -- boringssl_shake128, -- boringssl_shake256, --}; -- --// BORINGSSL_keccak hashes |in_len| bytes from |in| and writes |out_len| bytes --// of output to |out|. If the |config| specifies a fixed-output function, like --// SHA3-256, then |out_len| must be the correct length for that function. --OPENSSL_EXPORT void BORINGSSL_keccak(uint8_t *out, size_t out_len, -- const uint8_t *in, size_t in_len, -- enum boringssl_keccak_config_t config); -- --// BORINGSSL_keccak_init absorbs |in_len| bytes from |in| and sets up |ctx| for --// squeezing. The |config| must specify a SHAKE variant, otherwise callers --// should use |BORINGSSL_keccak|. --OPENSSL_EXPORT void BORINGSSL_keccak_init( -- struct BORINGSSL_keccak_st *ctx, const uint8_t *in, size_t in_len, -- enum boringssl_keccak_config_t config); -- --// BORINGSSL_keccak_squeeze writes |out_len| bytes to |out| from |ctx|. --OPENSSL_EXPORT void BORINGSSL_keccak_squeeze(struct BORINGSSL_keccak_st *ctx, -- uint8_t *out, size_t out_len); -- --// KYBER_generate_key_external_entropy is a deterministic function to create a --// pair of Kyber768 keys, using the supplied entropy. The entropy needs to be --// uniformly random generated. This function is should only be used for tests, --// regular callers should use the non-deterministic |KYBER_generate_key| --// directly. --OPENSSL_EXPORT void KYBER_generate_key_external_entropy( -- uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], -- struct KYBER_private_key *out_private_key, -- const uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]); -- --// KYBER_encap_external_entropy is a deterministic function to encapsulate --// |out_shared_secret_len| bytes of |out_shared_secret| to |ciphertext|, using --// |KYBER_ENCAP_ENTROPY| bytes of |entropy| for randomization. The --// decapsulating side will be able to recover |entropy| in full. This --// function is should only be used for tests, regular callers should use the --// non-deterministic |KYBER_encap| directly. --OPENSSL_EXPORT void KYBER_encap_external_entropy( -- uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], uint8_t *out_shared_secret, -- size_t out_shared_secret_len, const struct KYBER_public_key *public_key, -- const uint8_t entropy[KYBER_ENCAP_ENTROPY]); -- --#if defined(__cplusplus) --} --#endif -- --#endif // OPENSSL_HEADER_CRYPTO_KYBER_INTERNAL_H -diff --git a/src/crypto/kyber/keccak.c b/src/crypto/kyber/keccak.c -deleted file mode 100644 -index f1c012d11..000000000 ---- a/src/crypto/kyber/keccak.c -+++ /dev/null -@@ -1,204 +0,0 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -- --#include -- --#include --#include -- --#include "../internal.h" --#include "./internal.h" -- -- --// keccak_f implements the Keccak-1600 permutation as described at --// https://keccak.team/keccak_specs_summary.html. Each lane is represented as a --// 64-bit value and the 5×5 lanes are stored as an array in row-major order. --static void keccak_f(uint64_t state[25]) { -- static const int kNumRounds = 24; -- for (int round = 0; round < kNumRounds; round++) { -- // θ step -- uint64_t c[5]; -- for (int x = 0; x < 5; x++) { -- c[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ -- state[x + 20]; -- } -- -- for (int x = 0; x < 5; x++) { -- const uint64_t d = c[(x + 4) % 5] ^ CRYPTO_rotl_u64(c[(x + 1) % 5], 1); -- for (int y = 0; y < 5; y++) { -- state[y * 5 + x] ^= d; -- } -- } -- -- // ρ and π steps. -- // -- // These steps involve a mapping of the state matrix. Each input point, -- // (x,y), is rotated and written to the point (y, 2x + 3y). In the Keccak -- // pseudo-code a separate array is used because an in-place operation would -- // overwrite some values that are subsequently needed. However, the mapping -- // forms a trail through 24 of the 25 values so we can do it in place with -- // only a single temporary variable. -- // -- // Start with (1, 0). The value here will be mapped and end up at (0, 2). -- // That value will end up at (2, 1), then (1, 2), and so on. After 24 -- // steps, 24 of the 25 values have been hit (as this mapping is injective) -- // and the sequence will repeat. All that remains is to handle the element -- // at (0, 0), but the rotation for that element is zero, and it goes to (0, -- // 0), so we can ignore it. -- static const uint8_t kIndexes[24] = {10, 7, 11, 17, 18, 3, 5, 16, -- 8, 21, 24, 4, 15, 23, 19, 13, -- 12, 2, 20, 14, 22, 9, 6, 1}; -- static const uint8_t kRotations[24] = {1, 3, 6, 10, 15, 21, 28, 36, -- 45, 55, 2, 14, 27, 41, 56, 8, -- 25, 43, 62, 18, 39, 61, 20, 44}; -- uint64_t prev_value = state[1]; -- for (int i = 0; i < 24; i++) { -- const uint64_t value = CRYPTO_rotl_u64(prev_value, kRotations[i]); -- const size_t index = kIndexes[i]; -- prev_value = state[index]; -- state[index] = value; -- } -- -- // χ step -- for (int y = 0; y < 5; y++) { -- const int row_index = 5 * y; -- const uint64_t orig_x0 = state[row_index]; -- const uint64_t orig_x1 = state[row_index + 1]; -- state[row_index] ^= ~orig_x1 & state[row_index + 2]; -- state[row_index + 1] ^= ~state[row_index + 2] & state[row_index + 3]; -- state[row_index + 2] ^= ~state[row_index + 3] & state[row_index + 4]; -- state[row_index + 3] ^= ~state[row_index + 4] & orig_x0; -- state[row_index + 4] ^= ~orig_x0 & orig_x1; -- } -- -- // ι step -- // -- // From https://keccak.team/files/Keccak-reference-3.0.pdf, section -- // 1.2, the round constants are based on the output of a LFSR. Thus, as -- // suggested in the appendix of of -- // https://keccak.team/keccak_specs_summary.html, the values are -- // simply encoded here. -- static const uint64_t kRoundConstants[24] = { -- 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, -- 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, -- 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, -- 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, -- 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, -- 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, -- 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, -- 0x8000000000008080, 0x0000000080000001, 0x8000000080008008, -- }; -- -- state[0] ^= kRoundConstants[round]; -- } --} -- --static void keccak_init(struct BORINGSSL_keccak_st *ctx, -- size_t *out_required_out_len, const uint8_t *in, -- size_t in_len, enum boringssl_keccak_config_t config) { -- size_t capacity_bytes; -- uint8_t terminator; -- switch (config) { -- case boringssl_sha3_256: -- capacity_bytes = 512 / 8; -- *out_required_out_len = 32; -- terminator = 0x06; -- break; -- case boringssl_sha3_512: -- capacity_bytes = 1024 / 8; -- *out_required_out_len = 64; -- terminator = 0x06; -- break; -- case boringssl_shake128: -- capacity_bytes = 256 / 8; -- *out_required_out_len = 0; -- terminator = 0x1f; -- break; -- case boringssl_shake256: -- capacity_bytes = 512 / 8; -- *out_required_out_len = 0; -- terminator = 0x1f; -- break; -- default: -- abort(); -- } -- -- OPENSSL_memset(ctx, 0, sizeof(*ctx)); -- ctx->rate_bytes = 200 - capacity_bytes; -- assert(ctx->rate_bytes % 8 == 0); -- const size_t rate_words = ctx->rate_bytes / 8; -- -- while (in_len >= ctx->rate_bytes) { -- for (size_t i = 0; i < rate_words; i++) { -- ctx->state[i] ^= CRYPTO_load_u64_le(in + 8 * i); -- } -- keccak_f(ctx->state); -- in += ctx->rate_bytes; -- in_len -= ctx->rate_bytes; -- } -- -- // XOR the final block. Accessing |ctx->state| as a |uint8_t*| is allowed by -- // strict aliasing because we require |uint8_t| to be a character type. -- uint8_t *state_bytes = (uint8_t *)ctx->state; -- assert(in_len < ctx->rate_bytes); -- for (size_t i = 0; i < in_len; i++) { -- state_bytes[i] ^= in[i]; -- } -- state_bytes[in_len] ^= terminator; -- state_bytes[ctx->rate_bytes - 1] ^= 0x80; -- keccak_f(ctx->state); --} -- --void BORINGSSL_keccak(uint8_t *out, size_t out_len, const uint8_t *in, -- size_t in_len, enum boringssl_keccak_config_t config) { -- struct BORINGSSL_keccak_st ctx; -- size_t required_out_len; -- keccak_init(&ctx, &required_out_len, in, in_len, config); -- if (required_out_len != 0 && out_len != required_out_len) { -- abort(); -- } -- BORINGSSL_keccak_squeeze(&ctx, out, out_len); --} -- --void BORINGSSL_keccak_init(struct BORINGSSL_keccak_st *ctx, const uint8_t *in, -- size_t in_len, -- enum boringssl_keccak_config_t config) { -- size_t required_out_len; -- keccak_init(ctx, &required_out_len, in, in_len, config); -- if (required_out_len != 0) { -- abort(); -- } --} -- --void BORINGSSL_keccak_squeeze(struct BORINGSSL_keccak_st *ctx, uint8_t *out, -- size_t out_len) { -- // Accessing |ctx->state| as a |uint8_t*| is allowed by strict aliasing -- // because we require |uint8_t| to be a character type. -- const uint8_t *state_bytes = (const uint8_t *)ctx->state; -- while (out_len) { -- size_t remaining = ctx->rate_bytes - ctx->offset; -- size_t todo = out_len; -- if (todo > remaining) { -- todo = remaining; -- } -- OPENSSL_memcpy(out, &state_bytes[ctx->offset], todo); -- out += todo; -- out_len -= todo; -- ctx->offset += todo; -- if (ctx->offset == ctx->rate_bytes) { -- keccak_f(ctx->state); -- ctx->offset = 0; -- } -- } --} -diff --git a/src/crypto/kyber/kyber.c b/src/crypto/kyber/kyber.c -index 776c085f9..ccb5b3d9b 100644 ---- a/src/crypto/kyber/kyber.c -+++ b/src/crypto/kyber/kyber.c -@@ -1,833 +1,2426 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -+// Taken from round 3 public domain reference implementation -+// -+// https://github.com/pq-crystals/kyber -+// 8e00ec73035147d18b27d06048dff322f8de1f29 -+// -+// with some small modifications: -+// -+// - Merged into one file. -+// - Removed 90s version. -+// - Seeds are passed as paramters. -+// - Changed the API to be more BoringSSL-like -+// - Mitigated timing sidechannels (Kyberslash 1 and 2). -+// (Note that these do not affect ephemeral usage as in TLS.) -+// -+// TODO -+// -+// - Optimizations -+// -+// The majority of Kyber's time is spent in keccak: generating the matrix -+// A, hashing the public key, et cetera. This can be sped up dramatically -+// by using a multiway keccak implementation such as f1600x4 on AVX2. -+// -+// Also the NTT and other operations can be sped up with SIMD. This is -+// more complex and the gains are more modest. See the avx2 reference -+// implementation or https://github.com/cloudflare/circl/tree/main/pke/kyber -+// -+// - Option to keep A stored in private key. - --#include -+#ifndef KYBER_K -+#error "Don't compile this file direcly" -+#endif - --#include --#include -+#include -+#include - --#include --#include -+#include -+#include -+#include - - #include "../internal.h" --#include "./internal.h" -- -- --// See --// https://pq-crystals.org/kyber/data/kyber-specification-round3-20210804.pdf -- --#define DEGREE 256 --#define RANK 3 -- --static const size_t kBarrettMultiplier = 5039; --static const unsigned kBarrettShift = 24; --static const uint16_t kPrime = 3329; --static const int kLog2Prime = 12; --static const uint16_t kHalfPrime = (/*kPrime=*/3329 - 1) / 2; --static const int kDU = 10; --static const int kDV = 4; --// kInverseDegree is 128^-1 mod 3329; 128 because kPrime does not have a 512th --// root of unity. --static const uint16_t kInverseDegree = 3303; --static const size_t kEncodedVectorSize = -- (/*kLog2Prime=*/12 * DEGREE / 8) * RANK; --static const size_t kCompressedVectorSize = /*kDU=*/10 * RANK * DEGREE / 8; -- --typedef struct scalar { -- // On every function entry and exit, 0 <= c < kPrime. -- uint16_t c[DEGREE]; --} scalar; -- --typedef struct vector { -- scalar v[RANK]; --} vector; -- --typedef struct matrix { -- scalar v[RANK][RANK]; --} matrix; -- --// This bit of Python will be referenced in some of the following comments: --// --// p = 3329 --// --// def bitreverse(i): --// ret = 0 --// for n in range(7): --// bit = i & 1 --// ret <<= 1 --// ret |= bit --// i >>= 1 --// return ret -- --// kNTTRoots = [pow(17, bitreverse(i), p) for i in range(128)] --static const uint16_t kNTTRoots[128] = { -- 1, 1729, 2580, 3289, 2642, 630, 1897, 848, 1062, 1919, 193, 797, -- 2786, 3260, 569, 1746, 296, 2447, 1339, 1476, 3046, 56, 2240, 1333, -- 1426, 2094, 535, 2882, 2393, 2879, 1974, 821, 289, 331, 3253, 1756, -- 1197, 2304, 2277, 2055, 650, 1977, 2513, 632, 2865, 33, 1320, 1915, -- 2319, 1435, 807, 452, 1438, 2868, 1534, 2402, 2647, 2617, 1481, 648, -- 2474, 3110, 1227, 910, 17, 2761, 583, 2649, 1637, 723, 2288, 1100, -- 1409, 2662, 3281, 233, 756, 2156, 3015, 3050, 1703, 1651, 2789, 1789, -- 1847, 952, 1461, 2687, 939, 2308, 2437, 2388, 733, 2337, 268, 641, -- 1584, 2298, 2037, 3220, 375, 2549, 2090, 1645, 1063, 319, 2773, 757, -- 2099, 561, 2466, 2594, 2804, 1092, 403, 1026, 1143, 2150, 2775, 886, -- 1722, 1212, 1874, 1029, 2110, 2935, 885, 2154, --}; - --// kInverseNTTRoots = [pow(17, -bitreverse(i), p) for i in range(128)] --static const uint16_t kInverseNTTRoots[128] = { -- 1, 1600, 40, 749, 2481, 1432, 2699, 687, 1583, 2760, 69, 543, -- 2532, 3136, 1410, 2267, 2508, 1355, 450, 936, 447, 2794, 1235, 1903, -- 1996, 1089, 3273, 283, 1853, 1990, 882, 3033, 2419, 2102, 219, 855, -- 2681, 1848, 712, 682, 927, 1795, 461, 1891, 2877, 2522, 1894, 1010, -- 1414, 2009, 3296, 464, 2697, 816, 1352, 2679, 1274, 1052, 1025, 2132, -- 1573, 76, 2998, 3040, 1175, 2444, 394, 1219, 2300, 1455, 2117, 1607, -- 2443, 554, 1179, 2186, 2303, 2926, 2237, 525, 735, 863, 2768, 1230, -- 2572, 556, 3010, 2266, 1684, 1239, 780, 2954, 109, 1292, 1031, 1745, -- 2688, 3061, 992, 2596, 941, 892, 1021, 2390, 642, 1868, 2377, 1482, -- 1540, 540, 1678, 1626, 279, 314, 1173, 2573, 3096, 48, 667, 1920, -- 2229, 1041, 2606, 1692, 680, 2746, 568, 3312, --}; -+#if (KYBER_K == 2) -+#define KYBER_NAMESPACE(s) KYBER512_##s -+#elif (KYBER_K == 3) -+#define KYBER_NAMESPACE(s) KYBER768_##s -+#elif (KYBER_K == 4) -+#define KYBER_NAMESPACE(s) KYBER1024_##s -+#else -+#error "KYBER_K must be in {2,3,4}" -+#endif - --// kModRoots = [pow(17, 2*bitreverse(i) + 1, p) for i in range(128)] --static const uint16_t kModRoots[128] = { -- 17, 3312, 2761, 568, 583, 2746, 2649, 680, 1637, 1692, 723, 2606, -- 2288, 1041, 1100, 2229, 1409, 1920, 2662, 667, 3281, 48, 233, 3096, -- 756, 2573, 2156, 1173, 3015, 314, 3050, 279, 1703, 1626, 1651, 1678, -- 2789, 540, 1789, 1540, 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, -- 939, 2390, 2308, 1021, 2437, 892, 2388, 941, 733, 2596, 2337, 992, -- 268, 3061, 641, 2688, 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109, -- 375, 2954, 2549, 780, 2090, 1239, 1645, 1684, 1063, 2266, 319, 3010, -- 2773, 556, 757, 2572, 2099, 1230, 561, 2768, 2466, 863, 2594, 735, -- 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, 1143, 2186, 2150, 1179, -- 2775, 554, 886, 2443, 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300, -- 2110, 1219, 2935, 394, 885, 2444, 2154, 1175, --}; -+#define public_key KYBER_NAMESPACE(public_key) -+#define private_key KYBER_NAMESPACE(private_key) - --// reduce_once reduces 0 <= x < 2*kPrime, mod kPrime. --static uint16_t reduce_once(uint16_t x) { -- assert(x < 2 * kPrime); -- const uint16_t subtracted = x - kPrime; -- uint16_t mask = 0u - (subtracted >> 15); -- // On Aarch64, omitting a |value_barrier_u16| results in a 2x speedup of Kyber -- // overall and Clang still produces constant-time code using `csel`. On other -- // platforms & compilers on godbolt that we care about, this code also -- // produces constant-time output. -- return (mask & x) | (~mask & subtracted); --} -- --// constant time reduce x mod kPrime using Barrett reduction. x must be less --// than kPrime + 2×kPrime². --static uint16_t reduce(uint32_t x) { -- assert(x < kPrime + 2u * kPrime * kPrime); -- uint64_t product = (uint64_t)x * kBarrettMultiplier; -- uint32_t quotient = product >> kBarrettShift; -- uint32_t remainder = x - quotient * kPrime; -- return reduce_once(remainder); --} -- --static void scalar_zero(scalar *out) { OPENSSL_memset(out, 0, sizeof(*out)); } -- --static void vector_zero(vector *out) { OPENSSL_memset(out, 0, sizeof(*out)); } -- --// In place number theoretic transform of a given scalar. --// Note that Kyber's kPrime 3329 does not have a 512th root of unity, so this --// transform leaves off the last iteration of the usual FFT code, with the 128 --// relevant roots of unity being stored in |kNTTRoots|. This means the output --// should be seen as 128 elements in GF(3329^2), with the coefficients of the --// elements being consecutive entries in |s->c|. --static void scalar_ntt(scalar *s) { -- int offset = DEGREE; -- // `int` is used here because using `size_t` throughout caused a ~5% slowdown -- // with Clang 14 on Aarch64. -- for (int step = 1; step < DEGREE / 2; step <<= 1) { -- offset >>= 1; -- int k = 0; -- for (int i = 0; i < step; i++) { -- const uint32_t step_root = kNTTRoots[i + step]; -- for (int j = k; j < k + offset; j++) { -- uint16_t odd = reduce(step_root * s->c[j + offset]); -- uint16_t even = s->c[j]; -- s->c[j] = reduce_once(odd + even); -- s->c[j + offset] = reduce_once(even - odd + kPrime); -- } -- k += 2 * offset; -+#define generate_key KYBER_NAMESPACE(generate_key) -+#define encap KYBER_NAMESPACE(encap) -+#define decap KYBER_NAMESPACE(decap) -+#define marshal_public_key KYBER_NAMESPACE(marshal_public_key) -+#define parse_public_key KYBER_NAMESPACE(parse_public_key) -+ -+ -+// -+// params.h -+// -+#define KYBER_N 256 -+#define KYBER_Q 3329 -+ -+#define KYBER_SYMBYTES 32 /* size in bytes of hashes, and seeds */ -+#define KYBER_SSBYTES 32 /* size in bytes of shared key */ -+ -+#define KYBER_POLYBYTES 384 -+#define KYBER_POLYVECBYTES (KYBER_K * KYBER_POLYBYTES) -+ -+#if KYBER_K == 2 -+#define KYBER_ETA1 3 -+#define KYBER_POLYCOMPRESSEDBYTES 128 -+#define KYBER_POLYVECCOMPRESSEDBYTES (KYBER_K * 320) -+#elif KYBER_K == 3 -+#define KYBER_ETA1 2 -+#define KYBER_POLYCOMPRESSEDBYTES 128 -+#define KYBER_POLYVECCOMPRESSEDBYTES (KYBER_K * 320) -+#elif KYBER_K == 4 -+#define KYBER_ETA1 2 -+#define KYBER_POLYCOMPRESSEDBYTES 160 -+#define KYBER_POLYVECCOMPRESSEDBYTES (KYBER_K * 352) -+#endif -+ -+#define KYBER_ETA2 2 -+ -+#define KYBER_INDCPA_MSGBYTES (KYBER_SYMBYTES) -+#define KYBER_INDCPA_PUBLICKEYBYTES (KYBER_POLYVECBYTES + KYBER_SYMBYTES) -+#define KYBER_INDCPA_SECRETKEYBYTES (KYBER_POLYVECBYTES) -+#define KYBER_INDCPA_BYTES (KYBER_POLYVECCOMPRESSEDBYTES + KYBER_POLYCOMPRESSEDBYTES) -+ -+#define KYBER_PUBLICKEYBYTES (KYBER_INDCPA_PUBLICKEYBYTES) -+/* 32 bytes of additional space to save H(pk) */ -+#define KYBER_SECRETKEYBYTES (KYBER_INDCPA_SECRETKEYBYTES + KYBER_INDCPA_PUBLICKEYBYTES + 2*KYBER_SYMBYTES) -+#define KYBER_CIPHERTEXTBYTES (KYBER_INDCPA_BYTES) -+ -+// -+// verify.h -+// -+static int verify(const uint8_t *a, const uint8_t *b, size_t len); -+static void cmov(uint8_t *r, const uint8_t *x, size_t len, uint8_t b); -+ -+// -+// reduce.h -+// -+#define MONT -1044 // 2^16 mod q -+#define QINV -3327 // q^-1 mod 2^16 -+ -+static int16_t montgomery_reduce(int32_t a); -+static int16_t barrett_reduce(int16_t a); -+ -+// -+// ntt.h -+// -+static void ntt(int16_t poly[256]); -+static void invntt(int16_t poly[256]); -+static void basemul(int16_t r[2], const int16_t a[2], const int16_t b[2], int16_t zeta); -+ -+// -+// poly.h -+// -+ -+/* -+ * Elements of R_q = Z_q[X]/(X^n + 1). Represents polynomial -+ * coeffs[0] + X*coeffs[1] + X^2*xoeffs[2] + ... + X^{n-1}*coeffs[n-1] -+ */ -+typedef struct{ -+ int16_t coeffs[KYBER_N]; -+} poly; -+ -+static void poly_compress(uint8_t r[KYBER_POLYCOMPRESSEDBYTES], const poly *a); -+static void poly_decompress(poly *r, const uint8_t a[KYBER_POLYCOMPRESSEDBYTES]); -+ -+static void poly_tobytes(uint8_t r[KYBER_POLYBYTES], const poly *a); -+static void poly_frombytes(poly *r, const uint8_t a[KYBER_POLYBYTES]); -+ -+static void poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCPA_MSGBYTES]); -+static void poly_tomsg(uint8_t msg[KYBER_INDCPA_MSGBYTES], const poly *r); -+ -+static void poly_getnoise_eta1(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce); -+static void poly_getnoise_eta2(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce); -+ -+static void poly_ntt(poly *r); -+static void poly_invntt_tomont(poly *r); -+static void poly_basemul_montgomery(poly *r, const poly *a, const poly *b); -+static void poly_tomont(poly *r); -+ -+static void poly_reduce(poly *r); -+ -+static void poly_add(poly *r, const poly *a, const poly *b); -+static void poly_sub(poly *r, const poly *a, const poly *b); -+ -+// -+// cbd.h -+// -+static void poly_cbd_eta1(poly *r, const uint8_t buf[KYBER_ETA1*KYBER_N/4]); -+static void poly_cbd_eta2(poly *r, const uint8_t buf[KYBER_ETA2*KYBER_N/4]); -+ -+// -+// polyvec.h -+// -+ -+typedef struct{ -+ poly vec[KYBER_K]; -+} polyvec; -+ -+static void polyvec_compress(uint8_t r[KYBER_POLYVECCOMPRESSEDBYTES], const polyvec *a); -+static void polyvec_decompress(polyvec *r, const uint8_t a[KYBER_POLYVECCOMPRESSEDBYTES]); -+ -+static void polyvec_tobytes(uint8_t r[KYBER_POLYVECBYTES], const polyvec *a); -+static void polyvec_frombytes(polyvec *r, const uint8_t a[KYBER_POLYVECBYTES]); -+ -+static void polyvec_ntt(polyvec *r); -+static void polyvec_invntt_tomont(polyvec *r); -+ -+static void polyvec_basemul_acc_montgomery(poly *r, const polyvec *a, const polyvec *b); -+ -+static void polyvec_reduce(polyvec *r); -+ -+static void polyvec_add(polyvec *r, const polyvec *a, const polyvec *b); -+ -+// -+// indcpa.h -+// -+ -+static void gen_matrix(polyvec *a, const uint8_t seed[KYBER_SYMBYTES], int transposed); -+static void indcpa_keypair(uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], -+ uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES], -+ const uint8_t seed[KYBER_SYMBYTES]); -+ -+static int indcpa_enc(uint8_t c[KYBER_INDCPA_BYTES], -+ const uint8_t m[KYBER_INDCPA_MSGBYTES], -+ const uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], -+ const uint8_t coins[KYBER_SYMBYTES]); -+ -+static void indcpa_dec(uint8_t m[KYBER_INDCPA_MSGBYTES], -+ const uint8_t c[KYBER_INDCPA_BYTES], -+ const uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES]); -+ -+// -+// fips202.h -+// -+ -+#define SHAKE128_RATE 168 -+#define SHAKE256_RATE 136 -+#define SHA3_256_RATE 136 -+#define SHA3_512_RATE 72 -+ -+typedef struct { -+ uint64_t s[25]; -+ unsigned int pos; -+} keccak_state; -+ -+static void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen); -+static void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state); -+ -+static void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state); -+static void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen); -+static void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state); -+static void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen); -+static void shake256_finalize(keccak_state *state); -+static void shake256_init(keccak_state *state); -+ -+static void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen); -+static void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen); -+static void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen); -+ -+// -+// symmetric.h -+// -+ -+typedef keccak_state xof_state; -+ -+static void kyber_shake128_absorb(keccak_state *s, -+ const uint8_t seed[KYBER_SYMBYTES], -+ uint8_t x, -+ uint8_t y); -+ -+static void kyber_shake256_prf(uint8_t *out, size_t outlen, const uint8_t key[KYBER_SYMBYTES], uint8_t nonce); -+ -+#define XOF_BLOCKBYTES SHAKE128_RATE -+ -+#define hash_h(OUT, IN, INBYTES) sha3_256(OUT, IN, INBYTES) -+#define hash_g(OUT, IN, INBYTES) sha3_512(OUT, IN, INBYTES) -+#define xof_absorb(STATE, SEED, X, Y) kyber_shake128_absorb(STATE, SEED, X, Y) -+#define xof_squeezeblocks(OUT, OUTBLOCKS, STATE) shake128_squeezeblocks(OUT, OUTBLOCKS, STATE) -+#define prf(OUT, OUTBYTES, KEY, NONCE) kyber_shake256_prf(OUT, OUTBYTES, KEY, NONCE) -+#define kdf(OUT, IN, INBYTES) shake256(OUT, KYBER_SSBYTES, IN, INBYTES) -+ -+ -+// -+// verify.c -+// -+ -+/************************************************* -+* Name: verify -+* -+* Description: Compare two arrays for equality in constant time. -+* -+* Arguments: const uint8_t *a: pointer to first byte array -+* const uint8_t *b: pointer to second byte array -+* size_t len: length of the byte arrays -+* -+* Returns 0 if the byte arrays are equal, 1 otherwise -+**************************************************/ -+static int verify(const uint8_t *a, const uint8_t *b, size_t len) -+{ -+ size_t i; -+ uint8_t r = 0; -+ -+ for(i=0;i> 63; -+} -+ -+/************************************************* -+* Name: cmov -+* -+* Description: Copy len bytes from x to r if b is 1; -+* don't modify x if b is 0. Requires b to be in {0,1}; -+* assumes two's complement representation of negative integers. -+* Runs in constant time. -+* -+* Arguments: uint8_t *r: pointer to output byte array -+* const uint8_t *x: pointer to input byte array -+* size_t len: Amount of bytes to be copied -+* uint8_t b: Condition bit; has to be in {0,1} -+**************************************************/ -+static void cmov(uint8_t *r, const uint8_t *x, size_t len, uint8_t b) -+{ -+ size_t i; -+ -+ b = -b; -+ for(i=0;i> 16; -+ return t; -+} -+ -+/************************************************* -+* Name: barrett_reduce -+* -+* Description: Barrett reduction; given a 16-bit integer a, computes -+* centered representative congruent to a mod q in {-(q-1)/2,...,(q-1)/2} -+* -+* Arguments: - int16_t a: input integer to be reduced -+* -+* Returns: integer in {-(q-1)/2,...,(q-1)/2} congruent to a modulo q. -+**************************************************/ -+static int16_t barrett_reduce(int16_t a) { -+ int16_t t; -+ const int16_t v = ((1<<26) + KYBER_Q/2)/KYBER_Q; -+ -+ t = ((int32_t)v*a + (1<<25)) >> 26; -+ t *= KYBER_Q; -+ return a - t; -+} -+ -+// -+// cbd.c -+// -+ -+/************************************************* -+* Name: load32_littleendian -+* -+* Description: load 4 bytes into a 32-bit integer -+* in little-endian order -+* -+* Arguments: - const uint8_t *x: pointer to input byte array -+* -+* Returns 32-bit unsigned integer loaded from x -+**************************************************/ -+static uint32_t load32_littleendian(const uint8_t x[4]) -+{ -+ uint32_t r; -+ r = (uint32_t)x[0]; -+ r |= (uint32_t)x[1] << 8; -+ r |= (uint32_t)x[2] << 16; -+ r |= (uint32_t)x[3] << 24; -+ return r; -+} -+ -+/************************************************* -+* Name: load24_littleendian -+* -+* Description: load 3 bytes into a 32-bit integer -+* in little-endian order. -+* This function is only needed for Kyber-512 -+* -+* Arguments: - const uint8_t *x: pointer to input byte array -+* -+* Returns 32-bit unsigned integer loaded from x (most significant byte is zero) -+**************************************************/ -+#if KYBER_ETA1 == 3 -+static uint32_t load24_littleendian(const uint8_t x[3]) -+{ -+ uint32_t r; -+ r = (uint32_t)x[0]; -+ r |= (uint32_t)x[1] << 8; -+ r |= (uint32_t)x[2] << 16; -+ return r; -+} -+#endif -+ -+ -+/************************************************* -+* Name: cbd2 -+* -+* Description: Given an array of uniformly random bytes, compute -+* polynomial with coefficients distributed according to -+* a centered binomial distribution with parameter eta=2 -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *buf: pointer to input byte array -+**************************************************/ -+static void cbd2(poly *r, const uint8_t buf[2*KYBER_N/4]) -+{ -+ unsigned int i,j; -+ uint32_t t,d; -+ int16_t a,b; -+ -+ for(i=0;i>1) & 0x55555555; -+ -+ for(j=0;j<8;j++) { -+ a = (d >> (4*j+0)) & 0x3; -+ b = (d >> (4*j+2)) & 0x3; -+ r->coeffs[8*i+j] = a - b; -+ } -+ } -+} -+ -+/************************************************* -+* Name: cbd3 -+* -+* Description: Given an array of uniformly random bytes, compute -+* polynomial with coefficients distributed according to -+* a centered binomial distribution with parameter eta=3. -+* This function is only needed for Kyber-512 -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *buf: pointer to input byte array -+**************************************************/ -+#if KYBER_ETA1 == 3 -+static void cbd3(poly *r, const uint8_t buf[3*KYBER_N/4]) -+{ -+ unsigned int i,j; -+ uint32_t t,d; -+ int16_t a,b; -+ -+ for(i=0;i>1) & 0x00249249; -+ d += (t>>2) & 0x00249249; -+ -+ for(j=0;j<4;j++) { -+ a = (d >> (6*j+0)) & 0x7; -+ b = (d >> (6*j+3)) & 0x7; -+ r->coeffs[4*i+j] = a - b; - } - } - } -+#endif -+ -+static void poly_cbd_eta1(poly *r, const uint8_t buf[KYBER_ETA1*KYBER_N/4]) -+{ -+#if KYBER_ETA1 == 2 -+ cbd2(r, buf); -+#elif KYBER_ETA1 == 3 -+ cbd3(r, buf); -+#else -+#error "This implementation requires eta1 in {2,3}" -+#endif -+} - --static void vector_ntt(vector *a) { -- for (int i = 0; i < RANK; i++) { -- scalar_ntt(&a->v[i]); -+static void poly_cbd_eta2(poly *r, const uint8_t buf[KYBER_ETA2*KYBER_N/4]) -+{ -+#if KYBER_ETA2 == 2 -+ cbd2(r, buf); -+#else -+#error "This implementation requires eta2 = 2" -+#endif -+} -+ -+// -+// ntt.c -+// -+ -+/* Code to generate zetas and zetas_inv used in the number-theoretic transform: -+ -+#define KYBER_ROOT_OF_UNITY 17 -+ -+static const uint8_t tree[128] = { -+ 0, 64, 32, 96, 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, -+ 4, 68, 36, 100, 20, 84, 52, 116, 12, 76, 44, 108, 28, 92, 60, 124, -+ 2, 66, 34, 98, 18, 82, 50, 114, 10, 74, 42, 106, 26, 90, 58, 122, -+ 6, 70, 38, 102, 22, 86, 54, 118, 14, 78, 46, 110, 30, 94, 62, 126, -+ 1, 65, 33, 97, 17, 81, 49, 113, 9, 73, 41, 105, 25, 89, 57, 121, -+ 5, 69, 37, 101, 21, 85, 53, 117, 13, 77, 45, 109, 29, 93, 61, 125, -+ 3, 67, 35, 99, 19, 83, 51, 115, 11, 75, 43, 107, 27, 91, 59, 123, -+ 7, 71, 39, 103, 23, 87, 55, 119, 15, 79, 47, 111, 31, 95, 63, 127 -+}; -+ -+void init_ntt() { -+ unsigned int i; -+ int16_t tmp[128]; -+ -+ tmp[0] = MONT; -+ for(i=1;i<128;i++) -+ tmp[i] = fqmul(tmp[i-1],MONT*KYBER_ROOT_OF_UNITY % KYBER_Q); -+ -+ for(i=0;i<128;i++) { -+ zetas[i] = tmp[tree[i]]; -+ if(zetas[i] > KYBER_Q/2) -+ zetas[i] -= KYBER_Q; -+ if(zetas[i] < -KYBER_Q/2) -+ zetas[i] += KYBER_Q; - } - } -+*/ -+ -+static const int16_t zetas[128] = { -+ -1044, -758, -359, -1517, 1493, 1422, 287, 202, -+ -171, 622, 1577, 182, 962, -1202, -1474, 1468, -+ 573, -1325, 264, 383, -829, 1458, -1602, -130, -+ -681, 1017, 732, 608, -1542, 411, -205, -1571, -+ 1223, 652, -552, 1015, -1293, 1491, -282, -1544, -+ 516, -8, -320, -666, -1618, -1162, 126, 1469, -+ -853, -90, -271, 830, 107, -1421, -247, -951, -+ -398, 961, -1508, -725, 448, -1065, 677, -1275, -+ -1103, 430, 555, 843, -1251, 871, 1550, 105, -+ 422, 587, 177, -235, -291, -460, 1574, 1653, -+ -246, 778, 1159, -147, -777, 1483, -602, 1119, -+ -1590, 644, -872, 349, 418, 329, -156, -75, -+ 817, 1097, 603, 610, 1322, -1285, -1465, 384, -+ -1215, -136, 1218, -1335, -874, 220, -1187, -1659, -+ -1185, -1530, -1278, 794, -1510, -854, -870, 478, -+ -108, -308, 996, 991, 958, -1460, 1522, 1628 -+}; -+ -+/************************************************* -+* Name: fqmul -+* -+* Description: Multiplication followed by Montgomery reduction -+* -+* Arguments: - int16_t a: first factor -+* - int16_t b: second factor -+* -+* Returns 16-bit integer congruent to a*b*R^{-1} mod q -+**************************************************/ -+static int16_t fqmul(int16_t a, int16_t b) { -+ return montgomery_reduce((int32_t)a*b); -+} - --// In place inverse number theoretic transform of a given scalar, with pairs of --// entries of s->v being interpreted as elements of GF(3329^2). Just as with the --// number theoretic transform, this leaves off the first step of the normal iFFT --// to account for the fact that 3329 does not have a 512th root of unity, using --// the precomputed 128 roots of unity stored in |kInverseNTTRoots|. --static void scalar_inverse_ntt(scalar *s) { -- int step = DEGREE / 2; -- // `int` is used here because using `size_t` throughout caused a ~5% slowdown -- // with Clang 14 on Aarch64. -- for (int offset = 2; offset < DEGREE; offset <<= 1) { -- step >>= 1; -- int k = 0; -- for (int i = 0; i < step; i++) { -- uint32_t step_root = kInverseNTTRoots[i + step]; -- for (int j = k; j < k + offset; j++) { -- uint16_t odd = s->c[j + offset]; -- uint16_t even = s->c[j]; -- s->c[j] = reduce_once(odd + even); -- s->c[j + offset] = reduce(step_root * (even - odd + kPrime)); -+/************************************************* -+* Name: ntt -+* -+* Description: Inplace number-theoretic transform (NTT) in Rq. -+* input is in standard order, output is in bitreversed order -+* -+* Arguments: - int16_t r[256]: pointer to input/output vector of elements of Zq -+**************************************************/ -+static void ntt(int16_t r[256]) { -+ unsigned int len, start, j, k; -+ int16_t t, zeta; -+ -+ k = 1; -+ for(len = 128; len >= 2; len >>= 1) { -+ for(start = 0; start < 256; start = j + len) { -+ zeta = zetas[k++]; -+ for(j = start; j < start + len; j++) { -+ t = fqmul(zeta, r[j + len]); -+ r[j + len] = r[j] - t; -+ r[j] = r[j] + t; - } -- k += 2 * offset; - } - } -- for (int i = 0; i < DEGREE; i++) { -- s->c[i] = reduce(s->c[i] * kInverseDegree); -- } - } - --static void vector_inverse_ntt(vector *a) { -- for (int i = 0; i < RANK; i++) { -- scalar_inverse_ntt(&a->v[i]); -+/************************************************* -+* Name: invntt_tomont -+* -+* Description: Inplace inverse number-theoretic transform in Rq and -+* multiplication by Montgomery factor 2^16. -+* Input is in bitreversed order, output is in standard order -+* -+* Arguments: - int16_t r[256]: pointer to input/output vector of elements of Zq -+**************************************************/ -+static void invntt(int16_t r[256]) { -+ unsigned int start, len, j, k; -+ int16_t t, zeta; -+ const int16_t f = 1441; // mont^2/128 -+ -+ k = 127; -+ for(len = 2; len <= 128; len <<= 1) { -+ for(start = 0; start < 256; start = j + len) { -+ zeta = zetas[k--]; -+ for(j = start; j < start + len; j++) { -+ t = r[j]; -+ r[j] = barrett_reduce(t + r[j + len]); -+ r[j + len] = r[j + len] - t; -+ r[j + len] = fqmul(zeta, r[j + len]); -+ } -+ } - } -+ -+ for(j = 0; j < 256; j++) -+ r[j] = fqmul(r[j], f); -+} -+ -+/************************************************* -+* Name: basemul -+* -+* Description: Multiplication of polynomials in Zq[X]/(X^2-zeta) -+* used for multiplication of elements in Rq in NTT domain -+* -+* Arguments: - int16_t r[2]: pointer to the output polynomial -+* - const int16_t a[2]: pointer to the first factor -+* - const int16_t b[2]: pointer to the second factor -+* - int16_t zeta: integer defining the reduction polynomial -+**************************************************/ -+static void basemul(int16_t r[2], const int16_t a[2], const int16_t b[2], int16_t zeta) -+{ -+ r[0] = fqmul(a[1], b[1]); -+ r[0] = fqmul(r[0], zeta); -+ r[0] += fqmul(a[0], b[0]); -+ r[1] = fqmul(a[0], b[1]); -+ r[1] += fqmul(a[1], b[0]); - } - --static void scalar_add(scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE; i++) { -- lhs->c[i] = reduce_once(lhs->c[i] + rhs->c[i]); -+// -+// poly.c -+// -+ -+/************************************************* -+* Name: poly_compress -+* -+* Description: Compression and subsequent serialization of a polynomial -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* (of length KYBER_POLYCOMPRESSEDBYTES) -+* - const poly *a: pointer to input polynomial -+**************************************************/ -+static void poly_compress(uint8_t r[KYBER_POLYCOMPRESSEDBYTES], const poly *a) -+{ -+ unsigned int i,j; -+ int16_t u; -+ uint32_t d0; -+ uint8_t t[8]; -+ -+#if (KYBER_POLYCOMPRESSEDBYTES == 128) -+ for(i=0;icoeffs[8*i+j]; -+ u += (u >> 15) & KYBER_Q; -+ d0 = u << 4; -+ d0 += 1665; -+ d0 *= 80635; -+ d0 >>= 28; -+ t[j] = d0 & 0xf; -+ } -+ -+ r[0] = t[0] | (t[1] << 4); -+ r[1] = t[2] | (t[3] << 4); -+ r[2] = t[4] | (t[5] << 4); -+ r[3] = t[6] | (t[7] << 4); -+ r += 4; - } -+#elif (KYBER_POLYCOMPRESSEDBYTES == 160) -+ for(i=0;icoeffs[8*i+j]; -+ u += (u >> 15) & KYBER_Q; -+ d0 = u << 5; -+ d0 += 1664; -+ d0 *= 40318; -+ d0 >>= 27; -+ t[j] = d0 & 0x1f; -+ } -+ -+ r[0] = (t[0] >> 0) | (t[1] << 5); -+ r[1] = (t[1] >> 3) | (t[2] << 2) | (t[3] << 7); -+ r[2] = (t[3] >> 1) | (t[4] << 4); -+ r[3] = (t[4] >> 4) | (t[5] << 1) | (t[6] << 6); -+ r[4] = (t[6] >> 2) | (t[7] << 3); -+ r += 5; -+ } -+#else -+#error "KYBER_POLYCOMPRESSEDBYTES needs to be in {128, 160}" -+#endif - } - --static void scalar_sub(scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE; i++) { -- lhs->c[i] = reduce_once(lhs->c[i] - rhs->c[i] + kPrime); -+/************************************************* -+* Name: poly_decompress -+* -+* Description: De-serialization and subsequent decompression of a polynomial; -+* approximate inverse of poly_compress -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *a: pointer to input byte array -+* (of length KYBER_POLYCOMPRESSEDBYTES bytes) -+**************************************************/ -+static void poly_decompress(poly *r, const uint8_t a[KYBER_POLYCOMPRESSEDBYTES]) -+{ -+ unsigned int i; -+ -+#if (KYBER_POLYCOMPRESSEDBYTES == 128) -+ for(i=0;icoeffs[2*i+0] = (((uint16_t)(a[0] & 15)*KYBER_Q) + 8) >> 4; -+ r->coeffs[2*i+1] = (((uint16_t)(a[0] >> 4)*KYBER_Q) + 8) >> 4; -+ a += 1; -+ } -+#elif (KYBER_POLYCOMPRESSEDBYTES == 160) -+ unsigned int j; -+ uint8_t t[8]; -+ for(i=0;i> 0); -+ t[1] = (a[0] >> 5) | (a[1] << 3); -+ t[2] = (a[1] >> 2); -+ t[3] = (a[1] >> 7) | (a[2] << 1); -+ t[4] = (a[2] >> 4) | (a[3] << 4); -+ t[5] = (a[3] >> 1); -+ t[6] = (a[3] >> 6) | (a[4] << 2); -+ t[7] = (a[4] >> 3); -+ a += 5; -+ -+ for(j=0;j<8;j++) -+ r->coeffs[8*i+j] = ((uint32_t)(t[j] & 31)*KYBER_Q + 16) >> 5; - } -+#else -+#error "KYBER_POLYCOMPRESSEDBYTES needs to be in {128, 160}" -+#endif - } - --// Multiplying two scalars in the number theoretically transformed state. Since --// 3329 does not have a 512th root of unity, this means we have to interpret --// the 2*ith and (2*i+1)th entries of the scalar as elements of GF(3329)[X]/(X^2 --// - 17^(2*bitreverse(i)+1)) The value of 17^(2*bitreverse(i)+1) mod 3329 is --// stored in the precomputed |kModRoots| table. Note that our Barrett transform --// only allows us to multipy two reduced numbers together, so we need some --// intermediate reduction steps, even if an uint64_t could hold 3 multiplied --// numbers. --static void scalar_mult(scalar *out, const scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE / 2; i++) { -- uint32_t real_real = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i]; -- uint32_t img_img = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i + 1]; -- uint32_t real_img = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i + 1]; -- uint32_t img_real = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i]; -- out->c[2 * i] = -- reduce(real_real + (uint32_t)reduce(img_img) * kModRoots[i]); -- out->c[2 * i + 1] = reduce(img_real + real_img); -+/************************************************* -+* Name: poly_tobytes -+* -+* Description: Serialization of a polynomial -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* (needs space for KYBER_POLYBYTES bytes) -+* - const poly *a: pointer to input polynomial -+**************************************************/ -+static void poly_tobytes(uint8_t r[KYBER_POLYBYTES], const poly *a) -+{ -+ unsigned int i; -+ uint16_t t0, t1; -+ -+ for(i=0;icoeffs[2*i]; -+ t0 += ((int16_t)t0 >> 15) & KYBER_Q; -+ t1 = a->coeffs[2*i+1]; -+ t1 += ((int16_t)t1 >> 15) & KYBER_Q; -+ r[3*i+0] = (t0 >> 0); -+ r[3*i+1] = (t0 >> 8) | (t1 << 4); -+ r[3*i+2] = (t1 >> 4); - } - } - --static void vector_add(vector *lhs, const vector *rhs) { -- for (int i = 0; i < RANK; i++) { -- scalar_add(&lhs->v[i], &rhs->v[i]); -+/************************************************* -+* Name: poly_frombytes -+* -+* Description: De-serialization of a polynomial; -+* inverse of poly_tobytes -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *a: pointer to input byte array -+* (of KYBER_POLYBYTES bytes) -+**************************************************/ -+static void poly_frombytes(poly *r, const uint8_t a[KYBER_POLYBYTES]) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[2*i] = ((a[3*i+0] >> 0) | ((uint16_t)a[3*i+1] << 8)) & 0xFFF; -+ r->coeffs[2*i+1] = ((a[3*i+1] >> 4) | ((uint16_t)a[3*i+2] << 4)) & 0xFFF; - } - } - --static void matrix_mult(vector *out, const matrix *m, const vector *a) { -- vector_zero(out); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- scalar product; -- scalar_mult(&product, &m->v[i][j], &a->v[j]); -- scalar_add(&out->v[i], &product); -+/************************************************* -+* Name: poly_frommsg -+* -+* Description: Convert 32-byte message to polynomial -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *msg: pointer to input message -+**************************************************/ -+static void poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCPA_MSGBYTES]) -+{ -+ unsigned int i,j; -+ int16_t mask; -+ -+#if (KYBER_INDCPA_MSGBYTES != KYBER_N/8) -+#error "KYBER_INDCPA_MSGBYTES must be equal to KYBER_N/8 bytes!" -+#endif -+ -+ for(i=0;i> j)&1); -+ r->coeffs[8*i+j] = mask & ((KYBER_Q+1)/2); - } - } - } - --static void matrix_mult_transpose(vector *out, const matrix *m, -- const vector *a) { -- vector_zero(out); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- scalar product; -- scalar_mult(&product, &m->v[j][i], &a->v[j]); -- scalar_add(&out->v[i], &product); -+/************************************************* -+* Name: poly_tomsg -+* -+* Description: Convert polynomial to 32-byte message -+* -+* Arguments: - uint8_t *msg: pointer to output message -+* - const poly *a: pointer to input polynomial -+**************************************************/ -+static void poly_tomsg(uint8_t msg[KYBER_INDCPA_MSGBYTES], const poly *a) -+{ -+ unsigned int i,j; -+ uint32_t t; -+ -+ for(i=0;icoeffs[8*i+j]; -+ t <<= 1; -+ t += 1665; -+ t *= 80635; -+ t >>= 28; -+ t &= 1; -+ msg[i] |= t << j; - } - } - } - --static void scalar_inner_product(scalar *out, const vector *lhs, -- const vector *rhs) { -- scalar_zero(out); -- for (int i = 0; i < RANK; i++) { -- scalar product; -- scalar_mult(&product, &lhs->v[i], &rhs->v[i]); -- scalar_add(out, &product); -- } -+/************************************************* -+* Name: poly_getnoise_eta1 -+* -+* Description: Sample a polynomial deterministically from a seed and a nonce, -+* with output polynomial close to centered binomial distribution -+* with parameter KYBER_ETA1 -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *seed: pointer to input seed -+* (of length KYBER_SYMBYTES bytes) -+* - uint8_t nonce: one-byte input nonce -+**************************************************/ -+static void poly_getnoise_eta1(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce) -+{ -+ uint8_t buf[KYBER_ETA1*KYBER_N/4]; -+ prf(buf, sizeof(buf), seed, nonce); -+ poly_cbd_eta1(r, buf); - } - --// Algorithm 1 of the Kyber spec. Rejection samples a Keccak stream to get --// uniformly distributed elements. This is used for matrix expansion and only --// operates on public inputs. --static void scalar_from_keccak_vartime(scalar *out, -- struct BORINGSSL_keccak_st *keccak_ctx) { -- assert(keccak_ctx->offset == 0); -- assert(keccak_ctx->rate_bytes == 168); -- static_assert(168 % 3 == 0, "block and coefficient boundaries do not align"); -- -- int done = 0; -- while (done < DEGREE) { -- uint8_t block[168]; -- BORINGSSL_keccak_squeeze(keccak_ctx, block, sizeof(block)); -- for (size_t i = 0; i < sizeof(block) && done < DEGREE; i += 3) { -- uint16_t d1 = block[i] + 256 * (block[i + 1] % 16); -- uint16_t d2 = block[i + 1] / 16 + 16 * block[i + 2]; -- if (d1 < kPrime) { -- out->c[done++] = d1; -- } -- if (d2 < kPrime && done < DEGREE) { -- out->c[done++] = d2; -- } -- } -- } -+/************************************************* -+* Name: poly_getnoise_eta2 -+* -+* Description: Sample a polynomial deterministically from a seed and a nonce, -+* with output polynomial close to centered binomial distribution -+* with parameter KYBER_ETA2 -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *seed: pointer to input seed -+* (of length KYBER_SYMBYTES bytes) -+* - uint8_t nonce: one-byte input nonce -+**************************************************/ -+static void poly_getnoise_eta2(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce) -+{ -+ uint8_t buf[KYBER_ETA2*KYBER_N/4]; -+ prf(buf, sizeof(buf), seed, nonce); -+ poly_cbd_eta2(r, buf); - } - --// Algorithm 2 of the Kyber spec, with eta fixed to two and the PRF call --// included. Creates binominally distributed elements by sampling 2*|eta| bits, --// and setting the coefficient to the count of the first bits minus the count of --// the second bits, resulting in a centered binomial distribution. Since eta is --// two this gives -2/2 with a probability of 1/16, -1/1 with probability 1/4, --// and 0 with probability 3/8. --static void scalar_centered_binomial_distribution_eta_2_with_prf( -- scalar *out, const uint8_t input[33]) { -- uint8_t entropy[128]; -- static_assert(sizeof(entropy) == 2 * /*kEta=*/2 * DEGREE / 8, ""); -- BORINGSSL_keccak(entropy, sizeof(entropy), input, 33, boringssl_shake256); -- -- for (int i = 0; i < DEGREE; i += 2) { -- uint8_t byte = entropy[i / 2]; -- -- uint16_t value = kPrime; -- value += (byte & 1) + ((byte >> 1) & 1); -- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); -- out->c[i] = reduce_once(value); -- -- byte >>= 4; -- value = kPrime; -- value += (byte & 1) + ((byte >> 1) & 1); -- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); -- out->c[i + 1] = reduce_once(value); -- } -+ -+/************************************************* -+* Name: poly_ntt -+* -+* Description: Computes negacyclic number-theoretic transform (NTT) of -+* a polynomial in place; -+* inputs assumed to be in normal order, output in bitreversed order -+* -+* Arguments: - uint16_t *r: pointer to in/output polynomial -+**************************************************/ -+static void poly_ntt(poly *r) -+{ -+ ntt(r->coeffs); -+ poly_reduce(r); - } - --// Generates a secret vector by using --// |scalar_centered_binomial_distribution_eta_2_with_prf|, using the given seed --// appending and incrementing |counter| for entry of the vector. --static void vector_generate_secret_eta_2(vector *out, uint8_t *counter, -- const uint8_t seed[32]) { -- uint8_t input[33]; -- OPENSSL_memcpy(input, seed, 32); -- for (int i = 0; i < RANK; i++) { -- input[32] = (*counter)++; -- scalar_centered_binomial_distribution_eta_2_with_prf(&out->v[i], input); -- } -+/************************************************* -+* Name: poly_invntt_tomont -+* -+* Description: Computes inverse of negacyclic number-theoretic transform (NTT) -+* of a polynomial in place; -+* inputs assumed to be in bitreversed order, output in normal order -+* -+* Arguments: - uint16_t *a: pointer to in/output polynomial -+**************************************************/ -+static void poly_invntt_tomont(poly *r) -+{ -+ invntt(r->coeffs); - } - --// Expands the matrix of a seed for key generation and for encaps-CPA. --static void matrix_expand(matrix *out, const uint8_t rho[32]) { -- uint8_t input[34]; -- OPENSSL_memcpy(input, rho, 32); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- input[32] = i; -- input[33] = j; -- struct BORINGSSL_keccak_st keccak_ctx; -- BORINGSSL_keccak_init(&keccak_ctx, input, sizeof(input), -- boringssl_shake128); -- scalar_from_keccak_vartime(&out->v[i][j], &keccak_ctx); -- } -+/************************************************* -+* Name: poly_basemul_montgomery -+* -+* Description: Multiplication of two polynomials in NTT domain -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const poly *a: pointer to first input polynomial -+* - const poly *b: pointer to second input polynomial -+**************************************************/ -+static void poly_basemul_montgomery(poly *r, const poly *a, const poly *b) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[4*i], &a->coeffs[4*i], &b->coeffs[4*i], zetas[64+i]); -+ basemul(&r->coeffs[4*i+2], &a->coeffs[4*i+2], &b->coeffs[4*i+2], -zetas[64+i]); - } - } - --static const uint8_t kMasks[8] = {0x01, 0x03, 0x07, 0x0f, -- 0x1f, 0x3f, 0x7f, 0xff}; -- --static void scalar_encode(uint8_t *out, const scalar *s, int bits) { -- assert(bits <= (int)sizeof(*s->c) * 8 && bits != 1); -- -- uint8_t out_byte = 0; -- int out_byte_bits = 0; -- -- for (int i = 0; i < DEGREE; i++) { -- uint16_t element = s->c[i]; -- int element_bits_done = 0; -- -- while (element_bits_done < bits) { -- int chunk_bits = bits - element_bits_done; -- int out_bits_remaining = 8 - out_byte_bits; -- if (chunk_bits >= out_bits_remaining) { -- chunk_bits = out_bits_remaining; -- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; -- *out = out_byte; -- out++; -- out_byte_bits = 0; -- out_byte = 0; -- } else { -- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; -- out_byte_bits += chunk_bits; -+/************************************************* -+* Name: poly_tomont -+* -+* Description: Inplace conversion of all coefficients of a polynomial -+* from normal domain to Montgomery domain -+* -+* Arguments: - poly *r: pointer to input/output polynomial -+**************************************************/ -+static void poly_tomont(poly *r) -+{ -+ unsigned int i; -+ const int16_t f = (1ULL << 32) % KYBER_Q; -+ for(i=0;icoeffs[i] = montgomery_reduce((int32_t)r->coeffs[i]*f); -+} -+ -+/************************************************* -+* Name: poly_reduce -+* -+* Description: Applies Barrett reduction to all coefficients of a polynomial -+* for details of the Barrett reduction see comments in reduce.c -+* -+* Arguments: - poly *r: pointer to input/output polynomial -+**************************************************/ -+static void poly_reduce(poly *r) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[i] = barrett_reduce(r->coeffs[i]); -+} -+ -+/************************************************* -+* Name: poly_add -+* -+* Description: Add two polynomials; no modular reduction is performed -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const poly *a: pointer to first input polynomial -+* - const poly *b: pointer to second input polynomial -+**************************************************/ -+static void poly_add(poly *r, const poly *a, const poly *b) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[i] = a->coeffs[i] + b->coeffs[i]; -+} -+ -+/************************************************* -+* Name: poly_sub -+* -+* Description: Subtract two polynomials; no modular reduction is performed -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const poly *a: pointer to first input polynomial -+* - const poly *b: pointer to second input polynomial -+**************************************************/ -+static void poly_sub(poly *r, const poly *a, const poly *b) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[i] = a->coeffs[i] - b->coeffs[i]; -+} -+ -+// -+// polyvec.c -+// -+ -+/************************************************* -+* Name: polyvec_compress -+* -+* Description: Compress and serialize vector of polynomials -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* (needs space for KYBER_POLYVECCOMPRESSEDBYTES) -+* - const polyvec *a: pointer to input vector of polynomials -+**************************************************/ -+static void polyvec_compress(uint8_t r[KYBER_POLYVECCOMPRESSEDBYTES], const polyvec *a) -+{ -+ unsigned int i,j,k; -+ uint64_t d0; -+ -+#if (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 352)) -+ uint16_t t[8]; -+ for(i=0;ivec[i].coeffs[8*j+k]; -+ t[k] += ((int16_t)t[k] >> 15) & KYBER_Q; -+ d0 = t[k]; -+ d0 <<= 11; -+ d0 += 1664; -+ d0 *= 645084; -+ d0 >>= 31; -+ t[k] = d0 & 0x7ff; - } - -- element_bits_done += chunk_bits; -- element >>= chunk_bits; -+ r[ 0] = (t[0] >> 0); -+ r[ 1] = (t[0] >> 8) | (t[1] << 3); -+ r[ 2] = (t[1] >> 5) | (t[2] << 6); -+ r[ 3] = (t[2] >> 2); -+ r[ 4] = (t[2] >> 10) | (t[3] << 1); -+ r[ 5] = (t[3] >> 7) | (t[4] << 4); -+ r[ 6] = (t[4] >> 4) | (t[5] << 7); -+ r[ 7] = (t[5] >> 1); -+ r[ 8] = (t[5] >> 9) | (t[6] << 2); -+ r[ 9] = (t[6] >> 6) | (t[7] << 5); -+ r[10] = (t[7] >> 3); -+ r += 11; - } - } -+#elif (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 320)) -+ uint16_t t[4]; -+ for(i=0;ivec[i].coeffs[4*j+k]; -+ t[k] += ((int16_t)t[k] >> 15) & KYBER_Q; -+ d0 = t[k]; -+ d0 <<= 10; -+ d0 += 1665; -+ d0 *= 1290167; -+ d0 >>= 32; -+ t[k] = d0 & 0x3ff; -+ } - -- if (out_byte_bits > 0) { -- *out = out_byte; -+ r[0] = (t[0] >> 0); -+ r[1] = (t[0] >> 8) | (t[1] << 2); -+ r[2] = (t[1] >> 6) | (t[2] << 4); -+ r[3] = (t[2] >> 4) | (t[3] << 6); -+ r[4] = (t[3] >> 2); -+ r += 5; -+ } - } -+#else -+#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" -+#endif - } - --// scalar_encode_1 is |scalar_encode| specialised for |bits| == 1. --static void scalar_encode_1(uint8_t out[32], const scalar *s) { -- for (int i = 0; i < DEGREE; i += 8) { -- uint8_t out_byte = 0; -- for (int j = 0; j < 8; j++) { -- out_byte |= (s->c[i + j] & 1) << j; -+/************************************************* -+* Name: polyvec_decompress -+* -+* Description: De-serialize and decompress vector of polynomials; -+* approximate inverse of polyvec_compress -+* -+* Arguments: - polyvec *r: pointer to output vector of polynomials -+* - const uint8_t *a: pointer to input byte array -+* (of length KYBER_POLYVECCOMPRESSEDBYTES) -+**************************************************/ -+static void polyvec_decompress(polyvec *r, const uint8_t a[KYBER_POLYVECCOMPRESSEDBYTES]) -+{ -+ unsigned int i,j,k; -+ -+#if (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 352)) -+ uint16_t t[8]; -+ for(i=0;i> 0) | ((uint16_t)a[ 1] << 8); -+ t[1] = (a[1] >> 3) | ((uint16_t)a[ 2] << 5); -+ t[2] = (a[2] >> 6) | ((uint16_t)a[ 3] << 2) | ((uint16_t)a[4] << 10); -+ t[3] = (a[4] >> 1) | ((uint16_t)a[ 5] << 7); -+ t[4] = (a[5] >> 4) | ((uint16_t)a[ 6] << 4); -+ t[5] = (a[6] >> 7) | ((uint16_t)a[ 7] << 1) | ((uint16_t)a[8] << 9); -+ t[6] = (a[8] >> 2) | ((uint16_t)a[ 9] << 6); -+ t[7] = (a[9] >> 5) | ((uint16_t)a[10] << 3); -+ a += 11; -+ -+ for(k=0;k<8;k++) -+ r->vec[i].coeffs[8*j+k] = ((uint32_t)(t[k] & 0x7FF)*KYBER_Q + 1024) >> 11; -+ } -+ } -+#elif (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 320)) -+ uint16_t t[4]; -+ for(i=0;i> 0) | ((uint16_t)a[1] << 8); -+ t[1] = (a[1] >> 2) | ((uint16_t)a[2] << 6); -+ t[2] = (a[2] >> 4) | ((uint16_t)a[3] << 4); -+ t[3] = (a[3] >> 6) | ((uint16_t)a[4] << 2); -+ a += 5; -+ -+ for(k=0;k<4;k++) -+ r->vec[i].coeffs[4*j+k] = ((uint32_t)(t[k] & 0x3FF)*KYBER_Q + 512) >> 10; - } -- *out = out_byte; -- out++; - } -+#else -+#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" -+#endif - } - --// Encodes an entire vector into 32*|RANK|*|bits| bytes. Note that since 256 --// (DEGREE) is divisible by 8, the individual vector entries will always fill a --// whole number of bytes, so we do not need to worry about bit packing here. --static void vector_encode(uint8_t *out, const vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_encode(out + i * bits * DEGREE / 8, &a->v[i], bits); -- } -+/************************************************* -+* Name: polyvec_tobytes -+* -+* Description: Serialize vector of polynomials -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* (needs space for KYBER_POLYVECBYTES) -+* - const polyvec *a: pointer to input vector of polynomials -+**************************************************/ -+static void polyvec_tobytes(uint8_t r[KYBER_POLYVECBYTES], const polyvec *a) -+{ -+ unsigned int i; -+ for(i=0;ivec[i]); - } - --// scalar_decode parses |DEGREE * bits| bits from |in| into |DEGREE| values in --// |out|. It returns one on success and zero if any parsed value is >= --// |kPrime|. --static int scalar_decode(scalar *out, const uint8_t *in, int bits) { -- assert(bits <= (int)sizeof(*out->c) * 8 && bits != 1); -+/************************************************* -+* Name: polyvec_frombytes -+* -+* Description: De-serialize vector of polynomials; -+* inverse of polyvec_tobytes -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* - const polyvec *a: pointer to input vector of polynomials -+* (of length KYBER_POLYVECBYTES) -+**************************************************/ -+static void polyvec_frombytes(polyvec *r, const uint8_t a[KYBER_POLYVECBYTES]) -+{ -+ unsigned int i; -+ for(i=0;ivec[i], a+i*KYBER_POLYBYTES); -+} - -- uint8_t in_byte = 0; -- int in_byte_bits_left = 0; -+/************************************************* -+* Name: polyvec_ntt -+* -+* Description: Apply forward NTT to all elements of a vector of polynomials -+* -+* Arguments: - polyvec *r: pointer to in/output vector of polynomials -+**************************************************/ -+static void polyvec_ntt(polyvec *r) -+{ -+ unsigned int i; -+ for(i=0;ivec[i]); -+} - -- for (int i = 0; i < DEGREE; i++) { -- uint16_t element = 0; -- int element_bits_done = 0; -+/************************************************* -+* Name: polyvec_invntt_tomont -+* -+* Description: Apply inverse NTT to all elements of a vector of polynomials -+* and multiply by Montgomery factor 2^16 -+* -+* Arguments: - polyvec *r: pointer to in/output vector of polynomials -+**************************************************/ -+static void polyvec_invntt_tomont(polyvec *r) -+{ -+ unsigned int i; -+ for(i=0;ivec[i]); -+} - -- while (element_bits_done < bits) { -- if (in_byte_bits_left == 0) { -- in_byte = *in; -- in++; -- in_byte_bits_left = 8; -- } -+/************************************************* -+* Name: polyvec_basemul_acc_montgomery -+* -+* Description: Multiply elements of a and b in NTT domain, accumulate into r, -+* and multiply by 2^-16. -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const polyvec *a: pointer to first input vector of polynomials -+* - const polyvec *b: pointer to second input vector of polynomials -+**************************************************/ -+static void polyvec_basemul_acc_montgomery(poly *r, const polyvec *a, const polyvec *b) -+{ -+ unsigned int i; -+ poly t; -+ -+ poly_basemul_montgomery(r, &a->vec[0], &b->vec[0]); -+ for(i=1;ivec[i], &b->vec[i]); -+ poly_add(r, r, &t); -+ } - -- int chunk_bits = bits - element_bits_done; -- if (chunk_bits > in_byte_bits_left) { -- chunk_bits = in_byte_bits_left; -- } -+ poly_reduce(r); -+} - -- element |= (in_byte & kMasks[chunk_bits - 1]) << element_bits_done; -- in_byte_bits_left -= chunk_bits; -- in_byte >>= chunk_bits; -+/************************************************* -+* Name: polyvec_reduce -+* -+* Description: Applies Barrett reduction to each coefficient -+* of each element of a vector of polynomials; -+* for details of the Barrett reduction see comments in reduce.c -+* -+* Arguments: - polyvec *r: pointer to input/output polynomial -+**************************************************/ -+static void polyvec_reduce(polyvec *r) -+{ -+ unsigned int i; -+ for(i=0;ivec[i]); -+} - -- element_bits_done += chunk_bits; -- } -+/************************************************* -+* Name: polyvec_add -+* -+* Description: Add vectors of polynomials -+* -+* Arguments: - polyvec *r: pointer to output vector of polynomials -+* - const polyvec *a: pointer to first input vector of polynomials -+* - const polyvec *b: pointer to second input vector of polynomials -+**************************************************/ -+static void polyvec_add(polyvec *r, const polyvec *a, const polyvec *b) -+{ -+ unsigned int i; -+ for(i=0;ivec[i], &a->vec[i], &b->vec[i]); -+} - -- if (element >= kPrime) { -- return 0; -- } -- out->c[i] = element; -- } -+// -+// indcpa.c -+// -+ -+/************************************************* -+* Name: pack_pk -+* -+* Description: Serialize the public key as concatenation of the -+* serialized vector of polynomials pk -+* and the public seed used to generate the matrix A. -+* -+* Arguments: uint8_t *r: pointer to the output serialized public key -+* polyvec *pk: pointer to the input public-key polyvec -+* const uint8_t *seed: pointer to the input public seed -+**************************************************/ -+static void pack_pk(uint8_t r[KYBER_INDCPA_PUBLICKEYBYTES], -+ polyvec *pk, -+ const uint8_t seed[KYBER_SYMBYTES]) -+{ -+ size_t i; -+ polyvec_tobytes(r, pk); -+ for(i=0;ic[i + j] = in_byte & 1; -- in_byte >>= 1; -- } -+/************************************************* -+* Name: pack_sk -+* -+* Description: Serialize the secret key -+* -+* Arguments: - uint8_t *r: pointer to output serialized secret key -+* - polyvec *sk: pointer to input vector of polynomials (secret key) -+**************************************************/ -+static void pack_sk(uint8_t r[KYBER_INDCPA_SECRETKEYBYTES], polyvec *sk) -+{ -+ polyvec_tobytes(r, sk); -+} -+ -+/************************************************* -+* Name: unpack_sk -+* -+* Description: De-serialize the secret key; inverse of pack_sk -+* -+* Arguments: - polyvec *sk: pointer to output vector of polynomials (secret key) -+* - const uint8_t *packedsk: pointer to input serialized secret key -+**************************************************/ -+static void unpack_sk(polyvec *sk, const uint8_t packedsk[KYBER_INDCPA_SECRETKEYBYTES]) -+{ -+ polyvec_frombytes(sk, packedsk); -+} -+ -+/************************************************* -+* Name: pack_ciphertext -+* -+* Description: Serialize the ciphertext as concatenation of the -+* compressed and serialized vector of polynomials b -+* and the compressed and serialized polynomial v -+* -+* Arguments: uint8_t *r: pointer to the output serialized ciphertext -+* poly *pk: pointer to the input vector of polynomials b -+* poly *v: pointer to the input polynomial v -+**************************************************/ -+static void pack_ciphertext(uint8_t r[KYBER_INDCPA_BYTES], polyvec *b, poly *v) -+{ -+ polyvec_compress(r, b); -+ poly_compress(r+KYBER_POLYVECCOMPRESSEDBYTES, v); -+} -+ -+/************************************************* -+* Name: unpack_ciphertext -+* -+* Description: De-serialize and decompress ciphertext from a byte array; -+* approximate inverse of pack_ciphertext -+* -+* Arguments: - polyvec *b: pointer to the output vector of polynomials b -+* - poly *v: pointer to the output polynomial v -+* - const uint8_t *c: pointer to the input serialized ciphertext -+**************************************************/ -+static void unpack_ciphertext(polyvec *b, poly *v, const uint8_t c[KYBER_INDCPA_BYTES]) -+{ -+ polyvec_decompress(b, c); -+ poly_decompress(v, c+KYBER_POLYVECCOMPRESSEDBYTES); -+} -+ -+/************************************************* -+* Name: rej_uniform -+* -+* Description: Run rejection sampling on uniform random bytes to generate -+* uniform random integers mod q -+* -+* Arguments: - int16_t *r: pointer to output buffer -+* - unsigned int len: requested number of 16-bit integers (uniform mod q) -+* - const uint8_t *buf: pointer to input buffer (assumed to be uniformly random bytes) -+* - unsigned int buflen: length of input buffer in bytes -+* -+* Returns number of sampled 16-bit integers (at most len) -+**************************************************/ -+static unsigned int rej_uniform(int16_t *r, -+ unsigned int len, -+ const uint8_t *buf, -+ unsigned int buflen) -+{ -+ unsigned int ctr, pos; -+ uint16_t val0, val1; -+ -+ ctr = pos = 0; -+ while(ctr < len && pos + 3 <= buflen) { -+ val0 = ((buf[pos+0] >> 0) | ((uint16_t)buf[pos+1] << 8)) & 0xFFF; -+ val1 = ((buf[pos+1] >> 4) | ((uint16_t)buf[pos+2] << 4)) & 0xFFF; -+ pos += 3; -+ -+ if(val0 < KYBER_Q) -+ r[ctr++] = val0; -+ if(ctr < len && val1 < KYBER_Q) -+ r[ctr++] = val1; - } -+ -+ return ctr; - } - --// Decodes 32*|RANK|*|bits| bytes from |in| into |out|. It returns one on --// success or zero if any parsed value is >= |kPrime|. --static int vector_decode(vector *out, const uint8_t *in, int bits) { -- for (int i = 0; i < RANK; i++) { -- if (!scalar_decode(&out->v[i], in + i * bits * DEGREE / 8, bits)) { -- return 0; -+#define gen_a(A,B) gen_matrix(A,B,0) -+#define gen_at(A,B) gen_matrix(A,B,1) -+ -+/************************************************* -+* Name: gen_matrix -+* -+* Description: Deterministically generate matrix A (or the transpose of A) -+* from a seed. Entries of the matrix are polynomials that look -+* uniformly random. Performs rejection sampling on output of -+* a XOF -+* -+* Arguments: - polyvec *a: pointer to ouptput matrix A -+* - const uint8_t *seed: pointer to input seed -+* - int transposed: boolean deciding whether A or A^T is generated -+**************************************************/ -+#define GEN_MATRIX_NBLOCKS ((12*KYBER_N/8*(1 << 12)/KYBER_Q + XOF_BLOCKBYTES)/XOF_BLOCKBYTES) -+// Not static for benchmarking -+static void gen_matrix(polyvec *a, const uint8_t seed[KYBER_SYMBYTES], int transposed) -+{ -+ unsigned int ctr, i, j, k; -+ unsigned int buflen, off; -+ uint8_t buf[GEN_MATRIX_NBLOCKS*XOF_BLOCKBYTES+2]; -+ xof_state state; -+ -+ for(i=0;i> kBarrettShift; -- uint32_t remainder = product - quotient * kPrime; -- -- // Adjust the quotient to round correctly: -- // 0 <= remainder <= kHalfPrime round to 0 -- // kHalfPrime < remainder <= kPrime + kHalfPrime round to 1 -- // kPrime + kHalfPrime < remainder < 2 * kPrime round to 2 -- assert(remainder < 2u * kPrime); -- quotient += 1 & constant_time_lt_w(kHalfPrime, remainder); -- quotient += 1 & constant_time_lt_w(kPrime + kHalfPrime, remainder); -- return quotient & ((1 << bits) - 1); --} -- --// Decompresses |x| by using an equi-distant representative. The formula is --// round(kPrime/2^|bits|*x). Note that 2^|bits| being the divisor allows us to --// implement this logic using only bit operations. --static uint16_t decompress(uint16_t x, int bits) { -- uint32_t product = (uint32_t)x * kPrime; -- uint32_t power = 1 << bits; -- // This is |product| % power, since |power| is a power of 2. -- uint32_t remainder = product & (power - 1); -- // This is |product| / power, since |power| is a power of 2. -- uint32_t lower = product >> bits; -- // The rounding logic works since the first half of numbers mod |power| have a -- // 0 as first bit, and the second half has a 1 as first bit, since |power| is -- // a power of 2. As a 12 bit number, |remainder| is always positive, so we -- // will shift in 0s for a right shift. -- return lower + (remainder >> (bits - 1)); --} -- --static void scalar_compress(scalar *s, int bits) { -- for (int i = 0; i < DEGREE; i++) { -- s->c[i] = compress(s->c[i], bits); -+/************************************************* -+* Name: indcpa_keypair -+* -+* Description: Generates public and private key for the CPA-secure -+* public-key encryption scheme underlying Kyber -+* -+* Arguments: - uint8_t *pk: pointer to output public key -+* (of length KYBER_INDCPA_PUBLICKEYBYTES bytes) -+* - uint8_t *sk: pointer to output private key -+ (of length KYBER_INDCPA_SECRETKEYBYTES bytes) -+**************************************************/ -+static void indcpa_keypair(uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], -+ uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES], -+ const uint8_t seed[KYBER_SYMBYTES]) -+{ -+ unsigned int i; -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ const uint8_t *publicseed = buf; -+ const uint8_t *noiseseed = buf+KYBER_SYMBYTES; -+ uint8_t nonce = 0; -+ polyvec a[KYBER_K], e, pkpv, skpv; -+ -+ memcpy(buf, seed, KYBER_SYMBYTES); -+ hash_g(buf, buf, KYBER_SYMBYTES); -+ -+ gen_a(a, publicseed); -+ -+ for(i=0;ic[i] = decompress(s->c[i], bits); -- } -+/************************************************* -+* Name: indcpa_enc -+* -+* Description: Encryption function of the CPA-secure -+* public-key encryption scheme underlying Kyber. -+* -+* Arguments: - uint8_t *c: pointer to output ciphertext -+* (of length KYBER_INDCPA_BYTES bytes) -+* - const uint8_t *m: pointer to input message -+* (of length KYBER_INDCPA_MSGBYTES bytes) -+* - const uint8_t *pk: pointer to input public key -+* (of length KYBER_INDCPA_PUBLICKEYBYTES) -+* - const uint8_t *coins: pointer to input random coins used as seed -+* (of length KYBER_SYMBYTES) to deterministically -+* generate all randomness -+**************************************************/ -+static int indcpa_enc(uint8_t c[KYBER_INDCPA_BYTES], -+ const uint8_t m[KYBER_INDCPA_MSGBYTES], -+ const uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], -+ const uint8_t coins[KYBER_SYMBYTES]) -+{ -+ unsigned int i; -+ uint8_t seed[KYBER_SYMBYTES]; -+ uint8_t nonce = 0; -+ polyvec sp, pkpv, ep, at[KYBER_K], b; -+ poly v, k, epp; -+ -+ if (!unpack_pk(&pkpv, seed, pk)) -+ return 0; -+ -+ poly_frommsg(&k, m); -+ gen_at(at, seed); -+ -+ for(i=0;iv[i], bits); -- } -+/************************************************* -+* Name: indcpa_dec -+* -+* Description: Decryption function of the CPA-secure -+* public-key encryption scheme underlying Kyber. -+* -+* Arguments: - uint8_t *m: pointer to output decrypted message -+* (of length KYBER_INDCPA_MSGBYTES) -+* - const uint8_t *c: pointer to input ciphertext -+* (of length KYBER_INDCPA_BYTES) -+* - const uint8_t *sk: pointer to input secret key -+* (of length KYBER_INDCPA_SECRETKEYBYTES) -+**************************************************/ -+static void indcpa_dec(uint8_t m[KYBER_INDCPA_MSGBYTES], -+ const uint8_t c[KYBER_INDCPA_BYTES], -+ const uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES]) -+{ -+ polyvec b, skpv; -+ poly v, mp; -+ -+ unpack_ciphertext(&b, &v, c); -+ unpack_sk(&skpv, sk); -+ -+ polyvec_ntt(&b); -+ polyvec_basemul_acc_montgomery(&mp, &skpv, &b); -+ poly_invntt_tomont(&mp); -+ -+ poly_sub(&mp, &v, &mp); -+ poly_reduce(&mp); -+ -+ poly_tomsg(m, &mp); - } - --static void vector_decompress(vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_decompress(&a->v[i], bits); -- } -+// -+// fips202.c -+// -+ -+/* Based on the public domain implementation in crypto_hash/keccakc512/simple/ from -+ * http://bench.cr.yp.to/supercop.html by Ronny Van Keer and the public domain "TweetFips202" -+ * implementation from https://twitter.com/tweetfips202 by Gilles Van Assche, Daniel J. Bernstein, -+ * and Peter Schwabe */ -+ -+#define NROUNDS 24 -+#define ROL(a, offset) ((a << offset) ^ (a >> (64-offset))) -+ -+/************************************************* -+* Name: load64 -+* -+* Description: Load 8 bytes into uint64_t in little-endian order -+* -+* Arguments: - const uint8_t *x: pointer to input byte array -+* -+* Returns the loaded 64-bit unsigned integer -+**************************************************/ -+static uint64_t load64(const uint8_t x[8]) { -+ unsigned int i; -+ uint64_t r = 0; -+ -+ for(i=0;i<8;i++) -+ r |= (uint64_t)x[i] << 8*i; -+ -+ return r; - } - --struct public_key { -- vector t; -- uint8_t rho[32]; -- uint8_t public_key_hash[32]; -- matrix m; -+/************************************************* -+* Name: store64 -+* -+* Description: Store a 64-bit integer to array of 8 bytes in little-endian order -+* -+* Arguments: - uint8_t *x: pointer to the output byte array (allocated) -+* - uint64_t u: input 64-bit unsigned integer -+**************************************************/ -+static void store64(uint8_t x[8], uint64_t u) { -+ unsigned int i; -+ -+ for(i=0;i<8;i++) -+ x[i] = u >> 8*i; -+} -+ -+/* Keccak round constants */ -+static const uint64_t KeccakF_RoundConstants[NROUNDS] = { -+ (uint64_t)0x0000000000000001ULL, -+ (uint64_t)0x0000000000008082ULL, -+ (uint64_t)0x800000000000808aULL, -+ (uint64_t)0x8000000080008000ULL, -+ (uint64_t)0x000000000000808bULL, -+ (uint64_t)0x0000000080000001ULL, -+ (uint64_t)0x8000000080008081ULL, -+ (uint64_t)0x8000000000008009ULL, -+ (uint64_t)0x000000000000008aULL, -+ (uint64_t)0x0000000000000088ULL, -+ (uint64_t)0x0000000080008009ULL, -+ (uint64_t)0x000000008000000aULL, -+ (uint64_t)0x000000008000808bULL, -+ (uint64_t)0x800000000000008bULL, -+ (uint64_t)0x8000000000008089ULL, -+ (uint64_t)0x8000000000008003ULL, -+ (uint64_t)0x8000000000008002ULL, -+ (uint64_t)0x8000000000000080ULL, -+ (uint64_t)0x000000000000800aULL, -+ (uint64_t)0x800000008000000aULL, -+ (uint64_t)0x8000000080008081ULL, -+ (uint64_t)0x8000000000008080ULL, -+ (uint64_t)0x0000000080000001ULL, -+ (uint64_t)0x8000000080008008ULL - }; - --static struct public_key *public_key_from_external( -- const struct KYBER_public_key *external) { -- static_assert(sizeof(struct KYBER_public_key) >= sizeof(struct public_key), -- "Kyber public key is too small"); -- static_assert(alignof(struct KYBER_public_key) >= alignof(struct public_key), -- "Kyber public key align incorrect"); -- return (struct public_key *)external; -+/************************************************* -+* Name: KeccakF1600_StatePermute -+* -+* Description: The Keccak F1600 Permutation -+* -+* Arguments: - uint64_t *state: pointer to input/output Keccak state -+**************************************************/ -+static void KeccakF1600_StatePermute(uint64_t state[25]) -+{ -+ int round; -+ -+ uint64_t Aba, Abe, Abi, Abo, Abu; -+ uint64_t Aga, Age, Agi, Ago, Agu; -+ uint64_t Aka, Ake, Aki, Ako, Aku; -+ uint64_t Ama, Ame, Ami, Amo, Amu; -+ uint64_t Asa, Ase, Asi, Aso, Asu; -+ uint64_t BCa, BCe, BCi, BCo, BCu; -+ uint64_t Da, De, Di, Do, Du; -+ uint64_t Eba, Ebe, Ebi, Ebo, Ebu; -+ uint64_t Ega, Ege, Egi, Ego, Egu; -+ uint64_t Eka, Eke, Eki, Eko, Eku; -+ uint64_t Ema, Eme, Emi, Emo, Emu; -+ uint64_t Esa, Ese, Esi, Eso, Esu; -+ -+ //copyFromState(A, state) -+ Aba = state[ 0]; -+ Abe = state[ 1]; -+ Abi = state[ 2]; -+ Abo = state[ 3]; -+ Abu = state[ 4]; -+ Aga = state[ 5]; -+ Age = state[ 6]; -+ Agi = state[ 7]; -+ Ago = state[ 8]; -+ Agu = state[ 9]; -+ Aka = state[10]; -+ Ake = state[11]; -+ Aki = state[12]; -+ Ako = state[13]; -+ Aku = state[14]; -+ Ama = state[15]; -+ Ame = state[16]; -+ Ami = state[17]; -+ Amo = state[18]; -+ Amu = state[19]; -+ Asa = state[20]; -+ Ase = state[21]; -+ Asi = state[22]; -+ Aso = state[23]; -+ Asu = state[24]; -+ -+ for(round = 0; round < NROUNDS; round += 2) { -+ // prepareTheta -+ BCa = Aba^Aga^Aka^Ama^Asa; -+ BCe = Abe^Age^Ake^Ame^Ase; -+ BCi = Abi^Agi^Aki^Ami^Asi; -+ BCo = Abo^Ago^Ako^Amo^Aso; -+ BCu = Abu^Agu^Aku^Amu^Asu; -+ -+ //thetaRhoPiChiIotaPrepareTheta(round, A, E) -+ Da = BCu^ROL(BCe, 1); -+ De = BCa^ROL(BCi, 1); -+ Di = BCe^ROL(BCo, 1); -+ Do = BCi^ROL(BCu, 1); -+ Du = BCo^ROL(BCa, 1); -+ -+ Aba ^= Da; -+ BCa = Aba; -+ Age ^= De; -+ BCe = ROL(Age, 44); -+ Aki ^= Di; -+ BCi = ROL(Aki, 43); -+ Amo ^= Do; -+ BCo = ROL(Amo, 21); -+ Asu ^= Du; -+ BCu = ROL(Asu, 14); -+ Eba = BCa ^((~BCe)& BCi ); -+ Eba ^= (uint64_t)KeccakF_RoundConstants[round]; -+ Ebe = BCe ^((~BCi)& BCo ); -+ Ebi = BCi ^((~BCo)& BCu ); -+ Ebo = BCo ^((~BCu)& BCa ); -+ Ebu = BCu ^((~BCa)& BCe ); -+ -+ Abo ^= Do; -+ BCa = ROL(Abo, 28); -+ Agu ^= Du; -+ BCe = ROL(Agu, 20); -+ Aka ^= Da; -+ BCi = ROL(Aka, 3); -+ Ame ^= De; -+ BCo = ROL(Ame, 45); -+ Asi ^= Di; -+ BCu = ROL(Asi, 61); -+ Ega = BCa ^((~BCe)& BCi ); -+ Ege = BCe ^((~BCi)& BCo ); -+ Egi = BCi ^((~BCo)& BCu ); -+ Ego = BCo ^((~BCu)& BCa ); -+ Egu = BCu ^((~BCa)& BCe ); -+ -+ Abe ^= De; -+ BCa = ROL(Abe, 1); -+ Agi ^= Di; -+ BCe = ROL(Agi, 6); -+ Ako ^= Do; -+ BCi = ROL(Ako, 25); -+ Amu ^= Du; -+ BCo = ROL(Amu, 8); -+ Asa ^= Da; -+ BCu = ROL(Asa, 18); -+ Eka = BCa ^((~BCe)& BCi ); -+ Eke = BCe ^((~BCi)& BCo ); -+ Eki = BCi ^((~BCo)& BCu ); -+ Eko = BCo ^((~BCu)& BCa ); -+ Eku = BCu ^((~BCa)& BCe ); -+ -+ Abu ^= Du; -+ BCa = ROL(Abu, 27); -+ Aga ^= Da; -+ BCe = ROL(Aga, 36); -+ Ake ^= De; -+ BCi = ROL(Ake, 10); -+ Ami ^= Di; -+ BCo = ROL(Ami, 15); -+ Aso ^= Do; -+ BCu = ROL(Aso, 56); -+ Ema = BCa ^((~BCe)& BCi ); -+ Eme = BCe ^((~BCi)& BCo ); -+ Emi = BCi ^((~BCo)& BCu ); -+ Emo = BCo ^((~BCu)& BCa ); -+ Emu = BCu ^((~BCa)& BCe ); -+ -+ Abi ^= Di; -+ BCa = ROL(Abi, 62); -+ Ago ^= Do; -+ BCe = ROL(Ago, 55); -+ Aku ^= Du; -+ BCi = ROL(Aku, 39); -+ Ama ^= Da; -+ BCo = ROL(Ama, 41); -+ Ase ^= De; -+ BCu = ROL(Ase, 2); -+ Esa = BCa ^((~BCe)& BCi ); -+ Ese = BCe ^((~BCi)& BCo ); -+ Esi = BCi ^((~BCo)& BCu ); -+ Eso = BCo ^((~BCu)& BCa ); -+ Esu = BCu ^((~BCa)& BCe ); -+ -+ // prepareTheta -+ BCa = Eba^Ega^Eka^Ema^Esa; -+ BCe = Ebe^Ege^Eke^Eme^Ese; -+ BCi = Ebi^Egi^Eki^Emi^Esi; -+ BCo = Ebo^Ego^Eko^Emo^Eso; -+ BCu = Ebu^Egu^Eku^Emu^Esu; -+ -+ //thetaRhoPiChiIotaPrepareTheta(round+1, E, A) -+ Da = BCu^ROL(BCe, 1); -+ De = BCa^ROL(BCi, 1); -+ Di = BCe^ROL(BCo, 1); -+ Do = BCi^ROL(BCu, 1); -+ Du = BCo^ROL(BCa, 1); -+ -+ Eba ^= Da; -+ BCa = Eba; -+ Ege ^= De; -+ BCe = ROL(Ege, 44); -+ Eki ^= Di; -+ BCi = ROL(Eki, 43); -+ Emo ^= Do; -+ BCo = ROL(Emo, 21); -+ Esu ^= Du; -+ BCu = ROL(Esu, 14); -+ Aba = BCa ^((~BCe)& BCi ); -+ Aba ^= (uint64_t)KeccakF_RoundConstants[round+1]; -+ Abe = BCe ^((~BCi)& BCo ); -+ Abi = BCi ^((~BCo)& BCu ); -+ Abo = BCo ^((~BCu)& BCa ); -+ Abu = BCu ^((~BCa)& BCe ); -+ -+ Ebo ^= Do; -+ BCa = ROL(Ebo, 28); -+ Egu ^= Du; -+ BCe = ROL(Egu, 20); -+ Eka ^= Da; -+ BCi = ROL(Eka, 3); -+ Eme ^= De; -+ BCo = ROL(Eme, 45); -+ Esi ^= Di; -+ BCu = ROL(Esi, 61); -+ Aga = BCa ^((~BCe)& BCi ); -+ Age = BCe ^((~BCi)& BCo ); -+ Agi = BCi ^((~BCo)& BCu ); -+ Ago = BCo ^((~BCu)& BCa ); -+ Agu = BCu ^((~BCa)& BCe ); -+ -+ Ebe ^= De; -+ BCa = ROL(Ebe, 1); -+ Egi ^= Di; -+ BCe = ROL(Egi, 6); -+ Eko ^= Do; -+ BCi = ROL(Eko, 25); -+ Emu ^= Du; -+ BCo = ROL(Emu, 8); -+ Esa ^= Da; -+ BCu = ROL(Esa, 18); -+ Aka = BCa ^((~BCe)& BCi ); -+ Ake = BCe ^((~BCi)& BCo ); -+ Aki = BCi ^((~BCo)& BCu ); -+ Ako = BCo ^((~BCu)& BCa ); -+ Aku = BCu ^((~BCa)& BCe ); -+ -+ Ebu ^= Du; -+ BCa = ROL(Ebu, 27); -+ Ega ^= Da; -+ BCe = ROL(Ega, 36); -+ Eke ^= De; -+ BCi = ROL(Eke, 10); -+ Emi ^= Di; -+ BCo = ROL(Emi, 15); -+ Eso ^= Do; -+ BCu = ROL(Eso, 56); -+ Ama = BCa ^((~BCe)& BCi ); -+ Ame = BCe ^((~BCi)& BCo ); -+ Ami = BCi ^((~BCo)& BCu ); -+ Amo = BCo ^((~BCu)& BCa ); -+ Amu = BCu ^((~BCa)& BCe ); -+ -+ Ebi ^= Di; -+ BCa = ROL(Ebi, 62); -+ Ego ^= Do; -+ BCe = ROL(Ego, 55); -+ Eku ^= Du; -+ BCi = ROL(Eku, 39); -+ Ema ^= Da; -+ BCo = ROL(Ema, 41); -+ Ese ^= De; -+ BCu = ROL(Ese, 2); -+ Asa = BCa ^((~BCe)& BCi ); -+ Ase = BCe ^((~BCi)& BCo ); -+ Asi = BCi ^((~BCo)& BCu ); -+ Aso = BCo ^((~BCu)& BCa ); -+ Asu = BCu ^((~BCa)& BCe ); -+ } -+ -+ //copyToState(state, A) -+ state[ 0] = Aba; -+ state[ 1] = Abe; -+ state[ 2] = Abi; -+ state[ 3] = Abo; -+ state[ 4] = Abu; -+ state[ 5] = Aga; -+ state[ 6] = Age; -+ state[ 7] = Agi; -+ state[ 8] = Ago; -+ state[ 9] = Agu; -+ state[10] = Aka; -+ state[11] = Ake; -+ state[12] = Aki; -+ state[13] = Ako; -+ state[14] = Aku; -+ state[15] = Ama; -+ state[16] = Ame; -+ state[17] = Ami; -+ state[18] = Amo; -+ state[19] = Amu; -+ state[20] = Asa; -+ state[21] = Ase; -+ state[22] = Asi; -+ state[23] = Aso; -+ state[24] = Asu; - } - --struct private_key { -- struct public_key pub; -- vector s; -- uint8_t fo_failure_secret[32]; --}; - --static struct private_key *private_key_from_external( -- const struct KYBER_private_key *external) { -- static_assert(sizeof(struct KYBER_private_key) >= sizeof(struct private_key), -- "Kyber private key too small"); -- static_assert( -- alignof(struct KYBER_private_key) >= alignof(struct private_key), -- "Kyber private key align incorrect"); -- return (struct private_key *)external; --} -- --// Calls |KYBER_generate_key_external_entropy| with random bytes from --// |RAND_bytes|. --void KYBER_generate_key(uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], -- struct KYBER_private_key *out_private_key) { -- uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]; -- RAND_bytes(entropy, sizeof(entropy)); -- KYBER_generate_key_external_entropy(out_encoded_public_key, out_private_key, -- entropy); --} -- --static int kyber_marshal_public_key(CBB *out, const struct public_key *pub) { -- uint8_t *vector_output; -- if (!CBB_add_space(out, &vector_output, kEncodedVectorSize)) { -- return 0; -+/************************************************* -+* Name: keccak_squeeze -+* -+* Description: Squeeze step of Keccak. Squeezes arbitratrily many bytes. -+* Modifies the state. Can be called multiple times to keep -+* squeezing, i.e., is incremental. -+* -+* Arguments: - uint8_t *out: pointer to output -+* - size_t outlen: number of bytes to be squeezed (written to out) -+* - uint64_t *s: pointer to input/output Keccak state -+* - unsigned int pos: number of bytes in current block already squeezed -+* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) -+* -+* Returns new position pos in current block -+**************************************************/ -+static unsigned int keccak_squeeze(uint8_t *out, -+ size_t outlen, -+ uint64_t s[25], -+ unsigned int pos, -+ unsigned int r) -+{ -+ unsigned int i; -+ -+ while(outlen) { -+ if(pos == r) { -+ KeccakF1600_StatePermute(s); -+ pos = 0; -+ } -+ for(i=pos;i < r && i < pos+outlen; i++) -+ *out++ = s[i/8] >> 8*(i%8); -+ outlen -= i-pos; -+ pos = i; - } -- vector_encode(vector_output, &pub->t, kLog2Prime); -- if (!CBB_add_bytes(out, pub->rho, sizeof(pub->rho))) { -- return 0; -+ -+ return pos; -+} -+ -+/************************************************* -+* Name: keccak_absorb -+* -+* Description: Absorb step of Keccak; incremental. -+* -+* Arguments: - uint64_t *s: pointer to Keccak state -+* - unsigned int pos: position in current block to be absorbed -+* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+* -+* Returns new position pos in current block -+**************************************************/ -+static unsigned int keccak_absorb(uint64_t s[25], -+ unsigned int pos, -+ unsigned int r, -+ const uint8_t *in, -+ size_t inlen) -+{ -+ unsigned int i; -+ -+ while(pos+inlen >= r) { -+ for(i=pos;ipub.rho, hashed, sizeof(priv->pub.rho)); -- matrix_expand(&priv->pub.m, rho); -- uint8_t counter = 0; -- vector_generate_secret_eta_2(&priv->s, &counter, sigma); -- vector_ntt(&priv->s); -- vector error; -- vector_generate_secret_eta_2(&error, &counter, sigma); -- vector_ntt(&error); -- matrix_mult_transpose(&priv->pub.t, &priv->pub.m, &priv->s); -- vector_add(&priv->pub.t, &error); -- -- CBB cbb; -- CBB_init_fixed(&cbb, out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES); -- if (!kyber_marshal_public_key(&cbb, &priv->pub)) { -- abort(); -+ -+/************************************************* -+* Name: keccak_absorb_once -+* -+* Description: Absorb step of Keccak; -+* non-incremental, starts by zeroeing the state. -+* -+* Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak state -+* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+* - uint8_t p: domain-separation byte for different Keccak-derived functions -+**************************************************/ -+static void keccak_absorb_once(uint64_t s[25], -+ unsigned int r, -+ const uint8_t *in, -+ size_t inlen, -+ uint8_t p) -+{ -+ unsigned int i; -+ -+ for(i=0;i<25;i++) -+ s[i] = 0; -+ -+ while(inlen >= r) { -+ for(i=0;ipub.public_key_hash, sizeof(priv->pub.public_key_hash), -- out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES, -- boringssl_sha3_256); -- OPENSSL_memcpy(priv->fo_failure_secret, entropy + 32, 32); --} -- --void KYBER_public_from_private(struct KYBER_public_key *out_public_key, -- const struct KYBER_private_key *private_key) { -- struct public_key *const pub = public_key_from_external(out_public_key); -- const struct private_key *const priv = private_key_from_external(private_key); -- *pub = priv->pub; --} -- --// Algorithm 5 of the Kyber spec. Encrypts a message with given randomness to --// the ciphertext in |out|. Without applying the Fujisaki-Okamoto transform this --// would not result in a CCA secure scheme, since lattice schemes are vulnerable --// to decryption failure oracles. --static void encrypt_cpa(uint8_t out[KYBER_CIPHERTEXT_BYTES], -- const struct public_key *pub, const uint8_t message[32], -- const uint8_t randomness[32]) { -- uint8_t counter = 0; -- vector secret; -- vector_generate_secret_eta_2(&secret, &counter, randomness); -- vector_ntt(&secret); -- vector error; -- vector_generate_secret_eta_2(&error, &counter, randomness); -- uint8_t input[33]; -- OPENSSL_memcpy(input, randomness, 32); -- input[32] = counter; -- scalar scalar_error; -- scalar_centered_binomial_distribution_eta_2_with_prf(&scalar_error, input); -- vector u; -- matrix_mult(&u, &pub->m, &secret); -- vector_inverse_ntt(&u); -- vector_add(&u, &error); -- scalar v; -- scalar_inner_product(&v, &pub->t, &secret); -- scalar_inverse_ntt(&v); -- scalar_add(&v, &scalar_error); -- scalar expanded_message; -- scalar_decode_1(&expanded_message, message); -- scalar_decompress(&expanded_message, 1); -- scalar_add(&v, &expanded_message); -- vector_compress(&u, kDU); -- vector_encode(out, &u, kDU); -- scalar_compress(&v, kDV); -- scalar_encode(out + kCompressedVectorSize, &v, kDV); --} -- --// Calls KYBER_encap_external_entropy| with random bytes from |RAND_bytes| --void KYBER_encap(uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], -- uint8_t *out_shared_secret, size_t out_shared_secret_len, -- const struct KYBER_public_key *public_key) { -- uint8_t entropy[KYBER_ENCAP_ENTROPY]; -- RAND_bytes(entropy, KYBER_ENCAP_ENTROPY); -- KYBER_encap_external_entropy(out_ciphertext, out_shared_secret, -- out_shared_secret_len, public_key, entropy); --} -- --// Algorithm 8 of the Kyber spec, safe for line 2 of the spec. The spec there --// hashes the output of the system's random number generator, since the FO --// transform will reveal it to the decrypting party. There is no reason to do --// this when a secure random number generator is used. When an insecure random --// number generator is used, the caller should switch to a secure one before --// calling this method. --void KYBER_encap_external_entropy( -- uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], uint8_t *out_shared_secret, -- size_t out_shared_secret_len, const struct KYBER_public_key *public_key, -- const uint8_t entropy[KYBER_ENCAP_ENTROPY]) { -- const struct public_key *pub = public_key_from_external(public_key); -- uint8_t input[64]; -- OPENSSL_memcpy(input, entropy, KYBER_ENCAP_ENTROPY); -- OPENSSL_memcpy(input + KYBER_ENCAP_ENTROPY, pub->public_key_hash, -- sizeof(input) - KYBER_ENCAP_ENTROPY); -- uint8_t prekey_and_randomness[64]; -- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), input, -- sizeof(input), boringssl_sha3_512); -- encrypt_cpa(out_ciphertext, pub, entropy, prekey_and_randomness + 32); -- BORINGSSL_keccak(prekey_and_randomness + 32, 32, out_ciphertext, -- KYBER_CIPHERTEXT_BYTES, boringssl_sha3_256); -- BORINGSSL_keccak(out_shared_secret, out_shared_secret_len, -- prekey_and_randomness, sizeof(prekey_and_randomness), -- boringssl_shake256); --} -- --// Algorithm 6 of the Kyber spec. --static void decrypt_cpa(uint8_t out[32], const struct private_key *priv, -- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]) { -- vector u; -- vector_decode(&u, ciphertext, kDU); -- vector_decompress(&u, kDU); -- vector_ntt(&u); -- scalar v; -- scalar_decode(&v, ciphertext + kCompressedVectorSize, kDV); -- scalar_decompress(&v, kDV); -- scalar mask; -- scalar_inner_product(&mask, &priv->s, &u); -- scalar_inverse_ntt(&mask); -- scalar_sub(&v, &mask); -- scalar_compress(&v, 1); -- scalar_encode_1(out, &v); --} -- --// Algorithm 9 of the Kyber spec, performing the FO transform by running --// encrypt_cpa on the decrypted message. The spec does not allow the decryption --// failure to be passed on to the caller, and instead returns a result that is --// deterministic but unpredictable to anyone without knowledge of the private --// key. --void KYBER_decap(uint8_t *out_shared_secret, size_t out_shared_secret_len, -- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES], -- const struct KYBER_private_key *private_key) { -- const struct private_key *priv = private_key_from_external(private_key); -- uint8_t decrypted[64]; -- decrypt_cpa(decrypted, priv, ciphertext); -- OPENSSL_memcpy(decrypted + 32, priv->pub.public_key_hash, -- sizeof(decrypted) - 32); -- uint8_t prekey_and_randomness[64]; -- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), -- decrypted, sizeof(decrypted), boringssl_sha3_512); -- uint8_t expected_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- encrypt_cpa(expected_ciphertext, &priv->pub, decrypted, -- prekey_and_randomness + 32); -- uint8_t mask = -- constant_time_eq_int_8(CRYPTO_memcmp(ciphertext, expected_ciphertext, -- sizeof(expected_ciphertext)), -- 0); -- uint8_t input[64]; -- for (int i = 0; i < 32; i++) { -- input[i] = constant_time_select_8(mask, prekey_and_randomness[i], -- priv->fo_failure_secret[i]); -+ for(i=0;is, SHAKE128_RATE, in, inlen, 0x1F); -+ state->pos = SHAKE128_RATE; - } - --// kyber_parse_public_key_no_hash parses |in| into |pub| but doesn't calculate --// the value of |pub->public_key_hash|. --static int kyber_parse_public_key_no_hash(struct public_key *pub, CBS *in) { -- CBS t_bytes; -- if (!CBS_get_bytes(in, &t_bytes, kEncodedVectorSize) || -- !vector_decode(&pub->t, CBS_data(&t_bytes), kLog2Prime) || -- !CBS_copy_bytes(in, pub->rho, sizeof(pub->rho))) { -- return 0; -- } -- matrix_expand(&pub->m, pub->rho); -- return 1; -+/************************************************* -+* Name: shake128_squeezeblocks -+* -+* Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of -+* SHAKE128_RATE bytes each. Can be called multiple times -+* to keep squeezing. Assumes new block has not yet been -+* started (state->pos = SHAKE128_RATE). -+* -+* Arguments: - uint8_t *out: pointer to output blocks -+* - size_t nblocks: number of blocks to be squeezed (written to output) -+* - keccak_state *s: pointer to input/output Keccak state -+**************************************************/ -+static void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) -+{ -+ keccak_squeezeblocks(out, nblocks, state->s, SHAKE128_RATE); -+} -+ -+/************************************************* -+* Name: shake256_squeeze -+* -+* Description: Squeeze step of SHAKE256 XOF. Squeezes arbitraily many -+* bytes. Can be called multiple times to keep squeezing. -+* -+* Arguments: - uint8_t *out: pointer to output blocks -+* - size_t outlen : number of bytes to be squeezed (written to output) -+* - keccak_state *s: pointer to input/output Keccak state -+**************************************************/ -+static void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state) -+{ -+ state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE); -+} -+ -+/************************************************* -+* Name: shake256_absorb_once -+* -+* Description: Initialize, absorb into and finalize SHAKE256 XOF; non-incremental. -+* -+* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen) -+{ -+ keccak_absorb_once(state->s, SHAKE256_RATE, in, inlen, 0x1F); -+ state->pos = SHAKE256_RATE; -+} -+ -+/************************************************* -+* Name: shake256_squeezeblocks -+* -+* Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of -+* SHAKE256_RATE bytes each. Can be called multiple times -+* to keep squeezing. Assumes next block has not yet been -+* started (state->pos = SHAKE256_RATE). -+* -+* Arguments: - uint8_t *out: pointer to output blocks -+* - size_t nblocks: number of blocks to be squeezed (written to output) -+* - keccak_state *s: pointer to input/output Keccak state -+**************************************************/ -+static void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) -+{ -+ keccak_squeezeblocks(out, nblocks, state->s, SHAKE256_RATE); -+} -+ -+/************************************************* -+* Name: shake256_absorb -+* -+* Description: Absorb step of the SHAKE256 XOF; incremental. -+* -+* Arguments: - keccak_state *state: pointer to (initialized) output Keccak state -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen) -+{ -+ state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen); -+} -+ -+/************************************************* -+* Name: shake256_finalize -+* -+* Description: Finalize absorb step of the SHAKE256 XOF. -+* -+* Arguments: - keccak_state *state: pointer to Keccak state -+**************************************************/ -+static void shake256_finalize(keccak_state *state) -+{ -+ keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F); -+ state->pos = SHAKE256_RATE; -+} -+ -+/************************************************* -+* Name: keccak_init -+* -+* Description: Initializes the Keccak state. -+* -+* Arguments: - uint64_t *s: pointer to Keccak state -+**************************************************/ -+static void keccak_init(uint64_t s[25]) -+{ -+ unsigned int i; -+ for(i=0;i<25;i++) -+ s[i] = 0; -+} -+ -+/************************************************* -+* Name: shake256_init -+* -+* Description: Initilizes Keccak state for use as SHAKE256 XOF -+* -+* Arguments: - keccak_state *state: pointer to (uninitialized) Keccak state -+**************************************************/ -+static void shake256_init(keccak_state *state) -+{ -+ keccak_init(state->s); -+ state->pos = 0; -+} -+ -+ -+/************************************************* -+* Name: shake256 -+* -+* Description: SHAKE256 XOF with non-incremental API -+* -+* Arguments: - uint8_t *out: pointer to output -+* - size_t outlen: requested output length in bytes -+* - const uint8_t *in: pointer to input -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) -+{ -+ size_t nblocks; -+ keccak_state state; -+ -+ shake256_absorb_once(&state, in, inlen); -+ nblocks = outlen/SHAKE256_RATE; -+ shake256_squeezeblocks(out, nblocks, &state); -+ outlen -= nblocks*SHAKE256_RATE; -+ out += nblocks*SHAKE256_RATE; -+ shake256_squeeze(out, outlen, &state); -+} -+ -+/************************************************* -+* Name: sha3_256 -+* -+* Description: SHA3-256 with non-incremental API -+* -+* Arguments: - uint8_t *h: pointer to output (32 bytes) -+* - const uint8_t *in: pointer to input -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen) -+{ -+ unsigned int i; -+ uint64_t s[25]; -+ -+ keccak_absorb_once(s, SHA3_256_RATE, in, inlen, 0x06); -+ KeccakF1600_StatePermute(s); -+ for(i=0;i<4;i++) -+ store64(h+8*i,s[i]); -+} -+ -+/************************************************* -+* Name: sha3_512 -+* -+* Description: SHA3-512 with non-incremental API -+* -+* Arguments: - uint8_t *h: pointer to output (64 bytes) -+* - const uint8_t *in: pointer to input -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen) -+{ -+ unsigned int i; -+ uint64_t s[25]; -+ -+ keccak_absorb_once(s, SHA3_512_RATE, in, inlen, 0x06); -+ KeccakF1600_StatePermute(s); -+ for(i=0;i<8;i++) -+ store64(h+8*i,s[i]); -+} -+ -+// -+// symmetric-shake.c -+// -+ -+/************************************************* -+* Name: kyber_shake128_absorb -+* -+* Description: Absorb step of the SHAKE128 specialized for the Kyber context. -+* -+* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state -+* - const uint8_t *seed: pointer to KYBER_SYMBYTES input to be absorbed into state -+* - uint8_t i: additional byte of input -+* - uint8_t j: additional byte of input -+**************************************************/ -+static void kyber_shake128_absorb(keccak_state *state, -+ const uint8_t seed[KYBER_SYMBYTES], -+ uint8_t x, -+ uint8_t y) -+{ -+ uint8_t extseed[KYBER_SYMBYTES+2]; -+ -+ memcpy(extseed, seed, KYBER_SYMBYTES); -+ extseed[KYBER_SYMBYTES+0] = x; -+ extseed[KYBER_SYMBYTES+1] = y; -+ -+ shake128_absorb_once(state, extseed, sizeof(extseed)); - } - --int KYBER_parse_public_key(struct KYBER_public_key *public_key, CBS *in) { -- struct public_key *pub = public_key_from_external(public_key); -- CBS orig_in = *in; -- if (!kyber_parse_public_key_no_hash(pub, in) || // -- CBS_len(in) != 0) { -+/************************************************* -+* Name: kyber_shake256_prf -+* -+* Description: Usage of SHAKE256 as a PRF, concatenates secret and public input -+* and then generates outlen bytes of SHAKE256 output -+* -+* Arguments: - uint8_t *out: pointer to output -+* - size_t outlen: number of requested output bytes -+* - const uint8_t *key: pointer to the key (of length KYBER_SYMBYTES) -+* - uint8_t nonce: single-byte nonce (public PRF input) -+**************************************************/ -+static void kyber_shake256_prf(uint8_t *out, size_t outlen, const uint8_t key[KYBER_SYMBYTES], uint8_t nonce) -+{ -+ uint8_t extkey[KYBER_SYMBYTES+1]; -+ -+ memcpy(extkey, key, KYBER_SYMBYTES); -+ extkey[KYBER_SYMBYTES] = nonce; -+ -+ shake256(out, outlen, extkey, sizeof(extkey)); -+} -+ -+// -+// kem.c -+// -+ -+// Modified crypto_kem_keypair to BoringSSL style API -+void generate_key(struct public_key *out_pub, struct private_key *out_priv, -+ const uint8_t seed[KYBER_GENERATE_KEY_BYTES]) -+{ -+ size_t i; -+ uint8_t* pk = &out_pub->opaque[0]; -+ uint8_t* sk = &out_priv->opaque[0]; -+ -+ indcpa_keypair(pk, sk, seed); -+ for(i=0;iopaque[0]; -+ uint8_t *ct = out_ciphertext; -+ -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ /* Will contain key, coins */ -+ uint8_t kr[2*KYBER_SYMBYTES]; -+ -+ memcpy(buf, seed, KYBER_SYMBYTES); -+ -+ /* Don't release system RNG output */ -+ hash_h(buf, buf, KYBER_SYMBYTES); -+ -+ /* Multitarget countermeasure for coins + contributory KEM */ -+ hash_h(buf+KYBER_SYMBYTES, pk, KYBER_PUBLICKEYBYTES); -+ hash_g(kr, buf, 2*KYBER_SYMBYTES); -+ -+ /* coins are in kr+KYBER_SYMBYTES */ -+ if(!indcpa_enc(ct, buf, pk, kr+KYBER_SYMBYTES)) - return 0; -+ -+ if (mlkem == 1) { -+ memcpy(ss, kr, KYBER_SYMBYTES); -+ } else { -+ /* overwrite coins in kr with H(c) */ -+ hash_h(kr+KYBER_SYMBYTES, ct, KYBER_CIPHERTEXTBYTES); -+ /* hash concatenation of pre-k and H(c) to k */ -+ kdf(ss, kr, 2*KYBER_SYMBYTES); - } -- BORINGSSL_keccak(pub->public_key_hash, sizeof(pub->public_key_hash), -- CBS_data(&orig_in), CBS_len(&orig_in), boringssl_sha3_256); - return 1; - } - --int KYBER_marshal_private_key(CBB *out, -- const struct KYBER_private_key *private_key) { -- const struct private_key *const priv = private_key_from_external(private_key); -- uint8_t *s_output; -- if (!CBB_add_space(out, &s_output, kEncodedVectorSize)) { -- return 0; -+// Modified crypto_kem_decap to BoringSSL style API -+void decap(uint8_t out_shared_key[KYBER_SSBYTES], -+ const struct private_key *in_priv, -+ const uint8_t *ct, size_t ciphertext_len, int mlkem) -+{ -+ uint8_t *ss = out_shared_key; -+ const uint8_t *sk = &in_priv->opaque[0]; -+ -+ size_t i; -+ int fail = 1; -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ /* Will contain key, coins */ -+ uint8_t kr[2*KYBER_SYMBYTES]; -+ uint8_t cmp[KYBER_CIPHERTEXTBYTES]; -+ const uint8_t *pk = sk+KYBER_INDCPA_SECRETKEYBYTES; -+ -+ if (ciphertext_len == KYBER_CIPHERTEXTBYTES) { -+ indcpa_dec(buf, ct, sk); -+ -+ /* Multitarget countermeasure for coins + contributory KEM */ -+ for(i=0;is, kLog2Prime); -- if (!kyber_marshal_public_key(out, &priv->pub) || -- !CBB_add_bytes(out, priv->pub.public_key_hash, -- sizeof(priv->pub.public_key_hash)) || -- !CBB_add_bytes(out, priv->fo_failure_secret, -- sizeof(priv->fo_failure_secret))) { -- return 0; -+ -+ if (mlkem == 1) { -+ /* Compute shared secret in case of rejection: ss2 = PRF(z || c). */ -+ uint8_t ss2[KYBER_SYMBYTES]; -+ keccak_state ks; -+ shake256_init(&ks); -+ shake256_absorb( -+ &ks, -+ sk + KYBER_SECRETKEYBYTES - KYBER_SYMBYTES, -+ KYBER_SYMBYTES -+ ); -+ shake256_absorb(&ks, ct, ciphertext_len); -+ shake256_finalize(&ks); -+ shake256_squeeze(ss2, KYBER_SYMBYTES, &ks); -+ -+ /* Set ss2 to the real shared secret if c = c' */ -+ cmov(ss2, kr, KYBER_SYMBYTES, 1-fail); -+ memcpy(ss, ss2, KYBER_SYMBYTES); -+ } else { -+ /* overwrite coins in kr with H(c) */ -+ hash_h(kr+KYBER_SYMBYTES, ct, ciphertext_len); -+ -+ /* Overwrite pre-k with z on re-encryption failure */ -+ cmov(kr, sk+KYBER_SECRETKEYBYTES-KYBER_SYMBYTES, KYBER_SYMBYTES, fail); -+ -+ /* hash concatenation of pre-k and H(c) to k */ -+ kdf(ss, kr, 2*KYBER_SYMBYTES); - } -- return 1; - } - --int KYBER_parse_private_key(struct KYBER_private_key *out_private_key, -- CBS *in) { -- struct private_key *const priv = private_key_from_external(out_private_key); -+void marshal_public_key(uint8_t out[KYBER_PUBLICKEYBYTES], -+ const struct public_key *in_pub) { -+ memcpy(out, &in_pub->opaque, KYBER_PUBLICKEYBYTES); -+} - -- CBS s_bytes; -- if (!CBS_get_bytes(in, &s_bytes, kEncodedVectorSize) || -- !vector_decode(&priv->s, CBS_data(&s_bytes), kLog2Prime) || -- !kyber_parse_public_key_no_hash(&priv->pub, in) || -- !CBS_copy_bytes(in, priv->pub.public_key_hash, -- sizeof(priv->pub.public_key_hash)) || -- !CBS_copy_bytes(in, priv->fo_failure_secret, -- sizeof(priv->fo_failure_secret)) || -- CBS_len(in) != 0) { -- return 0; -- } -- return 1; -+void parse_public_key(struct public_key *out, -+ const uint8_t in[KYBER_PUBLICKEYBYTES]) { -+ memcpy(&out->opaque, in, KYBER_PUBLICKEYBYTES); - } -diff --git a/src/crypto/kyber/kyber512.c b/src/crypto/kyber/kyber512.c -new file mode 100644 -index 000000000..21eed11a2 ---- /dev/null -+++ b/src/crypto/kyber/kyber512.c -@@ -0,0 +1,5 @@ -+#define KYBER_K 2 -+ -+#include "kyber.c" -+ -+ -diff --git a/src/crypto/kyber/kyber768.c b/src/crypto/kyber/kyber768.c -new file mode 100644 -index 000000000..3e572b72e ---- /dev/null -+++ b/src/crypto/kyber/kyber768.c -@@ -0,0 +1,4 @@ -+#define KYBER_K 3 -+ -+#include "kyber.c" -+ -diff --git a/src/crypto/kyber/kyber_test.cc b/src/crypto/kyber/kyber_test.cc -deleted file mode 100644 -index eb76b5bd7..000000000 ---- a/src/crypto/kyber/kyber_test.cc -+++ /dev/null -@@ -1,229 +0,0 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -- --#include -- --#include -- --#include -- --#include --#include --#include -- --#include "../test/file_test.h" --#include "../test/test_util.h" --#include "./internal.h" -- -- --static void KeccakFileTest(FileTest *t) { -- std::vector input, sha3_256_expected, sha3_512_expected, -- shake128_expected, shake256_expected; -- ASSERT_TRUE(t->GetBytes(&input, "Input")); -- ASSERT_TRUE(t->GetBytes(&sha3_256_expected, "SHA3-256")); -- ASSERT_TRUE(t->GetBytes(&sha3_512_expected, "SHA3-512")); -- ASSERT_TRUE(t->GetBytes(&shake128_expected, "SHAKE-128")); -- ASSERT_TRUE(t->GetBytes(&shake256_expected, "SHAKE-256")); -- -- uint8_t sha3_256_digest[32]; -- BORINGSSL_keccak(sha3_256_digest, sizeof(sha3_256_digest), input.data(), -- input.size(), boringssl_sha3_256); -- uint8_t sha3_512_digest[64]; -- BORINGSSL_keccak(sha3_512_digest, sizeof(sha3_512_digest), input.data(), -- input.size(), boringssl_sha3_512); -- uint8_t shake128_output[512]; -- BORINGSSL_keccak(shake128_output, sizeof(shake128_output), input.data(), -- input.size(), boringssl_shake128); -- uint8_t shake256_output[512]; -- BORINGSSL_keccak(shake256_output, sizeof(shake256_output), input.data(), -- input.size(), boringssl_shake256); -- -- EXPECT_EQ(Bytes(sha3_256_expected), Bytes(sha3_256_digest)); -- EXPECT_EQ(Bytes(sha3_512_expected), Bytes(sha3_512_digest)); -- EXPECT_EQ(Bytes(shake128_expected), Bytes(shake128_output)); -- EXPECT_EQ(Bytes(shake256_expected), Bytes(shake256_output)); -- -- struct BORINGSSL_keccak_st ctx; -- -- BORINGSSL_keccak_init(&ctx, input.data(), input.size(), boringssl_shake128); -- for (size_t i = 0; i < sizeof(shake128_output); i++) { -- BORINGSSL_keccak_squeeze(&ctx, &shake128_output[i], 1); -- } -- EXPECT_EQ(Bytes(shake128_expected), Bytes(shake128_output)); -- -- BORINGSSL_keccak_init(&ctx, input.data(), input.size(), boringssl_shake256); -- for (size_t i = 0; i < sizeof(shake256_output); i++) { -- BORINGSSL_keccak_squeeze(&ctx, &shake256_output[i], 1); -- } -- EXPECT_EQ(Bytes(shake256_expected), Bytes(shake256_output)); --} -- --TEST(KyberTest, KeccakTestVectors) { -- FileTestGTest("crypto/kyber/keccak_tests.txt", KeccakFileTest); --} -- --template --static std::vector Marshal(int (*marshal_func)(CBB *, const T *), -- const T *t) { -- bssl::ScopedCBB cbb; -- uint8_t *encoded; -- size_t encoded_len; -- if (!CBB_init(cbb.get(), 1) || // -- !marshal_func(cbb.get(), t) || // -- !CBB_finish(cbb.get(), &encoded, &encoded_len)) { -- abort(); -- } -- -- std::vector ret(encoded, encoded + encoded_len); -- OPENSSL_free(encoded); -- return ret; --} -- --TEST(KyberTest, Basic) { -- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; -- KYBER_private_key priv; -- KYBER_generate_key(encoded_public_key, &priv); -- -- uint8_t first_two_bytes[2]; -- OPENSSL_memcpy(first_two_bytes, encoded_public_key, sizeof(first_two_bytes)); -- OPENSSL_memset(encoded_public_key, 0xff, sizeof(first_two_bytes)); -- CBS encoded_public_key_cbs; -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- KYBER_public_key pub; -- // Parsing should fail because the first coefficient is >= kPrime; -- ASSERT_FALSE(KYBER_parse_public_key(&pub, &encoded_public_key_cbs)); -- -- OPENSSL_memcpy(encoded_public_key, first_two_bytes, sizeof(first_two_bytes)); -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- ASSERT_TRUE(KYBER_parse_public_key(&pub, &encoded_public_key_cbs)); -- EXPECT_EQ(CBS_len(&encoded_public_key_cbs), 0u); -- -- EXPECT_EQ(Bytes(encoded_public_key), -- Bytes(Marshal(KYBER_marshal_public_key, &pub))); -- -- KYBER_public_key pub2; -- KYBER_public_from_private(&pub2, &priv); -- EXPECT_EQ(Bytes(encoded_public_key), -- Bytes(Marshal(KYBER_marshal_public_key, &pub2))); -- -- std::vector encoded_private_key( -- Marshal(KYBER_marshal_private_key, &priv)); -- EXPECT_EQ(encoded_private_key.size(), size_t{KYBER_PRIVATE_KEY_BYTES}); -- -- OPENSSL_memcpy(first_two_bytes, encoded_private_key.data(), -- sizeof(first_two_bytes)); -- OPENSSL_memset(encoded_private_key.data(), 0xff, sizeof(first_two_bytes)); -- CBS cbs; -- CBS_init(&cbs, encoded_private_key.data(), encoded_private_key.size()); -- KYBER_private_key priv2; -- // Parsing should fail because the first coefficient is >= kPrime. -- ASSERT_FALSE(KYBER_parse_private_key(&priv2, &cbs)); -- -- OPENSSL_memcpy(encoded_private_key.data(), first_two_bytes, -- sizeof(first_two_bytes)); -- CBS_init(&cbs, encoded_private_key.data(), encoded_private_key.size()); -- ASSERT_TRUE(KYBER_parse_private_key(&priv2, &cbs)); -- EXPECT_EQ(Bytes(encoded_private_key), -- Bytes(Marshal(KYBER_marshal_private_key, &priv2))); -- -- uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]; -- uint8_t shared_secret1[64]; -- uint8_t shared_secret2[sizeof(shared_secret1)]; -- KYBER_encap(ciphertext, shared_secret1, sizeof(shared_secret1), &pub); -- KYBER_decap(shared_secret2, sizeof(shared_secret2), ciphertext, &priv); -- EXPECT_EQ(Bytes(shared_secret1), Bytes(shared_secret2)); -- KYBER_decap(shared_secret2, sizeof(shared_secret2), ciphertext, &priv2); -- EXPECT_EQ(Bytes(shared_secret1), Bytes(shared_secret2)); --} -- --static void KyberFileTest(FileTest *t) { -- std::vector seed, public_key_expected, private_key_expected, -- ciphertext_expected, shared_secret_expected, given_generate_entropy, -- given_encap_entropy_pre_hash; -- t->IgnoreAttribute("count"); -- ASSERT_TRUE(t->GetBytes(&seed, "seed")); -- ASSERT_TRUE(t->GetBytes(&public_key_expected, "pk")); -- ASSERT_TRUE(t->GetBytes(&private_key_expected, "sk")); -- ASSERT_TRUE(t->GetBytes(&ciphertext_expected, "ct")); -- ASSERT_TRUE(t->GetBytes(&shared_secret_expected, "ss")); -- ASSERT_TRUE(t->GetBytes(&given_generate_entropy, "generateEntropy")); -- ASSERT_TRUE( -- t->GetBytes(&given_encap_entropy_pre_hash, "encapEntropyPreHash")); -- -- KYBER_private_key priv; -- uint8_t encoded_private_key[KYBER_PRIVATE_KEY_BYTES]; -- KYBER_public_key pub; -- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; -- uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]; -- uint8_t gen_key_entropy[KYBER_GENERATE_KEY_ENTROPY]; -- uint8_t encap_entropy[KYBER_ENCAP_ENTROPY]; -- uint8_t encapsulated_key[32]; -- uint8_t decapsulated_key[32]; -- // The test vectors provide a CTR-DRBG seed which is used to generate the -- // input entropy. -- ASSERT_EQ(seed.size(), size_t{CTR_DRBG_ENTROPY_LEN}); -- { -- bssl::UniquePtr state( -- CTR_DRBG_new(seed.data(), nullptr, 0)); -- ASSERT_TRUE(state); -- ASSERT_TRUE( -- CTR_DRBG_generate(state.get(), gen_key_entropy, 32, nullptr, 0)); -- ASSERT_TRUE( -- CTR_DRBG_generate(state.get(), gen_key_entropy + 32, 32, nullptr, 0)); -- ASSERT_TRUE(CTR_DRBG_generate(state.get(), encap_entropy, -- KYBER_ENCAP_ENTROPY, nullptr, 0)); -- } -- -- EXPECT_EQ(Bytes(gen_key_entropy), Bytes(given_generate_entropy)); -- EXPECT_EQ(Bytes(encap_entropy), Bytes(given_encap_entropy_pre_hash)); -- -- BORINGSSL_keccak(encap_entropy, sizeof(encap_entropy), encap_entropy, -- sizeof(encap_entropy), boringssl_sha3_256); -- -- KYBER_generate_key_external_entropy(encoded_public_key, &priv, -- gen_key_entropy); -- CBB cbb; -- CBB_init_fixed(&cbb, encoded_private_key, sizeof(encoded_private_key)); -- ASSERT_TRUE(KYBER_marshal_private_key(&cbb, &priv)); -- CBS encoded_public_key_cbs; -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- ASSERT_TRUE(KYBER_parse_public_key(&pub, &encoded_public_key_cbs)); -- KYBER_encap_external_entropy(ciphertext, encapsulated_key, -- sizeof(encapsulated_key), &pub, encap_entropy); -- KYBER_decap(decapsulated_key, sizeof(decapsulated_key), ciphertext, &priv); -- -- EXPECT_EQ(Bytes(encapsulated_key), Bytes(decapsulated_key)); -- EXPECT_EQ(Bytes(private_key_expected), Bytes(encoded_private_key)); -- EXPECT_EQ(Bytes(public_key_expected), Bytes(encoded_public_key)); -- EXPECT_EQ(Bytes(ciphertext_expected), Bytes(ciphertext)); -- EXPECT_EQ(Bytes(shared_secret_expected), Bytes(encapsulated_key)); -- -- uint8_t corrupted_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- OPENSSL_memcpy(corrupted_ciphertext, ciphertext, KYBER_CIPHERTEXT_BYTES); -- corrupted_ciphertext[3] ^= 0x40; -- uint8_t corrupted_decapsulated_key[32]; -- KYBER_decap(corrupted_decapsulated_key, sizeof(corrupted_decapsulated_key), -- corrupted_ciphertext, &priv); -- // It would be nice to have actual test vectors for the failure case, but the -- // NIST submission currently does not include those, so we are just testing -- // for inequality. -- EXPECT_NE(Bytes(encapsulated_key), Bytes(corrupted_decapsulated_key)); --} -- --TEST(KyberTest, TestVectors) { -- FileTestGTest("crypto/kyber/kyber_tests.txt", KyberFileTest); --} -diff --git a/src/crypto/obj/obj_dat.h b/src/crypto/obj/obj_dat.h -index 654b3c08e..6cef2c079 100644 ---- a/src/crypto/obj/obj_dat.h -+++ b/src/crypto/obj/obj_dat.h -@@ -57,7 +57,7 @@ - /* This file is generated by crypto/obj/objects.go. */ - - --#define NUM_NID 965 -+#define NUM_NID 969 - - static const uint8_t kObjectData[] = { - /* NID_rsadsi */ -@@ -8784,6 +8784,13 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { - {"HKDF", "hkdf", NID_hkdf, 0, NULL, 0}, - {"X25519Kyber768Draft00", "X25519Kyber768Draft00", - NID_X25519Kyber768Draft00, 0, NULL, 0}, -+ {"X25519Kyber512Draft00", "X25519Kyber512Draft00", -+ NID_X25519Kyber512Draft00, 0, NULL, 0}, -+ {"P256Kyber768Draft00", "P256Kyber768Draft00", NID_P256Kyber768Draft00, 0, -+ NULL, 0}, -+ {"X25519Kyber768Draft00Old", "X25519Kyber768Draft00Old", -+ NID_X25519Kyber768Draft00Old, 0, NULL, 0}, -+ {"X25519MLKEM768", "X25519MLKEM768", NID_X25519MLKEM768, 0, NULL, 0}, - }; - - static const uint16_t kNIDsInShortNameOrder[] = { -@@ -8916,6 +8923,7 @@ static const uint16_t kNIDsInShortNameOrder[] = { - 18 /* OU */, - 749 /* Oakley-EC2N-3 */, - 750 /* Oakley-EC2N-4 */, -+ 966 /* P256Kyber768Draft00 */, - 9 /* PBE-MD2-DES */, - 168 /* PBE-MD2-RC2-64 */, - 10 /* PBE-MD5-DES */, -@@ -8982,7 +8990,10 @@ static const uint16_t kNIDsInShortNameOrder[] = { - 458 /* UID */, - 0 /* UNDEF */, - 948 /* X25519 */, -+ 965 /* X25519Kyber512Draft00 */, - 964 /* X25519Kyber768Draft00 */, -+ 967 /* X25519Kyber768Draft00Old */, -+ 968 /* X25519MLKEM768 */, - 961 /* X448 */, - 11 /* X500 */, - 378 /* X500algorithms */, -@@ -9829,6 +9840,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { - 366 /* OCSP Nonce */, - 371 /* OCSP Service Locator */, - 180 /* OCSP Signing */, -+ 966 /* P256Kyber768Draft00 */, - 161 /* PBES2 */, - 69 /* PBKDF2 */, - 162 /* PBMAC1 */, -@@ -9853,7 +9865,10 @@ static const uint16_t kNIDsInLongNameOrder[] = { - 133 /* Time Stamping */, - 375 /* Trust Root */, - 948 /* X25519 */, -+ 965 /* X25519Kyber512Draft00 */, - 964 /* X25519Kyber768Draft00 */, -+ 967 /* X25519Kyber768Draft00Old */, -+ 968 /* X25519MLKEM768 */, - 961 /* X448 */, - 12 /* X509 */, - 402 /* X509v3 AC Targeting */, -diff --git a/src/crypto/obj/obj_mac.num b/src/crypto/obj/obj_mac.num -index a0519acee..2a46adfe8 100644 ---- a/src/crypto/obj/obj_mac.num -+++ b/src/crypto/obj/obj_mac.num -@@ -952,3 +952,7 @@ X448 961 - sha512_256 962 - hkdf 963 - X25519Kyber768Draft00 964 -+X25519Kyber512Draft00 965 -+P256Kyber768Draft00 966 -+X25519Kyber768Draft00Old 967 -+X25519MLKEM768 968 -diff --git a/src/crypto/obj/objects.txt b/src/crypto/obj/objects.txt -index 3ad32ea3d..347fc556a 100644 ---- a/src/crypto/obj/objects.txt -+++ b/src/crypto/obj/objects.txt -@@ -1332,8 +1332,12 @@ secg-scheme 14 3 : dhSinglePass-cofactorDH-sha512kdf-scheme - : dh-std-kdf - : dh-cofactor-kdf - --# NIDs for post quantum hybrid KEMs in TLS (no corresponding OIDs). -+# NID for Kyber hybrids (no corresponding OID). -+ : X25519Kyber512Draft00 - : X25519Kyber768Draft00 -+ : P256Kyber768Draft00 -+ : X25519Kyber768Draft00Old -+ : X25519MLKEM768 - - # See RFC 8410. - 1 3 101 110 : X25519 -diff --git a/src/include/openssl/kyber.h b/src/include/openssl/kyber.h -index cafae9d17..a05eb8957 100644 ---- a/src/include/openssl/kyber.h -+++ b/src/include/openssl/kyber.h -@@ -1,17 +1,3 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -- - #ifndef OPENSSL_HEADER_KYBER_H - #define OPENSSL_HEADER_KYBER_H - -@@ -21,105 +7,104 @@ - extern "C" { - #endif - -+#define KYBER512_PUBLIC_KEY_BYTES 800 -+#define KYBER512_CIPHERTEXT_BYTES 768 -+#define KYBER512_PRIVATE_KEY_BYTES 1632 -+#define KYBER768_PUBLIC_KEY_BYTES 1184 -+#define KYBER768_CIPHERTEXT_BYTES 1088 -+#define KYBER768_PRIVATE_KEY_BYTES 2400 - --// Kyber768. -- -- --// KYBER_public_key contains a Kyber768 public key. The contents of this --// object should never leave the address space since the format is unstable. --struct KYBER_public_key { -- union { -- uint8_t bytes[512 * (3 + 9) + 32 + 32]; -- uint16_t alignment; -- } opaque; -+struct KYBER512_private_key { -+ uint8_t opaque[KYBER512_PRIVATE_KEY_BYTES]; - }; -- --// KYBER_private_key contains a Kyber768 private key. The contents of this --// object should never leave the address space since the format is unstable. --struct KYBER_private_key { -- union { -- uint8_t bytes[512 * (3 + 3 + 9) + 32 + 32 + 32]; -- uint16_t alignment; -- } opaque; -+struct KYBER768_private_key { -+ uint8_t opaque[KYBER768_PRIVATE_KEY_BYTES]; -+}; -+struct KYBER512_public_key { -+ uint8_t opaque[KYBER512_PUBLIC_KEY_BYTES]; -+}; -+struct KYBER768_public_key { -+ uint8_t opaque[KYBER768_PUBLIC_KEY_BYTES]; - }; - --// KYBER_PUBLIC_KEY_BYTES is the number of bytes in an encoded Kyber768 public --// key. --#define KYBER_PUBLIC_KEY_BYTES 1184 -- --// KYBER_generate_key generates a random public/private key pair, writes the --// encoded public key to |out_encoded_public_key| and sets |out_private_key| to --// the private key. --OPENSSL_EXPORT void KYBER_generate_key( -- uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], -- struct KYBER_private_key *out_private_key); -- --// KYBER_public_from_private sets |*out_public_key| to the public key that --// corresponds to |private_key|. (This is faster than parsing the output of --// |KYBER_generate_key| if, for some reason, you need to encapsulate to a key --// that was just generated.) --OPENSSL_EXPORT void KYBER_public_from_private( -- struct KYBER_public_key *out_public_key, -- const struct KYBER_private_key *private_key); -- --// KYBER_CIPHERTEXT_BYTES is number of bytes in the Kyber768 ciphertext. --#define KYBER_CIPHERTEXT_BYTES 1088 -- --// KYBER_encap encrypts a random secret key of length |out_shared_secret_len| to --// |public_key|, writes the ciphertext to |ciphertext|, and writes the random --// key to |out_shared_secret|. The party calling |KYBER_decap| must already know --// the correct value of |out_shared_secret_len|. --OPENSSL_EXPORT void KYBER_encap(uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], -- uint8_t *out_shared_secret, -- size_t out_shared_secret_len, -- const struct KYBER_public_key *public_key); -- --// KYBER_decap decrypts a key of length |out_shared_secret_len| from --// |ciphertext| using |private_key| and writes it to |out_shared_secret|. If --// |ciphertext| is invalid, |out_shared_secret| is filled with a key that --// will always be the same for the same |ciphertext| and |private_key|, but --// which appears to be random unless one has access to |private_key|. These --// alternatives occur in constant time. Any subsequent symmetric encryption --// using |out_shared_secret| must use an authenticated encryption scheme in --// order to discover the decapsulation failure. --OPENSSL_EXPORT void KYBER_decap( -- uint8_t *out_shared_secret, size_t out_shared_secret_len, -- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES], -- const struct KYBER_private_key *private_key); -- -- --// Serialisation of keys. -- --// KYBER_marshal_public_key serializes |public_key| to |out| in the standard --// format for Kyber public keys. It returns one on success or zero on allocation --// error. --OPENSSL_EXPORT int KYBER_marshal_public_key( -- CBB *out, const struct KYBER_public_key *public_key); -- --// KYBER_parse_public_key parses a public key, in the format generated by --// |KYBER_marshal_public_key|, from |in| and writes the result to --// |out_public_key|. It returns one on success or zero on parse error or if --// there are trailing bytes in |in|. --OPENSSL_EXPORT int KYBER_parse_public_key( -- struct KYBER_public_key *out_public_key, CBS *in); -- --// KYBER_marshal_private_key serializes |private_key| to |out| in the standard --// format for Kyber private keys. It returns one on success or zero on --// allocation error. --OPENSSL_EXPORT int KYBER_marshal_private_key( -- CBB *out, const struct KYBER_private_key *private_key); -- --// KYBER_PRIVATE_KEY_BYTES is the length of the data produced by --// |KYBER_marshal_private_key|. --#define KYBER_PRIVATE_KEY_BYTES 2400 -- --// KYBER_parse_private_key parses a private key, in the format generated by --// |KYBER_marshal_private_key|, from |in| and writes the result to --// |out_private_key|. It returns one on success or zero on parse error or if --// there are trailing bytes in |in|. --OPENSSL_EXPORT int KYBER_parse_private_key( -- struct KYBER_private_key *out_private_key, CBS *in); -- -+// KYBER_GENERATE_KEY_BYTES is the number of bytes of entropy needed to -+// generate a keypair. -+#define KYBER_GENERATE_KEY_BYTES 64 -+ -+// KYBER_ENCAP_BYTES is the number of bytes of entropy needed to encapsulate a -+// session key. -+#define KYBER_ENCAP_BYTES 32 -+ -+// KYBER_KEY_BYTES is the number of bytes in a shared key. -+#define KYBER_KEY_BYTES 32 -+ -+// KYBER512_generate_key is a deterministic function that outputs a public and -+// private key based on the given entropy. -+OPENSSL_EXPORT void KYBER512_generate_key( -+ struct KYBER512_public_key *out_pub, struct KYBER512_private_key *out_priv, -+ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); -+ -+// KYBER768_generate_key is a deterministic function that outputs a public and -+// private key based on the given entropy. -+OPENSSL_EXPORT void KYBER768_generate_key( -+ struct KYBER768_public_key *out_pub, struct KYBER768_private_key *out_priv, -+ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); -+ -+// KYBER512_encap is a deterministic function the generates and encrypts a random -+// session key from the given entropy, writing those values to |out_shared_key| -+// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-512. -+OPENSSL_EXPORT int KYBER512_encap(uint8_t out_ciphertext[KYBER512_CIPHERTEXT_BYTES], -+ uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER512_public_key *in_pub, -+ const uint8_t in[KYBER_ENCAP_BYTES], -+ int mlkem); -+ -+// KYBER768_encap is a deterministic function the generates and encrypts a random -+// session key from the given entropy, writing those values to |out_shared_key| -+// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-768. -+OPENSSL_EXPORT int KYBER768_encap(uint8_t out_ciphertext[KYBER768_CIPHERTEXT_BYTES], -+ uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER768_public_key *in_pub, -+ const uint8_t in[KYBER_ENCAP_BYTES], -+ int mlkem); -+ -+// KYBER_decap decrypts a session key from |ciphertext_len| bytes of -+// |ciphertext|. If the ciphertext is valid, the decrypted key is written to -+// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept -+// in |in_priv|) is written. If the ciphertext is the wrong length then it will -+// leak which was done via side-channels. Otherwise it should perform either -+// action in constant-time. If |mlkem| is 1, will use ML-KEM-512. -+OPENSSL_EXPORT void KYBER512_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER512_private_key *in_priv, -+ const uint8_t *ciphertext, size_t ciphertext_len, -+ int mlkem); -+ -+// KYBER_decap decrypts a session key from |ciphertext_len| bytes of -+// |ciphertext|. If the ciphertext is valid, the decrypted key is written to -+// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept -+// in |in_priv|) is written. If the ciphertext is the wrong length then it will -+// leak which was done via side-channels. Otherwise it should perform either -+// action in constant-time. If |mlkem| is 1, will use ML-KEM-768. -+OPENSSL_EXPORT void KYBER768_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER768_private_key *in_priv, -+ const uint8_t *ciphertext, size_t ciphertext_len, -+ int mlkem); -+ -+// KYBER512_marshal_public_key serialises |in_pub| to |out|. -+OPENSSL_EXPORT void KYBER512_marshal_public_key( -+ uint8_t out[KYBER512_PUBLIC_KEY_BYTES], const struct KYBER512_public_key *in_pub); -+ -+// KYBER768_marshal_public_key serialises |in_pub| to |out|. -+OPENSSL_EXPORT void KYBER768_marshal_public_key( -+ uint8_t out[KYBER768_PUBLIC_KEY_BYTES], const struct KYBER768_public_key *in_pub); -+ -+// KYBER512_parse_public_key sets |*out| to the public-key encoded in |in|. -+OPENSSL_EXPORT void KYBER512_parse_public_key( -+ struct KYBER512_public_key *out, const uint8_t in[KYBER512_PUBLIC_KEY_BYTES]); -+ -+// KYBER768_parse_public_key sets |*out| to the public-key encoded in |in|. -+OPENSSL_EXPORT void KYBER768_parse_public_key( -+ struct KYBER768_public_key *out, const uint8_t in[KYBER768_PUBLIC_KEY_BYTES]); - - #if defined(__cplusplus) - } // extern C -diff --git a/src/include/openssl/nid.h b/src/include/openssl/nid.h -index 4dd8841b1..5b102c610 100644 ---- a/src/include/openssl/nid.h -+++ b/src/include/openssl/nid.h -@@ -4255,6 +4255,18 @@ extern "C" { - #define SN_X25519Kyber768Draft00 "X25519Kyber768Draft00" - #define NID_X25519Kyber768Draft00 964 - -+#define SN_X25519Kyber512Draft00 "X25519Kyber512Draft00" -+#define NID_X25519Kyber512Draft00 965 -+ -+#define SN_P256Kyber768Draft00 "P256Kyber768Draft00" -+#define NID_P256Kyber768Draft00 966 -+ -+#define SN_X25519Kyber768Draft00Old "X25519Kyber768Draft00Old" -+#define NID_X25519Kyber768Draft00Old 967 -+ -+#define SN_X25519MLKEM768 "X25519MLKEM768" -+#define NID_X25519MLKEM768 968 -+ - - #if defined(__cplusplus) - } /* extern C */ -diff --git a/src/include/openssl/ssl.h b/src/include/openssl/ssl.h -index 53aa9b453..f9683f4cf 100644 ---- a/src/include/openssl/ssl.h -+++ b/src/include/openssl/ssl.h -@@ -2378,6 +2378,10 @@ OPENSSL_EXPORT int SSL_set1_curves_list(SSL *ssl, const char *curves); - #define SSL_CURVE_SECP521R1 25 - #define SSL_CURVE_X25519 29 - #define SSL_CURVE_X25519_KYBER768_DRAFT00 0x6399 -+#define SSL_CURVE_X25519_KYBER512_DRAFT00 0xfe30 -+#define SSL_CURVE_X25519_KYBER768_DRAFT00_OLD 0xfe31 -+#define SSL_CURVE_P256_KYBER768_DRAFT00 0xfe32 -+#define SSL_CURVE_X25519_MLKEM768 0x11ec - - // SSL_get_curve_id returns the ID of the curve used by |ssl|'s most recently - // completed handshake or 0 if not applicable. -diff --git a/src/sources.cmake b/src/sources.cmake -index 5c7e881bf..3c0770cf3 100644 ---- a/src/sources.cmake -+++ b/src/sources.cmake -@@ -66,8 +66,6 @@ set( - crypto/fipsmodule/rand/ctrdrbg_vectors.txt - crypto/hmac_extra/hmac_tests.txt - crypto/hpke/hpke_test_vectors.txt -- crypto/kyber/keccak_tests.txt -- crypto/kyber/kyber_tests.txt - crypto/pkcs8/test/empty_password.p12 - crypto/pkcs8/test/no_encryption.p12 - crypto/pkcs8/test/nss.p12 -diff --git a/src/ssl/extensions.cc b/src/ssl/extensions.cc -index 5ee280221..aae3e6a7f 100644 ---- a/src/ssl/extensions.cc -+++ b/src/ssl/extensions.cc -@@ -207,6 +207,10 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { - static bool is_post_quantum_group(uint16_t id) { - switch (id) { - case SSL_CURVE_X25519_KYBER768_DRAFT00: -+ case SSL_CURVE_X25519_KYBER768_DRAFT00_OLD: -+ case SSL_CURVE_X25519_KYBER512_DRAFT00: -+ case SSL_CURVE_P256_KYBER768_DRAFT00: -+ case SSL_CURVE_X25519_MLKEM768: - return true; - default: - return false; -diff --git a/src/ssl/ssl_key_share.cc b/src/ssl/ssl_key_share.cc -index 09a9ad380..d7a8f0a80 100644 ---- a/src/ssl/ssl_key_share.cc -+++ b/src/ssl/ssl_key_share.cc -@@ -26,6 +26,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -193,63 +194,292 @@ class X25519KeyShare : public SSLKeyShare { - uint8_t private_key_[32]; - }; - --class X25519Kyber768KeyShare : public SSLKeyShare { -+class P256Kyber768Draft00KeyShare : public SSLKeyShare { - public: -- X25519Kyber768KeyShare() {} -+ P256Kyber768Draft00KeyShare() {} -+ -+ uint16_t GroupID() const override { return SSL_CURVE_P256_KYBER768_DRAFT00; } -+ -+ bool Generate(CBB *out) override { -+ assert(!p256_private_key_); -+ -+ // Set up a shared |BN_CTX| for P-256 operations. -+ UniquePtr bn_ctx(BN_CTX_new()); -+ if (!bn_ctx) { -+ return false; -+ } -+ -+ BN_CTXScope scope(bn_ctx.get()); -+ -+ // Generate a P-256 private key. -+ UniquePtr group; -+ group.reset(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); -+ p256_private_key_.reset(BN_new()); -+ if (!group || !p256_private_key_ || -+ !BN_rand_range_ex(p256_private_key_.get(), 1, -+ EC_GROUP_get0_order(group.get()))) { -+ return false; -+ } -+ -+ // Compute the corresponding P-256 public key and serialize it. -+ UniquePtr p256_public_key(EC_POINT_new(group.get())); -+ if (!p256_public_key || -+ !EC_POINT_mul(group.get(), p256_public_key.get(), p256_private_key_.get(), -+ NULL, NULL, bn_ctx.get()) || -+ !EC_POINT_point2cbb(out, group.get(), p256_public_key.get(), -+ POINT_CONVERSION_UNCOMPRESSED, bn_ctx.get())) { -+ return false; -+ } -+ -+ -+ // Kyber -+ uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; -+ KYBER768_public_key kyber_public_key; -+ RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); -+ KYBER768_generate_key(&kyber_public_key, &kyber_private_key_, kyber_entropy); -+ -+ uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; -+ KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); - -- uint16_t GroupID() const override { -- return SSL_CURVE_X25519_KYBER768_DRAFT00; -+ if (!CBB_add_bytes(out, kyber_public_key_bytes, -+ sizeof(kyber_public_key_bytes))) { -+ return false; -+ } -+ -+ return true; - } - -+ bool Encap(CBB *out_public_key, Array *out_secret, -+ uint8_t *out_alert, Span peer_key) override { -+ assert(!p256_private_key_); -+ -+ if (peer_key.size() != 65 + KYBER768_PUBLIC_KEY_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ -+ // Set up a shared |BN_CTX| for P-256 operations. -+ UniquePtr bn_ctx(BN_CTX_new()); -+ if (!bn_ctx) { -+ return false; -+ } -+ -+ BN_CTXScope scope(bn_ctx.get()); -+ -+ UniquePtr group; -+ group.reset(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); -+ if (!group) { -+ return false; -+ } -+ -+ // Parse peer point -+ UniquePtr peer_point(EC_POINT_new(group.get())); -+ UniquePtr result(EC_POINT_new(group.get())); -+ BIGNUM *x = BN_CTX_get(bn_ctx.get()); -+ if (!peer_point || !result || !x) { -+ return false; -+ } -+ -+ if (peer_key.empty() || peer_key[0] != POINT_CONVERSION_UNCOMPRESSED || -+ !EC_POINT_oct2point(group.get(), peer_point.get(), peer_key.data(), -+ 65, bn_ctx.get())) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ *out_alert = SSL_AD_DECODE_ERROR; -+ return false; -+ } -+ -+ p256_private_key_.reset(BN_new()); -+ if (!p256_private_key_ || !BN_rand_range_ex(p256_private_key_.get(), 1, -+ EC_GROUP_get0_order(group.get()))) { -+ return false; -+ } -+ -+ // Compute the corresponding P-256 public key and serialize it. -+ UniquePtr p256_public_key(EC_POINT_new(group.get())); -+ if (!p256_public_key || -+ !EC_POINT_mul(group.get(), p256_public_key.get(), p256_private_key_.get(), -+ NULL, NULL, bn_ctx.get()) || -+ !EC_POINT_point2cbb(out_public_key, group.get(), p256_public_key.get(), -+ POINT_CONVERSION_UNCOMPRESSED, bn_ctx.get())) { -+ return false; -+ } -+ -+ // Compute the x-coordinate of |peer_key| * |p256_private_key_|. -+ if (!EC_POINT_mul(group.get(), result.get(), NULL, peer_point.get(), -+ p256_private_key_.get(), bn_ctx.get()) || -+ !EC_POINT_get_affine_coordinates_GFp(group.get(), result.get(), x, NULL, -+ bn_ctx.get())) { -+ return false; -+ } -+ -+ // Encode the x-coordinate left-padded with zeros. -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES) || -+ !BN_bn2bin_padded(secret.data(), 32, x)) { -+ return false; -+ } -+ -+ -+ KYBER768_public_key peer_public_key; -+ KYBER768_parse_public_key(&peer_public_key, peer_key.data() + 65); -+ -+ uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ -+ if(!KYBER768_encap(ciphertext, secret.data() + 32, &peer_public_key, entropy, 0)) { -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; -+ } -+ if(!CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { -+ return false; -+ } -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ bool Decap(Array *out_secret, uint8_t *out_alert, -+ Span peer_key) override { -+ assert(p256_private_key_); -+ *out_alert = SSL_AD_INTERNAL_ERROR; -+ -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } -+ -+ if (peer_key.size() != 65 + KYBER768_CIPHERTEXT_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ -+ // Set up a shared |BN_CTX| for P-256 operations. -+ UniquePtr bn_ctx(BN_CTX_new()); -+ if (!bn_ctx) { -+ return false; -+ } -+ -+ BN_CTXScope scope(bn_ctx.get()); -+ -+ UniquePtr group; -+ group.reset(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); -+ if (!group) { -+ return false; -+ } -+ -+ // Parse peer point -+ UniquePtr peer_point(EC_POINT_new(group.get())); -+ UniquePtr result(EC_POINT_new(group.get())); -+ BIGNUM *x = BN_CTX_get(bn_ctx.get()); -+ if (!peer_point || !result || !x) { -+ return false; -+ } -+ -+ if (peer_key.empty() || peer_key[0] != POINT_CONVERSION_UNCOMPRESSED || -+ !EC_POINT_oct2point(group.get(), peer_point.get(), peer_key.data(), -+ 65, bn_ctx.get())) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ *out_alert = SSL_AD_DECODE_ERROR; -+ return false; -+ } -+ -+ // Compute the x-coordinate of |peer_key| * |p256_private_key_|. -+ if (!EC_POINT_mul(group.get(), result.get(), NULL, peer_point.get(), -+ p256_private_key_.get(), bn_ctx.get()) || -+ !EC_POINT_get_affine_coordinates_GFp(group.get(), result.get(), x, NULL, -+ bn_ctx.get())) { -+ return false; -+ } -+ -+ // Encode the x-coordinate left-padded with zeros. -+ if (!secret.Init(32 + KYBER_KEY_BYTES) || -+ !BN_bn2bin_padded(secret.data(), 32, x)) { -+ return false; -+ } -+ -+ KYBER768_decap(secret.data() + 32, &kyber_private_key_, -+ peer_key.data() + 65, peer_key.size() - 65, 0); -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ private: -+ UniquePtr p256_private_key_; -+ KYBER768_private_key kyber_private_key_; -+}; -+ -+class X25519Kyber768Draft00KeyShare : public SSLKeyShare { -+ public: -+ X25519Kyber768Draft00KeyShare(uint16_t group_id) : group_id_(group_id) { -+ assert(group_id == SSL_CURVE_X25519_KYBER768_DRAFT00 -+ || group_id == SSL_CURVE_X25519_KYBER768_DRAFT00_OLD); -+ } -+ -+ uint16_t GroupID() const override { return group_id_; } -+ - bool Generate(CBB *out) override { - uint8_t x25519_public_key[32]; - X25519_keypair(x25519_public_key, x25519_private_key_); +This patch adds: + +1. Enable X25519MLKEM768 by default. + +2. Supports for P256Kyber768Draft00 under 0xfe32, which we temporarily + need for compliance reasons. (Note that this is not the codepoint + allocated for that exchange in the IANA table.) + Enables by default and in FIPS mode. + +3. Add SSL(_CTX)_use_second_keyshare. By default BoringSSL will send a + non post-quantum and a post-quantum keyshare if available. These + functions allow one to change the behaviour to only send a single + keyshare. +--- + crypto/obj/obj_dat.h | 6 +- + crypto/obj/obj_mac.num | 1 + + crypto/obj/objects.txt | 1 + + include/openssl/nid.h | 3 + + include/openssl/ssl.h | 15 ++++ + ssl/extensions.cc | 26 ++++--- + ssl/internal.h | 12 ++- + ssl/ssl_key_share.cc | 111 +++++++++++++++++++++++++++- + ssl/ssl_lib.cc | 16 +++- + ssl/ssl_test.cc | 19 ++++- + ssl/test/runner/basic_tests.go | 2 + + ssl/test/runner/cbc_tests.go | 3 + + ssl/test/runner/common.go | 2 +- + ssl/test/runner/curve_tests.go | 28 +++---- + ssl/test/runner/ech_tests.go | 24 +++++- + ssl/test/runner/extension_tests.go | 3 +- + ssl/test/runner/key_update_tests.go | 6 +- + tool/client.cc | 9 +++ + 18 files changed, 245 insertions(+), 42 deletions(-) + +diff --git a/crypto/obj/obj_dat.h b/crypto/obj/obj_dat.h +index d8b86dcd2..6dd49ec36 100644 +--- a/crypto/obj/obj_dat.h ++++ b/crypto/obj/obj_dat.h +@@ -15,7 +15,7 @@ + // This file is generated by crypto/obj/objects.go. -- uint8_t kyber_public_key[KYBER_PUBLIC_KEY_BYTES]; -- KYBER_generate_key(kyber_public_key, &kyber_private_key_); -+ uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; -+ KYBER768_public_key kyber_public_key; -+ RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); -+ KYBER768_generate_key(&kyber_public_key, &kyber_private_key_, kyber_entropy); -+ -+ uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; -+ KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); - if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || -- !CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key))) { -+ !CBB_add_bytes(out, kyber_public_key_bytes, -+ sizeof(kyber_public_key_bytes))) { - return false; - } +-#define NUM_NID 971 ++#define NUM_NID 972 - return true; - } + static const uint8_t kObjectData[] = { + /* NID_rsadsi */ +@@ -8799,6 +8799,8 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { + {"id-ml-dsa-87", "ML-DSA-87", NID_ML_DSA_87, 9, &kObjectData[6223], 0}, + {"id-alg-ml-kem-768", "ML-KEM-768", NID_ML_KEM_768, 9, &kObjectData[6232], + 0}, ++ {"P256Kyber768Draft00", "P256Kyber768Draft00", NID_P256Kyber768Draft00, 0, ++ NULL, 0}, + }; -- bool Encap(CBB *out_ciphertext, Array *out_secret, -- uint8_t *out_alert, Span peer_key) override { -+ bool Encap(CBB *out_public_key, Array *out_secret, -+ uint8_t *out_alert, Span peer_key) override { - Array secret; -- if (!secret.Init(32 + 32)) { -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return false; - } + static const uint16_t kNIDsInShortNameOrder[] = { +@@ -8931,6 +8933,7 @@ static const uint16_t kNIDsInShortNameOrder[] = { + 18 /* OU */, + 749 /* Oakley-EC2N-3 */, + 750 /* Oakley-EC2N-4 */, ++ 971 /* P256Kyber768Draft00 */, + 9 /* PBE-MD2-DES */, + 168 /* PBE-MD2-RC2-64 */, + 10 /* PBE-MD5-DES */, +@@ -9854,6 +9857,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { + 366 /* OCSP Nonce */, + 371 /* OCSP Service Locator */, + 180 /* OCSP Signing */, ++ 971 /* P256Kyber768Draft00 */, + 161 /* PBES2 */, + 69 /* PBKDF2 */, + 162 /* PBMAC1 */, +diff --git a/crypto/obj/obj_mac.num b/crypto/obj/obj_mac.num +index ae863e29d..7231b9a58 100644 +--- a/crypto/obj/obj_mac.num ++++ b/crypto/obj/obj_mac.num +@@ -958,3 +958,4 @@ ML_DSA_44 967 + ML_DSA_65 968 + ML_DSA_87 969 + ML_KEM_768 970 ++P256Kyber768Draft00 971 +diff --git a/crypto/obj/objects.txt b/crypto/obj/objects.txt +index 1e0cb76db..e8b249dfd 100644 +--- a/crypto/obj/objects.txt ++++ b/crypto/obj/objects.txt +@@ -1340,6 +1340,7 @@ secg-scheme 14 3 : dhSinglePass-cofactorDH-sha512kdf-scheme + + # NIDs for post quantum hybrid KEMs in TLS (no corresponding OIDs). + : X25519Kyber768Draft00 ++ : P256Kyber768Draft00 + : X25519MLKEM768 - uint8_t x25519_public_key[32]; - X25519_keypair(x25519_public_key, x25519_private_key_); -- KYBER_public_key peer_kyber_pub; -- CBS peer_key_cbs; -- CBS peer_x25519_cbs; -- CBS peer_kyber_cbs; -- CBS_init(&peer_key_cbs, peer_key.data(), peer_key.size()); -- if (!CBS_get_bytes(&peer_key_cbs, &peer_x25519_cbs, 32) || -- !CBS_get_bytes(&peer_key_cbs, &peer_kyber_cbs, -- KYBER_PUBLIC_KEY_BYTES) || -- CBS_len(&peer_key_cbs) != 0 || -- !X25519(secret.data(), x25519_private_key_, -- CBS_data(&peer_x25519_cbs)) || -- !KYBER_parse_public_key(&peer_kyber_pub, &peer_kyber_cbs)) { -+ -+ KYBER768_public_key peer_public_key; -+ if (peer_key.size() != 32 + KYBER768_PUBLIC_KEY_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ -+ KYBER768_parse_public_key(&peer_public_key, peer_key.data() + 32); -+ -+ if (!X25519(secret.data(), x25519_private_key_, peer_key.data())) { - *out_alert = SSL_AD_DECODE_ERROR; - OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); - return false; - } + # See RFC 8410. +diff --git a/include/openssl/nid.h b/include/openssl/nid.h +index 83a1cf592..7265f15f6 100644 +--- a/include/openssl/nid.h ++++ b/include/openssl/nid.h +@@ -5508,6 +5508,9 @@ extern "C" { + #define OBJ_ML_KEM_768 2L, 16L, 840L, 1L, 101L, 3L, 4L, 4L, 2L + #define OBJ_ENC_ML_KEM_768 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02 -- uint8_t kyber_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- KYBER_encap(kyber_ciphertext, secret.data() + 32, secret.size() - 32, -- &peer_kyber_pub); -+ uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); ++#define SN_P256Kyber768Draft00 "P256Kyber768Draft00" ++#define NID_P256Kyber768Draft00 971 ++ -- if (!CBB_add_bytes(out_ciphertext, x25519_public_key, -+ if(!KYBER768_encap(ciphertext, secret.data() + 32, &peer_public_key, entropy, 0)) { -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; -+ } -+ if(!CBB_add_bytes(out_public_key, x25519_public_key, - sizeof(x25519_public_key)) || -- !CBB_add_bytes(out_ciphertext, kyber_ciphertext, -- sizeof(kyber_ciphertext))) { -+ !CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { + #if defined(__cplusplus) + } /* extern C */ +diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h +index ff68ba69e..0730e769a 100644 +--- a/include/openssl/ssl.h ++++ b/include/openssl/ssl.h +@@ -2550,6 +2550,7 @@ OPENSSL_EXPORT size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx); + #define SSL_GROUP_X25519_MLKEM768 0x11ec + #define SSL_GROUP_X25519_KYBER768_DRAFT00 0x6399 + #define SSL_GROUP_MLKEM1024 0x0202 ++#define SSL_GROUP_P256_KYBER768_DRAFT00 0xfe32 + + // SSL_CTX_set1_group_ids sets the preferred groups for |ctx| to |group_ids|. + // Each element of |group_ids| should be a unique one of the |SSL_GROUP_*| +@@ -5964,6 +5965,20 @@ OPENSSL_EXPORT int SSL_CTX_set1_curves_list(SSL_CTX *ctx, const char *curves); + // SSL_set1_curves_list calls |SSL_set1_groups_list|. + OPENSSL_EXPORT int SSL_set1_curves_list(SSL *ssl, const char *curves); + ++// By default, a client will send both a non post-quantum and a post-quantum ++// keyshare if available. ++// ++// SSL_use_second_keyshare controls this behaviour. If |enabled| is 0, then ++// a client using |ssl| will only send one keyshare. ++OPENSSL_EXPORT void SSL_use_second_keyshare(SSL *ssl, int enabled); ++ ++// By default, a client will send both a non post-quantum and a post-quantum ++// keyshare if available. ++// ++// SSL_CTX_use_second_keyshare controls this behaviour. If |enabled| is 0, then ++// a client using |ctx| will only send one keyshare. ++OPENSSL_EXPORT void SSL_CTX_use_second_keyshare(SSL_CTX *ctx, int enabled); ++ + // TLSEXT_nid_unknown is a constant used in OpenSSL for + // |SSL_get_negotiated_group| to return an unrecognized group. BoringSSL never + // returns this value, but we define this constant for compatibility. +diff --git a/ssl/extensions.cc b/ssl/extensions.cc +index c5f90688c..e0514fed3 100644 +--- a/ssl/extensions.cc ++++ b/ssl/extensions.cc +@@ -101,6 +101,7 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { + static bool is_post_quantum_group(uint16_t id) { + switch (id) { + case SSL_GROUP_X25519_KYBER768_DRAFT00: ++ case SSL_GROUP_P256_KYBER768_DRAFT00: + case SSL_GROUP_X25519_MLKEM768: + case SSL_GROUP_MLKEM1024: + return true; +@@ -2241,18 +2242,21 @@ bool ssl_setup_key_shares(SSL_HANDSHAKE *hs, uint16_t override_group_id) { + if (!default_key_shares.TryPushBack(supported_group_list[0])) { return false; } - -@@ -258,30 +488,233 @@ class X25519Kyber768KeyShare : public SSLKeyShare { +- // We'll try to include one post-quantum and one classical initial key +- // share. +- for (size_t i = 1; i < supported_group_list.size(); i++) { +- if (is_post_quantum_group(default_key_shares[0]) == +- is_post_quantum_group(supported_group_list[i])) { +- continue; +- } +- if (!default_key_shares.TryPushBack(supported_group_list[i])) { +- return false; ++ ++ if (!ssl->config->disable_second_keyshare) { ++ // We'll try to include one post-quantum and one classical initial key ++ // share. ++ for (size_t i = 1; i < supported_group_list.size(); i++) { ++ if (is_post_quantum_group(default_key_shares[0]) == ++ is_post_quantum_group(supported_group_list[i])) { ++ continue; ++ } ++ if (!default_key_shares.TryPushBack(supported_group_list[i])) { ++ return false; ++ } ++ assert(default_key_shares[1] != default_key_shares[0]); ++ break; + } +- assert(default_key_shares[1] != default_key_shares[0]); +- break; + } + selected_key_shares.emplace(default_key_shares); } +diff --git a/ssl/internal.h b/ssl/internal.h +index a69505b47..1f5ce51e6 100644 +--- a/ssl/internal.h ++++ b/ssl/internal.h +@@ -955,7 +955,7 @@ struct NamedGroup { + Span NamedGroups(); + + // kNumNamedGroups is the number of supported groups. +-constexpr size_t kNumNamedGroups = 7u; ++constexpr size_t kNumNamedGroups = 8u; + + // DefaultSupportedGroupIds returns the list of IDs for the default groups that + // are supported when the caller hasn't explicitly configured supported groups. +@@ -3388,6 +3388,11 @@ struct SSL_CONFIG { + // permute_extensions is whether to permute extensions when sending messages. + bool permute_extensions : 1; + ++ // As a client by default we will send a non post-quantum share and ++ // a post-quantum share if available. If disable_second_keyshare is set, ++ // we will only send the most preferred keyshare. ++ bool disable_second_keyshare : 1; ++ + // aes_hw_override if set indicates we should override checking for aes + // hardware support, and use the value in aes_hw_override_value instead. + bool aes_hw_override : 1; +@@ -4015,6 +4020,11 @@ struct ssl_ctx_st : public bssl::RefCounted { + // permute_extensions is whether to permute extensions when sending messages. + bool permute_extensions : 1; + ++ // As a client by default we will send a non post-quantum share and ++ // a post-quantum share if available. If disable_second_keyshare is set, ++ // we will only send the most preferred keyshare. ++ bool disable_second_keyshare : 1; ++ + // allow_unknown_alpn_protos is whether the client allows unsolicited ALPN + // protocols from the peer. + bool allow_unknown_alpn_protos : 1; +diff --git a/ssl/ssl_key_share.cc b/ssl/ssl_key_share.cc +index d155b5527..4fb08906b 100644 +--- a/ssl/ssl_key_share.cc ++++ b/ssl/ssl_key_share.cc +@@ -193,6 +193,109 @@ class X25519KeyShare : public SSLKeyShare { + uint8_t private_key_[32]; + }; - bool Decap(Array *out_secret, uint8_t *out_alert, -- Span ciphertext) override { -+ Span peer_key) override { -+ *out_alert = SSL_AD_INTERNAL_ERROR; -+ -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } -+ -+ if (peer_key.size() != 32 + KYBER768_CIPHERTEXT_BYTES || -+ !X25519(secret.data(), x25519_private_key_, peer_key.data())) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ -+ KYBER768_decap(secret.data() + 32, &kyber_private_key_, -+ peer_key.data() + 32, peer_key.size() - 32, 0); -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ private: -+ uint8_t x25519_private_key_[32]; -+ KYBER768_private_key kyber_private_key_; -+ uint16_t group_id_; -+}; -+ -+class X25519MLKEM768KeyShare : public SSLKeyShare { ++class P256Kyber768Draft00KeyShare : public SSLKeyShare { + public: -+ X25519MLKEM768KeyShare() {} ++ P256Kyber768Draft00KeyShare() ++ : ecks_(EC_group_p256(), SSL_GROUP_SECP256R1) {} + -+ uint16_t GroupID() const override { return SSL_CURVE_X25519_MLKEM768; } ++ uint16_t GroupID() const override { ++ return SSL_GROUP_P256_KYBER768_DRAFT00; ++ } + + bool Generate(CBB *out) override { -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); -+ -+ uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; -+ KYBER768_public_key kyber_public_key; -+ RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); -+ KYBER768_generate_key(&kyber_public_key, &kyber_private_key_, kyber_entropy); ++ uint8_t kyber_public_key[KYBER_PUBLIC_KEY_BYTES]; ++ KYBER_generate_key(kyber_public_key, &kyber_private_key_); + -+ uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; -+ KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); -+ -+ if (!CBB_add_bytes(out, kyber_public_key_bytes, sizeof(kyber_public_key_bytes)) || -+ !CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key))) { ++ if(!ecks_.Generate(out) || ++ !CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key))) { + return false; + } + + return true; + } + -+ bool Encap(CBB *out_public_key, Array *out_secret, -+ uint8_t *out_alert, Span peer_key) override { -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } ++ bool Encap(CBB *out_ciphertext, Array *out_secret, ++ uint8_t *out_alert, Span peer_key) override { ++ Array ec_secret; + -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); ++ *out_alert = SSL_AD_INTERNAL_ERROR; + -+ KYBER768_public_key peer_public_key; -+ if (peer_key.size() != KYBER768_PUBLIC_KEY_BYTES + 32) { -+ *out_alert = SSL_AD_DECODE_ERROR; ++ if(peer_key.size() != p256_share_size + KYBER_PUBLIC_KEY_BYTES) { ++ *out_alert = SSL_AD_ILLEGAL_PARAMETER; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + -+ KYBER768_parse_public_key(&peer_public_key, peer_key.data()); -+ -+ if (!X25519(secret.data() + 32, x25519_private_key_, -+ peer_key.data() + KYBER768_PUBLIC_KEY_BYTES)) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ if (!ecks_.Encap(out_ciphertext, &ec_secret, out_alert, ++ peer_key.subspan(0, p256_share_size))) { + return false; + } + -+ uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); ++ KYBER_public_key peer_kyber_pub; ++ CBS peer_kyber_cbs; ++ CBS_init(&peer_kyber_cbs, peer_key.data() + p256_share_size, ++ KYBER_PUBLIC_KEY_BYTES); + -+ if(!KYBER768_encap(ciphertext, secret.data(), &peer_public_key, entropy, 1)) { ++ if (!KYBER_parse_public_key(&peer_kyber_pub, &peer_kyber_cbs)) { + *out_alert = SSL_AD_ILLEGAL_PARAMETER; ++ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } -+ if(!CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext)) || -+ !CBB_add_bytes(out_public_key, x25519_public_key, sizeof(x25519_public_key))) { ++ ++ uint8_t kyber_ciphertext[KYBER_CIPHERTEXT_BYTES]; ++ Array secret; ++ if (!secret.InitForOverwrite(p256_secret_size + KYBER_SHARED_SECRET_BYTES)) { + return false; + } ++ OPENSSL_memcpy(secret.data(), ec_secret.data(), ec_secret.size()); ++ KYBER_encap(kyber_ciphertext, secret.data() + p256_secret_size, ++ &peer_kyber_pub); + -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ bool Decap(Array *out_secret, uint8_t *out_alert, -+ Span peer_key) override { - *out_alert = SSL_AD_INTERNAL_ERROR; - - Array secret; -- if (!secret.Init(32 + 32)) { -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return false; - } - -- if (ciphertext.size() != 32 + KYBER_CIPHERTEXT_BYTES || -- !X25519(secret.data(), x25519_private_key_, ciphertext.data())) { -+ if (peer_key.size() != KYBER768_CIPHERTEXT_BYTES + 32 || -+ !X25519(secret.data() + 32, x25519_private_key_, -+ peer_key.data() + KYBER768_CIPHERTEXT_BYTES )) { - *out_alert = SSL_AD_DECODE_ERROR; - OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); - return false; - } - -- KYBER_decap(secret.data() + 32, secret.size() - 32, ciphertext.data() + 32, -- &kyber_private_key_); -+ KYBER768_decap(secret.data(), &kyber_private_key_, -+ peer_key.data(), peer_key.size() - 32, 1); -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ private: -+ uint8_t x25519_private_key_[32]; -+ KYBER768_private_key kyber_private_key_; -+}; -+ -+class X25519Kyber512Draft00KeyShare : public SSLKeyShare { -+ public: -+ X25519Kyber512Draft00KeyShare() {} -+ -+ uint16_t GroupID() const override { return SSL_CURVE_X25519_KYBER512_DRAFT00; } -+ -+ bool Generate(CBB *out) override { -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); -+ -+ uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; -+ KYBER512_public_key kyber_public_key; -+ RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); -+ KYBER512_generate_key(&kyber_public_key, &kyber_private_key_, kyber_entropy); -+ -+ uint8_t kyber_public_key_bytes[KYBER512_PUBLIC_KEY_BYTES]; -+ KYBER512_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); -+ -+ if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || -+ !CBB_add_bytes(out, kyber_public_key_bytes, -+ sizeof(kyber_public_key_bytes))) { ++ if(!CBB_add_bytes(out_ciphertext, kyber_ciphertext, ++ sizeof(kyber_ciphertext))) { + return false; + } + ++ *out_secret = std::move(secret); + return true; + } + -+ bool Encap(CBB *out_public_key, Array *out_secret, -+ uint8_t *out_alert, Span peer_key) override { -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } ++ bool Decap(Array *out_secret, uint8_t *out_alert, ++ Span ciphertext) override { ++ *out_alert = SSL_AD_INTERNAL_ERROR; + -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); ++ Array ec_secret; + -+ KYBER512_public_key peer_public_key; -+ if (peer_key.size() != 32 + KYBER512_PUBLIC_KEY_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; ++ if (ciphertext.size() != p256_share_size + KYBER_CIPHERTEXT_BYTES) { ++ *out_alert = SSL_AD_ILLEGAL_PARAMETER; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + -+ KYBER512_parse_public_key(&peer_public_key, peer_key.data() + 32); -+ -+ if (!X25519(secret.data(), x25519_private_key_, peer_key.data())) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ if (!ecks_.Decap(&ec_secret, out_alert, ++ ciphertext.subspan(0, p256_share_size))) { + return false; + } + -+ uint8_t ciphertext[KYBER512_CIPHERTEXT_BYTES]; -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ -+ if(!KYBER512_encap(ciphertext, secret.data() + 32, &peer_public_key, entropy, 0)) { -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; -+ } -+ if(!CBB_add_bytes(out_public_key, x25519_public_key, -+ sizeof(x25519_public_key)) || -+ !CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { ++ Array secret; ++ if (!secret.InitForOverwrite(p256_secret_size + KYBER_SHARED_SECRET_BYTES)) { + return false; + } -+ ++ OPENSSL_memcpy(secret.data(), ec_secret.data(), ec_secret.size()); ++ KYBER_decap(secret.data() + p256_secret_size, ++ ciphertext.data() + p256_share_size, &kyber_private_key_); + *out_secret = std::move(secret); + return true; + } + -+ bool Decap(Array *out_secret, uint8_t *out_alert, -+ Span peer_key) override { -+ *out_alert = SSL_AD_INTERNAL_ERROR; -+ -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } -+ -+ if (peer_key.size() != 32 + KYBER512_CIPHERTEXT_BYTES || -+ !X25519(secret.data(), x25519_private_key_, peer_key.data())) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } ++ private: ++ ECKeyShare ecks_; ++ KYBER_private_key kyber_private_key_; + -+ KYBER512_decap(secret.data() + 32, &kyber_private_key_, -+ peer_key.data() + 32, peer_key.size() - 32, 0); ++ static constexpr size_t p256_share_size = 65; ++ static constexpr size_t p256_secret_size = 32; ++}; + - *out_secret = std::move(secret); - return true; - } - - private: - uint8_t x25519_private_key_[32]; -- KYBER_private_key kyber_private_key_; -+ KYBER512_private_key kyber_private_key_; - }; - - constexpr NamedGroup kNamedGroups[] = { -@@ -290,8 +723,16 @@ constexpr NamedGroup kNamedGroups[] = { - {NID_secp384r1, SSL_CURVE_SECP384R1, "P-384", "secp384r1"}, - {NID_secp521r1, SSL_CURVE_SECP521R1, "P-521", "secp521r1"}, - {NID_X25519, SSL_CURVE_X25519, "X25519", "x25519"}, -+ {NID_X25519Kyber512Draft00, SSL_CURVE_X25519_KYBER512_DRAFT00, -+ "X25519Kyber512Draft00", "Xyber512D00"}, - {NID_X25519Kyber768Draft00, SSL_CURVE_X25519_KYBER768_DRAFT00, + // draft-tls-westerbaan-xyber768d00-03 + class X25519Kyber768KeyShare : public SSLKeyShare { + public: +@@ -441,9 +544,11 @@ constexpr NamedGroup kNamedGroups[] = { + {NID_secp521r1, SSL_GROUP_SECP521R1, "P-521", "secp521r1"}, + {NID_X25519, SSL_GROUP_X25519, "X25519", "x25519"}, + {NID_X25519Kyber768Draft00, SSL_GROUP_X25519_KYBER768_DRAFT00, - "X25519Kyber768Draft00", ""}, -+ "X25519Kyber768Draft00", "Xyber768D00"}, -+ {NID_X25519Kyber768Draft00Old, SSL_CURVE_X25519_KYBER768_DRAFT00_OLD, -+ "X25519Kyber768Draft00Old", "Xyber768D00Old"}, -+ {NID_P256Kyber768Draft00, SSL_CURVE_P256_KYBER768_DRAFT00, -+ "P256Kyber768Draft00", "P256Kyber768D00"}, -+ {NID_X25519MLKEM768, SSL_CURVE_X25519_MLKEM768, -+ "X25519MLKEM768", "X25519MLKEM768"} ++ "X25519Kyber768Draft00", "Xyber768D00"}, + {NID_X25519MLKEM768, SSL_GROUP_X25519_MLKEM768, "X25519MLKEM768", ""}, + {NID_ML_KEM_1024, SSL_GROUP_MLKEM1024, "MLKEM1024", ""}, ++ {NID_P256Kyber768Draft00, SSL_GROUP_P256_KYBER768_DRAFT00, ++ "P256Kyber768Draft00", "P256Kyber768D00"}, }; - } // namespace -@@ -312,8 +753,18 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { - return MakeUnique(NID_secp521r1, SSL_CURVE_SECP521R1); - case SSL_CURVE_X25519: - return MakeUnique(); -+ case SSL_CURVE_X25519_KYBER512_DRAFT00: -+ return UniquePtr(New()); - case SSL_CURVE_X25519_KYBER768_DRAFT00: -- return MakeUnique(); -+ return UniquePtr(New( -+ group_id)); -+ case SSL_CURVE_X25519_KYBER768_DRAFT00_OLD: -+ return UniquePtr(New( -+ group_id)); -+ case SSL_CURVE_P256_KYBER768_DRAFT00: -+ return UniquePtr(New()); -+ case SSL_CURVE_X25519_MLKEM768: -+ return UniquePtr(New()); + static_assert(std::size(kNamedGroups) == kNumNamedGroups, +@@ -455,6 +560,8 @@ Span NamedGroups() { return kNamedGroups; } + + Span DefaultSupportedGroupIds() { + static const uint16_t kDefaultSupportedGroupIds[] = { ++ SSL_GROUP_X25519_MLKEM768, ++ SSL_GROUP_P256_KYBER768_DRAFT00, + SSL_GROUP_X25519, + SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1, +@@ -478,6 +585,8 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { + return MakeUnique(); + case SSL_GROUP_MLKEM1024: + return MakeUnique(); ++ case SSL_GROUP_P256_KYBER768_DRAFT00: ++ return MakeUnique(); default: return nullptr; } -diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc -index 838761af5..9eb201d37 100644 ---- a/src/ssl/ssl_lib.cc -+++ b/src/ssl/ssl_lib.cc -@@ -3151,7 +3151,7 @@ namespace fips202205 { +diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc +index f64b103fb..fe5bb9bc7 100644 +--- a/ssl/ssl_lib.cc ++++ b/ssl/ssl_lib.cc +@@ -397,6 +397,7 @@ ssl_ctx_st::ssl_ctx_st(const SSL_METHOD *ssl_method) + channel_id_enabled(false), + grease_enabled(false), + permute_extensions(false), ++ disable_second_keyshare(false), + allow_unknown_alpn_protos(false), + false_start_allowed_without_alpn(false), + handoff(false), +@@ -527,6 +528,7 @@ SSL *SSL_new(SSL_CTX *ctx) { + ssl->config->retain_only_sha256_of_client_certs = + ctx->retain_only_sha256_of_client_certs; + ssl->config->permute_extensions = ctx->permute_extensions; ++ ssl->config->disable_second_keyshare = ctx->disable_second_keyshare; + ssl->config->aes_hw_override = ctx->aes_hw_override; + ssl->config->aes_hw_override_value = ctx->aes_hw_override_value; + ssl->config->compliance_policy = ctx->compliance_policy; +@@ -586,6 +588,7 @@ SSL_CONFIG::SSL_CONFIG(SSL *ssl_arg) + jdk11_workaround(false), + quic_use_legacy_codepoint(false), + permute_extensions(false), ++ disable_second_keyshare(false), + alps_use_new_codepoint(true) { + assert(ssl); + } +@@ -3331,6 +3334,15 @@ int SSL_set1_curves_list(SSL *ssl, const char *curves) { + return SSL_set1_groups_list(ssl, curves); + } + ++void SSL_use_second_keyshare(SSL *ssl, int enabled) { ++ ssl->config->disable_second_keyshare = !enabled; ++} ++ ++void SSL_CTX_use_second_keyshare(SSL_CTX *ctx, int enabled) { ++ ctx->disable_second_keyshare = !enabled; ++} ++ ++ + namespace fips202205 { + + // (References are to SP 800-52r2): +@@ -3342,7 +3354,9 @@ namespace fips202205 { // Section 3.3.1 // "The server shall be configured to only use cipher suites that are // composed entirely of NIST approved algorithms" --static const int kCurves[] = {NID_X9_62_prime256v1, NID_secp384r1}; -+static const int kCurves[] = {NID_P256Kyber768Draft00, NID_X9_62_prime256v1, NID_secp384r1}; +-static const uint16_t kGroups[] = {SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; ++static const uint16_t kGroups[] = { ++ SSL_GROUP_P256_KYBER768_DRAFT00, ++ SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; static const uint16_t kSigAlgs[] = { SSL_SIGN_RSA_PKCS1_SHA256, -diff --git a/src/ssl/ssl_test.cc b/src/ssl/ssl_test.cc -index ef43a9e98..22178b5f6 100644 ---- a/src/ssl/ssl_test.cc -+++ b/src/ssl/ssl_test.cc -@@ -409,7 +409,34 @@ static const CurveTest kCurveTests[] = { - "P-256:X25519Kyber768Draft00", - { SSL_CURVE_SECP256R1, SSL_CURVE_X25519_KYBER768_DRAFT00 }, - }, -- -+ { -+ "Xyber512D00", -+ { SSL_CURVE_X25519_KYBER512_DRAFT00 }, -+ }, -+ { -+ "Xyber768D00", -+ { SSL_CURVE_X25519_KYBER768_DRAFT00 }, -+ }, -+ { -+ "Xyber768D00:Xyber768D00Old", -+ { SSL_CURVE_X25519_KYBER768_DRAFT00, SSL_CURVE_X25519_KYBER768_DRAFT00_OLD }, -+ }, -+ { -+ "P-256:Xyber512D00", -+ { SSL_CURVE_SECP256R1, SSL_CURVE_X25519_KYBER512_DRAFT00 }, -+ }, -+ { -+ "P256Kyber768D00", -+ { SSL_CURVE_P256_KYBER768_DRAFT00 }, -+ }, -+ { -+ "X25519MLKEM768", -+ { SSL_CURVE_X25519_MLKEM768 }, -+ }, -+ { -+ "P-256:P256Kyber768D00", -+ { SSL_CURVE_SECP256R1, SSL_CURVE_P256_KYBER768_DRAFT00 }, -+ }, - { - "P-256:P-384:P-521:X25519", +diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc +index 779a2c37a..36a0cab3b 100644 +--- a/ssl/ssl_test.cc ++++ b/ssl/ssl_test.cc +@@ -506,6 +506,14 @@ static const CurveTest kCurveTests[] = { + "MLKEM1024:X25519MLKEM768", + {SSL_GROUP_MLKEM1024, SSL_GROUP_X25519_MLKEM768}, + }, ++ { ++ "P256Kyber768Draft00", ++ {SSL_GROUP_P256_KYBER768_DRAFT00}, ++ }, ++ { ++ "P-256:P256Kyber768Draft00", ++ {SSL_GROUP_SECP256R1, SSL_GROUP_P256_KYBER768_DRAFT00}, ++ }, + { -diff --git a/src/tool/speed.cc b/src/tool/speed.cc -index 5b0205953..6b3c67dab 100644 ---- a/src/tool/speed.cc -+++ b/src/tool/speed.cc -@@ -904,6 +904,116 @@ static bool SpeedScrypt(const std::string &selected) { - return true; - } + "P-256:P-384:P-521:X25519", +@@ -668,7 +676,9 @@ TEST(SSLTest, CurveRules) { + } + + TEST(SSLTest, DefaultCurves) { +- const uint16_t kDefaults[] = {SSL_GROUP_X25519, SSL_GROUP_SECP256R1, ++ const uint16_t kDefaults[] = {SSL_GROUP_X25519_MLKEM768, ++ SSL_GROUP_P256_KYBER768_DRAFT00, ++ SSL_GROUP_X25519, SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1}; + + // Test the group ID APIs. +@@ -1522,6 +1532,9 @@ static bool GetClientHello(SSL *ssl, std::vector *out) { + static size_t GetClientHelloLen(uint16_t max_version, uint16_t session_version, + size_t ticket_len) { + bssl::UniquePtr ctx(SSL_CTX_new(TLS_method())); ++ // RTG-3417 bas: we need to disable PQ here so that the small ClientHello ++ // padding tests properly tests things. ++ SSL_CTX_set1_curves_list(ctx.get(), "X25519"); + bssl::UniquePtr session = + CreateSessionWithTicket(session_version, ticket_len); + if (!ctx || !session) { +@@ -6815,7 +6828,9 @@ TEST(SSLTest, ApplyHandoffRemovesUnsupportedCurves) { + + // The default list of groups is used before applying the handoff. + EXPECT_THAT(server->config->supported_group_list, +- ElementsAreArray({SSL_GROUP_X25519, SSL_GROUP_SECP256R1, ++ ElementsAreArray({SSL_GROUP_X25519_MLKEM768, ++ SSL_GROUP_P256_KYBER768_DRAFT00, ++ SSL_GROUP_X25519, SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1})); + ASSERT_TRUE(SSL_apply_handoff(server.get(), handoff)); + EXPECT_EQ(1u, server->config->supported_group_list.size()); +diff --git a/ssl/test/runner/basic_tests.go b/ssl/test/runner/basic_tests.go +index 08de8fa5f..dd945fa49 100644 +--- a/ssl/test/runner/basic_tests.go ++++ b/ssl/test/runner/basic_tests.go +@@ -129,6 +129,7 @@ read alert 1 0 + `write hs 1 + read hs 3 + write hs 1 ++write hs 1 + read hs 2 + read hs 11 + read hs 12 +@@ -1956,6 +1957,7 @@ read alert 1 0 + write hs 2 + write hs 8 + write hs 11 ++write hs 11 + write hs 15 + write hs 20 + read hs 20 +diff --git a/ssl/test/runner/cbc_tests.go b/ssl/test/runner/cbc_tests.go +index 6f49d12af..5e970b2b5 100644 +--- a/ssl/test/runner/cbc_tests.go ++++ b/ssl/test/runner/cbc_tests.go +@@ -14,6 +14,8 @@ + + package runner + ++import "strconv" ++ + func addCBCPaddingTests() { + testCases = append(testCases, testCase{ + name: "MaxCBCPadding", +@@ -104,6 +106,7 @@ func addCBCSplittingTests() { + "-partial-write", + // BoringSSL disables 3DES by default. + "-cipher", "ALL:3DES", ++ "-curves", strconv.Itoa(int(CurveX25519)), + }, + }) + } +diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go +index 7dbde72c9..9d18d9d45 100644 +--- a/ssl/test/runner/common.go ++++ b/ssl/test/runner/common.go +@@ -2095,7 +2095,7 @@ type ProtocolBugs struct { + FailIfHelloRetryRequested bool + + // FailIfPostQuantumOffered will cause a server to reject a ClientHello if +- // post-quantum curves are supported. ++ // post-quantum curves are not supported. + FailIfPostQuantumOffered bool + + // ExpectKeyShares, if not nil, lists (in order) the curves that a ClientHello +diff --git a/ssl/test/runner/curve_tests.go b/ssl/test/runner/curve_tests.go +index 8e7b0a45b..556bf314d 100644 +--- a/ssl/test/runner/curve_tests.go ++++ b/ssl/test/runner/curve_tests.go +@@ -579,17 +579,6 @@ func addCurveTests() { + }) + } + +- // ML-KEM and Kyber should not be offered by default as a client. +- testCases = append(testCases, testCase{ +- name: "PostQuantumNotEnabledByDefaultInClients", +- config: Config{ +- MinVersion: VersionTLS13, +- Bugs: ProtocolBugs{ +- FailIfPostQuantumOffered: true, +- }, +- }, +- }) +- + for _, curve := range testCurves { + if !isMLKEMGroup(curve.id) { + continue +@@ -679,18 +668,19 @@ func addCurveTests() { + }) + } + +- // As a server, ML-KEMs and Kyber are not yet supported by default. ++ // If ML-KEM is offered, both X25519 and ML-KEM should have a key-share. + testCases = append(testCases, testCase{ +- testType: serverTest, +- name: "PostQuantumNotEnabledByDefaultForAServer", ++ name: "NotJustMLKEMKeyShare", + config: Config{ +- MinVersion: VersionTLS13, +- CurvePreferences: []CurveID{CurveX25519MLKEM768, CurveMLKEM1024, CurveX25519Kyber768, CurveX25519}, +- DefaultCurves: []CurveID{CurveX25519MLKEM768, CurveMLKEM1024, CurveX25519Kyber768}, ++ MinVersion: VersionTLS13, ++ Bugs: ProtocolBugs{ ++ ExpectedKeyShares: []CurveID{CurveX25519MLKEM768, CurveX25519}, ++ }, + }, + flags: []string{ +- "-server-preference", +- "-expect-curve-id", strconv.Itoa(int(CurveX25519)), ++ "-curves", strconv.Itoa(int(CurveX25519MLKEM768)), ++ "-curves", strconv.Itoa(int(CurveX25519)), ++ "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)), + }, + }) + +diff --git a/ssl/test/runner/ech_tests.go b/ssl/test/runner/ech_tests.go +index 2cd3c10d3..f19d8d20a 100644 +--- a/ssl/test/runner/ech_tests.go ++++ b/ssl/test/runner/ech_tests.go +@@ -451,7 +451,8 @@ func addEncryptedClientHelloTests() { + expectMsgCallback += clientAndServerHello + } + // EncryptedExtensions onwards. +- expectMsgCallback += `write hs 8 ++ if protocol != dtls { ++ expectMsgCallback += `write hs 8 + write hs 11 + write hs 15 + write hs 20 +@@ -462,6 +463,20 @@ write hs 4 + read ack + read ack + ` ++ } else { ++ expectMsgCallback += `write hs 8 ++write hs 11 ++write hs 11 ++write hs 15 ++write hs 20 ++read hs 20 ++write ack ++write hs 4 ++write hs 4 ++read ack ++read ack ++` ++ } + if protocol != dtls { + expectMsgCallback = strings.ReplaceAll(expectMsgCallback, "write ack\n", "") + expectMsgCallback = strings.ReplaceAll(expectMsgCallback, "read ack\n", "") +@@ -2349,8 +2364,11 @@ read ack + + // Test the message callback is correctly reported, with and without + // HelloRetryRequest. +- clientAndServerHello := "write clienthelloinner\nwrite hs 1\nread hs 2\n" +- clientAndServerHelloInitial := clientAndServerHello ++ clientAndServerHelloInitial := "write clienthelloinner\nwrite hs 1\nwrite hs 1\nread hs 2\n" ++ clientAndServerHello := "write clienthelloinner\nwrite hs 1\nread hs 2\n" ++ if protocol != dtls { ++ clientAndServerHelloInitial = clientAndServerHello ++ } + if protocol == tls { + clientAndServerHelloInitial += "write ccs\n" + } +diff --git a/ssl/test/runner/extension_tests.go b/ssl/test/runner/extension_tests.go +index d6adb7759..4eb80aa8e 100644 +--- a/ssl/test/runner/extension_tests.go ++++ b/ssl/test/runner/extension_tests.go +@@ -16,6 +16,7 @@ package runner + + import ( + "fmt" ++ "strconv" + ) + + func addExtensionTests() { +@@ -1967,7 +1968,7 @@ func addExtensionTests() { + // This hostname just needs to be long enough to push the + // ClientHello into F5's danger zone between 256 and 511 bytes + // long. +- flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com"}, ++ flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com", "-curves", strconv.Itoa(int(CurveX25519))}, + }) + + // Test that illegal extensions in TLS 1.3 are rejected by the client if +diff --git a/ssl/test/runner/key_update_tests.go b/ssl/test/runner/key_update_tests.go +index 0a9053038..5ce709589 100644 +--- a/ssl/test/runner/key_update_tests.go ++++ b/ssl/test/runner/key_update_tests.go +@@ -14,7 +14,10 @@ + + package runner + +-import "slices" ++import ( ++ "slices" ++ "strconv" ++) + + func addKeyUpdateTests() { + // TLS tests. +@@ -295,6 +298,7 @@ func addKeyUpdateTests() { + }, + }, + shimSendsKeyUpdateBeforeRead: true, ++ flags: []string{"-curves", strconv.Itoa(int(CurveX25519))}, + }) + + // Test that shim responds to KeyUpdate requests. +diff --git a/tool/client.cc b/tool/client.cc +index 0839d4880..be9b79259 100644 +--- a/tool/client.cc ++++ b/tool/client.cc +@@ -125,6 +125,11 @@ static const struct argument kArguments[] = { + kBooleanArgument, + "Permute extensions in handshake messages", + }, ++ { ++ "-disable-second-keyshare", ++ kBooleanArgument, ++ "Do not send a second keyshare", ++ }, + { + "-test-resumption", kBooleanArgument, + "Connect to the server twice. The first connection is closed once a " +@@ -538,6 +543,10 @@ bool Client(const std::vector &args) { + SSL_CTX_set_permute_extensions(ctx.get(), 1); + } -+static bool SpeedKyber768(const std::string &selected) { -+ if (!selected.empty() && selected != "Kyber768") { -+ return true; -+ } -+ -+ TimeResults results; -+ -+ if (!TimeFunction(&results, []() -> bool { -+ struct KYBER768_public_key pub; -+ struct KYBER768_private_key priv; -+ uint8_t entropy[KYBER_GENERATE_KEY_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ KYBER768_generate_key(&pub, &priv, entropy); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER768_generate_key.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber768 generate"); -+ -+ struct KYBER768_public_key pub; -+ struct KYBER768_private_key priv; -+ uint8_t key_entropy[KYBER_GENERATE_KEY_BYTES]; -+ RAND_bytes(key_entropy, sizeof(key_entropy)); -+ KYBER768_generate_key(&pub, &priv, key_entropy); -+ -+ uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; -+ if (!TimeFunction(&results, [&pub, &ciphertext]() -> bool { -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ uint8_t shared_key[KYBER_KEY_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ KYBER768_encap(ciphertext, shared_key, &pub, entropy, 0); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER768_encap.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber768 encap"); -+ -+ if (!TimeFunction(&results, [&priv, &ciphertext]() -> bool { -+ uint8_t shared_key[KYBER_KEY_BYTES]; -+ KYBER768_decap(shared_key, &priv, ciphertext, sizeof(ciphertext), 0); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER768_decap.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber768 decap"); -+ -+ return true; -+} -+ -+static bool SpeedKyber512(const std::string &selected) { -+ if (!selected.empty() && selected != "Kyber512") { -+ return true; -+ } -+ -+ TimeResults results; -+ -+ if (!TimeFunction(&results, []() -> bool { -+ struct KYBER512_public_key pub; -+ struct KYBER512_private_key priv; -+ uint8_t entropy[KYBER_GENERATE_KEY_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ KYBER512_generate_key(&pub, &priv, entropy); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER512_generate_key.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber512 generate"); -+ -+ struct KYBER512_public_key pub; -+ struct KYBER512_private_key priv; -+ uint8_t key_entropy[KYBER_GENERATE_KEY_BYTES]; -+ RAND_bytes(key_entropy, sizeof(key_entropy)); -+ KYBER512_generate_key(&pub, &priv, key_entropy); -+ -+ uint8_t ciphertext[KYBER512_CIPHERTEXT_BYTES]; -+ if (!TimeFunction(&results, [&pub, &ciphertext]() -> bool { -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ uint8_t shared_key[KYBER_KEY_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ KYBER512_encap(ciphertext, shared_key, &pub, entropy, 0); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER512_encap.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber512 encap"); -+ -+ if (!TimeFunction(&results, [&priv, &ciphertext]() -> bool { -+ uint8_t shared_key[KYBER_KEY_BYTES]; -+ KYBER512_decap(shared_key, &priv, ciphertext, sizeof(ciphertext), 0); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER512_decap.\n"); -+ return false; ++ if (args_map.count("-disable-second-keyshare") != 0) { ++ SSL_CTX_use_second_keyshare(ctx.get(), 0); + } + -+ results.Print("Kyber512 decap"); -+ -+ return true; -+} -+ - static bool SpeedHRSS(const std::string &selected) { - if (!selected.empty() && selected != "HRSS") { - return true; -@@ -958,55 +1068,6 @@ static bool SpeedHRSS(const std::string &selected) { - return true; - } - --static bool SpeedKyber(const std::string &selected) { -- if (!selected.empty() && selected != "Kyber") { -- return true; -- } -- -- TimeResults results; -- -- KYBER_private_key priv; -- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; -- uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]; -- // This ciphertext is nonsense, but Kyber decap is constant-time so, for the -- // purposes of timing, it's fine. -- memset(ciphertext, 42, sizeof(ciphertext)); -- if (!TimeFunction(&results, -- [&priv, &encoded_public_key, &ciphertext]() -> bool { -- uint8_t shared_secret[32]; -- KYBER_generate_key(encoded_public_key, &priv); -- KYBER_decap(shared_secret, sizeof(shared_secret), -- ciphertext, &priv); -- return true; -- })) { -- fprintf(stderr, "Failed to time KYBER_generate_key + KYBER_decap.\n"); -- return false; -- } -- -- results.Print("Kyber generate + decap"); -- -- KYBER_public_key pub; -- if (!TimeFunction( -- &results, [&pub, &ciphertext, &encoded_public_key]() -> bool { -- CBS encoded_public_key_cbs; -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- if (!KYBER_parse_public_key(&pub, &encoded_public_key_cbs)) { -- return false; -- } -- uint8_t shared_secret[32]; -- KYBER_encap(ciphertext, shared_secret, sizeof(shared_secret), &pub); -- return true; -- })) { -- fprintf(stderr, "Failed to time KYBER_encap.\n"); -- return false; -- } -- -- results.Print("Kyber parse + encap"); -- -- return true; --} -- - static bool SpeedHashToCurve(const std::string &selected) { - if (!selected.empty() && selected.find("hashtocurve") == std::string::npos) { - return true; -@@ -1487,7 +1548,8 @@ bool Speed(const std::vector &args) { - !SpeedScrypt(selected) || - !SpeedRSAKeyGen(selected) || - !SpeedHRSS(selected) || -- !SpeedKyber(selected) || -+ !SpeedKyber512(selected) || -+ !SpeedKyber768(selected) || - !SpeedHashToCurve(selected) || - !SpeedTrustToken("TrustToken-Exp1-Batch1", TRUST_TOKEN_experiment_v1(), 1, - selected) || + if (args_map.count("-root-certs") != 0) { + if (!SSL_CTX_load_verify_locations( + ctx.get(), args_map["-root-certs"].c_str(), nullptr)) { -- -2.46.0 +2.40.0 diff --git a/boring-sys/patches/rpk.patch b/boring-sys/patches/rpk.patch index bc2e3a8f6..512566e94 100644 --- a/boring-sys/patches/rpk.patch +++ b/boring-sys/patches/rpk.patch @@ -1,120 +1,146 @@ -diff --git a/src/include/openssl/ssl.h b/src/include/openssl/ssl.h -index 53aa9b453..87309c3e1 100644 ---- a/src/include/openssl/ssl.h -+++ b/src/include/openssl/ssl.h -@@ -138,6 +138,25 @@ - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. - */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #ifndef OPENSSL_HEADER_SSL_H - #define OPENSSL_HEADER_SSL_H -@@ -1136,6 +1155,16 @@ OPENSSL_EXPORT int SSL_CTX_set_chain_and_key( - SSL_CTX *ctx, CRYPTO_BUFFER *const *certs, size_t num_certs, - EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method); - -+// SSL_CTX_set_nullchain_and_key sets the private key for a -+// TLS client or server. Reference to the given |EVP_PKEY| -+// object is added as needed. Exactly one of |privkey| or |privkey_method| -+// may be non-NULL. Returns one on success and zero on error. -+// Note the lack of a corresponding public-key certificate. -+// See SSL_CTX_set_server_raw_public_key_certificate. -+OPENSSL_EXPORT int SSL_CTX_set_nullchain_and_key( -+ SSL_CTX *ctx, -+ EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method); -+ - // SSL_set_chain_and_key sets the certificate chain and private key for a TLS - // client or server. References to the given |CRYPTO_BUFFER| and |EVP_PKEY| - // objects are added as needed. Exactly one of |privkey| or |privkey_method| -@@ -1144,6 +1173,16 @@ OPENSSL_EXPORT int SSL_set_chain_and_key( - SSL *ssl, CRYPTO_BUFFER *const *certs, size_t num_certs, EVP_PKEY *privkey, - const SSL_PRIVATE_KEY_METHOD *privkey_method); - -+// SSL_set_nullchain_and_key sets the private key for a TLS -+// client or server. Reference to the given |EVP_PKEY| -+// object is added as needed. Exactly one of |privkey| or |privkey_method| -+// may be non-NULL. Returns one on success and zero on error. -+// Note the lack of a corresponding public-key certificate. -+// See SSL_set_server_raw_public_key_certificate. -+OPENSSL_EXPORT int SSL_set_nullchain_and_key( -+ SSL *ssl, EVP_PKEY *privkey, -+ const SSL_PRIVATE_KEY_METHOD *privkey_method); -+ - // SSL_CTX_get0_chain returns the list of |CRYPTO_BUFFER|s that were set by - // |SSL_CTX_set_chain_and_key|. Reference counts are not incremented by this - // call. The return value may be |NULL| if no chain has been set. -@@ -3023,6 +3062,21 @@ OPENSSL_EXPORT void SSL_get0_peer_application_settings(const SSL *ssl, - OPENSSL_EXPORT int SSL_has_application_settings(const SSL *ssl); - - -+// Server Certificate Type. -+ -+#define TLSEXT_CERTIFICATETYPE_X509 0 -+#define TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY 2 -+ -+OPENSSL_EXPORT int SSL_CTX_set_server_raw_public_key_certificate( -+ SSL_CTX *ctx, const uint8_t *raw_public_key, unsigned raw_public_key_len); -+ -+OPENSSL_EXPORT int SSL_CTX_has_server_raw_public_key_certificate(SSL_CTX *ctx); -+ -+OPENSSL_EXPORT int SSL_set_server_raw_public_key_certificate( -+ SSL *ssl, const uint8_t *raw_public_key, unsigned raw_public_key_len); -+ -+OPENSSL_EXPORT int SSL_has_server_raw_public_key_certificate(SSL *ssl); +From 9725dabfc86f57607e60e48e09e10615a05bb053 Mon Sep 17 00:00:00 2001 +From: Anthony Ramine +Date: Wed, 17 Dec 2025 13:27:50 +0100 +Subject: [PATCH] Implement support for raw public keys as server certificates + (RFC 7250) + +--- + crypto/err/ssl.errordata | 1 + + include/openssl/ssl.h | 65 +++++++++++++ + include/openssl/tls1.h | 3 + + ssl/extensions.cc | 122 +++++++++++++++++++++++ + ssl/internal.h | 18 ++++ + ssl/ssl_cert.cc | 8 ++ + ssl/ssl_credential.cc | 48 +++++++++ + ssl/ssl_lib.cc | 52 +++++++++- + ssl/ssl_test.cc | 67 +++++++++++++ + ssl/test/bssl_shim.cc | 3 +- + ssl/test/runner/certificate_tests.go | 134 +++++++++++++++++++++++++- + ssl/test/runner/common.go | 19 ++++ + ssl/test/runner/handshake_client.go | 71 +++++++++++++- + ssl/test/runner/handshake_messages.go | 33 ++++++- + ssl/test/runner/handshake_server.go | 53 +++++++--- + ssl/test/runner/runner.go | 3 +- + ssl/test/test_config.cc | 92 +++++++++++++++--- + ssl/test/test_config.h | 2 + + ssl/tls13_both.cc | 98 +++++++++++++------ + ssl/tls13_server.cc | 34 ++++++- + 20 files changed, 861 insertions(+), 65 deletions(-) + +diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata +index 01c4ca616..c9f4994d9 100644 +--- a/crypto/err/ssl.errordata ++++ b/crypto/err/ssl.errordata +@@ -95,6 +95,7 @@ SSL,159,INVALID_MESSAGE + SSL,320,INVALID_OUTER_EXTENSION + SSL,251,INVALID_OUTER_RECORD_TYPE + SSL,269,INVALID_SCT_LIST ++SSL,331,INVALID_SERVER_CERTIFICATE_TYPE_LIST + SSL,295,INVALID_SIGNATURE_ALGORITHM + SSL,324,INVALID_SPAKE2PLUSV1_VALUE + SSL,160,INVALID_SSL_SESSION +diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h +index ff68ba69e..5a1cf42ca 100644 +--- a/include/openssl/ssl.h ++++ b/include/openssl/ssl.h +@@ -1781,6 +1781,10 @@ OPENSSL_EXPORT STACK_OF(X509) *SSL_get_peer_full_cert_chain(const SSL *ssl); + OPENSSL_EXPORT const STACK_OF(CRYPTO_BUFFER) *SSL_get0_peer_certificates( + const SSL *ssl); + ++// SSL_get0_peer_pubkey returns the peer's public key during a handshake, or ++// NULL if unavailable. The caller does not take ownership of the result. ++OPENSSL_EXPORT const EVP_PKEY *SSL_get0_peer_pubkey(const SSL *ssl); ++ + // SSL_get0_signed_cert_timestamp_list sets |*out| and |*out_len| to point to + // |*out_len| bytes of SCT information from the server. This is only valid if + // |ssl| is a client. The SCT information is a SignedCertificateTimestampList +@@ -3406,6 +3410,49 @@ OPENSSL_EXPORT int SSL_has_application_settings(const SSL *ssl); + // codepoint. By default, the old codepoint is used. + OPENSSL_EXPORT void SSL_set_alps_use_new_codepoint(SSL *ssl, int use_new); + ++// Server Certificate Type (RFC 7250). ++// ++// The Server Certificate Type extension (RFC 7301) allows negotiating ++// different server certificate types. This is used, for example, to receive ++// a raw public key instead of a full-fedged X.509 certificate from a server. ++ ++// SSL_CTX_set_server_certificate_types sets the server certificate type list ++// on |ctx| to |types|. This is the list of certificate types that the client ++// is willing to receive from the server. |types| must be an array ++// of |TLS_CERTIFICATE_TYPE_*| values. Configuring a non-empty array enables ++// the server_certificate_type extension on a client. ++OPENSSL_EXPORT int SSL_CTX_set_server_certificate_types(SSL_CTX *ctx, ++ const uint8_t *types, ++ size_t types_len); ++ ++// SSL_CTX_get0_server_certificate_types returns the server certificate type ++// list configured on |ctx|. ++OPENSSL_EXPORT void SSL_CTX_get0_server_certificate_types(const SSL_CTX *ctx, ++ const uint8_t **types, ++ size_t *types_len); ++ ++// SSL_set_server_certificate_types sets the server certificate type list ++// on |ssl| to |types|. This is the list of certificate types that the client ++// is willing to receive from the server. |types| must be an array ++// of |TLS_CERTIFICATE_TYPE_*| values. Configuring a non-empty array enables ++// the server_certificate_type extension on a client. ++OPENSSL_EXPORT int SSL_set_server_certificate_types(SSL *ssl, ++ const uint8_t *types, ++ size_t types_len); ++ ++// SSL_get0_server_certificate_types returns the server certificate type list ++// configured on |ssl|. ++OPENSSL_EXPORT void SSL_get0_server_certificate_types(const SSL *ssl, ++ const uint8_t **types, ++ size_t *types_len); ++ ++// SSL_get_server_certificate_type_selected gets the selected server ++// certificate type from |ssl|. ++OPENSSL_EXPORT uint8_t SSL_get_server_certificate_type_selected(const SSL *ssl); ++ ++#define TLS_CERTIFICATE_TYPE_X509 0 ++#define TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY 2 + + // Certificate compression. // - // Certificates in TLS 1.3 can be compressed (RFC 8879). BoringSSL supports this -diff --git a/src/include/openssl/tls1.h b/src/include/openssl/tls1.h -index 772fb87a3..be605c1aa 100644 ---- a/src/include/openssl/tls1.h -+++ b/src/include/openssl/tls1.h -@@ -146,6 +146,25 @@ - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. - */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #ifndef OPENSSL_HEADER_TLS1_H - #define OPENSSL_HEADER_TLS1_H -@@ -197,6 +216,9 @@ extern "C" { +@@ -3759,6 +3806,23 @@ OPENSSL_EXPORT SSL_CREDENTIAL *SSL_CREDENTIAL_new_delegated(void); + OPENSSL_EXPORT int SSL_CREDENTIAL_set1_delegated_credential( + SSL_CREDENTIAL *cred, CRYPTO_BUFFER *dc); + ++// Raw Public Key Credentials ++ ++// SSL_CREDENTIAL_new_raw_public_key returns a new, empty Raw Public Key ++// credential, or NULL on error. Callers should release the result with ++// |SSL_CREDENTIAL_free| when done. ++// ++// Callers should configure a raw public key and a private key on the ++// credential, then add it with |SSL_CTX_add1_credential|. ++OPENSSL_EXPORT SSL_CREDENTIAL *SSL_CREDENTIAL_new_raw_public_key(void); ++ ++// SSL_CREDENTIAL_set1_spki |cred|'s raw public key from |spki|. ++// If |spki| is NULL, the public key is extracted from |cred|'s private key. ++// It returns one on success and zero on error, including if |spki| is ++// malformed or if it is NULL and |cred| has no private key. |spki| should ++// be a SubjectPublicKeyInfo structure, as described in RFC 5280. ++int SSL_CREDENTIAL_set1_spki(SSL_CREDENTIAL *cred, ++ CRYPTO_BUFFER *spki); + + // Password Authenticated Key Exchange (PAKE). + // +@@ -6569,6 +6633,7 @@ BSSL_NAMESPACE_END + #define SSL_R_INVALID_TRUST_ANCHOR_LIST 328 + #define SSL_R_INVALID_CERTIFICATE_PROPERTY_LIST 329 + #define SSL_R_DUPLICATE_GROUP 330 ++#define SSL_R_INVALID_SERVER_CERTIFICATE_TYPE_LIST 331 + #define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000 + #define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010 + #define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020 +diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h +index ea55e2a07..f55c0d441 100644 +--- a/include/openssl/tls1.h ++++ b/include/openssl/tls1.h +@@ -64,6 +64,9 @@ extern "C" { // ExtensionType value from RFC 7301 #define TLSEXT_TYPE_application_layer_protocol_negotiation 16 @@ -124,669 +150,1318 @@ index 772fb87a3..be605c1aa 100644 // ExtensionType value from RFC 7685 #define TLSEXT_TYPE_padding 21 -diff --git a/src/ssl/extensions.cc b/src/ssl/extensions.cc -index 5ee280221..2692e5478 100644 ---- a/src/ssl/extensions.cc -+++ b/src/ssl/extensions.cc -@@ -105,6 +105,25 @@ - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #include - -@@ -3094,6 +3113,146 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert, +diff --git a/ssl/extensions.cc b/ssl/extensions.cc +index c5f90688c..eb5ef58e8 100644 +--- a/ssl/extensions.cc ++++ b/ssl/extensions.cc +@@ -3523,6 +3523,121 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert, return true; } +// Server Certificate Type ++// ++// https://datatracker.ietf.org/doc/html/rfc7250#section-3 + -+static bool ext_server_certificate_type_add_clienthello(const SSL_HANDSHAKE *hs, -+ CBB *out, -+ CBB *out_compressible, -+ ssl_client_hello_type_t type) { -+ -+ if (hs->max_version <= TLS1_2_VERSION) { -+ return true; -+ } -+ -+ if (hs->config->server_certificate_type_list.empty()) { -+ return true; -+ } -+ -+ CBB contents, server_certificate_types; -+ if (!CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) || -+ !CBB_add_u16_length_prefixed(out, &contents) || -+ !CBB_add_u8_length_prefixed(&contents, &server_certificate_types) || -+ !CBB_add_bytes(&server_certificate_types, -+ hs->config->server_certificate_type_list.data(), -+ hs->config->server_certificate_type_list.size()) || -+ !CBB_flush(out)) { ++bool ssl_is_valid_certificate_type_list(Span in) { ++ CBS type_list = in; ++ if (CBS_len(&type_list) == 0) { + return false; + } -+ ++ uint8_t type; ++ while (CBS_get_u8(&type_list, &type)) { ++ switch (type) { ++ case TLS_CERTIFICATE_TYPE_X509: ++ case TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY: ++ break; ++ default: ++ return false; ++ } ++ } + return true; +} + -+static bool ssl_is_certificate_type_allowed(CBS *certificate_type_list, -+ uint8_t certificate_type) -+{ -+ uint8_t supported_certificate_type; -+ while (CBS_len(certificate_type_list) > 0) { -+ if (!CBS_get_u8(certificate_type_list, -+ &supported_certificate_type)) { -+ break; -+ } -+ -+ if (supported_certificate_type != certificate_type) { -+ continue; -+ } -+ ++static bool ext_server_certificate_type_add_clienthello( ++ const SSL_HANDSHAKE *hs, CBB *out, CBB *out_compressible, ++ ssl_client_hello_type_t type) { ++ if (hs->max_version < TLS1_3_VERSION || ++ hs->config->server_certificate_type_list.empty()) { + return true; + } + -+ return false; ++ CBB contents, type_list; ++ return CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) && ++ CBB_add_u16_length_prefixed(out, &contents) && ++ CBB_add_u8_length_prefixed(&contents, &type_list) && ++ CBB_add_bytes(&type_list, ++ hs->config->server_certificate_type_list.data(), ++ hs->config->server_certificate_type_list.size()) && ++ CBB_flush(out); +} + +static bool ext_server_certificate_type_parse_serverhello(SSL_HANDSHAKE *hs, + uint8_t *out_alert, -+ CBS *content) -+{ -+ if (hs->max_version <= TLS1_2_VERSION || -+ hs->config->server_certificate_type_list.empty()) { ++ CBS *contents) { ++ if (hs->ssl->s3->session_reused) { + return true; + } + -+ // Strict -+ if (!content) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; ++ uint8_t cert_type = TLS_CERTIFICATE_TYPE_X509; ++ if (contents != nullptr) { ++ assert(!hs->config->server_certificate_type_list.empty()); ++ if (!CBS_get_u8(contents, &cert_type) || CBS_len(contents) != 0) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); ++ goto err; ++ } + } + -+ CBS certificate_type_list = -+ MakeConstSpan(hs->config->server_certificate_type_list); -+ -+ uint8_t certificate_type; -+ if (CBS_get_u8(content, &certificate_type) && -+ ssl_is_certificate_type_allowed(&certificate_type_list, -+ certificate_type)) { -+ hs->server_certificate_type = certificate_type; -+ hs->server_certificate_type_negotiated = 1; -+ return true; ++ if (!hs->config->server_certificate_type_list.empty() && ++ std::none_of( ++ hs->config->server_certificate_type_list.begin(), ++ hs->config->server_certificate_type_list.end(), ++ [cert_type](const auto &type) { return type == cert_type; })) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ goto err; + } + -+ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ hs->server_certificate_type = cert_type; ++ return true; ++ ++err: + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + return false; +} + +static bool ext_server_certificate_type_parse_clienthello(SSL_HANDSHAKE *hs, + uint8_t *out_alert, -+ CBS *content) -+{ -+ if (!content) { -+ return true; -+ } -+ -+ if (hs->max_version <= TLS1_2_VERSION || -+ hs->config->server_certificate_type_list.empty()) { ++ CBS *contents) { ++ if (contents == nullptr || ssl_protocol_version(hs->ssl) < TLS1_3_VERSION) { + return true; + } + -+ CBS certificate_type_list = -+ MakeConstSpan(hs->config->server_certificate_type_list); -+ + CBS type_list; -+ if (!CBS_get_u8_length_prefixed(content, &type_list)) { -+ type_list.len = 0; ++ if (!CBS_get_u8_length_prefixed(contents, &type_list) || ++ CBS_len(contents) != 0 || CBS_len(&type_list) == 0) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); ++ *out_alert = SSL_AD_ILLEGAL_PARAMETER; ++ return false; + } + -+ uint8_t type; -+ while(CBS_len(&type_list) > 0) { -+ if (!CBS_get_u8(&type_list, &type)) { -+ break; -+ } -+ -+ if (!ssl_is_certificate_type_allowed(&certificate_type_list, type)) { -+ continue; -+ } -+ -+ hs->server_certificate_type = type; -+ hs->server_certificate_type_negotiated = 1; -+ return true; ++ if (!hs->server_certificate_type_list.CopyFrom(type_list)) { ++ *out_alert = SSL_AD_INTERNAL_ERROR; ++ return false; + } + -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; ++ return true; +} + +static bool ext_server_certificate_type_add_serverhello(SSL_HANDSHAKE *hs, -+ CBB *out) -+{ -+ if (!hs->server_certificate_type_negotiated) { ++ CBB *out) { ++ if (hs->ssl->s3->session_reused || hs->credential == nullptr || ++ hs->credential->type == SSLCredentialType::kX509) { + return true; + } + -+ CBB contents; -+ if (!CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) || -+ !CBB_add_u16_length_prefixed(out, &contents) || -+ !CBB_add_u8(&contents, hs->server_certificate_type) || -+ !CBB_flush(out)) { -+ return false; ++ if (hs->credential->type != SSLCredentialType::kRawPublicKey) { ++ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); ++ return false; + } + -+ return true; ++ CBB cert_types; ++ return CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) && ++ CBB_add_u16_length_prefixed(out, &cert_types) && ++ CBB_add_u8(&cert_types, TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY) && ++ CBB_flush(out); +} + // kExtensions contains all the supported extensions. static const struct tls_extension kExtensions[] = { - { -@@ -3267,6 +3426,13 @@ static const struct tls_extension kExtensions[] = { - ignore_parse_clienthello, - ext_alps_add_serverhello, - }, -+ { -+ TLSEXT_TYPE_server_certificate_type, -+ ext_server_certificate_type_add_clienthello, -+ ext_server_certificate_type_parse_serverhello, -+ ext_server_certificate_type_parse_clienthello, -+ ext_server_certificate_type_add_serverhello, -+ }, + { +@@ -3727,6 +3842,13 @@ static const struct tls_extension kExtensions[] = { + ext_trust_anchors_parse_clienthello, + ext_trust_anchors_add_serverhello, + }, ++ { ++ TLSEXT_TYPE_server_certificate_type, ++ ext_server_certificate_type_add_clienthello, ++ ext_server_certificate_type_parse_serverhello, ++ ext_server_certificate_type_parse_clienthello, ++ ext_server_certificate_type_add_serverhello, ++ }, }; #define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension)) -diff --git a/src/ssl/handshake.cc b/src/ssl/handshake.cc -index 8d5a23872..b9ac70dfe 100644 ---- a/src/ssl/handshake.cc -+++ b/src/ssl/handshake.cc -@@ -109,6 +109,25 @@ - * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. - * ECC cipher suite support in OpenSSL originally developed by - * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ +diff --git a/ssl/internal.h b/ssl/internal.h +index a69505b47..867c62bd6 100644 +--- a/ssl/internal.h ++++ b/ssl/internal.h +@@ -1408,6 +1408,7 @@ enum class SSLCredentialType { + kDelegated, + kSPAKE2PlusV1Client, + kSPAKE2PlusV1Server, ++ kRawPublicKey, + }; - #include + BSSL_NAMESPACE_END +@@ -2062,6 +2063,14 @@ struct SSL_HANDSHAKE { -@@ -150,6 +169,7 @@ SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg) - cert_compression_negotiated(false), -+ server_certificate_type_negotiated(false), - apply_jdk11_workaround(false), - can_release_private_key(false), - channel_id_negotiated(false) { - assert(ssl); - -@@ -365,7 +385,21 @@ enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs) { - - uint8_t alert = SSL_AD_CERTIFICATE_UNKNOWN; - enum ssl_verify_result_t ret; -- if (hs->config->custom_verify_callback != nullptr) { -+ if (hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) { -+ ret = ssl_verify_invalid; -+ EVP_PKEY *peer_pubkey = hs->peer_pubkey.get(); -+ CBS spki = MakeConstSpan(ssl->config->server_raw_public_key_certificate); -+ EVP_PKEY *pubkey = EVP_parse_public_key(&spki); -+ if (!pubkey) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); -+ alert = SSL_AD_INTERNAL_ERROR; -+ } else if (EVP_PKEY_cmp(peer_pubkey, pubkey) == 1 /* Equal */) { -+ ret = ssl_verify_ok; -+ } else { -+ alert = SSL_AD_BAD_CERTIFICATE; -+ } -+ } else if (hs->config->custom_verify_callback != nullptr) { - ret = hs->config->custom_verify_callback(ssl, &alert); - switch (ret) { - case ssl_verify_ok: -diff --git a/src/ssl/internal.h b/src/ssl/internal.h -index 1e6da2153..f04888384 100644 ---- a/src/ssl/internal.h -+++ b/src/ssl/internal.h -@@ -138,6 +138,25 @@ - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. - */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #ifndef OPENSSL_HEADER_SSL_INTERNAL_H - #define OPENSSL_HEADER_SSL_INTERNAL_H -@@ -1286,6 +1305,8 @@ int ssl_write_buffer_flush(SSL *ssl); - // configured. - bool ssl_has_certificate(const SSL_HANDSHAKE *hs); - -+bool ssl_has_raw_public_key_certificate(const SSL_HANDSHAKE *hs); -+ - // ssl_parse_cert_chain parses a certificate list from |cbs| in the format used - // by a TLS Certificate message. On success, it advances |cbs| and returns - // true. Otherwise, it returns false and sets |*out_alert| to an alert to send -@@ -1887,6 +1908,8 @@ struct SSL_HANDSHAKE { - // |cert_compression_negotiated| is true. - uint16_t cert_compression_alg_id; - -+ uint8_t server_certificate_type; -+ - // ech_hpke_ctx is the HPKE context used in ECH. On the server, it is - // initialized if |ech_status| is |ssl_ech_accepted|. On the client, it is - // initialized if |selected_ech_config| is not nullptr. -@@ -2037,6 +2060,8 @@ struct SSL_HANDSHAKE { - // cert_compression_negotiated is true iff |cert_compression_alg_id| is valid. - bool cert_compression_negotiated : 1; - -+ bool server_certificate_type_negotiated : 1; -+ - // apply_jdk11_workaround is true if the peer is probably a JDK 11 client - // which implemented TLS 1.3 incorrectly. - bool apply_jdk11_workaround : 1; -@@ -3049,6 +3074,9 @@ struct SSL_CONFIG { - // along with their corresponding ALPS values. - GrowableArray alps_configs; + // pake_verifier is the PAKE context for a server. + UniquePtr pake_verifier; ++ ++ // server_certificate_type_list indicates the types of certificates ++ // the client is able to process. ++ Array server_certificate_type_list; ++ ++ // server_certificate_type indicates the type of certificates the server ++ // selected to send as the certificate payload. ++ uint8_t server_certificate_type = TLS_CERTIFICATE_TYPE_X509; + }; + + // kMaxTickets is the maximum number of tickets to send immediately after the +@@ -2256,6 +2265,10 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert, + // identifiers list. + bool ssl_is_valid_trust_anchor_list(Span in); + ++// ssl_is_valid_certificate_type_list returns whether |in| is a valid ++// certificate type list. ++bool ssl_is_valid_certificate_type_list(Span in); ++ + struct SSLExtension { + SSLExtension(uint16_t type_arg, bool allowed_arg = true) + : type(type_arg), allowed(allowed_arg), present(false) { +@@ -3339,6 +3352,8 @@ struct SSL_CONFIG { + // negotiating a TLS 1.3 connection. + enum ssl_compliance_policy_t compliance_policy = ssl_compliance_policy_none; + Array server_certificate_type_list; -+ Array server_raw_public_key_certificate; + - // Contains the QUIC transport params that this endpoint will send. - Array quic_transport_params; + // verify_mode is a bitmask of |SSL_VERIFY_*| values. + uint8_t verify_mode = SSL_VERIFY_NONE; -@@ -3648,6 +3676,9 @@ struct ssl_ctx_st { - // format. - bssl::Array alpn_client_proto_list; +@@ -3988,6 +4003,9 @@ struct ssl_ctx_st : public bssl::RefCounted { + // accepted from the peer in decreasing order of preference. + bssl::Array verify_sigalgs; ++ // For a client, this contains the list of supported server certificate types. + bssl::Array server_certificate_type_list; -+ bssl::Array server_raw_public_key_certificate; -+ - // SRTP profiles we are willing to do from RFC 5764 - bssl::UniquePtr srtp_profiles; - -diff --git a/src/ssl/ssl_cert.cc b/src/ssl/ssl_cert.cc -index aa46a8bb6..d90840fce 100644 ---- a/src/ssl/ssl_cert.cc -+++ b/src/ssl/ssl_cert.cc -@@ -111,6 +111,25 @@ - * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. - * ECC cipher suite support in OpenSSL originally developed by - * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ ++ + // retain_only_sha256_of_client_certs is true if we should compute the SHA256 + // hash of the peer's certificate and then discard it to save memory and + // session space. Only effective on the server side. +diff --git a/ssl/ssl_cert.cc b/ssl/ssl_cert.cc +index 72218aeea..a1a8f328b 100644 +--- a/ssl/ssl_cert.cc ++++ b/ssl/ssl_cert.cc +@@ -587,6 +587,14 @@ const STACK_OF(CRYPTO_BUFFER) *SSL_get0_peer_certificates(const SSL *ssl) { + return session->certs.get(); + } - #include ++const EVP_PKEY *SSL_get0_peer_pubkey(const SSL *ssl) { ++ if (ssl->s3->hs == nullptr) { ++ return nullptr; ++ } ++ ++ return ssl->s3->hs->peer_pubkey.get(); ++} ++ + const STACK_OF(CRYPTO_BUFFER) *SSL_get0_server_requested_CAs(const SSL *ssl) { + if (ssl->s3->hs == nullptr) { + return nullptr; +diff --git a/ssl/ssl_credential.cc b/ssl/ssl_credential.cc +index bbbd76701..16a069c28 100644 +--- a/ssl/ssl_credential.cc ++++ b/ssl/ssl_credential.cc +@@ -164,6 +164,7 @@ bool ssl_credential_st::UsesX509() const { + return true; + case SSLCredentialType::kSPAKE2PlusV1Client: + case SSLCredentialType::kSPAKE2PlusV1Server: ++ case SSLCredentialType::kRawPublicKey: + return false; + } + abort(); +@@ -173,6 +174,7 @@ bool ssl_credential_st::UsesPrivateKey() const { + switch (type) { + case SSLCredentialType::kX509: + case SSLCredentialType::kDelegated: ++ case SSLCredentialType::kRawPublicKey: + return true; + case SSLCredentialType::kSPAKE2PlusV1Client: + case SSLCredentialType::kSPAKE2PlusV1Server: +@@ -335,6 +337,10 @@ SSL_CREDENTIAL *SSL_CREDENTIAL_new_delegated(void) { + return New(SSLCredentialType::kDelegated); + } -@@ -302,6 +321,25 @@ static int cert_set_chain_and_key( ++SSL_CREDENTIAL *SSL_CREDENTIAL_new_raw_public_key(void) { ++ return New(SSLCredentialType::kRawPublicKey); ++} ++ + void SSL_CREDENTIAL_up_ref(SSL_CREDENTIAL *cred) { cred->UpRefInternal(); } + + void SSL_CREDENTIAL_free(SSL_CREDENTIAL *cred) { +@@ -448,6 +454,44 @@ int SSL_CREDENTIAL_set1_delegated_credential(SSL_CREDENTIAL *cred, return 1; } -+static int cert_set_key( -+ CERT *cert, -+ EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method) { -+ if (privkey == NULL && privkey_method == NULL) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_PASSED_NULL_PARAMETER); ++int SSL_CREDENTIAL_set1_spki(SSL_CREDENTIAL *cred, CRYPTO_BUFFER *spki) { ++ if (cred->type != SSLCredentialType::kRawPublicKey) { ++ OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); + return 0; + } + -+ if (privkey != NULL && privkey_method != NULL) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_CANNOT_HAVE_BOTH_PRIVKEY_AND_METHOD); ++ ScopedCBB cbb; ++ CBS cbs; ++ if (spki == nullptr) { ++ if (cred->privkey == nullptr) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_NO_PRIVATE_KEY_ASSIGNED); ++ return 0; ++ } ++ ++ if (!CBB_init(cbb.get(), /*initial_capacity=*/512) || ++ !EVP_marshal_public_key(cbb.get(), cred->privkey.get())) { ++ return 0; ++ } ++ CBS_init(&cbs, CBB_data(cbb.get()), CBB_len(cbb.get())); ++ } else { ++ CRYPTO_BUFFER_init_CBS(spki, &cbs); ++ } ++ ++ bssl::UniquePtr pubkey = ++ ssl_parse_peer_subject_public_key_info(cbs); ++ if (pubkey == nullptr) { + return 0; + } + -+ cert->privatekey = UpRef(privkey); -+ cert->key_method = privkey_method; ++ if (cred->privkey != nullptr && ++ !ssl_compare_public_and_private_key(pubkey.get(), cred->privkey.get())) { ++ return 0; ++ } + ++ cred->pubkey = std::move(pubkey); + return 1; +} + - bool ssl_set_cert(CERT *cert, UniquePtr buffer) { - switch (check_leaf_cert_and_privkey(buffer.get(), cert->privatekey.get())) { - case leaf_cert_and_privkey_error: -@@ -343,6 +381,12 @@ bool ssl_has_certificate(const SSL_HANDSHAKE *hs) { - ssl_has_private_key(hs); + int SSL_CREDENTIAL_set1_ocsp_response(SSL_CREDENTIAL *cred, + CRYPTO_BUFFER *ocsp) { + if (!cred->UsesX509()) { +@@ -611,6 +655,10 @@ void *SSL_CREDENTIAL_get_ex_data(const SSL_CREDENTIAL *cred, int idx) { } -+bool ssl_has_raw_public_key_certificate(const SSL_HANDSHAKE *hs) { -+ return hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY && -+ ssl_has_private_key(hs); -+} + void SSL_CREDENTIAL_set_must_match_issuer(SSL_CREDENTIAL *cred, int match) { ++ if (cred->type == SSLCredentialType::kRawPublicKey) { ++ return; ++ } + - bool ssl_parse_cert_chain(uint8_t *out_alert, - UniquePtr *out_chain, - UniquePtr *out_pubkey, -@@ -721,11 +765,20 @@ bool ssl_check_leaf_certificate(SSL_HANDSHAKE *hs, EVP_PKEY *pkey, - - bool ssl_on_certificate_selected(SSL_HANDSHAKE *hs) { - SSL *const ssl = hs->ssl; -- if (!ssl_has_certificate(hs)) { -+ if (!ssl_has_certificate(hs) && -+ !ssl_has_raw_public_key_certificate(hs)) { - // Nothing to do. - return true; + cred->must_match_issuer = !!match; + } + +diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc +index f64b103fb..d87c3f3c4 100644 +--- a/ssl/ssl_lib.cc ++++ b/ssl/ssl_lib.cc +@@ -534,7 +534,9 @@ SSL *SSL_new(SSL_CTX *ctx) { + if (!ssl->config->supported_group_list.CopyFrom(ctx->supported_group_list) || + !ssl->config->alpn_client_proto_list.CopyFrom( + ctx->alpn_client_proto_list) || +- !ssl->config->verify_sigalgs.CopyFrom(ctx->verify_sigalgs)) { ++ !ssl->config->verify_sigalgs.CopyFrom(ctx->verify_sigalgs) || ++ !ssl->config->server_certificate_type_list.CopyFrom( ++ ctx->server_certificate_type_list)) { + return nullptr; } -+ if (ssl_has_raw_public_key_certificate(hs)) { -+ CBS spki = MakeConstSpan( -+ ssl->config->server_raw_public_key_certificate.data(), -+ ssl->config->server_raw_public_key_certificate.size()); -+ hs->local_pubkey = UniquePtr(EVP_parse_public_key(&spki)); -+ return hs->local_pubkey != NULL; +@@ -3305,6 +3307,54 @@ int SSL_CTX_set_tlsext_status_arg(SSL_CTX *ctx, void *arg) { + return 1; + } + ++int SSL_CTX_set_server_certificate_types(SSL_CTX *ctx, const uint8_t *types, ++ size_t types_len) { ++ auto span = Span(types, types_len); ++ if (!span.empty() && !ssl_is_valid_certificate_type_list(span)) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SERVER_CERTIFICATE_TYPE_LIST); ++ return 0; + } ++ return ctx->server_certificate_type_list.CopyFrom(span); ++} + - if (!ssl->ctx->x509_method->ssl_auto_chain_if_needed(hs)) { - return false; - } -@@ -880,6 +933,15 @@ int SSL_set_chain_and_key(SSL *ssl, CRYPTO_BUFFER *const *certs, - privkey, privkey_method); ++void SSL_CTX_get0_server_certificate_types(const SSL_CTX *ctx, ++ const uint8_t **types, ++ size_t *types_len) { ++ *types = ctx->server_certificate_type_list.data(); ++ *types_len = ctx->server_certificate_type_list.size(); ++} ++ ++int SSL_set_server_certificate_types(SSL *ssl, const uint8_t *types, ++ size_t types_len) { ++ if (ssl->server || ssl->config == nullptr) { ++ return 0; ++ } ++ auto span = Span(types, types_len); ++ if (!span.empty() && !ssl_is_valid_certificate_type_list(span)) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SERVER_CERTIFICATE_TYPE_LIST); ++ return 0; ++ } ++ return ssl->config->server_certificate_type_list.CopyFrom(span); ++} ++ ++void SSL_get0_server_certificate_types(const SSL *ssl, const uint8_t **types, ++ size_t *types_len) { ++ if (ssl->server || ssl->config == nullptr) { ++ *types = nullptr; ++ *types_len = 0; ++ return; ++ } ++ *types = ssl->config->server_certificate_type_list.data(); ++ *types_len = ssl->config->server_certificate_type_list.size(); ++} ++ ++uint8_t SSL_get_server_certificate_type_selected(const SSL *ssl) { ++ if (ssl->s3->hs == nullptr) { ++ return TLS_CERTIFICATE_TYPE_X509; ++ } ++ return ssl->s3->hs->server_certificate_type; ++} ++ + uint16_t SSL_get_curve_id(const SSL *ssl) { return SSL_get_group_id(ssl); } + + const char *SSL_get_curve_name(uint16_t curve_id) { +diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc +index 779a2c37a..85aeb817f 100644 +--- a/ssl/ssl_test.cc ++++ b/ssl/ssl_test.cc +@@ -4398,6 +4398,73 @@ TEST_P(SSLVersionTest, DefaultTicketKeyRotation) { + new_session.get(), true /* reused */)); } -+int SSL_set_nullchain_and_key(SSL *ssl, -+ EVP_PKEY *privkey, -+ const SSL_PRIVATE_KEY_METHOD *privkey_method) { -+ if (!ssl->config) { -+ return 0; ++TEST_P(SSLVersionTest, RawPublicKeyCertificate) { ++ static const uint8_t kCertificateTypes[] = { ++ TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY, 18}; ++ ASSERT_TRUE(SSL_CTX_set_server_certificate_types(client_ctx_.get(), ++ kCertificateTypes, 0)); ++ ASSERT_FALSE(SSL_CTX_set_server_certificate_types(client_ctx_.get(), ++ kCertificateTypes, 2)); ++ ASSERT_TRUE(SSL_CTX_set_server_certificate_types(client_ctx_.get(), ++ kCertificateTypes, 1)); ++ ++ SSL_CTX_set_custom_verify( ++ client_ctx_.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, ++ [](SSL *ssl, uint8_t *out_alert) -> ssl_verify_result_t { ++ EXPECT_EQ(SSL_get_server_certificate_type_selected(ssl), ++ TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); ++ ++ const EVP_PKEY *peer_pubkey = SSL_get0_peer_pubkey(ssl); ++ EXPECT_TRUE(peer_pubkey); ++ ++ if (!peer_pubkey) { ++ *out_alert = SSL_AD_CERTIFICATE_UNKNOWN; ++ return ssl_verify_invalid; ++ } ++ ++ SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); ++ if (EVP_PKEY_cmp(reinterpret_cast SSL_CTX_get_app_data(ctx), ++ peer_pubkey) == 1) { ++ return ssl_verify_ok; ++ } ++ ++ *out_alert = SSL_AD_BAD_CERTIFICATE; ++ return ssl_verify_invalid; ++ }); ++ SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_CLIENT); ++ ++ // Server is not configured for raw public keys. ++ ASSERT_FALSE(Connect()); ++ ++ bssl::UniquePtr cred(SSL_CREDENTIAL_new_raw_public_key()); ++ bssl::UniquePtr key = GetECDSATestKey(); ++ ASSERT_TRUE(SSL_CREDENTIAL_set1_private_key(cred.get(), key.get())); ++ ASSERT_FALSE(SSL_CTX_add1_credential(server_ctx_.get(), cred.get())); ++ ASSERT_TRUE(SSL_CREDENTIAL_set1_spki(cred.get(), nullptr)); ++ ASSERT_TRUE(SSL_CTX_add1_credential(server_ctx_.get(), cred.get())); ++ ++ // Client is expecting |wrong_key|. ++ bssl::UniquePtr wrong_key = GetTestKey(); ++ ASSERT_TRUE(wrong_key); ++ SSL_CTX_set_app_data(client_ctx_.get(), wrong_key.get()); ++ ASSERT_FALSE(Connect()); ++ ++ if (!is_tls13()) { ++ return; + } -+ return cert_set_key(ssl->config->cert.get(), privkey, privkey_method); ++ ++ SSL_CTX_set_app_data(client_ctx_.get(), key.get()); ++ ASSERT_TRUE(Connect()); ++ ++ bssl::UniquePtr session = ++ CreateClientSession(client_ctx_.get(), server_ctx_.get()); ++ ASSERT_TRUE(session); ++ ++ TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(), ++ session.get(), ++ true /* expect session reused */)); +} + - int SSL_CTX_set_chain_and_key(SSL_CTX *ctx, CRYPTO_BUFFER *const *certs, - size_t num_certs, EVP_PKEY *privkey, - const SSL_PRIVATE_KEY_METHOD *privkey_method) { -@@ -887,6 +949,12 @@ int SSL_CTX_set_chain_and_key(SSL_CTX *ctx, CRYPTO_BUFFER *const *certs, - privkey_method); + static int SwitchContext(SSL *ssl, int *out_alert, void *arg) { + SSL_CTX *ctx = reinterpret_cast(arg); + SSL_set_SSL_CTX(ssl, ctx); +diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc +index 0eba60d22..9e2fe8da9 100644 +--- a/ssl/test/bssl_shim.cc ++++ b/ssl/test/bssl_shim.cc +@@ -672,7 +672,8 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume, + return false; + } + } else if (!config->is_server || config->require_any_client_certificate) { +- if (SSL_get_peer_cert_chain(ssl) == nullptr) { ++ if (!config->raw_public_key_mode && ++ SSL_get_peer_cert_chain(ssl) == nullptr) { + fprintf(stderr, "Received no peer certificate but expected one.\n"); + return false; + } +diff --git a/ssl/test/runner/certificate_tests.go b/ssl/test/runner/certificate_tests.go +index 7f6c4b82d..0007f9048 100644 +--- a/ssl/test/runner/certificate_tests.go ++++ b/ssl/test/runner/certificate_tests.go +@@ -14,7 +14,11 @@ + + package runner + +-import "crypto/x509" ++import ( ++ "crypto/x509" ++ "encoding/base64" ++ "strconv" ++) + + func makeCertPoolFromRoots(creds ...*Credential) *x509.CertPool { + certPool := x509.NewCertPool() +@@ -327,6 +331,134 @@ func addCertificateTests() { + } } -+int SSL_CTX_set_nullchain_and_key(SSL_CTX *ctx, -+ EVP_PKEY *privkey, -+ const SSL_PRIVATE_KEY_METHOD *privkey_method) { -+ return cert_set_key(ctx->cert.get(), privkey, privkey_method); ++func addRawPublicKeyCertificateTests() { ++ const decodeError = ":DECODE_ERROR:" ++ const unknownCertType = ":UNKNOWN_CERTIFICATE_TYPE:" ++ var extValueTests = []struct { ++ serverCertificateTypes []uint8 ++ expectedError string ++ }{ ++ // Explicitly requesting X.509 should be fine. ++ {[]uint8{certificateTypeX509}, ""}, ++ // ... even when mixed with unknown types. ++ {[]uint8{80, certificateTypeX509, 81, 82}, ""}, ++ // ... even when mixed with a request for raw public keys. ++ {[]uint8{certificateTypeRawPublicKey, certificateTypeX509, 81, 82}, ""}, ++ {[]uint8{certificateTypeX509, certificateTypeRawPublicKey, 81, 82}, ""}, ++ // Requesting only unknown certificate types should cause an error. ++ {[]uint8{80, 81, 82}, unknownCertType}, ++ // ... as should requesting a raw public key when the server is configured ++ // for X.509. ++ {[]uint8{certificateTypeRawPublicKey}, unknownCertType}, ++ // Listing no types is an error. ++ {[]uint8{}, decodeError}, ++ } ++ ++ for i, test := range extValueTests { ++ testCases = append(testCases, testCase{ ++ testType: serverTest, ++ name: "RawPublicKey-Server-ExtValue-" + strconv.Itoa(i), ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ Bugs: ProtocolBugs{ ++ ServerCertificateTypes: test.serverCertificateTypes, ++ }, ++ }, ++ shouldFail: len(test.expectedError) != 0, ++ expectedError: test.expectedError, ++ }) ++ } ++ ++ // An X.509 client should be rejected by a raw-public-key server. ++ testCases = append(testCases, testCase{ ++ testType: serverTest, ++ name: "RawPublicKey-Server-TLS13X509Client", ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ }, ++ flags: []string{ ++ "-raw-public-key-mode", ++ }, ++ shouldFail: true, ++ expectedError: unknownCertType, ++ }) ++ ++ testCases = append(testCases, testCase{ ++ testType: serverTest, ++ name: "RawPublicKey-Server", ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ Credential: ecdsaP384Certificate.WithSignatureAlgorithms(signatureECDSAWithP384AndSHA384), ++ useServerRawPublicKeyCertificate: true, ++ }, ++ flags: []string{ ++ "-raw-public-key-mode", ++ }, ++ shimCertificate: &ecdsaP384Certificate, ++ }) ++ ++ leaf, _ := x509.ParseCertificate(ecdsaP384Certificate.Certificate[0]) ++ base64SPKI := base64.StdEncoding.EncodeToString(leaf.RawSubjectPublicKeyInfo) ++ wrongLeaf, _ := x509.ParseCertificate(ecdsaP256Certificate.Certificate[0]) ++ wrongBase64SPKI := base64.StdEncoding.EncodeToString(wrongLeaf.RawSubjectPublicKeyInfo) ++ ++ for _, ok := range []bool{false, true} { ++ expectedSPKI, suffix, expectedError := base64SPKI, "", "" ++ if !ok { ++ expectedSPKI = wrongBase64SPKI ++ suffix = "-Mismatch" ++ expectedError = ":CERTIFICATE_VERIFY_FAILED:" ++ } ++ ++ testCases = append(testCases, testCase{ ++ testType: clientTest, ++ name: "RawPublicKey-Client" + suffix, ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ Credential: ecdsaP384Certificate.WithSignatureAlgorithms(signatureECDSAWithP384AndSHA384), ++ useServerRawPublicKeyCertificate: true, ++ }, ++ flags: []string{ ++ "-raw-public-key-mode", ++ "-verify-peer", ++ "-use-custom-verify-callback", ++ "-expect-spki", expectedSPKI, ++ }, ++ shouldFail: !ok, ++ expectedError: expectedError, ++ }) ++ } ++ ++ // Read the server's raw public key in a CompressedCertificate message. ++ testCases = append(testCases, testCase{ ++ testType: clientTest, ++ name: "RawPublicKey-Client-With-Compression", ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ Credential: ecdsaP384Certificate.WithSignatureAlgorithms(signatureECDSAWithP384AndSHA384), ++ useServerRawPublicKeyCertificate: true, ++ CertCompressionAlgs: map[uint16]CertCompressionAlg{ ++ expandingCompressionAlgID: expandingCompression, ++ }, ++ Bugs: ProtocolBugs{ ++ ExpectedCompressedCert: expandingCompressionAlgID, ++ }, ++ }, ++ flags: []string{ ++ "-raw-public-key-mode", ++ "-verify-peer", ++ "-use-custom-verify-callback", ++ "-expect-spki", base64SPKI, ++ "-install-cert-compression-algs", ++ }, ++ }) +} + - const STACK_OF(CRYPTO_BUFFER)* SSL_CTX_get0_chain(const SSL_CTX *ctx) { - return ctx->cert->chain.get(); + func addRetainOnlySHA256ClientCertTests() { + for _, ver := range tlsVersions { + // Test that enabling +diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go +index 7dbde72c9..f1fb623ea 100644 +--- a/ssl/test/runner/common.go ++++ b/ssl/test/runner/common.go +@@ -140,6 +140,12 @@ func messageTypeToString(typ uint8) string { + return fmt.Sprintf("unknown(%d)", typ) } -diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc -index 838761af5..e4f1a12b7 100644 ---- a/src/ssl/ssl_lib.cc -+++ b/src/ssl/ssl_lib.cc -@@ -137,6 +137,25 @@ - * SPECIFICALLY DISCLAIMS ANY LIABILITY FOR CLAIMS BROUGHT BY YOU OR ANY - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - #include ++// TLS certificate type extension values. ++const ( ++ certificateTypeX509 uint8 = 0 ++ certificateTypeRawPublicKey uint8 = 2 ++) ++ + // TLS compression types. + const ( + compressionNone uint8 = 0 +@@ -155,6 +161,7 @@ const ( + extensionUseSRTP uint16 = 14 + extensionALPN uint16 = 16 + extensionSignedCertificateTimestamp uint16 = 18 ++ extensionServerCertificateType uint16 = 20 // RFC7250 + extensionPadding uint16 = 21 + extensionExtendedMasterSecret uint16 = 23 + extensionCompressedCertAlgs uint16 = 27 +@@ -505,6 +512,14 @@ type Config struct { + // If Time is nil, TLS uses time.Now. + Time func() time.Time -@@ -687,6 +706,11 @@ SSL *SSL_new(SSL_CTX *ctx) { - ssl->config->handoff = ctx->handoff; - ssl->quic_method = ctx->quic_method; ++ // useServerRawPublicKeyCertificate indicates, for TLS 1.3 only, that raw ++ // public keys should be used. For servers, the DER-encoded X.509 ++ // SubjectPublicKeyInfo field of Certificates[0].Certificate[0] will be the ++ // CertificateEntry of Certificate messages, not including any ++ // CertificateEntry extensions. For clients, the field should be used to ++ // verify the server's Certificate message. ++ useServerRawPublicKeyCertificate bool ++ + // Credential contains the credential to present to the other side of + // the connection. Server configurations must include this field. + Credential *Credential +@@ -2172,6 +2187,10 @@ type ProtocolBugs struct { + // NewSessionTicket messages to have or not have the resumption_across_names + // flag set. + ExpectResumptionAcrossNames *bool ++ ++ // ServerCertificateTypes, if not nil, contains the contents of the server ++ // certificate types extension sent by a client, or echoed by a server. ++ ServerCertificateTypes []uint8 + } -+ ssl->config->server_certificate_type_list.CopyFrom( -+ ctx->server_certificate_type_list); -+ ssl->config->server_raw_public_key_certificate.CopyFrom( -+ ctx->server_raw_public_key_certificate); + func (c *Config) serverInit() { +diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go +index 1c4610815..cce76f47b 100644 +--- a/ssl/test/runner/handshake_client.go ++++ b/ssl/test/runner/handshake_client.go +@@ -557,6 +557,14 @@ func (hs *clientHandshakeState) createClientHello(innerHello *clientHelloMsg, ec + hello.vers = mapClientHelloVersion(maxVersion, c.isDTLS) + } + ++ if maxVersion >= VersionTLS13 && c.config.useServerRawPublicKeyCertificate { ++ hello.serverCertificateTypes = []uint8{certificateTypeRawPublicKey} ++ } + - if (!ssl->method->ssl_new(ssl.get()) || - !ssl->ctx->x509_method->ssl_new(ssl->s3->hs.get())) { - return nullptr; -@@ -3140,6 +3164,53 @@ int SSL_CTX_set_tlsext_status_arg(SSL_CTX *ctx, void *arg) { - return 1; ++ if c.config.Bugs.ServerCertificateTypes != nil { ++ hello.serverCertificateTypes = c.config.Bugs.ServerCertificateTypes ++ } ++ + if c.config.Bugs.SendClientVersion != 0 { + hello.vers = c.config.Bugs.SendClientVersion + } +@@ -1345,11 +1353,22 @@ func (hs *clientHandshakeState) doTLS13Handshake(msg any) error { + return errors.New("tls: server certificate unexpectedly did not match trust anchor") + } + +- if err := hs.verifyCertificates(certMsg); err != nil { +- return err ++ ex := encryptedExtensions.extensions ++ if c.config.useServerRawPublicKeyCertificate { ++ if !ex.hasServerCertificateType || ex.serverCertificateType != certificateTypeRawPublicKey { ++ c.sendAlert(alertUnsupportedCertificate) ++ return errors.New("tls: server did not support raw public keys") ++ } ++ if err := hs.verifyRawPublicKeyCertificates(certMsg); err != nil { ++ return err ++ } ++ } else { ++ if err := hs.verifyCertificates(certMsg); err != nil { ++ return err ++ } ++ c.ocspResponse = certMsg.certificates[0].ocspResponse ++ c.sctList = certMsg.certificates[0].sctList + } +- c.ocspResponse = certMsg.certificates[0].ocspResponse +- c.sctList = certMsg.certificates[0].sctList + + certVerifyMsg, err := readHandshakeType[certificateVerifyMsg](c) + if err != nil { +@@ -1854,6 +1873,50 @@ func delegatedCredentialSignedMessage(credBytes []byte, algorithm signatureAlgor + return ret } -+int SSL_CTX_set_server_raw_public_key_certificate(SSL_CTX *ctx, -+ const uint8_t *raw_public_key, unsigned raw_public_key_len) { -+ if (!ctx->server_raw_public_key_certificate.CopyFrom( -+ MakeConstSpan(raw_public_key, raw_public_key_len))) { -+ return 0; /* Failure */ -+ } ++func (hs *clientHandshakeState) verifyRawPublicKeyCertificates(certMsg *certificateMsg) error { ++ c := hs.c + -+ if (!ctx->server_certificate_type_list.Init(1)) { -+ return 0; -+ } -+ ctx->server_certificate_type_list[0] = TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY; ++ if len(certMsg.certificates) != 1 { ++ c.sendAlert(alertIllegalParameter) ++ return errors.New("tls: incorrect number of certificates") ++ } + -+ return 1; /* Success */ -+} ++ leafSPKI := certMsg.certificates[0].data + -+int SSL_CTX_has_server_raw_public_key_certificate(SSL_CTX *ctx) { -+ return !ctx->server_raw_public_key_certificate.empty(); -+} ++ if !c.config.InsecureSkipVerify { ++ expectedCert, err := x509.ParseCertificate(c.config.Credential.Certificate[0]) ++ if err != nil { ++ c.sendAlert(alertInternalError) ++ return errors.New("tls: failed to parse configured certificate: " + err.Error()) ++ } ++ expectedSPKI := expectedCert.RawSubjectPublicKeyInfo + -+int SSL_set_server_raw_public_key_certificate(SSL *ssl, -+ const uint8_t *raw_public_key, unsigned raw_public_key_len) { -+ if (!ssl->config) { -+ return 0; /* Failure */ -+ } ++ if !bytes.Equal(expectedSPKI, leafSPKI) { ++ c.sendAlert(alertBadCertificate) ++ return errors.New("tls: raw public key verification failed") ++ } ++ } + -+ if (!ssl->config->server_raw_public_key_certificate.CopyFrom( -+ MakeConstSpan(raw_public_key, raw_public_key_len))) { -+ return 0; -+ } ++ leafPublicKey, err := x509.ParsePKIXPublicKey(leafSPKI) ++ if err != nil { ++ c.sendAlert(alertBadCertificate) ++ return errors.New("tls: failed to parse raw public key certificate from server: " + err.Error()) ++ } + -+ if (!ssl->config->server_certificate_type_list.Init(1)) { -+ return 0; -+ } -+ ssl->config->server_certificate_type_list[0] = -+ TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY; ++ switch leafPublicKey.(type) { ++ case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: ++ break ++ default: ++ c.sendAlert(alertUnsupportedCertificate) ++ return fmt.Errorf("tls: server's certificate contains an unsupported type of public key: %T", leafPublicKey) ++ } ++ ++ c.peerCertificates = nil ++ hs.peerPublicKey = leafPublicKey + -+ return 1; /* Success */ ++ return nil +} + -+int SSL_has_server_raw_public_key_certificate(SSL *ssl) { -+ if (!ssl->config) { -+ return 0; /* Failure */ + func (hs *clientHandshakeState) verifyCertificates(certMsg *certificateMsg) error { + c := hs.c + +diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go +index d5097f37c..d570a76ab 100644 +--- a/ssl/test/runner/handshake_messages.go ++++ b/ssl/test/runner/handshake_messages.go +@@ -259,9 +259,10 @@ type clientHelloMsg struct { + prefixExtensions []uint16 + // The following fields are only filled in by |unmarshal| and ignored when + // marshaling a new ClientHello. +- echPayloadStart int +- echPayloadEnd int +- rawExtensions []byte ++ echPayloadStart int ++ echPayloadEnd int ++ rawExtensions []byte ++ serverCertificateTypes []uint8 + } + + func (m *clientHelloMsg) marshalKeyShares(bb *cryptobyte.Builder) { +@@ -634,6 +635,14 @@ func (m *clientHelloMsg) marshalBody(hello *cryptobyte.Builder, typ clientHelloT + body: body.BytesOrPanic(), + }) + } ++ if m.serverCertificateTypes != nil { ++ body := cryptobyte.NewBuilder(nil) ++ addUint8LengthPrefixedBytes(body, m.serverCertificateTypes) ++ extensions = append(extensions, extension{ ++ id: extensionServerCertificateType, ++ body: body.BytesOrPanic(), ++ }) ++ } + // The PSK extension must be last. See https://tools.ietf.org/html/rfc8446#section-4.2.11 + if len(m.pskIdentities) > 0 { + pskExtension := cryptobyte.NewBuilder(nil) +@@ -1144,6 +1153,10 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { + } + m.alpsProtocols = append(m.alpsProtocols, string(protocol)) + } ++ case extensionServerCertificateType: ++ if !readUint8LengthPrefixedBytes(&body, &m.serverCertificateTypes) || len(body) != 0 { ++ return false ++ } + case extensionApplicationSettingsOld: + var protocols cryptobyte.String + if !body.ReadUint16LengthPrefixed(&protocols) || len(body) != 0 { +@@ -1597,6 +1610,8 @@ type serverExtensions struct { + hasApplicationSettingsOld bool + echRetryConfigs []byte + trustAnchors [][]byte ++ hasServerCertificateType bool ++ serverCertificateType uint8 + } + + func (m *serverExtensions) marshal(extensions *cryptobyte.Builder) { +@@ -1731,6 +1746,10 @@ func (m *serverExtensions) marshal(extensions *cryptobyte.Builder) { + extensions.AddUint16(extensionEncryptedClientHello) + addUint16LengthPrefixedBytes(extensions, m.echRetryConfigs) + } ++ if m.hasServerCertificateType { ++ extensions.AddUint16(extensionServerCertificateType) ++ addUint16LengthPrefixedBytes(extensions, []byte{m.serverCertificateType}) ++ } + if len(m.trustAnchors) > 0 { + extensions.AddUint16(extensionTrustAnchors) + extensions.AddUint16LengthPrefixed(func(extension *cryptobyte.Builder) { +@@ -1797,6 +1816,14 @@ func (m *serverExtensions) unmarshal(data cryptobyte.String, version uint16) boo + return false + } + m.channelIDRequested = true ++ case extensionServerCertificateType: ++ if version < VersionTLS13 { ++ return false ++ } ++ if !body.ReadUint8(&m.serverCertificateType) || len(body) != 0 { ++ return false ++ } ++ m.hasServerCertificateType = true + case extensionExtendedMasterSecret: + if len(body) != 0 { + return false +diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go +index 4e6ae98e4..14353b5e9 100644 +--- a/ssl/test/runner/handshake_server.go ++++ b/ssl/test/runner/handshake_server.go +@@ -28,20 +28,22 @@ import ( + // serverHandshakeState contains details of a server handshake in progress. + // It's discarded once the handshake has completed. + type serverHandshakeState struct { +- c *Conn +- clientHello *clientHelloMsg +- hello *serverHelloMsg +- suite *cipherSuite +- ellipticOk bool +- ecdsaOk bool +- sessionState *sessionState +- finishedHash finishedHash +- masterSecret []byte +- certsFromClient [][]byte +- cert *Credential +- finishedBytes []byte +- echHPKEContext *hpke.Context +- echConfigID uint8 ++ c *Conn ++ clientHello *clientHelloMsg ++ hello *serverHelloMsg ++ suite *cipherSuite ++ ellipticOk bool ++ ecdsaOk bool ++ sessionState *sessionState ++ finishedHash finishedHash ++ masterSecret []byte ++ certsFromClient [][]byte ++ cert *Credential ++ finishedBytes []byte ++ echHPKEContext *hpke.Context ++ echConfigID uint8 ++ hasServerCertificateType bool ++ serverCertificateType uint8 + } + + // serverHandshake performs a TLS handshake as a server. +@@ -983,6 +985,18 @@ func (hs *serverHandshakeState) doTLS13Handshake() error { + encryptedExtensions.extensions.hasEarlyData = true + } + ++ if c.vers >= VersionTLS13 && config.useServerRawPublicKeyCertificate { ++ for _, t := range hs.clientHello.serverCertificateTypes { ++ if t != certificateTypeRawPublicKey { ++ continue ++ } ++ hs.hasServerCertificateType = true ++ hs.serverCertificateType = certificateTypeRawPublicKey ++ encryptedExtensions.extensions.hasServerCertificateType = true ++ encryptedExtensions.extensions.serverCertificateType = certificateTypeRawPublicKey ++ } ++ } ++ + // Resolve ECDHE and compute the handshake secret. + if hs.hello.hasKeyShare { + // Once a curve has been selected and a key share identified, +@@ -1184,6 +1198,17 @@ func (hs *serverHandshakeState) doTLS13Handshake() error { + } + if !config.Bugs.EmptyCertificateList { + for i, certData := range useCert.Certificate { ++ if hs.hasServerCertificateType && ++ hs.serverCertificateType == certificateTypeRawPublicKey { ++ cert, err := x509.ParseCertificate(certData) ++ if err != nil { ++ return fmt.Errorf("tls: failed to parse configured certificate: %s", err.Error()) ++ } ++ certMsg.certificates = append( ++ certMsg.certificates, ++ certificateEntry{data: cert.RawSubjectPublicKeyInfo}) ++ break ++ } + cert := certificateEntry{ + data: certData, + } +diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go +index 57f9cc410..091e44f48 100644 +--- a/ssl/test/runner/runner.go ++++ b/ssl/test/runner/runner.go +@@ -789,7 +789,7 @@ func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool, tr + tlsConn = Server(conn, config) + } + } else { +- config.InsecureSkipVerify = true ++ config.InsecureSkipVerify = !config.useServerRawPublicKeyCertificate + if test.protocol == dtls { + tlsConn = DTLSClient(conn, config) + } else { +@@ -2244,6 +2244,7 @@ func main() { + addKeyUpdateTests() + addPAKETests() + addTrustAnchorTests() ++ addRawPublicKeyCertificateTests() + + toAppend, err := convertToSplitHandshakeTests(testCases) + if err != nil { +diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc +index 9a7ee6a68..5b8cd5fa6 100644 +--- a/ssl/test/test_config.cc ++++ b/ssl/test/test_config.cc +@@ -37,6 +37,8 @@ + #include + + #include "../../crypto/internal.h" ++#include "../../crypto/mem_internal.h" ++#include "../../ssl/internal.h" + #include "handshake_util.h" + #include "mock_quic_transport.h" + #include "test_state.h" +@@ -639,6 +641,8 @@ const Flag *FindFlag(const char *name) { + OptionalBoolFalseFlag("-expect-not-resumable-across-names", + &TestConfig::expect_resumable_across_names), + BoolFlag("-no-server-name-ack", &TestConfig::no_server_name_ack), ++ BoolFlag("-raw-public-key-mode", &TestConfig::raw_public_key_mode), ++ Base64Flag("-expect-spki", &TestConfig::expect_spki), + }; + std::sort(ret.begin(), ret.end(), FlagNameComparator{}); + return ret; +@@ -1145,6 +1149,17 @@ static bool CheckVerifyCallback(SSL *ssl) { + fprintf(stderr, "ECH name did not match expected value.\n"); + return false; + } ++ if (!config->expect_spki.empty()) { ++ const EVP_PKEY *pkey = SSL_get0_peer_pubkey(ssl); ++ bssl::ScopedCBB cbb; ++ if (pkey == nullptr || !CBB_init(cbb.get(), /*initial_capacity=*/512) || ++ !EVP_marshal_public_key(cbb.get(), pkey) || ++ OPENSSL_memcmp(config->expect_spki.data(), CBB_data(cbb.get()), ++ CBB_len(cbb.get())) != 0) { ++ fprintf(stderr, "Incorrect SPKI observed\n"); ++ return false; ++ } + } + + if (config->expect_peer_match_trust_anchor.has_value() && + !!SSL_peer_matched_trust_anchor(ssl) != +@@ -1852,17 +1867,64 @@ static bool InstallCertificate(SSL *ssl) { + return false; + } + ++ const TestConfig *config = GetTestConfig(ssl); + -+ return !ssl->config->server_raw_public_key_certificate.empty(); -+} + if (pkey) { + TestState *test_state = GetTestState(ssl); +- const TestConfig *config = GetTestConfig(ssl); +- if (config->async || config->handshake_hints) { ++ // Install a custom private key if testing asynchronous callbacks, or if ++ // testing handshake hints. In the handshake hints case, we wish to check ++ // that hints only mismatch when allowed. ++ const bool use_private_key_method = ++ config->async || config->handshake_hints; ++ if (use_private_key_method) { + // Install a custom private key if testing asynchronous callbacks, or if + // testing handshake hints. In the handshake hints case, we wish to check + // that hints only mismatch when allowed. + test_state->private_key = std::move(pkey); +- SSL_set_private_key_method(ssl, &g_async_private_key_method); +- } else if (!SSL_use_PrivateKey(ssl, pkey.get())) { +- return false; ++ } ++ ++ if (config->raw_public_key_mode) { ++ bssl::UniquePtr cred(SSL_CREDENTIAL_new_raw_public_key()); ++ if (cred == nullptr) { ++ return false; ++ } + - namespace fips202205 { - - // (References are to SP 800-52r2): -diff --git a/src/ssl/tls13_both.cc b/src/ssl/tls13_both.cc -index 5ab5a1c93..79135613e 100644 ---- a/src/ssl/tls13_both.cc -+++ b/src/ssl/tls13_both.cc -@@ -11,6 +11,25 @@ - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ ++ if (use_private_key_method) { ++ SSL_CREDENTIAL_set_private_key_method(cred.get(), ++ &g_async_private_key_method); ++ ++ bssl::ScopedCBB cbb; ++ if (!CBB_init(cbb.get(), /*initial_capacity=*/512) || ++ !EVP_marshal_public_key(cbb.get(), test_state->private_key.get())) { ++ return false; ++ } ++ bssl::UniquePtr spki(CRYPTO_BUFFER_new( ++ CBB_data(cbb.get()), CBB_len(cbb.get()), /*pool=*/nullptr)); ++ if (spki == nullptr) { ++ return false; ++ } ++ if (!SSL_CREDENTIAL_set1_spki(cred.get(), spki.get())) { ++ return false; ++ } ++ } else { ++ if (!SSL_CREDENTIAL_set1_private_key(cred.get(), pkey.get())) { ++ return false; ++ } ++ if (!SSL_CREDENTIAL_set1_spki(cred.get(), nullptr)) { ++ return false; ++ } ++ } ++ ++ if (!SSL_add1_credential(ssl, cred.get())) { ++ return false; ++ } ++ return true; ++ } else { ++ if (use_private_key_method) { ++ SSL_set_private_key_method(ssl, &g_async_private_key_method); ++ } else if (!SSL_use_PrivateKey(ssl, pkey.get())) { ++ return false; ++ } + } + } - #include +@@ -2015,8 +2077,8 @@ bssl::UniquePtr TestConfig::SetupCtx(SSL_CTX *old_ctx) const { + } -@@ -197,7 +216,16 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, - return false; + if (async && is_server) { +- // Disable the internal session cache. To test asynchronous session lookup, +- // we use an external session cache. ++ // Disable the internal session cache. To test asynchronous session ++ // lookup, we use an external session cache. + SSL_CTX_set_session_cache_mode( + ssl_ctx.get(), SSL_SESS_CACHE_BOTH | SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_get_cb(ssl_ctx.get(), GetSessionCallback); +@@ -2338,6 +2400,12 @@ bssl::UniquePtr TestConfig::NewSSL( + if (verify_peer) { + mode = SSL_VERIFY_PEER; + } ++ static const uint8_t kCertificateTypes[] = { ++ TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY}; ++ if (!is_server && raw_public_key_mode && ++ !SSL_set_server_certificate_types(ssl.get(), kCertificateTypes, 1)) { ++ return nullptr; ++ } + if (use_custom_verify_callback) { + SSL_set_custom_verify(ssl.get(), mode, CustomVerifyCallback); + } else if (mode != SSL_VERIFY_NONE) { +@@ -2484,8 +2552,8 @@ bssl::UniquePtr TestConfig::NewSSL( + if (enable_signed_cert_timestamps) { + SSL_enable_signed_cert_timestamps(ssl.get()); + } +- // (D)TLS 1.0 and 1.1 are disabled by default, but the runner expects them to +- // be enabled. ++ // (D)TLS 1.0 and 1.1 are disabled by default, but the runner expects them ++ // to be enabled. + // TODO(davidben): Update the tests to explicitly enable the versions they + // need. + if (!SSL_set_min_proto_version( +@@ -2495,7 +2563,8 @@ bssl::UniquePtr TestConfig::NewSSL( + if (min_version != 0 && !SSL_set_min_proto_version(ssl.get(), min_version)) { + return nullptr; + } +- // TODO(crbug.com/42290594): Remove this once DTLS 1.3 is enabled by default. ++ // TODO(crbug.com/42290594): Remove this once DTLS 1.3 is enabled by ++ // default. + if (is_dtls && max_version == 0 && + !SSL_set_max_proto_version(ssl.get(), DTLS1_3_VERSION)) { + return nullptr; +@@ -2515,7 +2584,8 @@ bssl::UniquePtr TestConfig::NewSSL( + SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_once); + } + if (renegotiate_freely || forbid_renegotiation_after_handshake) { +- // |forbid_renegotiation_after_handshake| will disable renegotiation later. ++ // |forbid_renegotiation_after_handshake| will disable renegotiation ++ // later. + SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_freely); + } + if (renegotiate_ignore) { +diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h +index 9745de035..dc32f8136 100644 +--- a/ssl/test/test_config.h ++++ b/ssl/test/test_config.h +@@ -243,6 +243,8 @@ struct TestConfig { + bool resumption_across_names_enabled = false; + std::optional expect_resumable_across_names; + bool no_server_name_ack = false; ++ bool raw_public_key_mode = false; ++ std::vector expect_spki; + + std::vector handshaker_args; + +diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc +index 257e4c997..951ab7d52 100644 +--- a/ssl/tls13_both.cc ++++ b/ssl/tls13_both.cc +@@ -198,6 +198,45 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, } -- if (sk_CRYPTO_BUFFER_num(certs.get()) == 0) { -+ if (hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) { + const bool is_leaf = sk_CRYPTO_BUFFER_num(certs.get()) == 0; ++ ++ // Parse out the extensions. ++ SSLExtension status_request( ++ TLSEXT_TYPE_status_request, ++ !ssl->server && hs->config->ocsp_stapling_enabled); ++ SSLExtension sct( ++ TLSEXT_TYPE_certificate_timestamp, ++ !ssl->server && hs->config->signed_cert_timestamps_enabled); ++ SSLExtension trust_anchors( ++ TLSEXT_TYPE_trust_anchors, ++ !ssl->server && is_leaf && ++ hs->config->requested_trust_anchors.has_value()); ++ uint8_t alert = SSL_AD_DECODE_ERROR; ++ if (!ssl_parse_extensions(&extensions, &alert, ++ {&status_request, &sct, &trust_anchors}, ++ /*ignore_unknown=*/false)) { ++ ssl_send_alert(ssl, SSL3_AL_FATAL, alert); ++ return false; ++ } ++ ++ if (!ssl->server && ++ hs->server_certificate_type == TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY) { ++ if (pkey) { ++ // Only a single "certificate" is allowed if using raw public keys. ++ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); ++ ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER); ++ return false; ++ } ++ + pkey = UniquePtr(EVP_parse_public_key(&certificate)); -+ if (!pkey) { ++ if (!pkey || CBS_len(&certificate) != 0) { + ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR); + OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); + return false; + } ++ ++ continue; + } -+ else if (sk_CRYPTO_BUFFER_num(certs.get()) == 0) { ++ + if (is_leaf) { pkey = ssl_cert_parse_pubkey(&certificate); if (!pkey) { - ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR); -@@ -299,7 +327,10 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, +@@ -228,25 +267,6 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, + return false; + } + +- // Parse out the extensions. +- SSLExtension status_request( +- TLSEXT_TYPE_status_request, +- !ssl->server && hs->config->ocsp_stapling_enabled); +- SSLExtension sct( +- TLSEXT_TYPE_certificate_timestamp, +- !ssl->server && hs->config->signed_cert_timestamps_enabled); +- SSLExtension trust_anchors( +- TLSEXT_TYPE_trust_anchors, +- !ssl->server && is_leaf && +- hs->config->requested_trust_anchors.has_value()); +- uint8_t alert = SSL_AD_DECODE_ERROR; +- if (!ssl_parse_extensions(&extensions, &alert, +- {&status_request, &sct, &trust_anchors}, +- /*ignore_unknown=*/false)) { +- ssl_send_alert(ssl, SSL3_AL_FATAL, alert); +- return false; +- } +- + // All Certificate extensions are parsed, but only the leaf extensions are + // stored. + if (status_request.present) { +@@ -313,7 +333,16 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, + return false; } - if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) { -- if (!allow_anonymous) { -+ if (!allow_anonymous && -+ !(hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == -+ TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY)) { +- if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) { ++ if (!ssl->server && ++ hs->server_certificate_type == TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY) { ++ if (!hs->peer_pubkey) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE); ++ ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_CERTIFICATE_REQUIRED); ++ return false; ++ } ++ ++ return true; ++ } else if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) { + if (!allow_anonymous) { OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE); ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_CERTIFICATE_REQUIRED); - return false; -@@ -416,6 +447,20 @@ bool tls13_add_certificate(SSL_HANDSHAKE *hs) { - return false; +@@ -436,13 +465,28 @@ bool tls13_add_certificate(SSL_HANDSHAKE *hs) { + return ssl_add_message_cbb(ssl, cbb.get()); } -+ if (hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) { -+ CBB leaf, extensions; -+ if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) || -+ !CBB_add_bytes(&leaf, -+ ssl->config->server_raw_public_key_certificate.data(), -+ ssl->config->server_raw_public_key_certificate.size()) || -+ !CBB_add_u16_length_prefixed(&certificate_list, &extensions)) { +- assert(hs->credential->UsesX509()); +- CRYPTO_BUFFER *leaf_buf = sk_CRYPTO_BUFFER_value(cred->chain.get(), 0); +- CBB leaf, extensions; +- if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) || +- !CBB_add_bytes(&leaf, CRYPTO_BUFFER_data(leaf_buf), +- CRYPTO_BUFFER_len(leaf_buf)) || +- !CBB_add_u16_length_prefixed(&certificate_list, &extensions)) { ++ CBB leaf; ++ if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf)) { ++ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); ++ return false; ++ } ++ ++ if (hs->credential->type == SSLCredentialType::kRawPublicKey) { ++ if (!EVP_marshal_public_key(&leaf, cred->pubkey.get())) { ++ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); ++ return false; ++ } ++ } else { ++ CRYPTO_BUFFER *leaf_buf = sk_CRYPTO_BUFFER_value(cred->chain.get(), 0); ++ if (!CBB_add_bytes(&leaf, CRYPTO_BUFFER_data(leaf_buf), ++ CRYPTO_BUFFER_len(leaf_buf))) { + OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); + return false; + } -+ return ssl_add_message_cbb(ssl, cbb.get()); + } + - if (!ssl_has_certificate(hs)) { - return ssl_add_message_cbb(ssl, cbb.get()); ++ CBB extensions; ++ if (!CBB_add_u16_length_prefixed(&certificate_list, &extensions)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); + return false; } -diff --git a/src/ssl/tls13_server.cc b/src/ssl/tls13_server.cc -index 9d26f4e00..a92689761 100644 ---- a/src/ssl/tls13_server.cc -+++ b/src/ssl/tls13_server.cc -@@ -11,6 +11,25 @@ - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #include +diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc +index eade4bd66..9de0bea28 100644 +--- a/ssl/tls13_server.cc ++++ b/ssl/tls13_server.cc +@@ -261,6 +261,7 @@ bool ssl_check_tls13_credential_ignoring_issuer(SSL_HANDSHAKE *hs, + uint16_t *out_sigalg) { + switch (cred->type) { + case SSLCredentialType::kX509: ++ case SSLCredentialType::kRawPublicKey: + break; + case SSLCredentialType::kDelegated: + // Check that the peer supports the signature over the delegated +@@ -284,7 +285,34 @@ bool ssl_check_tls13_credential_ignoring_issuer(SSL_HANDSHAKE *hs, -@@ -860,7 +879,8 @@ static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) { - - // Send the server Certificate message, if necessary. - if (!ssl->s3->session_reused) { -- if (!ssl_has_certificate(hs)) { -+ if (!ssl_has_certificate(hs) && -+ !ssl_has_raw_public_key_certificate(hs)) { - OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_SET); - return ssl_hs_error; + static bool check_signature_credential(SSL_HANDSHAKE *hs, + const SSL_CREDENTIAL *cred, +- uint16_t *out_sigalg) { ++ uint16_t *out_sigalg, ++ uint8_t *cert_type) { ++ switch (cred->type) { ++ case SSLCredentialType::kDelegated: ++ case SSLCredentialType::kX509: ++ *cert_type = TLS_CERTIFICATE_TYPE_X509; ++ break; ++ case SSLCredentialType::kRawPublicKey: ++ if (hs->server_certificate_type_list.empty()) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ return false; ++ } ++ *cert_type = TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY; ++ break; ++ default: ++ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ return false; ++ } ++ ++ if (!hs->server_certificate_type_list.empty() && ++ std::none_of( ++ hs->server_certificate_type_list.begin(), ++ hs->server_certificate_type_list.end(), ++ [cert_type](const auto &type) { return *cert_type == type; })) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ return false; ++ } ++ + return ssl_check_tls13_credential_ignoring_issuer(hs, cred, out_sigalg) && + // Use this credential if it either matches a requested issuer, + // or does not require issuer matching. +@@ -359,9 +387,11 @@ static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) { + } + } else { + uint16_t sigalg; +- if (check_signature_credential(hs, cred, &sigalg)) { ++ uint8_t cert_type; ++ if (check_signature_credential(hs, cred, &sigalg, &cert_type)) { + hs->credential = UpRef(cred); + hs->signature_algorithm = sigalg; ++ hs->server_certificate_type = cert_type; + break; + } } +-- +2.40.0 + diff --git a/boring-sys/patches/underscore-wildcards.patch b/boring-sys/patches/underscore-wildcards.patch index f281b3a1a..67272a733 100644 --- a/boring-sys/patches/underscore-wildcards.patch +++ b/boring-sys/patches/underscore-wildcards.patch @@ -1,8 +1,19 @@ -https://github.com/google/boringssl/compare/master...cloudflare:boringssl:underscore-wildcards +From 2128aa4382ba668e2c4f77bf18da719b2ad0087e Mon Sep 17 00:00:00 2001 +From: Anthony Ramine +Date: Fri, 5 Dec 2025 08:19:56 +0100 +Subject: [PATCH] Introduce X509_CHECK_FLAG_UNDERSCORE_WILDCARDS ---- a/src/crypto/x509v3/v3_utl.c -+++ b/src/crypto/x509v3/v3_utl.c -@@ -790,7 +790,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len, +--- + crypto/x509/v3_utl.cc | 4 +++- + crypto/x509/x509_test.cc | 25 +++++++++++++++++++++++++ + include/openssl/x509.h | 3 +++ + 3 files changed, 31 insertions(+), 1 deletion(-) + +diff --git a/crypto/x509/v3_utl.cc b/crypto/x509/v3_utl.cc +index 015bbcad2..2b9b63430 100644 +--- a/crypto/x509/v3_utl.cc ++++ b/crypto/x509/v3_utl.cc +@@ -740,7 +740,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len, // Check that the part matched by the wildcard contains only // permitted characters and only matches a single label. for (p = wildcard_start; p != wildcard_end; ++p) { @@ -13,9 +24,11 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:unders return 0; } } ---- a/src/crypto/x509/x509_test.cc -+++ b/src/crypto/x509/x509_test.cc -@@ -4500,6 +4500,31 @@ TEST(X509Test, Names) { +diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc +index c6ce62dd1..f284f421f 100644 +--- a/crypto/x509/x509_test.cc ++++ b/crypto/x509/x509_test.cc +@@ -5209,6 +5209,31 @@ TEST(X509Test, Names) { /*invalid_emails=*/{}, /*flags=*/0, }, @@ -47,15 +60,20 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:unders }; size_t i = 0; ---- a/src/include/openssl/x509c3.h -+++ b/src/include/openssl/x509v3.h -@@ -4497,6 +4497,8 @@ OPENSSL_EXPORT int X509_PURPOSE_get_id(const X509_PURPOSE *); - #define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0 - // Skip the subject common name fallback if subjectAltNames is missing. +diff --git a/include/openssl/x509.h b/include/openssl/x509.h +index 926f365f4..cc538cceb 100644 +--- a/include/openssl/x509.h ++++ b/include/openssl/x509.h +@@ -3359,6 +3359,9 @@ OPENSSL_EXPORT int X509_VERIFY_PARAM_add1_host(X509_VERIFY_PARAM *param, + // enabled when subjectAltNames is missing. #define X509_CHECK_FLAG_NEVER_CHECK_SUBJECT 0x20 -+// Allow underscores in DNS wildcard matches. -+#define X509_CHECK_FLAG_UNDERSCORE_WILDCARDS 0x40 - OPENSSL_EXPORT int X509_check_host(X509 *x, const char *chk, size_t chklen, - unsigned int flags, char **peername); ++// X509_CHECK_FLAG_UNDERSCORE_WILDCARDS allows underscores in DNS wildcard matches. ++#define X509_CHECK_FLAG_UNDERSCORE_WILDCARDS 0x40 ++ + // X509_VERIFY_PARAM_set_hostflags sets the name-checking flags on |param| to + // |flags|. |flags| should be a combination of |X509_CHECK_FLAG_*| constants. + OPENSSL_EXPORT void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param, -- +2.40.0 + diff --git a/boring-sys/src/lib.rs b/boring-sys/src/lib.rs index f19ef7757..57ceffc76 100644 --- a/boring-sys/src/lib.rs +++ b/boring-sys/src/lib.rs @@ -19,12 +19,20 @@ use std::os::raw::{c_char, c_int, c_uint, c_ulong}; clippy::useless_transmute, clippy::derive_partial_eq_without_eq, clippy::ptr_offset_with_cast, - unpredictable_function_pointer_comparisons, // TODO: remove Eq/PartialEq in v5 dead_code )] mod generated { include!(concat!(env!("OUT_DIR"), "/bindings.rs")); } + +// explicitly require presence of some symbols to check if the bindings worked +pub use generated::{ssl_compliance_policy_t, ERR_add_error_data, SSL_set1_groups}; // if these are missing, your include path is incorrect or has a wrong version of boringssl +pub use generated::{BIO_new, OPENSSL_free, SSL_ERROR_NONE}; // if these are missing, your include path is incorrect +#[cfg(feature = "fips")] +pub use generated::{FIPS_mode, SSL_CTX_set_compliance_policy}; // your include path is incorrect or has a version of boringssl without FIPS support +#[cfg(feature = "rpk")] +pub use generated::{SSL_CREDENTIAL_new_raw_public_key, SSL_CREDENTIAL_set1_spki}; // your include path is incorrect or has a version of boringssl without rpk support + pub use generated::*; #[cfg(target_pointer_width = "64")] diff --git a/boring/Cargo.toml b/boring/Cargo.toml index f9a3527b9..b9605ff13 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -7,41 +7,27 @@ description = "BoringSSL bindings" repository = { workspace = true } documentation = "https://docs.rs/boring" readme = "README.md" -keywords = ["crypto", "tls", "ssl", "dtls"] +keywords = ["tls", "ssl", "dtls", "post-quantum", "fips"] categories = ["cryptography", "api-bindings"] edition = { workspace = true } -rust-version = "1.80" +rust-version = { workspace = true } [package.metadata.docs.rs] -features = ["rpk", "pq-experimental", "underscore-wildcards"] +features = ["rpk", "underscore-wildcards"] rustdoc-args = ["--cfg", "docsrs"] [features] # Controlling the build -# NOTE: This feature is deprecated. It is needed for the submoduled -# boringssl-fips, which is extremely old and requires modifications to the -# bindings, as some newer APIs don't exist and some function signatures have -# changed. It is highly recommended to use `fips-precompiled` instead. -# -# This feature sets `fips-compat` on behalf of the user to guarantee bindings -# compatibility with the submoduled boringssl-fips. -# # Use a FIPS-validated version of BoringSSL. -fips = ["fips-compat", "boring-sys/fips"] +fips = ["boring-sys/fips"] -# Build with compatibility for the submoduled boringssl-fips, without enabling -# the `fips` feature itself (useful e.g. if `fips-link-precompiled` is used -# with an older BoringSSL version). -fips-compat = [] +# **DO NOT USE** This will be removed without warning in future releases. +legacy-compat-deprecated = [] -# Use a precompiled FIPS-validated version of BoringSSL. Meant to be used with -# FIPS-20230428 or newer. Users must set `BORING_BSSL_FIPS_PATH` to use this -# feature, or else the build will fail. -fips-precompiled = ["boring-sys/fips-precompiled"] - -# Link with precompiled FIPS-validated `bcm.o` module. -fips-link-precompiled = ["boring-sys/fips-link-precompiled"] +# **DO NOT USE** This will be removed without warning in future releases. +# PQ is always enabled. This feature is a no-op, only for backwards compatibility. +pq-experimental = [] # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) # This feature is necessary in order to compile the bindings for the @@ -50,40 +36,13 @@ fips-link-precompiled = ["boring-sys/fips-link-precompiled"] # `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`. rpk = ["boring-sys/rpk"] -# Applies a patch to the boringSSL source code that enables support for PQ key -# exchange. This feature is necessary in order to compile the bindings for the -# default branch of boringSSL. Alternatively, a version of boringSSL that -# implements the same feature set can be provided by setting -# `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`. -pq-experimental = ["boring-sys/pq-experimental"] - -# Applies a patch to enable -# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. Same caveats as -# those for `pq-experimental` feature apply. +# Applies a patch to enable `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. This +# feature is necessary in order to compile the bindings for the default branch +# of boringSSL. Alternatively, a version of boringSSL that implements the same +# feature set can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH` and +# `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`. underscore-wildcards = ["boring-sys/underscore-wildcards"] -# Controlling key exchange preferences at compile time - -# Choose key exchange preferences at compile time. This prevents the user from -# choosing their own preferences. -kx-safe-default = [] - -# Support PQ key exchange. The client will prefer classical key exchange, but -# will upgrade to PQ key exchange if requested by the server. This is the -# safest option if you don't know if the peer supports PQ key exchange. This -# feature implies "kx-safe-default". -kx-client-pq-supported = ["kx-safe-default"] - -# Prefer PQ key exchange. The client will prefer PQ exchange, but fallback to -# classical key exchange if requested by the server. This is the best option if -# you know the peer supports PQ key exchange. This feature implies -# "kx-safe-default" and "kx-client-pq-supported". -kx-client-pq-preferred = ["kx-safe-default", "kx-client-pq-supported"] - -# Disable key exchange involving non-NIST key exchange on the client side. -# Implies "kx-safe-default". -kx-client-nist-required = ["kx-safe-default"] - [dependencies] bitflags = { workspace = true } foreign-types = { workspace = true } diff --git a/boring/examples/mk_certs.rs b/boring/examples/mk_certs.rs index 1fc4993d8..6b4c6e01e 100644 --- a/boring/examples/mk_certs.rs +++ b/boring/examples/mk_certs.rs @@ -43,18 +43,19 @@ fn mk_ca_cert() -> Result<(X509, PKey), ErrorStack> { let not_after = Asn1Time::days_from_now(365)?; cert_builder.set_not_after(¬_after)?; - cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?)?; + cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?.as_ref())?; cert_builder.append_extension( KeyUsage::new() .critical() .key_cert_sign() .crl_sign() - .build()?, + .build()? + .as_ref(), )?; let subject_key_identifier = SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?; - cert_builder.append_extension(subject_key_identifier)?; + cert_builder.append_extension(&subject_key_identifier)?; cert_builder.sign(&privkey, MessageDigest::sha256())?; let cert = cert_builder.build(); @@ -106,7 +107,7 @@ fn mk_ca_signed_cert( let not_after = Asn1Time::days_from_now(365)?; cert_builder.set_not_after(¬_after)?; - cert_builder.append_extension(BasicConstraints::new().build()?)?; + cert_builder.append_extension(BasicConstraints::new().build()?.as_ref())?; cert_builder.append_extension( KeyUsage::new() @@ -114,24 +115,25 @@ fn mk_ca_signed_cert( .non_repudiation() .digital_signature() .key_encipherment() - .build()?, + .build()? + .as_ref(), )?; let subject_key_identifier = SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(Some(ca_cert), None))?; - cert_builder.append_extension(subject_key_identifier)?; + cert_builder.append_extension(&subject_key_identifier)?; let auth_key_identifier = AuthorityKeyIdentifier::new() .keyid(false) .issuer(false) .build(&cert_builder.x509v3_context(Some(ca_cert), None))?; - cert_builder.append_extension(auth_key_identifier)?; + cert_builder.append_extension(&auth_key_identifier)?; let subject_alt_name = SubjectAlternativeName::new() .dns("*.example.com") .dns("hello.com") .build(&cert_builder.x509v3_context(Some(ca_cert), None))?; - cert_builder.append_extension(subject_alt_name)?; + cert_builder.append_extension(&subject_alt_name)?; cert_builder.sign(ca_privkey, MessageDigest::sha256())?; let cert = cert_builder.build(); diff --git a/boring/src/aes.rs b/boring/src/aes.rs index 8a47cbafc..e535fa217 100644 --- a/boring/src/aes.rs +++ b/boring/src/aes.rs @@ -38,7 +38,7 @@ //! ``` //! use crate::ffi; -use libc::{c_int, c_uint, size_t}; +use libc::c_int; use openssl_macros::corresponds; use std::mem::MaybeUninit; use std::ptr; @@ -63,8 +63,8 @@ impl AesKey { let mut aes_key = MaybeUninit::uninit(); let r = ffi::AES_set_encrypt_key( - key.as_ptr() as *const _, - key.len() as c_uint * 8, + key.as_ptr(), + (key.len() * 8).try_into().map_err(|_| KeyError(()))?, aes_key.as_mut_ptr(), ); if r == 0 { @@ -87,8 +87,8 @@ impl AesKey { let mut aes_key = MaybeUninit::uninit(); let r = ffi::AES_set_decrypt_key( - key.as_ptr() as *const _, - key.len() as c_uint * 8, + key.as_ptr(), + (key.len() * 8).try_into().map_err(|_| KeyError(()))?, aes_key.as_mut_ptr(), ); @@ -125,12 +125,11 @@ pub fn wrap_key( assert!(out.len() >= in_.len() + 8); // Ciphertext is 64 bits longer (see 2.2.1) let written = ffi::AES_wrap_key( - &key.0 as *const _ as *mut _, // this is safe, the implementation only uses the key as a const pointer. - iv.as_ref() - .map_or(ptr::null(), |iv| iv.as_ptr() as *const _), - out.as_ptr() as *mut _, - in_.as_ptr() as *const _, - in_.len() as size_t, + std::ptr::addr_of!(key.0).cast_mut(), // this is safe, the implementation only uses the key as a const pointer. + iv.as_ref().map_or(ptr::null(), |iv| iv.as_ptr()), + out.as_mut_ptr(), + in_.as_ptr(), + in_.len(), ); if written <= 0 { Err(KeyError(())) @@ -164,12 +163,11 @@ pub fn unwrap_key( assert!(out.len() + 8 <= in_.len()); let written = ffi::AES_unwrap_key( - &key.0 as *const _ as *mut _, // this is safe, the implementation only uses the key as a const pointer. - iv.as_ref() - .map_or(ptr::null(), |iv| iv.as_ptr() as *const _), - out.as_ptr() as *mut _, - in_.as_ptr() as *const _, - in_.len() as size_t, + std::ptr::addr_of!(key.0).cast_mut(), // this is safe, the implementation only uses the key as a const pointer. + iv.as_ref().map_or(ptr::null(), |iv| iv.as_ptr().cast()), + out.as_ptr().cast_mut(), + in_.as_ptr().cast(), + in_.len(), ); if written <= 0 { diff --git a/boring/src/asn1.rs b/boring/src/asn1.rs index e00214941..da5576341 100644 --- a/boring/src/asn1.rs +++ b/boring/src/asn1.rs @@ -26,7 +26,7 @@ //! ``` use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; -use libc::{c_char, c_int, c_long, time_t}; +use libc::{c_int, c_long, time_t}; use std::cmp::Ordering; use std::ffi::CString; use std::fmt; @@ -315,7 +315,10 @@ impl Asn1Time { ffi::init(); unsafe { - let handle = cvt_p(ffi::ASN1_TIME_set(ptr::null_mut(), time))?; + // for higher musl version, need to convert i32 to i64 + // https://github.com/rust-lang/libc/issues/1848 + #[allow(clippy::useless_conversion)] + let handle = cvt_p(ffi::ASN1_TIME_set(ptr::null_mut(), time.into()))?; Ok(Asn1Time::from_ptr(handle)) } } @@ -405,7 +408,7 @@ impl Asn1StringRef { return Err(ErrorStack::get()); } - Ok(OpensslString::from_ptr(ptr as *mut c_char)) + Ok(OpensslString::from_ptr(ptr.cast())) } } @@ -505,7 +508,6 @@ impl Asn1IntegerRef { self.as_ptr(), c_long::from(value), )) - .map(|_| ()) } } } @@ -547,7 +549,7 @@ impl Asn1BitStringRef { #[corresponds(ASN1_STRING_length)] #[must_use] pub fn len(&self) -> usize { - unsafe { ffi::ASN1_STRING_length(self.as_ptr() as *const _) as usize } + unsafe { ffi::ASN1_STRING_length(self.as_ptr().cast_const()) as usize } } /// Determines if the string is empty. @@ -589,7 +591,7 @@ impl Asn1Object { unsafe { ffi::init(); let txt = CString::new(txt).map_err(ErrorStack::internal_error)?; - let obj: *mut ffi::ASN1_OBJECT = cvt_p(ffi::OBJ_txt2obj(txt.as_ptr() as *const _, 0))?; + let obj: *mut ffi::ASN1_OBJECT = cvt_p(ffi::OBJ_txt2obj(txt.as_ptr(), 0))?; Ok(Asn1Object::from_ptr(obj)) } } @@ -614,9 +616,9 @@ impl Asn1ObjectRef { // To promote this to `pub`, the call-site parameter meaning ought to be clearer fn to_text(&self, no_name: bool) -> String { unsafe { - let mut buf = [0; 80]; + let mut buf = [0u8; 80]; let len = ffi::OBJ_obj2txt( - buf.as_mut_ptr() as *mut _, + buf.as_mut_ptr().cast(), buf.len() as c_int, self.as_ptr(), no_name as c_int, diff --git a/boring/src/bio.rs b/boring/src/bio.rs index 71120606f..bb4aa1a54 100644 --- a/boring/src/bio.rs +++ b/boring/src/bio.rs @@ -1,11 +1,12 @@ -use crate::ffi; -use crate::ffi::BIO_new_mem_buf; use std::marker::PhantomData; use std::ptr; use std::slice; use crate::cvt_p; use crate::error::ErrorStack; +use crate::ffi; +use crate::ffi::BIO_new_mem_buf; +use crate::try_int; pub struct MemBioSlice<'a>(*mut ffi::BIO, PhantomData<&'a [u8]>); @@ -19,20 +20,9 @@ impl Drop for MemBioSlice<'_> { impl<'a> MemBioSlice<'a> { pub fn new(buf: &'a [u8]) -> Result, ErrorStack> { - #[cfg(not(feature = "fips-compat"))] - type BufLen = isize; - #[cfg(feature = "fips-compat")] - type BufLen = libc::c_int; - ffi::init(); - assert!(buf.len() <= BufLen::MAX as usize); - let bio = unsafe { - cvt_p(BIO_new_mem_buf( - buf.as_ptr() as *const _, - buf.len() as BufLen, - ))? - }; + let bio = unsafe { cvt_p(BIO_new_mem_buf(buf.as_ptr().cast(), try_int(buf.len())?))? }; Ok(MemBioSlice(bio, PhantomData)) } @@ -68,7 +58,7 @@ impl MemBio { unsafe { let mut ptr = ptr::null_mut(); let len = ffi::BIO_get_mem_data(self.0, &mut ptr); - if ptr.is_null() { + if ptr.is_null() || len < 0 { return &[]; } slice::from_raw_parts(ptr.cast_const().cast(), len as usize) diff --git a/boring/src/bn.rs b/boring/src/bn.rs index bf4ca1c77..545eb3487 100644 --- a/boring/src/bn.rs +++ b/boring/src/bn.rs @@ -24,7 +24,7 @@ //! [`BIGNUM`]: https://wiki.openssl.org/index.php/Manual:Bn_internal(3) use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; -use libc::{c_int, size_t}; +use libc::c_int; use std::cmp::Ordering; use std::ffi::CString; use std::ops::{Add, Deref, Div, Mul, Neg, Rem, Shl, Shr, Sub}; @@ -121,19 +121,19 @@ impl BigNumRef { /// Adds a `u32` to `self`. #[corresponds(BN_add_word)] pub fn add_word(&mut self, w: u32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_add_word(self.as_ptr(), ffi::BN_ULONG::from(w))).map(|_| ()) } + unsafe { cvt(ffi::BN_add_word(self.as_ptr(), ffi::BN_ULONG::from(w))) } } /// Subtracts a `u32` from `self`. #[corresponds(BN_sub_word)] pub fn sub_word(&mut self, w: u32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_sub_word(self.as_ptr(), ffi::BN_ULONG::from(w))).map(|_| ()) } + unsafe { cvt(ffi::BN_sub_word(self.as_ptr(), ffi::BN_ULONG::from(w))) } } /// Multiplies a `u32` by `self`. #[corresponds(BN_mul_word)] pub fn mul_word(&mut self, w: u32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_mul_word(self.as_ptr(), ffi::BN_ULONG::from(w))).map(|_| ()) } + unsafe { cvt(ffi::BN_mul_word(self.as_ptr(), ffi::BN_ULONG::from(w))) } } /// Divides `self` by a `u32`, returning the remainder. @@ -168,13 +168,13 @@ impl BigNumRef { /// number less than `self` in `rnd`. #[corresponds(BN_rand_range)] pub fn rand_range(&self, rnd: &mut BigNumRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_rand_range(rnd.as_ptr(), self.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_rand_range(rnd.as_ptr(), self.as_ptr())) } } /// The cryptographically weak counterpart to `rand_in_range`. #[corresponds(BN_pseudo_rand_range)] pub fn pseudo_rand_range(&self, rnd: &mut BigNumRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_pseudo_rand_range(rnd.as_ptr(), self.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_pseudo_rand_range(rnd.as_ptr(), self.as_ptr())) } } /// Sets bit `n`. Equivalent to `self |= (1 << n)`. @@ -183,7 +183,7 @@ impl BigNumRef { #[corresponds(BN_set_bit)] #[allow(clippy::useless_conversion)] pub fn set_bit(&mut self, n: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_set_bit(self.as_ptr(), n.into())).map(|_| ()) } + unsafe { cvt(ffi::BN_set_bit(self.as_ptr(), n.into())) } } /// Clears bit `n`, setting it to 0. Equivalent to `self &= ~(1 << n)`. @@ -192,7 +192,7 @@ impl BigNumRef { #[corresponds(BN_clear_bit)] #[allow(clippy::useless_conversion)] pub fn clear_bit(&mut self, n: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_clear_bit(self.as_ptr(), n.into())).map(|_| ()) } + unsafe { cvt(ffi::BN_clear_bit(self.as_ptr(), n.into())) } } /// Returns `true` if the `n`th bit of `self` is set to 1, `false` otherwise. @@ -209,19 +209,19 @@ impl BigNumRef { #[corresponds(BN_mask_bits)] #[allow(clippy::useless_conversion)] pub fn mask_bits(&mut self, n: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_mask_bits(self.as_ptr(), n.into())).map(|_| ()) } + unsafe { cvt(ffi::BN_mask_bits(self.as_ptr(), n.into())) } } /// Places `a << 1` in `self`. Equivalent to `self * 2`. #[corresponds(BN_lshift1)] pub fn lshift1(&mut self, a: &BigNumRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_lshift1(self.as_ptr(), a.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_lshift1(self.as_ptr(), a.as_ptr())) } } /// Places `a >> 1` in `self`. Equivalent to `self / 2`. #[corresponds(BN_rshift1)] pub fn rshift1(&mut self, a: &BigNumRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_rshift1(self.as_ptr(), a.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_rshift1(self.as_ptr(), a.as_ptr())) } } /// Places `a + b` in `self`. [`core::ops::Add`] is also implemented for `BigNumRef`. @@ -229,7 +229,7 @@ impl BigNumRef { /// [`core::ops::Add`]: struct.BigNumRef.html#method.add #[corresponds(BN_add)] pub fn checked_add(&mut self, a: &BigNumRef, b: &BigNumRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_add(self.as_ptr(), a.as_ptr(), b.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_add(self.as_ptr(), a.as_ptr(), b.as_ptr())) } } /// Places `a - b` in `self`. [`core::ops::Sub`] is also implemented for `BigNumRef`. @@ -237,21 +237,21 @@ impl BigNumRef { /// [`core::ops::Sub`]: struct.BigNumRef.html#method.sub #[corresponds(BN_sub)] pub fn checked_sub(&mut self, a: &BigNumRef, b: &BigNumRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_sub(self.as_ptr(), a.as_ptr(), b.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_sub(self.as_ptr(), a.as_ptr(), b.as_ptr())) } } /// Places `a << n` in `self`. Equivalent to `a * 2 ^ n`. #[corresponds(BN_lshift)] #[allow(clippy::useless_conversion)] pub fn lshift(&mut self, a: &BigNumRef, n: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_lshift(self.as_ptr(), a.as_ptr(), n.into())).map(|_| ()) } + unsafe { cvt(ffi::BN_lshift(self.as_ptr(), a.as_ptr(), n.into())) } } /// Places `a >> n` in `self`. Equivalent to `a / 2 ^ n`. #[corresponds(BN_rshift)] #[allow(clippy::useless_conversion)] pub fn rshift(&mut self, a: &BigNumRef, n: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_rshift(self.as_ptr(), a.as_ptr(), n.into())).map(|_| ()) } + unsafe { cvt(ffi::BN_rshift(self.as_ptr(), a.as_ptr(), n.into())) } } /// Creates a new BigNum with the same value. @@ -339,7 +339,6 @@ impl BigNumRef { msb.0, c_int::from(odd), )) - .map(|_| ()) } } @@ -354,7 +353,6 @@ impl BigNumRef { msb.0, c_int::from(odd), )) - .map(|_| ()) } } @@ -392,13 +390,12 @@ impl BigNumRef { unsafe { cvt(ffi::BN_generate_prime_ex( self.as_ptr(), - bits as c_int, + c_int::from(bits), c_int::from(safe), add.map(|n| n.as_ptr()).unwrap_or(ptr::null_mut()), rem.map(|n| n.as_ptr()).unwrap_or(ptr::null_mut()), ptr::null_mut(), )) - .map(|_| ()) } } @@ -420,7 +417,6 @@ impl BigNumRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -443,7 +439,6 @@ impl BigNumRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -463,7 +458,6 @@ impl BigNumRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -484,14 +478,13 @@ impl BigNumRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } /// Places the result of `a²` in `self`. #[corresponds(BN_sqr)] pub fn sqr(&mut self, a: &BigNumRef, ctx: &mut BigNumContextRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_sqr(self.as_ptr(), a.as_ptr(), ctx.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_sqr(self.as_ptr(), a.as_ptr(), ctx.as_ptr())) } } /// Places the result of `a mod m` in `self`. As opposed to `div_rem` @@ -510,7 +503,6 @@ impl BigNumRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -531,7 +523,6 @@ impl BigNumRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -552,7 +543,6 @@ impl BigNumRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -573,7 +563,6 @@ impl BigNumRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -592,7 +581,6 @@ impl BigNumRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -611,7 +599,6 @@ impl BigNumRef { p.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -632,7 +619,6 @@ impl BigNumRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -670,7 +656,6 @@ impl BigNumRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -846,7 +831,7 @@ impl BigNum { ffi::init(); let c_str = CString::new(s.as_bytes()).map_err(ErrorStack::internal_error)?; let mut bn = ptr::null_mut(); - cvt(ffi::BN_dec2bn(&mut bn, c_str.as_ptr() as *const _))?; + cvt(ffi::BN_dec2bn(&mut bn, c_str.as_ptr()))?; Ok(BigNum::from_ptr(bn)) } } @@ -858,7 +843,7 @@ impl BigNum { ffi::init(); let c_str = CString::new(s.as_bytes()).map_err(ErrorStack::internal_error)?; let mut bn = ptr::null_mut(); - cvt(ffi::BN_hex2bn(&mut bn, c_str.as_ptr() as *const _))?; + cvt(ffi::BN_hex2bn(&mut bn, c_str.as_ptr()))?; Ok(BigNum::from_ptr(bn)) } } @@ -880,12 +865,7 @@ impl BigNum { unsafe { ffi::init(); assert!(n.len() <= c_int::MAX as usize); - cvt_p(ffi::BN_bin2bn( - n.as_ptr(), - n.len() as size_t, - ptr::null_mut(), - )) - .map(|p| BigNum::from_ptr(p)) + cvt_p(ffi::BN_bin2bn(n.as_ptr(), n.len(), ptr::null_mut())).map(|p| BigNum::from_ptr(p)) } } } diff --git a/boring/src/derive.rs b/boring/src/derive.rs index 1952118d4..a094c66d7 100644 --- a/boring/src/derive.rs +++ b/boring/src/derive.rs @@ -44,7 +44,7 @@ impl<'a> Deriver<'a> { where T: HasPublic, { - unsafe { cvt(ffi::EVP_PKEY_derive_set_peer(self.0, key.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::EVP_PKEY_derive_set_peer(self.0, key.as_ptr())) } } /// Returns the size of the shared secret. @@ -64,14 +64,7 @@ impl<'a> Deriver<'a> { #[corresponds(EVP_PKEY_derive)] pub fn derive(&mut self, buf: &mut [u8]) -> Result { let mut len = buf.len(); - unsafe { - cvt(ffi::EVP_PKEY_derive( - self.0, - buf.as_mut_ptr() as *mut _, - &mut len, - )) - .map(|_| len) - } + unsafe { cvt(ffi::EVP_PKEY_derive(self.0, buf.as_mut_ptr(), &mut len)).map(|_| len) } } /// A convenience function which derives a shared secret and returns it in a new buffer. diff --git a/boring/src/dsa.rs b/boring/src/dsa.rs index be13da9fa..36562699e 100644 --- a/boring/src/dsa.rs +++ b/boring/src/dsa.rs @@ -5,7 +5,6 @@ //! using the private key that can be validated with the public key but not be generated //! without the private key. -use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; use libc::c_uint; use openssl_macros::corresponds; @@ -15,7 +14,9 @@ use std::ptr; use crate::bn::{BigNum, BigNumRef}; use crate::error::ErrorStack; +use crate::ffi; use crate::pkey::{HasParams, HasPrivate, HasPublic, Private, Public}; +use crate::try_int; use crate::{cvt, cvt_p}; generic_foreign_type_and_impl_send_sync! { @@ -103,7 +104,7 @@ where unsafe { let mut pub_key = ptr::null(); DSA_get0_key(self.as_ptr(), &mut pub_key, ptr::null_mut()); - BigNumRef::from_ptr(pub_key as *mut _) + BigNumRef::from_ptr(pub_key.cast_mut()) } } } @@ -132,7 +133,7 @@ where unsafe { let mut priv_key = ptr::null(); DSA_get0_key(self.as_ptr(), ptr::null_mut(), &mut priv_key); - BigNumRef::from_ptr(priv_key as *mut _) + BigNumRef::from_ptr(priv_key.cast_mut()) } } } @@ -154,7 +155,7 @@ where unsafe { let mut p = ptr::null(); DSA_get0_pqg(self.as_ptr(), &mut p, ptr::null_mut(), ptr::null_mut()); - BigNumRef::from_ptr(p as *mut _) + BigNumRef::from_ptr(p.cast_mut()) } } @@ -164,7 +165,7 @@ where unsafe { let mut q = ptr::null(); DSA_get0_pqg(self.as_ptr(), ptr::null_mut(), &mut q, ptr::null_mut()); - BigNumRef::from_ptr(q as *mut _) + BigNumRef::from_ptr(q.cast_mut()) } } @@ -174,7 +175,7 @@ where unsafe { let mut g = ptr::null(); DSA_get0_pqg(self.as_ptr(), ptr::null_mut(), ptr::null_mut(), &mut g); - BigNumRef::from_ptr(g as *mut _) + BigNumRef::from_ptr(g.cast_mut()) } } } @@ -195,7 +196,7 @@ impl Dsa { let dsa = Dsa::from_ptr(cvt_p(ffi::DSA_new())?); cvt(ffi::DSA_generate_parameters_ex( dsa.0, - bits as c_uint, + c_uint::from(bits), ptr::null(), 0, ptr::null_mut(), @@ -300,7 +301,7 @@ mod test { let mut ctx = BigNumContext::new().unwrap(); let mut calc = BigNum::new().unwrap(); calc.mod_exp(g, priv_key, p, &mut ctx).unwrap(); - assert_eq!(&calc, pub_key) + assert_eq!(&calc, pub_key); } #[test] diff --git a/boring/src/ec.rs b/boring/src/ec.rs index 588408aa2..9eb7a4010 100644 --- a/boring/src/ec.rs +++ b/boring/src/ec.rs @@ -15,7 +15,6 @@ //! [`EcGroup`]: struct.EcGroup.html //! [`Nid`]: ../nid/struct.Nid.html //! [Eliptic Curve Cryptography]: https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography -use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; use libc::c_int; use openssl_macros::corresponds; @@ -24,8 +23,10 @@ use std::ptr; use crate::bn::{BigNumContextRef, BigNumRef}; use crate::error::ErrorStack; +use crate::ffi; use crate::nid::Nid; use crate::pkey::{HasParams, HasPrivate, HasPublic, Params, Private, Public}; +use crate::try_int; use crate::{cvt, cvt_n, cvt_p, init}; /// Compressed or Uncompressed conversion @@ -143,7 +144,6 @@ impl EcGroupRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -160,7 +160,6 @@ impl EcGroupRef { cofactor.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -185,7 +184,7 @@ impl EcGroupRef { pub fn generator(&self) -> &EcPointRef { unsafe { let ptr = ffi::EC_GROUP_get0_generator(self.as_ptr()); - EcPointRef::from_ptr(ptr as *mut _) + EcPointRef::from_ptr(ptr.cast_mut()) } } @@ -202,7 +201,6 @@ impl EcGroupRef { order.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -260,7 +258,6 @@ impl EcPointRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -271,8 +268,7 @@ impl EcPointRef { group: &EcGroupRef, q: &EcPointRef, m: &BigNumRef, - // FIXME should be &mut - ctx: &BigNumContextRef, + ctx: &mut BigNumContextRef, ) -> Result<(), ErrorStack> { unsafe { cvt(ffi::EC_POINT_mul( @@ -283,7 +279,6 @@ impl EcPointRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -292,8 +287,7 @@ impl EcPointRef { &mut self, group: &EcGroupRef, n: &BigNumRef, - // FIXME should be &mut - ctx: &BigNumContextRef, + ctx: &mut BigNumContextRef, ) -> Result<(), ErrorStack> { unsafe { cvt(ffi::EC_POINT_mul( @@ -304,7 +298,6 @@ impl EcPointRef { ptr::null(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -326,7 +319,6 @@ impl EcPointRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -339,7 +331,6 @@ impl EcPointRef { self.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -428,7 +419,6 @@ impl EcPointRef { y.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } } @@ -506,7 +496,7 @@ where pub fn private_key(&self) -> &BigNumRef { unsafe { let ptr = ffi::EC_KEY_get0_private_key(self.as_ptr()); - BigNumRef::from_ptr(ptr as *mut _) + BigNumRef::from_ptr(ptr.cast_mut()) } } } @@ -521,7 +511,7 @@ where pub fn public_key(&self) -> &EcPointRef { unsafe { let ptr = ffi::EC_KEY_get0_public_key(self.as_ptr()); - EcPointRef::from_ptr(ptr as *mut _) + EcPointRef::from_ptr(ptr.cast_mut()) } } @@ -552,14 +542,14 @@ where pub fn group(&self) -> &EcGroupRef { unsafe { let ptr = ffi::EC_KEY_get0_group(self.as_ptr()); - EcGroupRef::from_ptr(ptr as *mut _) + EcGroupRef::from_ptr(ptr.cast_mut()) } } /// Checks the key for validity. #[corresponds(EC_KEY_check_key)] pub fn check_key(&self) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::EC_KEY_check_key(self.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::EC_KEY_check_key(self.as_ptr())) } } } @@ -847,7 +837,7 @@ mod test { let mut ctx = BigNumContext::new().unwrap(); let mut public_key = EcPoint::new(&group).unwrap(); public_key - .mul_generator(&group, key.private_key(), &ctx) + .mul_generator(&group, key.private_key(), &mut ctx) .unwrap(); assert!(public_key.eq(&group, key.public_key(), &mut ctx).unwrap()); } @@ -859,7 +849,7 @@ mod test { let one = BigNum::from_u32(1).unwrap(); let mut ctx = BigNumContext::new().unwrap(); let mut ecp = EcPoint::new(&group).unwrap(); - ecp.mul_generator(&group, &one, &ctx).unwrap(); + ecp.mul_generator(&group, &one, &mut ctx).unwrap(); assert!(ecp.eq(&group, gen, &mut ctx).unwrap()); } diff --git a/boring/src/ecdsa.rs b/boring/src/ecdsa.rs index 4d5bb0979..27be5c51f 100644 --- a/boring/src/ecdsa.rs +++ b/boring/src/ecdsa.rs @@ -2,7 +2,7 @@ use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; -use libc::{c_int, size_t}; +use libc::c_int; use openssl_macros::corresponds; use std::mem; use std::ptr; @@ -36,10 +36,10 @@ impl EcdsaSig { assert!(data.len() <= c_int::MAX as usize); let sig = cvt_p(ffi::ECDSA_do_sign( data.as_ptr(), - data.len() as size_t, + data.len(), eckey.as_ptr(), ))?; - Ok(EcdsaSig::from_ptr(sig as *mut _)) + Ok(EcdsaSig::from_ptr(sig)) } } @@ -51,7 +51,7 @@ impl EcdsaSig { let sig = cvt_p(ffi::ECDSA_SIG_new())?; ECDSA_SIG_set0(sig, r.as_ptr(), s.as_ptr()); mem::forget((r, s)); - Ok(EcdsaSig::from_ptr(sig as *mut _)) + Ok(EcdsaSig::from_ptr(sig)) } } @@ -83,7 +83,7 @@ impl EcdsaSigRef { assert!(data.len() <= c_int::MAX as usize); cvt_n(ffi::ECDSA_do_verify( data.as_ptr(), - data.len() as size_t, + data.len(), self.as_ptr(), eckey.as_ptr(), )) @@ -98,7 +98,7 @@ impl EcdsaSigRef { unsafe { let mut r = ptr::null(); ECDSA_SIG_get0(self.as_ptr(), &mut r, ptr::null_mut()); - BigNumRef::from_ptr(r as *mut _) + BigNumRef::from_ptr(r.cast_mut()) } } @@ -109,7 +109,7 @@ impl EcdsaSigRef { unsafe { let mut s = ptr::null(); ECDSA_SIG_get0(self.as_ptr(), ptr::null_mut(), &mut s); - BigNumRef::from_ptr(s as *mut _) + BigNumRef::from_ptr(s.cast_mut()) } } } diff --git a/boring/src/error.rs b/boring/src/error.rs index 04815863d..5087d10fb 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -38,6 +38,9 @@ pub struct ErrorStack(Vec); impl ErrorStack { /// Pops the contents of the OpenSSL error stack, and returns it. + /// + /// This should be used only immediately after calling Boring FFI functions, + /// otherwise the stack may be empty or a leftover from unrelated calls. #[corresponds(ERR_get_error_line_data)] #[must_use = "Use ErrorStack::clear() to drop the error stack"] pub fn get() -> ErrorStack { @@ -62,6 +65,12 @@ impl ErrorStack { Self(vec![Error::new_internal(Data::String(err.to_string()))]) } + /// Used to report errors from the Rust crate + #[cold] + pub(crate) fn internal_error_str(message: &'static str) -> Self { + Self(vec![Error::new_internal(Data::Static(message))]) + } + /// Empties the current thread's error queue. #[corresponds(ERR_clear_error)] pub(crate) fn clear() { @@ -94,7 +103,7 @@ impl fmt::Display for ErrorStack { write!( fmt, "[{}]", - err.reason_internal() + err.reason() .or_else(|| err.library()) .unwrap_or("unknown reason") )?; @@ -131,6 +140,7 @@ enum Data { None, CString(CString), String(String), + Static(&'static str), } unsafe impl Sync for Error {} @@ -242,7 +252,10 @@ impl Error { /// Returns the reason for the error. #[must_use] - pub fn reason(&self) -> Option<&'static str> { + pub fn reason(&self) -> Option<&str> { + if self.is_internal() { + return self.data(); + } unsafe { let cstr = ffi::ERR_reason_error_string(self.code); if cstr.is_null() { @@ -293,6 +306,7 @@ impl Error { Data::None => None, Data::CString(cstring) => cstring.to_str().ok(), Data::String(s) => Some(s), + Data::Static(s) => Some(s), } } @@ -302,6 +316,7 @@ impl Error { Data::None => return None, Data::CString(cstr) => return Some(Cow::Borrowed(cstr)), Data::String(s) => s.as_str(), + Data::Static(s) => s, }; CString::new(s).ok().map(Cow::Owned) } @@ -318,15 +333,6 @@ impl Error { fn is_internal(&self) -> bool { std::ptr::eq(self.file, BORING_INTERNAL.as_ptr()) } - - // reason() needs 'static - fn reason_internal(&self) -> Option<&str> { - if self.is_internal() { - self.data() - } else { - self.reason() - } - } } impl fmt::Debug for Error { @@ -357,7 +363,7 @@ impl fmt::Display for Error { write!( fmt, "{}\n\nCode: {:08X}\nLoc: {}:{}", - self.reason_internal().unwrap_or("unknown TLS error"), + self.reason().unwrap_or("unknown TLS error"), &self.code, self.file(), self.line() diff --git a/boring/src/fips.rs b/boring/src/fips.rs index 8e4512264..708b0903f 100644 --- a/boring/src/fips.rs +++ b/boring/src/fips.rs @@ -15,16 +15,8 @@ pub fn enabled() -> bool { #[test] fn is_enabled() { - #[cfg(any( - feature = "fips", - feature = "fips-precompiled", - feature = "fips-link-precompiled" - ))] + #[cfg(feature = "fips")] assert!(enabled()); - #[cfg(not(any( - feature = "fips", - feature = "fips-precompiled", - feature = "fips-link-precompiled" - )))] + #[cfg(not(feature = "fips"))] assert!(!enabled()); } diff --git a/boring/src/hash.rs b/boring/src/hash.rs index 5deadade2..543e401fd 100644 --- a/boring/src/hash.rs +++ b/boring/src/hash.rs @@ -1,7 +1,5 @@ -use crate::ffi; use openssl_macros::corresponds; -use std::convert::TryInto; -use std::ffi::{c_uint, c_void}; +use std::ffi::c_uint; use std::fmt; use std::io; use std::io::prelude::*; @@ -9,8 +7,10 @@ use std::ops::{Deref, DerefMut}; use std::ptr; use crate::error::ErrorStack; +use crate::ffi; use crate::ffi::{EVP_MD_CTX_free, EVP_MD_CTX_new}; use crate::nid::Nid; +use crate::try_int; use crate::{cvt, cvt_p}; #[derive(Copy, Clone, PartialEq, Eq)] @@ -196,7 +196,7 @@ impl Hasher { unsafe { cvt(ffi::EVP_DigestUpdate( self.ctx, - data.as_ptr() as *mut _, + data.as_ptr().cast_mut().cast(), data.len(), ))?; } @@ -210,7 +210,7 @@ impl Hasher { self.init()?; } unsafe { - let mut len = ffi::EVP_MAX_MD_SIZE.try_into().unwrap(); + let mut len = try_int(ffi::EVP_MAX_MD_SIZE)?; let mut buf = [0; ffi::EVP_MAX_MD_SIZE as usize]; cvt(ffi::EVP_DigestFinal_ex( self.ctx, @@ -220,7 +220,7 @@ impl Hasher { self.state = Finalized; Ok(DigestBytes { buf, - len: len as usize, + len: try_int(len)?, }) } } @@ -359,7 +359,7 @@ pub fn hmac_sha1(key: &[u8], data: &[u8]) -> Result<[u8; 20], ErrorStack> { hmac(MessageDigest::sha1(), key, data) } -fn hmac( +pub(crate) fn hmac( digest: MessageDigest, key: &[u8], data: &[u8], @@ -370,7 +370,7 @@ fn hmac( cvt_p(unsafe { ffi::HMAC( digest.as_ptr(), - key.as_ptr() as *const c_void, + key.as_ptr().cast(), key.len(), data.as_ptr(), data.len(), diff --git a/boring/src/hmac.rs b/boring/src/hmac.rs new file mode 100644 index 000000000..a015409cb --- /dev/null +++ b/boring/src/hmac.rs @@ -0,0 +1,130 @@ +use crate::cvt; +use crate::error::ErrorStack; +use crate::foreign_types::ForeignTypeRef; +use crate::hash::MessageDigest; +use openssl_macros::corresponds; + +foreign_type_and_impl_send_sync! { + type CType = ffi::HMAC_CTX; + fn drop = ffi::HMAC_CTX_free; + + pub struct HmacCtx; +} + +impl HmacCtxRef { + /// Configures HmacCtx to use `md` as the hash function and `key` as the key. + /// + #[corresponds(HMAC_Init_ex)] + pub fn init(&mut self, key: &[u8], md: &MessageDigest) -> Result<(), ErrorStack> { + ffi::init(); + + unsafe { + cvt(ffi::HMAC_Init_ex( + self.as_ptr(), + key.as_ptr().cast(), + key.len(), + md.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + )) + } + } +} + +/// Provides an init-update-finalize API for HMAC. +pub struct Hmac(*mut ffi::HMAC_CTX); + +impl Hmac { + /// Creates a new HMAC object with the given key and hash algorithm. + pub fn init(key: &[u8], md: &MessageDigest) -> Result { + ffi::init(); + + let ctx = unsafe { + let ctx = ffi::HMAC_CTX_new(); + cvt(ffi::HMAC_Init_ex( + ctx, + key.as_ptr().cast(), + key.len(), + md.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + ))?; + ctx + }; + + Ok(Hmac(ctx)) + } + + /// Updates the HMAC input. + pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::HMAC_Update(self.0, data.as_ptr().cast(), data.len())) } + } + + /// Finalizes the HMAC and returns the output. + pub fn finalize(self) -> Result, ErrorStack> { + let out_len = unsafe { ffi::HMAC_size(self.0) }; + let mut out = vec![0; out_len]; + unsafe { + cvt(ffi::HMAC_Final( + self.0, + out.as_mut_ptr().cast(), + // ENGINE api is deprecated + core::ptr::null_mut(), + ))?; + } + Ok(out) + } +} + +impl Drop for Hmac { + fn drop(&mut self) { + unsafe { ffi::HMAC_CTX_free(self.0) } + } +} + +#[cfg(test)] +mod tests { + use crate::hash; + + use super::*; + + fn test(md: MessageDigest) { + assert_eq!(N, md.size()); + let key = vec![0; N]; + let message_parts = [ + b"hello".to_vec(), + b"world!".to_vec(), + b"".to_vec(), + vec![0; 23], + b"fella guy".to_vec(), + ]; + let message = message_parts.concat(); + + let mut hmac = Hmac::init(&key, &md).unwrap(); + for part in &message_parts { + hmac.update(part).unwrap(); + } + let res = hmac.finalize().unwrap(); + assert_eq!(res, hash::hmac::(md, &key, &message).unwrap()); + } + + #[test] + fn test_sha1() { + test::<20>(MessageDigest::sha1()); + } + + #[test] + fn test_sha256() { + test::<32>(MessageDigest::sha256()); + } + + #[test] + fn test_sha384() { + test::<48>(MessageDigest::sha384()); + } + + #[test] + fn test_sha512() { + test::<64>(MessageDigest::sha512()); + } +} diff --git a/boring/src/lib.rs b/boring/src/lib.rs index e9d0e01b0..d1d87e595 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -106,13 +106,12 @@ extern crate libc; #[cfg(test)] extern crate hex; -use std::ffi::{c_long, c_void}; +use std::ffi::{c_int, c_long, c_void}; +use std::num::NonZeroUsize; #[doc(inline)] pub use crate::ffi::init; -use libc::{c_int, size_t}; - use crate::error::ErrorStack; #[macro_use] @@ -135,9 +134,10 @@ pub mod error; pub mod ex_data; pub mod fips; pub mod hash; -#[cfg(not(feature = "fips"))] +pub mod hmac; pub mod hpke; pub mod memcmp; +pub mod mlkem; pub mod nid; pub mod pkcs12; pub mod pkcs5; @@ -162,11 +162,11 @@ fn cvt_p(r: *mut T) -> Result<*mut T, ErrorStack> { } } -fn cvt_0(r: size_t) -> Result { +fn cvt_0(r: usize) -> Result<(), ErrorStack> { if r == 0 { Err(ErrorStack::get()) } else { - Ok(r) + Ok(()) } } @@ -178,14 +178,21 @@ fn cvt_0i(r: c_int) -> Result { } } -fn cvt(r: c_int) -> Result { +fn cvt(r: c_int) -> Result<(), ErrorStack> { if r <= 0 { Err(ErrorStack::get()) } else { - Ok(r) + Ok(()) } } +fn cvt_nz(r: c_int) -> Result { + usize::try_from(r) + .ok() + .and_then(NonZeroUsize::new) + .ok_or_else(ErrorStack::get) +} + fn cvt_n(r: c_int) -> Result { if r < 0 { Err(ErrorStack::get()) @@ -194,6 +201,15 @@ fn cvt_n(r: c_int) -> Result { } } +fn try_int(from: F) -> Result +where + F: TryInto + Send + Sync + Copy + 'static, + T: Send + Sync + Copy + 'static, +{ + from.try_into() + .map_err(|_| ErrorStack::internal_error_str("int overflow")) +} + unsafe extern "C" fn free_data_box( _parent: *mut c_void, ptr: *mut c_void, @@ -203,6 +219,6 @@ unsafe extern "C" fn free_data_box( _argp: *mut c_void, ) { if !ptr.is_null() { - drop(Box::::from_raw(ptr as *mut T)); + drop(Box::::from_raw(ptr.cast::())); } } diff --git a/boring/src/macros.rs b/boring/src/macros.rs index b6cbeb8fc..f5bffae2f 100644 --- a/boring/src/macros.rs +++ b/boring/src/macros.rs @@ -7,7 +7,8 @@ macro_rules! private_key_from_pem { unsafe { ffi::init(); let bio = crate::bio::MemBioSlice::new(pem)?; - let passphrase = ::std::ffi::CString::new(passphrase).unwrap(); + let passphrase = ::std::ffi::CString::new(passphrase) + .map_err(crate::error::ErrorStack::internal_error)?; cvt_p($f(bio.as_ptr(), ptr::null_mut(), None, @@ -27,7 +28,7 @@ macro_rules! private_key_from_pem { cvt_p($f(bio.as_ptr(), ptr::null_mut(), Some(crate::util::invoke_passwd_cb::), - &mut cb as *mut _ as *mut _)) + ptr::from_mut(&mut cb).cast())) .map(|p| ::foreign_types::ForeignType::from_ptr(p)) } } @@ -59,12 +60,11 @@ macro_rules! private_key_to_pem { ) -> Result, crate::error::ErrorStack> { unsafe { let bio = crate::bio::MemBio::new()?; - assert!(passphrase.len() <= ::libc::c_int::MAX as usize); cvt($f(bio.as_ptr(), self.as_ptr(), cipher.as_ptr(), passphrase.as_ptr() as *const _ as *mut _, - passphrase.len() as ::libc::c_int, + try_int(passphrase.len())?, None, ptr::null_mut()))?; Ok(bio.get_buf().to_owned()) @@ -91,9 +91,9 @@ macro_rules! to_der { $(#[$m])* pub fn $n(&self) -> Result, crate::error::ErrorStack> { unsafe { - let len = crate::cvt($f(::foreign_types::ForeignTypeRef::as_ptr(self), + let len = crate::cvt_nz($f(::foreign_types::ForeignTypeRef::as_ptr(self), ptr::null_mut()))?; - let mut buf = vec![0; len as usize]; + let mut buf = vec![0; len.get()]; crate::cvt($f(::foreign_types::ForeignTypeRef::as_ptr(self), &mut buf.as_mut_ptr()))?; Ok(buf) diff --git a/boring/src/memcmp.rs b/boring/src/memcmp.rs index 99cefdbee..4ac08b655 100644 --- a/boring/src/memcmp.rs +++ b/boring/src/memcmp.rs @@ -30,7 +30,6 @@ //! assert!(!eq(&a, &c)); //! ``` use crate::ffi; -use libc::size_t; /// Returns `true` iff `a` and `b` contain the same bytes. /// @@ -64,13 +63,7 @@ use libc::size_t; #[must_use] pub fn eq(a: &[u8], b: &[u8]) -> bool { assert!(a.len() == b.len()); - let ret = unsafe { - ffi::CRYPTO_memcmp( - a.as_ptr() as *const _, - b.as_ptr() as *const _, - a.len() as size_t, - ) - }; + let ret = unsafe { ffi::CRYPTO_memcmp(a.as_ptr().cast(), b.as_ptr().cast(), a.len()) }; ret == 0 } diff --git a/boring/src/mlkem.rs b/boring/src/mlkem.rs new file mode 100644 index 000000000..f5b84147e --- /dev/null +++ b/boring/src/mlkem.rs @@ -0,0 +1,767 @@ +//! ML-KEM (FIPS 203) post-quantum key encapsulation. +//! +//! ML-KEM is a low-level cryptographic primitive. For most applications, +//! using higher-level constructions like HPKE is preferred. +//! Note that it's also enabled in TLS by default, in the X25519MLKEM768 exchange. +//! +//! Provides ML-KEM-768 (recommended) and ML-KEM-1024 variants via [`Algorithm`]. +//! +//! ``` +//! use boring::mlkem::{Algorithm, MlKemPrivateKey}; +//! +//! let (public_key, private_key) = MlKemPrivateKey::generate(Algorithm::MlKem768).unwrap(); +//! let (ciphertext, shared_secret) = public_key.encapsulate().unwrap(); +//! let decrypted = private_key.decapsulate(&ciphertext).unwrap(); +//! assert_eq!(shared_secret, decrypted); +//! ``` + +use std::fmt; +use std::mem::MaybeUninit; + +use crate::cvt; +use crate::error::ErrorStack; +use crate::ffi; + +// CBS_init is inline in BoringSSL, so bindgen can't generate bindings for it. +#[inline] +fn cbs_init(data: &[u8]) -> ffi::CBS { + ffi::CBS { + data: data.as_ptr(), + len: data.len(), + } +} + +/// Private key seed size (64 bytes). +pub const PRIVATE_KEY_SEED_BYTES: usize = ffi::MLKEM_SEED_BYTES as usize; + +/// Shared secret size (32 bytes). +pub const SHARED_SECRET_BYTES: usize = ffi::MLKEM_SHARED_SECRET_BYTES as usize; + +/// Raw bytes of the private key seed ([`PRIVATE_KEY_SEED_BYTES`] long) +pub type MlKemPrivateKeySeed = [u8; PRIVATE_KEY_SEED_BYTES]; + +/// Raw bytes of the shared secret ([`SHARED_SECRET_BYTES`] long) +pub type MlKemSharedSecret = [u8; SHARED_SECRET_BYTES]; + +/// ML-KEM runtime algorithm selection. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Algorithm { + /// Recommended. AES-192 equivalent security. + MlKem768, + /// AES-256 equivalent security. + MlKem1024, +} + +impl Algorithm { + /// Returns 1184 for ML-KEM-768, 1568 for ML-KEM-1024. + #[must_use] + pub const fn public_key_bytes(&self) -> usize { + match self { + Self::MlKem768 => MlKem768PublicKey::PUBLIC_KEY_BYTES, + Self::MlKem1024 => MlKem1024PublicKey::PUBLIC_KEY_BYTES, + } + } + + /// Returns 1088 for ML-KEM-768, 1568 for ML-KEM-1024. + #[must_use] + pub const fn ciphertext_bytes(&self) -> usize { + match self { + Self::MlKem768 => MlKem768PrivateKey::CIPHERTEXT_BYTES, + Self::MlKem1024 => MlKem1024PrivateKey::CIPHERTEXT_BYTES, + } + } +} + +#[derive(Clone)] +pub struct MlKemPublicKey(Either, Box>); + +#[derive(Clone)] +pub struct MlKemPrivateKey(Either, Box>); + +#[derive(Clone)] +enum Either { + MlKem768(T768), + MlKem1024(T1024), +} + +impl MlKemPrivateKey { + /// Generates a new key pair, returning `(public_key, private_key)`. + /// + /// The private key is a 64-byte seed. Keep it secret. + pub fn generate(algorithm: Algorithm) -> Result<(MlKemPublicKey, MlKemPrivateKey), ErrorStack> { + match algorithm { + Algorithm::MlKem768 => { + let (pk, sk) = MlKem768PrivateKey::generate(); + Ok(( + MlKemPublicKey(Either::MlKem768(pk)), + MlKemPrivateKey(Either::MlKem768(sk)), + )) + } + Algorithm::MlKem1024 => { + let (pk, sk) = MlKem1024PrivateKey::generate(); + Ok(( + MlKemPublicKey(Either::MlKem1024(pk)), + MlKemPrivateKey(Either::MlKem1024(sk)), + )) + } + } + } +} + +impl MlKemPublicKey { + pub fn from_slice(algorithm: Algorithm, public_key: &[u8]) -> Result { + match algorithm { + Algorithm::MlKem768 => Ok(Self(Either::MlKem768(Box::new( + MlKem768PublicKey::from_slice(public_key)?, + )))), + Algorithm::MlKem1024 => Ok(Self(Either::MlKem1024(Box::new( + MlKem1024PublicKey::from_slice(public_key)?, + )))), + } + } + + /// Serialized bytes of the public key + pub fn as_bytes(&self) -> &[u8] { + match &self.0 { + Either::MlKem768(pk) => &pk.bytes, + Either::MlKem1024(pk) => &pk.bytes, + } + } + + /// Encapsulates a shared secret to the given public key, returning + /// `(ciphertext, shared_secret)`. + pub fn encapsulate(&self) -> Result<(Vec, MlKemSharedSecret), ErrorStack> { + match &self.0 { + Either::MlKem768(pk) => { + let (ct, ss) = pk.encapsulate(); + Ok((ct.to_vec(), ss)) + } + Either::MlKem1024(pk) => { + let (ct, ss) = pk.encapsulate(); + Ok((ct.to_vec(), ss)) + } + } + } + + /// Query public key and ciphertext length + pub fn algorithm(&self) -> Algorithm { + match self.0 { + Either::MlKem768(_) => Algorithm::MlKem768, + Either::MlKem1024(_) => Algorithm::MlKem1024, + } + } +} + +impl MlKemPrivateKey { + /// Expand private key from the seed bytes + pub fn from_seed( + algorithm: Algorithm, + private_seed: &MlKemPrivateKeySeed, + ) -> Result { + match algorithm { + Algorithm::MlKem768 => Ok(Self(Either::MlKem768(Box::new( + MlKem768PrivateKey::from_seed(private_seed)?, + )))), + Algorithm::MlKem1024 => Ok(Self(Either::MlKem1024(Box::new( + MlKem1024PrivateKey::from_seed(private_seed)?, + )))), + } + } + + /// Secret seed bytes of this private key + pub fn seed_bytes(&self) -> &MlKemPrivateKeySeed { + match &self.0 { + Either::MlKem768(sk) => &sk.seed, + Either::MlKem1024(sk) => &sk.seed, + } + } + + /// Decapsulates a shared secret from a ciphertext using the private key. + pub fn decapsulate(&self, ciphertext: &[u8]) -> Result { + match &self.0 { + Either::MlKem768(sk) => { + let ct: &[u8; MlKem768PrivateKey::CIPHERTEXT_BYTES] = ciphertext + .try_into() + .map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?; + Ok(sk.decapsulate(ct)) + } + Either::MlKem1024(sk) => { + let ct: &[u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES] = ciphertext + .try_into() + .map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?; + Ok(sk.decapsulate(ct)) + } + } + } + + /// Query public key and ciphertext length + pub fn algorithm(&self) -> Algorithm { + match self.0 { + Either::MlKem768(_) => Algorithm::MlKem768, + Either::MlKem1024(_) => Algorithm::MlKem1024, + } + } +} + +/// ML-KEM-768 private key. +/// +/// Caches the expanded key for fast decapsulation. +struct MlKem768PrivateKey { + seed: MlKemPrivateKeySeed, + expanded: ffi::MLKEM768_private_key, +} + +impl Clone for MlKem768PrivateKey { + fn clone(&self) -> Self { + // unwrap is safe: cloning a valid key with a valid seed always succeeds + Self::from_seed(&self.seed).unwrap() + } +} + +impl MlKem768PrivateKey { + pub const CIPHERTEXT_BYTES: usize = ffi::MLKEM768_CIPHERTEXT_BYTES as usize; + + /// Generate a new key pair. + #[must_use] + fn generate() -> (Box, Box) { + // SAFETY: all buffers are out parameters, correctly sized + unsafe { + ffi::init(); + let mut public_key_bytes: MaybeUninit<[u8; MlKem768PublicKey::PUBLIC_KEY_BYTES]> = + MaybeUninit::uninit(); + let mut seed: MaybeUninit = MaybeUninit::uninit(); + let mut expanded: MaybeUninit = MaybeUninit::uninit(); + + ffi::MLKEM768_generate_key( + public_key_bytes.as_mut_ptr().cast(), + seed.as_mut_ptr().cast(), + expanded.as_mut_ptr(), + ); + + let bytes = public_key_bytes.assume_init(); + + // Parse the public key bytes to get the parsed struct + let mut cbs = cbs_init(&bytes); + let mut parsed: MaybeUninit = MaybeUninit::uninit(); + ffi::MLKEM768_parse_public_key(parsed.as_mut_ptr(), &mut cbs); + + ( + Box::new(MlKem768PublicKey { + bytes, + parsed: parsed.assume_init(), + }), + Box::new(MlKem768PrivateKey { + seed: seed.assume_init(), + expanded: expanded.assume_init(), + }), + ) + } + } + + /// Restore private key from seed. + fn from_seed(seed: &MlKemPrivateKeySeed) -> Result { + // SAFETY: seed is 64 bytes, out parameter correctly sized + unsafe { + ffi::init(); + let mut expanded: MaybeUninit = MaybeUninit::uninit(); + cvt(ffi::MLKEM768_private_key_from_seed( + expanded.as_mut_ptr(), + seed.as_ptr(), + seed.len(), + ))?; + Ok(Self { + seed: *seed, + expanded: expanded.assume_init(), + }) + } + } + + /// Derive the public key. + #[cfg(test)] + fn public_key(&self) -> Result { + // SAFETY: expanded key is valid, buffers correctly sized + unsafe { + ffi::init(); + let mut parsed: MaybeUninit = MaybeUninit::uninit(); + ffi::MLKEM768_public_from_private(parsed.as_mut_ptr(), &self.expanded); + + let mut bytes = [0u8; MlKem768PublicKey::PUBLIC_KEY_BYTES]; + let mut cbb: MaybeUninit = MaybeUninit::uninit(); + cvt(ffi::CBB_init_fixed( + cbb.as_mut_ptr(), + bytes.as_mut_ptr(), + bytes.len(), + ))?; + cvt(ffi::MLKEM768_marshal_public_key( + cbb.as_mut_ptr(), + parsed.as_ptr(), + ))?; + + Ok(MlKem768PublicKey { + bytes, + parsed: parsed.assume_init(), + }) + } + } + + /// Decapsulate to get the shared secret. + fn decapsulate(&self, ciphertext: &[u8; Self::CIPHERTEXT_BYTES]) -> MlKemSharedSecret { + // SAFETY: expanded key is valid, ciphertext is correctly sized + unsafe { + ffi::init(); + let mut shared_secret = [0u8; SHARED_SECRET_BYTES]; + + ffi::MLKEM768_decap( + shared_secret.as_mut_ptr(), + ciphertext.as_ptr(), + ciphertext.len(), + &self.expanded, + ); + + shared_secret + } + } +} + +impl fmt::Debug for MlKem768PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MlKem768PrivateKey") + .field("key", &"[redacted]") + .finish() + } +} + +impl Drop for MlKem768PrivateKey { + fn drop(&mut self) { + // SAFETY: pointers and lengths are valid + unsafe { + ffi::OPENSSL_cleanse(self.seed.as_mut_ptr().cast(), self.seed.len()); + ffi::OPENSSL_cleanse( + self.expanded.opaque.bytes.as_mut_ptr().cast(), + self.expanded.opaque.bytes.len(), + ); + } + } +} + +/// ML-KEM-768 public key. +#[derive(Clone)] +struct MlKem768PublicKey { + bytes: [u8; Self::PUBLIC_KEY_BYTES], + parsed: ffi::MLKEM768_public_key, +} + +impl MlKem768PublicKey { + pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM768_PUBLIC_KEY_BYTES as usize; + + /// Parse and validate a public key. + /// + /// The slice must be [`Self::PUBLIC_KEY_BYTES`] long. + fn from_slice(slice: &[u8]) -> Result { + if slice.len() != Self::PUBLIC_KEY_BYTES { + return Err(ErrorStack::internal_error_str("invalid public key length")); + } + + // SAFETY: CBS correctly initialized, length already checked + unsafe { + ffi::init(); + let mut cbs = cbs_init(slice); + let mut parsed: MaybeUninit = MaybeUninit::uninit(); + + cvt(ffi::MLKEM768_parse_public_key( + parsed.as_mut_ptr(), + &mut cbs, + ))?; + if cbs.len != 0 { + return Err(ErrorStack::internal_error_str( + "trailing bytes after public key", + )); + } + + let mut bytes = [0u8; Self::PUBLIC_KEY_BYTES]; + bytes.copy_from_slice(slice); + Ok(Self { + bytes, + parsed: parsed.assume_init(), + }) + } + } + + /// Encapsulate: returns (ciphertext, shared_secret). + fn encapsulate( + &self, + ) -> ( + [u8; MlKem768PrivateKey::CIPHERTEXT_BYTES], + MlKemSharedSecret, + ) { + // SAFETY: buffers correctly sized, parsed key is valid + unsafe { + ffi::init(); + let mut ciphertext = [0u8; MlKem768PrivateKey::CIPHERTEXT_BYTES]; + let mut shared_secret = [0u8; SHARED_SECRET_BYTES]; + + ffi::MLKEM768_encap( + ciphertext.as_mut_ptr(), + shared_secret.as_mut_ptr(), + &self.parsed, + ); + + (ciphertext, shared_secret) + } + } +} + +impl fmt::Debug for MlKem768PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MlKem768PublicKey") + .field("bytes", &format_args!("[{}]", self.bytes.len())) + .finish() + } +} + +/// ML-KEM-1024 private key. +/// +/// Prefer ML-KEM-768 unless you need AES-256 equivalent security. +/// Caches the expanded key for fast decapsulation. +struct MlKem1024PrivateKey { + seed: MlKemPrivateKeySeed, + expanded: ffi::MLKEM1024_private_key, +} + +impl Clone for MlKem1024PrivateKey { + fn clone(&self) -> Self { + // unwrap is safe: cloning a valid key with a valid seed always succeeds + Self::from_seed(&self.seed).unwrap() + } +} + +impl MlKem1024PrivateKey { + pub const CIPHERTEXT_BYTES: usize = ffi::MLKEM1024_CIPHERTEXT_BYTES as usize; + + /// Generate a new key pair. + #[must_use] + fn generate() -> (Box, Box) { + // SAFETY: all buffers are out parameters, correctly sized + unsafe { + ffi::init(); + let mut public_key_bytes: MaybeUninit<[u8; MlKem1024PublicKey::PUBLIC_KEY_BYTES]> = + MaybeUninit::uninit(); + let mut seed: MaybeUninit = MaybeUninit::uninit(); + let mut expanded: MaybeUninit = MaybeUninit::uninit(); + + ffi::MLKEM1024_generate_key( + public_key_bytes.as_mut_ptr().cast(), + seed.as_mut_ptr().cast(), + expanded.as_mut_ptr(), + ); + + let bytes = public_key_bytes.assume_init(); + + // Parse the public key bytes to get the parsed struct + let mut cbs = cbs_init(&bytes); + let mut parsed: MaybeUninit = MaybeUninit::uninit(); + ffi::MLKEM1024_parse_public_key(parsed.as_mut_ptr(), &mut cbs); + + ( + Box::new(MlKem1024PublicKey { + bytes, + parsed: parsed.assume_init(), + }), + Box::new(MlKem1024PrivateKey { + seed: seed.assume_init(), + expanded: expanded.assume_init(), + }), + ) + } + } + + /// Restore private key from seed. + fn from_seed(seed: &MlKemPrivateKeySeed) -> Result { + // SAFETY: seed is 64 bytes, out parameter correctly sized + unsafe { + ffi::init(); + let mut expanded: MaybeUninit = MaybeUninit::uninit(); + cvt(ffi::MLKEM1024_private_key_from_seed( + expanded.as_mut_ptr(), + seed.as_ptr(), + seed.len(), + ))?; + Ok(Self { + seed: *seed, + expanded: expanded.assume_init(), + }) + } + } + + /// Derive the public key. + #[cfg(test)] + fn public_key(&self) -> Result { + // SAFETY: expanded key is valid, buffers correctly sized + unsafe { + ffi::init(); + let mut parsed: MaybeUninit = MaybeUninit::uninit(); + ffi::MLKEM1024_public_from_private(parsed.as_mut_ptr(), &self.expanded); + + let mut bytes = [0u8; MlKem1024PublicKey::PUBLIC_KEY_BYTES]; + let mut cbb: MaybeUninit = MaybeUninit::uninit(); + cvt(ffi::CBB_init_fixed( + cbb.as_mut_ptr(), + bytes.as_mut_ptr(), + bytes.len(), + ))?; + cvt(ffi::MLKEM1024_marshal_public_key( + cbb.as_mut_ptr(), + parsed.as_ptr(), + ))?; + + Ok(MlKem1024PublicKey { + bytes, + parsed: parsed.assume_init(), + }) + } + } + + /// Decapsulate to get the shared secret. + fn decapsulate(&self, ciphertext: &[u8; Self::CIPHERTEXT_BYTES]) -> MlKemSharedSecret { + // SAFETY: expanded key is valid, ciphertext is correctly sized + unsafe { + ffi::init(); + let mut shared_secret = [0u8; SHARED_SECRET_BYTES]; + + ffi::MLKEM1024_decap( + shared_secret.as_mut_ptr(), + ciphertext.as_ptr(), + ciphertext.len(), + &self.expanded, + ); + + shared_secret + } + } +} + +impl fmt::Debug for MlKem1024PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MlKem1024PrivateKey") + .field("key", &"[redacted]") + .finish() + } +} + +impl Drop for MlKem1024PrivateKey { + fn drop(&mut self) { + // SAFETY: pointers and lengths are valid + unsafe { + ffi::OPENSSL_cleanse(self.seed.as_mut_ptr().cast(), self.seed.len()); + ffi::OPENSSL_cleanse( + self.expanded.opaque.bytes.as_mut_ptr().cast(), + self.expanded.opaque.bytes.len(), + ); + } + } +} + +/// ML-KEM-1024 public key. +/// +/// Prefer ML-KEM-768 unless you need AES-256 equivalent security. +#[derive(Clone)] +struct MlKem1024PublicKey { + bytes: [u8; Self::PUBLIC_KEY_BYTES], + parsed: ffi::MLKEM1024_public_key, +} + +impl MlKem1024PublicKey { + pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM1024_PUBLIC_KEY_BYTES as usize; + + /// Parse and validate a serialized public key. + /// + /// The slice must be [`Self::PUBLIC_KEY_BYTES`] long. + fn from_slice(slice: &[u8]) -> Result { + if slice.len() != Self::PUBLIC_KEY_BYTES { + return Err(ErrorStack::internal_error_str("invalid public key length")); + } + + // SAFETY: CBS correctly initialized, length already checked + unsafe { + ffi::init(); + let mut cbs = cbs_init(slice); + let mut parsed: MaybeUninit = MaybeUninit::uninit(); + + cvt(ffi::MLKEM1024_parse_public_key( + parsed.as_mut_ptr(), + &mut cbs, + ))?; + if cbs.len != 0 { + return Err(ErrorStack::internal_error_str( + "trailing bytes after public key", + )); + } + + let mut bytes = [0u8; Self::PUBLIC_KEY_BYTES]; + bytes.copy_from_slice(slice); + Ok(Self { + bytes, + parsed: parsed.assume_init(), + }) + } + } + + /// Encapsulate: returns (ciphertext, shared_secret). + fn encapsulate( + &self, + ) -> ( + [u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES], + [u8; SHARED_SECRET_BYTES], + ) { + // SAFETY: buffers correctly sized, parsed key is valid + unsafe { + ffi::init(); + let mut ciphertext = [0u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES]; + let mut shared_secret = [0u8; SHARED_SECRET_BYTES]; + + ffi::MLKEM1024_encap( + ciphertext.as_mut_ptr(), + shared_secret.as_mut_ptr(), + &self.parsed, + ); + + (ciphertext, shared_secret) + } + } +} + +impl fmt::Debug for MlKem1024PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MlKem1024PublicKey") + .field("bytes", &format_args!("[{}]", self.bytes.len())) + .finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! mlkem_tests { + ($name:ident, $priv:ty, $pub:ty, $ct_len:expr) => { + mod $name { + use super::*; + + #[test] + fn roundtrip() { + let (pk, sk) = <$priv>::generate(); + let (ct, ss1) = pk.encapsulate(); + let ss2 = sk.decapsulate(&ct); + assert_eq!(ss1, ss2); + } + + #[test] + fn seed_roundtrip() { + let (pk, sk) = <$priv>::generate(); + let sk2 = <$priv>::from_seed(&sk.seed).unwrap(); + let (ct, ss1) = pk.encapsulate(); + let ss2 = sk2.decapsulate(&ct); + assert_eq!(ss1, ss2); + } + + #[test] + fn derive_pubkey() { + let (pk, sk) = <$priv>::generate(); + assert_eq!(pk.bytes, sk.public_key().unwrap().bytes); + } + + #[test] + fn from_slice_rejects_bad_len() { + assert!(<$pub>::from_slice(&[0u8; 100]).is_err()); + assert!(<$pub>::from_slice(&[]).is_err()); + } + + #[test] + fn from_slice_roundtrip() { + let (pk, _) = <$priv>::generate(); + let pk2 = <$pub>::from_slice(&pk.bytes).unwrap(); + assert_eq!(pk.bytes, pk2.bytes); + } + + #[test] + fn implicit_rejection() { + let (_, sk) = <$priv>::generate(); + let bad_ct = [0x42u8; $ct_len]; + // bad ciphertext still "works", just returns deterministic garbage + let ss1 = sk.decapsulate(&bad_ct); + let ss2 = sk.decapsulate(&bad_ct); + assert_eq!(ss1, ss2); + } + + #[test] + fn debug_redacts_seed() { + let (_, sk) = <$priv>::generate(); + let dbg = format!("{:?}", sk); + assert!(dbg.contains("redacted")); + } + } + }; + } + + mlkem_tests!(mlkem768, MlKem768PrivateKey, MlKem768PublicKey, 1088); + mlkem_tests!(mlkem1024, MlKem1024PrivateKey, MlKem1024PublicKey, 1568); + + // Tests for unified API (MlKem struct) + mod unified_api { + use super::*; + + macro_rules! unified_tests { + ($name:ident, $algorithm:expr, $pk_len:expr, $ct_len:expr) => { + mod $name { + use super::*; + + #[test] + fn roundtrip() { + let (pk, sk) = MlKemPrivateKey::generate($algorithm).unwrap(); + let (ct, ss1) = pk.encapsulate().unwrap(); + let ss2 = sk.decapsulate(&ct).unwrap(); + assert_eq!(ss1, ss2); + } + + #[test] + fn key_sizes() { + assert_eq!($algorithm.public_key_bytes(), $pk_len); + assert_eq!($algorithm.ciphertext_bytes(), $ct_len); + + let (pk, private_key) = MlKemPrivateKey::generate($algorithm).unwrap(); + assert_eq!(pk.as_bytes().len(), $pk_len); + assert_eq!(private_key.seed_bytes().len(), PRIVATE_KEY_SEED_BYTES); + + let (ct, ss) = pk.encapsulate().unwrap(); + assert_eq!(ct.len(), $ct_len); + assert_eq!(ss.len(), SHARED_SECRET_BYTES); + } + + #[test] + fn invalid_public_key_length() { + let result = MlKemPublicKey::from_slice($algorithm, &[0u8; 100]); + assert!(result.is_err()); + } + + #[test] + fn invalid_ciphertext_length() { + let (_, sk) = MlKemPrivateKey::generate($algorithm).unwrap(); + let result = sk.decapsulate(&[0u8; 100]); + assert!(result.is_err()); + } + } + }; + } + + unified_tests!(mlkem768, Algorithm::MlKem768, 1184, 1088); + unified_tests!(mlkem1024, Algorithm::MlKem1024, 1568, 1568); + + #[test] + fn params_constants() { + assert_eq!(Algorithm::MlKem768.public_key_bytes(), 1184); + assert_eq!(Algorithm::MlKem768.ciphertext_bytes(), 1088); + assert_eq!(Algorithm::MlKem1024.public_key_bytes(), 1568); + assert_eq!(Algorithm::MlKem1024.ciphertext_bytes(), 1568); + } + } +} diff --git a/boring/src/nid.rs b/boring/src/nid.rs index 347b30f74..15cfe4eb9 100644 --- a/boring/src/nid.rs +++ b/boring/src/nid.rs @@ -1,6 +1,6 @@ //! A collection of numerical identifiers for OpenSSL objects. use crate::ffi; -use libc::{c_char, c_int}; +use libc::c_int; use openssl_macros::corresponds; use std::ffi::CStr; @@ -87,7 +87,7 @@ impl Nid { #[allow(clippy::trivially_copy_pass_by_ref)] pub fn long_name(&self) -> Result<&'static str, ErrorStack> { unsafe { - let nameptr = cvt_p(ffi::OBJ_nid2ln(self.0) as *mut c_char)?; + let nameptr = cvt_p(ffi::OBJ_nid2ln(self.0).cast_mut())?; CStr::from_ptr(nameptr) .to_str() .map_err(ErrorStack::internal_error) @@ -99,7 +99,7 @@ impl Nid { #[allow(clippy::trivially_copy_pass_by_ref)] pub fn short_name(&self) -> Result<&'static str, ErrorStack> { unsafe { - let nameptr = cvt_p(ffi::OBJ_nid2sn(self.0) as *mut c_char)?; + let nameptr = cvt_p(ffi::OBJ_nid2sn(self.0).cast_mut())?; CStr::from_ptr(nameptr) .to_str() .map_err(ErrorStack::internal_error) diff --git a/boring/src/pkcs12.rs b/boring/src/pkcs12.rs index dd255e3a7..bb851421e 100644 --- a/boring/src/pkcs12.rs +++ b/boring/src/pkcs12.rs @@ -180,8 +180,8 @@ impl Pkcs12Builder { let keytype = 0; cvt_p(ffi::PKCS12_create( - pass.as_ptr() as *const _ as *mut _, - friendly_name.as_ptr() as *const _ as *mut _, + pass.as_ptr(), + friendly_name.as_ptr(), pkey, cert, ca, @@ -260,7 +260,7 @@ mod test { .unwrap(); builder.set_subject_name(&name).unwrap(); builder.set_issuer_name(&name).unwrap(); - builder.append_extension(key_usage).unwrap(); + builder.append_extension(&key_usage).unwrap(); builder.set_pubkey(&pkey).unwrap(); builder.sign(&pkey, MessageDigest::sha256()).unwrap(); let cert = builder.build(); diff --git a/boring/src/pkcs5.rs b/boring/src/pkcs5.rs index 916846615..1e1665bc5 100644 --- a/boring/src/pkcs5.rs +++ b/boring/src/pkcs5.rs @@ -1,11 +1,11 @@ use crate::ffi; -use libc::{c_int, c_uint}; +use std::ffi::c_int; use std::ptr; -use crate::cvt; use crate::error::ErrorStack; use crate::hash::MessageDigest; use crate::symm::Cipher; +use crate::{cvt, cvt_nz, try_int}; #[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct KeyIvPair { @@ -49,7 +49,7 @@ pub fn bytes_to_key( let cipher = cipher.as_ptr(); let digest = digest.as_ptr(); - let len = cvt(ffi::EVP_BytesToKey( + let len = cvt_nz(ffi::EVP_BytesToKey( cipher, digest, salt_ptr, @@ -60,7 +60,7 @@ pub fn bytes_to_key( ptr::null_mut(), ))?; - let mut key = vec![0; len as usize]; + let mut key = vec![0; len.get()]; let iv_ptr = iv .as_mut() .map(|v| v.as_mut_ptr()) @@ -90,22 +90,18 @@ pub fn pbkdf2_hmac( key: &mut [u8], ) -> Result<(), ErrorStack> { unsafe { - assert!(pass.len() <= c_int::MAX as usize); - assert!(salt.len() <= c_int::MAX as usize); - assert!(key.len() <= c_int::MAX as usize); - ffi::init(); + cvt(ffi::PKCS5_PBKDF2_HMAC( - pass.as_ptr() as *const _, + pass.as_ptr().cast(), pass.len(), salt.as_ptr(), salt.len(), - iter as c_uint, + try_int(iter)?, hash.as_ptr(), key.len(), key.as_mut_ptr(), )) - .map(|_| ()) } } @@ -122,18 +118,17 @@ pub fn scrypt( unsafe { ffi::init(); cvt(ffi::EVP_PBE_scrypt( - pass.as_ptr() as *const _, + pass.as_ptr().cast(), pass.len(), - salt.as_ptr() as *const _, + salt.as_ptr().cast(), salt.len(), n, r, p, maxmem, - key.as_mut_ptr() as *mut _, + key.as_mut_ptr(), key.len(), )) - .map(|_| ()) } } diff --git a/boring/src/pkey.rs b/boring/src/pkey.rs index 9245f5b64..0ab9c5288 100644 --- a/boring/src/pkey.rs +++ b/boring/src/pkey.rs @@ -40,7 +40,6 @@ //! println!("{:?}", str::from_utf8(pub_key.as_slice()).unwrap()); //! ``` -use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; use libc::{c_int, c_long}; use openssl_macros::corresponds; @@ -54,7 +53,9 @@ use crate::dh::Dh; use crate::dsa::Dsa; use crate::ec::EcKey; use crate::error::ErrorStack; +use crate::ffi; use crate::rsa::Rsa; +use crate::try_int; use crate::util::{invoke_passwd_cb, CallbackState}; use crate::{cvt, cvt_0i, cvt_p}; @@ -361,7 +362,7 @@ impl PKey { cvt(ffi::EVP_PKEY_assign( pkey.0, ffi::EVP_PKEY_RSA, - rsa.as_ptr() as *mut _, + rsa.as_ptr().cast(), ))?; mem::forget(rsa); Ok(pkey) @@ -377,7 +378,7 @@ impl PKey { cvt(ffi::EVP_PKEY_assign( pkey.0, ffi::EVP_PKEY_EC, - ec_key.as_ptr() as *mut _, + ec_key.as_ptr().cast(), ))?; mem::forget(ec_key); Ok(pkey) @@ -455,7 +456,7 @@ impl PKey { bio.as_ptr(), ptr::null_mut(), Some(invoke_passwd_cb::), - &mut cb as *mut _ as *mut _, + std::ptr::addr_of_mut!(cb).cast(), )) .map(|p| PKey::from_ptr(p)) } @@ -479,7 +480,7 @@ impl PKey { bio.as_ptr(), ptr::null_mut(), None, - passphrase.as_ptr() as *const _ as *mut _, + passphrase.as_ptr().cast_mut().cast(), )) .map(|p| PKey::from_ptr(p)) } diff --git a/boring/src/rand.rs b/boring/src/rand.rs index e5b843e0b..e9a69e469 100644 --- a/boring/src/rand.rs +++ b/boring/src/rand.rs @@ -36,7 +36,7 @@ pub fn rand_bytes(buf: &mut [u8]) -> Result<(), ErrorStack> { unsafe { ffi::init(); assert!(buf.len() <= c_int::MAX as usize); - cvt(ffi::RAND_bytes(buf.as_mut_ptr(), buf.len())).map(|_| ()) + cvt(ffi::RAND_bytes(buf.as_mut_ptr(), buf.len())) } } diff --git a/boring/src/rsa.rs b/boring/src/rsa.rs index ff47e71ba..49f955bda 100644 --- a/boring/src/rsa.rs +++ b/boring/src/rsa.rs @@ -23,7 +23,6 @@ //! let mut buf = vec![0; rsa.size() as usize]; //! let encrypted_len = rsa.public_encrypt(data, &mut buf, Padding::PKCS1).unwrap(); //! ``` -use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; use libc::c_int; use openssl_macros::corresponds; @@ -33,7 +32,9 @@ use std::ptr; use crate::bn::{BigNum, BigNumRef}; use crate::error::ErrorStack; +use crate::ffi; use crate::pkey::{HasPrivate, HasPublic, Private, Public}; +use crate::try_int; use crate::{cvt, cvt_n, cvt_p}; pub const EVP_PKEY_OP_SIGN: c_int = 1 << 3; @@ -194,7 +195,7 @@ where unsafe { let mut d = ptr::null(); RSA_get0_key(self.as_ptr(), ptr::null_mut(), ptr::null_mut(), &mut d); - BigNumRef::from_ptr(d as *mut _) + BigNumRef::from_ptr(d.cast_mut()) } } @@ -208,7 +209,7 @@ where if p.is_null() { None } else { - Some(BigNumRef::from_ptr(p as *mut _)) + Some(BigNumRef::from_ptr(p.cast_mut())) } } } @@ -223,7 +224,7 @@ where if q.is_null() { None } else { - Some(BigNumRef::from_ptr(q as *mut _)) + Some(BigNumRef::from_ptr(q.cast_mut())) } } } @@ -238,7 +239,7 @@ where if dp.is_null() { None } else { - Some(BigNumRef::from_ptr(dp as *mut _)) + Some(BigNumRef::from_ptr(dp.cast_mut())) } } } @@ -253,7 +254,7 @@ where if dq.is_null() { None } else { - Some(BigNumRef::from_ptr(dq as *mut _)) + Some(BigNumRef::from_ptr(dq.cast_mut())) } } } @@ -268,7 +269,7 @@ where if qi.is_null() { None } else { - Some(BigNumRef::from_ptr(qi as *mut _)) + Some(BigNumRef::from_ptr(qi.cast_mut())) } } } @@ -391,7 +392,7 @@ where unsafe { let mut n = ptr::null(); RSA_get0_key(self.as_ptr(), &mut n, ptr::null_mut(), ptr::null_mut()); - BigNumRef::from_ptr(n as *mut _) + BigNumRef::from_ptr(n.cast_mut()) } } @@ -402,7 +403,7 @@ where unsafe { let mut e = ptr::null(); RSA_get0_key(self.as_ptr(), ptr::null_mut(), &mut e, ptr::null_mut()); - BigNumRef::from_ptr(e as *mut _) + BigNumRef::from_ptr(e.cast_mut()) } } } @@ -416,7 +417,7 @@ impl Rsa { pub fn from_public_components(n: BigNum, e: BigNum) -> Result, ErrorStack> { unsafe { let rsa = cvt_p(ffi::RSA_new())?; - RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), ptr::null_mut()); + cvt(RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), ptr::null_mut()))?; mem::forget((n, e)); Ok(Rsa::from_ptr(rsa)) } @@ -474,7 +475,7 @@ impl RsaPrivateKeyBuilder { pub fn new(n: BigNum, e: BigNum, d: BigNum) -> Result { unsafe { let rsa = cvt_p(ffi::RSA_new())?; - RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), d.as_ptr()); + cvt(RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), d.as_ptr()))?; mem::forget((n, e, d)); Ok(RsaPrivateKeyBuilder { rsa: Rsa::from_ptr(rsa), @@ -485,12 +486,10 @@ impl RsaPrivateKeyBuilder { /// Sets the factors of the Rsa key. /// /// `p` and `q` are the first and second factors of `n`. - /// - // FIXME should be infallible #[corresponds(RSA_set0_factors)] pub fn set_factors(self, p: BigNum, q: BigNum) -> Result { unsafe { - RSA_set0_factors(self.rsa.as_ptr(), p.as_ptr(), q.as_ptr()); + cvt(RSA_set0_factors(self.rsa.as_ptr(), p.as_ptr(), q.as_ptr()))?; mem::forget((p, q)); } Ok(self) @@ -500,8 +499,6 @@ impl RsaPrivateKeyBuilder { /// /// `dmp1`, `dmq1`, and `iqmp` are the exponents and coefficient for /// CRT calculations which is used to speed up RSA operations. - /// - // FIXME should be infallible #[corresponds(RSA_set0_crt_params)] pub fn set_crt_params( self, @@ -510,12 +507,12 @@ impl RsaPrivateKeyBuilder { iqmp: BigNum, ) -> Result { unsafe { - RSA_set0_crt_params( + cvt(RSA_set0_crt_params( self.rsa.as_ptr(), dmp1.as_ptr(), dmq1.as_ptr(), iqmp.as_ptr(), - ); + ))?; mem::forget((dmp1, dmq1, iqmp)); } Ok(self) diff --git a/boring/src/sha.rs b/boring/src/sha.rs index f0f9b15c1..dd0671852 100644 --- a/boring/src/sha.rs +++ b/boring/src/sha.rs @@ -54,7 +54,6 @@ use std::mem::MaybeUninit; /// SHA1 is known to be insecure - it should not be used unless required for /// compatibility with existing systems. #[inline] -#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn sha1(data: &[u8]) -> [u8; 20] { unsafe { @@ -66,7 +65,6 @@ pub fn sha1(data: &[u8]) -> [u8; 20] { /// Computes the SHA224 hash of some data. #[inline] -#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn sha224(data: &[u8]) -> [u8; 28] { unsafe { @@ -78,7 +76,6 @@ pub fn sha224(data: &[u8]) -> [u8; 28] { /// Computes the SHA256 hash of some data. #[inline] -#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn sha256(data: &[u8]) -> [u8; 32] { unsafe { @@ -90,7 +87,6 @@ pub fn sha256(data: &[u8]) -> [u8; 32] { /// Computes the SHA384 hash of some data. #[inline] -#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn sha384(data: &[u8]) -> [u8; 48] { unsafe { @@ -102,7 +98,6 @@ pub fn sha384(data: &[u8]) -> [u8; 48] { /// Computes the SHA512 hash of some data. #[inline] -#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn sha512(data: &[u8]) -> [u8; 64] { unsafe { @@ -114,7 +109,6 @@ pub fn sha512(data: &[u8]) -> [u8; 64] { /// Computes the SHA512-256 hash of some data. #[inline] -#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn sha512_256(data: &[u8]) -> [u8; 32] { unsafe { @@ -143,7 +137,6 @@ impl Default for Sha1 { impl Sha1 { /// Creates a new hasher. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn new() -> Sha1 { unsafe { @@ -159,13 +152,12 @@ impl Sha1 { #[inline] pub fn update(&mut self, buf: &[u8]) { unsafe { - ffi::SHA1_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + ffi::SHA1_Update(&mut self.0, buf.as_ptr().cast::(), buf.len()); } } /// Returns the hash of the data. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn finish(mut self) -> [u8; 20] { unsafe { @@ -190,7 +182,6 @@ impl Default for Sha224 { impl Sha224 { /// Creates a new hasher. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn new() -> Sha224 { unsafe { @@ -206,13 +197,12 @@ impl Sha224 { #[inline] pub fn update(&mut self, buf: &[u8]) { unsafe { - ffi::SHA224_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + ffi::SHA224_Update(&mut self.0, buf.as_ptr().cast::(), buf.len()); } } /// Returns the hash of the data. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn finish(mut self) -> [u8; 28] { unsafe { @@ -237,7 +227,6 @@ impl Default for Sha256 { impl Sha256 { /// Creates a new hasher. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn new() -> Sha256 { unsafe { @@ -253,13 +242,12 @@ impl Sha256 { #[inline] pub fn update(&mut self, buf: &[u8]) { unsafe { - ffi::SHA256_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + ffi::SHA256_Update(&mut self.0, buf.as_ptr().cast::(), buf.len()); } } /// Returns the hash of the data. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn finish(mut self) -> [u8; 32] { unsafe { @@ -284,7 +272,6 @@ impl Default for Sha384 { impl Sha384 { /// Creates a new hasher. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn new() -> Sha384 { unsafe { @@ -300,13 +287,12 @@ impl Sha384 { #[inline] pub fn update(&mut self, buf: &[u8]) { unsafe { - ffi::SHA384_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + ffi::SHA384_Update(&mut self.0, buf.as_ptr().cast::(), buf.len()); } } /// Returns the hash of the data. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn finish(mut self) -> [u8; 48] { unsafe { @@ -331,7 +317,6 @@ impl Default for Sha512 { impl Sha512 { /// Creates a new hasher. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn new() -> Sha512 { unsafe { @@ -347,13 +332,12 @@ impl Sha512 { #[inline] pub fn update(&mut self, buf: &[u8]) { unsafe { - ffi::SHA512_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + ffi::SHA512_Update(&mut self.0, buf.as_ptr().cast::(), buf.len()); } } /// Returns the hash of the data. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn finish(mut self) -> [u8; 64] { unsafe { @@ -378,7 +362,6 @@ impl Default for Sha512_256 { impl Sha512_256 { /// Creates a new hasher. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn new() -> Sha512_256 { unsafe { @@ -394,13 +377,12 @@ impl Sha512_256 { #[inline] pub fn update(&mut self, buf: &[u8]) { unsafe { - ffi::SHA512_256_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + ffi::SHA512_256_Update(&mut self.0, buf.as_ptr().cast::(), buf.len()); } } /// Returns the hash of the data. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn finish(mut self) -> [u8; 32] { unsafe { diff --git a/boring/src/sign.rs b/boring/src/sign.rs index b87e60107..eea911e67 100644 --- a/boring/src/sign.rs +++ b/boring/src/sign.rs @@ -174,7 +174,6 @@ impl<'a> Signer<'a> { self.pctx, padding.as_raw(), )) - .map(|_| ()) } } @@ -188,7 +187,6 @@ impl<'a> Signer<'a> { self.pctx, len.as_raw(), )) - .map(|_| ()) } } @@ -200,9 +198,8 @@ impl<'a> Signer<'a> { unsafe { cvt(ffi::EVP_PKEY_CTX_set_rsa_mgf1_md( self.pctx, - md.as_ptr() as *mut _, + md.as_ptr().cast_mut(), )) - .map(|_| ()) } } @@ -215,10 +212,9 @@ impl<'a> Signer<'a> { unsafe { cvt(ffi::EVP_DigestUpdate( self.md_ctx, - buf.as_ptr() as *const _, + buf.as_ptr().cast(), buf.len(), )) - .map(|_| ()) } } @@ -255,7 +251,7 @@ impl<'a> Signer<'a> { let mut len = buf.len(); cvt(ffi::EVP_DigestSignFinal( self.md_ctx, - buf.as_mut_ptr() as *mut _, + buf.as_mut_ptr().cast(), &mut len, ))?; Ok(len) @@ -290,9 +286,9 @@ impl<'a> Signer<'a> { let mut sig_len = sig_buf.len(); cvt(ffi::EVP_DigestSign( self.md_ctx, - sig_buf.as_mut_ptr() as *mut _, + sig_buf.as_mut_ptr(), &mut sig_len, - data_buf.as_ptr() as *const _, + data_buf.as_ptr(), data_buf.len(), ))?; Ok(sig_len) @@ -421,7 +417,6 @@ impl<'a> Verifier<'a> { self.pctx, padding.as_raw(), )) - .map(|_| ()) } } @@ -435,7 +430,6 @@ impl<'a> Verifier<'a> { self.pctx, len.as_raw(), )) - .map(|_| ()) } } @@ -447,9 +441,8 @@ impl<'a> Verifier<'a> { unsafe { cvt(ffi::EVP_PKEY_CTX_set_rsa_mgf1_md( self.pctx, - md.as_ptr() as *mut _, + md.as_ptr().cast_mut(), )) - .map(|_| ()) } } @@ -462,10 +455,9 @@ impl<'a> Verifier<'a> { unsafe { cvt(ffi::EVP_DigestUpdate( self.md_ctx, - buf.as_ptr() as *const _, + buf.as_ptr().cast(), buf.len(), )) - .map(|_| ()) } } @@ -474,7 +466,7 @@ impl<'a> Verifier<'a> { pub fn verify(&self, signature: &[u8]) -> Result { unsafe { let r = - EVP_DigestVerifyFinal(self.md_ctx, signature.as_ptr() as *mut _, signature.len()); + EVP_DigestVerifyFinal(self.md_ctx, signature.as_ptr().cast_mut(), signature.len()); match r { 1 => Ok(true), 0 => { @@ -492,9 +484,9 @@ impl<'a> Verifier<'a> { unsafe { let r = ffi::EVP_DigestVerify( self.md_ctx, - signature.as_ptr() as *const _, + signature.as_ptr().cast(), signature.len(), - buf.as_ptr() as *const _, + buf.as_ptr().cast(), buf.len(), ); match r { diff --git a/boring/src/srtp.rs b/boring/src/srtp.rs index 8d23b722c..7dffc436e 100644 --- a/boring/src/srtp.rs +++ b/boring/src/srtp.rs @@ -27,7 +27,7 @@ impl SrtpProtectionProfileRef { #[must_use] pub fn name(&self) -> &'static str { - unsafe { CStr::from_ptr((*self.as_ptr()).name as *const _) } + unsafe { CStr::from_ptr((*self.as_ptr()).name.cast()) } .to_str() .expect("should be UTF-8") } diff --git a/boring/src/ssl/async_callbacks.rs b/boring/src/ssl/async_callbacks.rs index 4ab18d11a..ad9683a08 100644 --- a/boring/src/ssl/async_callbacks.rs +++ b/boring/src/ssl/async_callbacks.rs @@ -4,7 +4,9 @@ use super::{ Ssl, SslAlert, SslContextBuilder, SslRef, SslSession, SslSignatureAlgorithm, SslVerifyError, SslVerifyMode, }; +use crate::error::ErrorStack; use crate::ex_data::Index; +use crate::ssl::SslCredentialBuilder; use std::convert::identity; use std::future::Future; use std::pin::Pin; @@ -171,6 +173,21 @@ impl SslContextBuilder { } } +impl SslCredentialBuilder { + /// Configures a custom private key method on the context. + /// + /// A task waker must be set on `Ssl` values associated with the resulting + /// `SslContext` with [`SslRef::set_task_waker`]. + /// + /// See [`AsyncPrivateKeyMethod`] for more details. + pub fn set_async_private_key_method( + &mut self, + method: impl AsyncPrivateKeyMethod, + ) -> Result<(), ErrorStack> { + self.set_private_key_method(AsyncPrivateKeyMethodBridge(Box::new(method))) + } +} + impl SslRef { pub fn set_async_custom_verify_callback(&mut self, mode: SslVerifyMode, callback: F) where diff --git a/boring/src/ssl/bio.rs b/boring/src/ssl/bio.rs index e700dbe3d..3bae8a79b 100644 --- a/boring/src/ssl/bio.rs +++ b/boring/src/ssl/bio.rs @@ -44,7 +44,7 @@ pub fn new(stream: S) -> Result<(*mut BIO, BioMethod), ErrorSta unsafe { let bio = cvt_p(BIO_new(method.0.get()))?; - BIO_set_data(bio, Box::into_raw(state) as *mut _); + BIO_set_data(bio, Box::into_raw(state).cast()); BIO_set_init(bio, 1); Ok((bio, method)) @@ -76,7 +76,7 @@ pub unsafe extern "C" fn take_stream(bio: *mut BIO) -> S { assert!(!data.is_null()); - let state = Box::>::from_raw(data as *mut _); + let state = Box::>::from_raw(data.cast()); BIO_set_data(bio, ptr::null_mut()); @@ -91,7 +91,7 @@ pub unsafe fn set_dtls_mtu_size(bio: *mut BIO, mtu_size: usize) { } unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState { - let data = BIO_get_data(bio) as *mut StreamState; + let data = BIO_get_data(bio).cast::>(); assert!(!data.is_null()); @@ -101,8 +101,12 @@ unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState { unsafe extern "C" fn bwrite(bio: *mut BIO, buf: *const c_char, len: c_int) -> c_int { BIO_clear_retry_flags(bio); + let Ok(len) = usize::try_from(len) else { + return -1; + }; + let state = state::(bio); - let buf = slice::from_raw_parts(buf as *const _, len as usize); + let buf = slice::from_raw_parts(buf.cast(), len); match catch_unwind(AssertUnwindSafe(|| state.stream.write(buf))) { Ok(Ok(len)) => len as c_int, @@ -123,8 +127,12 @@ unsafe extern "C" fn bwrite(bio: *mut BIO, buf: *const c_char, len: c_ unsafe extern "C" fn bread(bio: *mut BIO, buf: *mut c_char, len: c_int) -> c_int { BIO_clear_retry_flags(bio); + let Ok(len) = usize::try_from(len) else { + return -1; + }; + let state = state::(bio); - let buf = slice::from_raw_parts_mut(buf as *mut _, len as usize); + let buf = slice::from_raw_parts_mut(buf.cast(), len); match catch_unwind(AssertUnwindSafe(|| state.stream.read(buf))) { Ok(Ok(len)) => len as c_int, @@ -163,9 +171,13 @@ unsafe extern "C" fn ctrl( let state = state::(bio); if cmd == BIO_CTRL_FLUSH { + BIO_clear_retry_flags(bio); match catch_unwind(AssertUnwindSafe(|| state.stream.flush())) { Ok(Ok(())) => 1, Ok(Err(err)) => { + if retriable_error(&err) { + BIO_set_retry_write(bio); + } state.error = Some(err); 0 } @@ -197,7 +209,7 @@ unsafe extern "C" fn destroy(bio: *mut BIO) -> c_int { let data = BIO_get_data(bio); if !data.is_null() { - drop(Box::>::from_raw(data as *mut _)); + drop(Box::>::from_raw(data.cast())); BIO_set_data(bio, ptr::null_mut()); } diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index f618e591d..41c08c6b9 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -8,12 +8,15 @@ use super::{ }; use crate::error::ErrorStack; use crate::ffi; +use crate::hmac::HmacCtxRef; +use crate::ssl::TicketKeyCallbackResult; +use crate::symm::CipherCtxRef; use crate::x509::{X509StoreContext, X509StoreContextRef}; use foreign_types::ForeignType; use foreign_types::ForeignTypeRef; -use libc::c_char; -use libc::{c_int, c_uchar, c_uint, c_void}; +use libc::{c_char, c_int, c_uchar, c_uint, c_void}; use std::ffi::CStr; +use std::mem::{self, MaybeUninit}; use std::ptr; use std::slice; use std::str; @@ -38,7 +41,7 @@ where // SAFETY: The callback won't outlive the context it's associated with // because there is no `X509StoreContextRef::ssl_mut(&mut self)` method. - let verify = unsafe { &*(verify as *const F) }; + let verify = unsafe { &*std::ptr::from_ref::(verify) }; c_int::from(verify(preverify_ok != 0, ctx)) } @@ -87,7 +90,7 @@ where // SAFETY: The callback won't outlive the context it's associated with // because there is no way to get a mutable reference to the `SslContext`, // so the callback can't replace itself. - let verify = unsafe { &*(verify as *const F) }; + let verify = unsafe { &*std::ptr::from_ref::(verify) }; c_int::from(verify(ctx)) } @@ -157,7 +160,7 @@ where // Give the callback mutable slices into which it can write the identity and psk. let identity_sl = - unsafe { slice::from_raw_parts_mut(identity as *mut u8, max_identity_len as usize) }; + unsafe { slice::from_raw_parts_mut(identity.cast::(), max_identity_len as usize) }; let psk_sl = unsafe { slice::from_raw_parts_mut(psk, max_psk_len as usize) }; let ssl_context = ssl.ssl_context().to_owned(); @@ -269,6 +272,68 @@ where } } +unsafe fn to_uninit<'a, T: 'a>(ptr: *mut T) -> &'a mut MaybeUninit { + assert!(!ptr.is_null()); + unsafe { &mut *ptr.cast::>() } +} + +pub(super) unsafe extern "C" fn raw_ticket_key( + ssl: *mut ffi::SSL, + key_name: *mut u8, + iv: *mut u8, + evp_ctx: *mut ffi::EVP_CIPHER_CTX, + hmac_ctx: *mut ffi::HMAC_CTX, + encrypt: c_int, +) -> c_int +where + F: Fn( + &SslRef, + &mut [u8; 16], + &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], + &mut CipherCtxRef, + &mut HmacCtxRef, + bool, + ) -> TicketKeyCallbackResult + + 'static + + Sync + + Send, +{ + // SAFETY: boring provides valid inputs. + let ssl = unsafe { SslRef::from_ptr_mut(ssl) }; + + let ssl_context = ssl.ssl_context().to_owned(); + let callback = ssl_context + .ex_data::(SslContext::cached_ex_index::()) + .expect("expected session resumption callback"); + + // SAFETY: the callback guarantees that key_name is 16 bytes + let key_name = + unsafe { to_uninit(key_name.cast::<[u8; ffi::SSL_TICKET_KEY_NAME_LEN as usize]>()) }; + + // SAFETY: the callback provides 16 bytes iv + // + // https://github.com/google/boringssl/blob/main/ssl/ssl_session.cc#L331 + let iv = unsafe { to_uninit(iv.cast::<[u8; ffi::EVP_MAX_IV_LENGTH as usize]>()) }; + + // When encrypting a new ticket, encrypt will be one. + let encrypt = encrypt == 1; + + // Zero-initialize the key_name and iv, since the application is expected to populate these + // fields in the encrypt mode. + if encrypt { + *key_name = MaybeUninit::zeroed(); + *iv = MaybeUninit::zeroed(); + } + let key_name = unsafe { key_name.assume_init_mut() }; + let iv = unsafe { iv.assume_init_mut() }; + + // The EVP_CIPHER_CTX and HMAC_CTX are owned by boringSSL. + let evp_ctx = unsafe { CipherCtxRef::from_ptr_mut(evp_ctx) }; + let hmac_ctx = unsafe { HmacCtxRef::from_ptr_mut(hmac_ctx) }; + + callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into() +} + pub(super) unsafe extern "C" fn raw_alpn_select( ssl: *mut ffi::SSL, out: *mut *const c_uchar, @@ -293,7 +358,7 @@ where match callback(ssl, protos) { Ok(proto) => { - *out = proto.as_ptr() as *const c_uchar; + *out = proto.as_ptr(); *outlen = proto.len() as c_uchar; ffi::SSL_TLSEXT_ERR_OK @@ -377,7 +442,7 @@ where // SAFETY: We can make `callback` outlive `ssl` because it is a callback // stored in the session context set in `Ssl::new` so it is always // guaranteed to outlive the lifetime of this function's scope. - let callback = unsafe { &*(callback as *const F) }; + let callback = unsafe { &*std::ptr::from_ref::(callback) }; callback(ssl, session); @@ -430,7 +495,7 @@ where // SAFETY: We can make `callback` outlive `ssl` because it is a callback // stored in the session context set in `Ssl::new` so it is always // guaranteed to outlive the lifetime of this function's scope. - let callback = unsafe { &*(callback as *const F) }; + let callback = unsafe { &*std::ptr::from_ref::(callback) }; match callback(ssl, data) { Ok(Some(session)) => { @@ -450,7 +515,7 @@ where F: Fn(&SslRef, &str) + 'static + Sync + Send, { // SAFETY: boring provides valid inputs. - let ssl = unsafe { SslRef::from_ptr(ssl as *mut _) }; + let ssl = unsafe { SslRef::from_ptr(ssl.cast_mut()) }; let line = unsafe { CStr::from_ptr(line).to_string_lossy() }; let callback = ssl @@ -560,7 +625,7 @@ pub(super) unsafe extern "C" fn raw_info_callback( { // Due to FFI signature requirements we have to pass a *const SSL into this function, but // foreign-types requires a *mut SSL to get the Rust SslRef - let mut_ref = ssl as *mut ffi::SSL; + let mut_ref = ssl.cast_mut(); // SAFETY: boring provides valid inputs. let ssl = unsafe { SslRef::from_ptr(mut_ref) }; @@ -702,14 +767,10 @@ impl<'a> CryptoBufferBuilder<'a> { let buffer_capacity = unsafe { ffi::CRYPTO_BUFFER_len(self.buffer) }; if self.cursor.position() != buffer_capacity as u64 { // Make sure all bytes in buffer initialized as required by Boring SSL. - return Err(ErrorStack::get()); - } - unsafe { - let mut result = ptr::null_mut(); - ptr::swap(&mut self.buffer, &mut result); - std::mem::forget(self); - Ok(result) + return Err(ErrorStack::internal_error_str("invalid len")); } + // Drop is no-op if the buffer is null + Ok(mem::replace(&mut self.buffer, ptr::null_mut())) } } diff --git a/boring/src/ssl/connector.rs b/boring/src/ssl/connector.rs index 111b45c2a..49f146aac 100644 --- a/boring/src/ssl/connector.rs +++ b/boring/src/ssl/connector.rs @@ -23,19 +23,9 @@ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== -----END DH PARAMETERS----- "; -enum ContextType { - WithMethod(SslMethod), - #[cfg(feature = "rpk")] - Rpk, -} - #[allow(clippy::inconsistent_digit_grouping)] -fn ctx(ty: ContextType) -> Result { - let mut ctx = match ty { - ContextType::WithMethod(method) => SslContextBuilder::new(method), - #[cfg(feature = "rpk")] - ContextType::Rpk => SslContextBuilder::new_rpk(), - }?; +fn ctx(method: SslMethod) -> Result { + let mut ctx = SslContextBuilder::new(method)?; let mut opts = SslOptions::ALL | SslOptions::NO_COMPRESSION @@ -77,7 +67,7 @@ impl SslConnector { /// /// The default configuration is subject to change, and is currently derived from Python. pub fn builder(method: SslMethod) -> Result { - let mut ctx = ctx(ContextType::WithMethod(method))?; + let mut ctx = ctx(method)?; ctx.set_default_verify_paths()?; ctx.set_cipher_list( "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK", @@ -87,17 +77,6 @@ impl SslConnector { Ok(SslConnectorBuilder(ctx)) } - /// Creates a new builder for TLS connections with raw public key. - #[cfg(feature = "rpk")] - pub fn rpk_builder() -> Result { - let mut ctx = ctx(ContextType::Rpk)?; - ctx.set_cipher_list( - "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK", - )?; - - Ok(SslConnectorBuilder(ctx)) - } - /// Initiates a client-side TLS session on a stream. /// /// The domain is used for SNI and hostname verification. @@ -224,13 +203,7 @@ impl ConnectConfiguration { self.ssl.set_hostname(domain)?; } - #[cfg(feature = "rpk")] - let verify_hostname = !self.ssl.ssl_context().is_rpk() && self.verify_hostname; - - #[cfg(not(feature = "rpk"))] - let verify_hostname = self.verify_hostname; - - if verify_hostname { + if self.verify_hostname { setup_verify_hostname(&mut self.ssl, domain)?; } @@ -292,21 +265,6 @@ impl DerefMut for ConnectConfiguration { pub struct SslAcceptor(SslContext); impl SslAcceptor { - /// Creates a new builder configured to connect to clients that support Raw Public Keys. - #[cfg(feature = "rpk")] - pub fn rpk() -> Result { - let mut ctx = ctx(ContextType::Rpk)?; - ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1); - let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?; - ctx.set_tmp_dh(&dh)?; - ctx.set_cipher_list( - "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:\ - ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\ - DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384" - )?; - Ok(SslAcceptorBuilder(ctx)) - } - /// Creates a new builder configured to connect to non-legacy clients. This should generally be /// considered a reasonable default choice. /// @@ -315,7 +273,7 @@ impl SslAcceptor { /// /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS pub fn mozilla_intermediate_v5(method: SslMethod) -> Result { - let mut ctx = ctx(ContextType::WithMethod(method))?; + let mut ctx = ctx(method)?; ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1); let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?; ctx.set_tmp_dh(&dh)?; @@ -336,7 +294,7 @@ impl SslAcceptor { /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS // FIXME remove in next major version pub fn mozilla_intermediate(method: SslMethod) -> Result { - let mut ctx = ctx(ContextType::WithMethod(method))?; + let mut ctx = ctx(method)?; ctx.set_options(SslOptions::CIPHER_SERVER_PREFERENCE); ctx.set_options(SslOptions::NO_TLSV1_3); let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?; @@ -362,7 +320,7 @@ impl SslAcceptor { /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS // FIXME remove in next major version pub fn mozilla_modern(method: SslMethod) -> Result { - let mut ctx = ctx(ContextType::WithMethod(method))?; + let mut ctx = ctx(method)?; ctx.set_options( SslOptions::CIPHER_SERVER_PREFERENCE | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1, ); diff --git a/boring/src/ssl/credential.rs b/boring/src/ssl/credential.rs new file mode 100644 index 000000000..df8fb0c30 --- /dev/null +++ b/boring/src/ssl/credential.rs @@ -0,0 +1,211 @@ +#[cfg(feature = "rpk")] +use crate::cvt_p; +use crate::error::ErrorStack; +use crate::ex_data::Index; +use crate::pkey::{PKeyRef, Private}; +use crate::ssl::callbacks; +use crate::ssl::PrivateKeyMethod; +use crate::{cvt_0i, cvt_n}; +use crate::{ffi, free_data_box}; +use foreign_types::{ForeignType, ForeignTypeRef}; +use openssl_macros::corresponds; +use std::any::TypeId; +use std::collections::HashMap; +use std::ffi::{c_int, c_void}; +use std::mem; +use std::ptr; +use std::sync::{LazyLock, Mutex}; + +static SSL_CREDENTIAL_INDEXES: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); + +foreign_type_and_impl_send_sync! { + type CType = ffi::SSL_CREDENTIAL; + fn drop = ffi::SSL_CREDENTIAL_free; + + /// A credential. + pub struct SslCredential; +} + +impl SslCredential { + /// Create a credential suitable for a handshake using a raw public key. + #[corresponds(SSL_CREDENTIAL_new_raw_public_key)] + #[cfg(feature = "rpk")] + pub fn new_raw_public_key() -> Result { + unsafe { + Ok(SslCredentialBuilder(Self::from_ptr(cvt_p( + ffi::SSL_CREDENTIAL_new_raw_public_key(), + )?))) + } + } + + /// Returns a new extra data index. + /// + /// Each invocation of this function is guaranteed to return a distinct index. These can be used + /// to store data in the context that can be retrieved later by callbacks, for example. + #[corresponds(SSL_C_get_ex_new_index)] + pub fn new_ex_index() -> Result, ErrorStack> + where + T: 'static + Sync + Send, + { + unsafe { + ffi::init(); + let idx = cvt_n(get_new_ssl_credential_idx(Some(free_data_box::)))?; + Ok(Index::from_raw(idx)) + } + } + + // FIXME should return a result? + pub(crate) fn cached_ex_index() -> Index + where + T: 'static + Sync + Send, + { + unsafe { + let idx = *SSL_CREDENTIAL_INDEXES + .lock() + .unwrap_or_else(|e| e.into_inner()) + .entry(TypeId::of::()) + .or_insert_with(|| Self::new_ex_index::().unwrap().as_raw()); + Index::from_raw(idx) + } + } +} + +impl SslCredentialRef { + /// Returns a reference to the extra data at the specified index. + #[corresponds(SSL_CREDENTIAL_get_ex_data)] + #[must_use] + pub fn ex_data(&self, index: Index) -> Option<&T> { + unsafe { + let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); + if data.is_null() { + None + } else { + Some(&*(data as *const T)) + } + } + } + + // Unsafe because SSL contexts are not guaranteed to be unique, we call + // this only from SslCredentialBuilder. + #[corresponds(SSL_CREDENTIAL_get_ex_data)] + pub(crate) unsafe fn ex_data_mut( + &mut self, + index: Index, + ) -> Option<&mut T> { + let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); + if data.is_null() { + None + } else { + Some(&mut *(data as *mut T)) + } + } + + // Unsafe because SSL contexts are not guaranteed to be unique, we call + // this only from SslCredentialBuilder. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + pub(crate) unsafe fn replace_ex_data( + &mut self, + index: Index, + data: T, + ) -> Option { + if let Some(old) = self.ex_data_mut(index) { + return Some(mem::replace(old, data)); + } + + unsafe { + let data = Box::into_raw(Box::new(data)) as *mut c_void; + ffi::SSL_CREDENTIAL_set_ex_data(self.as_ptr(), index.as_raw(), data); + } + + None + } +} + +/// A builder for [`SslCredential`] +pub struct SslCredentialBuilder(SslCredential); + +impl SslCredentialBuilder { + /// Sets or overwrites the extra data at the specified index. + /// + /// This can be used to provide data to callbacks registered with the context. Use the + /// `SslCredential::new_ex_index` method to create an `Index`. + /// + /// Any previous value will be returned and replaced by the new one. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + pub fn replace_ex_data(&mut self, index: Index, data: T) -> Option { + unsafe { self.0.replace_ex_data(index, data) } + } + + // Sets the private key of the credential. + #[corresponds(SSL_CREDENTIAL_set1_private_key)] + pub fn set_private_key(&mut self, private_key: &PKeyRef) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_CREDENTIAL_set1_private_key( + self.0.as_ptr(), + private_key.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Configures a custom private key method on the credential. + /// + /// See [`PrivateKeyMethod`] for more details. + #[corresponds(SSL_CREDENTIAL_set_private_key_method)] + pub fn set_private_key_method(&mut self, method: M) -> Result<(), ErrorStack> + where + M: PrivateKeyMethod, + { + unsafe { + self.replace_ex_data(SslCredential::cached_ex_index::(), method); + + cvt_0i(ffi::SSL_CREDENTIAL_set_private_key_method( + self.0.as_ptr(), + &ffi::SSL_PRIVATE_KEY_METHOD { + sign: Some(callbacks::raw_sign::), + decrypt: Some(callbacks::raw_decrypt::), + complete: Some(callbacks::raw_complete::), + }, + )) + .map(|_| ()) + } + } + + // Sets the SPKI of the raw public key credential. + // + // If `spki` is `None`, the SPKI is extracted from the credential's private key. + #[corresponds(SSL_CREDENTIAL_set1_spki)] + #[cfg(feature = "rpk")] + pub fn set_spki_bytes(&mut self, spki: Option<&[u8]>) -> Result<(), ErrorStack> { + unsafe { + let spki = spki + .map(|spki| { + cvt_p(ffi::CRYPTO_BUFFER_new( + spki.as_ptr(), + spki.len(), + ptr::null_mut(), + )) + }) + .transpose()? + .unwrap_or(ptr::null_mut()); + + let ret = cvt_0i(ffi::SSL_CREDENTIAL_set1_spki(self.0.as_ptr(), spki)).map(|_| ()); + + if !spki.is_null() { + ffi::CRYPTO_BUFFER_free(spki); + } + + ret + } + } + + #[must_use] + pub fn build(self) -> SslCredential { + self.0 + } +} + +unsafe fn get_new_ssl_credential_idx(f: ffi::CRYPTO_EX_free) -> c_int { + ffi::SSL_CREDENTIAL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) +} diff --git a/boring/src/ssl/error.rs b/boring/src/ssl/error.rs index 5acad8200..1766724cb 100644 --- a/boring/src/ssl/error.rs +++ b/boring/src/ssl/error.rs @@ -79,6 +79,7 @@ impl ErrorCode { } #[corresponds(SSL_error_description)] + #[must_use] pub fn description(self) -> Option<&'static str> { unsafe { let msg = ffi::SSL_error_description(self.0); @@ -249,8 +250,7 @@ fn fmt_mid_handshake_error( f: &mut fmt::Formatter, prefix: &str, ) -> fmt::Result { - #[cfg(feature = "rpk")] - if s.ssl().ssl_context().is_rpk() { + if !s.ssl().ssl_context().has_x509_support() { write!(f, "{}", prefix)?; return write!(f, " {}", s.error()); } diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index a697cdc37..802e09390 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -58,18 +58,18 @@ //! } //! ``` use foreign_types::{ForeignType, ForeignTypeRef, Opaque}; -use libc::{c_char, c_int, c_uchar, c_uint, c_void}; use openssl_macros::corresponds; use std::any::TypeId; use std::collections::HashMap; use std::convert::TryInto; +use std::ffi::{c_char, c_int, c_uchar, c_uint}; use std::ffi::{CStr, CString}; use std::fmt; use std::io; use std::io::prelude::*; use std::marker::PhantomData; use std::mem::{self, ManuallyDrop, MaybeUninit}; -use std::ops::{Deref, DerefMut}; +use std::ops::Deref; use std::panic::resume_unwind; use std::path::Path; use std::ptr::{self, NonNull}; @@ -81,13 +81,18 @@ use crate::dh::DhRef; use crate::ec::EcKeyRef; use crate::error::ErrorStack; use crate::ex_data::Index; +use crate::hmac::HmacCtxRef; use crate::nid::Nid; +#[cfg(feature = "rpk")] +use crate::pkey::Public; use crate::pkey::{HasPrivate, PKeyRef, Params, Private}; use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef}; use crate::ssl::bio::BioMethod; use crate::ssl::callbacks::*; use crate::ssl::error::InnerError; use crate::stack::{Stack, StackRef, Stackable}; +use crate::symm::CipherCtxRef; +use crate::try_int; use crate::x509::store::{X509Store, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef}; use crate::x509::verify::X509VerifyParamRef; use crate::x509::{ @@ -104,7 +109,7 @@ pub use self::async_callbacks::{ pub use self::connector::{ ConnectConfiguration, SslAcceptor, SslAcceptorBuilder, SslConnector, SslConnectorBuilder, }; -#[cfg(not(feature = "fips"))] +pub use self::credential::{SslCredential, SslCredentialBuilder, SslCredentialRef}; pub use self::ech::{SslEchKeys, SslEchKeysRef}; pub use self::error::{Error, ErrorCode, HandshakeError}; @@ -112,7 +117,7 @@ mod async_callbacks; mod bio; mod callbacks; mod connector; -#[cfg(not(feature = "fips"))] +mod credential; mod ech; mod error; mod mut_only; @@ -243,59 +248,87 @@ bitflags! { /// A type specifying the kind of protocol an `SslContext` will speak. #[derive(Copy, Clone)] -pub struct SslMethod(*const ffi::SSL_METHOD); +pub struct SslMethod { + ptr: *const ffi::SSL_METHOD, + is_x509_method: bool, +} impl SslMethod { /// Support all versions of the TLS protocol. #[corresponds(TLS_method)] #[must_use] - pub fn tls() -> SslMethod { - unsafe { SslMethod(TLS_method()) } + pub fn tls() -> Self { + unsafe { + Self { + ptr: ffi::TLS_method(), + is_x509_method: true, + } + } } - /// Same as `tls`, but doesn't create X509 for certificates. - #[cfg(feature = "rpk")] - pub fn tls_with_buffer() -> SslMethod { - unsafe { SslMethod(ffi::TLS_with_buffers_method()) } + /// Same as `tls`, but doesn't create X.509 for certificates. + /// + /// # Safety + /// + /// BoringSSL will crash if the user calls a function that involves + /// X.509 certificates with an object configured with this method. + /// You most probably don't need it. + #[must_use] + pub unsafe fn tls_with_buffer() -> Self { + unsafe { + Self { + ptr: ffi::TLS_with_buffers_method(), + is_x509_method: false, + } + } } /// Support all versions of the DTLS protocol. #[corresponds(DTLS_method)] #[must_use] - pub fn dtls() -> SslMethod { - unsafe { SslMethod(DTLS_method()) } - } - - /// Support all versions of the TLS protocol, explicitly as a client. - #[corresponds(TLS_client_method)] - #[must_use] - pub fn tls_client() -> SslMethod { - unsafe { SslMethod(TLS_client_method()) } - } - - /// Support all versions of the TLS protocol, explicitly as a server. - #[corresponds(TLS_server_method)] - #[must_use] - pub fn tls_server() -> SslMethod { - unsafe { SslMethod(TLS_server_method()) } + pub fn dtls() -> Self { + unsafe { + Self { + ptr: ffi::DTLS_method(), + is_x509_method: true, + } + } } /// Constructs an `SslMethod` from a pointer to the underlying OpenSSL value. /// + /// This method assumes that the `SslMethod` is not configured for X.509 + /// certificates. The user can call `SslMethod::assume_x509_method` + /// to change that. + /// /// # Safety /// /// The caller must ensure the pointer is valid. #[corresponds(TLS_server_method)] #[must_use] pub unsafe fn from_ptr(ptr: *const ffi::SSL_METHOD) -> SslMethod { - SslMethod(ptr) + SslMethod { + ptr, + is_x509_method: false, + } + } + + /// Assumes that this `SslMethod` is configured for X.509 certificates. + /// + /// # Safety + /// + /// BoringSSL will crash if the user calls a function that involves + /// X.509 certificates with an object configured with this method. + /// You most probably don't need it. + pub unsafe fn assume_x509(&mut self) { + self.is_x509_method = true; } /// Returns a pointer to the underlying OpenSSL value. #[allow(clippy::trivially_copy_pass_by_ref)] #[must_use] pub fn as_ptr(&self) -> *const ffi::SSL_METHOD { - self.0 + self.ptr } } @@ -447,8 +480,7 @@ static SSL_INDEXES: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); static SESSION_CTX_INDEX: LazyLock> = LazyLock::new(|| Ssl::new_ex_index().unwrap()); -#[cfg(feature = "rpk")] -static RPK_FLAG_INDEX: LazyLock> = +static X509_FLAG_INDEX: LazyLock> = LazyLock::new(|| SslContext::new_ex_index().unwrap()); /// An error returned from the SNI callback. @@ -697,127 +729,13 @@ impl From for SslSignatureAlgorithm { } } -/// Numeric identifier of a TLS curve. -#[repr(transparent)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[deprecated(note = "this is not ABI-stable and will be removed in the next release")] -pub struct SslCurveNid(c_int); - -/// A TLS Curve. -#[repr(transparent)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[deprecated(note = "this is not ABI-stable and will be removed in the next release")] -pub struct SslCurve(c_int); - -#[allow(deprecated)] -impl SslCurve { - pub const SECP224R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP224R1 as _); - - pub const SECP256R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP256R1 as _); - - pub const SECP384R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP384R1 as _); - - pub const SECP521R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP521R1 as _); - - pub const X25519: SslCurve = SslCurve(ffi::SSL_CURVE_X25519 as _); - - #[cfg(not(any(feature = "fips", feature = "fips-precompiled")))] - pub const X25519_KYBER768_DRAFT00: SslCurve = - SslCurve(ffi::SSL_CURVE_X25519_KYBER768_DRAFT00 as _); - - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - pub const X25519_KYBER768_DRAFT00_OLD: SslCurve = - SslCurve(ffi::SSL_CURVE_X25519_KYBER768_DRAFT00_OLD as _); - - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - pub const X25519_KYBER512_DRAFT00: SslCurve = - SslCurve(ffi::SSL_CURVE_X25519_KYBER512_DRAFT00 as _); - - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - pub const P256_KYBER768_DRAFT00: SslCurve = SslCurve(ffi::SSL_CURVE_P256_KYBER768_DRAFT00 as _); - - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - pub const X25519_MLKEM768: SslCurve = SslCurve(ffi::SSL_CURVE_X25519_MLKEM768 as _); - - /// Returns the curve name - #[corresponds(SSL_get_curve_name)] - #[must_use] - pub fn name(&self) -> Option<&'static str> { - unsafe { - let ptr = ffi::SSL_get_curve_name(self.0 as u16); - if ptr.is_null() { - return None; - } - - CStr::from_ptr(ptr).to_str().ok() - } - } - - // We need to allow dead_code here because `SslRef::set_curves` is conditionally compiled - // against the absence of the `kx-safe-default` feature and thus this function is never used. - // - // **NOTE**: This function only exists because the version of boringssl we currently use does - // not expose SSL_CTX_set1_group_ids. Because `SslRef::curve()` returns the public SSL_CURVE id - // as opposed to the internal NID, but `SslContextBuilder::set_curves()` requires the internal - // NID, we need this mapping in place to avoid breaking changes to the public API. Once the - // underlying boringssl version is upgraded, this should be removed in favor of the new - // SSL_CTX_set1_group_ids API. - #[allow(dead_code)] - pub fn nid(&self) -> Option { - match self.0 { - ffi::SSL_CURVE_SECP224R1 => Some(ffi::NID_secp224r1), - ffi::SSL_CURVE_SECP256R1 => Some(ffi::NID_X9_62_prime256v1), - ffi::SSL_CURVE_SECP384R1 => Some(ffi::NID_secp384r1), - ffi::SSL_CURVE_SECP521R1 => Some(ffi::NID_secp521r1), - ffi::SSL_CURVE_X25519 => Some(ffi::NID_X25519), - #[cfg(not(any(feature = "fips", feature = "fips-precompiled")))] - ffi::SSL_CURVE_X25519_KYBER768_DRAFT00 => Some(ffi::NID_X25519Kyber768Draft00), - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - ffi::SSL_CURVE_X25519_KYBER768_DRAFT00_OLD => Some(ffi::NID_X25519Kyber768Draft00Old), - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - ffi::SSL_CURVE_X25519_KYBER512_DRAFT00 => Some(ffi::NID_X25519Kyber512Draft00), - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - ffi::SSL_CURVE_P256_KYBER768_DRAFT00 => Some(ffi::NID_P256Kyber768Draft00), - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - ffi::SSL_CURVE_X25519_MLKEM768 => Some(ffi::NID_X25519MLKEM768), - _ => None, - } - .map(SslCurveNid) - } -} - /// A compliance policy. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg(not(feature = "fips-compat"))] pub struct CompliancePolicy(ffi::ssl_compliance_policy_t); -#[cfg(not(feature = "fips-compat"))] impl CompliancePolicy { /// Does nothing, however setting this does not undo other policies, so trying to set this is an error. + #[cfg(not(feature = "legacy-compat-deprecated"))] pub const NONE: Self = Self(ffi::ssl_compliance_policy_t::ssl_compliance_policy_none); /// Configures a TLS connection to try and be compliant with NIST requirements, but does not guarantee success. @@ -827,6 +745,7 @@ impl CompliancePolicy { /// Partially configures a TLS connection to be compliant with WPA3. Callers must enforce certificate chain requirements themselves. /// Use of this policy is less secure than the default and not recommended. + #[cfg(not(feature = "legacy-compat-deprecated"))] pub const WPA3_192_202304: Self = Self(ffi::ssl_compliance_policy_t::ssl_compliance_policy_wpa3_192_202304); } @@ -865,13 +784,13 @@ pub fn select_next_proto<'a>(server: &'a [u8], client: &'a [u8]) -> Option<&'a [ &mut out, &mut outlen, server.as_ptr(), - server.len() as c_uint, + try_int(server.len()).ok()?, client.as_ptr(), - client.len() as c_uint, + try_int(client.len()).ok()?, ); if r == ffi::OPENSSL_NPN_NEGOTIATED { - Some(slice::from_raw_parts(out as *const u8, outlen as usize)) + Some(slice::from_raw_parts(out.cast_const(), outlen as usize)) } else { None } @@ -919,6 +838,53 @@ pub enum SslInfoCallbackValue { Alert(SslInfoCallbackAlert), } +/// Ticket key callback status. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum TicketKeyCallbackResult { + /// Abort the handshake. + Error, + + /// Continue with a full handshake. + /// + /// When in decryption mode, this indicates that the peer supplied session ticket was not + /// recognized. When in encryption mode, this instructs boring to not send a session ticket. + /// + /// # Note + /// + /// This is a decryption specific status code when using the submoduled BoringSSL. + Noop, + + /// Resumption callback was successful. + /// + /// When in decryption mode, attempt an abbreviated handshake via session resumption. When in + /// encryption mode, provide a new ticket to the client. + Success, + + /// Resumption callback was successful. Attempt an abbreviated handshake, and additionally + /// provide new session tickets to the peer. + /// + /// Session resumption short-circuits some security checks of a full-handshake, in exchange for + /// potential performance gains. For this reason, a session ticket should only be valid for a + /// limited time. Providing the peer with renewed session tickets allows them to continue + /// session resumption with the new tickets. + /// + /// # Note + /// + /// This is a decryption specific status code. + DecryptSuccessRenew, +} + +impl From for c_int { + fn from(value: TicketKeyCallbackResult) -> Self { + match value { + TicketKeyCallbackResult::Error => -1, + TicketKeyCallbackResult::Noop => 0, + TicketKeyCallbackResult::Success => 1, + TicketKeyCallbackResult::DecryptSuccessRenew => 2, + } + } +} + #[derive(Hash, Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Debug)] pub struct SslInfoCallbackAlert(c_int); @@ -946,63 +912,11 @@ impl Ssl3AlertLevel { pub const FATAL: Ssl3AlertLevel = Self(ffi::SSL3_AL_FATAL); } -#[cfg(feature = "rpk")] -extern "C" fn rpk_verify_failure_callback( - _ssl: *mut ffi::SSL, - _out_alert: *mut u8, -) -> ffi::ssl_verify_result_t { - // Always verify the peer. - ffi::ssl_verify_result_t::ssl_verify_invalid -} - /// A builder for `SslContext`s. pub struct SslContextBuilder { ctx: SslContext, /// If it's not shared, it can be exposed as mutable has_shared_cert_store: bool, - #[cfg(feature = "rpk")] - is_rpk: bool, -} - -#[cfg(feature = "rpk")] -impl SslContextBuilder { - /// Creates a new `SslContextBuilder` to be used with Raw Public Key. - #[corresponds(SSL_CTX_new)] - pub fn new_rpk() -> Result { - unsafe { - init(); - let ctx = cvt_p(ffi::SSL_CTX_new(SslMethod::tls_with_buffer().as_ptr()))?; - - Ok(SslContextBuilder::from_ptr(ctx, true)) - } - } - - /// Sets raw public key certificate in DER format. - pub fn set_rpk_certificate(&mut self, cert: &[u8]) -> Result<(), ErrorStack> { - unsafe { - cvt(ffi::SSL_CTX_set_server_raw_public_key_certificate( - self.as_ptr(), - cert.as_ptr(), - cert.len() as u32, - )) - .map(|_| ()) - } - } - - /// Sets RPK null chain private key. - pub fn set_null_chain_private_key(&mut self, key: &PKeyRef) -> Result<(), ErrorStack> - where - T: HasPrivate, - { - unsafe { - cvt(ffi::SSL_CTX_set_nullchain_and_key( - self.as_ptr(), - key.as_ptr(), - ptr::null_mut(), - )) - .map(|_| ()) - } - } } impl SslContextBuilder { @@ -1012,49 +926,43 @@ impl SslContextBuilder { unsafe { init(); let ctx = cvt_p(ffi::SSL_CTX_new(method.as_ptr()))?; + let mut builder = SslContextBuilder::from_ptr(ctx); - #[cfg(feature = "rpk")] - { - Ok(SslContextBuilder::from_ptr(ctx, false)) + if method.is_x509_method { + builder.ctx.assume_x509(); } - #[cfg(not(feature = "rpk"))] - { - Ok(SslContextBuilder::from_ptr(ctx)) - } + Ok(builder) } } /// Creates an `SslContextBuilder` from a pointer to a raw OpenSSL value. /// + /// This method can find out whether `ctx` is configured for X.509 certificates + /// if `ctx` was itself a context created by this crate. If it was created by + /// other means and it supports X.509 certificates, the use can call + /// `SslContextBuilder::assume_x509`. + /// /// # Safety /// /// The caller must ensure that the pointer is valid and uniquely owned by the builder. - #[cfg(feature = "rpk")] - pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX, is_rpk: bool) -> SslContextBuilder { - let ctx = SslContext::from_ptr(ctx); - let mut builder = SslContextBuilder { - ctx, - is_rpk, + /// The context must own its cert store exclusively. + pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> Self { + Self { + ctx: SslContext::from_ptr(ctx), has_shared_cert_store: false, - }; - - builder.replace_ex_data(*RPK_FLAG_INDEX, is_rpk); - - builder + } } - /// Creates an `SslContextBuilder` from a pointer to a raw OpenSSL value. + /// Assumes that this `SslContextBuilder` is configured for X.509 certificates. /// /// # Safety /// - /// The caller must ensure that the pointer is valid and uniquely owned by the builder. - #[cfg(not(feature = "rpk"))] - pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextBuilder { - SslContextBuilder { - ctx: SslContext::from_ptr(ctx), - has_shared_cert_store: false, - } + /// BoringSSL will crash if the user calls a function that involves + /// X.509 certificates with an object configured with this method. + /// You most probably don't need it. + pub unsafe fn assume_x509(&mut self) { + self.ctx.assume_x509(); } /// Returns a pointer to the raw OpenSSL value. @@ -1073,11 +981,11 @@ impl SslContextBuilder { /// whether the chain is accepted or not. /// /// *Warning*: Providing a complete verification procedure is a complex task. See - /// https://docs.openssl.org/master/man3/SSL_CTX_set_cert_verify_callback/#notes for more - /// information. + /// [`SSL_CTX_set_cert_verify_callback`](https://docs.openssl.org/master/man3/SSL_CTX_set_cert_verify_callback/#notes) + /// for more information. /// - /// TODO: Add the ability to unset the callback by either adding a new function or wrapping the - /// callback in an `Option`. + // TODO: Add the ability to unset the callback by either adding a new function or wrapping the + // callback in an `Option`. /// /// # Panics /// @@ -1087,8 +995,7 @@ impl SslContextBuilder { where F: Fn(&mut X509StoreContextRef) -> bool + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); // NOTE(jlarisch): Q: Why don't we wrap the callback in an Arc, since // `set_verify_callback` does? @@ -1109,11 +1016,10 @@ impl SslContextBuilder { /// Configures the certificate verification method for new connections. #[corresponds(SSL_CTX_set_verify)] pub fn set_verify(&mut self, mode: SslVerifyMode) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { - ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, None); + ffi::SSL_CTX_set_verify(self.as_ptr(), c_int::from(mode.bits()), None); } } @@ -1138,12 +1044,15 @@ impl SslContextBuilder { where F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { self.replace_ex_data(SslContext::cached_ex_index::(), callback); - ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, Some(raw_verify::)); + ffi::SSL_CTX_set_verify( + self.as_ptr(), + c_int::from(mode.bits()), + Some(raw_verify::), + ); } } @@ -1166,14 +1075,11 @@ impl SslContextBuilder { where F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); - unsafe { self.replace_ex_data(SslContext::cached_ex_index::(), callback); ffi::SSL_CTX_set_custom_verify( self.as_ptr(), - mode.bits() as c_int, + c_int::from(mode.bits()), Some(raw_custom_verify::), ); } @@ -1203,21 +1109,65 @@ impl SslContextBuilder { let callback_index = SslContext::cached_ex_index::(); self.ctx.replace_ex_data(callback_index, callback); + let callback = self.ctx.ex_data(callback_index).unwrap(); - let arg = self.ctx.ex_data(callback_index).unwrap() as *const F as *mut c_void; - + let arg = std::ptr::from_ref(callback).cast_mut().cast(); ffi::SSL_CTX_set_tlsext_servername_arg(self.as_ptr(), arg); ffi::SSL_CTX_set_tlsext_servername_callback(self.as_ptr(), Some(raw_sni::)); } } + /// Configures a custom session ticket key callback for session resumption. + /// + /// Session Resumption uses the security context (aka. session tickets) of a previous + /// connection to establish a new connection via an abbreviated handshake. Skipping portions of + /// a handshake can potentially yield performance gains. + /// + /// An attacker that compromises a server's session ticket key can impersonate the server and, + /// prior to TLS 1.3, retroactively decrypt all application traffic from sessions using that + /// ticket key. Thus ticket keys must be regularly rotated for forward secrecy. + /// + /// CipherCtx and HmacCtx are guaranteed to be initialized. + /// + /// # Panics + /// + /// This method panics if this `Ssl` is associated with a RPK context. + /// + /// # Safety + /// + /// The application is responsible for correctly setting the key_name, iv, encryption context + /// and hmac context. See the [`SSL_CTX_set_tlsext_ticket_key_cb`] docs for additional info. + /// + /// [`SSL_CTX_set_tlsext_ticket_key_cb`]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_CTX_set_tlsext_ticket_key_cb + #[corresponds(SSL_CTX_set_tlsext_ticket_key_cb)] + pub unsafe fn set_ticket_key_callback(&mut self, callback: F) + where + F: Fn( + &SslRef, + &mut [u8; 16], + &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], + &mut CipherCtxRef, + &mut HmacCtxRef, + bool, + ) -> TicketKeyCallbackResult + + 'static + + Sync + + Send, + { + self.ctx.check_x509(); + + unsafe { + self.replace_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_tlsext_ticket_key_cb(self.as_ptr(), Some(raw_ticket_key::)) + }; + } + /// Sets the certificate verification depth. /// /// If the peer's certificate chain is longer than this value, verification will fail. #[corresponds(SSL_CTX_set_verify_depth)] pub fn set_verify_depth(&mut self, depth: u32) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { ffi::SSL_CTX_set_verify_depth(self.as_ptr(), depth as c_int); @@ -1227,28 +1177,27 @@ impl SslContextBuilder { /// Sets a custom certificate store for verifying peer certificates. #[corresponds(SSL_CTX_set0_verify_cert_store)] pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { - cvt( - ffi::SSL_CTX_set0_verify_cert_store(self.as_ptr(), cert_store.into_ptr()) as c_int, - )?; - - Ok(()) + cvt(ffi::SSL_CTX_set0_verify_cert_store( + self.as_ptr(), + cert_store.into_ptr(), + )) } } - /// Use [`set_cert_store_builder`] or [`set_cert_store_ref`] instead. + /// Replaces the context's certificate store, and keeps it immutable. + /// + /// This method allows sharing the `X509Store`, but calls to `cert_store_mut` will panic. /// - /// Replaces the context's certificate store. + /// Use [`set_cert_store_builder`] to set a mutable cert store + /// (there's no way to have both sharing and mutability). #[corresponds(SSL_CTX_set_cert_store)] - #[deprecated(note = "Use set_cert_store_builder or set_cert_store_ref instead")] pub fn set_cert_store(&mut self, cert_store: X509Store) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); - self.has_shared_cert_store = false; + self.has_shared_cert_store = true; unsafe { ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.into_ptr()); } @@ -1257,8 +1206,7 @@ impl SslContextBuilder { /// Replaces the context's certificate store, and allows mutating the store afterwards. #[corresponds(SSL_CTX_set_cert_store)] pub fn set_cert_store_builder(&mut self, cert_store: X509StoreBuilder) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); self.has_shared_cert_store = false; unsafe { @@ -1271,14 +1219,7 @@ impl SslContextBuilder { /// This method allows sharing the `X509Store`, but calls to `cert_store_mut` will panic. #[corresponds(SSL_CTX_set_cert_store)] pub fn set_cert_store_ref(&mut self, cert_store: &X509Store) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); - - self.has_shared_cert_store = true; - unsafe { - ffi::X509_STORE_up_ref(cert_store.as_ptr()); - ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.as_ptr()); - } + self.set_cert_store(cert_store.to_owned()); } /// Controls read ahead behavior. @@ -1304,13 +1245,13 @@ impl SslContextBuilder { /// Sets the parameters to be used during ephemeral Diffie-Hellman key exchange. #[corresponds(SSL_CTX_set_tmp_dh)] pub fn set_tmp_dh(&mut self, dh: &DhRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_set_tmp_dh(self.as_ptr(), dh.as_ptr()) as c_int).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_set_tmp_dh(self.as_ptr(), dh.as_ptr())) } } /// Sets the parameters to be used during ephemeral elliptic curve Diffie-Hellman key exchange. #[corresponds(SSL_CTX_set_tmp_ecdh)] pub fn set_tmp_ecdh(&mut self, key: &EcKeyRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_set_tmp_ecdh(self.as_ptr(), key.as_ptr()) as c_int).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_set_tmp_ecdh(self.as_ptr(), key.as_ptr())) } } /// Use the default locations of trusted certificates for verification. @@ -1319,10 +1260,9 @@ impl SslContextBuilder { /// if present, or defaults specified at OpenSSL build time otherwise. #[corresponds(SSL_CTX_set_default_verify_paths)] pub fn set_default_verify_paths(&mut self) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); - unsafe { cvt(ffi::SSL_CTX_set_default_verify_paths(self.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_set_default_verify_paths(self.as_ptr())) } } /// Loads trusted root certificates from a file. @@ -1330,18 +1270,16 @@ impl SslContextBuilder { /// The file should contain a sequence of PEM-formatted CA certificates. #[corresponds(SSL_CTX_load_verify_locations)] pub fn set_ca_file>(&mut self, file: P) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes()) .map_err(ErrorStack::internal_error)?; unsafe { cvt(ffi::SSL_CTX_load_verify_locations( self.as_ptr(), - file.as_ptr() as *const _, + file.as_ptr(), ptr::null(), )) - .map(|_| ()) } } @@ -1351,8 +1289,7 @@ impl SslContextBuilder { /// as trusted by this method. #[corresponds(SSL_CTX_set_client_CA_list)] pub fn set_client_ca_list(&mut self, list: Stack) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { ffi::SSL_CTX_set_client_CA_list(self.as_ptr(), list.as_ptr()); @@ -1364,10 +1301,9 @@ impl SslContextBuilder { /// requesting client-side TLS authentication. #[corresponds(SSL_CTX_add_client_CA)] pub fn add_client_ca(&mut self, cacert: &X509Ref) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); - unsafe { cvt(ffi::SSL_CTX_add_client_CA(self.as_ptr(), cacert.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_add_client_CA(self.as_ptr(), cacert.as_ptr())) } } /// Set the context identifier for sessions. @@ -1387,7 +1323,6 @@ impl SslContextBuilder { sid_ctx.as_ptr(), sid_ctx.len(), )) - .map(|_| ()) } } @@ -1402,18 +1337,16 @@ impl SslContextBuilder { file: P, file_type: SslFiletype, ) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes()) .map_err(ErrorStack::internal_error)?; unsafe { cvt(ffi::SSL_CTX_use_certificate_file( self.as_ptr(), - file.as_ptr() as *const _, + file.as_ptr(), file_type.as_raw(), )) - .map(|_| ()) } } @@ -1432,9 +1365,8 @@ impl SslContextBuilder { unsafe { cvt(ffi::SSL_CTX_use_certificate_chain_file( self.as_ptr(), - file.as_ptr() as *const _, + file.as_ptr(), )) - .map(|_| ()) } } @@ -1443,7 +1375,7 @@ impl SslContextBuilder { /// Use `add_extra_chain_cert` to add the remainder of the certificate chain. #[corresponds(SSL_CTX_use_certificate)] pub fn set_certificate(&mut self, cert: &X509Ref) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_use_certificate(self.as_ptr(), cert.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_use_certificate(self.as_ptr(), cert.as_ptr())) } } /// Appends a certificate to the certificate chain. @@ -1452,12 +1384,13 @@ impl SslContextBuilder { /// `set_certificate` to a trusted root. #[corresponds(SSL_CTX_add_extra_chain_cert)] pub fn add_extra_chain_cert(&mut self, cert: X509) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { - cvt(ffi::SSL_CTX_add_extra_chain_cert(self.as_ptr(), cert.into_ptr()) as c_int)?; - Ok(()) + cvt(ffi::SSL_CTX_add_extra_chain_cert( + self.as_ptr(), + cert.into_ptr(), + )) } } @@ -1473,10 +1406,9 @@ impl SslContextBuilder { unsafe { cvt(ffi::SSL_CTX_use_PrivateKey_file( self.as_ptr(), - file.as_ptr() as *const _, + file.as_ptr(), file_type.as_raw(), )) - .map(|_| ()) } } @@ -1486,27 +1418,49 @@ impl SslContextBuilder { where T: HasPrivate, { - unsafe { cvt(ffi::SSL_CTX_use_PrivateKey(self.as_ptr(), key.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_use_PrivateKey(self.as_ptr(), key.as_ptr())) } } - /// Sets the list of supported ciphers for protocols before TLSv1.3. + /// Sets the list of supported ciphers for protocols before TLSv1.3, ignoring meaningless entries. + /// + /// See [`SslContextBuilder::set_strict_cipher_list()`]. /// /// The `set_ciphersuites` method controls the cipher suites for TLSv1.3 in OpenSSL. /// BoringSSL doesn't implement `set_ciphersuites`. - /// See https://github.com/google/boringssl/blob/master/include/openssl/ssl.h#L1542-L1544 + /// See [ssl.h](https://github.com/google/boringssl/blob/master/include/openssl/ssl.h#L1542-L1544). /// /// See [`ciphers`] for details on the format. /// /// [`ciphers`]: https://www.openssl.org/docs/manmaster/apps/ciphers.html #[corresponds(SSL_CTX_set_cipher_list)] pub fn set_cipher_list(&mut self, cipher_list: &str) -> Result<(), ErrorStack> { - let cipher_list = CString::new(cipher_list).unwrap(); + let cipher_list = CString::new(cipher_list).map_err(ErrorStack::internal_error)?; unsafe { cvt(ffi::SSL_CTX_set_cipher_list( self.as_ptr(), - cipher_list.as_ptr() as *const _, + cipher_list.as_ptr(), + )) + } + } + + /// Sets the list of supported ciphers for protocols before TLSv1.3 but do not + /// tolerate anything meaningless in the cipher list. + /// + /// The `set_ciphersuites` method controls the cipher suites for TLSv1.3 in OpenSSL. + /// BoringSSL doesn't implement `set_ciphersuites`. + /// See + /// + /// See [`ciphers`] for details on the format. + /// + /// [`ciphers`]: . + #[corresponds(SSL_CTX_set_strict_cipher_list)] + pub fn set_strict_cipher_list(&mut self, cipher_list: &str) -> Result<(), ErrorStack> { + let cipher_list = CString::new(cipher_list).map_err(ErrorStack::internal_error)?; + unsafe { + cvt(ffi::SSL_CTX_set_strict_cipher_list( + self.as_ptr(), + cipher_list.as_ptr(), )) - .map(|_| ()) } } @@ -1559,7 +1513,6 @@ impl SslContextBuilder { self.as_ptr(), version.map_or(0, |v| v.0 as _), )) - .map(|_| ()) } } @@ -1573,7 +1526,6 @@ impl SslContextBuilder { self.as_ptr(), version.map_or(0, |v| v.0 as _), )) - .map(|_| ()) } } @@ -1612,14 +1564,10 @@ impl SslContextBuilder { #[corresponds(SSL_CTX_set_alpn_protos)] pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> { unsafe { - #[cfg_attr(not(feature = "fips-compat"), allow(clippy::unnecessary_cast))] - { - assert!(protocols.len() <= ProtosLen::MAX as usize); - } let r = ffi::SSL_CTX_set_alpn_protos( self.as_ptr(), protocols.as_ptr(), - protocols.len() as ProtosLen, + try_int(protocols.len())?, ); // fun fact, SSL_CTX_set_alpn_protos has a reversed return code D: if r == 0 { @@ -1700,7 +1648,7 @@ impl SslContextBuilder { C: CertificateCompressor, { const { - assert!(C::CAN_COMPRESS || C::CAN_DECOMPRESS, "Either compression or decompression must be supported for algorithm to be registered") + assert!(C::CAN_COMPRESS || C::CAN_DECOMPRESS, "Either compression or decompression must be supported for algorithm to be registered"); }; let success = unsafe { ffi::SSL_CTX_add_cert_compression_alg( @@ -1747,22 +1695,21 @@ impl SslContextBuilder { decrypt: Some(callbacks::raw_decrypt::), complete: Some(callbacks::raw_complete::), }, - ) + ); } } /// Checks for consistency between the private key and certificate. #[corresponds(SSL_CTX_check_private_key)] pub fn check_private_key(&self) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_check_private_key(self.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_check_private_key(self.as_ptr())) } } /// Returns a shared reference to the context's certificate store. #[corresponds(SSL_CTX_get_cert_store)] #[must_use] pub fn cert_store(&self) -> &X509StoreBuilderRef { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { X509StoreBuilderRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) } } @@ -1778,12 +1725,12 @@ impl SslContextBuilder { /// #[corresponds(SSL_CTX_get_cert_store)] pub fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); assert!( !self.has_shared_cert_store, - "Shared X509Store can't be mutated. Make a new store" + "Shared X509Store can't be mutated. Use set_cert_store_builder() instead of set_cert_store() + or completely finish building the cert store setting it." ); // OTOH, it's not safe to return a shared &X509Store when the builder owns it exclusively @@ -1809,11 +1756,10 @@ impl SslContextBuilder { { unsafe { self.replace_ex_data(SslContext::cached_ex_index::(), callback); - cvt( - ffi::SSL_CTX_set_tlsext_status_cb(self.as_ptr(), Some(raw_tlsext_status::)) - as c_int, - ) - .map(|_| ()) + cvt(ffi::SSL_CTX_set_tlsext_status_cb( + self.as_ptr(), + Some(raw_tlsext_status::), + )) } } @@ -1956,33 +1902,24 @@ impl SslContextBuilder { } } - /// DEPRECATED: use `Self::replace_ex_data()` to set the data. - /// - /// If this method is called more than once with the same index, any previous - /// value stored in the `SslContextBuilder` may be leaked. This will change in the future release. - /// /// Sets the extra data at the specified index. /// /// This can be used to provide data to callbacks registered with the context. Use the /// `SslContext::new_ex_index` method to create an `Index`. #[corresponds(SSL_CTX_set_ex_data)] - #[deprecated( - note = "This may leak memory. Don't rely on leaking. Use `replace_ex_data()` instead." - )] pub fn set_ex_data(&mut self, index: Index, data: T) { unsafe { - self.ctx.write_ex_data(index, data); + self.ctx.replace_ex_data(index, data); } } /// Sets or overwrites the extra data at the specified index. /// /// This can be used to provide data to callbacks registered with the context. Use the - /// `Ssl::new_ex_index` method to create an `Index`. + /// `SslContext::new_ex_index` method to create an `Index`. /// /// Any previous value will be returned and replaced by the new one. #[corresponds(SSL_CTX_set_ex_data)] - #[doc(alias = "set_ex_data")] pub fn replace_ex_data(&mut self, index: Index, data: T) -> Option { unsafe { self.ctx.replace_ex_data(index, data) } } @@ -2001,8 +1938,10 @@ impl SslContextBuilder { pub fn set_sigalgs_list(&mut self, sigalgs: &str) -> Result<(), ErrorStack> { let sigalgs = CString::new(sigalgs).map_err(ErrorStack::internal_error)?; unsafe { - cvt(ffi::SSL_CTX_set1_sigalgs_list(self.as_ptr(), sigalgs.as_ptr()) as c_int) - .map(|_| ()) + cvt(ffi::SSL_CTX_set1_sigalgs_list( + self.as_ptr(), + sigalgs.as_ptr(), + )) } } @@ -2013,12 +1952,7 @@ impl SslContextBuilder { } /// Configures whether ClientHello extensions should be permuted. - /// - /// Note: This is gated to non-fips because the fips feature builds with a separate - /// version of BoringSSL which doesn't yet include these APIs. - /// Once the submoduled fips commit is upgraded, these gates can be removed. #[corresponds(SSL_CTX_set_permute_extensions)] - #[cfg(not(feature = "fips-compat"))] pub fn set_permute_extensions(&mut self, enabled: bool) { unsafe { ffi::SSL_CTX_set_permute_extensions(self.as_ptr(), enabled as _) } } @@ -2032,7 +1966,7 @@ impl SslContextBuilder { unsafe { cvt_0i(ffi::SSL_CTX_set_verify_algorithm_prefs( self.as_ptr(), - prefs.as_ptr() as *const _, + prefs.as_ptr().cast(), prefs.len(), )) .map(|_| ()) @@ -2058,36 +1992,7 @@ impl SslContextBuilder { unsafe { cvt_0i(ffi::SSL_CTX_set1_curves_list( self.as_ptr(), - curves.as_ptr() as *const _, - )) - .map(|_| ()) - } - } - - /// Use `Self::set_curves_list()` instead. - /// - /// Sets the context's supported curves. - // - // If the "kx-*" flags are used to set key exchange preference, then don't allow the user to - // set them here. This ensures we don't override the user's preference without telling them: - // when the flags are used, the preferences are set just before connecting or accepting. - #[corresponds(SSL_CTX_set1_curves)] - #[cfg(not(feature = "kx-safe-default"))] - #[deprecated( - note = "Use set_curves_list(). set_curves() and SslCurve will be removed in the next release" - )] - #[allow(deprecated)] - pub fn set_curves(&mut self, curves: &[SslCurve]) -> Result<(), ErrorStack> { - let curves: Vec = curves - .iter() - .filter_map(|curve| curve.nid().map(|nid| nid.0)) - .collect(); - - unsafe { - cvt_0i(ffi::SSL_CTX_set1_curves( - self.as_ptr(), - curves.as_ptr() as *const _, - curves.len(), + curves.as_ptr(), )) .map(|_| ()) } @@ -2097,7 +2002,6 @@ impl SslContextBuilder { /// /// This feature isn't available in the certified version of BoringSSL. #[corresponds(SSL_CTX_set_compliance_policy)] - #[cfg(not(feature = "fips-compat"))] pub fn set_compliance_policy(&mut self, policy: CompliancePolicy) -> Result<(), ErrorStack> { unsafe { cvt_0i(ffi::SSL_CTX_set_compliance_policy(self.as_ptr(), policy.0)).map(|_| ()) } } @@ -2118,10 +2022,39 @@ impl SslContextBuilder { /// ECHConfigs to allow stale DNS caches to update. Unlike most `SSL_CTX` APIs, this function /// is safe to call even after the `SSL_CTX` has been associated with connections on various /// threads. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_CTX_set1_ech_keys)] pub fn set_ech_keys(&self, keys: &SslEchKeys) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())) } + } + + /// Adds a credential. + #[corresponds(SSL_CTX_add1_credential)] + pub fn add_credential(&mut self, credential: &SslCredentialRef) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_CTX_add1_credential( + self.as_ptr(), + credential.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Sets the list of server certificate types that clients attached to this context + /// can process. + #[corresponds(SSL_CTX_set_server_certificate_types)] + #[cfg(feature = "rpk")] + pub fn set_server_certificate_types( + &mut self, + types: &[CertificateType], + ) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_CTX_set_server_certificate_types( + self.as_ptr(), + types.as_ptr() as *const u8, + types.len(), + )) + .map(|_| ()) + } } /// Consumes the builder, returning a new `SslContext`. @@ -2227,8 +2160,7 @@ impl SslContextRef { #[corresponds(SSL_CTX_get0_certificate)] #[must_use] pub fn certificate(&self) -> Option<&X509Ref> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk(), "This API is not supported for RPK"); + self.check_x509(); unsafe { let ptr = ffi::SSL_CTX_get0_certificate(self.as_ptr()); @@ -2258,8 +2190,7 @@ impl SslContextRef { #[corresponds(SSL_CTX_get_cert_store)] #[must_use] pub fn cert_store(&self) -> &X509StoreRef { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk(), "This API is not supported for RPK"); + self.check_x509(); unsafe { X509StoreRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) } } @@ -2294,22 +2225,18 @@ impl SslContextRef { // this only from SslContextBuilder. #[corresponds(SSL_CTX_get_ex_data)] unsafe fn ex_data_mut(&mut self, index: Index) -> Option<&mut T> { - let data = ffi::SSL_CTX_get_ex_data(self.as_ptr(), index.as_raw()); - if data.is_null() { - None - } else { - Some(&mut *(data as *mut T)) - } + ffi::SSL_CTX_get_ex_data(self.as_ptr(), index.as_raw()) + .cast::() + .as_mut() } - /// Internal: does not run destructors for the previous value // Unsafe because SSL contexts are not guaranteed to be unique, we call // this only from SslContextBuilder. #[corresponds(SSL_CTX_set_ex_data)] - unsafe fn write_ex_data(&mut self, index: Index, data: T) { + unsafe fn set_ex_data(&mut self, index: Index, data: T) { unsafe { - let data = Box::into_raw(Box::new(data)) as *mut c_void; - ffi::SSL_CTX_set_ex_data(self.as_ptr(), index.as_raw(), data); + let data = Box::into_raw(Box::new(data)); + ffi::SSL_CTX_set_ex_data(self.as_ptr(), index.as_raw(), data.cast()); } } @@ -2321,7 +2248,7 @@ impl SslContextRef { return Some(mem::replace(old, data)); } - self.write_ex_data(index, data); + self.set_ex_data(index, data); None } @@ -2370,27 +2297,65 @@ impl SslContextRef { #[corresponds(SSL_CTX_get_verify_mode)] #[must_use] pub fn verify_mode(&self) -> SslVerifyMode { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk(), "This API is not supported for RPK"); + self.check_x509(); let mode = unsafe { ffi::SSL_CTX_get_verify_mode(self.as_ptr()) }; SslVerifyMode::from_bits(mode).expect("SSL_CTX_get_verify_mode returned invalid mode") } - /// Returns `true` if context was created for Raw Public Key verification - #[cfg(feature = "rpk")] - pub fn is_rpk(&self) -> bool { - self.ex_data(*RPK_FLAG_INDEX).copied().unwrap_or_default() + /// Assumes that this `SslContext` is configured for X.509 certificates. + /// + /// # Safety + /// + /// BoringSSL will crash if the user calls a function that involves + /// X.509 certificates with an object configured with this method. + /// You most probably don't need it. + pub unsafe fn assume_x509(&mut self) { + self.replace_ex_data(*X509_FLAG_INDEX, true); + } + + /// Returns `true` if context is configured for X.509 certificates. + #[must_use] + pub fn has_x509_support(&self) -> bool { + self.ex_data(*X509_FLAG_INDEX).copied().unwrap_or_default() + } + + #[track_caller] + fn check_x509(&self) { + assert!( + self.has_x509_support(), + "This context is not configured for X.509 certificates" + ); } /// Registers a list of ECH keys on the context. This list should contain new and old /// ECHConfigs to allow stale DNS caches to update. Unlike most `SSL_CTX` APIs, this function /// is safe to call even after the `SSL_CTX` has been associated with connections on various /// threads. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_CTX_set1_ech_keys)] pub fn set_ech_keys(&self, keys: &SslEchKeys) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())) } + } + + /// Returns the list of server certificate types. + #[corresponds(SSL_CTX_get0_server_certificate_types)] + #[cfg(feature = "rpk")] + #[must_use] + pub fn server_certificate_types(&self) -> Option<&[CertificateType]> { + let mut types = ptr::null(); + let mut types_len = 0; + unsafe { + ffi::SSL_CTX_get0_server_certificate_types(self.as_ptr(), &mut types, &mut types_len); + + if types_len == 0 { + return None; + } + + Some(slice::from_raw_parts( + types as *const CertificateType, + types_len, + )) + } } } @@ -2401,11 +2366,6 @@ impl SslContextRef { #[derive(Debug)] pub struct GetSessionPendingError; -#[cfg(not(feature = "fips-compat"))] -type ProtosLen = usize; -#[cfg(feature = "fips-compat")] -type ProtosLen = libc::c_uint; - /// Information about the state of a cipher. pub struct CipherBits { /// The number of secret bits used for the cipher. @@ -2482,7 +2442,8 @@ impl ClientHello<'_> { } /// Information about a cipher. -pub struct SslCipher(*mut ffi::SSL_CIPHER); +#[derive(Clone, Copy)] +pub struct SslCipher(&'static SslCipherRef); impl SslCipher { #[corresponds(SSL_get_cipher_by_value)] @@ -2493,7 +2454,7 @@ impl SslCipher { if ptr.is_null() { None } else { - Some(Self::from_ptr(ptr as *mut ffi::SSL_CIPHER)) + Some(Self::from_ptr(ptr.cast_mut())) } } } @@ -2509,12 +2470,12 @@ unsafe impl ForeignType for SslCipher { #[inline] unsafe fn from_ptr(ptr: *mut ffi::SSL_CIPHER) -> SslCipher { - SslCipher(ptr) + SslCipher(SslCipherRef::from_ptr(ptr)) } #[inline] fn as_ptr(&self) -> *mut ffi::SSL_CIPHER { - self.0 + self.0.as_ptr() } } @@ -2522,13 +2483,7 @@ impl Deref for SslCipher { type Target = SslCipherRef; fn deref(&self) -> &SslCipherRef { - unsafe { SslCipherRef::from_ptr(self.0) } - } -} - -impl DerefMut for SslCipher { - fn deref_mut(&mut self) -> &mut SslCipherRef { - unsafe { SslCipherRef::from_ptr_mut(self.0) } + self.0 } } @@ -2537,11 +2492,21 @@ impl DerefMut for SslCipher { /// [`SslCipher`]: struct.SslCipher.html pub struct SslCipherRef(Opaque); +unsafe impl Send for SslCipherRef {} +unsafe impl Sync for SslCipherRef {} + unsafe impl ForeignTypeRef for SslCipherRef { type CType = ffi::SSL_CIPHER; } impl SslCipherRef { + /// Returns the IANA number of the cipher. + #[corresponds(SSL_CIPHER_get_protocol_id)] + #[must_use] + pub fn protocol_id(&self) -> u16 { + unsafe { ffi::SSL_CIPHER_get_protocol_id(self.as_ptr()) } + } + /// Returns the name of the cipher. #[corresponds(SSL_CIPHER_get_name)] #[must_use] @@ -2572,7 +2537,7 @@ impl SslCipherRef { pub fn version(&self) -> &'static str { let version = unsafe { let ptr = ffi::SSL_CIPHER_get_version(self.as_ptr()); - CStr::from_ptr(ptr as *const _) + CStr::from_ptr(ptr) }; version.to_str().unwrap() @@ -2601,7 +2566,7 @@ impl SslCipherRef { // SSL_CIPHER_description requires a buffer of at least 128 bytes. let mut buf = [0; 128]; let ptr = ffi::SSL_CIPHER_description(self.as_ptr(), buf.as_mut_ptr(), 128); - CStr::from_ptr(ptr.cast()).to_string_lossy().into_owned() + CStr::from_ptr(ptr).to_string_lossy().into_owned() } } @@ -2789,32 +2754,13 @@ impl Ssl { } } - /// Creates a new `Ssl`. - /// - // FIXME should take &SslContextRef - #[corresponds(SSL_new)] - pub fn new(ctx: &SslContext) -> Result { - unsafe { - let ptr = cvt_p(ffi::SSL_new(ctx.as_ptr()))?; - let mut ssl = Ssl::from_ptr(ptr); - ssl.set_ex_data(*SESSION_CTX_INDEX, ctx.clone()); - - Ok(ssl) - } - } - /// Creates a new [`Ssl`]. - /// - /// This function does the same as [`Self:new`] except that it takes &[SslContextRef]. - // Both functions exist for backward compatibility (no breaking API). #[corresponds(SSL_new)] - pub fn new_from_ref(ctx: &SslContextRef) -> Result { + pub fn new(ctx: &SslContextRef) -> Result { unsafe { let ptr = cvt_p(ffi::SSL_new(ctx.as_ptr()))?; let mut ssl = Ssl::from_ptr(ptr); - SSL_CTX_up_ref(ctx.as_ptr()); - let ctx_owned = SslContext::from_ptr(ctx.as_ptr()); - ssl.set_ex_data(*SESSION_CTX_INDEX, ctx_owned); + ssl.set_ex_data(*SESSION_CTX_INDEX, ctx.to_owned()); Ok(ssl) } @@ -2869,20 +2815,20 @@ impl Ssl { where S: Read + Write, { - #[cfg(feature = "rpk")] - { - let ctx = self.ssl_context(); - - if ctx.is_rpk() { - unsafe { - ffi::SSL_CTX_set_custom_verify( - ctx.as_ptr(), - SslVerifyMode::PEER.bits(), - Some(rpk_verify_failure_callback), - ); - } - } - } + // #[cfg(feature = "rpk")] + // { + // let ctx = self.ssl_context(); + + // if !ctx.has_x509_support() { + // unsafe { + // ffi::SSL_CTX_set_custom_verify( + // ctx.as_ptr(), + // SslVerifyMode::PEER.bits(), + // Some(rpk_verify_failure_callback), + // ); + // } + // } + // } SslStreamBuilder::new(self, stream).setup_accept() } @@ -2912,8 +2858,7 @@ impl fmt::Debug for SslRef { builder.field("state", &self.state_string_long()); - #[cfg(feature = "rpk")] - if !self.ssl_context().is_rpk() { + if self.ssl_context().has_x509_support() { builder.field("verify_result", &self.verify_result()); } @@ -2951,76 +2896,34 @@ impl SslRef { #[corresponds(SSL_set1_curves_list)] pub fn set_curves_list(&mut self, curves: &str) -> Result<(), ErrorStack> { let curves = CString::new(curves).map_err(ErrorStack::internal_error)?; - unsafe { - cvt_0i(ffi::SSL_set1_curves_list( - self.as_ptr(), - curves.as_ptr() as *const _, - )) - .map(|_| ()) - } - } - - /// Sets the ongoing session's supported groups by their named identifiers - /// (formerly referred to as curves). - #[corresponds(SSL_set1_groups)] - #[deprecated(note = "SslCurveNid will be removed in the next release. Use set_curves_list")] - #[allow(deprecated)] - pub fn set_group_nids(&mut self, group_nids: &[SslCurveNid]) -> Result<(), ErrorStack> { - unsafe { - cvt_0i(ffi::SSL_set1_curves( - self.as_ptr(), - group_nids.as_ptr() as *const _, - group_nids.len(), - )) - .map(|_| ()) - } - } - - #[cfg(feature = "kx-safe-default")] - fn client_set_default_curves_list(&mut self) { - let curves = if cfg!(feature = "kx-client-pq-preferred") { - if cfg!(feature = "kx-client-nist-required") { - "P256Kyber768Draft00:P-256:P-384:P-521" - } else { - "X25519MLKEM768:X25519Kyber768Draft00:X25519:P256Kyber768Draft00:P-256:P-384:P-521" - } - } else if cfg!(feature = "kx-client-pq-supported") { - if cfg!(feature = "kx-client-nist-required") { - "P-256:P-384:P-521:P256Kyber768Draft00" - } else { - "X25519:P-256:P-384:P-521:X25519MLKEM768:X25519Kyber768Draft00:P256Kyber768Draft00" - } - } else { - if cfg!(feature = "kx-client-nist-required") { - "P-256:P-384:P-521" - } else { - "X25519:P-256:P-384:P-521" - } - }; - - self.set_curves_list(curves) - .expect("invalid default client curves list"); + unsafe { cvt_0i(ffi::SSL_set1_curves_list(self.as_ptr(), curves.as_ptr())).map(|_| ()) } } - #[cfg(feature = "kx-safe-default")] - fn server_set_default_curves_list(&mut self) { - self.set_curves_list( - "X25519MLKEM768:X25519Kyber768Draft00:P256Kyber768Draft00:X25519:P-256:P-384", - ) - .expect("invalid default server curves list"); - } - - /// Returns the [`SslCurve`] used for this `SslRef`. + /// Returns the curve ID (aka group ID) used for this `SslRef`. #[corresponds(SSL_get_curve_id)] #[must_use] - #[deprecated(note = "SslCurve will be removed in the next release")] - #[allow(deprecated)] - pub fn curve(&self) -> Option { + pub fn curve(&self) -> Option { let curve_id = unsafe { ffi::SSL_get_curve_id(self.as_ptr()) }; if curve_id == 0 { return None; } - Some(SslCurve(curve_id.into())) + Some(curve_id) + } + + /// Returns the curve name used for this `SslRef`. + #[corresponds(SSL_get_curve_name)] + #[must_use] + pub fn curve_name(&self) -> Option<&'static str> { + let curve_id = self.curve()?; + + unsafe { + let ptr = ffi::SSL_get_curve_name(curve_id); + if ptr.is_null() { + return None; + } + + CStr::from_ptr(ptr).to_str().ok() + } } /// Returns an `ErrorCode` value for the most recent operation on this `SslRef`. @@ -3035,13 +2938,9 @@ impl SslRef { /// [`SslContextBuilder::set_verify`]: struct.SslContextBuilder.html#method.set_verify #[corresponds(SSL_set_verify)] pub fn set_verify(&mut self, mode: SslVerifyMode) { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); - unsafe { ffi::SSL_set_verify(self.as_ptr(), mode.bits() as c_int, None) } + unsafe { ffi::SSL_set_verify(self.as_ptr(), c_int::from(mode.bits()), None) } } /// Sets the certificate verification depth. @@ -3049,11 +2948,7 @@ impl SslRef { /// If the peer's certificate chain is longer than this value, verification will fail. #[corresponds(SSL_set_verify_depth)] pub fn set_verify_depth(&mut self, depth: u32) { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { ffi::SSL_set_verify_depth(self.as_ptr(), depth as c_int); @@ -3064,11 +2959,7 @@ impl SslRef { #[corresponds(SSL_get_verify_mode)] #[must_use] pub fn verify_mode(&self) -> SslVerifyMode { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); let mode = unsafe { ffi::SSL_get_verify_mode(self.as_ptr()) }; SslVerifyMode::from_bits(mode).expect("SSL_get_verify_mode returned invalid mode") @@ -3095,18 +2986,14 @@ impl SslRef { where F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { // this needs to be in an Arc since the callback can register a new callback! self.replace_ex_data(Ssl::cached_ex_index(), Arc::new(callback)); ffi::SSL_set_verify( self.as_ptr(), - mode.bits() as c_int, + c_int::from(mode.bits()), Some(ssl_raw_verify::), ); } @@ -3115,15 +3002,13 @@ impl SslRef { /// Sets a custom certificate store for verifying peer certificates. #[corresponds(SSL_set0_verify_cert_store)] pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { - cvt(ffi::SSL_set0_verify_cert_store(self.as_ptr(), cert_store.into_ptr()) as c_int)?; - Ok(()) + cvt(ffi::SSL_set0_verify_cert_store( + self.as_ptr(), + cert_store.into_ptr(), + )) } } @@ -3137,18 +3022,14 @@ impl SslRef { where F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { // this needs to be in an Arc since the callback can register a new callback! self.replace_ex_data(Ssl::cached_ex_index(), Arc::new(callback)); ffi::SSL_set_custom_verify( self.as_ptr(), - mode.bits() as c_int, + c_int::from(mode.bits()), Some(ssl_raw_custom_verify::), ); } @@ -3159,7 +3040,7 @@ impl SslRef { /// [`SslContextBuilder::set_tmp_dh`]: struct.SslContextBuilder.html#method.set_tmp_dh #[corresponds(SSL_set_tmp_dh)] pub fn set_tmp_dh(&mut self, dh: &DhRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_set_tmp_dh(self.as_ptr(), dh.as_ptr()) as c_int).map(|_| ()) } + unsafe { cvt(ffi::SSL_set_tmp_dh(self.as_ptr(), dh.as_ptr())) } } /// Like [`SslContextBuilder::set_tmp_ecdh`]. @@ -3167,16 +3048,11 @@ impl SslRef { /// [`SslContextBuilder::set_tmp_ecdh`]: struct.SslContextBuilder.html#method.set_tmp_ecdh #[corresponds(SSL_set_tmp_ecdh)] pub fn set_tmp_ecdh(&mut self, key: &EcKeyRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_set_tmp_ecdh(self.as_ptr(), key.as_ptr()) as c_int).map(|_| ()) } + unsafe { cvt(ffi::SSL_set_tmp_ecdh(self.as_ptr(), key.as_ptr())) } } /// Configures whether ClientHello extensions should be permuted. #[corresponds(SSL_set_permute_extensions)] - /// - /// Note: This is gated to non-fips because the fips feature builds with a separate - /// version of BoringSSL which doesn't yet include these APIs. - /// Once the submoduled fips commit is upgraded, these gates can be removed. - #[cfg(not(feature = "fips-compat"))] pub fn set_permute_extensions(&mut self, enabled: bool) { unsafe { ffi::SSL_set_permute_extensions(self.as_ptr(), enabled as _) } } @@ -3187,14 +3063,10 @@ impl SslRef { #[corresponds(SSL_set_alpn_protos)] pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> { unsafe { - #[cfg_attr(not(feature = "fips-compat"), allow(clippy::unnecessary_cast))] - { - assert!(protocols.len() <= ProtosLen::MAX as usize); - } let r = ffi::SSL_set_alpn_protos( self.as_ptr(), protocols.as_ptr(), - protocols.len() as ProtosLen, + try_int(protocols.len())?, ); // fun fact, SSL_set_alpn_protos has a reversed return code D: if r == 0 { @@ -3225,7 +3097,7 @@ impl SslRef { if ptr.is_null() { None } else { - Some(SslCipherRef::from_ptr(ptr as *mut _)) + Some(SslCipherRef::from_ptr(ptr.cast_mut())) } } } @@ -3238,7 +3110,7 @@ impl SslRef { pub fn state_string(&self) -> &'static str { let state = unsafe { let ptr = ffi::SSL_state_string(self.as_ptr()); - CStr::from_ptr(ptr as *const _) + CStr::from_ptr(ptr) }; state.to_str().unwrap_or_default() @@ -3252,7 +3124,7 @@ impl SslRef { pub fn state_string_long(&self) -> &'static str { let state = unsafe { let ptr = ffi::SSL_state_string_long(self.as_ptr()); - CStr::from_ptr(ptr as *const _) + CStr::from_ptr(ptr) }; state.to_str().unwrap_or_default() @@ -3264,21 +3136,14 @@ impl SslRef { #[corresponds(SSL_set_tlsext_host_name)] pub fn set_hostname(&mut self, hostname: &str) -> Result<(), ErrorStack> { let cstr = CString::new(hostname).map_err(ErrorStack::internal_error)?; - unsafe { - cvt(ffi::SSL_set_tlsext_host_name(self.as_ptr(), cstr.as_ptr() as *mut _) as c_int) - .map(|_| ()) - } + unsafe { cvt(ffi::SSL_set_tlsext_host_name(self.as_ptr(), cstr.as_ptr())) } } /// Returns the peer's certificate, if present. #[corresponds(SSL_get_peer_certificate)] #[must_use] pub fn peer_certificate(&self) -> Option { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { let ptr = ffi::SSL_get_peer_certificate(self.as_ptr()); @@ -3294,14 +3159,10 @@ impl SslRef { /// /// On the client side, the chain includes the leaf certificate, but on the server side it does /// not. Fun! - #[corresponds(SSL_get_peer_certificate)] + #[corresponds(SSL_get_peer_cert_chain)] #[must_use] pub fn peer_cert_chain(&self) -> Option<&StackRef> { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { let ptr = ffi::SSL_get_peer_cert_chain(self.as_ptr()); @@ -3317,11 +3178,7 @@ impl SslRef { #[corresponds(SSL_get_certificate)] #[must_use] pub fn certificate(&self) -> Option<&X509Ref> { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { let ptr = ffi::SSL_get_certificate(self.as_ptr()); @@ -3374,7 +3231,7 @@ impl SslRef { pub fn version_str(&self) -> &'static str { let version = unsafe { let ptr = ffi::SSL_get_version(self.as_ptr()); - CStr::from_ptr(ptr as *const _) + CStr::from_ptr(ptr) }; version.to_str().unwrap() @@ -3391,7 +3248,6 @@ impl SslRef { self.as_ptr(), version.map_or(0, |v| v.0 as _), )) - .map(|_| ()) } } @@ -3405,7 +3261,6 @@ impl SslRef { self.as_ptr(), version.map_or(0, |v| v.0 as _), )) - .map(|_| ()) } } @@ -3484,7 +3339,7 @@ impl SslRef { if chain.is_null() { None } else { - Some(StackRef::from_ptr(chain as *mut _)) + Some(StackRef::from_ptr(chain.cast_mut())) } } } @@ -3501,7 +3356,7 @@ impl SslRef { if profile.is_null() { None } else { - Some(SrtpProtectionProfileRef::from_ptr(profile as *mut _)) + Some(SrtpProtectionProfileRef::from_ptr(profile.cast_mut())) } } } @@ -3550,7 +3405,7 @@ impl SslRef { if name.is_null() { None } else { - Some(CStr::from_ptr(name as *const _).to_bytes()) + Some(CStr::from_ptr(name).to_bytes()) } } } @@ -3560,6 +3415,12 @@ impl SslRef { /// It is most commonly used in the Server Name Indication (SNI) callback. #[corresponds(SSL_set_SSL_CTX)] pub fn set_ssl_context(&mut self, ctx: &SslContextRef) -> Result<(), ErrorStack> { + assert_eq!( + self.ssl_context().has_x509_support(), + ctx.has_x509_support(), + "X.509 certificate support in old and new contexts doesn't match", + ); + unsafe { cvt_p(ffi::SSL_set_SSL_CTX(self.as_ptr(), ctx.as_ptr())).map(|_| ()) } } @@ -3576,11 +3437,7 @@ impl SslRef { /// Returns a mutable reference to the X509 verification configuration. #[corresponds(SSL_get0_param)] pub fn verify_param_mut(&mut self) -> &mut X509VerifyParamRef { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { X509VerifyParamRef::from_ptr_mut(ffi::SSL_get0_param(self.as_ptr())) } } @@ -3593,11 +3450,7 @@ impl SslRef { /// Returns the certificate verification result. #[corresponds(SSL_get_verify_result)] pub fn verify_result(&self) -> X509VerifyResult { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { X509VerifyError::from_raw(ffi::SSL_get_verify_result(self.as_ptr()) as c_int) } } @@ -3622,9 +3475,7 @@ impl SslRef { /// value. #[corresponds(SSL_get_client_random)] pub fn client_random(&self, buf: &mut [u8]) -> usize { - unsafe { - ffi::SSL_get_client_random(self.as_ptr(), buf.as_mut_ptr() as *mut c_uchar, buf.len()) - } + unsafe { ffi::SSL_get_client_random(self.as_ptr(), buf.as_mut_ptr(), buf.len()) } } /// Copies the server_random value sent by the server in the TLS handshake into a buffer. @@ -3633,9 +3484,7 @@ impl SslRef { /// value. #[corresponds(SSL_get_server_random)] pub fn server_random(&self, buf: &mut [u8]) -> usize { - unsafe { - ffi::SSL_get_server_random(self.as_ptr(), buf.as_mut_ptr() as *mut c_uchar, buf.len()) - } + unsafe { ffi::SSL_get_server_random(self.as_ptr(), buf.as_mut_ptr(), buf.len()) } } /// Derives keying material for application use in accordance to RFC 5705. @@ -3648,20 +3497,19 @@ impl SslRef { ) -> Result<(), ErrorStack> { unsafe { let (context, contextlen, use_context) = match context { - Some(context) => (context.as_ptr() as *const c_uchar, context.len(), 1), + Some(context) => (context.as_ptr(), context.len(), 1), None => (ptr::null(), 0, 0), }; cvt(ffi::SSL_export_keying_material( self.as_ptr(), - out.as_mut_ptr() as *mut c_uchar, + out.as_mut_ptr(), out.len(), - label.as_ptr() as *const c_char, + label.as_ptr().cast::(), label.len(), context, contextlen, use_context, )) - .map(|_| ()) } } @@ -3677,7 +3525,7 @@ impl SslRef { /// with the same `SslContext` as this `Ssl`. #[corresponds(SSL_set_session)] pub unsafe fn set_session(&mut self, session: &SslSessionRef) -> Result<(), ErrorStack> { - cvt(ffi::SSL_set_session(self.as_ptr(), session.as_ptr())).map(|_| ()) + cvt(ffi::SSL_set_session(self.as_ptr(), session.as_ptr())) } /// Determines if the session provided to `set_session` was successfully reused. @@ -3691,7 +3539,10 @@ impl SslRef { #[corresponds(SSL_set_tlsext_status_type)] pub fn set_status_type(&mut self, type_: StatusType) -> Result<(), ErrorStack> { unsafe { - cvt(ffi::SSL_set_tlsext_status_type(self.as_ptr(), type_.as_raw()) as c_int).map(|_| ()) + cvt(ffi::SSL_set_tlsext_status_type( + self.as_ptr(), + type_.as_raw(), + )) } } @@ -3712,18 +3563,15 @@ impl SslRef { } /// Sets the OCSP response to be returned to the client. - #[corresponds(SSL_set_tlsext_status_ocsp_resp)] + #[corresponds(SSL_set_ocsp_response)] pub fn set_ocsp_status(&mut self, response: &[u8]) -> Result<(), ErrorStack> { unsafe { assert!(response.len() <= c_int::MAX as usize); - let p = cvt_p(ffi::OPENSSL_malloc(response.len() as _))?; - ptr::copy_nonoverlapping(response.as_ptr(), p as *mut u8, response.len()); - cvt(ffi::SSL_set_tlsext_status_ocsp_resp( + cvt(ffi::SSL_set_ocsp_response( self.as_ptr(), - p as *mut c_uchar, + response.as_ptr(), response.len(), - ) as c_int) - .map(|_| ()) + )) } } @@ -3745,17 +3593,12 @@ impl SslRef { pub fn set_ex_data(&mut self, index: Index, data: T) { if let Some(old) = self.ex_data_mut(index) { *old = data; - return; } unsafe { - let data = Box::new(data); - ffi::SSL_set_ex_data( - self.as_ptr(), - index.as_raw(), - Box::into_raw(data) as *mut c_void, - ); + let data = Box::into_raw(Box::new(data)); + ffi::SSL_set_ex_data(self.as_ptr(), index.as_raw(), data.cast()); } } @@ -3794,12 +3637,9 @@ impl SslRef { #[corresponds(SSL_get_ex_data)] pub fn ex_data_mut(&mut self, index: Index) -> Option<&mut T> { unsafe { - let data = ffi::SSL_get_ex_data(self.as_ptr(), index.as_raw()); - if data.is_null() { - None - } else { - Some(&mut *(data as *mut T)) - } + ffi::SSL_get_ex_data(self.as_ptr(), index.as_raw()) + .cast::() + .as_mut() } } @@ -3809,7 +3649,7 @@ impl SslRef { /// buffer required. #[corresponds(SSL_get_finished)] pub fn finished(&self, buf: &mut [u8]) -> usize { - unsafe { ffi::SSL_get_finished(self.as_ptr(), buf.as_mut_ptr() as *mut c_void, buf.len()) } + unsafe { ffi::SSL_get_finished(self.as_ptr(), buf.as_mut_ptr().cast(), buf.len()) } } /// Copies the contents of the last Finished message received from the peer into the provided @@ -3819,9 +3659,7 @@ impl SslRef { /// buffer required. #[corresponds(SSL_get_peer_finished)] pub fn peer_finished(&self, buf: &mut [u8]) -> usize { - unsafe { - ffi::SSL_get_peer_finished(self.as_ptr(), buf.as_mut_ptr() as *mut c_void, buf.len()) - } + unsafe { ffi::SSL_get_peer_finished(self.as_ptr(), buf.as_mut_ptr().cast(), buf.len()) } } /// Determines if the initial handshake has been completed. @@ -3834,7 +3672,7 @@ impl SslRef { /// Sets the MTU used for DTLS connections. #[corresponds(SSL_set_mtu)] pub fn set_mtu(&mut self, mtu: u32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_set_mtu(self.as_ptr(), mtu as c_uint) as c_int).map(|_| ()) } + unsafe { cvt(ffi::SSL_set_mtu(self.as_ptr(), mtu as c_uint)) } } /// Sets the certificate. @@ -3853,11 +3691,7 @@ impl SslRef { /// as trusted by this method. #[corresponds(SSL_set_client_CA_list)] pub fn set_client_ca_list(&mut self, list: Stack) { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { ffi::SSL_set_client_CA_list(self.as_ptr(), list.as_ptr()) } mem::forget(list); @@ -3869,7 +3703,7 @@ impl SslRef { where T: HasPrivate, { - unsafe { cvt(ffi::SSL_use_PrivateKey(self.as_ptr(), key.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_use_PrivateKey(self.as_ptr(), key.as_ptr())) } } /// Enables all modes set in `mode` in `SSL`. Returns a bitmask representing the resulting @@ -3891,7 +3725,7 @@ impl SslRef { /// Appends `cert` to the chain associated with the current certificate of `SSL`. #[corresponds(SSL_add1_chain_cert)] pub fn add_chain_cert(&mut self, cert: &X509Ref) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_add1_chain_cert(self.as_ptr(), cert.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_add1_chain_cert(self.as_ptr(), cert.as_ptr())) } } /// Configures `ech_config_list` on `SSL` for offering ECH during handshakes. If the server @@ -3901,7 +3735,6 @@ impl SslRef { /// Clients should use `get_ech_name_override` to verify the server certificate in case of ECH /// rejection, and follow up with `get_ech_retry_configs` to retry the connection with a fresh /// set of ECHConfigs. If the retry also fails, clients should report a connection failure. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_set1_ech_config_list)] pub fn set_ech_config_list(&mut self, ech_config_list: &[u8]) -> Result<(), ErrorStack> { unsafe { @@ -3920,7 +3753,6 @@ impl SslRef { /// Clients should call this function when handling an `SSL_R_ECH_REJECTED` error code to /// recover from potential key mismatches. If the result is `Some`, the client should retry the /// connection using the returned `ECHConfigList`. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_get0_ech_retry_configs)] #[must_use] pub fn get_ech_retry_configs(&self) -> Option<&[u8]> { @@ -3943,7 +3775,6 @@ impl SslRef { /// Clients should call this function during the certificate verification callback to /// ensure the server's certificate is valid for the public name, which is required to /// authenticate retry configs. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_get0_ech_name_override)] #[must_use] pub fn get_ech_name_override(&self) -> Option<&[u8]> { @@ -3955,13 +3786,12 @@ impl SslRef { if data.is_null() { None } else { - Some(slice::from_raw_parts(data as *const u8, len)) + Some(slice::from_raw_parts(data.cast::(), len)) } } } // Whether or not `SSL` negotiated ECH. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_ech_accepted)] #[must_use] pub fn ech_accepted(&self) -> bool { @@ -3969,7 +3799,6 @@ impl SslRef { } // Whether or not to enable ECH grease on `SSL`. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_set_enable_ech_grease)] pub fn set_enable_ech_grease(&self, enable: bool) { let enable = if enable { 1 } else { 0 }; @@ -3980,11 +3809,79 @@ impl SslRef { } /// Sets the compliance policy on `SSL`. - #[cfg(not(feature = "fips-compat"))] #[corresponds(SSL_set_compliance_policy)] pub fn set_compliance_policy(&mut self, policy: CompliancePolicy) -> Result<(), ErrorStack> { unsafe { cvt_0i(ffi::SSL_set_compliance_policy(self.as_ptr(), policy.0)).map(|_| ()) } } + + /// Adds a credential. + #[corresponds(SSL_add1_credential)] + pub fn add_credential(&mut self, credential: &SslCredentialRef) -> Result<(), ErrorStack> { + unsafe { cvt_0i(ffi::SSL_add1_credential(self.as_ptr(), credential.as_ptr())).map(|_| ()) } + } + + /// Returns the public key sent by the other peer, `None` if there is no ongoing handshake. + #[corresponds(SSL_get0_peer_pubkey)] + #[cfg(feature = "rpk")] + pub fn peer_pubkey(&self) -> Option<&PKeyRef> { + unsafe { + let pubkey = ffi::SSL_get0_peer_pubkey(self.as_ptr()); + + if pubkey.is_null() { + return None; + } + + Some(PKeyRef::from_ptr(pubkey as *mut _)) + } + } + + /// Sets the list of server certificate types that clients attached to this `Ssl` + /// can process. + #[corresponds(SSL_set_server_certificate_types)] + #[cfg(feature = "rpk")] + pub fn set_server_certificate_types( + &mut self, + types: &[CertificateType], + ) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_set_server_certificate_types( + self.as_ptr(), + types.as_ptr() as *const u8, + types.len(), + )) + .map(|_| ()) + } + } + + /// Returns the list of server certificate types. + #[corresponds(SSL_get0_server_certificate_types)] + #[must_use] + #[cfg(feature = "rpk")] + pub fn server_certificate_types(&self) -> Option<&[CertificateType]> { + let mut types = ptr::null(); + let mut types_len = 0; + unsafe { + ffi::SSL_get0_server_certificate_types(self.as_ptr(), &mut types, &mut types_len); + + if types_len == 0 { + return None; + } + + Some(slice::from_raw_parts( + types as *const CertificateType, + types_len, + )) + } + } + + /// Returns the server certificate type selected by the server, or `CertificateType::X509` + /// if there is no handshake. + #[corresponds(SSL_get_server_certificate_type_selected)] + #[must_use] + #[cfg(feature = "rpk")] + pub fn selected_server_certificate_type(&self) -> CertificateType { + unsafe { CertificateType(ffi::SSL_get_server_certificate_type_selected(self.as_ptr())) } + } } /// An SSL stream midway through the handshake process. @@ -4088,26 +3985,23 @@ where } impl SslStream { - fn new_base(ssl: Ssl, stream: S) -> Self { - unsafe { - let (bio, method) = bio::new(stream).unwrap(); - ffi::SSL_set_bio(ssl.as_ptr(), bio, bio); - - SslStream { - ssl: ManuallyDrop::new(ssl), - method: ManuallyDrop::new(method), - _p: PhantomData, - } - } - } - /// Creates a new `SslStream`. /// /// This function performs no IO; the stream will not have performed any part of the handshake /// with the peer. The `connect` and `accept` methods can be used to /// explicitly perform the handshake. pub fn new(ssl: Ssl, stream: S) -> Result { - Ok(Self::new_base(ssl, stream)) + let (bio, method) = bio::new(stream)?; + + unsafe { + ffi::SSL_set_bio(ssl.as_ptr(), bio, bio); + } + + Ok(SslStream { + ssl: ManuallyDrop::new(ssl), + method: ManuallyDrop::new(method), + _p: PhantomData, + }) } /// Constructs an `SslStream` from a pointer to the underlying OpenSSL `SSL` struct. @@ -4119,7 +4013,7 @@ impl SslStream { /// The caller must ensure the pointer is valid. pub unsafe fn from_raw_parts(ssl: *mut ffi::SSL, stream: S) -> Self { let ssl = Ssl::from_ptr(ssl); - Self::new_base(ssl, stream) + Self::new(ssl, stream).unwrap() } /// Like `read`, but takes a possibly-uninitialized slice. @@ -4386,7 +4280,7 @@ where /// Begin creating an `SslStream` atop `stream` pub fn new(ssl: Ssl, stream: S) -> Self { Self { - inner: SslStream::new_base(ssl, stream), + inner: SslStream::new(ssl, stream).unwrap(), } } @@ -4411,9 +4305,6 @@ where pub fn setup_connect(mut self) -> MidHandshakeSslStream { self.set_connect_state(); - #[cfg(feature = "kx-safe-default")] - self.inner.ssl.client_set_default_curves_list(); - MidHandshakeSslStream { stream: self.inner, error: Error { @@ -4443,9 +4334,6 @@ where pub fn setup_accept(mut self) -> MidHandshakeSslStream { self.set_accept_state(); - #[cfg(feature = "kx-safe-default")] - self.inner.ssl.server_set_default_curves_list(); - MidHandshakeSslStream { stream: self.inner, error: Error { @@ -4536,6 +4424,21 @@ impl SslStreamBuilder { } } +/// A certificate type. +#[cfg(feature = "rpk")] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct CertificateType(u8); + +#[cfg(feature = "rpk")] +impl CertificateType { + /// A X.509 certificate. + pub const X509: Self = Self(ffi::TLS_CERTIFICATE_TYPE_X509 as u8); + + /// A raw public key. + pub const RAW_PUBLIC_KEY: Self = Self(ffi::TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY as u8); +} + /// The result of a shutdown request. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ShutdownResult { @@ -4660,26 +4563,10 @@ pub trait CertificateCompressor: Send + Sync + 'static { use crate::ffi::{SSL_CTX_up_ref, SSL_SESSION_get_master_key, SSL_SESSION_up_ref, SSL_is_server}; -use crate::ffi::{DTLS_method, TLS_client_method, TLS_method, TLS_server_method}; - -use std::sync::Once; - unsafe fn get_new_idx(f: ffi::CRYPTO_EX_free) -> c_int { - // hack around https://rt.openssl.org/Ticket/Display.html?id=3710&user=guest&pass=guest - static ONCE: Once = Once::new(); - ONCE.call_once(|| { - ffi::SSL_CTX_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, None); - }); - ffi::SSL_CTX_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) } unsafe fn get_new_ssl_idx(f: ffi::CRYPTO_EX_free) -> c_int { - // hack around https://rt.openssl.org/Ticket/Display.html?id=3710&user=guest&pass=guest - static ONCE: Once = Once::new(); - ONCE.call_once(|| { - ffi::SSL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, None); - }); - ffi::SSL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) } diff --git a/boring/src/ssl/test/cert_compressor.rs b/boring/src/ssl/test/cert_compressor.rs index d62ffa879..78124f19a 100644 --- a/boring/src/ssl/test/cert_compressor.rs +++ b/boring/src/ssl/test/cert_compressor.rs @@ -54,7 +54,7 @@ fn server_only_cert_compression() { let mut store = X509StoreBuilder::new().unwrap(); let x509 = X509::from_pem(super::ROOT_CERT).unwrap(); - store.add_cert(x509).unwrap(); + store.add_cert(&x509).unwrap(); let client = server.client(); @@ -67,7 +67,7 @@ fn client_only_cert_compression() { let mut store = X509StoreBuilder::new().unwrap(); let x509 = X509::from_pem(super::ROOT_CERT).unwrap(); - store.add_cert(x509).unwrap(); + store.add_cert(&x509).unwrap(); let mut client = server_builder.client(); client @@ -90,7 +90,7 @@ fn client_and_server_cert_compression() { let mut store = X509StoreBuilder::new().unwrap(); let x509 = X509::from_pem(super::ROOT_CERT).unwrap(); - store.add_cert(x509).unwrap(); + store.add_cert(&x509).unwrap(); let mut client = server.client(); client diff --git a/boring/src/ssl/test/ech.rs b/boring/src/ssl/test/ech.rs index d2797d427..db4d43149 100644 --- a/boring/src/ssl/test/ech.rs +++ b/boring/src/ssl/test/ech.rs @@ -40,7 +40,7 @@ fn ech() { let (_server, client) = bootstrap_ech(ECH_CONFIG, ECH_KEY, ECH_CONFIG_LIST); let ssl_stream = client.connect(); - assert!(ssl_stream.ssl().ech_accepted()) + assert!(ssl_stream.ssl().ech_accepted()); } #[test] @@ -57,7 +57,7 @@ fn ech_rejection() { Some(b"ech.com".to_vec().as_ref()) ); assert!(failed_ssl_stream.ssl().get_ech_retry_configs().is_some()); - assert!(!failed_ssl_stream.ssl().ech_accepted()) + assert!(!failed_ssl_stream.ssl().ech_accepted()); } #[test] @@ -69,5 +69,5 @@ fn ech_grease() { client.ssl().set_enable_ech_grease(true); let ssl_stream = client.connect(); - assert!(!ssl_stream.ssl().ech_accepted()) + assert!(!ssl_stream.ssl().ech_accepted()); } diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index 1d698ad4e..9317a7bae 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -1,4 +1,3 @@ -#![allow(deprecated)] // SslCurve use foreign_types::{ForeignType, ForeignTypeRef}; use std::io; use std::io::prelude::*; @@ -13,27 +12,25 @@ use crate::hash::MessageDigest; use crate::pkey::PKey; use crate::srtp::SrtpProfileId; use crate::ssl::test::server::Server; -use crate::ssl::SslVersion; -use crate::ssl::{self, SslCurve}; use crate::ssl::{ - ExtensionType, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslAcceptorBuilder, + self, ExtensionType, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslAcceptorBuilder, SslConnector, SslContext, SslFiletype, SslMethod, SslOptions, SslStream, SslVerifyMode, }; +use crate::ssl::{HandshakeError, SslVersion}; use crate::x509::store::X509StoreBuilder; use crate::x509::verify::X509CheckFlags; use crate::x509::{X509Name, X509}; -#[cfg(not(feature = "fips"))] use super::CompliancePolicy; mod cert_compressor; mod cert_verify; mod custom_verify; -#[cfg(not(feature = "fips"))] mod ech; mod private_key_method; mod server; mod session; +mod session_resumption; mod verify; static ROOT_CERT: &[u8] = include_bytes!("../../../test/root-ca.pem"); @@ -320,18 +317,18 @@ fn test_mutable_store() { let cert2 = X509::from_pem(cert2).unwrap(); let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); - ctx.cert_store_mut().add_cert(cert.clone()).unwrap(); + ctx.cert_store_mut().add_cert(&cert.clone()).unwrap(); assert_eq!(1, ctx.cert_store().objects_len()); ctx.set_cert_store_builder(X509StoreBuilder::new().unwrap()); assert_eq!(0, ctx.cert_store().objects_len()); - ctx.cert_store_mut().add_cert(cert.clone()).unwrap(); + ctx.cert_store_mut().add_cert(&cert.clone()).unwrap(); assert_eq!(1, ctx.cert_store().objects_len()); let mut new_store = X509StoreBuilder::new().unwrap(); - new_store.add_cert(cert).unwrap(); - new_store.add_cert(cert2).unwrap(); + new_store.add_cert(&cert).unwrap(); + new_store.add_cert(&cert2).unwrap(); let new_store = new_store.build(); assert_eq!(2, new_store.objects_len()); @@ -420,6 +417,68 @@ fn test_select_cert_alpn_extension() { ); } +#[test] +fn test_io_retry() { + #[derive(Debug)] + struct RetryStream { + inner: TcpStream, + first_read: bool, + first_write: bool, + first_flush: bool, + } + + impl Read for RetryStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if mem::replace(&mut self.first_read, false) { + Err(io::Error::new(io::ErrorKind::WouldBlock, "first read")) + } else { + self.inner.read(buf) + } + } + } + + impl Write for RetryStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + if mem::replace(&mut self.first_write, false) { + Err(io::Error::new(io::ErrorKind::WouldBlock, "first write")) + } else { + self.inner.write(buf) + } + } + + fn flush(&mut self) -> io::Result<()> { + if mem::replace(&mut self.first_flush, false) { + Err(io::Error::new(io::ErrorKind::WouldBlock, "first flush")) + } else { + self.inner.flush() + } + } + } + + let server = Server::builder().build(); + + let stream = RetryStream { + inner: server.connect_tcp(), + first_read: true, + first_write: true, + first_flush: true, + }; + + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let mut s = match Ssl::new(&ctx.build()).unwrap().connect(stream) { + Ok(mut s) => return s.read_exact(&mut [0]).unwrap(), + Err(HandshakeError::WouldBlock(s)) => s, + Err(_) => panic!("should not fail on setup"), + }; + loop { + match s.handshake() { + Ok(mut s) => return s.read_exact(&mut [0]).unwrap(), + Err(HandshakeError::WouldBlock(mid_s)) => s = mid_s, + Err(_) => panic!("should not fail on handshake"), + } + } +} + #[test] #[should_panic(expected = "blammo")] fn write_panic() { @@ -955,59 +1014,15 @@ fn sni_callback_swapped_ctx() { assert!(CALLED_BACK.load(Ordering::SeqCst)); } -#[cfg(feature = "kx-safe-default")] -#[test] -fn client_set_default_curves_list() { - let ssl_ctx = crate::ssl::SslContextBuilder::new(SslMethod::tls()) - .unwrap() - .build(); - let mut ssl = Ssl::new(&ssl_ctx).unwrap(); - - // Panics if Kyber768 missing in boringSSL. - ssl.client_set_default_curves_list(); -} - -#[cfg(feature = "kx-safe-default")] -#[test] -fn server_set_default_curves_list() { - let ssl_ctx = crate::ssl::SslContextBuilder::new(SslMethod::tls()) - .unwrap() - .build(); - let mut ssl = Ssl::new(&ssl_ctx).unwrap(); - - // Panics if Kyber768 missing in boringSSL. - ssl.server_set_default_curves_list(); -} - #[test] fn get_curve() { let server = Server::builder().build(); let client = server.client_with_root_ca(); let client_stream = client.connect(); - let curve = client_stream.ssl().curve().expect("curve"); - assert!(curve.name().is_some()); -} - -#[test] -fn get_curve_name() { - assert_eq!(SslCurve::SECP224R1.name(), Some("P-224")); - assert_eq!(SslCurve::SECP256R1.name(), Some("P-256")); - assert_eq!(SslCurve::SECP384R1.name(), Some("P-384")); - assert_eq!(SslCurve::SECP521R1.name(), Some("P-521")); - assert_eq!(SslCurve::X25519.name(), Some("X25519")); -} - -#[cfg(not(feature = "kx-safe-default"))] -#[test] -fn set_curves() { - let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); - ctx.set_curves(&[ - SslCurve::SECP224R1, - SslCurve::SECP256R1, - SslCurve::SECP384R1, - SslCurve::X25519, - ]) - .expect("Failed to set curves"); + let curve = client_stream.ssl().curve(); + assert!(curve.is_some()); + let curve_name = client_stream.ssl().curve_name(); + assert!(curve_name.is_some()); } #[test] @@ -1038,7 +1053,6 @@ fn test_get_ciphers() { } #[test] -#[cfg(not(feature = "fips"))] fn test_set_compliance() { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_compliance_policy(CompliancePolicy::FIPS_202205) @@ -1058,7 +1072,7 @@ fn test_set_compliance() { assert_eq!(ciphers.len(), FIPS_CIPHERS.len()); for cipher in ciphers.into_iter().zip(FIPS_CIPHERS) { - assert_eq!(cipher.0.name(), cipher.1) + assert_eq!(cipher.0.name(), cipher.1); } let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); @@ -1077,7 +1091,7 @@ fn test_set_compliance() { assert_eq!(ciphers.len(), WPA3_192_CIPHERS.len()); for cipher in ciphers.into_iter().zip(WPA3_192_CIPHERS) { - assert_eq!(cipher.0.name(), cipher.1) + assert_eq!(cipher.0.name(), cipher.1); } ctx.set_compliance_policy(CompliancePolicy::NONE) @@ -1119,7 +1133,6 @@ fn test_info_callback() { assert!(CALLED_BACK.load(Ordering::Relaxed)); } -#[cfg(not(feature = "fips-compat"))] #[test] fn test_ssl_set_compliance() { let ctx = SslContext::builder(SslMethod::tls()).unwrap().build(); @@ -1141,7 +1154,7 @@ fn test_ssl_set_compliance() { assert_eq!(ciphers.len(), FIPS_CIPHERS.len()); for cipher in ciphers.into_iter().zip(FIPS_CIPHERS) { - assert_eq!(cipher.0.name(), cipher.1) + assert_eq!(cipher.0.name(), cipher.1); } let ctx = SslContext::builder(SslMethod::tls()).unwrap().build(); @@ -1161,7 +1174,7 @@ fn test_ssl_set_compliance() { assert_eq!(ciphers.len(), WPA3_192_CIPHERS.len()); for cipher in ciphers.into_iter().zip(WPA3_192_CIPHERS) { - assert_eq!(cipher.0.name(), cipher.1) + assert_eq!(cipher.0.name(), cipher.1); } ssl.set_compliance_policy(CompliancePolicy::NONE) @@ -1169,7 +1182,6 @@ fn test_ssl_set_compliance() { } #[test] -#[allow(deprecated)] fn ex_data_drop() { use crate::ssl::SslContextBuilder; use std::sync::atomic::AtomicU32; @@ -1208,6 +1220,6 @@ fn ex_data_drop() { ctx2.set_ex_data(index, TrackDrop(d1.clone())); ctx2.set_ex_data(index, TrackDrop(d2.clone())); drop(ctx2); - assert_eq!(101, d1.load(Relaxed), "set_ex_data has a leak"); + assert_eq!(102, d1.load(Relaxed)); assert_eq!(202, d2.load(Relaxed)); } diff --git a/boring/src/ssl/test/session.rs b/boring/src/ssl/test/session.rs index 23c0f4d5d..97ed8c1c3 100644 --- a/boring/src/ssl/test/session.rs +++ b/boring/src/ssl/test/session.rs @@ -49,7 +49,7 @@ fn new_get_session_callback() { .ctx() .set_session_cache_mode(SslSessionCacheMode::SERVER | SslSessionCacheMode::NO_INTERNAL); server.ctx().set_new_session_callback(|_, session| { - SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap() + SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap(); }); unsafe { server.ctx().set_get_session_callback(|_, id| { @@ -76,7 +76,7 @@ fn new_get_session_callback() { .ctx() .set_session_cache_mode(SslSessionCacheMode::CLIENT); client.ctx().set_new_session_callback(|_, session| { - CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap() + CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap(); }); let client = client.build(); diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs new file mode 100644 index 000000000..5c65c53ce --- /dev/null +++ b/boring/src/ssl/test/session_resumption.rs @@ -0,0 +1,242 @@ +use super::server::Server; +use crate::ssl::test::MessageDigest; +use crate::ssl::HmacCtxRef; +use crate::ssl::SslRef; +use crate::ssl::SslSession; +use crate::ssl::SslSessionCacheMode; +use crate::ssl::TicketKeyCallbackResult; +use crate::symm::Cipher; +use crate::symm::CipherCtxRef; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::OnceLock; + +static SUCCESS_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); +static SUCCESS_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); +static NOOP_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); +static NOOP_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); + +#[test] +fn resume_session() { + static SESSION_TICKET: OnceLock = OnceLock::new(); + static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); + + let mut server = Server::builder(); + server.expected_connections_count(2); + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .set_session_cache_mode(SslSessionCacheMode::CLIENT); + client.ctx().set_new_session_callback(|_, session| { + NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); + // The server sends multiple session tickets but we only care to retrieve one. + let _ = SESSION_TICKET.set(session); + }); + let ssl_stream = client.connect(); + + assert!(!ssl_stream.ssl().session_reused()); + assert!(SESSION_TICKET.get().is_some()); + assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); + + // Retrieve the session ticket + let session_ticket = SESSION_TICKET.get().unwrap(); + + // Attempt to resume the connection using the session ticket + let client_2 = server.client(); + let mut ssl_builder = client_2.build().builder(); + unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() }; + let ssl_stream_2 = ssl_builder.connect(); + + assert!(ssl_stream_2.ssl().session_reused()); +} + +#[test] +fn custom_callback_success() { + static SESSION_TICKET: OnceLock = OnceLock::new(); + static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); + + let mut server = Server::builder(); + server.expected_connections_count(2); + unsafe { + server + .ctx() + .set_ticket_key_callback(test_success_tickey_key_callback); + }; + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .set_session_cache_mode(SslSessionCacheMode::CLIENT); + client.ctx().set_new_session_callback(|_, session| { + NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); + // The server sends multiple session tickets but we only care to retrieve one. + let _ = SESSION_TICKET.set(session); + }); + let ssl_stream = client.connect(); + + assert!(!ssl_stream.ssl().session_reused()); + assert!(SESSION_TICKET.get().is_some()); + assert_eq!(SUCCESS_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2); + assert_eq!(SUCCESS_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0); + assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); + + // Retrieve the session ticket + let session_ticket = SESSION_TICKET.get().unwrap(); + + // Attempt to resume the connection using the session ticket + let client_2 = server.client(); + let mut ssl_builder = client_2.build().builder(); + unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() }; + let ssl_stream_2 = ssl_builder.connect(); + + assert!(ssl_stream_2.ssl().session_reused()); + assert_eq!(SUCCESS_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4); + assert_eq!(SUCCESS_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1); +} + +#[test] +fn custom_callback_unrecognized_decryption_ticket() { + static SESSION_TICKET: OnceLock = OnceLock::new(); + static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); + + let mut server = Server::builder(); + server.expected_connections_count(2); + unsafe { + server + .ctx() + .set_ticket_key_callback(test_noop_tickey_key_callback); + }; + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .set_session_cache_mode(SslSessionCacheMode::CLIENT); + client.ctx().set_new_session_callback(|_, session| { + NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); + // The server sends multiple session tickets but we only care to retrieve one. + let _ = SESSION_TICKET.set(session); + }); + let ssl_stream = client.connect(); + + assert!(!ssl_stream.ssl().session_reused()); + assert!(SESSION_TICKET.get().is_some()); + assert_eq!(NOOP_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2); + assert_eq!(NOOP_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0); + assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); + + // Retrieve the session ticket + let session_ticket = SESSION_TICKET.get().unwrap(); + + // Attempt to resume the connection using the session ticket + let client_2 = server.client(); + let mut ssl_builder = client_2.build().builder(); + unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() }; + let ssl_stream_2 = ssl_builder.connect(); + + // Second connection was NOT resumed due to TicketKeyCallbackResult::Noop on decryption + assert!(!ssl_stream_2.ssl().session_reused()); + assert_eq!(NOOP_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4); + assert_eq!(NOOP_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1); +} + +// Successfully return a session ticket in encryption mode but return a +// TicketKeyCallbackResult::Noop in decryption mode. +fn test_noop_tickey_key_callback( + _ssl: &SslRef, + key_name: &mut [u8; 16], + iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], + evp_ctx: &mut CipherCtxRef, + hmac_ctx: &mut HmacCtxRef, + encrypt: bool, +) -> TicketKeyCallbackResult { + // These should only be used for testing purposes. + const TEST_KEY_NAME: [u8; 16] = [5; 16]; + const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize]; + const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; + const TEST_HMAC_KEY: [u8; 32] = [3; 32]; + + let digest = MessageDigest::sha256(); + let cipher = Cipher::aes_128_cbc(); + + if encrypt { + NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + + // Ensure key_name and iv are initialized and set test values. + assert_eq!(key_name, &[0; 16]); + assert_eq!(iv, &[0; 16]); + key_name.copy_from_slice(&TEST_KEY_NAME); + iv.copy_from_slice(&TEST_CBC_IV); + + // Set the encryption context. + evp_ctx + .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) + .unwrap(); + + // Set the hmac context. + hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap(); + + TicketKeyCallbackResult::Success + } else { + NOOP_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + + // Check key_name matches. + assert_eq!(key_name, &TEST_KEY_NAME); + + TicketKeyCallbackResult::Noop + } +} + +// Custom callback to encrypt and decrypt session tickets +fn test_success_tickey_key_callback( + _ssl: &SslRef, + key_name: &mut [u8; 16], + iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], + evp_ctx: &mut CipherCtxRef, + hmac_ctx: &mut HmacCtxRef, + encrypt: bool, +) -> TicketKeyCallbackResult { + // These should only be used for testing purposes. + const TEST_KEY_NAME: [u8; 16] = [5; 16]; + const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize]; + const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; + const TEST_HMAC_KEY: [u8; 32] = [3; 32]; + + let digest = MessageDigest::sha256(); + let cipher = Cipher::aes_128_cbc(); + + if encrypt { + SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + + // Ensure key_name and iv are initialized and set test values. + assert_eq!(key_name, &[0; 16]); + assert_eq!(iv, &[0; 16]); + key_name.copy_from_slice(&TEST_KEY_NAME); + iv.copy_from_slice(&TEST_CBC_IV); + + // Set the encryption context. + evp_ctx + .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) + .unwrap(); + + // Set the hmac context. + hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap(); + } else { + SUCCESS_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + + // Check key_name matches. + assert_eq!(key_name, &TEST_KEY_NAME); + + // Set the decryption context. + evp_ctx + .init_decrypt(&cipher, &TEST_AES_128_CBC_KEY, iv) + .unwrap(); + + // Set the hmac context. + hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap(); + } + + TicketKeyCallbackResult::Success +} diff --git a/boring/src/ssl/test/verify.rs b/boring/src/ssl/test/verify.rs index 5fed57038..b7981e68c 100644 --- a/boring/src/ssl/test/verify.rs +++ b/boring/src/ssl/test/verify.rs @@ -32,7 +32,7 @@ fn trusted_with_set_cert() { let mut store = X509StoreBuilder::new().unwrap(); let x509 = X509::from_pem(super::ROOT_CERT).unwrap(); - store.add_cert(x509).unwrap(); + store.add_cert(&x509).unwrap(); let mut client = server.client(); client.ctx().set_verify(SslVerifyMode::PEER); diff --git a/boring/src/stack.rs b/boring/src/stack.rs index cc909aace..395299467 100644 --- a/boring/src/stack.rs +++ b/boring/src/stack.rs @@ -48,7 +48,7 @@ impl Drop for Stack { fn drop(&mut self) { unsafe { while self.pop().is_some() {} - OPENSSL_sk_free(self.0 as *mut _); + OPENSSL_sk_free(self.0.cast()); } } } @@ -58,7 +58,7 @@ impl Stack { unsafe { ffi::init(); let ptr = cvt_p(OPENSSL_sk_new_null())?; - Ok(Stack(ptr as *mut _)) + Ok(Stack(ptr.cast())) } } } @@ -132,7 +132,7 @@ impl Drop for IntoIter { fn drop(&mut self) { unsafe { for _ in &mut *self {} - OPENSSL_sk_free(self.stack as *mut _); + OPENSSL_sk_free(self.stack.cast()); } } } @@ -144,7 +144,7 @@ impl Iterator for IntoIter { unsafe { self.idxs .next() - .map(|i| T::from_ptr(OPENSSL_sk_value(self.stack as *mut _, i) as *mut _)) + .map(|i| T::from_ptr(OPENSSL_sk_value(self.stack.cast(), i).cast())) } } @@ -158,7 +158,7 @@ impl DoubleEndedIterator for IntoIter { unsafe { self.idxs .next_back() - .map(|i| T::from_ptr(OPENSSL_sk_value(self.stack as *mut _, i) as *mut _)) + .map(|i| T::from_ptr(OPENSSL_sk_value(self.stack.cast(), i).cast())) } } } @@ -176,7 +176,7 @@ unsafe impl ForeignTypeRef for StackRef { impl StackRef { fn as_stack(&self) -> *mut OPENSSL_STACK { - self.as_ptr() as *mut _ + self.as_ptr().cast() } /// Returns the number of items in the stack. @@ -234,7 +234,7 @@ impl StackRef { /// Pushes a value onto the top of the stack. pub fn push(&mut self, data: T) -> Result<(), ErrorStack> { unsafe { - cvt_0(OPENSSL_sk_push(self.as_stack(), data.as_ptr() as *mut _))?; + cvt_0(OPENSSL_sk_push(self.as_stack(), data.as_ptr().cast()))?; mem::forget(data); Ok(()) } @@ -247,13 +247,13 @@ impl StackRef { if ptr.is_null() { None } else { - Some(T::from_ptr(ptr as *mut _)) + Some(T::from_ptr(ptr.cast())) } } } unsafe fn _get(&self, idx: usize) -> *mut T::CType { - OPENSSL_sk_value(self.as_stack(), idx) as *mut _ + OPENSSL_sk_value(self.as_stack(), idx).cast() } } @@ -333,7 +333,7 @@ impl<'a, T: Stackable> Iterator for Iter<'a, T> { unsafe { self.idxs .next() - .map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _)) + .map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i).cast())) } } @@ -347,7 +347,7 @@ impl<'a, T: Stackable> DoubleEndedIterator for Iter<'a, T> { unsafe { self.idxs .next_back() - .map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _)) + .map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i).cast())) } } } @@ -367,7 +367,7 @@ impl<'a, T: Stackable> Iterator for IterMut<'a, T> { unsafe { self.idxs .next() - .map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _)) + .map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i).cast())) } } @@ -381,7 +381,7 @@ impl<'a, T: Stackable> DoubleEndedIterator for IterMut<'a, T> { unsafe { self.idxs .next_back() - .map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _)) + .map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i).cast())) } } } diff --git a/boring/src/string.rs b/boring/src/string.rs index 4527f0d7d..94b2d48d1 100644 --- a/boring/src/string.rs +++ b/boring/src/string.rs @@ -1,6 +1,6 @@ use crate::ffi; use foreign_types::ForeignTypeRef; -use libc::{c_char, c_void}; +use libc::c_char; use std::convert::AsRef; use std::ffi::CStr; use std::fmt; @@ -83,5 +83,5 @@ impl fmt::Debug for OpensslStringRef { } unsafe fn free(buf: *mut c_char) { - crate::ffi::OPENSSL_free(buf as *mut c_void); + crate::ffi::OPENSSL_free(buf.cast()); } diff --git a/boring/src/symm.rs b/boring/src/symm.rs index fff8a4a10..7067b86b5 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -53,14 +53,15 @@ //! ``` use crate::ffi; -use libc::{c_int, c_uint}; +use foreign_types::ForeignTypeRef; use openssl_macros::corresponds; use std::cmp; +use std::ffi::c_int; use std::ptr; use crate::error::ErrorStack; use crate::nid::Nid; -use crate::{cvt, cvt_p}; +use crate::{cvt, cvt_p, try_int}; #[derive(Copy, Clone)] pub enum Mode { @@ -68,12 +69,75 @@ pub enum Mode { Decrypt, } +foreign_type_and_impl_send_sync! { + type CType = ffi::EVP_CIPHER_CTX; + fn drop = ffi::EVP_CIPHER_CTX_free; + + pub struct CipherCtx; +} + +impl CipherCtxRef { + /// Configures CipherCtx for a fresh encryption operation using `cipher`. + /// + #[corresponds(EVP_EncryptInit_ex)] + pub fn init_encrypt( + &mut self, + cipher: &Cipher, + key: &[u8], + iv: &[u8; ffi::EVP_MAX_IV_LENGTH as usize], + ) -> Result<(), ErrorStack> { + ffi::init(); + + if key.len() != cipher.key_len() { + return Err(ErrorStack::internal_error_str("invalid key size")); + } + + unsafe { + cvt(ffi::EVP_EncryptInit_ex( + self.as_ptr(), + cipher.as_ptr(), + // ENGINE api is deprecated + ptr::null_mut(), + key.as_ptr(), + iv.as_ptr(), + )) + } + } + + /// Configures CipherCtx for a fresh decryption operation using `cipher`. + /// + #[corresponds(EVP_DecryptInit_ex)] + pub fn init_decrypt( + &mut self, + cipher: &Cipher, + key: &[u8], + iv: &[u8; ffi::EVP_MAX_IV_LENGTH as usize], + ) -> Result<(), ErrorStack> { + ffi::init(); + + if key.len() != cipher.key_len() { + return Err(ErrorStack::internal_error_str("invalid key size")); + } + + unsafe { + cvt(ffi::EVP_DecryptInit_ex( + self.as_ptr(), + cipher.as_ptr(), + // ENGINE api is deprecated + ptr::null_mut(), + key.as_ptr(), + iv.as_ptr(), + )) + } + } +} + /// Represents a particular cipher algorithm. /// /// See OpenSSL doc at [`EVP_EncryptInit`] for more information on each algorithms. /// /// [`EVP_EncryptInit`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_EncryptInit.html -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Cipher(*const ffi::EVP_CIPHER); impl Cipher { @@ -81,7 +145,7 @@ impl Cipher { #[corresponds(EVP_get_cipherbynid)] #[must_use] pub fn from_nid(nid: Nid) -> Option { - let ptr = unsafe { ffi::EVP_get_cipherbyname(ffi::OBJ_nid2sn(nid.as_raw())) }; + let ptr = unsafe { ffi::EVP_get_cipherbynid(nid.as_raw()) }; if ptr.is_null() { None } else { @@ -237,6 +301,14 @@ impl Cipher { pub fn block_size(&self) -> usize { unsafe { EVP_CIPHER_block_size(self.0) as usize } } + + /// Returns the cipher's NID. + #[corresponds(EVP_CIPHER_nid)] + pub fn nid(&self) -> Nid { + ffi::init(); + let nid = unsafe { ffi::EVP_CIPHER_nid(self.as_ptr()) }; + Nid::from_raw(nid) + } } unsafe impl Sync for Cipher {} @@ -347,25 +419,22 @@ impl Crypter { mode, ))?; - assert!(key.len() <= c_int::MAX as usize); cvt(ffi::EVP_CIPHER_CTX_set_key_length( crypter.ctx, - key.len() as c_uint, + try_int(key.len())?, ))?; - let key = key.as_ptr() as *mut _; let iv = match (iv, t.iv_len()) { (Some(iv), Some(len)) => { if iv.len() != len { - assert!(iv.len() <= c_int::MAX as usize); cvt(ffi::EVP_CIPHER_CTX_ctrl( crypter.ctx, ffi::EVP_CTRL_GCM_SET_IVLEN, - iv.len() as c_int, + try_int(iv.len())?, ptr::null_mut(), ))?; } - iv.as_ptr() as *mut _ + iv.as_ptr().cast_mut() } (Some(_) | None, None) => ptr::null_mut(), (None, Some(_)) => panic!("an IV is required for this cipher"), @@ -374,7 +443,7 @@ impl Crypter { crypter.ctx, ptr::null(), ptr::null_mut(), - key, + key.as_ptr().cast_mut(), iv, mode, ))?; @@ -398,15 +467,13 @@ impl Crypter { /// When decrypting cipher text using an AEAD cipher, this must be called before `finalize`. pub fn set_tag(&mut self, tag: &[u8]) -> Result<(), ErrorStack> { unsafe { - assert!(tag.len() <= c_int::MAX as usize); // NB: this constant is actually more general than just GCM. cvt(ffi::EVP_CIPHER_CTX_ctrl( self.ctx, ffi::EVP_CTRL_GCM_SET_TAG, - tag.len() as c_int, - tag.as_ptr() as *mut _, + try_int(tag.len())?, + tag.as_ptr().cast_mut().cast(), )) - .map(|_| ()) } } @@ -416,15 +483,13 @@ impl Crypter { /// to use a value different than the default 12 bytes. pub fn set_tag_len(&mut self, tag_len: usize) -> Result<(), ErrorStack> { unsafe { - assert!(tag_len <= c_int::MAX as usize); // NB: this constant is actually more general than just GCM. cvt(ffi::EVP_CIPHER_CTX_ctrl( self.ctx, ffi::EVP_CTRL_GCM_SET_TAG, - tag_len as c_int, + try_int(tag_len)?, ptr::null_mut(), )) - .map(|_| ()) } } @@ -434,16 +499,14 @@ impl Crypter { /// CCM mode. pub fn set_data_len(&mut self, data_len: usize) -> Result<(), ErrorStack> { unsafe { - assert!(data_len <= c_int::MAX as usize); let mut len = 0; cvt(ffi::EVP_CipherUpdate( self.ctx, ptr::null_mut(), &mut len, ptr::null_mut(), - data_len as c_int, + try_int(data_len)?, )) - .map(|_| ()) } } @@ -454,16 +517,14 @@ impl Crypter { /// `update`. pub fn aad_update(&mut self, input: &[u8]) -> Result<(), ErrorStack> { unsafe { - assert!(input.len() <= c_int::MAX as usize); let mut len = 0; cvt(ffi::EVP_CipherUpdate( self.ctx, ptr::null_mut(), &mut len, input.as_ptr(), - input.len() as c_int, + try_int(input.len())?, )) - .map(|_| ()) } } @@ -479,8 +540,6 @@ impl Crypter { /// /// Panics for block ciphers if `output.len() < input.len() + block_size`, /// where `block_size` is the block size of the cipher (see `Cipher::block_size`). - /// - /// Panics if `output.len() > c_int::MAX`. pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result { unsafe { let block_size = if self.block_size > 1 { @@ -489,16 +548,14 @@ impl Crypter { 0 }; assert!(output.len() >= input.len() + block_size); - assert!(output.len() <= c_int::MAX as usize); - let mut outl = output.len() as c_int; - let inl = input.len() as c_int; + let mut outl = try_int(output.len())?; cvt(ffi::EVP_CipherUpdate( self.ctx, output.as_mut_ptr(), &mut outl, input.as_ptr(), - inl, + try_int(input.len())?, ))?; Ok(outl as usize) @@ -543,14 +600,12 @@ impl Crypter { /// bytes, for example. pub fn get_tag(&self, tag: &mut [u8]) -> Result<(), ErrorStack> { unsafe { - assert!(tag.len() <= c_int::MAX as usize); cvt(ffi::EVP_CIPHER_CTX_ctrl( self.ctx, ffi::EVP_CTRL_GCM_GET_TAG, - tag.len() as c_int, - tag.as_mut_ptr() as *mut _, + try_int(tag.len())?, + tag.as_mut_ptr().cast(), )) - .map(|_| ()) } } } @@ -985,4 +1040,129 @@ mod tests { .unwrap(); assert_eq!(pt, hex::encode(out)); } + + #[test] + fn test_nid_roundtrip() { + for cipher in [ + Cipher::aes_128_gcm(), + Cipher::aes_192_gcm(), + Cipher::aes_256_gcm(), + Cipher::aes_128_ecb(), + Cipher::aes_128_cbc(), + Cipher::aes_128_ctr(), + Cipher::aes_128_ofb(), + Cipher::aes_192_ecb(), + Cipher::aes_192_cbc(), + Cipher::aes_192_ctr(), + Cipher::aes_192_ofb(), + Cipher::aes_256_ecb(), + Cipher::aes_256_cbc(), + Cipher::aes_256_ctr(), + Cipher::aes_256_ofb(), + Cipher::des_ecb(), + Cipher::des_ede3_cbc(), + Cipher::des_cbc(), + Cipher::rc4(), + ] { + let name = cipher.nid().short_name().unwrap_or("unknown"); + assert_eq!(Cipher::from_nid(cipher.nid()), Some(cipher), "{}", name); + } + + assert_eq!(Cipher::from_nid(Cipher::des_ede3().nid()), None); + } + + // Make sure the NIDs don't actually change upstream. + #[test] + fn test_nid_regression() { + struct TestCase { + cipher: Cipher, + nid: c_int, + } + + for t in [ + TestCase { + cipher: Cipher::aes_128_ecb(), + nid: 418, + }, + TestCase { + cipher: Cipher::aes_128_cbc(), + nid: 419, + }, + TestCase { + cipher: Cipher::aes_128_ctr(), + nid: 904, + }, + TestCase { + cipher: Cipher::aes_128_gcm(), + nid: 895, + }, + TestCase { + cipher: Cipher::aes_128_ofb(), + nid: 420, + }, + TestCase { + cipher: Cipher::aes_192_ecb(), + nid: 422, + }, + TestCase { + cipher: Cipher::aes_192_cbc(), + nid: 423, + }, + TestCase { + cipher: Cipher::aes_192_ctr(), + nid: 905, + }, + TestCase { + cipher: Cipher::aes_192_gcm(), + nid: 898, + }, + TestCase { + cipher: Cipher::aes_192_ofb(), + nid: 424, + }, + TestCase { + cipher: Cipher::aes_256_ecb(), + nid: 426, + }, + TestCase { + cipher: Cipher::aes_256_cbc(), + nid: 427, + }, + TestCase { + cipher: Cipher::aes_256_ctr(), + nid: 906, + }, + TestCase { + cipher: Cipher::aes_256_gcm(), + nid: 901, + }, + TestCase { + cipher: Cipher::aes_256_ofb(), + nid: 428, + }, + TestCase { + cipher: Cipher::des_ecb(), + nid: 29, + }, + TestCase { + cipher: Cipher::des_ede3_cbc(), + nid: 44, + }, + TestCase { + cipher: Cipher::des_cbc(), + nid: 31, + }, + TestCase { + cipher: Cipher::rc4(), + nid: 5, + }, + TestCase { + cipher: Cipher::des_ede3(), + nid: 33, + }, + ] { + let name = t.cipher.nid().short_name().unwrap_or("unknown"); + assert_eq!(t.cipher.nid().as_raw(), t.nid, "{}", name); + } + } } diff --git a/boring/src/util.rs b/boring/src/util.rs index bb6373c18..6583f0aaf 100644 --- a/boring/src/util.rs +++ b/boring/src/util.rs @@ -46,17 +46,17 @@ pub unsafe extern "C" fn invoke_passwd_cb( where F: FnOnce(&mut [u8]) -> Result, { - let callback = &mut *(cb_state as *mut CallbackState); + let callback = &mut *cb_state.cast::>(); let result = panic::catch_unwind(AssertUnwindSafe(|| { - let pass_slice = slice::from_raw_parts_mut(buf as *mut u8, size as usize); + let pass_slice = slice::from_raw_parts_mut(buf.cast::(), size as usize); callback.cb.take().unwrap()(pass_slice) })); match result { Ok(Ok(len)) => len as c_int, - Ok(Err(_)) => { - // FIXME restore error stack + Ok(Err(err)) => { + err.put(); 0 } Err(err) => { @@ -80,14 +80,14 @@ impl ForeignTypeExt for FT {} pub trait ForeignTypeRefExt: ForeignTypeRef { unsafe fn from_const_ptr<'a>(ptr: *const Self::CType) -> &'a Self { - Self::from_ptr(ptr as *mut Self::CType) + Self::from_ptr(ptr.cast_mut()) } unsafe fn from_const_ptr_opt<'a>(ptr: *const Self::CType) -> Option<&'a Self> { if ptr.is_null() { None } else { - Some(Self::from_const_ptr(ptr as *mut Self::CType)) + Some(Self::from_const_ptr(ptr.cast_mut())) } } } diff --git a/boring/src/x509/crl.rs b/boring/src/x509/crl.rs index f09f4b592..602227eca 100644 --- a/boring/src/x509/crl.rs +++ b/boring/src/x509/crl.rs @@ -31,7 +31,7 @@ //! builder.sign(&pkey, MessageDigest::sha256())?; //! //! let mut store_builder = X509StoreBuilder::new()?; -//! store_builder.add_cert(issuer)?; +//! store_builder.add_cert(&issuer)?; //! store_builder.add_crl(builder.build())?; //! store_builder //! .param_mut() diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index c9fb9a8aa..b604c9263 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -20,7 +20,7 @@ use std::net::IpAddr; use std::path::Path; use std::ptr; use std::str; -use std::sync::{LazyLock, Once}; +use std::sync::LazyLock; use crate::asn1::{ Asn1BitStringRef, Asn1IntegerRef, Asn1Object, Asn1ObjectRef, Asn1StringRef, Asn1TimeRef, @@ -36,6 +36,7 @@ use crate::pkey::{HasPrivate, HasPublic, PKey, PKeyRef, Public}; use crate::ssl::SslRef; use crate::stack::{Stack, StackRef, Stackable}; use crate::string::OpensslString; +use crate::try_int; use crate::util::ForeignTypeRefExt; use crate::x509::crl::X509CRL; use crate::x509::verify::{X509VerifyParam, X509VerifyParamRef}; @@ -107,12 +108,9 @@ impl X509StoreContextRef { #[must_use] pub fn ex_data(&self, index: Index) -> Option<&T> { unsafe { - let data = ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw()); - if data.is_null() { - None - } else { - Some(&*(data as *const T)) - } + ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw()) + .cast::() + .as_ref() } } @@ -120,12 +118,9 @@ impl X509StoreContextRef { #[corresponds(X509_STORE_CTX_get_ex_data)] pub fn ex_data_mut(&mut self, index: Index) -> Option<&mut T> { unsafe { - let data = ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw()); - if data.is_null() { - None - } else { - Some(&mut *(data as *mut T)) - } + ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw()) + .cast::() + .as_mut() } } @@ -134,7 +129,6 @@ impl X509StoreContextRef { /// This can be used to provide data to callbacks registered with the context. Use the /// `Ssl::new_ex_index` method to create an `Index`. #[corresponds(X509_STORE_CTX_set_ex_data)] - #[doc(alias = "replace_ex_data")] pub fn set_ex_data(&mut self, index: Index, data: T) { if let Some(old) = self.ex_data_mut(index) { *old = data; @@ -148,7 +142,7 @@ impl X509StoreContextRef { ffi::X509_STORE_CTX_set_ex_data( self.as_ptr(), index.as_raw(), - Box::into_raw(data) as *mut c_void, + Box::into_raw(data).cast(), ); } } @@ -397,13 +391,13 @@ impl X509Builder { /// Sets the notAfter constraint on the certificate. #[corresponds(X509_set1_notAfter)] pub fn set_not_after(&mut self, not_after: &Asn1TimeRef) -> Result<(), ErrorStack> { - unsafe { cvt(X509_set1_notAfter(self.0.as_ptr(), not_after.as_ptr())).map(|_| ()) } + unsafe { cvt(X509_set1_notAfter(self.0.as_ptr(), not_after.as_ptr())) } } /// Sets the notBefore constraint on the certificate. #[corresponds(X509_set1_notBefore)] pub fn set_not_before(&mut self, not_before: &Asn1TimeRef) -> Result<(), ErrorStack> { - unsafe { cvt(X509_set1_notBefore(self.0.as_ptr(), not_before.as_ptr())).map(|_| ()) } + unsafe { cvt(X509_set1_notBefore(self.0.as_ptr(), not_before.as_ptr())) } } /// Sets the version of the certificate. @@ -412,7 +406,7 @@ impl X509Builder { /// the X.509 standard should pass `2` to this method. #[corresponds(X509_set_version)] pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::X509_set_version(self.0.as_ptr(), version.into())).map(|_| ()) } + unsafe { cvt(ffi::X509_set_version(self.0.as_ptr(), version.into())) } } /// Sets the serial number of the certificate. @@ -423,7 +417,6 @@ impl X509Builder { self.0.as_ptr(), serial_number.as_ptr(), )) - .map(|_| ()) } } @@ -435,7 +428,6 @@ impl X509Builder { self.0.as_ptr(), issuer_name.as_ptr(), )) - .map(|_| ()) } } @@ -464,7 +456,6 @@ impl X509Builder { self.0.as_ptr(), subject_name.as_ptr(), )) - .map(|_| ()) } } @@ -474,7 +465,7 @@ impl X509Builder { where T: HasPublic, { - unsafe { cvt(ffi::X509_set_pubkey(self.0.as_ptr(), key.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::X509_set_pubkey(self.0.as_ptr(), key.as_ptr())) } } /// Returns a context object which is needed to create certain X509 extension values. @@ -513,16 +504,9 @@ impl X509Builder { } } - /// Adds an X509 extension value to the certificate. - /// - /// This works just as `append_extension` except it takes ownership of the `X509Extension`. - pub fn append_extension(&mut self, extension: X509Extension) -> Result<(), ErrorStack> { - self.append_extension2(&extension) - } - /// Adds an X509 extension value to the certificate. #[corresponds(X509_add_ext)] - pub fn append_extension2(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> { + pub fn append_extension(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> { unsafe { cvt(ffi::X509_add_ext(self.0.as_ptr(), extension.as_ptr(), -1))?; Ok(()) @@ -535,7 +519,7 @@ impl X509Builder { where T: HasPrivate, { - unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), hash.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), hash.as_ptr())) } } /// Consumes the builder, returning the certificate. @@ -585,7 +569,7 @@ impl X509Ref { if stack.is_null() { None } else { - Some(Stack::from_ptr(stack as *mut _)) + Some(Stack::from_ptr(stack.cast())) } } } @@ -614,7 +598,7 @@ impl X509Ref { if stack.is_null() { None } else { - Some(Stack::from_ptr(stack as *mut _)) + Some(Stack::from_ptr(stack.cast())) } } } @@ -655,14 +639,14 @@ impl X509Ref { buf: [0; ffi::EVP_MAX_MD_SIZE as usize], len: ffi::EVP_MAX_MD_SIZE as usize, }; - let mut len = ffi::EVP_MAX_MD_SIZE.try_into().unwrap(); + let mut len = try_int(ffi::EVP_MAX_MD_SIZE)?; cvt(ffi::X509_digest( self.as_ptr(), hash_type.as_ptr(), - digest.buf.as_mut_ptr() as *mut _, + digest.buf.as_mut_ptr(), &mut len, ))?; - digest.len = len as usize; + digest.len = try_int(len)?; Ok(digest) } @@ -703,7 +687,7 @@ impl X509Ref { let mut signature = ptr::null(); X509_get0_signature(&mut signature, ptr::null_mut(), self.as_ptr()); assert!(!signature.is_null()); - Asn1BitStringRef::from_ptr(signature as *mut _) + Asn1BitStringRef::from_ptr(signature.cast_mut()) } } @@ -715,7 +699,7 @@ impl X509Ref { let mut algor = ptr::null(); X509_get0_signature(ptr::null_mut(), &mut algor, self.as_ptr()); assert!(!algor.is_null()); - X509AlgorithmRef::from_ptr(algor as *mut _) + X509AlgorithmRef::from_ptr(algor.cast_mut()) } } @@ -779,7 +763,7 @@ impl X509Ref { unsafe { cvt_n(ffi::X509_check_host( self.as_ptr(), - host.as_ptr() as _, + host.as_ptr().cast(), host.len(), 0, std::ptr::null_mut(), @@ -931,7 +915,7 @@ pub struct X509v3Context<'a>(ffi::X509V3_CTX, PhantomData<(&'a X509Ref, &'a Conf impl X509v3Context<'_> { #[must_use] pub fn as_ptr(&self) -> *mut ffi::X509V3_CTX { - &self.0 as *const _ as *mut _ + std::ptr::addr_of!(self.0).cast_mut() } } @@ -986,8 +970,8 @@ impl X509Extension { &mut ctx } }; - let name = name.as_ptr() as *mut _; - let value = value.as_ptr() as *mut _; + let name = name.as_ptr().cast_mut(); + let value = value.as_ptr().cast_mut(); cvt_p(ffi::X509V3_EXT_nconf(conf, context_ptr, name, value)) .map(|p| X509Extension::from_ptr(p)) @@ -1032,7 +1016,7 @@ impl X509Extension { } }; let name = name.as_raw(); - let value = value.as_ptr() as *mut _; + let value = value.as_ptr().cast_mut(); cvt_p(ffi::X509V3_EXT_nconf_nid(conf, context_ptr, name, value)) .map(|p| X509Extension::from_ptr(p)) @@ -1116,17 +1100,15 @@ impl X509NameBuilder { pub fn append_entry_by_text(&mut self, field: &str, value: &str) -> Result<(), ErrorStack> { unsafe { let field = CString::new(field).map_err(ErrorStack::internal_error)?; - assert!(value.len() <= ValueLen::MAX as usize); cvt(ffi::X509_NAME_add_entry_by_txt( self.0.as_ptr(), - field.as_ptr() as *mut _, + field.as_ptr().cast_mut(), ffi::MBSTRING_UTF8, value.as_ptr(), - value.len() as ValueLen, + try_int(value.len())?, -1, 0, )) - .map(|_| ()) } } @@ -1140,17 +1122,15 @@ impl X509NameBuilder { ) -> Result<(), ErrorStack> { unsafe { let field = CString::new(field).map_err(ErrorStack::internal_error)?; - assert!(value.len() <= ValueLen::MAX as usize); cvt(ffi::X509_NAME_add_entry_by_txt( self.0.as_ptr(), - field.as_ptr() as *mut _, + field.as_ptr().cast_mut(), ty.as_raw(), value.as_ptr(), - value.len() as ValueLen, + try_int(value.len())?, -1, 0, )) - .map(|_| ()) } } @@ -1158,17 +1138,15 @@ impl X509NameBuilder { #[corresponds(X509_NAME_add_entry_by_NID)] pub fn append_entry_by_nid(&mut self, field: Nid, value: &str) -> Result<(), ErrorStack> { unsafe { - assert!(value.len() <= ValueLen::MAX as usize); cvt(ffi::X509_NAME_add_entry_by_NID( self.0.as_ptr(), field.as_raw(), ffi::MBSTRING_UTF8, - value.as_ptr() as *mut _, - value.len() as ValueLen, + value.as_ptr().cast_mut(), + try_int(value.len())?, -1, 0, )) - .map(|_| ()) } } @@ -1181,17 +1159,15 @@ impl X509NameBuilder { ty: Asn1Type, ) -> Result<(), ErrorStack> { unsafe { - assert!(value.len() <= ValueLen::MAX as usize); cvt(ffi::X509_NAME_add_entry_by_NID( self.0.as_ptr(), field.as_raw(), ty.as_raw(), - value.as_ptr() as *mut _, - value.len() as ValueLen, + value.as_ptr().cast_mut(), + try_int(value.len())?, -1, 0, )) - .map(|_| ()) } } @@ -1205,11 +1181,6 @@ impl X509NameBuilder { } } -#[cfg(not(feature = "fips-compat"))] -type ValueLen = isize; -#[cfg(feature = "fips-compat")] -type ValueLen = i32; - foreign_type_and_impl_send_sync! { type CType = ffi::X509_NAME; fn drop = ffi::X509_NAME_free; @@ -1388,7 +1359,7 @@ impl X509ReqBuilder { /// Set the numerical value of the version field. #[corresponds(X509_REQ_set_version)] pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::X509_REQ_set_version(self.0.as_ptr(), version.into())).map(|_| ()) } + unsafe { cvt(ffi::X509_REQ_set_version(self.0.as_ptr(), version.into())) } } /// Set the issuer name. @@ -1399,7 +1370,6 @@ impl X509ReqBuilder { self.0.as_ptr(), subject_name.as_ptr(), )) - .map(|_| ()) } } @@ -1409,7 +1379,7 @@ impl X509ReqBuilder { where T: HasPublic, { - unsafe { cvt(ffi::X509_REQ_set_pubkey(self.0.as_ptr(), key.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::X509_REQ_set_pubkey(self.0.as_ptr(), key.as_ptr())) } } /// Return an `X509v3Context`. This context object can be used to construct @@ -1447,7 +1417,6 @@ impl X509ReqBuilder { self.0.as_ptr(), extensions.as_ptr(), )) - .map(|_| ()) } } @@ -1463,7 +1432,6 @@ impl X509ReqBuilder { key.as_ptr(), hash.as_ptr(), )) - .map(|_| ()) } } @@ -1858,7 +1826,7 @@ impl X509AlgorithmRef { let mut oid = ptr::null(); X509_ALGOR_get0(&mut oid, ptr::null_mut(), ptr::null_mut(), self.as_ptr()); assert!(!oid.is_null()); - Asn1ObjectRef::from_ptr(oid as *mut _) + Asn1ObjectRef::from_ptr(oid.cast_mut()) } } } @@ -1901,16 +1869,9 @@ use crate::ffi::X509_OBJECT_get0_X509; #[allow(bad_style)] unsafe fn X509_OBJECT_free(x: *mut ffi::X509_OBJECT) { ffi::X509_OBJECT_free_contents(x); - ffi::OPENSSL_free(x as *mut libc::c_void); + ffi::OPENSSL_free(x.cast()); } unsafe fn get_new_x509_store_ctx_idx(f: ffi::CRYPTO_EX_free) -> c_int { - // hack around https://rt.openssl.org/Ticket/Display.html?id=3710&user=guest&pass=guest - static ONCE: Once = Once::new(); - - ONCE.call_once(|| { - ffi::X509_STORE_CTX_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, None); - }); - ffi::X509_STORE_CTX_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) } diff --git a/boring/src/x509/store.rs b/boring/src/x509/store.rs index 3051d7232..7dcb5e4f3 100644 --- a/boring/src/x509/store.rs +++ b/boring/src/x509/store.rs @@ -36,7 +36,7 @@ //! //! let certificate: X509 = builder.build(); //! let mut builder = X509StoreBuilder::new().unwrap(); -//! let _ = builder.add_cert(certificate); +//! let _ = builder.add_cert(&certificate); //! let store: X509Store = builder.build(); //! ``` @@ -45,11 +45,11 @@ use crate::ffi; use crate::stack::StackRef; use crate::x509::crl::X509CRL; use crate::x509::verify::{X509VerifyFlags, X509VerifyParamRef}; -use crate::x509::{X509Object, X509}; +use crate::x509::{X509Object, X509Ref}; use crate::{cvt, cvt_p}; use foreign_types::{ForeignType, ForeignTypeRef}; use openssl_macros::corresponds; -use std::mem; +use std::mem::ManuallyDrop; foreign_type_and_impl_send_sync! { type CType = ffi::X509_STORE; @@ -74,18 +74,15 @@ impl X509StoreBuilder { /// Constructs the `X509Store`. #[must_use] pub fn build(self) -> X509Store { - let store = X509Store(self.0); - mem::forget(self); - store + X509Store(ManuallyDrop::new(self).0) } } impl X509StoreBuilderRef { /// Adds a certificate to the certificate store. - // FIXME should take an &X509Ref #[corresponds(X509_STORE_add_cert)] - pub fn add_cert(&mut self, cert: X509) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), cert.as_ptr())).map(|_| ()) } + pub fn add_cert(&mut self, cert: &X509Ref) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), cert.as_ptr())) } } /// Adds a CRL to the certificate store. @@ -113,7 +110,7 @@ impl X509StoreBuilderRef { /// build time otherwise. #[corresponds(X509_STORE_set_default_paths)] pub fn set_default_paths(&mut self) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::X509_STORE_set_default_paths(self.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::X509_STORE_set_default_paths(self.as_ptr())) } } /// Sets certificate chain validation related flags. @@ -133,7 +130,7 @@ impl X509StoreBuilderRef { /// Sets certificate chain validation related parameters. #[corresponds(X509_STORE_set1_param)] pub fn set_param(&mut self, param: &X509VerifyParamRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::X509_STORE_set1_param(self.as_ptr(), param.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::X509_STORE_set1_param(self.as_ptr(), param.as_ptr())) } } /// For testing only @@ -153,6 +150,23 @@ foreign_type_and_impl_send_sync! { pub struct X509Store; } +impl ToOwned for X509StoreRef { + type Owned = X509Store; + + fn to_owned(&self) -> X509Store { + unsafe { + ffi::X509_STORE_up_ref(self.as_ptr()); + X509Store::from_ptr(self.as_ptr()) + } + } +} + +impl Clone for X509Store { + fn clone(&self) -> X509Store { + (**self).to_owned() + } +} + impl X509StoreRef { /// **Warning: this method is unsound** /// @@ -179,12 +193,16 @@ impl X509StoreRef { } #[test] -#[allow(dead_code)] -// X509Store must not implement Clone because `SslContextBuilder::cert_store_mut` lets -// you get a mutable reference to a store that could have been cloned before being -// passed to `SslContextBuilder::set_cert_store`. -fn no_clone_for_x509store() { - trait MustNotImplementClone {} - impl MustNotImplementClone for T {} - impl MustNotImplementClone for X509Store {} +#[allow(clippy::redundant_clone)] +#[should_panic = "Shared X509Store can't be mutated"] +fn set_cert_store_pevents_mutability() { + use crate::ssl::*; + + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let store = X509StoreBuilder::new().unwrap().build(); + + ctx.set_cert_store(store.clone()); + + // This is bad. + let _aliased_store = ctx.cert_store_mut(); } diff --git a/boring/src/x509/tests/mod.rs b/boring/src/x509/tests/mod.rs index ba96141b5..8563ddd44 100644 --- a/boring/src/x509/tests/mod.rs +++ b/boring/src/x509/tests/mod.rs @@ -84,7 +84,7 @@ fn test_subject_read_cn() { let cert = X509::from_pem(cert).unwrap(); let subject = cert.subject_name(); let cn = subject.entries_by_nid(Nid::COMMONNAME).next().unwrap(); - assert_eq!(cn.data().as_slice(), b"foobar.com") + assert_eq!(cn.data().as_slice(), b"foobar.com"); } #[test] @@ -261,34 +261,36 @@ fn x509_builder() { .unwrap(); let basic_constraints = BasicConstraints::new().critical().ca().build().unwrap(); - builder.append_extension(basic_constraints).unwrap(); + builder + .append_extension(basic_constraints.as_ref()) + .unwrap(); let key_usage = KeyUsage::new() .digital_signature() .key_encipherment() .build() .unwrap(); - builder.append_extension(key_usage).unwrap(); + builder.append_extension(&key_usage).unwrap(); let ext_key_usage = ExtendedKeyUsage::new() .client_auth() .server_auth() .other("2.999.1") .build() .unwrap(); - builder.append_extension(ext_key_usage).unwrap(); + builder.append_extension(&ext_key_usage).unwrap(); let subject_key_identifier = SubjectKeyIdentifier::new() .build(&builder.x509v3_context(None, None)) .unwrap(); - builder.append_extension(subject_key_identifier).unwrap(); + builder.append_extension(&subject_key_identifier).unwrap(); let authority_key_identifier = AuthorityKeyIdentifier::new() .keyid(true) .build(&builder.x509v3_context(None, None)) .unwrap(); - builder.append_extension(authority_key_identifier).unwrap(); + builder.append_extension(&authority_key_identifier).unwrap(); let subject_alternative_name = SubjectAlternativeName::new() .dns("example.com") .build(&builder.x509v3_context(None, None)) .unwrap(); - builder.append_extension(subject_alternative_name).unwrap(); + builder.append_extension(&subject_alternative_name).unwrap(); builder.sign(&pkey, MessageDigest::sha256()).unwrap(); @@ -503,7 +505,7 @@ fn test_verify_cert() { let chain = Stack::new().unwrap(); let mut store_bldr = X509StoreBuilder::new().unwrap(); - store_bldr.add_cert(ca).unwrap(); + store_bldr.add_cert(&ca).unwrap(); let store = store_bldr.build(); let empty_store = X509StoreBuilder::new().unwrap().build(); @@ -536,7 +538,7 @@ fn test_verify_fails() { let chain = Stack::new().unwrap(); let mut store_bldr = X509StoreBuilder::new().unwrap(); - store_bldr.add_cert(ca).unwrap(); + store_bldr.add_cert(&ca).unwrap(); let store = store_bldr.build(); let mut context = X509StoreContext::new().unwrap(); @@ -556,7 +558,7 @@ fn test_verify_revoked() { let chain = Stack::new().unwrap(); let mut store_bldr = X509StoreBuilder::new().unwrap(); - store_bldr.add_cert(ca).unwrap(); + store_bldr.add_cert(&ca).unwrap(); store_bldr.add_crl(crl).unwrap(); store_bldr .param_mut() @@ -592,7 +594,7 @@ fn test_untrusted_valid_crl() { let chain = Stack::new().unwrap(); let mut store_bldr = X509StoreBuilder::new().unwrap(); - store_bldr.add_cert(ca).unwrap(); + store_bldr.add_cert(&ca).unwrap(); store_bldr .param_mut() .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL); @@ -612,11 +614,13 @@ fn test_untrusted_valid_crl() { let crl = include_bytes!("../../../test/crl.pem"); let crl = X509CRL::from_pem(crl).unwrap(); let mut context = X509StoreContext::new().unwrap(); - assert!(!context - .init(&store, &cert, &chain, |c| c - .verify_cert_with_crls(stack_of(crl))) - .unwrap()); - assert_eq!(context.verify_result(), Err(X509VerifyError::CERT_REVOKED)); + context + .init(&store, &cert, &chain, |c| { + assert!(!c.verify_cert_with_crls(stack_of(crl)).unwrap()); + assert_eq!(c.verify_result(), Err(X509VerifyError::CERT_REVOKED)); + Ok(()) + }) + .unwrap(); } #[test] @@ -628,7 +632,7 @@ fn test_untrusted_invalid_crl() { let chain = Stack::new().unwrap(); let mut store_bldr = X509StoreBuilder::new().unwrap(); - store_bldr.add_cert(ca).unwrap(); + store_bldr.add_cert(&ca).unwrap(); store_bldr .param_mut() .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL); @@ -639,27 +643,28 @@ fn test_untrusted_invalid_crl() { let crl = include_bytes!("../../../test/invalid_crl.pem"); let crl = X509CRL::from_pem(crl).unwrap(); let mut context = X509StoreContext::new().unwrap(); - assert!(!context - .init(&store, &cert, &chain, |c| c - .verify_cert_with_crls(stack_of(crl))) - .unwrap()); - assert_eq!( - context.verify_result(), - Err(X509VerifyError::UNABLE_TO_GET_CRL) - ); + context + .init(&store, &cert, &chain, |c| { + assert!(!c.verify_cert_with_crls(stack_of(crl)).unwrap()); + assert_eq!(c.verify_result(), Err(X509VerifyError::UNABLE_TO_GET_CRL)); + Ok(()) + }) + .unwrap(); // this CRL has an invalid signature let crl = include_bytes!("../../../test/bad_sig.pem"); let crl = X509CRL::from_pem(crl).unwrap(); let mut context = X509StoreContext::new().unwrap(); - assert!(!context - .init(&store, &cert, &chain, |c| c - .verify_cert_with_crls(stack_of(crl))) - .unwrap()); - assert_eq!( - context.verify_result(), - Err(X509VerifyError::CRL_SIGNATURE_FAILURE) - ); + context + .init(&store, &cert, &chain, |c| { + assert!(!c.verify_cert_with_crls(stack_of(crl)).unwrap()); + assert_eq!( + c.verify_result(), + Err(X509VerifyError::CRL_SIGNATURE_FAILURE) + ); + Ok(()) + }) + .unwrap(); } #[test] @@ -741,7 +746,7 @@ fn test_custom_time_valid() { let chain = Stack::new().unwrap(); let mut store_bldr = X509StoreBuilder::new().unwrap(); - store_bldr.add_cert(ca).unwrap(); + store_bldr.add_cert(&ca).unwrap(); store_bldr.param_mut().set_time(1656633600); // 2022-07-01, everything is valid let store = store_bldr.build(); @@ -760,7 +765,7 @@ fn test_custom_time_expired() { let chain = Stack::new().unwrap(); let mut store_bldr = X509StoreBuilder::new().unwrap(); - store_bldr.add_cert(ca).unwrap(); + store_bldr.add_cert(&ca).unwrap(); store_bldr.param_mut().set_time(1786838400); // 2026-08-16, after the root and leaf expiration let store = store_bldr.build(); @@ -779,7 +784,7 @@ fn test_custom_time_too_soon() { let chain = Stack::new().unwrap(); let mut store_bldr = X509StoreBuilder::new().unwrap(); - store_bldr.add_cert(ca).unwrap(); + store_bldr.add_cert(&ca).unwrap(); store_bldr.param_mut().set_time(1262304000); // 2010-01-01, before the root and leaf issuance let store = store_bldr.build(); @@ -800,7 +805,7 @@ fn test_custom_time_crl() { let chain = Stack::new().unwrap(); let mut store_bldr = X509StoreBuilder::new().unwrap(); - store_bldr.add_cert(ca).unwrap(); + store_bldr.add_cert(&ca).unwrap(); store_bldr .param_mut() .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL); diff --git a/boring/src/x509/tests/trusted_first.rs b/boring/src/x509/tests/trusted_first.rs index 4dd68dbab..3755a876b 100644 --- a/boring/src/x509/tests/trusted_first.rs +++ b/boring/src/x509/tests/trusted_first.rs @@ -15,7 +15,7 @@ fn test_verify_cert() { assert_eq!(Ok(()), verify(&leaf, &[&root1], &[&intermediate], |_| {})); - #[cfg(not(feature = "fips-compat"))] + #[cfg(not(feature = "legacy-compat-deprecated"))] assert_eq!( Ok(()), verify( @@ -26,7 +26,7 @@ fn test_verify_cert() { ) ); - #[cfg(feature = "fips-compat")] + #[cfg(feature = "legacy-compat-deprecated")] assert_eq!( Err(X509VerifyError::CERT_HAS_EXPIRED), verify( @@ -60,8 +60,8 @@ fn test_verify_cert() { assert_eq!( Ok(()), verify(&leaf, &[&root1], &[&intermediate, &root1_cross], |param| { - param.clear_flags(X509VerifyFlags::TRUSTED_FIRST) - },) + param.clear_flags(X509VerifyFlags::TRUSTED_FIRST); + }) ); } @@ -75,7 +75,7 @@ fn verify( let mut builder = X509StoreBuilder::new().unwrap(); for cert in trusted { - builder.add_cert((**cert).to_owned()).unwrap(); + builder.add_cert(cert).unwrap(); } builder.build() diff --git a/boring/src/x509/verify.rs b/boring/src/x509/verify.rs index 7bd12d6dc..0e7b81ebb 100644 --- a/boring/src/x509/verify.rs +++ b/boring/src/x509/verify.rs @@ -20,20 +20,14 @@ bitflags! { const NEVER_CHECK_SUBJECT = ffi::X509_CHECK_FLAG_NEVER_CHECK_SUBJECT as _; #[cfg(feature = "underscore-wildcards")] const UNDERSCORE_WILDCARDS = ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS as _; - - #[deprecated(since = "0.10.6", note = "renamed to NO_WILDCARDS")] - const FLAG_NO_WILDCARDS = ffi::X509_CHECK_FLAG_NO_WILDCARDS as _; } } -#[doc(hidden)] -#[deprecated(note = "X509Flags renamed to X509VerifyFlags")] -pub use X509VerifyFlags as X509Flags; - bitflags! { /// Flags used to configure verification of an `X509` certificate #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)] #[repr(transparent)] + #[doc(alias = "X509Flags")] pub struct X509VerifyFlags: c_ulong { const CB_ISSUER_CHECK = ffi::X509_V_FLAG_CB_ISSUER_CHECK as _; const USE_CHECK_TIME = ffi::X509_V_FLAG_USE_CHECK_TIME as _; @@ -126,10 +120,9 @@ impl X509VerifyParamRef { let raw_host = if host.is_empty() { "\0" } else { host }; cvt(ffi::X509_VERIFY_PARAM_set1_host( self.as_ptr(), - raw_host.as_ptr() as *const _, + raw_host.as_ptr().cast(), host.len(), )) - .map(|_| ()) } } @@ -141,10 +134,9 @@ impl X509VerifyParamRef { let raw_email = if email.is_empty() { "\0" } else { email }; cvt(ffi::X509_VERIFY_PARAM_set1_email( self.as_ptr(), - raw_email.as_ptr() as *const _, + raw_email.as_ptr().cast(), email.len(), )) - .map(|_| ()) } } @@ -165,10 +157,9 @@ impl X509VerifyParamRef { }; cvt(ffi::X509_VERIFY_PARAM_set1_ip( self.as_ptr(), - buf.as_ptr() as *const _, + buf.as_ptr().cast(), len, )) - .map(|_| ()) } } @@ -189,6 +180,6 @@ impl X509VerifyParamRef { /// If a parameter is unset in `src`, the existing value in `self`` is preserved. #[corresponds(X509_VERIFY_PARAM_set1)] pub fn copy_from(&mut self, src: &Self) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::X509_VERIFY_PARAM_set1(self.as_ptr(), src.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::X509_VERIFY_PARAM_set1(self.as_ptr(), src.as_ptr())) } } } diff --git a/hyper-boring/Cargo.toml b/hyper-boring/Cargo.toml index 91f744246..9963e6ee6 100644 --- a/hyper-boring/Cargo.toml +++ b/hyper-boring/Cargo.toml @@ -9,59 +9,33 @@ repository = { workspace = true } documentation = "https://docs.rs/hyper-boring" readme = "README.md" exclude = ["test/*"] -rust-version = "1.80" +rust-version = { workspace = true } [package.metadata.docs.rs] -features = ["pq-experimental"] +features = [] rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["runtime"] - -runtime = ["hyper_old/runtime"] - # Use a FIPS-validated version of boringssl. -fips = ["tokio-boring/fips"] - -# Use a FIPS build of BoringSSL, but don't set "fips-compat". -# -# As of boringSSL commit a430310d6563c0734ddafca7731570dfb683dc19, we no longer -# need to make exceptions for the types of BufLen, ProtosLen, and ValueLen, -# which means the "fips-compat" feature is no longer needed. -# -# TODO(cjpatton) Delete this feature and modify "fips" so that it doesn't imply -# "fips-compat". -fips-precompiled = ["tokio-boring/fips-precompiled"] - -# Link with precompiled FIPS-validated `bcm.o` module. -fips-link-precompiled = ["tokio-boring/fips-link-precompiled"] - -# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) -pq-experimental = ["tokio-boring/pq-experimental"] - -# Enable Hyper 1 support -hyper1 = ["dep:http", "dep:hyper", "dep:hyper-util", "dep:tower-service"] +fips = ["boring/fips", "tokio-boring/fips"] [dependencies] antidote = { workspace = true } -http = { workspace = true, optional = true } -http_old = { workspace = true } -hyper = { workspace = true, optional = true } -hyper-util = { workspace = true, optional = true, features = ["client", "client-legacy"] } -hyper_old = { workspace = true, features = ["client"] } +http = { workspace = true } +hyper = { workspace = true } +hyper-util = { workspace = true, features = ["client", "client-legacy"] } linked_hash_set = { workspace = true } boring = { workspace = true } tokio = { workspace = true } tokio-boring = { workspace = true } tower-layer = { workspace = true } -tower-service = { workspace = true, optional = true } +tower-service = { workspace = true } [dev-dependencies] bytes = { workspace = true } http-body-util = { workspace = true } hyper-util = { workspace = true, features = ["http1", "http2", "service", "tokio"] } hyper = { workspace = true, features = ["server"] } -hyper_old = { workspace = true, features = [ "full" ] } tokio = { workspace = true, features = [ "full" ] } tower = { workspace = true, features = ["util"] } futures = { workspace = true } diff --git a/hyper-boring/src/cache.rs b/hyper-boring/src/cache.rs index 185c3dc41..ad0bddbb5 100644 --- a/hyper-boring/src/cache.rs +++ b/hyper-boring/src/cache.rs @@ -86,9 +86,8 @@ impl SessionCache { } pub fn remove(&mut self, session: &SslSessionRef) { - let key = match self.reverse.remove(session.id()) { - Some(key) => key, - None => return, + let Some(key) = self.reverse.remove(session.id()) else { + return; }; if let Entry::Occupied(mut sessions) = self.sessions.entry(key) { diff --git a/hyper-boring/src/lib.rs b/hyper-boring/src/lib.rs index a3dd92554..c43d48456 100644 --- a/hyper-boring/src/lib.rs +++ b/hyper-boring/src/lib.rs @@ -10,12 +10,9 @@ use std::sync::LazyLock; use tokio_boring::SslStream; mod cache; -mod v0; -/// Hyper 1 support. -#[cfg(feature = "hyper1")] -pub mod v1; +mod v1; -pub use self::v0::*; +pub use self::v1::*; fn key_index() -> Result, ErrorStack> { static IDX: LazyLock> = LazyLock::new(|| Ssl::new_ex_index().unwrap()); diff --git a/hyper-boring/src/v0.rs b/hyper-boring/src/v0.rs deleted file mode 100644 index c365abfc0..000000000 --- a/hyper-boring/src/v0.rs +++ /dev/null @@ -1,346 +0,0 @@ -use crate::cache::{SessionCache, SessionKey}; -use crate::{key_index, HttpsLayerSettings, MaybeHttpsStream}; -use antidote::Mutex; -use boring::error::ErrorStack; -use boring::ssl::{ - ConnectConfiguration, Ssl, SslConnector, SslConnectorBuilder, SslMethod, SslRef, - SslSessionCacheMode, -}; -use http_old::uri::Scheme; -use hyper_old::client::connect::{Connected, Connection}; -#[cfg(feature = "runtime")] -use hyper_old::client::HttpConnector; -use hyper_old::service::Service; -use hyper_old::Uri; -use std::error::Error; -use std::future::Future; -use std::net; -use std::pin::Pin; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::{fmt, io}; -use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; -use tower_layer::Layer; - -/// A Connector using BoringSSL to support `http` and `https` schemes. -#[derive(Clone)] -pub struct HttpsConnector { - http: T, - inner: Inner, -} - -#[cfg(feature = "runtime")] -impl HttpsConnector { - /// Creates a a new `HttpsConnector` using default settings. - /// - /// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both - /// HTTP/2 and HTTP/1.1. - /// - /// Requires the `runtime` Cargo feature. - pub fn new() -> Result, ErrorStack> { - let mut http = HttpConnector::new(); - http.enforce_http(false); - - HttpsLayer::new().map(|l| l.layer(http)) - } -} - -impl HttpsConnector -where - S: Service + Send, - S::Error: Into>, - S::Future: Unpin + Send + 'static, - T: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static, -{ - /// Creates a new `HttpsConnector`. - /// - /// The session cache configuration of `ssl` will be overwritten. - pub fn with_connector( - http: S, - ssl: SslConnectorBuilder, - ) -> Result, ErrorStack> { - HttpsLayer::with_connector(ssl).map(|l| l.layer(http)) - } - - /// Registers a callback which can customize the configuration of each connection. - /// - /// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`), - /// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`] - /// instead. - pub fn set_callback(&mut self, callback: F) - where - F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send, - { - self.inner.callback = Some(Arc::new(callback)); - } - - /// Registers a callback which can customize the `Ssl` of each connection. - pub fn set_ssl_callback(&mut self, callback: F) - where - F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send, - { - self.inner.ssl_callback = Some(Arc::new(callback)); - } -} - -/// A layer which wraps services in an `HttpsConnector`. -pub struct HttpsLayer { - inner: Inner, -} - -#[derive(Clone)] -struct Inner { - ssl: SslConnector, - cache: Arc>, - callback: Option, - ssl_callback: Option, -} - -type Callback = - Arc Result<(), ErrorStack> + Sync + Send>; -type SslCallback = Arc Result<(), ErrorStack> + Sync + Send>; - -impl HttpsLayer { - /// Creates a new `HttpsLayer` with default settings. - /// - /// ALPN is configured to support both HTTP/1 and HTTP/1.1. - pub fn new() -> Result { - let mut ssl = SslConnector::builder(SslMethod::tls())?; - - ssl.set_alpn_protos(b"\x02h2\x08http/1.1")?; - - Self::with_connector(ssl) - } - - /// Creates a new `HttpsLayer`. - /// - /// The session cache configuration of `ssl` will be overwritten. - pub fn with_connector(ssl: SslConnectorBuilder) -> Result { - Self::with_connector_and_settings(ssl, Default::default()) - } - - /// Creates a new `HttpsLayer` with settings - pub fn with_connector_and_settings( - mut ssl: SslConnectorBuilder, - settings: HttpsLayerSettings, - ) -> Result { - let cache = Arc::new(Mutex::new(SessionCache::with_capacity( - settings.session_cache_capacity, - ))); - - ssl.set_session_cache_mode(SslSessionCacheMode::CLIENT); - - ssl.set_new_session_callback({ - let cache = cache.clone(); - move |ssl, session| { - if let Some(key) = key_index().ok().and_then(|idx| ssl.ex_data(idx)) { - cache.lock().insert(key.clone(), session); - } - } - }); - - Ok(HttpsLayer { - inner: Inner { - ssl: ssl.build(), - cache, - callback: None, - ssl_callback: None, - }, - }) - } - - /// Registers a callback which can customize the configuration of each connection. - /// - /// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`), - /// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`] - /// instead. - pub fn set_callback(&mut self, callback: F) - where - F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send, - { - self.inner.callback = Some(Arc::new(callback)); - } - - /// Registers a callback which can customize the `Ssl` of each connection. - pub fn set_ssl_callback(&mut self, callback: F) - where - F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send, - { - self.inner.ssl_callback = Some(Arc::new(callback)); - } -} - -impl Layer for HttpsLayer { - type Service = HttpsConnector; - - fn layer(&self, inner: S) -> HttpsConnector { - HttpsConnector { - http: inner, - inner: self.inner.clone(), - } - } -} - -impl Inner { - fn setup_ssl(&self, uri: &Uri, host: &str) -> Result { - let mut conf = self.ssl.configure()?; - - if let Some(ref callback) = self.callback { - callback(&mut conf, uri)?; - } - - let key = SessionKey { - host: host.to_string(), - port: uri.port_u16().unwrap_or(443), - }; - - if let Some(session) = self.cache.lock().get(&key) { - unsafe { - conf.set_session(&session)?; - } - } - - let idx = key_index()?; - conf.set_ex_data(idx, key); - - let mut ssl = conf.into_ssl(host)?; - - if let Some(ref ssl_callback) = self.ssl_callback { - ssl_callback(&mut ssl, uri)?; - } - - Ok(ssl) - } -} - -impl Service for HttpsConnector -where - S: Service + Send, - S::Error: Into>, - S::Future: Unpin + Send + 'static, - S::Response: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static, -{ - type Response = MaybeHttpsStream; - type Error = Box; - #[allow(clippy::type_complexity)] - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.http.poll_ready(cx).map_err(Into::into) - } - - fn call(&mut self, uri: Uri) -> Self::Future { - let is_tls_scheme = uri - .scheme() - .map(|s| s == &Scheme::HTTPS || s.as_str() == "wss") - .unwrap_or(false); - - let tls_setup = if is_tls_scheme { - Some((self.inner.clone(), uri.clone())) - } else { - None - }; - - let connect = self.http.call(uri); - - let f = async { - let conn = connect.await.map_err(Into::into)?; - - let (inner, uri) = match tls_setup { - Some((inner, uri)) => (inner, uri), - None => return Ok(MaybeHttpsStream::Http(conn)), - }; - - let mut host = uri.host().ok_or("URI missing host")?; - - // If `host` is an IPv6 address, we must strip away the square brackets that surround - // it (otherwise, boring will fail to parse the host as an IP address, eventually - // causing the handshake to fail due a hostname verification error). - if !host.is_empty() { - let last = host.len() - 1; - let mut chars = host.chars(); - - if (chars.next(), chars.last()) == (Some('['), Some(']')) - && host[1..last].parse::().is_ok() - { - host = &host[1..last]; - } - } - - let ssl = inner.setup_ssl(&uri, host)?; - let stream = tokio_boring::SslStreamBuilder::new(ssl, conn) - .connect() - .await?; - - Ok(MaybeHttpsStream::Https(stream)) - }; - - Box::pin(f) - } -} - -impl Connection for MaybeHttpsStream -where - T: Connection, -{ - fn connected(&self) -> Connected { - match self { - MaybeHttpsStream::Http(s) => s.connected(), - MaybeHttpsStream::Https(s) => { - let mut connected = s.get_ref().connected(); - - if s.ssl().selected_alpn_protocol() == Some(b"h2") { - connected = connected.negotiated_h2(); - } - - connected - } - } - } -} - -impl AsyncRead for MaybeHttpsStream -where - T: AsyncRead + AsyncWrite + Unpin, -{ - fn poll_read( - mut self: Pin<&mut Self>, - ctx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - match &mut *self { - MaybeHttpsStream::Http(s) => Pin::new(s).poll_read(ctx, buf), - MaybeHttpsStream::Https(s) => Pin::new(s).poll_read(ctx, buf), - } - } -} - -impl AsyncWrite for MaybeHttpsStream -where - T: AsyncRead + AsyncWrite + Unpin, -{ - fn poll_write( - mut self: Pin<&mut Self>, - ctx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - match &mut *self { - MaybeHttpsStream::Http(s) => Pin::new(s).poll_write(ctx, buf), - MaybeHttpsStream::Https(s) => Pin::new(s).poll_write(ctx, buf), - } - } - - fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { - match &mut *self { - MaybeHttpsStream::Http(s) => Pin::new(s).poll_flush(ctx), - MaybeHttpsStream::Https(s) => Pin::new(s).poll_flush(ctx), - } - } - - fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { - match &mut *self { - MaybeHttpsStream::Http(s) => Pin::new(s).poll_shutdown(ctx), - MaybeHttpsStream::Https(s) => Pin::new(s).poll_shutdown(ctx), - } - } -} diff --git a/hyper-boring/src/v1.rs b/hyper-boring/src/v1.rs index e1f9a43da..38b454229 100644 --- a/hyper-boring/src/v1.rs +++ b/hyper-boring/src/v1.rs @@ -29,14 +29,11 @@ pub struct HttpsConnector { inner: Inner, } -#[cfg(feature = "runtime")] impl HttpsConnector { /// Creates a a new `HttpsConnector` using default settings. /// /// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both /// HTTP/2 and HTTP/1.1. - /// - /// Requires the `runtime` Cargo feature. pub fn new() -> Result, ErrorStack> { let mut http = HttpConnector::new(); http.enforce_http(false); @@ -116,7 +113,7 @@ impl HttpsLayer { /// /// The session cache configuration of `ssl` will be overwritten. pub fn with_connector(ssl: SslConnectorBuilder) -> Result { - Self::with_connector_and_settings(ssl, Default::default()) + Self::with_connector_and_settings(ssl, HttpsLayerSettings::default()) } /// Creates a new `HttpsLayer` with settings @@ -246,9 +243,8 @@ where let f = async { let conn = connect.await.map_err(Into::into)?.into_inner(); - let (inner, uri) = match tls_setup { - Some((inner, uri)) => (inner, uri), - None => return Ok(MaybeHttpsStream::Http(conn)), + let Some((inner, uri)) = tls_setup else { + return Ok(MaybeHttpsStream::Http(conn)); }; let mut host = uri.host().ok_or("URI missing host")?; @@ -256,15 +252,12 @@ where // If `host` is an IPv6 address, we must strip away the square brackets that surround // it (otherwise, boring will fail to parse the host as an IP address, eventually // causing the handshake to fail due a hostname verification error). - if !host.is_empty() { - let last = host.len() - 1; - let mut chars = host.chars(); - - if let (Some('['), Some(']')) = (chars.next(), chars.last()) { - if host[1..last].parse::().is_ok() { - host = &host[1..last]; - } - } + if let Some(ipv6) = host + .strip_prefix('[') + .and_then(|h| h.strip_suffix(']')) + .filter(|h| h.parse::().is_ok()) + { + host = ipv6; } let ssl = inner.setup_ssl(&uri, host)?; diff --git a/hyper-boring/tests/v0.rs b/hyper-boring/tests/v0.rs deleted file mode 100644 index f52e18512..000000000 --- a/hyper-boring/tests/v0.rs +++ /dev/null @@ -1,156 +0,0 @@ -use boring::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod}; -use futures::StreamExt; -use hyper_boring::HttpsConnector; -use hyper_old::client::HttpConnector; -use hyper_old::server::conn::Http; -use hyper_old::{service, Response}; -use hyper_old::{Body, Client}; -use std::convert::Infallible; -use std::{io, iter}; -use tokio::net::TcpListener; - -#[tokio::test] -#[cfg(feature = "runtime")] -async fn google() { - let ssl = HttpsConnector::new().unwrap(); - let client = Client::builder() - .pool_max_idle_per_host(0) - .build::<_, Body>(ssl); - - for _ in 0..3 { - let resp = client - .get("https://www.google.com".parse().unwrap()) - .await - .expect("connection should succeed"); - let mut body = resp.into_body(); - while body.next().await.transpose().unwrap().is_some() {} - } -} - -#[tokio::test] -async fn localhost() { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - let port = addr.port(); - - let server = async move { - let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - acceptor.set_session_id_context(b"test").unwrap(); - acceptor - .set_private_key_file("tests/test/key.pem", SslFiletype::PEM) - .unwrap(); - acceptor - .set_certificate_chain_file("tests/test/cert.pem") - .unwrap(); - let acceptor = acceptor.build(); - - for _ in 0..3 { - let stream = listener.accept().await.unwrap().0; - let stream = tokio_boring::accept(&acceptor, stream).await.unwrap(); - - let service = - service::service_fn(|_| async { Ok::<_, io::Error>(Response::new(Body::empty())) }); - - Http::new() - .http1_keep_alive(false) - .serve_connection(stream, service) - .await - .unwrap(); - } - }; - tokio::spawn(server); - - let resolver = - tower::service_fn(move |_name| async move { Ok::<_, Infallible>(iter::once(addr)) }); - - let mut connector = HttpConnector::new_with_resolver(resolver); - - connector.enforce_http(false); - - let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); - - ssl.set_ca_file("tests/test/root-ca.pem").unwrap(); - - use std::fs::File; - use std::io::Write; - - let file = File::create("../target/keyfile.log").unwrap(); - ssl.set_keylog_callback(move |_, line| { - let _ = writeln!(&file, "{line}"); - }); - - let ssl = HttpsConnector::with_connector(connector, ssl).unwrap(); - let client = Client::builder().build::<_, Body>(ssl); - - for _ in 0..3 { - let resp = client - .get(format!("https://foobar.com:{port}").parse().unwrap()) - .await - .unwrap(); - assert!(resp.status().is_success(), "{}", resp.status()); - let mut body = resp.into_body(); - while body.next().await.transpose().unwrap().is_some() {} - } -} - -#[tokio::test] -async fn alpn_h2() { - use boring::ssl::{self, AlpnError}; - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - let port = addr.port(); - - let server = async move { - let mut acceptor = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap(); - acceptor - .set_certificate_chain_file("tests/test/cert.pem") - .unwrap(); - acceptor - .set_private_key_file("tests/test/key.pem", SslFiletype::PEM) - .unwrap(); - acceptor.set_alpn_select_callback(|_, client| { - ssl::select_next_proto(b"\x02h2", client).ok_or(AlpnError::NOACK) - }); - let acceptor = acceptor.build(); - - let stream = listener.accept().await.unwrap().0; - let stream = tokio_boring::accept(&acceptor, stream).await.unwrap(); - assert_eq!(stream.ssl().selected_alpn_protocol().unwrap(), b"h2"); - - let service = - service::service_fn(|_| async { Ok::<_, io::Error>(Response::new(Body::empty())) }); - - Http::new() - .http2_only(true) - .serve_connection(stream, service) - .await - .unwrap(); - }; - tokio::spawn(server); - - let resolver = - tower::service_fn(move |_name| async move { Ok::<_, Infallible>(iter::once(addr)) }); - - let mut connector = HttpConnector::new_with_resolver(resolver); - - connector.enforce_http(false); - - let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); - - ssl.set_ca_file("tests/test/root-ca.pem").unwrap(); - - let mut ssl = HttpsConnector::with_connector(connector, ssl).unwrap(); - - ssl.set_ssl_callback(|ssl, _| ssl.set_alpn_protos(b"\x02h2\x08http/1.1")); - - let client = Client::builder().build::<_, Body>(ssl); - - let resp = client - .get(format!("https://foobar.com:{port}").parse().unwrap()) - .await - .unwrap(); - assert!(resp.status().is_success(), "{}", resp.status()); - let mut body = resp.into_body(); - while body.next().await.transpose().unwrap().is_some() {} -} diff --git a/hyper-boring/tests/v1.rs b/hyper-boring/tests/v1.rs index 441caea6d..1965959f3 100644 --- a/hyper-boring/tests/v1.rs +++ b/hyper-boring/tests/v1.rs @@ -1,11 +1,9 @@ -#![cfg(feature = "hyper1")] - use boring::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod}; use bytes::Bytes; use futures::StreamExt; use http_body_util::{BodyStream, Empty}; use hyper::{service, Response}; -use hyper_boring::v1::HttpsConnector; +use hyper_boring::HttpsConnector; use hyper_util::client::legacy::connect::HttpConnector; use hyper_util::client::legacy::Client; use hyper_util::rt::{TokioExecutor, TokioIo}; @@ -80,7 +78,7 @@ async fn localhost() { let file = File::create("../target/keyfile.log").unwrap(); ssl.set_keylog_callback(move |_, line| { - let _ = writeln!(&file, "{}", line); + let _ = writeln!(&file, "{line}"); }); let ssl = HttpsConnector::with_connector(connector, ssl).unwrap(); @@ -88,7 +86,7 @@ async fn localhost() { for _ in 0..3 { let resp = client - .get(format!("https://foobar.com:{}", port).parse().unwrap()) + .get(format!("https://foobar.com:{port}").parse().unwrap()) .await .unwrap(); assert!(resp.status().is_success(), "{}", resp.status()); @@ -151,7 +149,7 @@ async fn alpn_h2() { let client = Client::builder(TokioExecutor::new()).build::<_, Empty>(ssl); let resp = client - .get(format!("https://foobar.com:{}", port).parse().unwrap()) + .get(format!("https://foobar.com:{port}").parse().unwrap()) .await .unwrap(); assert!(resp.status().is_success(), "{}", resp.status()); diff --git a/tokio-boring/Cargo.toml b/tokio-boring/Cargo.toml index 75c64129c..18d2f2b20 100644 --- a/tokio-boring/Cargo.toml +++ b/tokio-boring/Cargo.toml @@ -4,6 +4,7 @@ version = { workspace = true } authors = ["Alex Crichton ", "Ivan Nikulin "] license = "MIT OR Apache-2.0" edition = { workspace = true } +rust-version = { workspace = true } repository = { workspace = true } homepage = "https://github.com/cloudflare/boring" documentation = "https://docs.rs/tokio-boring" @@ -12,29 +13,13 @@ An implementation of SSL streams for Tokio backed by BoringSSL """ [package.metadata.docs.rs] -features = ["rpk", "pq-experimental"] +features = ["rpk"] rustdoc-args = ["--cfg", "docsrs"] [features] # Use a FIPS-validated version of boringssl. fips = ["boring/fips", "boring-sys/fips"] -# Use a FIPS build of BoringSSL, but don't set "fips-compat". -# -# As of boringSSL commit a430310d6563c0734ddafca7731570dfb683dc19, we no longer -# need to make exceptions for the types of BufLen, ProtosLen, and ValueLen, -# which means the "fips-compat" feature is no longer needed. -# -# TODO(cjpatton) Delete this feature and modify "fips" so that it doesn't imply -# "fips-compat". -fips-precompiled = ["boring/fips-precompiled"] - -# Link with precompiled FIPS-validated `bcm.o` module. -fips-link-precompiled = ["boring/fips-link-precompiled", "boring-sys/fips-link-precompiled"] - -# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) -pq-experimental = ["boring/pq-experimental"] - # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) rpk = ["boring/rpk"] diff --git a/tokio-boring/examples/simple-async.rs b/tokio-boring/examples/simple-async.rs index f4a69a1c9..c51368bd1 100644 --- a/tokio-boring/examples/simple-async.rs +++ b/tokio-boring/examples/simple-async.rs @@ -6,7 +6,7 @@ async fn main() -> anyhow::Result<()> { let listener = TcpListener::bind("127.0.0.1:8080").await?; let (tcp_stream, _addr) = listener.accept().await?; - let server = ssl::SslMethod::tls_server(); + let server = ssl::SslMethod::tls(); let mut ssl_builder = boring::ssl::SslAcceptor::mozilla_modern(server)?; ssl_builder.set_default_verify_paths()?; ssl_builder.set_verify(ssl::SslVerifyMode::PEER); diff --git a/tokio-boring/src/async_callbacks.rs b/tokio-boring/src/async_callbacks.rs index 7d3888beb..734606888 100644 --- a/tokio-boring/src/async_callbacks.rs +++ b/tokio-boring/src/async_callbacks.rs @@ -41,7 +41,7 @@ pub trait SslContextBuilderExt: private::Sealed { /// /// # Safety /// - /// The returned [`SslSession`] must not be associated with a different [`SslContext`]. + /// The returned [`boring::ssl::SslSession`] must not be associated with a different [`boring::ssl::SslContext`]. unsafe fn set_async_get_session_callback(&mut self, callback: F) where F: Fn(&mut SslRef, &[u8]) -> Option + Send + Sync + 'static; diff --git a/tokio-boring/tests/async_get_session.rs b/tokio-boring/tests/async_get_session.rs index 0ab9b396e..177d84940 100644 --- a/tokio-boring/tests/async_get_session.rs +++ b/tokio-boring/tests/async_get_session.rs @@ -26,7 +26,7 @@ async fn test() { builder .set_session_cache_mode(SslSessionCacheMode::SERVER | SslSessionCacheMode::NO_INTERNAL); builder.set_new_session_callback(|_, session| { - SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap() + SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap(); }); unsafe { @@ -49,7 +49,7 @@ async fn test() { let connector = create_connector(|builder| { builder.set_session_cache_mode(SslSessionCacheMode::CLIENT); builder.set_new_session_callback(|_, session| { - CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap() + CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap(); }); builder.set_ca_file("tests/cert.pem") diff --git a/tokio-boring/tests/rpk.rs b/tokio-boring/tests/rpk.rs index 5492767ab..b39f9d53c 100644 --- a/tokio-boring/tests/rpk.rs +++ b/tokio-boring/tests/rpk.rs @@ -1,109 +1,151 @@ -#[cfg(feature = "rpk")] -mod test_rpk { - use boring::pkey::PKey; - use boring::ssl::{SslAcceptor, SslConnector}; - use futures::future; - use std::future::Future; - use std::net::SocketAddr; - use std::pin::Pin; - use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; - use tokio::net::{TcpListener, TcpStream}; - use tokio_boring::{HandshakeError, SslStream}; - - fn create_server() -> ( - impl Future, HandshakeError>>, - SocketAddr, - ) { - let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); - - listener.set_nonblocking(true).unwrap(); - - let listener = TcpListener::from_std(listener).unwrap(); - let addr = listener.local_addr().unwrap(); +#![cfg(feature = "rpk")] + +use boring::pkey::PKey; +use boring::ssl::{ + CertificateType, SslAcceptor, SslAlert, SslConnector, SslCredential, SslMethod, SslVerifyError, + SslVerifyMode, +}; +use futures::future; +use std::future::Future; +use std::net::SocketAddr; +use std::pin::Pin; +use std::sync::Arc; +use std::sync::OnceLock; +use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpStream}; +use tokio_boring::{HandshakeError, SslStream}; + +fn create_server() -> ( + impl Future, HandshakeError>>, + SocketAddr, +) { + let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + + listener.set_nonblocking(true).unwrap(); + + let listener = TcpListener::from_std(listener).unwrap(); + let addr = listener.local_addr().unwrap(); + + let server = async move { + let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap(); + let private_key = + PKey::private_key_from_pem(&std::fs::read("tests/key.pem").unwrap()).unwrap(); + let spki = std::fs::read("tests/pubkey.der").unwrap(); + + acceptor + .add_credential({ + let mut cred = SslCredential::new_raw_public_key().unwrap(); + + cred.set_private_key(&private_key).unwrap(); + cred.set_spki_bytes(Some(&spki)).unwrap(); + + &cred.build() + }) + .unwrap(); + + let acceptor = acceptor.build(); + + let stream = listener.accept().await.unwrap().0; + + tokio_boring::accept(&acceptor, stream).await + }; + + (server, addr) +} - let server = async move { - let mut acceptor = SslAcceptor::rpk().unwrap(); - let pkey = std::fs::read("tests/key.pem").unwrap(); - let pkey = PKey::private_key_from_pem(&pkey).unwrap(); - let cert = std::fs::read("tests/pubkey.der").unwrap(); +async fn connect( + addr: SocketAddr, + spki_path: &str, + is_ok_cell: &Arc>, +) -> Result, HandshakeError> { + let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); + let spki = PKey::public_key_from_der(&std::fs::read(spki_path).unwrap()).unwrap(); + let is_ok_cell = Arc::clone(is_ok_cell); - acceptor.set_rpk_certificate(&cert).unwrap(); - acceptor.set_null_chain_private_key(&pkey).unwrap(); + connector + .set_server_certificate_types(&[CertificateType::RAW_PUBLIC_KEY]) + .unwrap(); - let acceptor = acceptor.build(); + connector.set_custom_verify_callback(SslVerifyMode::PEER, move |ssl| { + let public_key = ssl + .peer_pubkey() + .ok_or(SslVerifyError::Invalid(SslAlert::CERTIFICATE_UNKNOWN))?; - let stream = listener.accept().await.unwrap().0; + let is_ok = public_key.public_eq(&spki); - tokio_boring::accept(&acceptor, stream).await - }; + is_ok_cell.set(is_ok).unwrap(); - (server, addr) - } + if !is_ok { + return Err(SslVerifyError::Invalid(SslAlert::BAD_CERTIFICATE)); + } - #[tokio::test] - async fn server_rpk() { - let (stream, addr) = create_server(); + Ok(()) + }); - let server = async { - let mut stream = stream.await.unwrap(); - let mut buf = [0; 4]; - stream.read_exact(&mut buf).await.unwrap(); - assert_eq!(&buf, b"asdf"); + let config = connector.build().configure().unwrap(); - stream.write_all(b"jkl;").await.unwrap(); + tokio_boring::connect( + config, + "localhost", + TcpStream::connect(&addr).await.unwrap(), + ) + .await +} - future::poll_fn(|ctx| Pin::new(&mut stream).poll_shutdown(ctx)) - .await - .unwrap(); - }; +#[tokio::test] +async fn server_rpk() { + let (stream, addr) = create_server(); - let client = async { - let mut connector = SslConnector::rpk_builder().unwrap(); - let cert = std::fs::read("tests/pubkey.der").unwrap(); + let server = async { + let mut stream = stream.await.unwrap(); + let mut buf = [0; 4]; + stream.read_exact(&mut buf).await.unwrap(); + assert_eq!(&buf, b"asdf"); - connector.set_rpk_certificate(&cert).unwrap(); - let config = connector.build().configure().unwrap(); + stream.write_all(b"jkl;").await.unwrap(); - let stream = TcpStream::connect(&addr).await.unwrap(); - let mut stream = tokio_boring::connect(config, "localhost", stream) - .await - .unwrap(); + future::poll_fn(|ctx| Pin::new(&mut stream).poll_shutdown(ctx)) + .await + .unwrap(); + }; - stream.write_all(b"asdf").await.unwrap(); + let client = async { + let is_ok_cell = Arc::new(OnceLock::new()); + let mut stream = connect(addr, "tests/pubkey.der", &is_ok_cell) + .await + .unwrap(); - let mut buf = vec![]; - stream.read_to_end(&mut buf).await.unwrap(); - assert_eq!(buf, b"jkl;"); - }; + assert!(is_ok_cell.get().unwrap()); - future::join(server, client).await; - } + stream.write_all(b"asdf").await.unwrap(); - #[tokio::test] - async fn client_rpk_unknown_cert() { - let (stream, addr) = create_server(); + let mut buf = vec![]; + stream.read_to_end(&mut buf).await.unwrap(); + assert_eq!(buf, b"jkl;"); + }; - let server = async { - assert!(stream.await.is_err()); - }; + future::join(server, client).await; +} - let client = async { - let mut connector = SslConnector::rpk_builder().unwrap(); - let cert = std::fs::read("tests/pubkey2.der").unwrap(); +#[tokio::test] +async fn client_rpk_unknown_cert() { + let (stream, addr) = create_server(); - connector.set_rpk_certificate(&cert).unwrap(); - let config = connector.build().configure().unwrap(); + let server = async { + assert!(stream.await.is_err()); + }; - let stream = TcpStream::connect(&addr).await.unwrap(); + let client = async { + let is_ok_cell = Arc::new(OnceLock::new()); + let err = connect(addr, "tests/pubkey2.der", &is_ok_cell) + .await + .unwrap_err(); - let err = tokio_boring::connect(config, "localhost", stream) - .await - .unwrap_err(); + assert!(!is_ok_cell.get().unwrap()); - // NOTE: smoke test for https://github.com/cloudflare/boring/issues/140 - let _ = err.to_string(); - }; + // NOTE: smoke test for https://github.com/cloudflare/boring/issues/140 + let _ = err.to_string(); + }; - future::join(server, client).await; - } + future::join(server, client).await; }