Merge tag 'v5.0.0'
This commit is contained in:
commit
ed051f3b63
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.toml text eol=lf
|
||||
171
.github/workflows/ci.yml
vendored
171
.github/workflows/ci.yml
vendored
@ -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
|
||||
@ -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
|
||||
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 -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: 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 }}
|
||||
- name: Fetch deps
|
||||
run: cargo fetch --target ${{ matrix.target }}
|
||||
shell: bash
|
||||
env: ${{ matrix.custom_env }}
|
||||
- name: Run tests
|
||||
# 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 -vv --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }}
|
||||
shell: bash
|
||||
env: ${{ matrix.custom_env }}
|
||||
# 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
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
||||
|
||||
11
Cargo.toml
11
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"
|
||||
|
||||
17
README.md
17
README.md
@ -2,7 +2,9 @@
|
||||
|
||||
[](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: <https://docs.rs/hyper-boring>
|
||||
- FFI bindings: <https://docs.rs/boring-sys>
|
||||
|
||||
# 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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -1 +0,0 @@
|
||||
../README.md
|
||||
15
boring-sys/README.md
Normal file
15
boring-sys/README.md
Normal file
@ -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.
|
||||
@ -10,15 +10,14 @@ 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) 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 +26,6 @@ pub(crate) struct Env {
|
||||
pub(crate) path: Option<PathBuf>,
|
||||
pub(crate) include_path: Option<PathBuf>,
|
||||
pub(crate) source_path: Option<PathBuf>,
|
||||
pub(crate) precompiled_bcm_o: Option<PathBuf>,
|
||||
pub(crate) assume_patched: bool,
|
||||
pub(crate) sysroot: Option<PathBuf>,
|
||||
pub(crate) compiler_external_toolchain: Option<PathBuf>,
|
||||
@ -50,6 +48,8 @@ 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 features = Features::from_env();
|
||||
let env = Env::from_env(&host, &target, features.is_fips_like());
|
||||
@ -67,6 +67,8 @@ impl Config {
|
||||
target,
|
||||
target_arch,
|
||||
target_os,
|
||||
unix,
|
||||
target_env,
|
||||
features,
|
||||
env,
|
||||
};
|
||||
@ -81,10 +83,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 +94,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,48 +103,29 @@ 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('-', "_");
|
||||
|
||||
@ -160,26 +137,34 @@ impl Env {
|
||||
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"),
|
||||
|
||||
@ -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<PathBuf> = 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,19 @@ 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
|
||||
boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreadedDLL");
|
||||
}
|
||||
|
||||
if config.host == config.target {
|
||||
return boringssl_cmake;
|
||||
}
|
||||
|
||||
@ -255,8 +271,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 +320,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 +356,6 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
|
||||
boringssl_cmake
|
||||
}
|
||||
|
||||
/// Verify that the toolchains match <https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp3678.pdf>
|
||||
/// 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<String> {
|
||||
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<OsString> {
|
||||
let toolchains = std::fs::read_dir(toolchains_dir)?.collect::<Result<Vec<_>, _>>()?;
|
||||
// First look for one of the toolchains that Google has documented.
|
||||
@ -420,31 +387,23 @@ fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec<String> {
|
||||
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 +414,18 @@ fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec<String> {
|
||||
|
||||
android_sysroot.extend(["toolchains", "llvm", "prebuilt"]);
|
||||
|
||||
let toolchain = match pick_best_android_ndk_toolchain(&android_sysroot) {
|
||||
Ok(toolchain) => toolchain,
|
||||
Err(e) => {
|
||||
println!(
|
||||
"cargo:warning=failed to find prebuilt Android NDK toolchain for bindgen: {e}"
|
||||
);
|
||||
// Uh... let's try anyway, I guess?
|
||||
return params;
|
||||
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());
|
||||
}
|
||||
};
|
||||
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}");
|
||||
// Uh... let's try anyway, I guess?
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -484,14 +441,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 +461,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 +507,18 @@ fn apply_patch(config: &Config, patch_name: &str) -> io::Result<()> {
|
||||
}
|
||||
|
||||
fn run_command(command: &mut Command) -> io::Result<Output> {
|
||||
let out = command.output()?;
|
||||
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(),
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
println!("{}", std::str::from_utf8(&out.stdout).unwrap());
|
||||
eprintln!("{}", std::str::from_utf8(&out.stderr).unwrap());
|
||||
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 +533,16 @@ fn run_command(command: &mut Command) -> io::Result<Output> {
|
||||
}
|
||||
|
||||
fn built_boring_source_path(config: &Config) -> &PathBuf {
|
||||
if let Some(path) = &config.env.path {
|
||||
return path;
|
||||
}
|
||||
|
||||
static BUILD_SOURCE_PATH: OnceLock<PathBuf> = 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 +555,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<String> {
|
||||
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 +621,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 +633,33 @@ fn emit_link_directives(config: &Config) {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_include_path(path: PathBuf) -> Result<PathBuf, String> {
|
||||
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 +670,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 +691,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 +719,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 +740,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 +764,7 @@ fn ensure_err_lib_enum_is_named(source_code: &mut Vec<u8>) {
|
||||
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(
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 44b3df6f03d85c901767250329c571db405122d5
|
||||
Subproject commit 91a66a59b6c1435120ff83e245d7719411294386
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 853ca1ea1168dff08011e5d42d94609cc0ca2e27
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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 <aramine@cloudflare.com>
|
||||
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
|
||||
|
||||
|
||||
@ -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")]
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -43,18 +43,19 @@ fn mk_ca_cert() -> Result<(X509, PKey<Private>), 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();
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<MemBioSlice<'a>, 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)
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<usize, ErrorStack> {
|
||||
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.
|
||||
|
||||
@ -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<Private> {
|
||||
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]
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,6 +38,9 @@ pub struct ErrorStack(Vec<Error>);
|
||||
|
||||
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()
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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<const N: usize>(
|
||||
pub(crate) fn hmac<const N: usize>(
|
||||
digest: MessageDigest,
|
||||
key: &[u8],
|
||||
data: &[u8],
|
||||
@ -370,7 +370,7 @@ fn hmac<const N: usize>(
|
||||
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(),
|
||||
|
||||
130
boring/src/hmac.rs
Normal file
130
boring/src/hmac.rs
Normal file
@ -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<Hmac, ErrorStack> {
|
||||
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<Vec<u8>, 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<const N: usize>(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::<N>(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());
|
||||
}
|
||||
}
|
||||
@ -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<T>(r: *mut T) -> Result<*mut T, ErrorStack> {
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt_0(r: size_t) -> Result<size_t, ErrorStack> {
|
||||
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<c_int, ErrorStack> {
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt(r: c_int) -> Result<c_int, ErrorStack> {
|
||||
fn cvt(r: c_int) -> Result<(), ErrorStack> {
|
||||
if r <= 0 {
|
||||
Err(ErrorStack::get())
|
||||
} else {
|
||||
Ok(r)
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt_nz(r: c_int) -> Result<NonZeroUsize, ErrorStack> {
|
||||
usize::try_from(r)
|
||||
.ok()
|
||||
.and_then(NonZeroUsize::new)
|
||||
.ok_or_else(ErrorStack::get)
|
||||
}
|
||||
|
||||
fn cvt_n(r: c_int) -> Result<c_int, ErrorStack> {
|
||||
if r < 0 {
|
||||
Err(ErrorStack::get())
|
||||
@ -194,6 +201,15 @@ fn cvt_n(r: c_int) -> Result<c_int, ErrorStack> {
|
||||
}
|
||||
}
|
||||
|
||||
fn try_int<F, T>(from: F) -> Result<T, ErrorStack>
|
||||
where
|
||||
F: TryInto<T> + 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<T>(
|
||||
_parent: *mut c_void,
|
||||
ptr: *mut c_void,
|
||||
@ -203,6 +219,6 @@ unsafe extern "C" fn free_data_box<T>(
|
||||
_argp: *mut c_void,
|
||||
) {
|
||||
if !ptr.is_null() {
|
||||
drop(Box::<T>::from_raw(ptr as *mut T));
|
||||
drop(Box::<T>::from_raw(ptr.cast::<T>()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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::<F>),
|
||||
&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<Vec<u8>, 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<Vec<u8>, 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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
767
boring/src/mlkem.rs
Normal file
767
boring/src/mlkem.rs
Normal file
@ -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<MlKem768PublicKey>, Box<MlKem1024PublicKey>>);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MlKemPrivateKey(Either<Box<MlKem768PrivateKey>, Box<MlKem1024PrivateKey>>);
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Either<T768, T1024> {
|
||||
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<Self, ErrorStack> {
|
||||
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<u8>, 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<Self, ErrorStack> {
|
||||
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<MlKemSharedSecret, ErrorStack> {
|
||||
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<MlKem768PublicKey>, Box<MlKem768PrivateKey>) {
|
||||
// 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<MlKemPrivateKeySeed> = MaybeUninit::uninit();
|
||||
let mut expanded: MaybeUninit<ffi::MLKEM768_private_key> = 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<ffi::MLKEM768_public_key> = 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<Self, ErrorStack> {
|
||||
// SAFETY: seed is 64 bytes, out parameter correctly sized
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut expanded: MaybeUninit<ffi::MLKEM768_private_key> = 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<MlKem768PublicKey, ErrorStack> {
|
||||
// SAFETY: expanded key is valid, buffers correctly sized
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut parsed: MaybeUninit<ffi::MLKEM768_public_key> = 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<ffi::CBB> = 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<Self, ErrorStack> {
|
||||
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<ffi::MLKEM768_public_key> = 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<MlKem1024PublicKey>, Box<MlKem1024PrivateKey>) {
|
||||
// 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<MlKemPrivateKeySeed> = MaybeUninit::uninit();
|
||||
let mut expanded: MaybeUninit<ffi::MLKEM1024_private_key> = 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<ffi::MLKEM1024_public_key> = 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<Self, ErrorStack> {
|
||||
// SAFETY: seed is 64 bytes, out parameter correctly sized
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut expanded: MaybeUninit<ffi::MLKEM1024_private_key> = 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<MlKem1024PublicKey, ErrorStack> {
|
||||
// SAFETY: expanded key is valid, buffers correctly sized
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut parsed: MaybeUninit<ffi::MLKEM1024_public_key> = 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<ffi::CBB> = 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<Self, ErrorStack> {
|
||||
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<ffi::MLKEM1024_public_key> = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<T> PKey<T> {
|
||||
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<T> PKey<T> {
|
||||
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<Private> {
|
||||
bio.as_ptr(),
|
||||
ptr::null_mut(),
|
||||
Some(invoke_passwd_cb::<F>),
|
||||
&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<Private> {
|
||||
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))
|
||||
}
|
||||
|
||||
@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<Public> {
|
||||
pub fn from_public_components(n: BigNum, e: BigNum) -> Result<Rsa<Public>, 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<RsaPrivateKeyBuilder, ErrorStack> {
|
||||
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<RsaPrivateKeyBuilder, ErrorStack> {
|
||||
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<RsaPrivateKeyBuilder, ErrorStack> {
|
||||
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)
|
||||
|
||||
@ -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::<c_void>(), 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::<c_void>(), 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::<c_void>(), 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::<c_void>(), 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::<c_void>(), 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::<c_void>(), 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 {
|
||||
|
||||
@ -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<bool, ErrorStack> {
|
||||
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 {
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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<F>(&mut self, mode: SslVerifyMode, callback: F)
|
||||
where
|
||||
|
||||
@ -44,7 +44,7 @@ pub fn new<S: Read + Write>(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<S>(bio: *mut BIO) -> S {
|
||||
|
||||
assert!(!data.is_null());
|
||||
|
||||
let state = Box::<StreamState<S>>::from_raw(data as *mut _);
|
||||
let state = Box::<StreamState<S>>::from_raw(data.cast());
|
||||
|
||||
BIO_set_data(bio, ptr::null_mut());
|
||||
|
||||
@ -91,7 +91,7 @@ pub unsafe fn set_dtls_mtu_size<S>(bio: *mut BIO, mtu_size: usize) {
|
||||
}
|
||||
|
||||
unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState<S> {
|
||||
let data = BIO_get_data(bio) as *mut StreamState<S>;
|
||||
let data = BIO_get_data(bio).cast::<StreamState<S>>();
|
||||
|
||||
assert!(!data.is_null());
|
||||
|
||||
@ -101,8 +101,12 @@ unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState<S> {
|
||||
unsafe extern "C" fn bwrite<S: Write>(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::<S>(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<S: Write>(bio: *mut BIO, buf: *const c_char, len: c_
|
||||
unsafe extern "C" fn bread<S: Read>(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::<S>(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<S: Write>(
|
||||
let state = state::<S>(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<S>(bio: *mut BIO) -> c_int {
|
||||
let data = BIO_get_data(bio);
|
||||
|
||||
if !data.is_null() {
|
||||
drop(Box::<StreamState<S>>::from_raw(data as *mut _));
|
||||
drop(Box::<StreamState<S>>::from_raw(data.cast()));
|
||||
BIO_set_data(bio, ptr::null_mut());
|
||||
}
|
||||
|
||||
|
||||
@ -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::<F>(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::<F>(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::<u8>(), 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<T> {
|
||||
assert!(!ptr.is_null());
|
||||
unsafe { &mut *ptr.cast::<MaybeUninit<T>>() }
|
||||
}
|
||||
|
||||
pub(super) unsafe extern "C" fn raw_ticket_key<F>(
|
||||
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::<F>(SslContext::cached_ex_index::<F>())
|
||||
.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<F>(
|
||||
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::<F>(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::<F>(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<F>(
|
||||
{
|
||||
// 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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<SslContextBuilder, ErrorStack> {
|
||||
let mut ctx = match ty {
|
||||
ContextType::WithMethod(method) => SslContextBuilder::new(method),
|
||||
#[cfg(feature = "rpk")]
|
||||
ContextType::Rpk => SslContextBuilder::new_rpk(),
|
||||
}?;
|
||||
fn ctx(method: SslMethod) -> Result<SslContextBuilder, ErrorStack> {
|
||||
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<SslConnectorBuilder, ErrorStack> {
|
||||
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<SslConnectorBuilder, ErrorStack> {
|
||||
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<SslAcceptorBuilder, ErrorStack> {
|
||||
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<SslAcceptorBuilder, ErrorStack> {
|
||||
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<SslAcceptorBuilder, ErrorStack> {
|
||||
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<SslAcceptorBuilder, ErrorStack> {
|
||||
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,
|
||||
);
|
||||
|
||||
211
boring/src/ssl/credential.rs
Normal file
211
boring/src/ssl/credential.rs
Normal file
@ -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<Mutex<HashMap<TypeId, c_int>>> =
|
||||
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<SslCredentialBuilder, ErrorStack> {
|
||||
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<T>() -> Result<Index<Self, T>, ErrorStack>
|
||||
where
|
||||
T: 'static + Sync + Send,
|
||||
{
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let idx = cvt_n(get_new_ssl_credential_idx(Some(free_data_box::<T>)))?;
|
||||
Ok(Index::from_raw(idx))
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME should return a result?
|
||||
pub(crate) fn cached_ex_index<T>() -> Index<Self, T>
|
||||
where
|
||||
T: 'static + Sync + Send,
|
||||
{
|
||||
unsafe {
|
||||
let idx = *SSL_CREDENTIAL_INDEXES
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| Self::new_ex_index::<T>().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<T>(&self, index: Index<SslCredential, T>) -> 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<T>(
|
||||
&mut self,
|
||||
index: Index<SslCredential, T>,
|
||||
) -> 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<T>(
|
||||
&mut self,
|
||||
index: Index<SslCredential, T>,
|
||||
data: T,
|
||||
) -> Option<T> {
|
||||
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<T>(&mut self, index: Index<SslCredential, T>, data: T) -> Option<T> {
|
||||
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<Private>) -> 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<M>(&mut self, method: M) -> Result<(), ErrorStack>
|
||||
where
|
||||
M: PrivateKeyMethod,
|
||||
{
|
||||
unsafe {
|
||||
self.replace_ex_data(SslCredential::cached_ex_index::<M>(), method);
|
||||
|
||||
cvt_0i(ffi::SSL_CREDENTIAL_set_private_key_method(
|
||||
self.0.as_ptr(),
|
||||
&ffi::SSL_PRIVATE_KEY_METHOD {
|
||||
sign: Some(callbacks::raw_sign::<M>),
|
||||
decrypt: Some(callbacks::raw_decrypt::<M>),
|
||||
complete: Some(callbacks::raw_complete::<M>),
|
||||
},
|
||||
))
|
||||
.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)
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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<usize> {
|
||||
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<usize> {
|
||||
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));
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
242
boring/src/ssl/test/session_resumption.rs
Normal file
242
boring/src/ssl/test/session_resumption.rs
Normal file
@ -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<SslSession> = 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<SslSession> = 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<SslSession> = 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
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -48,7 +48,7 @@ impl<T: Stackable> Drop for Stack<T> {
|
||||
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<T: Stackable> Stack<T> {
|
||||
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<T: Stackable> Drop for IntoIter<T> {
|
||||
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<T: Stackable> Iterator for IntoIter<T> {
|
||||
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<T: Stackable> DoubleEndedIterator for IntoIter<T> {
|
||||
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<T: Stackable> ForeignTypeRef for StackRef<T> {
|
||||
|
||||
impl<T: Stackable> StackRef<T> {
|
||||
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<T: Stackable> StackRef<T> {
|
||||
/// 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<T: Stackable> StackRef<T> {
|
||||
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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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<Cipher> {
|
||||
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<usize, ErrorStack> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,17 +46,17 @@ pub unsafe extern "C" fn invoke_passwd_cb<F>(
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<usize, ErrorStack>,
|
||||
{
|
||||
let callback = &mut *(cb_state as *mut CallbackState<F>);
|
||||
let callback = &mut *cb_state.cast::<CallbackState<F>>();
|
||||
|
||||
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::<u8>(), 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<FT: ForeignType> 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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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<T>(&self, index: Index<X509StoreContext, T>) -> 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::<T>()
|
||||
.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,12 +118,9 @@ impl X509StoreContextRef {
|
||||
#[corresponds(X509_STORE_CTX_get_ex_data)]
|
||||
pub fn ex_data_mut<T>(&mut self, index: Index<X509StoreContext, T>) -> 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::<T>()
|
||||
.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<T>(&mut self, index: Index<X509StoreContext, T>, 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)
|
||||
}
|
||||
|
||||
@ -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<T: Clone> 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();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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())) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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<Index<Ssl, SessionKey>, ErrorStack> {
|
||||
static IDX: LazyLock<Index<Ssl, SessionKey>> = LazyLock::new(|| Ssl::new_ex_index().unwrap());
|
||||
|
||||
@ -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<T> {
|
||||
http: T,
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime")]
|
||||
impl HttpsConnector<HttpConnector> {
|
||||
/// 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<HttpsConnector<HttpConnector>, ErrorStack> {
|
||||
let mut http = HttpConnector::new();
|
||||
http.enforce_http(false);
|
||||
|
||||
HttpsLayer::new().map(|l| l.layer(http))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> HttpsConnector<S>
|
||||
where
|
||||
S: Service<Uri, Response = T> + Send,
|
||||
S::Error: Into<Box<dyn Error + Send + Sync>>,
|
||||
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<HttpsConnector<S>, 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<F>(&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<F>(&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<Mutex<SessionCache>>,
|
||||
callback: Option<Callback>,
|
||||
ssl_callback: Option<SslCallback>,
|
||||
}
|
||||
|
||||
type Callback =
|
||||
Arc<dyn Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + Sync + Send>;
|
||||
type SslCallback = Arc<dyn Fn(&mut SslRef, &Uri) -> 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<HttpsLayer, ErrorStack> {
|
||||
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<HttpsLayer, ErrorStack> {
|
||||
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<HttpsLayer, ErrorStack> {
|
||||
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<F>(&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<F>(&mut self, callback: F)
|
||||
where
|
||||
F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
|
||||
{
|
||||
self.inner.ssl_callback = Some(Arc::new(callback));
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Layer<S> for HttpsLayer {
|
||||
type Service = HttpsConnector<S>;
|
||||
|
||||
fn layer(&self, inner: S) -> HttpsConnector<S> {
|
||||
HttpsConnector {
|
||||
http: inner,
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn setup_ssl(&self, uri: &Uri, host: &str) -> Result<Ssl, ErrorStack> {
|
||||
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<S> Service<Uri> for HttpsConnector<S>
|
||||
where
|
||||
S: Service<Uri> + Send,
|
||||
S::Error: Into<Box<dyn Error + Send + Sync>>,
|
||||
S::Future: Unpin + Send + 'static,
|
||||
S::Response: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static,
|
||||
{
|
||||
type Response = MaybeHttpsStream<S::Response>;
|
||||
type Error = Box<dyn Error + Sync + Send>;
|
||||
#[allow(clippy::type_complexity)]
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
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::<net::Ipv6Addr>().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<T> Connection for MaybeHttpsStream<T>
|
||||
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<T> AsyncRead for MaybeHttpsStream<T>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
ctx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
match &mut *self {
|
||||
MaybeHttpsStream::Http(s) => Pin::new(s).poll_read(ctx, buf),
|
||||
MaybeHttpsStream::Https(s) => Pin::new(s).poll_read(ctx, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsyncWrite for MaybeHttpsStream<T>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
ctx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
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<io::Result<()>> {
|
||||
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<io::Result<()>> {
|
||||
match &mut *self {
|
||||
MaybeHttpsStream::Http(s) => Pin::new(s).poll_shutdown(ctx),
|
||||
MaybeHttpsStream::Https(s) => Pin::new(s).poll_shutdown(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -29,14 +29,11 @@ pub struct HttpsConnector<T> {
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime")]
|
||||
impl HttpsConnector<HttpConnector> {
|
||||
/// 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<HttpsConnector<HttpConnector>, 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<HttpsLayer, ErrorStack> {
|
||||
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::<net::Ipv6Addr>().is_ok() {
|
||||
host = &host[1..last];
|
||||
}
|
||||
}
|
||||
if let Some(ipv6) = host
|
||||
.strip_prefix('[')
|
||||
.and_then(|h| h.strip_suffix(']'))
|
||||
.filter(|h| h.parse::<net::Ipv6Addr>().is_ok())
|
||||
{
|
||||
host = ipv6;
|
||||
}
|
||||
|
||||
let ssl = inner.setup_ssl(&uri, host)?;
|
||||
|
||||
@ -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() {}
|
||||
}
|
||||
@ -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<Bytes>>(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());
|
||||
|
||||
@ -4,6 +4,7 @@ version = { workspace = true }
|
||||
authors = ["Alex Crichton <alex@alexcrichton.com>", "Ivan Nikulin <ifaaan@gmail.com>"]
|
||||
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"]
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<F>(&mut self, callback: F)
|
||||
where
|
||||
F: Fn(&mut SslRef, &[u8]) -> Option<BoxGetSessionFuture> + Send + Sync + 'static;
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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};
|
||||
#![cfg(feature = "rpk")]
|
||||
|
||||
fn create_server() -> (
|
||||
impl Future<Output = Result<SslStream<TcpStream>, HandshakeError<TcpStream>>>,
|
||||
SocketAddr,
|
||||
) {
|
||||
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
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};
|
||||
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
fn create_server() -> (
|
||||
impl Future<Output = Result<SslStream<TcpStream>, HandshakeError<TcpStream>>>,
|
||||
SocketAddr,
|
||||
) {
|
||||
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
|
||||
let listener = TcpListener::from_std(listener).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
|
||||
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();
|
||||
let listener = TcpListener::from_std(listener).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
acceptor.set_rpk_certificate(&cert).unwrap();
|
||||
acceptor.set_null_chain_private_key(&pkey).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();
|
||||
|
||||
let acceptor = acceptor.build();
|
||||
acceptor
|
||||
.add_credential({
|
||||
let mut cred = SslCredential::new_raw_public_key().unwrap();
|
||||
|
||||
let stream = listener.accept().await.unwrap().0;
|
||||
cred.set_private_key(&private_key).unwrap();
|
||||
cred.set_spki_bytes(Some(&spki)).unwrap();
|
||||
|
||||
tokio_boring::accept(&acceptor, stream).await
|
||||
};
|
||||
&cred.build()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
(server, addr)
|
||||
}
|
||||
let acceptor = acceptor.build();
|
||||
|
||||
#[tokio::test]
|
||||
async fn server_rpk() {
|
||||
let (stream, addr) = create_server();
|
||||
let stream = listener.accept().await.unwrap().0;
|
||||
|
||||
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");
|
||||
tokio_boring::accept(&acceptor, stream).await
|
||||
};
|
||||
|
||||
stream.write_all(b"jkl;").await.unwrap();
|
||||
|
||||
future::poll_fn(|ctx| Pin::new(&mut stream).poll_shutdown(ctx))
|
||||
.await
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
let client = async {
|
||||
let mut connector = SslConnector::rpk_builder().unwrap();
|
||||
let cert = std::fs::read("tests/pubkey.der").unwrap();
|
||||
|
||||
connector.set_rpk_certificate(&cert).unwrap();
|
||||
let config = connector.build().configure().unwrap();
|
||||
|
||||
let stream = TcpStream::connect(&addr).await.unwrap();
|
||||
let mut stream = tokio_boring::connect(config, "localhost", stream)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
stream.write_all(b"asdf").await.unwrap();
|
||||
|
||||
let mut buf = vec![];
|
||||
stream.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, b"jkl;");
|
||||
};
|
||||
|
||||
future::join(server, client).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn client_rpk_unknown_cert() {
|
||||
let (stream, addr) = create_server();
|
||||
|
||||
let server = async {
|
||||
assert!(stream.await.is_err());
|
||||
};
|
||||
|
||||
let client = async {
|
||||
let mut connector = SslConnector::rpk_builder().unwrap();
|
||||
let cert = std::fs::read("tests/pubkey2.der").unwrap();
|
||||
|
||||
connector.set_rpk_certificate(&cert).unwrap();
|
||||
let config = connector.build().configure().unwrap();
|
||||
|
||||
let stream = TcpStream::connect(&addr).await.unwrap();
|
||||
|
||||
let err = tokio_boring::connect(config, "localhost", stream)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
// NOTE: smoke test for https://github.com/cloudflare/boring/issues/140
|
||||
let _ = err.to_string();
|
||||
};
|
||||
|
||||
future::join(server, client).await;
|
||||
}
|
||||
(server, addr)
|
||||
}
|
||||
|
||||
async fn connect(
|
||||
addr: SocketAddr,
|
||||
spki_path: &str,
|
||||
is_ok_cell: &Arc<OnceLock<bool>>,
|
||||
) -> Result<SslStream<TcpStream>, HandshakeError<TcpStream>> {
|
||||
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);
|
||||
|
||||
connector
|
||||
.set_server_certificate_types(&[CertificateType::RAW_PUBLIC_KEY])
|
||||
.unwrap();
|
||||
|
||||
connector.set_custom_verify_callback(SslVerifyMode::PEER, move |ssl| {
|
||||
let public_key = ssl
|
||||
.peer_pubkey()
|
||||
.ok_or(SslVerifyError::Invalid(SslAlert::CERTIFICATE_UNKNOWN))?;
|
||||
|
||||
let is_ok = public_key.public_eq(&spki);
|
||||
|
||||
is_ok_cell.set(is_ok).unwrap();
|
||||
|
||||
if !is_ok {
|
||||
return Err(SslVerifyError::Invalid(SslAlert::BAD_CERTIFICATE));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let config = connector.build().configure().unwrap();
|
||||
|
||||
tokio_boring::connect(
|
||||
config,
|
||||
"localhost",
|
||||
TcpStream::connect(&addr).await.unwrap(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn server_rpk() {
|
||||
let (stream, addr) = create_server();
|
||||
|
||||
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");
|
||||
|
||||
stream.write_all(b"jkl;").await.unwrap();
|
||||
|
||||
future::poll_fn(|ctx| Pin::new(&mut stream).poll_shutdown(ctx))
|
||||
.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();
|
||||
|
||||
assert!(is_ok_cell.get().unwrap());
|
||||
|
||||
stream.write_all(b"asdf").await.unwrap();
|
||||
|
||||
let mut buf = vec![];
|
||||
stream.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, b"jkl;");
|
||||
};
|
||||
|
||||
future::join(server, client).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn client_rpk_unknown_cert() {
|
||||
let (stream, addr) = create_server();
|
||||
|
||||
let server = async {
|
||||
assert!(stream.await.is_err());
|
||||
};
|
||||
|
||||
let client = async {
|
||||
let is_ok_cell = Arc::new(OnceLock::new());
|
||||
let err = connect(addr, "tests/pubkey2.der", &is_ok_cell)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(!is_ok_cell.get().unwrap());
|
||||
|
||||
// NOTE: smoke test for https://github.com/cloudflare/boring/issues/140
|
||||
let _ = err.to_string();
|
||||
};
|
||||
|
||||
future::join(server, client).await;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user