Compare commits

...

79 Commits

Author SHA1 Message Date
Jordan Rose
2c157897fe Merge in upstream v5.0.2
Some checks failed
CI / Test (true, map[CC:gcc CPLUS_INCLUDE_PATH:C:\msys64\usr\include CXX:g++ C_INCLUDE_PATH:C:\msys64\usr\include LIBRARY_PATH:C:\msys64\usr\lib RUSTC_BOOTSTRAP:1], --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness, windows-lat… (push) Has been cancelled
CI / Test (true, map[CMAKE_GENERATOR:MinGW Makefiles COLLECT_GCC:<nil> RUSTC_BOOTSTRAP:1], --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness, windows-latest, stable, i686-pc-windows-gnu, i686-mingw) (push) Has been cancelled
CI / Test (true, map[CXXFLAGS:-msse2], ubuntu-latest, stable, i686-linux-android, i686-android) (push) Has been cancelled
CI / Test (true, map[IPHONEOS_DEPLOYMENT_TARGET:17.5], macos-latest, aarch64-apple-ios, aarch64-ios) (push) Has been cancelled
CI / Test (true, map[IPHONEOS_DEPLOYMENT_TARGET:17.5], macos-latest, aarch64-apple-ios-sim, aarch64-ios-sim) (push) Has been cancelled
CI / Test (true, map[IPHONEOS_DEPLOYMENT_TARGET:17.5], macos-latest, x86_64-apple-ios, x86_64-ios) (push) Has been cancelled
CI / Test (true, ubuntu-latest, stable, aarch64-linux-android, arm64-android) (push) Has been cancelled
CI / Test (true, ubuntu-latest, stable, armv7-linux-androideabi, arm-android) (push) Has been cancelled
CI / Test (true, ubuntu-latest, stable, x86_64-linux-android, x86_64-android) (push) Has been cancelled
CI / Test (ubuntu-latest, stable, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
CI / Test (macos-latest, stable, x86_64-apple-darwin, x86_64-macos) (push) Has been cancelled
CI / Test (map[CXXFLAGS:-msse2 RUSTC_BOOTSTRAP:1], --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness, windows-latest, stable-x86_64-msvc, i686-pc-windows-msvc, i686-msvc) (push) Has been cancelled
CI / Test (map[RUSTC_BOOTSTRAP:1 RUSTFLAGS:-Dwarnings -C target-feature=+crt-static], --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness, windows-latest, stable-x86_64-msvc, x86_64-pc-windows-msvc, x86_64-msvc-static) (push) Has been cancelled
CI / Test (map[RUSTC_BOOTSTRAP:1], --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness, windows-latest, stable-x86_64-msvc, x86_64-pc-windows-msvc, x86_64-msvc) (push) Has been cancelled
CI / Test (map[]) (push) Has been cancelled
CI / Test (true, macos-latest, stable, aarch64-apple-darwin, arm64-macos) (push) Has been cancelled
CI / rustfmt (push) Has been cancelled
CI / clippy (push) Has been cancelled
CI / Test () (push) Has been cancelled
CI / Test (crossbuild-essential-arm64, true, map[CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER:aarch64-linux-gnu-g++ CC:aarch64-linux-gnu-gcc CXX:aarch64-linux-gnu-g++], ubuntu-latest, stable, aarch64-unknown-linux-gnu, aarch64-linux) (push) Has been cancelled
CI / Test (false) (push) Has been cancelled
CI / Test (gcc-arm-linux-gnueabi g++-arm-linux-gnueabi, true, map[CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER:arm-linux-gnueabi-g++ CC:arm-linux-gnueabi-gcc CXX:arm-linux-gnueabi-g++], ubuntu-latest, stable, arm-unknown-linux-gnueabi, arm-linux) (push) Has been cancelled
CI / Test (gcc-multilib g++-multilib, map[CXXFLAGS:-msse2], ubuntu-latest, stable, i686-unknown-linux-gnu, i686-linux) (push) Has been cancelled
CI / Test FIPS integration (push) Has been cancelled
CI / Cross build from macOS to Linux (x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / Test features (push) Has been cancelled
2026-02-18 17:24:41 -08:00
Kornel
e71b24328f Re-add fips-precompiled for v4 compat 2026-02-17 19:59:23 +00:00
Kornel
1f8a1041df Don't add build/ to non-FIPS pre-built path 2026-02-17 19:59:23 +00:00
Kornel
fa3a4ca915 Expose load_verify_locations like rust-openssl 2026-02-17 13:35:27 +00:00
Kornel
434585d90d Fix null handling, sync with rust-openssl 2026-02-17 13:35:27 +00:00
Jordan Rose
ee95e2060a boring-sys: Support static MSVC runtime 2026-02-13 01:51:50 +00:00
Kornel
f19374dfad Bump 2026-02-12 13:29:59 +00:00
Kornel
5bd645077b README links 2026-02-12 13:29:59 +00:00
Jordan Rose
c740bd7715 Revert "Support TARGET_CC and CC_{target}"
This reverts commit a50a39fde7,
which interferes with CMake's own support for these variables,
at least how Signal has been using them.
2026-02-11 13:37:41 -08:00
Kornel
144b67bb10 Support building without default features 2026-02-11 21:00:08 +00:00
Kornel
484afea507 Make SslCredential optional 2026-02-11 21:00:08 +00:00
Kornel
a40cba6cd6 Make ML-KEM optional 2026-02-11 21:00:08 +00:00
Kornel
9cc97c87c7 Don't always require all headers from all versions of BoringSSL 2026-02-11 21:00:08 +00:00
Kornel
d0973d7617 Backwards-compatible add_cert() 2026-02-11 21:00:08 +00:00
Kornel
e65f394509 Ensure we don't leave unit memory if generate_key fails 2026-02-11 20:59:52 +00:00
Kornel
fc02d4a47c Add missing error handling in ML-KEM generate 2026-02-11 20:59:52 +00:00
Kornel
532003e437 Don't always require all headers from all versions of BoringSSL 2026-02-11 16:22:11 +00:00
Kornel
c3f96c9cb0 Make ML-KEM optional 2026-02-11 16:22:11 +00:00
Andrew
a721d89d08 Merge in upstream v4.21.1 2026-02-02 22:39:20 -05:00
Kornel
395f57810b v4.21.1
Only fixes docs.rs builds
2026-01-23 13:21:20 +00:00
Kornel
64b3e14976 Fix docs.rs build 2026-01-23 13:03:08 +00:00
Kornel
33b36ab7a1 Release 4.21.0 2026-01-21 13:25:44 +00:00
Kornel
80c3a8911e Fix build with --no-default-features 2026-01-20 17:05:55 +00:00
Kornel
f10414d927 Ensure dependency requirements are bumped
#436
2026-01-20 17:05:55 +00:00
Kornel
98215dbdb0 Bump and fix boring-sys minimum version
Fixes #436
2026-01-20 17:05:55 +00:00
Kornel
05e3aafd0b Fix deprecated call 2026-01-20 17:05:55 +00:00
Kornel
35774a0116 Can't cross-build with clang-12 due to libc++ mismatch 2026-01-20 17:05:55 +00:00
Kornel
16290eb11b Use fips-build-compatible ERR_add_error_data 2026-01-20 17:05:55 +00:00
Kornel
97aa4a987e Include err.h in FFI bindings 2026-01-20 17:05:55 +00:00
Kornel
5a3d1ae4b2 Make set_curves_list always available
kx-* features don't exist in v5
2026-01-05 23:25:21 +00:00
Kornel
a54b351cc0 Warn about set_curves() removal
b46d77087e
2026-01-05 22:36:36 +00:00
Kornel
36a2a1ca44 Deprecate set_ex_data 2026-01-05 20:52:12 +00:00
Kornel
feb62b627b Release notes 2025-12-05 15:33:20 +00:00
Kornel
7efa9e1dd0 Bump 2025-12-05 14:15:58 +00:00
Kornel
ff3d69c075 Fix string data conversion in ErrorStack::put() 2025-12-05 14:15:58 +00:00
Jaap Aarts
f2f4871a39 Freebsd build 2025-12-05 14:15:58 +00:00
Bas Westerbaan
237c82d394 pq: fix MSVC C4146 warning 2025-12-05 14:15:58 +00:00
Kornel
327162be0d Add more reliable library_reason() 2025-12-05 14:15:58 +00:00
Kornel
7c8dd2678a Ensure that ERR_LIB type can be named 2025-12-05 14:15:58 +00:00
Alex Bakon
81d4aa19ac Merge in upstream v4.18.0 2025-08-29 15:46:15 -04:00
Andrew
bb42da53b3 Pull in release v4.15.0 plus the CI fix from upstream 'cloudflare/boring' 2025-03-10 17:05:16 -04:00
Andrew
615af5aafb Add history back from upstream so future merges apply cleanly 2024-12-04 22:14:22 -05:00
Andrew
1af143f509 Revert 12d00aa166
This reverts commit 12d00aa166.

We do not use this flag, so revert this commit to get back closer to upstream.
2024-12-04 18:48:37 -05:00
Andrew
f7b6f5dfa1 Merge upstream 4.13.0; resolve conflicts 2024-12-04 18:48:15 -05:00
Jordan Rose
3d4180b232 Skip bindgen 0.70's layout tests before Rust 1.77 2024-10-22 16:08:26 -07:00
Mike Aizatsky
889ad0f41e Update bindgen to 0.70.1
bindgen has had a lot of improvements since 0.68, and this newer version seems to able to compile libbssl from within edgeworker
2024-10-22 10:55:12 -07:00
Jordan Rose
59883d7e23 CI: Adjust CI branch to "main" instead of "master" and "libsignal" 2024-08-02 10:29:53 -07:00
Jordan Rose
fbea17998a Merge tag 'v4.9.0' into libsignal 2024-08-02 10:21:51 -07:00
Rushil Mehra
6c4a02f131 Actually Release 4.9.0 2024-08-02 01:49:23 -07:00
Jordan Rose
b9a75167dc Merge tag 'v4.6.0' into libsignal
chore: Release boring-sys version 4.6.0
2024-04-11 16:16:29 -07:00
Jordan Rose
7c88181568 boring-sys: Don't use CMake cross-compilation for macOS->iOS
(or macOS->macOS)
2023-11-10 16:34:06 -08:00
Jordan Rose
e17586365c Drop Android 19 feature flag, part 2 2023-11-10 16:09:32 -08:00
Jordan Rose
e8e6122545 Merge tag 'v4.0.0' into libsignal 2023-11-10 16:08:07 -08:00
Anthony Ramine
2f63b8affd Introduce struct Config in build script
Using a struct improves navigation of the build script,
as we can rely on rust-analyzer to help us check how
a feature flag or an environment variable is used,
as opposed to grepping for multiple env::var calls
or #[cfg] attributes.

This commit also removes some obsolete blocks of code
related to the now defunct ndk-old-gcc and fuzzing features.
2023-11-10 15:48:50 -08:00
Jordan Rose
7f4dca3405 Drop Android 19 feature flag 2023-11-10 15:48:50 -08:00
Chris Eager
8245063ae6 Update CI to build all matrix targets
This adds the matrix `--target` to relevant `cargo` commands. Some
targets are now “check-only”, which confirms cross-compilation succeeds,
without doing a full test run, because running these tests may require
emulation.

musl and MinGW have been dropped, because musl has no standard C++
setup, and MinGW isn’t a target for us.

All this requires some adjustments to boring-sys:

- Blocklist max_align_t in bindgen
   - https://github.com/rust-lang/rust-bindgen/issues/1823
- Don't check for MSVC with target_env
   - x86_64-pc-windows-gnu is identified as `target_env = "msvc"` too,
but doesn't use the Visual Studio CMake generator.
2023-10-18 15:50:51 -05:00
Anthony Ramine
8d4822be72 Fix clippy lints 2023-10-12 15:27:55 -07:00
Jordan Rose
02e4a3f9aa Merge upstream release 3.1.0 into libsignal branch 2023-10-12 15:19:22 -07:00
Jordan Rose
3c50b28a61 Remove Catalyst support 2023-10-10 16:14:12 -07:00
Jordan Rose
3809a7e1ca Update to a more recent BoringSSL 2023-05-08 16:39:30 -07:00
Jordan Rose
9534656269
Merge branch 'cloudflare:master' into libsignal 2023-05-08 15:29:47 -07:00
Lauren N. Liberda
bdf9d2ac06 upgrade bindgen build-dependency to 0.62.0
fixes build error on too new clang: https://github.com/cloudflare/boring/issues/109
2023-04-24 09:55:14 -07:00
Jordan Rose
25e1dd8fa5 boring-sys: Update Android NDK sysroot path
This longer path (inside the prebuilt toolchain included in the NDK)
has been the preferred sysroot since NDK r19. Newer NDKs no longer
have a top-level "sysroot" directory at all.
2022-11-30 15:32:47 -08:00
morph027
73ee9d7085 boring-sys: Add support for Linux ARM(v7)
Signed-off-by: morph027 <stefan.heitmueller@gmx.com>
2022-11-10 14:17:08 -08:00
Jordan Rose
81eeaab77a Merge remote-tracking branch 'origin/master' into libsignal 2022-11-10 14:16:43 -08:00
Jordan Rose
b95cb545b9 boring-sys: Add an android-api-19 feature for compat with Android 4.4 2022-08-22 12:40:38 -07:00
Jordan Rose
e1c719c096 boring-sys: Always treat macOS targets as a cross-compile, like iOS
This fixes actual cross-compile cases, like compiling for Apple
Silicon on an Intel Mac. It also makes builds more consistent by using
the SDK in the installed Xcode instead of the host root headers, which
is how Apple recommends people build anyway. (If you only have the
command line tools installed, though, it should fall back to those
without a problem.)
2022-07-29 17:42:43 -07:00
Jordan Rose
8a2d686286 boring: Expose PKey::private_key_to_der_pkcs8(_passphrase) 2022-07-28 16:28:31 -07:00
Jordan Rose
8e6f19aa9a boring-sys: Use the Android NDK sysroot when running bindgen 2022-07-25 15:04:01 -07:00
Jordan Rose
879ffef1b5 boring: Expose X509VerifyParamRef::set_time 2022-07-19 15:29:21 -07:00
Jordan Rose
a04d27b0c4 Put BoringSSL's SSL/TLS support behind a new feature 'ssl'
BoringSSL vends two libraries: libcrypto and libssl. Some clients
don't need the libssl part. With a feature flag, we can skip building
it (saving on compile time) and initializing it (saving on code size
and a tiny bit of run time).

The new feature is on by default for backwards compatibility.
2022-07-12 14:56:46 -07:00
Ravi Khadiwala
7451d82bb2 boring: use the right no_name value for oid_string 2022-07-05 14:57:27 -05:00
Chris Eager
b73043e51b boring: add Asn1Object::oid_string 2022-07-05 14:57:27 -05:00
Chris Eager
7afa7798cf boring: add X509Ref::extensions
This corresponds to `X509_get0_extensions`.
2022-07-05 14:57:27 -05:00
Ravi Khadiwala
29b7ed4cbd boring: add bindings to validate against CRLs
Adds CRL types and the ability to verify certificates against CRLs with
an X509StoreContext
2022-07-01 14:35:24 -05:00
Ravi Khadiwala
1e2df380d9 Enable CI on pushes to libsignal 2022-07-01 14:15:42 -05:00
Ravi Khadiwala
2dbdb97351 boring: Fix memory leak in Deriver 2022-07-01 12:30:42 -05:00
Jordan Rose
ea59de3542 Add minimal cross-compilation support for Windows and AArch64 Linux
Cross-compiling to AArch64 Linux can be done with a CMake toolchain
file, along with setting the correct compiler and include paths in the
environment.

Cross-compiling from X64 Windows to ARM64 Windows doesn't look at the
toolchain at all, because CMake + Visual Studio can already
cross-compile. Unfortunately, the Visual Studio CMake generator
doesn't set CMAKE_SYSTEM_PROCESSOR, which is what the BoringSSL
CMakeLists.txt is looking at to choose the architecture. For now,
disable the use of assembly when cross-compiling on Windows (assuming
that the Visual Studio generator will be used there).
2022-06-27 17:57:53 -07:00
Jordan Rose
ceb140105b Add support for Mac Catalyst (on Intel and Apple Silicon chips)
This is a Tier 3 target, but apart from working around a hardcoded
version in the 'cc' crate everything does work.
2022-06-16 12:30:46 -07:00
27 changed files with 1245 additions and 174 deletions

View File

@ -3,11 +3,10 @@ name: CI
on:
pull_request:
branches:
- master
- v4.x
- main
push:
branches:
- master
- main
env:
RUSTFLAGS: -Dwarnings
@ -58,7 +57,7 @@ jobs:
path: target
key: clippy-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }}
- name: Run clippy
run: cargo clippy --all --all-targets
run: cargo clippy --all --all-targets --features rpk,mlkem
- name: Check docs
run: cargo doc --no-deps -p boring -p boring-sys -p hyper-boring -p tokio-boring --features rpk,underscore-wildcards
env:
@ -162,8 +161,8 @@ jobs:
apt_packages: gcc-arm-linux-gnueabi g++-arm-linux-gnueabi
check_only: true
custom_env:
CC_arm-unknown-linux-gnueabi: arm-linux-gnueabi-gcc
CXX_arm-unknown-linux-gnueabi: arm-linux-gnueabi-g++
CC: arm-linux-gnueabi-gcc
CXX: arm-linux-gnueabi-g++
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER: arm-linux-gnueabi-g++
- thing: aarch64-linux
target: aarch64-unknown-linux-gnu
@ -172,8 +171,8 @@ jobs:
apt_packages: crossbuild-essential-arm64
check_only: true
custom_env:
CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc
CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++
CC: aarch64-linux-gnu-gcc
CXX: aarch64-linux-gnu-g++
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-g++
- thing: arm64-macos
target: aarch64-apple-darwin
@ -226,6 +225,15 @@ jobs:
RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness
# CI's Windows doesn't have required root certs
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness
- thing: x86_64-msvc-static
target: x86_64-pc-windows-msvc
rust: stable-x86_64-msvc
os: windows-latest
custom_env:
RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness
RUSTFLAGS: -Dwarnings -C target-feature=+crt-static
# CI's Windows doesn't have required root certs
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
@ -415,9 +423,15 @@ jobs:
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
shell: bash
- run: cargo check --no-default-features
name: Check `--no-default-features`
- run: cargo check --features mlkem,credential
name: Check `mlkem,credential`
- run: cargo test --features rpk
name: Run `rpk` tests
- run: cargo test --features underscore-wildcards
name: Run `underscore-wildcards` tests
- run: cargo test --features rpk,underscore-wildcards
name: Run `rpk,underscore-wildcards` tests
- run: cargo test --features rpk,underscore-wildcards,mlkem
name: Run `rpk,underscore-wildcards` tests

View File

@ -8,7 +8,7 @@ members = [
resolver = "2"
[workspace.package]
version = "5.0.0"
version = "5.0.2"
rust-version = "1.85"
repository = "https://github.com/cloudflare/boring"
edition = "2021"
@ -20,9 +20,9 @@ tag-prefix = ""
publish = false
[workspace.dependencies]
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" }
boring-sys = { version = "5.0.2", path = "./boring-sys", default-features = false }
boring = { version = "5.0.2", path = "./boring", default-features = false }
tokio-boring = { version = "5.0.2", path = "./tokio-boring", default-features = false }
bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] }
bitflags = "2.9"

