Merge tag 'v5.0.0'

This commit is contained in:
Jordan Rose 2026-02-10 17:34:54 -08:00
commit ed051f3b63
78 changed files with 5255 additions and 8135 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.toml text eol=lf

View File

@ -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
View File

@ -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

View File

@ -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"

View File

@ -2,7 +2,9 @@
[![crates.io](https://img.shields.io/crates/v/boring.svg)](https://crates.io/crates/boring)
BoringSSL bindings for the Rust programming language and TLS adapters for [tokio](https://github.com/tokio-rs/tokio)
[BoringSSL](https://boringssl.googlesource.com/boringssl) is Google's fork of OpenSSL for Chrome/Chromium and Android.
This crate provides safe bindings for the Rust programming language and TLS adapters for [tokio](https://github.com/tokio-rs/tokio)
and [hyper](https://github.com/hyperium/hyper) built on top of it.
## Documentation
@ -11,6 +13,19 @@ and [hyper](https://github.com/hyperium/hyper) built on top of it.
- hyper HTTPS connector: <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

View File

@ -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

View File

@ -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]

View File

@ -1 +0,0 @@
../README.md

15
boring-sys/README.md Normal file
View 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.

View File

@ -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"),

View File

@ -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

View File

@ -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

View File

@ -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")]

View File

@ -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 }

View File

@ -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(&not_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(&not_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();

View File

@ -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 {

View File

@ -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,

View File

@ -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)

View File

@ -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))
}
}
}

View File

@ -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.

View File

@ -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]

View File

@ -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());
}

View File

@ -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())
}
}
}

View File

@ -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()

View File

@ -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());
}

View File

@ -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
View 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());
}
}

View File

@ -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>()));
}
}

View File

@ -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)

View File

@ -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
View 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);
}
}
}

View File

@ -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)

View File

@ -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();

View File

@ -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(|_| ())
}
}

View File

@ -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))
}

View File

@ -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()))
}
}

View File

@ -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)

View File

@ -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 {

View File

@ -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 {

View File

@ -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")
}

View File

@ -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

View File

@ -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());
}

View File

@ -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()))
}
}

View File

@ -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,
);

View 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)
}

View File

@ -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

View File

@ -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

View File

@ -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());
}

View File

@ -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));
}

View File

@ -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();

View 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
}

View File

@ -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);

View File

@ -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()))
}
}
}

View File

@ -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());
}

View File

@ -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);
}
}
}

View File

@ -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()))
}
}
}

View File

@ -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()

View File

@ -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)
}

View File

@ -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();
}

View File

@ -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);

View File

@ -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()

View File

@ -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())) }
}
}

View File

@ -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 }

View File

@ -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) {

View File

@ -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());

View File

@ -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),
}
}
}

View File

@ -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)?;

View File

@ -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() {}
}

View File

@ -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());

View File

@ -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"]

View File

@ -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);

View File

@ -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;

View File

@ -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")

View File

@ -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;
}