Compare commits

...

48 Commits

Author SHA1 Message Date
0x676e67 be22a3940d Merge remote-tracking branch 'upstream/master' 2025-10-29 19:49:47 +08:00
Bas Westerbaan 47c33f6428 pq patch: also enable P256Kyber768Draft00 by default 2025-10-28 10:54:23 +00:00
Bas Westerbaan 410a96752b pq patch: enable PQ by default like upstream
The big diff is misleading. Applying each patch to the base 478b28ab12f
and comparing them, we see:

git range-diff 478b28ab12f2001a03261624261fd041f5439706..adcd4022f75953605a9bf9f6a4a45c0b4fd8ed94 478b28ab12f2001a03261624261fd041f5439706..6f1b1e1f451e61cd2bda0922eecaa8387397ac5a
1:  adcd4022f ! 1:  6f1b1e1f4 Add additional post-quantum key agreements
    @@ Commit message

         This patch adds:

    -    1. Support for MLKEM768X25519 under the codepoint 0x11ec. The version
    -       of BoringSSL we patch against did not support it yet.
    +    1. Support for X25519MLKEM768 under the codepoint 0x11ec. The version
    +       of BoringSSL we patch against did not support it yet. Like recent
    +       upstream, enable by default.

         2. Supports for P256Kyber768Draft00 under 0xfe32, which we temporarily
            need for compliance reasons.  (Note that this is not the codepoint
    @@ ssl/extensions.cc: static bool tls1_check_duplicate_extensions(const CBS *cbs) {
            return true;
          default:
            return false;
    +@@ ssl/extensions.cc: bool ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello,
    + }
    +
    + static const uint16_t kDefaultGroups[] = {
    ++    SSL_GROUP_X25519_MLKEM768,
    +     SSL_GROUP_X25519,
    +     SSL_GROUP_SECP256R1,
    +     SSL_GROUP_SECP384R1,

      ## ssl/ssl_key_share.cc ##
     @@
2025-10-28 10:54:23 +00:00
0x676e67 5ddfb2e097
chore(ssl): remove deprecated code (#98) 2025-10-21 13:15:12 +08:00
0x676e67 d7805d6053
Modify prerelease condition in CI workflow
Updated prerelease condition to exclude 'alpha' branch.
2025-10-21 12:57:44 +08:00
0x676e67 231010c0cb Merge remote-tracking branch 'upstream/master' 2025-10-21 12:35:13 +08:00
Jaap Aarts e23d2d16d4 Update main.rs 2025-10-16 13:31:09 +01:00
Christopher Patton 5cd912df1d Remove "pq-experimental", apply PQ patch by default
Users can override the new default behavior in the usual way. The
expectation is that the build of BoringSSL they provide the feature set
implemented by the patch.
2025-10-15 10:36:27 +01:00
Kornel 77f612c16c Simplify Error::reason() 2025-10-15 10:35:38 +01:00
Kornel 75ef523230 Safer CryptoBufferBuilder::build 2025-10-02 17:55:21 +01:00
Kornel 5957ce94cc ErrorStack ctor for custom errors 2025-10-02 17:55:21 +01:00
Kornel e3998212ed Fix string data conversion in ErrorStack::put() 2025-10-02 17:55:21 +01:00
Apoorv Kothari 353ea62c17 Convert CipherCtx fns into a safe abstraction. Additional testing. 2025-10-01 11:00:57 +01:00
Kornel 8773f0e1fa Use Ref foreign type instead of forgetting 2025-10-01 11:00:57 +01:00
Apoorv Kothari ab8513ef8f Expose a safe Rust interface for the session resumption callback 2025-10-01 11:00:57 +01:00
Kornel ac1d71cb54 Use MaybeUninit for raw_ticket_key key/iv 2025-10-01 11:00:57 +01:00
Apoorv Kothari 5cb35db989 initialize key_name and iv. mark fn as _unsafe to allow for future changes to the api 2025-10-01 11:00:57 +01:00
Apoorv Kothari b9af0ef176 clippy 2025-10-01 11:00:57 +01:00
Apoorv Kothari ba85fbb7ad simplify tests 2025-10-01 11:00:57 +01:00
Apoorv Kothari f526b57daa update documentation 2025-10-01 11:00:57 +01:00
Apoorv Kothari ae783f8273 add test case for TicketKeyCallbackResult::Noop 2025-10-01 11:00:57 +01:00
Apoorv Kothari ea1d120912 pr comments: safety, receive multiple nst, return status refactor 2025-10-01 11:00:57 +01:00
Apoorv Kothari c49282f112 Add set_ticket_key_callback (SSL_CTX_set_tlsext_ticket_key_cb)
Add a wrapper for the `SSL_CTX_set_tlsext_ticket_key_cb`, which allows
consumers to configure the EVP_CIPHER_CTX and HMAC_CTX used for
encrypting/decrypting session tickets.

See https://docs.openssl.org/1.0.2/man3/SSL_CTX_set_tlsext_ticket_key_cb/
for more details.
2025-10-01 11:00:57 +01:00
Alessandro Ghedini b3521e5523 Add SslRef::curve_name() 2025-09-30 16:57:59 +01:00
Kornel 4ce1308e1c Make rpk feature flag additive 2025-09-30 16:45:49 +01:00
Christopher Patton 1c51c7ee3b Add back the `curve()` method on `SslRef`
Instead of returning an `SslCurve`, just return the `u16` returned by
BoringSSL.
2025-09-30 16:14:54 +01:00
Christopher Patton 7078f61077 Remove outdated comments on FIPS API compatibility 2025-09-30 16:14:54 +01:00
Christopher Patton b46d77087e Remove `SslCurve` API
This is incompatible with the latest internal FIPS build. Namely, the
various group identifiers have been renamed since the previous version.
2025-09-30 16:14:54 +01:00
Bas Westerbaan 21735accf8 pq: fix MSVC C4146 warning 2025-09-30 16:22:47 +02:00
Christopher Patton 72dabe1d85 Remove the "kx-*" features
The "kx-*" features control default key exchange preferences. Its
implementation requires disabling APIs for manually setting curve
preferences via `set_curves()` or `set_curves_list()`.

In practice, most teams need to be able to override default preferences
at runtime anyway, which means these features were never really used.
This commit gets rid of them, thereby reducing some complexity in the
API.
2025-09-30 09:36:33 +01:00
Rushil Mehra 646ae33c61 X509Builder::append_extension2 -> X509Builder::append_extension 2025-09-26 17:38:53 +01:00
Rushil Mehra 8abba360d3 `Ssl::new_from_ref` -> `Ssl::new()` 2025-09-26 17:38:53 +01:00
Rushil Mehra 0fc992bd76 Align SslStream APIs with upstream
SslStream::new() is fallible, but `SslStream::from_raw_parts()` and
`SslStreamBuilder::new()` now unwrap. Upstream has also deprecated the
`SslStreamBuilder`, maybe we should do the same.
2025-09-26 17:38:53 +01:00
Alessandro Ghedini 4cb7e260a8 Clean-up legacy FIPS options
Per BoringSSL's FIPS policy, its `main` branch is the "update branch"
for FedRAMP compliance's purposes.

This means that we can stop using a specific BoringSSL branch when
enabling FIPS, as well as a number of hacks that allowed us to build
more recent BoringSSL versions with an older pre-compiled FIPS modules.

This also required slightly updating the main BoringSSL submodule, as
the previous version had an issue when building with the FIPS option
enabled. This is turn required some changes to the PQ patch as well as
some APIs that don't seem to be exposed publicly, as well as changing
some paths in the other patches.

In order to allow a smooth upgrade of internal projects, the `fips-compat`
feature is reduced in scope and renamed to `legacy-compat-deprecated` so
that we can incrementally upgrade internal BoringSSL forks. In practice
this shouldn't really be something anyone else would need, since in
order to work it requires a specific mix of BoringSSL version and
backported patches.
2025-09-26 17:12:23 +01:00
Kornel 78b8ceaf10 Add more reliable library_reason() 2025-09-26 14:17:31 +01:00
Kornel 974c3d2db0 Ensure that ERR_LIB type can be named 2025-09-26 14:17:31 +01:00
Alessandro Ghedini b4bf601394 Remove support for Hyper v0 2025-09-26 13:46:44 +01:00
Kornel c3f33f0ea1 Upgrade deps 2025-09-26 13:34:13 +01:00
Kornel 3116032a83 Skip Rust version detection for bindgen 2025-09-26 13:34:13 +01:00
Kornel 9bad96e48b Style nits 2025-09-26 13:33:19 +01:00
Kornel fa9df8081d Deprecated GHA feature 2025-09-26 13:20:26 +01:00
Kornel 4814eb8547 Ensure rustfmt and clippy are available 2025-09-26 13:20:26 +01:00
Kornel a50a39fde7 Support TARGET_CC and CC_{target} 2025-09-26 10:57:01 +01:00
Kornel 21f2885be3 Fix swapped host/target args 2025-09-26 10:57:01 +01:00
Kornel 79338a99ea CStr UTF-8 improvements 2025-09-26 10:55:46 +01:00
0x676e67 ee94551993
Fix duplicate entry for RPK support in README
Removed duplicate mention of RPK not being supported.
2025-09-22 01:58:11 +08:00
0x676e67 bcc3ccb390
Fix formatting in FUNDING.yml for ko-fi entry 2025-09-22 01:03:48 +08:00
0x676e67 b17dd9d23c
Update FUNDING.yml for sponsorship links 2025-09-22 01:03:17 +08:00
36 changed files with 1072 additions and 1433 deletions

4
.github/FUNDING.yml vendored
View File

@ -3,7 +3,7 @@
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: '0x676e67'
ko_fi:
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
@ -12,4 +12,4 @@ lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cl
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
custom: ['https://github.com/0x676e67/0x676e67/blob/main/SPONSOR.md']

View File

@ -25,8 +25,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: rustup update stable && rustup default stable
- name: Install Rustfmt
run: rustup default stable && rustup component add rustfmt
- name: Check formatting
run: cargo fmt --all -- --check
@ -38,10 +38,11 @@ jobs:
with:
submodules: 'recursive'
- name: Install Rust
run: rustup update stable && rustup default stable
run: rustup update --no-self-update stable && rustup default stable && rustup component add clippy
- name: Get rust version
id: rust-version
run: echo "::set-output name=version::$(rustc --version)"
run: |
echo "version=$(rustc --version)" >> $GITHUB_OUTPUT
- name: Cache cargo index
uses: actions/cache@v4
with:
@ -66,7 +67,7 @@ jobs:
- name: Run clippy
run: cargo clippy --all --all-targets
- name: Check docs
run: cargo doc --no-deps -p boring2 -p boring-sys2 --features pq-experimental,underscore-wildcards
run: cargo doc --no-deps -p boring2 -p boring-sys2 --features underscore-wildcards
env:
DOCS_RS: 1
test:
@ -157,8 +158,8 @@ jobs:
apt_packages: gcc-arm-linux-gnueabi g++-arm-linux-gnueabi
check_only: true
custom_env:
CC: arm-linux-gnueabi-gcc
CXX: arm-linux-gnueabi-g++
CC_arm-unknown-linux-gnueabi: arm-linux-gnueabi-gcc
CXX_arm-unknown-linux-gnueabi: arm-linux-gnueabi-g++
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER: arm-linux-gnueabi-g++
extra_test_args: --workspace --exclude compio-boring2
- thing: aarch64-linux
@ -168,8 +169,8 @@ jobs:
apt_packages: crossbuild-essential-arm64
check_only: true
custom_env:
CC: aarch64-linux-gnu-gcc
CXX: aarch64-linux-gnu-g++
CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc
CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-g++
- thing: arm64-macos
target: aarch64-apple-darwin
@ -214,6 +215,10 @@ jobs:
# run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }}
# shell: bash
- run: rustup target add ${{ matrix.target }}
- 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 }}
@ -272,6 +277,10 @@ jobs:
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable && rustup target add ${{ matrix.target }}
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
@ -291,14 +300,8 @@ jobs:
submodules: 'recursive'
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
- run: cargo test --features pq-experimental
name: Run `pq-experimental` 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 underscore-wildcards
name: Run `underscore-wildcards` tests
- name: Run `underscore-wildcards` tests
run: cargo test --features underscore-wildcards
crates:
name: crates
@ -335,5 +338,5 @@ jobs:
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
generate_release_notes: true

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

@ -26,11 +26,12 @@ tokio-boring = { package = "tokio-boring2", version = "5.0.0-alpha.10", path = "
compio-boring = { package = "compio-boring2", version = "5.0.0-alpha.10", path = "./compio-boring" }
bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] }
bitflags = "2.9"
brotli = "8.0"
bytes = "1"
cmake = "0.1.18"
cmake = "0.1.54"
fs_extra = "1.3.0"
fslock = "0.2"
bitflags = "2.4"
foreign-types = "0.5"
libc = "0.2"
hex = "0.4"
@ -42,6 +43,5 @@ antidote = "1.0.0"
linked_hash_set = "0.1"
openssl-macros = "0.1.1"
autocfg = "1.3.0"
brotli = "8"
compio = { version = "0.16.0" }
compio-io = { version = "0.8.0" }