View File

@ -7,6 +7,10 @@
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.
It supports [FIPS-compatible builds of BoringSSL](https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/FIPS.md),
as well as [Post-Quantum crypto](https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/)
and [Raw Public Key](https://docs.rs/boring/latest/boring/ssl/struct.SslRef.html#method.peer_pubkey) extensions.
## Documentation
- Boring API: <https://docs.rs/boring>
- tokio TLS adapters: <https://docs.rs/tokio-boring>

View File

@ -43,7 +43,7 @@ include = [
]
[package.metadata.docs.rs]
features = ["rpk", "underscore-wildcards"]
features = ["rpk", "underscore-wildcards", "mlkem"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
@ -58,6 +58,9 @@ fips = []
# Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250)
rpk = []
# Require mlkem.h
mlkem = []
# Applies a patch (`patches/underscore-wildcards.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

View File

@ -3,7 +3,9 @@
[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.
It supports [FIPS-compatible builds of BoringSSL](https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/FIPS.md),
as well as [Post-Quantum crypto](https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/)
and [Raw Public Key](https://docs.rs/boring/latest/boring/ssl/struct.SslRef.html#method.peer_pubkey) extensions.
To use BoringSSL from Rust, prefer the [higher-level safe API](https://docs.rs/boring).

View File

@ -12,6 +12,7 @@ pub(crate) struct Config {
pub(crate) target_os: String,
pub(crate) unix: bool,
pub(crate) target_env: String,
pub(crate) target_features: Vec<String>,
pub(crate) features: Features,
pub(crate) env: Env,
}
@ -34,9 +35,6 @@ pub(crate) struct Env {
pub(crate) android_ndk_home: Option<PathBuf>,
pub(crate) cmake_toolchain_file: Option<PathBuf>,
pub(crate) cpp_runtime_lib: Option<OsString>,
/// C compiler (ignored if using FIPS)
pub(crate) cc: Option<OsString>,
pub(crate) cxx: Option<OsString>,
pub(crate) docs_rs: bool,
}
@ -51,6 +49,12 @@ impl Config {
let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
let unix = env::var("CARGO_CFG_UNIX").is_ok();
let target_features = env::var("CARGO_CFG_TARGET_FEATURE")
.unwrap_or_default()
.split(',')
.map(|s| s.to_owned())
.collect();
let features = Features::from_env();
let env = Env::from_env(&host, &target, features.is_fips_like());
@ -69,6 +73,7 @@ impl Config {
target_os,
unix,
target_env,
target_features,
features,
env,
};
@ -126,15 +131,18 @@ impl Features {
impl Env {
fn from_env(host: &str, target: &str, is_fips_like: bool) -> Self {
let var_prefix = if host == target { "HOST" } else { "TARGET" };
let target_with_underscores = target.replace('-', "_");
let target_only_var = |name: &str| {
// Logic stolen from cmake-rs.
let target_var = |name: &str| {
let kind = if host == target { "HOST" } else { "TARGET" };
// TODO(rmehra): look for just `name` first, as most people just set that
var(&format!("{name}_{target}"))
.or_else(|| var(&format!("{name}_{target_with_underscores}")))
.or_else(|| var(&format!("{var_prefix}_{name}")))
.or_else(|| var(&format!("{kind}_{name}")))
.or_else(|| var(name))
};
let target_var = |name: &str| target_only_var(name).or_else(|| var(name));
let boringssl_var = |name: &str| {
const BORING_BSSL_PREFIX: &str = "BORING_BSSL_";
@ -171,9 +179,6 @@ impl Env {
android_ndk_home: target_var("ANDROID_NDK_HOME").map(Into::into),
cmake_toolchain_file: target_var("CMAKE_TOOLCHAIN_FILE").map(Into::into),
cpp_runtime_lib: target_var("BORING_BSSL_RUST_CPPLIB"),
// matches the `cc` crate
cc: target_only_var("CC"),
cxx: target_only_var("CXX"),
docs_rs: var("DOCS_RS").is_some(),
}
}

View File

@ -161,7 +161,7 @@ fn get_boringssl_source_path(config: &Config) -> &Path {
/// MSVC generator on Windows place static libs in a target sub-folder,
/// so adjust library location based on platform and build target.
/// See issue: <https://github.com/alexcrichton/cmake-rs/issues/18>
fn get_boringssl_platform_output_path(config: &Config) -> String {
fn msvc_lib_subdir(config: &Config) -> Option<&'static str> {
if config.target.ends_with("-msvc") {
// Code under this branch should match the logic in cmake-rs
let debug_env_var = config
@ -195,9 +195,9 @@ fn get_boringssl_platform_output_path(config: &Config) -> String {
_ => panic!("Unknown OPT_LEVEL={opt_env_var:?} env var."),
};
subdir.to_string()
Some(subdir)
} else {
String::new()
None
}
}
@ -217,7 +217,11 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
// 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.target_features.iter().any(|f| f == "crt-static") {
boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded");
} else {
boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreadedDLL");
}
}
if config.host == config.target {
@ -232,15 +236,6 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
.define("CMAKE_ASM_COMPILER_TARGET", &config.target);
}
if !config.features.fips {
if let Some(cc) = &config.env.cc {
boringssl_cmake.define("CMAKE_C_COMPILER", cc);
}
if let Some(cxx) = &config.env.cxx {
boringssl_cmake.define("CMAKE_CXX_COMPILER", cxx);
}
}
if let Some(sysroot) = &config.env.sysroot {
boringssl_cmake.define("CMAKE_SYSROOT", sysroot);
}
@ -532,7 +527,7 @@ fn run_command(command: &mut Command) -> io::Result<Output> {
Ok(out)
}
fn built_boring_source_path(config: &Config) -> &PathBuf {
fn build_boringssl_or_get_prebuilt(config: &Config) -> &Path {
static BUILD_SOURCE_PATH: OnceLock<PathBuf> = OnceLock::new();
BUILD_SOURCE_PATH.get_or_init(|| {
@ -562,7 +557,13 @@ fn built_boring_source_path(config: &Config) -> &PathBuf {
}
cfg.build_target("ssl").build();
cfg.build_target("crypto").build()
let path = cfg.build_target("crypto").build();
let build_dir = path.join("build");
if build_dir.exists() {
build_dir
} else {
path
}
})
}
@ -589,36 +590,23 @@ fn main() {
}
fn emit_link_directives(config: &Config) {
let bssl_dir = built_boring_source_path(config);
let build_path = get_boringssl_platform_output_path(config);
let bssl_dir = build_boringssl_or_get_prebuilt(config);
let msvc_lib_subdir = msvc_lib_subdir(config);
if config.is_bazel || (config.features.is_fips_like() && config.env.path.is_some()) {
println!(
"cargo:rustc-link-search=native={}/lib/{}",
bssl_dir.display(),
build_path
);
} else {
// todo(rmehra): clean this up, I think these are pretty redundant
println!(
"cargo:rustc-link-search=native={}/build/crypto/{}",
bssl_dir.display(),
build_path
);
println!(
"cargo:rustc-link-search=native={}/build/ssl/{}",
bssl_dir.display(),
build_path
);
println!(
"cargo:rustc-link-search=native={}/build/{}",
bssl_dir.display(),
build_path
);
println!(
"cargo:rustc-link-search=native={}/build",
bssl_dir.display(),
);
let subdirs =
if config.is_bazel || (config.features.is_fips_like() && config.env.path.is_some()) {
&["lib"][..]
} else {
&["lib", "crypto", "ssl", ""][..]
};
for subdir in subdirs {
let dir = bssl_dir.join(subdir);
let dir = msvc_lib_subdir
.map(|s| dir.join(s))
.filter(|d| d.exists())
.unwrap_or(dir);
println!("cargo:rustc-link-search=native={}", dir.display());
}
if let Some(cpp_lib) = get_cpp_runtime_lib(config) {
@ -704,7 +692,7 @@ fn generate_bindings(config: &Config) {
}
}
let headers = [
let must_have_headers = [
"aes.h",
"asn1_mac.h",
"asn1t.h",
@ -720,6 +708,12 @@ fn generate_bindings(config: &Config) {
"err.h",
"hkdf.h",
"hpke.h",
"ossl_typ.h",
"pkcs12.h",
"poly1305.h",
"x509v3.h",
];
let headers = [
"hmac.h",
"hrss.h",
"md4.h",
@ -728,26 +722,25 @@ fn generate_bindings(config: &Config) {
"obj_mac.h",
"objects.h",
"opensslv.h",
"ossl_typ.h",
"pkcs12.h",
"poly1305.h",
"rand.h",
"rc4.h",
"ripemd.h",
"siphash.h",
"srtp.h",
"trust_token.h",
"x509v3.h",
];
for header in &headers {
for (i, header) in must_have_headers.into_iter().chain(headers).enumerate() {
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());
if header_path.exists() {
builder = builder.header(header_path.to_str().unwrap());
} else {
let err = format!("'openssl/{header}' is missing from '{}'. The include path may be incorrect or contain an outdated version of OpenSSL/BoringSSL", include_path.display());
let required = i < must_have_headers.len();
println!(
"cargo::{}={err}",
if required { "error" } else { "warning" }
);
}
}
let bindings = builder.generate().expect("Unable to generate bindings");

View File

@ -30,6 +30,8 @@ pub use generated::{ssl_compliance_policy_t, ERR_add_error_data, SSL_set1_groups
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 = "mlkem")]
pub use generated::{MLKEM768_encap, MLKEM768_private_key_from_seed}; // your include path is incorrect or has a version of boringssl without mlkem 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

View File

@ -17,7 +17,7 @@ features = ["rpk", "underscore-wildcards"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
# Controlling the build
default = []
# Use a FIPS-validated version of BoringSSL.
fips = ["boring-sys/fips"]
@ -29,12 +29,18 @@ legacy-compat-deprecated = []
# PQ is always enabled. This feature is a no-op, only for backwards compatibility.
pq-experimental = []
# Interface for ML-KEM (FIPS 203) post-quantum key encapsulation. Does not affect ciphers used in TLS.
mlkem = []
# SslCredential API
credential = []
# Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250)
# 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`.
rpk = ["boring-sys/rpk"]
rpk = ["credential", "boring-sys/rpk"]
# 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
@ -43,6 +49,10 @@ rpk = ["boring-sys/rpk"]
# `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`.
underscore-wildcards = ["boring-sys/underscore-wildcards"]
# **DO NOT USE** This will be removed without warning in future releases.
# Alias for 'fips', only for backwards compatibility.
fips-precompiled = ["fips"]
[dependencies]
bitflags = { workspace = true }
foreign-types = { workspace = true }

View File

@ -603,27 +603,37 @@ impl Asn1ObjectRef {
pub fn nid(&self) -> Nid {
unsafe { Nid::from_raw(ffi::OBJ_obj2nid(self.as_ptr())) }
}
}
impl fmt::Display for Asn1ObjectRef {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
/// Returns the numerical string OID of this object.
///
/// This corresponds to [`OBJ_obj2txt`] with `no_name = 1`.
///
/// [`OBJ_obj2txt`]: https://www.openssl.org/docs/man1.1.1/man3/OBJ_obj2txt.html
pub fn oid_string(&self) -> String {
self.to_text(true)
}
// 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 = [0u8; 80];
let len = ffi::OBJ_obj2txt(
buf.as_mut_ptr().cast(),
buf.len() as c_int,
self.as_ptr(),
0,
no_name as c_int,
);
fmt.write_str(
buf.get(..len as usize)
.and_then(|s| str::from_utf8(s).ok())
.unwrap_or("error"),
)
String::from_utf8_lossy(&buf[..len as usize]).into_owned()
}
}
}
impl fmt::Display for Asn1ObjectRef {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(&self.to_text(false))
}
}
impl fmt::Debug for Asn1ObjectRef {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(self.to_string().as_str())
@ -723,4 +733,13 @@ mod tests {
.map(|object| object.to_string())
.expect_err("parsing invalid OID should fail");
}
#[test]
fn object_to_text() {
let oid = "2.16.840.1.101.3.4.2.1";
let object = Asn1Object::from_str(oid).unwrap();
assert_eq!(object.to_text(false), Nid::SHA256.long_name().unwrap());
assert_eq!(object.to_text(true), oid.to_string());
assert_eq!(object.oid_string(), oid.to_string());
}
}

View File

@ -137,6 +137,7 @@ pub mod hash;
pub mod hmac;
pub mod hpke;
pub mod memcmp;
#[cfg(feature = "mlkem")]
pub mod mlkem;
pub mod nid;
pub mod pkcs12;

View File

@ -91,14 +91,14 @@ impl MlKemPrivateKey {
pub fn generate(algorithm: Algorithm) -> Result<(MlKemPublicKey, MlKemPrivateKey), ErrorStack> {
match algorithm {
Algorithm::MlKem768 => {
let (pk, sk) = MlKem768PrivateKey::generate();
let (pk, sk) = MlKem768PrivateKey::generate()?;
Ok((
MlKemPublicKey(Either::MlKem768(pk)),
MlKemPrivateKey(Either::MlKem768(sk)),
))
}
Algorithm::MlKem1024 => {
let (pk, sk) = MlKem1024PrivateKey::generate();
let (pk, sk) = MlKem1024PrivateKey::generate()?;
Ok((
MlKemPublicKey(Either::MlKem1024(pk)),
MlKemPrivateKey(Either::MlKem1024(sk)),
@ -222,39 +222,27 @@ 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>) {
fn generate() -> Result<(Box<MlKem768PublicKey>, Box<MlKem768PrivateKey>), ErrorStack> {
// 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 bytes = [0; MlKem768PublicKey::PUBLIC_KEY_BYTES];
let mut seed = [0; PRIVATE_KEY_SEED_BYTES];
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(),
bytes.as_mut_ptr().cast(),
seed.as_mut_ptr(),
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(),
}),
Ok((
Box::new(MlKem768PublicKey::from_slice(&bytes)?),
Box::new(MlKem768PrivateKey {
seed: seed.assume_init(),
seed,
expanded: expanded.assume_init(),
}),
)
))
}
}
@ -439,39 +427,27 @@ 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>) {
fn generate() -> Result<(Box<MlKem1024PublicKey>, Box<MlKem1024PrivateKey>), ErrorStack> {
// 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 bytes = [0; MlKem1024PublicKey::PUBLIC_KEY_BYTES];
let mut seed = [0; PRIVATE_KEY_SEED_BYTES];
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(),
bytes.as_mut_ptr().cast(),
seed.as_mut_ptr(),
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(),
}),
Ok((
Box::new(MlKem1024PublicKey::from_slice(&bytes)?),
Box::new(MlKem1024PrivateKey {
seed: seed.assume_init(),
seed,
expanded: expanded.assume_init(),
}),
)
))
}
}
@ -649,7 +625,7 @@ mod tests {
#[test]
fn roundtrip() {
let (pk, sk) = <$priv>::generate();
let (pk, sk) = <$priv>::generate().unwrap();
let (ct, ss1) = pk.encapsulate();
let ss2 = sk.decapsulate(&ct);
assert_eq!(ss1, ss2);
@ -657,7 +633,7 @@ mod tests {
#[test]
fn seed_roundtrip() {
let (pk, sk) = <$priv>::generate();
let (pk, sk) = <$priv>::generate().unwrap();
let sk2 = <$priv>::from_seed(&sk.seed).unwrap();
let (ct, ss1) = pk.encapsulate();
let ss2 = sk2.decapsulate(&ct);
@ -666,7 +642,7 @@ mod tests {
#[test]
fn derive_pubkey() {
let (pk, sk) = <$priv>::generate();
let (pk, sk) = <$priv>::generate().unwrap();
assert_eq!(pk.bytes, sk.public_key().unwrap().bytes);
}
@ -678,14 +654,14 @@ mod tests {
#[test]
fn from_slice_roundtrip() {
let (pk, _) = <$priv>::generate();
let (pk, _) = <$priv>::generate().unwrap();
let pk2 = <$pub>::from_slice(&pk.bytes).unwrap();
assert_eq!(pk.bytes, pk2.bytes);
}
#[test]
fn implicit_rejection() {
let (_, sk) = <$priv>::generate();
let (_, sk) = <$priv>::generate().unwrap();
let bad_ct = [0x42u8; $ct_len];
// bad ciphertext still "works", just returns deterministic garbage
let ss1 = sk.decapsulate(&bad_ct);
@ -695,7 +671,7 @@ mod tests {
#[test]
fn debug_redacts_seed() {
let (_, sk) = <$priv>::generate();
let (_, sk) = <$priv>::generate().unwrap();
let dbg = format!("{:?}", sk);
assert!(dbg.contains("redacted"));
}

View File

@ -10,7 +10,7 @@ use std::ptr;
use crate::error::ErrorStack;
use crate::nid::Nid;
use crate::pkey::{HasPrivate, PKey, PKeyRef, Private};
use crate::stack::Stack;
use crate::stack::{Stack, StackRef};
use crate::x509::{X509Ref, X509};
use crate::{cvt_0i, cvt_p};
@ -31,33 +31,43 @@ impl Pkcs12Ref {
ffi::i2d_PKCS12
}
/// Extracts the contents of the `Pkcs12`.
/// Extracts the contents of the `Pkcs12` with `pkey` and `cert` required.
pub fn parse(&self, pass: &str) -> Result<ParsedPkcs12, ErrorStack> {
let p2 = self.parse2(pass)?;
Ok(ParsedPkcs12 {
pkey: p2
.pkey
.ok_or_else(|| ErrorStack::internal_error_str("missing pkey"))?,
cert: p2
.cert
.ok_or_else(|| ErrorStack::internal_error_str("missing cert"))?,
chain: p2.ca,
})
}
/// Extracts the contents of the `Pkcs12` with `pkey` and `cert` optional.
#[corresponds(PKCS12_parse)]
pub fn parse2(&self, pass: &str) -> Result<ParsedPkcs12_2, ErrorStack> {
unsafe {
let pass = CString::new(pass.as_bytes()).map_err(ErrorStack::internal_error)?;
let mut pkey = ptr::null_mut();
let mut cert = ptr::null_mut();
let mut chain = ptr::null_mut();
let mut ca = ptr::null_mut();
cvt_0i(ffi::PKCS12_parse(
self.as_ptr(),
pass.as_ptr(),
&mut pkey,
&mut cert,
&mut chain,
&mut ca,
))?;
let pkey = PKey::from_ptr(pkey);
let cert = X509::from_ptr(cert);
let pkey = (!pkey.is_null()).then(|| PKey::from_ptr(pkey));
let cert = (!cert.is_null()).then(|| X509::from_ptr(cert));
let ca = (!ca.is_null()).then(|| Stack::from_ptr(ca));
let chain = if chain.is_null() {
None
} else {
Some(Stack::from_ptr(chain))
};
Ok(ParsedPkcs12 { pkey, cert, chain })
Ok(ParsedPkcs12_2 { pkey, cert, ca })
}
}
}
@ -100,6 +110,19 @@ pub struct ParsedPkcs12 {
pub chain: Option<Stack<X509>>,
}
/// [`ParsedPkcs12`] with optional fields
pub struct ParsedPkcs12_2 {
pub pkey: Option<PKey<Private>>,
pub cert: Option<X509>,
pub ca: Option<Stack<X509>>,
}
impl ParsedPkcs12_2 {
pub fn chain(&self) -> Option<&StackRef<X509>> {
self.ca.as_deref()
}
}
pub struct Pkcs12Builder {
nid_key: Nid,
nid_cert: Nid,

View File

@ -4,8 +4,10 @@ use super::{
Ssl, SslAlert, SslContextBuilder, SslRef, SslSession, SslSignatureAlgorithm, SslVerifyError,
SslVerifyMode,
};
#[cfg(feature = "credential")]
use crate::error::ErrorStack;
use crate::ex_data::Index;
#[cfg(feature = "credential")]
use crate::ssl::SslCredentialBuilder;
use std::convert::identity;
use std::future::Future;
@ -173,6 +175,7 @@ impl SslContextBuilder {
}
}
#[cfg(feature = "credential")]
impl SslCredentialBuilder {
/// Configures a custom private key method on the context.
///

View File

@ -109,6 +109,7 @@ pub use self::async_callbacks::{
pub use self::connector::{
ConnectConfiguration, SslAcceptor, SslAcceptorBuilder, SslConnector, SslConnectorBuilder,
};
#[cfg(feature = "credential")]
pub use self::credential::{SslCredential, SslCredentialBuilder, SslCredentialRef};
pub use self::ech::{SslEchKeys, SslEchKeysRef};
pub use self::error::{Error, ErrorCode, HandshakeError};
@ -117,6 +118,7 @@ mod async_callbacks;
mod bio;
mod callbacks;
mod connector;
#[cfg(feature = "credential")]
mod credential;
mod ech;
mod error;
@ -1270,15 +1272,26 @@ impl SslContextBuilder {
/// The file should contain a sequence of PEM-formatted CA certificates.
#[corresponds(SSL_CTX_load_verify_locations)]
pub fn set_ca_file<P: AsRef<Path>>(&mut self, file: P) -> Result<(), ErrorStack> {
self.load_verify_locations(Some(file.as_ref()), None)
}
/// Loads trusted root certificates from a file and/or a directory.
#[corresponds(SSL_CTX_load_verify_locations)]
pub fn load_verify_locations(
&mut self,
ca_file: Option<&Path>,
ca_path: Option<&Path>,
) -> Result<(), ErrorStack> {
self.ctx.check_x509();
let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes())
.map_err(ErrorStack::internal_error)?;
let ca_file = ca_file.map(path_to_cstring).transpose()?;
let ca_path = ca_path.map(path_to_cstring).transpose()?;
unsafe {
cvt(ffi::SSL_CTX_load_verify_locations(
self.as_ptr(),
file.as_ptr(),
ptr::null(),
ca_file.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
ca_path.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
))
}
}
@ -1339,8 +1352,7 @@ impl SslContextBuilder {
) -> Result<(), ErrorStack> {
self.ctx.check_x509();
let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes())
.map_err(ErrorStack::internal_error)?;
let file = path_to_cstring(file.as_ref())?;
unsafe {
cvt(ffi::SSL_CTX_use_certificate_file(
self.as_ptr(),
@ -1360,8 +1372,7 @@ impl SslContextBuilder {
&mut self,
file: P,
) -> Result<(), ErrorStack> {
let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes())
.map_err(ErrorStack::internal_error)?;
let file = path_to_cstring(file.as_ref())?;
unsafe {
cvt(ffi::SSL_CTX_use_certificate_chain_file(
self.as_ptr(),
@ -1401,8 +1412,7 @@ impl SslContextBuilder {
file: P,
file_type: SslFiletype,
) -> Result<(), ErrorStack> {
let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes())
.map_err(ErrorStack::internal_error)?;
let file = path_to_cstring(file.as_ref())?;
unsafe {
cvt(ffi::SSL_CTX_use_PrivateKey_file(
self.as_ptr(),
@ -2029,6 +2039,7 @@ impl SslContextBuilder {
/// Adds a credential.
#[corresponds(SSL_CTX_add1_credential)]
#[cfg(feature = "credential")]
pub fn add_credential(&mut self, credential: &SslCredentialRef) -> Result<(), ErrorStack> {
unsafe {
cvt_0i(ffi::SSL_CTX_add1_credential(
@ -3816,6 +3827,7 @@ impl SslRef {
/// Adds a credential.
#[corresponds(SSL_add1_credential)]
#[cfg(feature = "credential")]
pub fn add_credential(&mut self, credential: &SslCredentialRef) -> Result<(), ErrorStack> {
unsafe { cvt_0i(ffi::SSL_add1_credential(self.as_ptr(), credential.as_ptr())).map(|_| ()) }
}
@ -4570,3 +4582,7 @@ unsafe fn get_new_idx(f: ffi::CRYPTO_EX_free) -> c_int {
unsafe fn get_new_ssl_idx(f: ffi::CRYPTO_EX_free) -> c_int {
ffi::SSL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f)
}
fn path_to_cstring(path: &Path) -> Result<CString, ErrorStack> {
CString::new(path.as_os_str().as_encoded_bytes()).map_err(ErrorStack::internal_error)
}

View File

@ -317,13 +317,13 @@ 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();

View File

@ -257,6 +257,16 @@ impl<T: Stackable> StackRef<T> {
}
}
impl<T> fmt::Debug for StackRef<T>
where
T: Stackable,
T::Ref: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_list().entries(self).finish()
}
}
impl<T: Stackable> Index<usize> for StackRef<T> {
type Output = T::Ref;

509
boring/src/x509/crl.rs Normal file
View File

@ -0,0 +1,509 @@
//! Certificate revocation lists describe certificates that have been revoked
//! by their issuer and should no longer be trusted.
//!
//! An `X509CRL` can be provided along with an issuing `X509` to verify that
//! issued certificates have not been revoked.
//!
//! # Example
//!
//! ```rust
//! use boring::hash::MessageDigest;
//! use boring::pkey::{PKey, Private};
//! use boring::x509::crl::{X509CRLBuilder, X509Revoked};
//! use boring::x509::extension::BasicConstraints;
//! use boring::x509::verify::X509VerifyFlags;
//! use boring::x509::X509Extension;
//! use boring::x509::X509;
//! use boring::x509::store::{X509Store, X509StoreBuilder};
//! use boring::asn1::Asn1Time;
//! use boring::bn::BigNum;
//! use boring::error::ErrorStack;
//!
//! fn crl_checking_store(issuer: X509, pkey: PKey<Private>) -> Result<X509Store, ErrorStack> {
//! let mut builder = X509CRLBuilder::new()?;
//! builder.set_issuer_name(issuer.subject_name())?;
//! builder.add_revoked(X509Revoked::from_parts(
//! &*BigNum::from_u32(1)?.to_asn1_integer()?,
//! &*Asn1Time::days_from_now(0)?
//! )?)?;
//! builder.set_last_update(&*Asn1Time::days_from_now(0)?)?;
//! builder.set_next_update(&*Asn1Time::days_from_now(30)?)?;
//! builder.sign(&pkey, MessageDigest::sha256())?;
//!
//! let mut store_builder = X509StoreBuilder::new()?;
//! store_builder.add_cert(issuer)?;
//! store_builder.add_crl(builder.build())?;
//! store_builder
//! .param_mut()
//! .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
//! Ok(store_builder.build())
//! }
//! ```
use crate::asn1::{Asn1BitStringRef, Asn1IntegerRef, Asn1TimeRef};
use crate::foreign_types::ForeignType;
use crate::foreign_types::ForeignTypeRef;
use crate::hash::{DigestBytes, MessageDigest};
use crate::pkey::{HasPrivate, HasPublic, PKeyRef};
use crate::stack::{StackRef, Stackable};
use crate::x509::X509ExtensionRef;
use crate::x509::{X509AlgorithmRef, X509Extension, X509NameRef};
use crate::{cvt, cvt_n, cvt_p, ErrorStack};
use std::convert::TryInto;
use std::fmt::Formatter;
use std::{fmt, mem, ptr};
foreign_type_and_impl_send_sync! {
type CType = ffi::X509_REVOKED;
fn drop = ffi::X509_REVOKED_free;
/// An `X509_REVOKED` containing information about a revoked certificate
pub struct X509Revoked;
}
impl Stackable for X509Revoked {
type StackType = ffi::stack_st_X509_REVOKED;
}
impl X509Revoked {
/// Create an `X509Revoked`
///
/// This corresponds to [`X509_REVOKED_new`] followed by calls to
/// [`X509_REVOKED_set_serialNumber`] and [`X509_REVOKED_set_revocationDate`]
/// with the provided parameters
///
/// [`X509_REVOKED_new`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_new.html
/// [`X509_REVOKED_set_serialNumber`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_set_serialNumber.html
/// [`X509_REVOKED_set_revocationDate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_set_revocationDate.html
pub fn from_parts(
serial_number: &Asn1IntegerRef,
revocation_date: &Asn1TimeRef,
) -> Result<X509Revoked, ErrorStack> {
unsafe {
ffi::init();
let revoked = cvt_p(ffi::X509_REVOKED_new())?;
cvt(ffi::X509_REVOKED_set_serialNumber(
revoked,
serial_number.as_ptr(),
))?;
cvt(ffi::X509_REVOKED_set_revocationDate(
revoked,
revocation_date.as_ptr(),
))?;
Ok(X509Revoked::from_ptr(revoked))
}
}
}
impl X509RevokedRef {
/// Returns the serial number of the revoked certificate
///
/// This corresponds to [`X509_REVOKED_get0_serialNumber`].
///
/// [`X509_REVOKED_get0_serialNumber`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_get0_serialNumber.html
pub fn serial_number(&self) -> &Asn1IntegerRef {
unsafe {
let r = ffi::X509_REVOKED_get0_serialNumber(self.as_ptr());
assert!(!r.is_null());
Asn1IntegerRef::from_ptr(r as *mut _)
}
}
/// Returns certificate's revocation date
///
/// This corresponds to [`X509_REVOKED_get0_revocationDate`].
///
/// [`X509_REVOKED_get0_revocationDate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_get0_revocationDate
pub fn revocation_date(&self) -> &Asn1TimeRef {
unsafe {
let date = ffi::X509_REVOKED_get0_revocationDate(self.as_ptr());
assert!(!date.is_null());
Asn1TimeRef::from_ptr(date as *mut _)
}
}
}
impl fmt::Debug for X509RevokedRef {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
let sn = self.serial_number().to_bn().and_then(|bn| bn.to_hex_str());
let sn = sn.as_ref().map(|x| &***x).unwrap_or("");
fmt.debug_struct("X509Revoked")
.field("serial_number", &sn)
.field("revocation_date", self.revocation_date())
.finish()
}
}
foreign_type_and_impl_send_sync! {
type CType = ffi::X509_CRL;
fn drop = ffi::X509_CRL_free;
/// An `X509CRL` certificate revocation list
pub struct X509CRL;
}
impl Stackable for X509CRL {
type StackType = ffi::stack_st_X509_CRL;
}
impl X509CRL {
from_pem! {
/// Deserializes a PEM-encoded X509CRL structure.
///
/// The input should have a header of `-----BEGIN X509 CRL-----`
///
/// This corresponds to [`PEM_read_bio_X509_CRL`].
///
/// [`PEM_read_bio_X509_CRL`]: https://www.openssl.org/docs/man1.1.1/man3/PEM_read_bio_X509_CRL
from_pem,
X509CRL,
ffi::PEM_read_bio_X509_CRL
}
from_der! {
/// Deserializes a DER-encoded X509 structure.
///
/// This corresponds to [`d2i_X509_CRL`].
///
/// [`d2i_X509_CRL`]: https://www.openssl.org/docs/man1.1.1/man3/d2i_X509_CRL.html
from_der,
X509CRL,
ffi::d2i_X509_CRL,
::libc::c_long
}
}
impl X509CRLRef {
/// Returns the CRL's last update time
///
/// This corresponds to [`X509_CRL_get0_lastUpdate`]
///
/// [`X509_CRL_get0_lastUpdate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get0_lastUpdate
pub fn last_update(&self) -> Option<&Asn1TimeRef> {
unsafe {
let date = ffi::X509_CRL_get0_lastUpdate(self.as_ptr());
if date.is_null() {
None
} else {
Some(Asn1TimeRef::from_ptr(date as *mut _))
}
}
}
/// Returns the CRL's next update time
///
/// This corresponds to [`X509_CRL_get0_nextUpdate`]
///
/// [`X509_CRL_get0_nextUpdate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get0_nextUpdate
pub fn next_update(&self) -> Option<&Asn1TimeRef> {
unsafe {
let date = ffi::X509_CRL_get0_nextUpdate(self.as_ptr());
if date.is_null() {
None
} else {
Some(Asn1TimeRef::from_ptr(date as *mut _))
}
}
}
/// Returns the CRL's issuer name
///
/// This corresponds to [`X509_CRL_get_issuer`]
///
/// [`X509_CRL_get_issuer`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get_issuer
pub fn issuer(&self) -> &X509NameRef {
unsafe {
let name = ffi::X509_CRL_get_issuer(self.as_ptr());
assert!(!name.is_null());
X509NameRef::from_ptr(name)
}
}
/// Returns the CRL's extensions
///
/// This corresponds to [`X509_CRL_get0_extensions`]
///
/// [`X509_CRL_get0_extensions`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get0_extensions
pub fn extensions(&self) -> Option<&StackRef<X509Extension>> {
unsafe {
let extensions = ffi::X509_CRL_get0_extensions(self.as_ptr());
if extensions.is_null() {
None
} else {
Some(StackRef::from_ptr(extensions as *mut _))
}
}
}
/// Returns the revoked certificates in this CRL
///
/// This corresponds to [`X509_CRL_get_REVOKED`]
///
/// [`X509_CRL_get_REVOKED`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get_REVOKED
pub fn revoked(&self) -> Option<&StackRef<X509Revoked>> {
unsafe {
let revoked = ffi::X509_CRL_get_REVOKED(self.as_ptr());
if revoked.is_null() {
None
} else {
Some(StackRef::from_ptr(revoked))
}
}
}
/// Returns the CRL's signature and signature algorithm
///
/// This corresponds to [`X509_CRL_get0_signature`]
///
/// [`X509_CRL_get0_signature`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get0_signature
pub fn signature(&self) -> (&Asn1BitStringRef, &X509AlgorithmRef) {
unsafe {
let mut signature = ptr::null();
let mut algor = ptr::null();
ffi::X509_CRL_get0_signature(self.as_ptr(), &mut signature, &mut algor);
assert!(!algor.is_null());
assert!(!signature.is_null());
(
Asn1BitStringRef::from_ptr(signature as *mut _),
X509AlgorithmRef::from_ptr(algor as *mut _),
)
}
}
/// Returns a digest of the DER representation of the CRL
///
/// This corresponds to [`X509_CRL_digest`]
///
/// [`X509_CRL_digest`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_digest
pub fn digest(&self, hash_type: MessageDigest) -> Result<DigestBytes, ErrorStack> {
unsafe {
let mut digest = DigestBytes {
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();
cvt(ffi::X509_CRL_digest(
self.as_ptr(),
hash_type.as_ptr(),
digest.buf.as_mut_ptr() as *mut _,
&mut len,
))?;
digest.len = len as usize;
Ok(digest)
}
}
/// Check if the CRL is signed using the given public key.
///
/// Only the signature is checked: no other checks (such as certificate chain validity)
/// are performed.
///
/// Returns `true` if verification succeeds.
///
/// This corresponds to [`X509_CRL_verify"].
///
/// [`X509_CRL_verify`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_verify
pub fn verify<T>(&self, key: &PKeyRef<T>) -> Result<bool, ErrorStack>
where
T: HasPublic,
{
unsafe { cvt_n(ffi::X509_CRL_verify(self.as_ptr(), key.as_ptr())).map(|n| n != 0) }
}
to_pem! {
/// Serializes the CRL into a PEM-encoded X509 CRL structure.
///
/// The output will have a header of `-----BEGIN X509 CRL-----`
///
/// This corresponds to [`PEM_write_bio_X509_CRL`]
///
/// [`PEM_write_bio_X509_CRL`]: https://www.openssl.org/docs/man1.1.1/man3/PEM_write_bio_X509_CRL
to_pem,
ffi::PEM_write_bio_X509_CRL
}
to_der! {
/// Serializes the CRL into a DER-encoded X509 CRL structure
///
/// This corresponds to `i2d_X509_CRL`
///
/// [`i2d_X509_CRL`]: https://www.openssl.org/docs/man1.1.1/man3/i2d_X509_CRL
to_der,
ffi::i2d_X509_CRL
}
}
impl ToOwned for X509CRLRef {
type Owned = X509CRL;
fn to_owned(&self) -> X509CRL {
unsafe {
ffi::X509_CRL_up_ref(self.as_ptr());
X509CRL::from_ptr(self.as_ptr())
}
}
}
impl Clone for X509CRL {
fn clone(&self) -> X509CRL {
X509CRLRef::to_owned(self)
}
}
impl fmt::Debug for X509CRLRef {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
let mut debug_struct = formatter.debug_struct("X509CRL");
debug_struct.field("issuer", self.issuer());
debug_struct.field("signature_algorithm", &self.signature().1.object());
if let Some(next_update) = self.next_update() {
debug_struct.field("next_update", next_update);
}
if let Some(last_update) = self.last_update() {
debug_struct.field("last_update", last_update);
}
if let Some(revoked) = self.revoked() {
debug_struct.field("revoked", &revoked);
}
if let Some(extensions) = self.extensions() {
debug_struct.field("extensions", &extensions);
}
debug_struct.finish()
}
}
impl fmt::Debug for X509CRL {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
let x: &X509CRLRef = self;
x.fmt(formatter)
}
}
/// A builder used to construct an `X509CRL`
pub struct X509CRLBuilder(X509CRL);
impl X509CRLBuilder {
/// Creates a new builder.
pub fn new() -> Result<X509CRLBuilder, ErrorStack> {
unsafe {
ffi::init();
cvt_p(ffi::X509_CRL_new()).map(|p| X509CRLBuilder(X509CRL::from_ptr(p)))
}
}
/// Append an `X509Extension` to the certificate revocation list
///
/// This corresponds to [`X509_CRL_add_ext`]
///
/// [`X509_CRL_add_ext`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_add_ext
pub fn append_extension(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> {
unsafe {
// -1 indicates append to end
cvt(ffi::X509_CRL_add_ext(
self.0.as_ptr(),
extension.as_ptr(),
-1,
))?;
Ok(())
}
}
/// Signs the certificate revocation list with a private key.
///
/// This corresponds to [`X509_CRL_sign`]
///
/// [`X509_CRL_sign`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_sign
pub fn sign<T>(&mut self, key: &PKeyRef<T>, hash: MessageDigest) -> Result<(), ErrorStack>
where
T: HasPrivate,
{
unsafe {
cvt(ffi::X509_CRL_sign(
self.0.as_ptr(),
key.as_ptr(),
hash.as_ptr(),
))
.map(|_| ())
}
}
/// Add a revoked certificate to the certificate revocation list
///
/// This corresponds to [`X509_CRL_add0_revoked`]
///
/// [`X509_CRL_add0_revoked`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_add0_revoked
pub fn add_revoked(&mut self, revoked: X509Revoked) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_CRL_add0_revoked(
self.0.as_ptr(),
revoked.as_ptr(),
))?;
mem::forget(revoked);
Ok(())
}
}
/// Sets the issuer name of the certificate revocation list.
///
/// This corresponds to [`X509_CRL_set_issuer_name`]
///
/// [`X509_CRL_set_issuer_name`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_set_issuer_name
pub fn set_issuer_name(&mut self, issuer_name: &X509NameRef) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_CRL_set_issuer_name(
self.0.as_ptr(),
issuer_name.as_ptr(),
))
.map(|_| ())
}
}
/// Sets the version of the certificate revocation list.
///
/// Note that the version is zero-indexed; that is, a certificate corresponding to version 3 of
/// the X.509 standard should pass `2` to this method.
///
/// This corresponds to [`X509_CRL_set_version`]
///
/// [`X509_CRL_set_version`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_set_version
pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::X509_CRL_set_version(self.0.as_ptr(), version.into())).map(|_| ()) }
}
/// Sets the last update time on the certificate revocation list.
///
/// This corresponds to [`X509_CRL_set1_lastUpdate`]
///
/// [`X509_CRL_set1_lastUpdate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_set1_lastUpdate
pub fn set_last_update(&mut self, last_update: &Asn1TimeRef) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_CRL_set1_lastUpdate(
self.0.as_ptr(),
last_update.as_ptr(),
))
.map(|_| ())
}
}
/// Sets the next update time on the certificate revocation list.
///
/// This corresponds to [`X509_CRL_set1_nextUpdate`]
///
/// [`X509_CRL_set1_nextUpdate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_set1_nextUpdate
pub fn set_next_update(&mut self, next_update: &Asn1TimeRef) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_CRL_set1_nextUpdate(
self.0.as_ptr(),
next_update.as_ptr(),
))
.map(|_| ())
}
}
/// Consumes the builder, returning the certificate revocation list
pub fn build(self) -> X509CRL {
self.0
}
}

View File

@ -38,10 +38,12 @@ 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};
use crate::{cvt, cvt_n, cvt_p};
use crate::{ffi, free_data_box};
pub mod crl;
pub mod extension;
pub mod store;
pub mod verify;
@ -127,6 +129,7 @@ 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;
@ -261,6 +264,32 @@ impl X509StoreContextRef {
unsafe { cvt_n(ffi::X509_verify_cert(self.as_ptr())).map(|n| n != 0) }
}
/// Verifies the stored certificate with additional untrusted CRLs
///
/// Returns `true` if verification succeeds. The `error` method will return the specific
/// validation error if the certificate was not valid.
///
/// This will only work inside of a call to `init`.
///
/// This corresponds to [`X509_STORE_CTX_set0_crls`] followed by [`X509_verify_cert`].
///
/// [`X509_STORE_CTX_set0_crls`]: https://www.openssl.org/docs/man1.0.2/crypto/X509_STORE_CTX_set0_crls.html
/// [`X509_verify_cert`]: https://www.openssl.org/docs/man1.0.2/crypto/X509_verify_cert.html
pub fn verify_cert_with_crls(
&mut self,
untrusted_crls: Stack<X509CRL>,
) -> Result<bool, ErrorStack> {
unsafe {
ffi::X509_STORE_CTX_set0_crls(self.as_ptr(), untrusted_crls.as_ptr());
let res = cvt_n(ffi::X509_verify_cert(self.as_ptr())).map(|n| n != 0);
// set0_crls does not take ownership of the stack, so we'll drop and free
// untrusted_crls after this method. null out the crls in ctx to make sure
// no one has a reference to it.
ffi::X509_STORE_CTX_set0_crls(self.as_ptr(), ptr::null_mut());
res
}
}
/// Set the verify result of the context.
#[corresponds(X509_STORE_CTX_set_error)]
pub fn set_error(&mut self, result: X509VerifyResult) {
@ -716,6 +745,21 @@ impl X509Ref {
}
}
/// Returns the extensions of the certificate.
///
/// This corresponds to [`X509_get0_extensions`].
///
/// [`X509_get0_extensions`]: https://www.openssl.org/docs/man1.1.1/man3/X509_get0_extensions.html
pub fn extensions(&self) -> Option<&StackRef<X509Extension>> {
unsafe {
let stack = ffi::X509_get0_extensions(self.as_ptr());
if stack.is_null() {
return None;
}
Some(StackRef::from_ptr(stack as *mut _))
}
}
pub fn check_host(&self, host: &str) -> Result<bool, ErrorStack> {
unsafe {
cvt_n(ffi::X509_check_host(
@ -999,6 +1043,47 @@ impl X509ExtensionRef {
}
}
impl X509ExtensionRef {
/// Returns whether this extension is critical
///
/// This corresponds to [`X509_EXTENSION_get_critical`].
///
/// [`X509_EXTENSION_get_critical`]: https://www.openssl.org/docs/man1.1.1/man3/X509_EXTENSION_get_critical
pub fn critical(&self) -> bool {
unsafe { ffi::X509_EXTENSION_get_critical(self.as_ptr()) != 0 }
}
/// Returns the `Asn1Object` of this `X509Extension`
///
/// This can be used to determine the `Nid` of this extension
///
/// This corresponds to [`X509_EXTENSION_get_object`].
///
/// [`X509_EXTENSION_get_object`]: https://www.openssl.org/docs/man1.1.1/man3/X509_EXTENSION_get_object
pub fn object(&self) -> &Asn1ObjectRef {
unsafe { Asn1ObjectRef::from_ptr(ffi::X509_EXTENSION_get_object(self.as_ptr())) }
}
/// Returns the `data` of this `X509Extension`
///
/// This corresponds to [`X509_EXTENSION_get_data`].
///
/// [`X509_EXTENSION_get_data`]: https://www.openssl.org/docs/man1.1.1/man3/X509_EXTENSION_get_data
pub fn data(&self) -> &Asn1StringRef {
unsafe { Asn1StringRef::from_ptr(ffi::X509_EXTENSION_get_data(self.as_ptr())) }
}
}
impl fmt::Debug for X509ExtensionRef {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter
.debug_struct("X509Extension")
.field("critical", &self.critical())
.field("object_nid", &self.object())
.finish()
}
}
/// A builder used to construct an `X509Name`.
pub struct X509NameBuilder(X509Name);

View File

@ -43,6 +43,7 @@
use crate::error::ErrorStack;
use crate::ffi;
use crate::stack::StackRef;
use crate::x509::crl::X509CRL;
use crate::x509::verify::{X509VerifyFlags, X509VerifyParamRef};
use crate::x509::{X509Object, X509Ref};
use crate::{cvt, cvt_p};
@ -80,10 +81,29 @@ impl X509StoreBuilder {
impl X509StoreBuilderRef {
/// Adds a certificate to the certificate store.
#[corresponds(X509_STORE_add_cert)]
pub fn add_cert(&mut self, cert: &X509Ref) -> Result<(), ErrorStack> {
pub fn add_cert(&mut self, cert: impl AsRef<X509Ref>) -> Result<(), ErrorStack> {
let cert = cert.as_ref();
unsafe { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), cert.as_ptr())) }
}
/// Adds a CRL to the certificate store.
///
/// This corresponds to [`X509_STORE_add_crl`].
///
/// [`X509_STORE_add_crl`]: https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_add_crl
pub fn add_crl(&mut self, crl: X509CRL) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::X509_STORE_add_crl(self.as_ptr(), crl.as_ptr())).map(|_| ()) }
}
/// Returns a mutable reference to the X509 verification configuration.
///
/// This corresponds to [`X509_STORE_get0_param`].
///
/// [`X509_STORE_add_crl`]: https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_get0_param
pub fn param_mut(&mut self) -> &mut X509VerifyParamRef {
unsafe { X509VerifyParamRef::from_ptr_mut(ffi::X509_STORE_get0_param(self.as_ptr())) }
}
/// Load certificates from their default locations.
///
/// These locations are read from the `SSL_CERT_FILE` and `SSL_CERT_DIR`

View File

@ -6,13 +6,15 @@ use crate::hash::MessageDigest;
use crate::nid::Nid;
use crate::pkey::{PKey, Private};
use crate::rsa::Rsa;
use crate::stack::Stack;
use crate::stack::{Stack, Stackable};
use crate::x509::crl::{X509CRLBuilder, X509Revoked, X509CRL};
use crate::x509::extension::{
AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName,
SubjectKeyIdentifier,
};
use crate::x509::store::X509StoreBuilder;
use crate::x509::{X509Extension, X509Name, X509Req, X509StoreContext, X509};
use crate::x509::verify::X509VerifyFlags;
use crate::x509::{X509Extension, X509Name, X509Req, X509StoreContext, X509VerifyError, X509};
mod trusted_first;
@ -21,6 +23,15 @@ fn pkey() -> PKey<Private> {
PKey::from_rsa(rsa).unwrap()
}
fn stack_of<T>(item: T) -> Stack<T>
where
T: Stackable,
{
let mut stack = Stack::new().expect("unable to initialize stack");
stack.push(item).expect("failed to add to stack");
stack
}
#[test]
fn test_cert_loading() {
let cert = include_bytes!("../../../test/cert.pem");
@ -287,6 +298,7 @@ fn x509_builder() {
assert!(pkey.public_eq(&x509.public_key().unwrap()));
assert!(x509.verify(&pkey).unwrap());
assert_eq!(x509.extensions().unwrap().len(), 6);
let cn = x509
.subject_name()
@ -297,6 +309,48 @@ fn x509_builder() {
assert_eq!(serial, x509.serial_number().to_bn().unwrap());
}
#[test]
fn x509_crl_builder() {
let mut builder = X509CRLBuilder::new().unwrap();
let mut name = X509Name::builder().unwrap();
name.append_entry_by_nid(Nid::COMMONNAME, "foobar.com")
.unwrap();
let name = name.build();
builder.set_issuer_name(&name).unwrap();
let mut serial = BigNum::new().unwrap();
serial.rand(128, MsbOption::MAYBE_ZERO, false).unwrap();
let serial_asn = serial.to_asn1_integer().unwrap();
let revoked =
X509Revoked::from_parts(&serial_asn, &Asn1Time::days_from_now(0).unwrap()).unwrap();
builder.add_revoked(revoked).unwrap();
builder
.set_last_update(&Asn1Time::days_from_now(0).unwrap())
.unwrap();
builder
.set_next_update(&Asn1Time::days_from_now(30).unwrap())
.unwrap();
let pkey = pkey();
builder.sign(&pkey, MessageDigest::sha256()).unwrap();
let crl = builder.build();
let cn = crl.issuer().entries_by_nid(Nid::COMMONNAME).next().unwrap();
assert_eq!(cn.data().as_slice(), b"foobar.com");
let revoked_sn = crl
.revoked()
.unwrap()
.iter()
.next()
.unwrap()
.serial_number();
assert_eq!(serial, revoked_sn.to_bn().unwrap());
assert!(crl.verify(&pkey).unwrap());
}
#[test]
fn x509_extension_new() {
assert!(X509Extension::new(None, None, "crlDistributionPoints", "section").is_err());
@ -493,6 +547,278 @@ fn test_verify_fails() {
.unwrap());
}
#[test]
fn test_verify_revoked() {
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(&ca).unwrap();
store_bldr.add_crl(crl).unwrap();
store_bldr
.param_mut()
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
let store = store_bldr.build();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c.verify_cert())
.unwrap());
}
#[test]
fn test_crl_signature() {
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let crl = include_bytes!("../../../test/bad_sig.pem");
let crl = X509CRL::from_pem(crl).unwrap();
assert!(!crl.verify(&ca.public_key().unwrap()).unwrap());
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
assert!(crl.verify(&ca.public_key().unwrap()).unwrap());
}
#[test]
fn test_untrusted_valid_crl() {
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(&ca).unwrap();
store_bldr
.param_mut()
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
store_bldr.param_mut().set_time(1656633600); // 2022-07-01, everything is valid
let store = store_bldr.build();
// cert is not revoked
let crl = include_bytes!("../../../test/empty_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());
// cert is revoked
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let mut context = X509StoreContext::new().unwrap();
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]
fn test_untrusted_invalid_crl() {
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(&ca).unwrap();
store_bldr
.param_mut()
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
store_bldr.param_mut().set_time(1656633600); // 2022-07-01, everything is valid
let store = store_bldr.build();
// this CRL was issued by a different CA (not in the trusted store)
let crl = include_bytes!("../../../test/invalid_crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let mut context = X509StoreContext::new().unwrap();
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();
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]
fn test_revoked_serial_numbers() {
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let cert_sn = cert.serial_number().to_bn().unwrap();
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
assert_eq!(
crl.revoked()
.unwrap()
.iter()
.filter(|revoked| revoked.serial_number().to_bn().unwrap() == cert_sn)
.count(),
1
);
}
#[test]
fn test_serialize_crl() {
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let digest = hex::encode(crl.digest(MessageDigest::sha1()).unwrap());
let serialized = crl.to_pem().unwrap();
let crl_deserialized = X509CRL::from_pem(&serialized).unwrap();
let new_digest = crl_deserialized.digest(MessageDigest::sha1()).unwrap();
assert_eq!(digest, hex::encode(new_digest));
}
#[test]
fn test_crl_extensions() {
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let extensions = crl.extensions();
assert_eq!(
extensions
.unwrap()
.iter()
.filter(|ext| ext.object().nid() == Nid::AUTHORITY_KEY_IDENTIFIER)
.count(),
1
);
assert_eq!(
extensions
.unwrap()
.iter()
.filter(|ext| ext.object().nid() == Nid::CRL_NUMBER)
.count(),
1
);
}
#[test]
fn test_debug_crl() {
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let debugged = format!("{:#?}", crl);
assert!(debugged.contains(r#"countryName = "AU""#));
assert!(debugged.contains(r#"stateOrProvinceName = "Some-State""#));
assert!(debugged.contains(r#"organizationName = "Internet Widgits Pty Ltd""#));
assert!(debugged.contains(r#"last_update: Jun 21 20:22:02 2022 GMT"#));
assert!(debugged.contains(r#"next_update: Jun 18 20:22:02 2032 GMT"#));
assert!(debugged.contains(r#"revocation_date: Jun 21 20:21:55 2022 GMT"#));
assert!(debugged.contains(r#"serial_number: "8771f7bdee982fa5""#));
assert!(debugged.contains(r#"object_nid: X509v3 Authority Key Identifier"#));
assert!(debugged.contains(r#"object_nid: X509v3 CRL Number"#));
}
#[test]
fn test_custom_time_valid() {
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().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();
let mut context = X509StoreContext::new().unwrap();
assert!(context
.init(&store, &cert, &chain, |c| c.verify_cert())
.unwrap());
}
#[test]
fn test_custom_time_expired() {
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().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();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c.verify_cert())
.unwrap());
}
#[test]
fn test_custom_time_too_soon() {
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().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();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c.verify_cert())
.unwrap());
}
#[test]
fn test_custom_time_crl() {
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(&ca).unwrap();
store_bldr
.param_mut()
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
store_bldr.param_mut().set_time(1640995200); // 2022-01-01, before the CRL's issue date
let store = store_bldr.build();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c
.verify_cert_with_crls(stack_of(crl)))
.unwrap());
}
#[test]
fn test_save_subject_der() {
let cert = include_bytes!("../../../test/cert.pem");

11
boring/test/bad_sig.pem Normal file
View File

@ -0,0 +1,11 @@
-----BEGIN X509 CRL-----
MIIBqTCBkjANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwK
U29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkFw0y
MjA2MTYyMjQ0NDFaFw0yMzA2MTYyMjQ0NDFaMBwwGgIJAIdx973umC+lFw0yMjA2
MTYyMjIxMDVaMA0GCSqGSIb3DQEBBQUAA4IBAQApqFdwm46jxkJK8J5kprGm6cp8
b7XMKB1epvhJGIkXHjp7O+2rxYGIExcNlM7jPcwqnUE0E50qGrqSMEupmtaBH03a
fmmDKyhLema7KD64UaERLqWjaW2DPeX9VX6vL4ECc6zTVLxfmYzxVt6A9hhqCm3b
fu8klWczGTa79r/WhTbA7uVf5+OI98da5tlxw+DlAQfqd34L2qq5aFg2dcTGqIdz
3pxP6UlTyj0ZPK3tUtTpIURVO2/MX3j5V+QjWz81UeCv0gQcmOiIVSRUGwi9c6JY
jDqBIvDY6df0riz5is1SS+D94sp1iovBlluwpq4kB8xyDuwt7vblkzleS2YU
-----END X509 CRL-----

13
boring/test/crl.pem Normal file
View File

@ -0,0 +1,13 @@
-----BEGIN X509 CRL-----
MIIB3jCBxwIBATANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJBVTETMBEGA1UE
CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
Fw0yMjA2MjEyMDIyMDJaFw0zMjA2MTgyMDIyMDJaMBwwGgIJAIdx973umC+lFw0y
MjA2MjEyMDIxNTVaoDAwLjAfBgNVHSMEGDAWgBRs06UDqw1fLMmNipyIp4h3uDf9
mjALBgNVHRQEBAICEAEwDQYJKoZIhvcNAQELBQADggEBABgsWr/sVZqXm1AzgGCJ
JBMJW1oUY18aqroxo4kAIoI4QveLmHxi1Wm2I4dqdc6pM09SJhU5v5CfqpJ2BDc0
JBfEk8KKi5O/OYyLcUKa4dEpAlYPgeDyLc6zF8rGLtJoDIYuk4JUeuuByoXt0Sh+
7vx6UzuI7EH+mr4ZjnyAkD3f9jZy+mDcTm/+0REuh4iZ1AotE2YuQWQgxc1Y8TlD
eK+ks1zBKI23s0hPBxJQunmz2k3Uu9Yf+Sg0KxCiDgJZWFiGSw/6DtnT0oYAFGaD
mCyQWtwmS6zGBg+p76wNXkwyJMVvSDgrXSZ55bmImNmA38yKqOLOpB5i+FAS3r4V
ApQ=
-----END X509 CRL-----

11
boring/test/empty_crl.pem Normal file
View File

@ -0,0 +1,11 @@
-----BEGIN X509 CRL-----
MIIBijB0MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApT
b21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQXDTIy
MDYxNjIyNDU1NVoXDTIzMDYxNjIyNDU1NVowDQYJKoZIhvcNAQEFBQADggEBAEtx
nBr3aI0qlegMVJsJn3GfkMzaPVTSTHuw76Dzdl9eGDj0hXzAzZW5k4WBHvaInzNT
NKkeISoQJHLH981R9sQU2zA8sTESLTJGyCFu05Y6XhdmqX4ywmzVRjL6p/aoHNdZ
H1mxgK16wG+Sv0pd+9qNJgC/cNFmNbWzbiEAi5kID4IUxSmId/FZsXsms1EjqDH4
DFIwLIQO/kR5zwE5fZ5EjqUBdAxoSVHfD+OPKl4x2t8CHMmao+ih2FOfd70+NLBD
2oxaJMjZL/SIf8vYxjpjimMR+7yJ5J5P1j/RBfG3LwwUDP0RtWLIvRQo/dZUyXTg
LuC1vNuUoObe12z/NQ4=
-----END X509 CRL-----

View File

@ -0,0 +1,11 @@
-----BEGIN X509 CRL-----
MIIBmTCBggIBATANBgkqhkiG9w0BAQsFADAhMQ0wCwYDVQQKDARGQUtFMRAwDgYD
VQQDDAdGQUtFIENBFw0yMjA2MjcyMTQ3NTBaFw0zMjA2MjQyMTQ3NTBaMBwwGgIJ
AIdx973umC+lFw0yMjA2MjcyMTQ2MjhaoA8wDTALBgNVHRQEBAICEAMwDQYJKoZI
hvcNAQELBQADggEBAMXZRqTG28rnJSUPnVaqkmePSH15iz5Q/e4MdrM0cipXGuzX
z5C8Oh0D2uT3ddawBxTosnbjuzlT7Tanbp3xCRBm9spRPxFbGaFWysBlG1aLTDka
e9t9YeErg7wpwU6Qar0dzkLL5IkW3NArgbe8gP9PkYQxz/B0ESdHIPYJP1YBMNG6
tLgEhg74Xs9UhOBInNsQB8qMGsEeOnzfiuvfspU/yvKHHzvjAcjeIrONLJaZcu2Y
Dsfm5gOXGkHEm5/qJZ/IILoY0GSsSBekCAZda5+v3nvyjfRaBPyhx3Zv+rXh7u5z
77bIzPZP60rslomacgEr4p/Y52E4GmKGV+X2+Hc=
-----END X509 CRL-----

View File

@ -16,6 +16,8 @@ features = []
rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["boring/default", "tokio-boring/default"]
# Use a FIPS-validated version of boringssl.
fips = ["boring/fips", "tokio-boring/fips"]
@ -25,9 +27,9 @@ http = { workspace = true }
hyper = { workspace = true }
hyper-util = { workspace = true, features = ["client", "client-legacy"] }
linked_hash_set = { workspace = true }
boring = { workspace = true }
boring = { workspace = true, default-features = false }
tokio = { workspace = true }
tokio-boring = { workspace = true }
tokio-boring = { workspace = true, default-features = false }
tower-layer = { workspace = true }
tower-service = { workspace = true }

View File

@ -17,6 +17,8 @@ features = ["rpk"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["boring/default"]
# Use a FIPS-validated version of boringssl.
fips = ["boring/fips", "boring-sys/fips"]
@ -24,8 +26,8 @@ fips = ["boring/fips", "boring-sys/fips"]
rpk = ["boring/rpk"]
[dependencies]
boring = { workspace = true }
boring-sys = { workspace = true }
boring = { workspace = true, default-features = false }
boring-sys = { workspace = true, default-features = false }
tokio = { workspace = true }
[dev-dependencies]