View File

@ -8,9 +8,9 @@ BoringSSL bindings for the Rust programming language and HTTP client for [wreq](
This package implements only the TLS extensions specification and supports the original [boring](https://github.com/cloudflare/boring) library with the following features:
- RPK is not supported
- Required TLS extensions for Safari and Firefox
- kDHE, ffdhe2048, and ffdhe3072 implementations
- RPK is not supported
- Support for LoongArch P64 and P32 architectures
## Documentation

View File

@ -13,13 +13,12 @@ build = "build/main.rs"
readme = "README.md"
categories = ["cryptography", "external-ffi-bindings"]
edition = { workspace = true }
rust-version = "1.77"
include = [
"/*.md",
"/*.toml",
"/LICENSE-MIT",
"/cmake/*.cmake",
# boringssl (non-FIPS)
"/deps/boringssl/src/util/32-bit-toolchain.cmake",
"/deps/boringssl/**/*.[chS]",
"/deps/boringssl/**/*.asm",
"/deps/boringssl/sources.json",
@ -27,34 +26,18 @@ include = [
"/deps/boringssl/src/crypto/obj/objects.txt",
"/deps/boringssl/src/util/32-bit-toolchain.cmake",
"/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 = ["pq-experimental", "underscore-wildcards"]
features = ["underscore-wildcards"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
@ -66,28 +49,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 = []
# 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]
autocfg = { workspace = true }
bindgen = { workspace = true }
cmake = { workspace = true }
fs_extra = { workspace = true }

View File

@ -16,9 +16,6 @@ pub(crate) struct Config {
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 +24,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>,
@ -36,6 +32,9 @@ 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,10 +50,10 @@ impl Config {
let features = Features::from_env();
let env = Env::from_env(&host, &target, features.is_fips_like());
let mut is_bazel = false;
if let Some(src_path) = &env.source_path {
is_bazel = src_path.join("src").exists();
}
let is_bazel = env
.source_path
.as_ref()
.is_some_and(|path| path.join("src").exists());
let config = Self {
manifest_dir,
@ -78,10 +77,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();
@ -93,9 +88,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;
@ -104,60 +97,41 @@ 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(target: &str, host: &str, is_fips_like: bool) -> Self {
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('-', "_");
// 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
let target_only_var = |name: &str| {
var(&format!("{name}_{target}"))
.or_else(|| var(&format!("{name}_{target_with_underscores}")))
.or_else(|| var(&format!("{kind}_{name}")))
.or_else(|| var(name))
.or_else(|| var(&format!("{var_prefix}_{name}")))
};
let target_var = |name: &str| target_only_var(name).or_else(|| var(name));
let boringssl_var = |name: &str| {
// The passed name is the non-fips version of the environment variable,
@ -175,7 +149,6 @@ impl Env {
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")
.is_some_and(|v| !v.is_empty()),
sysroot: boringssl_var("BORING_BSSL_SYSROOT").map(PathBuf::from),
@ -186,6 +159,9 @@ 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

@ -50,6 +50,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[
&[
("CMAKE_OSX_ARCHITECTURES", "arm64"),
("CMAKE_OSX_SYSROOT", "iphoneos"),
("CMAKE_MACOSX_BUNDLE", "OFF"),
],
),
(
@ -57,6 +58,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[
&[
("CMAKE_OSX_ARCHITECTURES", "arm64"),
("CMAKE_OSX_SYSROOT", "iphonesimulator"),
("CMAKE_MACOSX_BUNDLE", "OFF"),
],
),
(
@ -64,6 +66,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[
&[
("CMAKE_OSX_ARCHITECTURES", "x86_64"),
("CMAKE_OSX_SYSROOT", "iphonesimulator"),
("CMAKE_MACOSX_BUNDLE", "OFF"),
],
),
// macOS
@ -114,11 +117,7 @@ fn get_boringssl_source_path(config: &Config) -> &PathBuf {
static SOURCE_PATH: OnceLock<PathBuf> = OnceLock::new();
SOURCE_PATH.get_or_init(|| {
let submodule_dir = if config.features.fips {
"boringssl-fips"
} else {
"boringssl"
};
let submodule_dir = "boringssl";
let src_path = config.out_dir.join(submodule_dir);
@ -152,7 +151,7 @@ fn get_boringssl_source_path(config: &Config) -> &PathBuf {
///
/// 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
/// See issue: <https://github.com/alexcrichton/cmake-rs/issues/18>
fn get_boringssl_platform_output_path(config: &Config) -> String {
if config.target.ends_with("-msvc") {
// Code under this branch should match the logic in cmake-rs
@ -193,7 +192,7 @@ fn get_boringssl_platform_output_path(config: &Config) -> String {
}
}
/// Returns a new cmake::Config for building BoringSSL.
/// Returns a new `cmake::Config` for building BoringSSL.
///
/// It will add platform-specific parameters if needed.
fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
@ -216,6 +215,15 @@ 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);
}
@ -331,55 +339,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.
@ -475,14 +434,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"
);
}
@ -581,66 +538,17 @@ 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();
@ -699,10 +607,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}");
}
@ -731,12 +635,8 @@ fn generate_bindings(config: &Config) {
}
});
// bindgen 0.70 replaced the run-time layout tests with compile-time ones,
// but they depend on std::mem::offset_of, stabilized in 1.77.
let supports_layout_tests = autocfg::new().probe_rustc_version(1, 77);
let Ok(target_rust_version) = bindgen::RustTarget::stable(68, 0) else {
panic!("bindgen does not recognize target rust version");
};
let target_rust_version =
bindgen::RustTarget::stable(77, 0).expect("bindgen does not recognize target rust version");
let mut builder = bindgen::Builder::default()
.rust_target(target_rust_version) // bindgen MSRV is 1.70, so this is enough
@ -752,7 +652,7 @@ fn generate_bindings(config: &Config) {
.generate_comments(true)
.fit_macro_constants(false)
.size_t_is_usize(true)
.layout_tests(supports_layout_tests)
.layout_tests(config.env.debug.is_some())
.prepend_enum_name(true)
.blocklist_type("max_align_t") // Not supported by bindgen on all targets, not used by BoringSSL
.clang_args(get_extra_clang_args_for_bindgen(config))
@ -779,7 +679,6 @@ fn generate_bindings(config: &Config) {
"des.h",
"dtls1.h",
"hkdf.h",
#[cfg(not(feature = "fips"))]
"hpke.h",
"hmac.h",
"hrss.h",
@ -804,7 +703,24 @@ fn generate_bindings(config: &Config) {
}
let bindings = builder.generate().expect("Unable to generate bindings");
let mut source_code = Vec::new();
bindings
.write_to_file(config.out_dir.join("bindings.rs"))
.expect("Couldn't write bindings!");
.write(Box::new(&mut source_code))
.expect("Couldn't serialize bindings!");
ensure_err_lib_enum_is_named(&mut source_code);
fs::write(config.out_dir.join("bindings.rs"), source_code).expect("Couldn't write bindings!");
}
/// err.h has anonymous `enum { ERR_LIB_NONE = 1 }`, which makes a dodgy `_bindgen_ty_1` name
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))
.unwrap_or("_bindgen_ty_1");
source_code.extend_from_slice(
format!("\n/// Newtype for [`ERR_LIB_SSL`] constants\npub use {enum_type} as ErrLib;\n")
.as_bytes(),
);
}

File diff suppressed because it is too large Load Diff

View File

@ -13,70 +13,25 @@ edition = { workspace = true }
rust-version = "1.80"
[package.metadata.docs.rs]
features = ["pq-experimental", "underscore-wildcards"]
features = ["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"]
# 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();
@ -147,7 +149,7 @@ fn real_main() -> Result<(), ErrorStack> {
match ca_cert.issued(&cert) {
Ok(()) => println!("Certificate verified!"),
Err(ver_err) => println!("Failed to verify certificate: {ver_err}"),
};
}
Ok(())
}
@ -156,5 +158,5 @@ fn main() {
match real_main() {
Ok(()) => println!("Finished."),
Err(e) => println!("Error: {e}"),
};
}
}

View File

@ -63,20 +63,19 @@ foreign_type_and_impl_send_sync! {
impl fmt::Display for Asn1GeneralizedTimeRef {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
unsafe {
let mem_bio = match MemBio::new() {
Err(_) => return f.write_str("error"),
Ok(m) => m,
};
let print_result = cvt(ffi::ASN1_GENERALIZEDTIME_print(
let bio = MemBio::new().ok();
let msg = bio
.as_ref()
.and_then(|mem_bio| unsafe {
cvt(ffi::ASN1_GENERALIZEDTIME_print(
mem_bio.as_ptr(),
self.as_ptr(),
));
match print_result {
Err(_) => f.write_str("error"),
Ok(_) => f.write_str(str::from_utf8_unchecked(mem_bio.get_buf())),
}
}
))
.ok()?;
str::from_utf8(mem_bio.get_buf()).ok()
})
.unwrap_or("error");
f.write_str(msg)
}
}
@ -528,7 +527,20 @@ impl Asn1BitStringRef {
#[corresponds(ASN1_STRING_get0_data)]
#[must_use]
pub fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(ASN1_STRING_get0_data(self.as_ptr() as *mut _), self.len()) }
unsafe {
let ptr = ASN1_STRING_get0_data(self.as_ptr().cast());
if ptr.is_null() {
return &[];
}
slice::from_raw_parts(ptr, self.len())
}
}
/// Returns the Asn1BitString as a str, if possible.
#[corresponds(ASN1_STRING_get0_data)]
#[must_use]
pub fn to_str(&self) -> Option<&str> {
str::from_utf8(self.as_slice()).ok()
}
/// Returns the number of bytes in the string.
@ -601,10 +613,11 @@ impl fmt::Display for Asn1ObjectRef {
self.as_ptr(),
0,
);
match str::from_utf8(&buf[..len as usize]) {
Err(_) => fmt.write_str("error"),
Ok(s) => fmt.write_str(s),
}
fmt.write_str(
buf.get(..len as usize)
.and_then(|s| str::from_utf8(s).ok())
.unwrap_or("error"),
)
}
}
}

View File

@ -19,9 +19,9 @@ impl Drop for MemBioSlice<'_> {
impl<'a> MemBioSlice<'a> {
pub fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> {
#[cfg(not(feature = "fips-compat"))]
#[cfg(not(feature = "legacy-compat-deprecated"))]
type BufLen = isize;
#[cfg(feature = "fips-compat")]
#[cfg(feature = "legacy-compat-deprecated")]
type BufLen = libc::c_int;
ffi::init();
@ -68,7 +68,10 @@ impl MemBio {
unsafe {
let mut ptr = ptr::null_mut();
let len = ffi::BIO_get_mem_data(self.0, &mut ptr);
slice::from_raw_parts(ptr as *const _ as *const _, len as usize)
if ptr.is_null() {
return &[];
}
slice::from_raw_parts(ptr.cast_const().cast(), len as usize)
}
}
}

View File

@ -32,7 +32,7 @@ where
}
to_der! {
/// Serializes the parameters into a DER-encoded PKCS#3 DHparameter structure.
/// Serializes the parameters into a DER-encoded PKCS#3 `DHparameter` structure.
#[corresponds(i2d_DHparams)]
params_to_der,
ffi::i2d_DHparams

View File

@ -20,6 +20,7 @@ use openssl_macros::corresponds;
use std::borrow::Cow;
use std::error;
use std::ffi::CStr;
use std::ffi::CString;
use std::fmt;
use std::io;
use std::ptr;
@ -27,6 +28,8 @@ use std::str;
use crate::ffi;
pub use crate::ffi::ErrLib;
/// Collection of [`Error`]s from OpenSSL.
///
/// [`Error`]: struct.Error.html
@ -35,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 {
@ -56,7 +62,13 @@ impl ErrorStack {
/// Used to report errors from the Rust crate
#[cold]
pub(crate) fn internal_error(err: impl error::Error) -> Self {
Self(vec![Error::new_internal(err.to_string())])
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.
@ -91,7 +103,7 @@ impl fmt::Display for ErrorStack {
write!(
fmt,
"[{}]",
err.reason_internal()
err.reason()
.or_else(|| err.library())
.unwrap_or("unknown reason")
)?;
@ -120,7 +132,15 @@ pub struct Error {
code: c_uint,
file: *const c_char,
line: c_uint,
data: Option<Cow<'static, str>>,
data: Data,
}
#[derive(Clone)]
enum Data {
None,
CString(CString),
String(String),
Static(&'static str),
}
unsafe impl Sync for Error {}
@ -146,11 +166,9 @@ impl Error {
// The memory referenced by data is only valid until that slot is overwritten
// in the error stack, so we'll need to copy it off if it's dynamic
let data = if flags & ffi::ERR_FLAG_STRING != 0 {
let bytes = CStr::from_ptr(data as *const _).to_bytes();
let data = String::from_utf8_lossy(bytes).into_owned();
Some(data.into())
Data::CString(CStr::from_ptr(data.cast()).to_owned())
} else {
None
Data::None
};
Some(Error {
code,
@ -174,31 +192,29 @@ impl Error {
self.file,
self.line,
);
let ptr = match self.data {
Some(Cow::Borrowed(data)) => Some(data.as_ptr() as *mut c_char),
Some(Cow::Owned(ref data)) => {
let ptr = ffi::OPENSSL_malloc((data.len() + 1) as _) as *mut c_char;
if ptr.is_null() {
None
} else {
ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, data.len());
*ptr.add(data.len()) = 0;
Some(ptr)
}
}
None => None,
};
if let Some(ptr) = ptr {
ffi::ERR_add_error_data(1, ptr);
if let Some(cstr) = self.data_cstr() {
ffi::ERR_set_error_data(cstr.as_ptr().cast_mut(), ffi::ERR_FLAG_STRING);
}
}
}
/// Get `{lib}_R_{reason}` reason code for the given library, or `None` if the error is from a different library.
///
/// Libraries are identified by [`ERR_LIB_{name}`(ffi::ERR_LIB_SSL) constants.
#[inline]
#[must_use]
#[track_caller]
pub fn library_reason(&self, library_code: ErrLib) -> Option<c_int> {
debug_assert!(library_code.0 < ffi::ERR_NUM_LIBS.0);
(self.library_code() == library_code.0 as c_int).then_some(self.reason_code())
}
/// Returns a raw OpenSSL **packed** error code for this error, which **can't be reliably compared to any error constant**.
///
/// Use [`Error::library_code()`] and [`Error::reason_code()`] instead.
/// Use [`Error::library_code()`] and [`Error::library_reason()`] instead.
/// Packed error codes are different than [SSL error codes](crate::ssl::ErrorCode).
#[must_use]
#[deprecated(note = "use library_reason() to compare error codes")]
pub fn code(&self) -> c_uint {
self.code
}
@ -214,14 +230,16 @@ impl Error {
if cstr.is_null() {
return None;
}
let bytes = CStr::from_ptr(cstr as *const _).to_bytes();
str::from_utf8(bytes).ok()
CStr::from_ptr(cstr.cast())
.to_str()
.ok()
.filter(|&msg| msg != "unknown library")
}
}
/// Returns the raw OpenSSL error constant for the library reporting the error (`ERR_LIB_{name}`).
///
/// Error [reason codes](Error::reason_code) are not globally unique, but scoped to each library.
/// Error [reason codes](Error::library_reason) are not globally unique, but scoped to each library.
#[must_use]
pub fn library_code(&self) -> c_int {
ffi::ERR_GET_LIB(self.code)
@ -234,20 +252,23 @@ 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() {
return None;
}
let bytes = CStr::from_ptr(cstr as *const _).to_bytes();
str::from_utf8(bytes).ok()
CStr::from_ptr(cstr.cast()).to_str().ok()
}
}
/// Returns [library-specific](Error::library_code) reason code corresponding to some of the `{lib}_R_{reason}` constants.
///
/// Reason codes are ambiguous, and different libraries reuse the same numeric values for different errors.
/// Use [`Error::library_reason`] to compare error codes.
///
/// For `ERR_LIB_SYS` the reason code is `errno`. `ERR_LIB_USER` can use any values.
/// Other libraries may use [`ERR_R_*`](ffi::ERR_R_FATAL) or their own codes.
@ -263,8 +284,9 @@ impl Error {
if self.file.is_null() {
return "";
}
let bytes = CStr::from_ptr(self.file as *const _).to_bytes();
str::from_utf8(bytes).unwrap_or_default()
CStr::from_ptr(self.file.cast())
.to_str()
.unwrap_or_default()
}
}
@ -280,30 +302,37 @@ impl Error {
/// Returns additional data describing the error.
#[must_use]
pub fn data(&self) -> Option<&str> {
self.data.as_deref()
match &self.data {
Data::None => None,
Data::CString(cstring) => cstring.to_str().ok(),
Data::String(s) => Some(s),
Data::Static(s) => Some(s),
}
}
fn new_internal(msg: String) -> Self {
#[must_use]
fn data_cstr(&self) -> Option<Cow<'_, CStr>> {
let s = match &self.data {
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)
}
fn new_internal(msg: Data) -> Self {
Self {
code: ffi::ERR_PACK(ffi::ERR_LIB_NONE.0 as _, 0, 0) as _,
file: BORING_INTERNAL.as_ptr(),
line: 0,
data: Some(msg.into()),
data: msg,
}
}
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 {
@ -334,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());
}

32
boring/src/hmac.rs Normal file
View File

@ -0,0 +1,32 @@
use crate::cvt;
use crate::error::ErrorStack;
use crate::foreign_types::ForeignTypeRef;
use crate::hash::MessageDigest;
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.
///
/// https://commondatastorage.googleapis.com/chromium-boringssl-docs/hmac.h.html#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(),
))
.map(|_| ())
}
}
}

View File

@ -132,7 +132,7 @@ 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 nid;

View File

@ -88,7 +88,9 @@ impl Nid {
pub fn long_name(&self) -> Result<&'static str, ErrorStack> {
unsafe {
let nameptr = cvt_p(ffi::OBJ_nid2ln(self.0) as *mut c_char)?;
str::from_utf8(CStr::from_ptr(nameptr).to_bytes()).map_err(ErrorStack::internal_error)
CStr::from_ptr(nameptr)
.to_str()
.map_err(ErrorStack::internal_error)
}
}
@ -98,7 +100,9 @@ impl Nid {
pub fn short_name(&self) -> Result<&'static str, ErrorStack> {
unsafe {
let nameptr = cvt_p(ffi::OBJ_nid2sn(self.0) as *mut c_char)?;
str::from_utf8(CStr::from_ptr(nameptr).to_bytes()).map_err(ErrorStack::internal_error)
CStr::from_ptr(nameptr)
.to_str()
.map_err(ErrorStack::internal_error)
}
}

View File

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

@ -95,7 +95,7 @@ impl SslContextBuilder {
let finish = fut_result.or(Err(SelectCertError::ERROR))?;
finish(client_hello).or(Err(SelectCertError::ERROR))
})
});
}
/// Configures a custom private key method on the context.
@ -144,7 +144,7 @@ impl SslContextBuilder {
}
};
self.set_get_session_callback(async_callback)
self.set_get_session_callback(async_callback);
}
/// Configures certificate verification.
@ -167,7 +167,7 @@ impl SslContextBuilder {
where
F: Fn(&mut SslRef) -> Result<BoxCustomVerifyFuture, SslAlert> + Send + Sync + 'static,
{
self.set_custom_verify_callback(mode, async_custom_verify_callback(callback))
self.set_custom_verify_callback(mode, async_custom_verify_callback(callback));
}
}
@ -176,7 +176,7 @@ impl SslRef {
where
F: Fn(&mut SslRef) -> Result<BoxCustomVerifyFuture, SslAlert> + Send + Sync + 'static,
{
self.set_custom_verify_callback(mode, async_custom_verify_callback(callback))
self.set_custom_verify_callback(mode, async_custom_verify_callback(callback));
}
/// Sets the task waker to be used in async callbacks installed on this `Ssl`.

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;
@ -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,
@ -399,7 +464,7 @@ pub(super) unsafe extern "C" fn raw_remove_session<F>(
.ex_data(SslContext::cached_ex_index::<F>())
.expect("BUG: remove session callback missing");
callback(ctx, session)
callback(ctx, session);
}
type DataPtr = *const c_uchar;
@ -451,14 +516,14 @@ where
{
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr(ssl as *mut _) };
let line = unsafe { str::from_utf8_unchecked(CStr::from_ptr(line).to_bytes()) };
let line = unsafe { CStr::from_ptr(line).to_string_lossy() };
let callback = ssl
.ssl_context()
.ex_data(SslContext::cached_ex_index::<F>())
.expect("BUG: get session callback missing");
callback(ssl, line);
callback(ssl, &line);
}
pub(super) unsafe extern "C" fn raw_sign<M>(
@ -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

@ -81,6 +81,7 @@ use crate::dh::DhRef;
use crate::ec::EcKeyRef;
use crate::error::ErrorStack;
use crate::ex_data::Index;
use crate::hmac::HmacCtxRef;
use crate::nid::Nid;
use crate::pkey::{HasPrivate, PKeyRef, Params, Private};
use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef};
@ -90,6 +91,7 @@ use crate::ssl::callbacks::*;
use crate::ssl::ech::SslEchKeys;
use crate::ssl::error::InnerError;
use crate::stack::{Stack, StackRef, Stackable};
use crate::symm::CipherCtxRef;
use crate::x509::store::{X509Store, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef};
use crate::x509::verify::X509VerifyParamRef;
use crate::x509::{
@ -114,7 +116,6 @@ mod async_callbacks;
mod bio;
mod callbacks;
mod connector;
#[cfg(not(feature = "fips"))]
mod ech;
mod error;
mod mut_only;
@ -697,131 +698,13 @@ impl From<u16> for SslSignatureAlgorithm {
}
}
/// Numeric identifier of a TLS curve.
#[repr(transparent)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SslCurveNid(c_int);
/// A TLS Curve.
#[repr(transparent)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SslCurve(c_int);
impl SslCurve {
pub const SECP224R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP224R1 as _);
pub const SECP256R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP256R1 as _);
pub const SECP384R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP384R1 as _);
pub const SECP521R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP521R1 as _);
pub const X25519: SslCurve = SslCurve(ffi::SSL_CURVE_X25519 as _);
pub const FFDHE2048: SslCurve = SslCurve(ffi::SSL_CURVE_DHE2048 as _);
pub const FFDHE3072: SslCurve = SslCurve(ffi::SSL_CURVE_DHE3072 as _);
#[cfg(feature = "pq-experimental")]
#[cfg(not(any(feature = "fips", feature = "fips-precompiled")))]
pub const X25519_KYBER768_DRAFT00: SslCurve =
SslCurve(ffi::SSL_CURVE_X25519_KYBER768_DRAFT00 as _);
#[cfg(all(
not(any(feature = "fips", feature = "fips-precompiled")),
feature = "pq-experimental"
))]
pub const X25519_KYBER768_DRAFT00_OLD: SslCurve =
SslCurve(ffi::SSL_CURVE_X25519_KYBER768_DRAFT00_OLD as _);
#[cfg(all(
not(any(feature = "fips", feature = "fips-precompiled")),
feature = "pq-experimental"
))]
pub const X25519_KYBER512_DRAFT00: SslCurve =
SslCurve(ffi::SSL_CURVE_X25519_KYBER512_DRAFT00 as _);
#[cfg(all(
not(any(feature = "fips", feature = "fips-precompiled")),
feature = "pq-experimental"
))]
pub const P256_KYBER768_DRAFT00: SslCurve = SslCurve(ffi::SSL_CURVE_P256_KYBER768_DRAFT00 as _);
#[cfg(all(
not(any(feature = "fips", feature = "fips-precompiled")),
feature = "pq-experimental"
))]
pub const X25519_MLKEM768: SslCurve = SslCurve(ffi::SSL_CURVE_X25519_MLKEM768 as _);
/// Returns the curve name
#[corresponds(SSL_get_curve_name)]
#[must_use]
pub fn name(&self) -> Option<&'static str> {
unsafe {
let ptr = ffi::SSL_get_curve_name(self.0 as u16);
if ptr.is_null() {
return None;
}
CStr::from_ptr(ptr).to_str().ok()
}
}
// We need to allow dead_code here because `SslRef::set_curves` is conditionally compiled
// against the absence of the `kx-safe-default` feature and thus this function is never used.
//
// **NOTE**: This function only exists because the version of boringssl we currently use does
// not expose SSL_CTX_set1_group_ids. Because `SslRef::curve()` returns the public SSL_CURVE id
// as opposed to the internal NID, but `SslContextBuilder::set_curves()` requires the internal
// NID, we need this mapping in place to avoid breaking changes to the public API. Once the
// underlying boringssl version is upgraded, this should be removed in favor of the new
// SSL_CTX_set1_group_ids API.
#[allow(dead_code)]
pub fn nid(&self) -> Option<SslCurveNid> {
match self.0 {
ffi::SSL_CURVE_SECP224R1 => Some(ffi::NID_secp224r1),
ffi::SSL_CURVE_SECP256R1 => Some(ffi::NID_X9_62_prime256v1),
ffi::SSL_CURVE_SECP384R1 => Some(ffi::NID_secp384r1),
ffi::SSL_CURVE_SECP521R1 => Some(ffi::NID_secp521r1),
ffi::SSL_CURVE_X25519 => Some(ffi::NID_X25519),
#[cfg(not(any(feature = "fips", feature = "fips-precompiled")))]
ffi::SSL_CURVE_X25519_KYBER768_DRAFT00 => Some(ffi::NID_X25519Kyber768Draft00),
#[cfg(all(
not(any(feature = "fips", feature = "fips-precompiled")),
feature = "pq-experimental"
))]
ffi::SSL_CURVE_X25519_KYBER768_DRAFT00_OLD => Some(ffi::NID_X25519Kyber768Draft00Old),
#[cfg(all(
not(any(feature = "fips", feature = "fips-precompiled")),
feature = "pq-experimental"
))]
ffi::SSL_CURVE_X25519_KYBER512_DRAFT00 => Some(ffi::NID_X25519Kyber512Draft00),
#[cfg(all(
not(any(feature = "fips", feature = "fips-precompiled")),
feature = "pq-experimental"
))]
ffi::SSL_CURVE_P256_KYBER768_DRAFT00 => Some(ffi::NID_P256Kyber768Draft00),
#[cfg(all(
not(any(feature = "fips", feature = "fips-precompiled")),
feature = "pq-experimental"
))]
ffi::SSL_CURVE_X25519_MLKEM768 => Some(ffi::NID_X25519MLKEM768),
ffi::SSL_CURVE_DHE2048 => Some(ffi::NID_ffdhe2048),
ffi::SSL_CURVE_DHE3072 => Some(ffi::NID_ffdhe3072),
_ => None,
}
.map(SslCurveNid)
}
}
/// A compliance policy.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg(not(feature = "fips-compat"))]
pub struct CompliancePolicy(ffi::ssl_compliance_policy_t);
#[cfg(not(feature = "fips-compat"))]
impl CompliancePolicy {
/// Does nothing, however setting this does not undo other policies, so trying to set this is an error.
#[cfg(not(feature = "legacy-compat-deprecated"))]
pub const NONE: Self = Self(ffi::ssl_compliance_policy_t::ssl_compliance_policy_none);
/// Configures a TLS connection to try and be compliant with NIST requirements, but does not guarantee success.
@ -831,6 +714,7 @@ impl CompliancePolicy {
/// Partially configures a TLS connection to be compliant with WPA3. Callers must enforce certificate chain requirements themselves.
/// Use of this policy is less secure than the default and not recommended.
#[cfg(not(feature = "legacy-compat-deprecated"))]
pub const WPA3_192_202304: Self =
Self(ffi::ssl_compliance_policy_t::ssl_compliance_policy_wpa3_192_202304);
}
@ -925,6 +809,53 @@ pub enum SslInfoCallbackValue {
Alert(SslInfoCallbackAlert),
}
/// Ticket key callback status.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TicketKeyCallbackResult {
/// Abort the handshake.
Error,
/// Continue with a full handshake.
///
/// When in decryption mode, this indicates that the peer supplied session ticket was not
/// recognized. When in encryption mode, this instructs boring to not send a session ticket.
///
/// # Note
///
/// This is a decryption specific status code when using the submoduled BoringSSL.
Noop,
/// Resumption callback was successful.
///
/// When in decryption mode, attempt an abbreviated handshake via session resumption. When in
/// encryption mode, provide a new ticket to the client.
Success,
/// Resumption callback was successful. Attempt an abbreviated handshake, and additionally
/// provide new session tickets to the peer.
///
/// Session resumption short-circuits some security checks of a full-handshake, in exchange for
/// potential performance gains. For this reason, a session ticket should only be valid for a
/// limited time. Providing the peer with renewed session tickets allows them to continue
/// session resumption with the new tickets.
///
/// # Note
///
/// This is a decryption specific status code.
DecryptSuccessRenew,
}
impl From<TicketKeyCallbackResult> for c_int {
fn from(value: TicketKeyCallbackResult) -> Self {
match value {
TicketKeyCallbackResult::Error => -1,
TicketKeyCallbackResult::Noop => 0,
TicketKeyCallbackResult::Success => 1,
TicketKeyCallbackResult::DecryptSuccessRenew => 2,
}
}
}
#[derive(Hash, Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Debug)]
pub struct SslInfoCallbackAlert(c_int);
@ -966,7 +897,6 @@ impl SslContextBuilder {
unsafe {
init();
let ctx = cvt_p(ffi::SSL_CTX_new(method.as_ptr()))?;
Ok(SslContextBuilder::from_ptr(ctx))
}
}
@ -977,9 +907,10 @@ impl SslContextBuilder {
///
/// The caller must ensure that the pointer is valid and uniquely owned by the builder.
pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextBuilder {
let ctx = SslContext::from_ptr(ctx);
SslContextBuilder {
ctx: SslContext::from_ptr(ctx),
has_shared_cert_store: false,
ctx,
}
}
@ -1125,6 +1056,49 @@ impl SslContextBuilder {
}
}
/// Configures a custom session ticket key callback for session resumption.
///
/// Session Resumption uses the security context (aka. session tickets) of a previous
/// connection to establish a new connection via an abbreviated handshake. Skipping portions of
/// a handshake can potentially yield performance gains.
///
/// An attacker that compromises a server's session ticket key can impersonate the server and,
/// prior to TLS 1.3, retroactively decrypt all application traffic from sessions using that
/// ticket key. Thus ticket keys must be regularly rotated for forward secrecy.
///
/// CipherCtx and HmacCtx are guaranteed to be initialized.
///
/// # Panics
///
/// This method panics if this `Ssl` is associated with a RPK context.
///
/// # Safety
///
/// The application is responsible for correctly setting the key_name, iv, encryption context
/// and hmac context. See the [`SSL_CTX_set_tlsext_ticket_key_cb`] docs for additional info.
///
/// [`SSL_CTX_set_tlsext_ticket_key_cb`]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_CTX_set_tlsext_ticket_key_cb
#[corresponds(SSL_CTX_set_tlsext_ticket_key_cb)]
pub unsafe fn set_ticket_key_callback<F>(&mut self, callback: F)
where
F: Fn(
&SslRef,
&mut [u8; 16],
&mut [u8; ffi::EVP_MAX_IV_LENGTH as usize],
&mut CipherCtxRef,
&mut HmacCtxRef,
bool,
) -> TicketKeyCallbackResult
+ 'static
+ Sync
+ Send,
{
unsafe {
self.replace_ex_data(SslContext::cached_ex_index::<F>(), callback);
ffi::SSL_CTX_set_tlsext_ticket_key_cb(self.as_ptr(), Some(raw_ticket_key::<F>))
};
}
/// Sets the certificate verification depth.
///
/// If the peer's certificate chain is longer than this value, verification will fail.
@ -1493,7 +1467,10 @@ impl SslContextBuilder {
#[corresponds(SSL_CTX_set_alpn_protos)]
pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> {
unsafe {
#[cfg_attr(not(feature = "fips-compat"), allow(clippy::unnecessary_cast))]
#[cfg_attr(
not(feature = "legacy-compat-deprecated"),
allow(clippy::unnecessary_cast)
)]
{
assert!(protocols.len() <= ProtosLen::MAX as usize);
}
@ -1719,7 +1696,7 @@ impl SslContextBuilder {
+ Sync
+ Send,
{
self.set_psk_client_callback(callback)
self.set_psk_client_callback(callback);
}
/// Sets the callback for providing an identity and pre-shared key for a TLS-PSK server.
@ -1936,21 +1913,8 @@ impl SslContextBuilder {
unsafe { ffi::SSL_CTX_set_preserve_tls13_cipher_list(self.as_ptr(), enable as _) }
}
/// Sets whether the ChaCha20 preference should be enabled.
///
/// Controls the priority of TLS 1.3 cipher suites. When set to `true`, the client prefers:
/// AES_128_GCM, CHACHA20_POLY1305, then AES_256_GCM. Useful in environments with specific
/// encryption requirements.
#[deprecated(note = "use `set_preserve_tls13_cipher_list` instead")]
#[cfg(not(feature = "fips"))]
#[corresponds(SSL_CTX_set_prefer_chacha20)]
pub fn set_prefer_chacha20(&mut self, enable: bool) {
unsafe { ffi::SSL_CTX_set_preserve_tls13_cipher_list(self.as_ptr(), enable as _) }
}
/// Sets the indices of the extensions to be permuted.
#[corresponds(SSL_CTX_set_extension_order)]
#[cfg(not(feature = "fips-compat"))]
pub fn set_extension_permutation(
&mut self,
indices: &[ExtensionType],
@ -1966,12 +1930,7 @@ impl SslContextBuilder {
}
/// Configures whether ClientHello extensions should be permuted.
///
/// Note: This is gated to non-fips because the fips feature builds with a separate
/// version of BoringSSL which doesn't yet include these APIs.
/// Once the submoduled fips commit is upgraded, these gates can be removed.
#[corresponds(SSL_CTX_set_permute_extensions)]
#[cfg(not(feature = "fips-compat"))]
pub fn set_permute_extensions(&mut self, enabled: bool) {
unsafe { ffi::SSL_CTX_set_permute_extensions(self.as_ptr(), enabled as _) }
}
@ -2005,11 +1964,6 @@ impl SslContextBuilder {
}
/// Sets the context's supported curves.
//
// If the "kx-*" flags are used to set key exchange preference, then don't allow the user to
// set them here. This ensures we don't override the user's preference without telling them:
// when the flags are used, the preferences are set just before connecting or accepting.
#[cfg(not(feature = "kx-safe-default"))]
#[corresponds(SSL_CTX_set1_curves_list)]
pub fn set_curves_list(&mut self, curves: &str) -> Result<(), ErrorStack> {
let curves = CString::new(curves).map_err(ErrorStack::internal_error)?;
@ -2022,34 +1976,10 @@ impl SslContextBuilder {
}
}
/// Sets the context's supported curves.
//
// If the "kx-*" flags are used to set key exchange preference, then don't allow the user to
// set them here. This ensures we don't override the user's preference without telling them:
// when the flags are used, the preferences are set just before connecting or accepting.
#[corresponds(SSL_CTX_set1_curves)]
#[cfg(not(feature = "kx-safe-default"))]
pub fn set_curves(&mut self, curves: &[SslCurve]) -> Result<(), ErrorStack> {
let curves: Vec<i32> = curves
.iter()
.filter_map(|curve| curve.nid().map(|nid| nid.0))
.collect();
unsafe {
cvt_0i(ffi::SSL_CTX_set1_curves(
self.as_ptr(),
curves.as_ptr() as *const _,
curves.len(),
))
.map(|_| ())
}
}
/// Sets the context's compliance policy.
///
/// This feature isn't available in the certified version of BoringSSL.
#[corresponds(SSL_CTX_set_compliance_policy)]
#[cfg(not(feature = "fips-compat"))]
pub fn set_compliance_policy(&mut self, policy: CompliancePolicy) -> Result<(), ErrorStack> {
unsafe { cvt_0i(ffi::SSL_CTX_set_compliance_policy(self.as_ptr(), policy.0)).map(|_| ()) }
}
@ -2070,7 +2000,6 @@ impl SslContextBuilder {
/// ECHConfigs to allow stale DNS caches to update. Unlike most `SSL_CTX` APIs, this function
/// is safe to call even after the `SSL_CTX` has been associated with connections on various
/// threads.
#[cfg(not(feature = "fips"))]
#[corresponds(SSL_CTX_set1_ech_keys)]
pub fn set_ech_keys(&self, keys: &SslEchKeys) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) }
@ -2323,7 +2252,6 @@ impl SslContextRef {
/// ECHConfigs to allow stale DNS caches to update. Unlike most `SSL_CTX` APIs, this function
/// is safe to call even after the `SSL_CTX` has been associated with connections on various
/// threads.
#[cfg(not(feature = "fips"))]
#[corresponds(SSL_CTX_set1_ech_keys)]
pub fn set_ech_keys(&self, keys: &SslEchKeys) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) }
@ -2337,9 +2265,9 @@ impl SslContextRef {
#[derive(Debug)]
pub struct GetSessionPendingError;
#[cfg(not(feature = "fips-compat"))]
#[cfg(not(feature = "legacy-compat-deprecated"))]
type ProtosLen = usize;
#[cfg(feature = "fips-compat")]
#[cfg(feature = "legacy-compat-deprecated")]
type ProtosLen = libc::c_uint;
/// Information about the state of a cipher.
@ -2511,7 +2439,7 @@ impl SslCipherRef {
CStr::from_ptr(ptr as *const _)
};
str::from_utf8(version.to_bytes()).unwrap()
version.to_str().unwrap()
}
/// Returns the number of bits used for the cipher.
@ -2537,7 +2465,7 @@ impl SslCipherRef {
// SSL_CIPHER_description requires a buffer of at least 128 bytes.
let mut buf = [0; 128];
let ptr = ffi::SSL_CIPHER_description(self.as_ptr(), buf.as_mut_ptr(), 128);
String::from_utf8(CStr::from_ptr(ptr as *const _).to_bytes().to_vec()).unwrap()
CStr::from_ptr(ptr.cast()).to_string_lossy().into_owned()
}
}
@ -2725,32 +2653,13 @@ impl Ssl {
}
}
/// Creates a new `Ssl`.
///
// FIXME should take &SslContextRef
#[corresponds(SSL_new)]
pub fn new(ctx: &SslContext) -> Result<Ssl, ErrorStack> {
unsafe {
let ptr = cvt_p(ffi::SSL_new(ctx.as_ptr()))?;
let mut ssl = Ssl::from_ptr(ptr);
ssl.set_ex_data(*SESSION_CTX_INDEX, ctx.clone());
Ok(ssl)
}
}
/// Creates a new [`Ssl`].
///
/// This function does the same as [`Self:new`] except that it takes &[SslContextRef].
// Both functions exist for backward compatibility (no breaking API).
#[corresponds(SSL_new)]
pub fn new_from_ref(ctx: &SslContextRef) -> Result<Ssl, ErrorStack> {
pub fn new(ctx: &SslContextRef) -> Result<Ssl, ErrorStack> {
unsafe {
let ptr = cvt_p(ffi::SSL_new(ctx.as_ptr()))?;
let mut ssl = Ssl::from_ptr(ptr);
SSL_CTX_up_ref(ctx.as_ptr());
let ctx_owned = SslContext::from_ptr(ctx.as_ptr());
ssl.set_ex_data(*SESSION_CTX_INDEX, ctx_owned);
ssl.set_ex_data(*SESSION_CTX_INDEX, ctx.to_owned());
Ok(ssl)
}
@ -2875,63 +2784,31 @@ impl SslRef {
}
}
/// Sets the ongoing session's supported groups by their named identifiers
/// (formerly referred to as curves).
#[corresponds(SSL_set1_groups)]
pub fn set_group_nids(&mut self, group_nids: &[SslCurveNid]) -> Result<(), ErrorStack> {
unsafe {
cvt_0i(ffi::SSL_set1_curves(
self.as_ptr(),
group_nids.as_ptr() as *const _,
group_nids.len(),
))
.map(|_| ())
}
}
#[cfg(feature = "kx-safe-default")]
fn client_set_default_curves_list(&mut self) {
let curves = if cfg!(feature = "kx-client-pq-preferred") {
if cfg!(feature = "kx-client-nist-required") {
"P256Kyber768Draft00:P-256:P-384:P-521"
} else {
"X25519MLKEM768:X25519Kyber768Draft00:X25519:P256Kyber768Draft00:P-256:P-384:P-521"
}
} else if cfg!(feature = "kx-client-pq-supported") {
if cfg!(feature = "kx-client-nist-required") {
"P-256:P-384:P-521:P256Kyber768Draft00"
} else {
"X25519:P-256:P-384:P-521:X25519MLKEM768:X25519Kyber768Draft00:P256Kyber768Draft00"
}
} else {
if cfg!(feature = "kx-client-nist-required") {
"P-256:P-384:P-521"
} else {
"X25519:P-256:P-384:P-521"
}
};
self.set_curves_list(curves)
.expect("invalid default client curves list");
}
#[cfg(feature = "kx-safe-default")]
fn server_set_default_curves_list(&mut self) {
self.set_curves_list(
"X25519MLKEM768:X25519Kyber768Draft00:P256Kyber768Draft00:X25519:P-256:P-384",
)
.expect("invalid default server curves list");
}
/// Returns the [`SslCurve`] used for this `SslRef`.
/// Returns the curve ID (aka group ID) used for this `SslRef`.
#[corresponds(SSL_get_curve_id)]
#[must_use]
pub fn curve(&self) -> Option<SslCurve> {
pub fn curve(&self) -> Option<u16> {
let curve_id = unsafe { ffi::SSL_get_curve_id(self.as_ptr()) };
if curve_id == 0 {
return None;
}
Some(SslCurve(curve_id.into()))
Some(curve_id)
}
/// Returns the curve name used for this `SslRef`.
#[corresponds(SSL_get_curve_name)]
#[must_use]
pub fn curve_name(&self) -> Option<&'static str> {
let curve_id = self.curve()?;
unsafe {
let ptr = ffi::SSL_get_curve_name(curve_id);
if ptr.is_null() {
return None;
}
CStr::from_ptr(ptr).to_str().ok()
}
}
/// Returns an `ErrorCode` value for the most recent operation on this `SslRef`.
@ -3047,11 +2924,6 @@ impl SslRef {
/// Configures whether ClientHello extensions should be permuted.
#[corresponds(SSL_set_permute_extensions)]
///
/// Note: This is gated to non-fips because the fips feature builds with a separate
/// version of BoringSSL which doesn't yet include these APIs.
/// Once the submoduled fips commit is upgraded, these gates can be removed.
#[cfg(not(feature = "fips-compat"))]
pub fn set_permute_extensions(&mut self, enabled: bool) {
unsafe { ffi::SSL_set_permute_extensions(self.as_ptr(), enabled as _) }
}
@ -3062,7 +2934,10 @@ impl SslRef {
#[corresponds(SSL_set_alpn_protos)]
pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> {
unsafe {
#[cfg_attr(not(feature = "fips-compat"), allow(clippy::unnecessary_cast))]
#[cfg_attr(
not(feature = "legacy-compat-deprecated"),
allow(clippy::unnecessary_cast)
)]
{
assert!(protocols.len() <= ProtosLen::MAX as usize);
}
@ -3106,6 +2981,8 @@ impl SslRef {
}
/// Returns a short string describing the state of the session.
///
/// Returns empty string if the state wasn't valid UTF-8.
#[corresponds(SSL_state_string)]
#[must_use]
pub fn state_string(&self) -> &'static str {
@ -3114,10 +2991,12 @@ impl SslRef {
CStr::from_ptr(ptr as *const _)
};
str::from_utf8(state.to_bytes()).unwrap()
state.to_str().unwrap_or_default()
}
/// Returns a longer string describing the state of the session.
///
/// Returns empty string if the state wasn't valid UTF-8.
#[corresponds(SSL_state_string_long)]
#[must_use]
pub fn state_string_long(&self) -> &'static str {
@ -3126,7 +3005,7 @@ impl SslRef {
CStr::from_ptr(ptr as *const _)
};
str::from_utf8(state.to_bytes()).unwrap()
state.to_str().unwrap_or_default()
}
/// Sets the host name to be sent to the server for Server Name Indication (SNI).
@ -3220,6 +3099,8 @@ impl SslRef {
}
/// Returns a string describing the protocol version of the session.
///
/// This may panic if the string isn't valid UTF-8 for some reason. Use [`Self::version2`] instead.
#[corresponds(SSL_get_version)]
#[must_use]
pub fn version_str(&self) -> &'static str {
@ -3228,7 +3109,7 @@ impl SslRef {
CStr::from_ptr(ptr as *const _)
};
str::from_utf8(version.to_bytes()).unwrap()
version.to_str().unwrap()
}
/// Sets the minimum supported protocol version.
@ -3734,7 +3615,6 @@ impl SslRef {
/// Clients should use `get_ech_name_override` to verify the server certificate in case of ECH
/// rejection, and follow up with `get_ech_retry_configs` to retry the connection with a fresh
/// set of ECHConfigs. If the retry also fails, clients should report a connection failure.
#[cfg(not(feature = "fips"))]
#[corresponds(SSL_set1_ech_config_list)]
pub fn set_ech_config_list(&mut self, ech_config_list: &[u8]) -> Result<(), ErrorStack> {
unsafe {
@ -3753,7 +3633,6 @@ impl SslRef {
/// Clients should call this function when handling an `SSL_R_ECH_REJECTED` error code to
/// recover from potential key mismatches. If the result is `Some`, the client should retry the
/// connection using the returned `ECHConfigList`.
#[cfg(not(feature = "fips"))]
#[corresponds(SSL_get0_ech_retry_configs)]
#[must_use]
pub fn get_ech_retry_configs(&self) -> Option<&[u8]> {
@ -3776,7 +3655,6 @@ impl SslRef {
/// Clients should call this function during the certificate verification callback to
/// ensure the server's certificate is valid for the public name, which is required to
/// authenticate retry configs.
#[cfg(not(feature = "fips"))]
#[corresponds(SSL_get0_ech_name_override)]
#[must_use]
pub fn get_ech_name_override(&self) -> Option<&[u8]> {
@ -3794,7 +3672,6 @@ impl SslRef {
}
// Whether or not `SSL` negotiated ECH.
#[cfg(not(feature = "fips"))]
#[corresponds(SSL_ech_accepted)]
#[must_use]
pub fn ech_accepted(&self) -> bool {
@ -3802,7 +3679,6 @@ impl SslRef {
}
// Whether or not to enable ECH grease on `SSL`.
#[cfg(not(feature = "fips"))]
#[corresponds(SSL_set_enable_ech_grease)]
pub fn set_enable_ech_grease(&self, enable: bool) {
let enable = if enable { 1 } else { 0 };
@ -3813,7 +3689,6 @@ impl SslRef {
}
/// Sets the compliance policy on `SSL`.
#[cfg(not(feature = "fips-compat"))]
#[corresponds(SSL_set_compliance_policy)]
pub fn set_compliance_policy(&mut self, policy: CompliancePolicy) -> Result<(), ErrorStack> {
unsafe { cvt_0i(ffi::SSL_set_compliance_policy(self.as_ptr(), policy.0)).map(|_| ()) }
@ -3882,10 +3757,11 @@ impl<S> MidHandshakeSslStream<S> {
Ok(self.stream)
} else {
self.error = self.stream.make_error(ret);
match self.error.would_block() {
true => Err(HandshakeError::WouldBlock(self)),
false => Err(HandshakeError::Failure(self)),
}
Err(if self.error.would_block() {
HandshakeError::WouldBlock(self)
} else {
HandshakeError::Failure(self)
})
}
}
}
@ -3920,26 +3796,23 @@ where
}
impl<S: Read + Write> SslStream<S> {
fn new_base(ssl: Ssl, stream: S) -> Self {
unsafe {
let (bio, method) = bio::new(stream).unwrap();
ffi::SSL_set_bio(ssl.as_ptr(), bio, bio);
SslStream {
ssl: ManuallyDrop::new(ssl),
method: ManuallyDrop::new(method),
_p: PhantomData,
}
}
}
/// Creates a new `SslStream`.
///
/// This function performs no IO; the stream will not have performed any part of the handshake
/// with the peer. The `connect` and `accept` methods can be used to
/// explicitly perform the handshake.
pub fn new(ssl: Ssl, stream: S) -> Result<Self, ErrorStack> {
Ok(Self::new_base(ssl, stream))
let (bio, method) = bio::new(stream)?;
unsafe {
ffi::SSL_set_bio(ssl.as_ptr(), bio, bio);
}
Ok(SslStream {
ssl: ManuallyDrop::new(ssl),
method: ManuallyDrop::new(method),
_p: PhantomData,
})
}
/// Constructs an `SslStream` from a pointer to the underlying OpenSSL `SSL` struct.
@ -3951,7 +3824,7 @@ impl<S: Read + Write> SslStream<S> {
/// The caller must ensure the pointer is valid.
pub unsafe fn from_raw_parts(ssl: *mut ffi::SSL, stream: S) -> Self {
let ssl = Ssl::from_ptr(ssl);
Self::new_base(ssl, stream)
Self::new(ssl, stream).unwrap()
}
/// Like `read`, but takes a possibly-uninitialized slice.
@ -4218,7 +4091,7 @@ where
/// Begin creating an `SslStream` atop `stream`
pub fn new(ssl: Ssl, stream: S) -> Self {
Self {
inner: SslStream::new_base(ssl, stream),
inner: SslStream::new(ssl, stream).unwrap(),
}
}
@ -4243,9 +4116,6 @@ where
pub fn setup_connect(mut self) -> MidHandshakeSslStream<S> {
self.set_connect_state();
#[cfg(feature = "kx-safe-default")]
self.inner.ssl.client_set_default_curves_list();
MidHandshakeSslStream {
stream: self.inner,
error: Error {
@ -4275,9 +4145,6 @@ where
pub fn setup_accept(mut self) -> MidHandshakeSslStream<S> {
self.set_accept_state();
#[cfg(feature = "kx-safe-default")]
self.inner.ssl.server_set_default_curves_list();
MidHandshakeSslStream {
stream: self.inner,
error: Error {
@ -4309,16 +4176,11 @@ where
Ok(stream)
} else {
let error = stream.make_error(ret);
match error.would_block() {
true => Err(HandshakeError::WouldBlock(MidHandshakeSslStream {
stream,
error,
})),
false => Err(HandshakeError::Failure(MidHandshakeSslStream {
stream,
error,
})),
}
Err(if error.would_block() {
HandshakeError::WouldBlock(MidHandshakeSslStream { stream, error })
} else {
HandshakeError::Failure(MidHandshakeSslStream { stream, error })
})
}
}
}

View File

@ -13,26 +13,24 @@ 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::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");
@ -954,59 +952,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]
@ -1037,7 +991,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)
@ -1118,7 +1071,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();

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

@ -13,6 +13,9 @@ foreign_type_and_impl_send_sync! {
type CType = c_char;
fn drop = free;
/// # Safety
///
/// MUST be UTF-8.
pub struct OpensslString;
}

View File

@ -53,6 +53,7 @@
//! ```
use crate::ffi;
use foreign_types::ForeignTypeRef;
use libc::{c_int, c_uint};
use openssl_macros::corresponds;
use std::cmp;
@ -68,6 +69,71 @@ 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`.
///
/// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#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(),
))
.map(|_| ())
}
}
/// Configures CipherCtx for a fresh decryption operation using `cipher`.
///
/// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#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(),
))
.map(|_| ())
}
}
}
/// Represents a particular cipher algorithm.
///
/// See OpenSSL doc at [`EVP_EncryptInit`] for more information on each algorithms.

View File

@ -55,8 +55,8 @@ where
match result {
Ok(Ok(len)) => len as c_int,
Ok(Err(_)) => {
// FIXME restore error stack
Ok(Err(err)) => {
err.put();
0
}
Err(err) => {

View File

@ -19,7 +19,6 @@ use std::mem;
use std::net::IpAddr;
use std::path::Path;
use std::ptr;
use std::slice;
use std::str;
use std::sync::{LazyLock, Once};
@ -485,16 +484,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(())
@ -865,7 +857,7 @@ impl fmt::Debug for X509 {
if let Ok(public_key) = &self.public_key() {
debug_struct.field("public_key", public_key);
};
}
// TODO: Print extensions once they are supported on the X509 struct.
debug_struct.finish()
@ -1121,9 +1113,9 @@ impl X509NameBuilder {
}
}
#[cfg(not(feature = "fips-compat"))]
#[cfg(not(feature = "legacy-compat-deprecated"))]
type ValueLen = isize;
#[cfg(feature = "fips-compat")]
#[cfg(feature = "legacy-compat-deprecated")]
type ValueLen = i32;
foreign_type_and_impl_send_sync! {
@ -1535,6 +1527,8 @@ impl X509VerifyError {
}
/// Return a human readable error string from the verification error.
///
/// Returns empty string if the message was not UTF-8.
#[corresponds(X509_verify_cert_error_string)]
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
@ -1543,7 +1537,7 @@ impl X509VerifyError {
unsafe {
let s = ffi::X509_verify_cert_error_string(c_long::from(self.0));
str::from_utf8(CStr::from_ptr(s).to_bytes()).unwrap()
CStr::from_ptr(s).to_str().unwrap_or_default()
}
}
}
@ -1695,14 +1689,12 @@ impl GeneralNameRef {
return None;
}
let ptr = ASN1_STRING_get0_data((*self.as_ptr()).d.ia5 as *mut _);
let len = ffi::ASN1_STRING_length((*self.as_ptr()).d.ia5 as *mut _);
let asn = Asn1BitStringRef::from_ptr((*self.as_ptr()).d.ia5);
let slice = slice::from_raw_parts(ptr, len as usize);
// IA5Strings are stated to be ASCII (specifically IA5). Hopefully
// OpenSSL checks that when loading a certificate but if not we'll
// use this instead of from_utf8_unchecked just in case.
str::from_utf8(slice).ok()
asn.to_str()
}
}
@ -1732,10 +1724,7 @@ impl GeneralNameRef {
return None;
}
let ptr = ASN1_STRING_get0_data((*self.as_ptr()).d.ip as *mut _);
let len = ffi::ASN1_STRING_length((*self.as_ptr()).d.ip as *mut _);
Some(slice::from_raw_parts(ptr, len as usize))
Some(Asn1BitStringRef::from_ptr((*self.as_ptr()).d.ip).as_slice())
}
}
}
@ -1811,8 +1800,8 @@ impl Stackable for X509Object {
use crate::ffi::{X509_get0_signature, X509_getm_notAfter, X509_getm_notBefore, X509_up_ref};
use crate::ffi::{
ASN1_STRING_get0_data, X509_ALGOR_get0, X509_REQ_get_subject_name, X509_REQ_get_version,
X509_STORE_CTX_get0_chain, X509_set1_notAfter, X509_set1_notBefore,
X509_ALGOR_get0, X509_REQ_get_subject_name, X509_REQ_get_version, X509_STORE_CTX_get0_chain,
X509_set1_notAfter, X509_set1_notBefore,
};
use crate::ffi::X509_OBJECT_get0_X509;

View File

@ -250,34 +250,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();

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(
@ -61,7 +61,7 @@ fn test_verify_cert() {
Ok(()),
verify(&leaf, &[&root1], &[&intermediate, &root1_cross], |param| {
param.clear_flags(X509Flags::TRUSTED_FIRST)
},)
})
);
}

View File

@ -12,29 +12,12 @@ An implementation of SSL streams for Compio backed by BoringSSL
"""
[package.metadata.docs.rs]
features = ["pq-experimental"]
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"]
[dependencies]
boring = { workspace = true }
boring-sys = { workspace = true }

View File

@ -15,12 +15,8 @@ maintenance = { status = "passively-maintained" }
[features]
default = ["runtime-tokio"]
runtime-tokio = ["quinn/runtime-tokio"]
# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/)
pq-experimental = ["boring/pq-experimental"]
[dependencies]
boring = { workspace = true }
boring-sys = { workspace = true }

View File

@ -12,29 +12,12 @@ An implementation of SSL streams for Tokio backed by BoringSSL
"""
[package.metadata.docs.rs]
features = ["pq-experimental"]
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"]
[dependencies]
boring = { workspace = true }
boring-sys = { workspace = true }

View File

@ -22,7 +22,7 @@ impl<S> AsyncStreamBridge<S> {
}
pub(crate) fn set_waker(&mut self, ctx: Option<&mut Context<'_>>) {
self.waker = ctx.map(|ctx| ctx.waker().clone())
self.waker = ctx.map(|ctx| ctx.waker().clone());
}
/// # Panics