Introduce X509CheckFlags::UNDERSCORE_WILDCARDS

This commit is contained in:
Anthony Ramine 2023-12-18 12:42:11 +01:00 committed by Anthony Ramine
parent 3df405443a
commit a8dea4a22c
10 changed files with 929 additions and 5 deletions

View File

@ -340,7 +340,15 @@ jobs:
name: Run `rpk` tests name: Run `rpk` tests
- run: cargo test --features pq-experimental - run: cargo test --features pq-experimental
name: Run `pq-experimental` tests name: Run `pq-experimental` tests
- run: cargo test --features underscore-wildcards
name: Run `underscore-wildcards` tests
- run: cargo test --features pq-experimental,rpk - run: cargo test --features pq-experimental,rpk
name: Run `pq-experimental,rpk` tests name: Run `pq-experimental,rpk` tests
- run: cargo test --features kx-safe-default,pq-experimental - run: cargo test --features kx-safe-default,pq-experimental
name: Run `kx-safe-default` tests name: Run `kx-safe-default` tests
- run: cargo test --features pq-experimental,underscore-wildcards
name: Run `pq-experimental,underscore-wildcards` tests
- run: cargo test --features rpk,underscore-wildcards
name: Run `rpk,underscore-wildcards` tests
- run: cargo test --features pq-experimental,rpk,underscore-wildcards
name: Run `pq-experimental,rpk,underscore-wildcards` tests

View File

@ -51,7 +51,7 @@ include = [
] ]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["rpk", "pq-experimental"] features = ["rpk", "pq-experimental", "underscore-wildcards"]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[features] [features]
@ -71,6 +71,11 @@ rpk = []
# can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH`. # can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH`.
pq-experimental = [] 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.
underscore-wildcards = []
[build-dependencies] [build-dependencies]
bindgen = { workspace = true } bindgen = { workspace = true }
cmake = { workspace = true } cmake = { workspace = true }

743
boring-sys/build.rs Normal file
View File

@ -0,0 +1,743 @@
use fslock::LockFile;
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::sync::Once;
// NOTE: this build script is adopted from quiche (https://github.com/cloudflare/quiche)
// Additional parameters for Android build of BoringSSL.
//
// Android NDK < 18 with GCC.
const CMAKE_PARAMS_ANDROID_NDK_OLD_GCC: &[(&str, &[(&str, &str)])] = &[
(
"aarch64",
&[("ANDROID_TOOLCHAIN_NAME", "aarch64-linux-android-4.9")],
),
(
"arm",
&[("ANDROID_TOOLCHAIN_NAME", "arm-linux-androideabi-4.9")],
),
(
"x86",
&[("ANDROID_TOOLCHAIN_NAME", "x86-linux-android-4.9")],
),
(
"x86_64",
&[("ANDROID_TOOLCHAIN_NAME", "x86_64-linux-android-4.9")],
),
];
// Android NDK >= 19.
const CMAKE_PARAMS_ANDROID_NDK: &[(&str, &[(&str, &str)])] = &[
("aarch64", &[("ANDROID_ABI", "arm64-v8a")]),
("arm", &[("ANDROID_ABI", "armeabi-v7a")]),
("x86", &[("ANDROID_ABI", "x86")]),
("x86_64", &[("ANDROID_ABI", "x86_64")]),
];
fn cmake_params_android() -> &'static [(&'static str, &'static str)] {
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let cmake_params_android = if cfg!(feature = "ndk-old-gcc") {
CMAKE_PARAMS_ANDROID_NDK_OLD_GCC
} else {
CMAKE_PARAMS_ANDROID_NDK
};
for (android_arch, params) in cmake_params_android {
if *android_arch == arch {
return params;
}
}
&[]
}
const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[
// iOS
(
"aarch64-apple-ios",
&[
("CMAKE_OSX_ARCHITECTURES", "arm64"),
("CMAKE_OSX_SYSROOT", "iphoneos"),
],
),
(
"aarch64-apple-ios-sim",
&[
("CMAKE_OSX_ARCHITECTURES", "arm64"),
("CMAKE_OSX_SYSROOT", "iphonesimulator"),
],
),
(
"x86_64-apple-ios",
&[
("CMAKE_OSX_ARCHITECTURES", "x86_64"),
("CMAKE_OSX_SYSROOT", "iphonesimulator"),
],
),
// macOS
(
"aarch64-apple-darwin",
&[
("CMAKE_OSX_ARCHITECTURES", "arm64"),
("CMAKE_OSX_SYSROOT", "macosx"),
],
),
(
"x86_64-apple-darwin",
&[
("CMAKE_OSX_ARCHITECTURES", "x86_64"),
("CMAKE_OSX_SYSROOT", "macosx"),
],
),
];
fn cmake_params_apple() -> &'static [(&'static str, &'static str)] {
let target = env::var("TARGET").unwrap();
for (next_target, params) in CMAKE_PARAMS_APPLE {
if *next_target == target {
return params;
}
}
&[]
}
fn get_apple_sdk_name() -> &'static str {
for (name, value) in cmake_params_apple() {
if *name == "CMAKE_OSX_SYSROOT" {
return value;
}
}
let target = env::var("TARGET").unwrap();
panic!("cannot find SDK for {} in CMAKE_PARAMS_APPLE", target);
}
/// Returns an absolute path to the BoringSSL source.
fn get_boringssl_source_path() -> String {
#[cfg(feature = "fips")]
const SUBMODULE_DIR: &str = "boringssl-fips";
#[cfg(not(feature = "fips"))]
const SUBMODULE_DIR: &str = "boringssl";
static COPY_SOURCES: Once = Once::new();
if let Ok(src_path) = env::var("BORING_BSSL_SOURCE_PATH") {
return src_path;
}
let out_dir = env::var("OUT_DIR").unwrap();
let src_path = Path::new(&out_dir).join(SUBMODULE_DIR);
COPY_SOURCES.call_once(|| {
let submodule_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("deps")
.join(SUBMODULE_DIR);
if !submodule_path.join("CMakeLists.txt").exists() {
println!("cargo:warning=fetching boringssl git submodule");
run_command(Command::new("git").args([
"submodule",
"update",
"--init",
"--recursive",
&submodule_path.display().to_string(),
]))
.unwrap();
}
let _ = fs::remove_dir_all(&src_path);
fs_extra::dir::copy(submodule_path, &out_dir, &Default::default()).unwrap();
// NOTE: .git can be both file and dir, depening on whether it was copied from a submodule
// or created by the patches code.
let src_git_path = src_path.join(".git");
let _ = fs::remove_file(&src_git_path);
let _ = fs::remove_dir_all(&src_git_path);
});
src_path.display().to_string()
}
/// Returns the platform-specific output path for lib.
///
/// MSVC generator on Windows place static libs in a target sub-folder,
/// so adjust library location based on platform and build target.
/// See issue: https://github.com/alexcrichton/cmake-rs/issues/18
fn get_boringssl_platform_output_path() -> String {
if cfg!(target_env = "msvc") {
// Code under this branch should match the logic in cmake-rs
let debug_env_var = env::var("DEBUG").expect("DEBUG variable not defined in env");
let deb_info = match &debug_env_var[..] {
"false" => false,
"true" => true,
unknown => panic!("Unknown DEBUG={} env var.", unknown),
};
let opt_env_var = env::var("OPT_LEVEL").expect("OPT_LEVEL variable not defined in env");
let subdir = match &opt_env_var[..] {
"0" => "Debug",
"1" | "2" | "3" => {
if deb_info {
"RelWithDebInfo"
} else {
"Release"
}
}
"s" | "z" => "MinSizeRel",
unknown => panic!("Unknown OPT_LEVEL={} env var.", unknown),
};
subdir.to_string()
} else {
"".to_string()
}
}
/// Returns a new cmake::Config for building BoringSSL.
///
/// It will add platform-specific parameters if needed.
fn get_boringssl_cmake_config() -> cmake::Config {
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let host = env::var("HOST").unwrap();
let target = env::var("TARGET").unwrap();
let pwd = std::env::current_dir().unwrap();
let src_path = get_boringssl_source_path();
let mut boringssl_cmake = cmake::Config::new(&src_path);
if host != target {
// Add platform-specific parameters for cross-compilation.
match os.as_ref() {
"android" => {
// We need ANDROID_NDK_HOME to be set properly.
println!("cargo:rerun-if-env-changed=ANDROID_NDK_HOME");
let android_ndk_home = env::var("ANDROID_NDK_HOME")
.expect("Please set ANDROID_NDK_HOME for Android build");
let android_ndk_home = std::path::Path::new(&android_ndk_home);
for (name, value) in cmake_params_android() {
eprintln!("android arch={} add {}={}", arch, name, value);
boringssl_cmake.define(name, value);
}
let toolchain_file = android_ndk_home.join("build/cmake/android.toolchain.cmake");
let toolchain_file = toolchain_file.to_str().unwrap();
eprintln!("android toolchain={}", toolchain_file);
boringssl_cmake.define("CMAKE_TOOLCHAIN_FILE", toolchain_file);
// 21 is the minimum level tested. You can give higher value.
boringssl_cmake.define("ANDROID_NATIVE_API_LEVEL", "21");
boringssl_cmake.define("ANDROID_STL", "c++_shared");
}
"macos" => {
for (name, value) in cmake_params_apple() {
eprintln!("macos arch={} add {}={}", arch, name, value);
boringssl_cmake.define(name, value);
}
}
"ios" => {
for (name, value) in cmake_params_apple() {
eprintln!("ios arch={} add {}={}", arch, name, value);
boringssl_cmake.define(name, value);
}
// Bitcode is always on.
let bitcode_cflag = "-fembed-bitcode";
// Hack for Xcode 10.1.
let target_cflag = if arch == "x86_64" {
"-target x86_64-apple-ios-simulator"
} else {
""
};
let cflag = format!("{} {}", bitcode_cflag, target_cflag);
boringssl_cmake.define("CMAKE_ASM_FLAGS", &cflag);
boringssl_cmake.cflag(&cflag);
}
"windows" => {
if host.contains("windows") {
// BoringSSL's CMakeLists.txt isn't set up for cross-compiling using Visual Studio.
// Disable assembly support so that it at least builds.
boringssl_cmake.define("OPENSSL_NO_ASM", "YES");
}
}
"linux" => match arch.as_str() {
"x86" => {
boringssl_cmake.define(
"CMAKE_TOOLCHAIN_FILE",
pwd.join(&src_path)
.join("src/util/32-bit-toolchain.cmake")
.as_os_str(),
);
}
"aarch64" => {
boringssl_cmake.define(
"CMAKE_TOOLCHAIN_FILE",
pwd.join("cmake/aarch64-linux.cmake").as_os_str(),
);
}
"arm" => {
boringssl_cmake.define(
"CMAKE_TOOLCHAIN_FILE",
pwd.join("cmake/armv7-linux.cmake").as_os_str(),
);
}
_ => {
eprintln!(
"warning: no toolchain file configured by boring-sys for {}",
target
);
}
},
_ => {}
}
}
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) -> String {
let output = match Command::new(tool).arg("--version").output() {
Ok(o) => o,
Err(e) => {
eprintln!("warning: missing {}, trying other compilers: {}", tool, e);
// NOTE: hard-codes that the loop below checks the version
return String::new();
}
};
if !output.status.success() {
return String::new();
}
let output = std::str::from_utf8(&output.stdout).expect("invalid utf8 output");
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 cc_version = version(cc);
if cc_version.contains(REQUIRED_CLANG_VERSION) {
assert!(
version(cxx).contains(REQUIRED_CLANG_VERSION),
"mismatched versions of cc and c++"
);
return (cc, cxx);
} else if cc == "cc" {
panic!(
"unsupported clang version \"{}\": FIPS requires clang {}",
cc_version, REQUIRED_CLANG_VERSION
);
} else if !cc_version.is_empty() {
eprintln!(
"warning: FIPS requires clang version {}, skipping incompatible version \"{}\"",
REQUIRED_CLANG_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.
// https://developer.android.com/ndk/guides/other_build_systems
for known_toolchain in ["linux-x86_64", "darwin-x86_64", "windows-x86_64"] {
if let Some(toolchain) = toolchains
.iter()
.find(|entry| entry.file_name() == known_toolchain)
{
return Ok(toolchain.file_name());
}
}
// Then fall back to any subdirectory, in case Google has added support for a new host.
// (Maybe there's a linux-aarch64 toolchain now.)
if let Some(toolchain) = toolchains
.into_iter()
.find(|entry| entry.file_type().map(|ty| ty.is_dir()).unwrap_or(false))
{
return Ok(toolchain.file_name());
}
// Finally give up.
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"no subdirectories at given path",
))
}
fn get_extra_clang_args_for_bindgen() -> Vec<String> {
let os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let mut params = Vec::new();
// Add platform-specific parameters.
#[allow(clippy::single_match)]
match os.as_ref() {
"ios" | "macos" => {
// When cross-compiling for Apple targets, tell bindgen to use SDK sysroot,
// and *don't* use system headers of the host macOS.
let sdk = get_apple_sdk_name();
let output = std::process::Command::new("xcrun")
.args(["--show-sdk-path", "--sdk", sdk])
.output()
.unwrap();
if !output.status.success() {
if let Some(exit_code) = output.status.code() {
eprintln!("xcrun failed: exit code {}", exit_code);
} else {
eprintln!("xcrun failed: killed");
}
std::io::stderr().write_all(&output.stderr).unwrap();
// Uh... let's try anyway, I guess?
return params;
}
let mut sysroot = String::from_utf8(output.stdout).unwrap();
// There is typically a newline at the end which confuses clang.
sysroot.truncate(sysroot.trim_end().len());
params.push("-isysroot".to_string());
params.push(sysroot);
}
"android" => {
let android_ndk_home = env::var("ANDROID_NDK_HOME")
.expect("Please set ANDROID_NDK_HOME for Android build");
let mut android_sysroot = std::path::PathBuf::from(android_ndk_home);
android_sysroot.extend(["toolchains", "llvm", "prebuilt"]);
let toolchain = match pick_best_android_ndk_toolchain(&android_sysroot) {
Ok(toolchain) => toolchain,
Err(e) => {
eprintln!(
"warning: failed to find prebuilt Android NDK toolchain for bindgen: {}",
e
);
// Uh... let's try anyway, I guess?
return params;
}
};
android_sysroot.push(toolchain);
android_sysroot.push("sysroot");
params.push("--sysroot".to_string());
// If ANDROID_NDK_HOME weren't a valid UTF-8 string,
// we'd already know from env::var.
params.push(android_sysroot.into_os_string().into_string().unwrap());
}
_ => {}
}
params
}
fn ensure_patches_applied() -> io::Result<()> {
let out_dir = env::var("OUT_DIR").unwrap();
let mut lock_file = LockFile::open(&PathBuf::from(&out_dir).join(".patch_lock"))?;
let src_path = get_boringssl_source_path();
let has_git = Path::new(&src_path).join(".git").exists();
lock_file.lock()?;
// NOTE: init git in the copied files, so we can apply patches
if !has_git {
run_command(Command::new("git").args(["init"]).current_dir(&src_path))?;
}
if cfg!(feature = "pq-experimental") {
println!("cargo:warning=applying experimental post quantum crypto patch to boringssl");
apply_patch("boring-pq.patch")?;
}
if cfg!(feature = "rpk") {
println!("cargo:warning=applying RPK patch to boringssl");
apply_patch("rpk.patch")?;
}
Ok(())
}
fn apply_patch(patch_name: &str) -> io::Result<()> {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let src_path = get_boringssl_source_path();
let cmd_path = manifest_dir
.join("patches")
.join(patch_name)
.canonicalize()?;
run_command(
Command::new("git")
.args([
"apply",
"-v",
"--whitespace=fix",
&cmd_path.display().to_string(),
])
.current_dir(src_path),
)?;
Ok(())
}
fn run_command(command: &mut Command) -> io::Result<Output> {
let out = command.output()?;
println!("{}", std::str::from_utf8(&out.stdout).unwrap());
eprintln!("{}", std::str::from_utf8(&out.stderr).unwrap());
if !out.status.success() {
let err = match out.status.code() {
Some(code) => format!("{:?} exited with status: {}", command, code),
None => format!("{:?} was terminated by signal", command),
};
return Err(io::Error::new(io::ErrorKind::Other, err));
}
Ok(out)
}
fn build_boring_from_sources() -> String {
if cfg!(feature = "no-patches") {
println!(
"cargo:warning=skipping git patches application, provided\
native BoringSSL is expected to have the patches included"
);
} else {
ensure_patches_applied().unwrap();
}
let mut cfg = get_boringssl_cmake_config();
if cfg!(feature = "fuzzing") {
cfg.cxxflag("-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE")
.cxxflag("-DBORINGSSL_UNSAFE_FUZZER_MODE");
}
if cfg!(feature = "fips") {
let (clang, clangxx) = verify_fips_clang_version();
cfg.define("CMAKE_C_COMPILER", clang);
cfg.define("CMAKE_CXX_COMPILER", clangxx);
cfg.define("CMAKE_ASM_COMPILER", clang);
cfg.define("FIPS", "1");
}
if cfg!(feature = "fips-link-precompiled") {
cfg.define("FIPS", "1");
}
cfg.build_target("ssl").build();
cfg.build_target("crypto").build().display().to_string()
}
fn link_in_precompiled_bcm_o(bssl_dir: &str) {
println!("cargo:warning=linking in precompiled `bcm.o` module");
let bcm_o_src_path = env::var("BORING_SSL_PRECOMPILED_BCM_O")
.expect("`fips-link-precompiled` requires `BORING_SSL_PRECOMPILED_BCM_O` env variable to be specified");
let libcrypto_path = PathBuf::from(bssl_dir)
.join("build/crypto/libcrypto.a")
.canonicalize()
.unwrap()
.display()
.to_string();
let bcm_o_dst_path = PathBuf::from(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").args(["t", &libcrypto_path, "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",
&libcrypto_path,
bcm_o_dst_path.display().to_string().as_str(),
]))
.unwrap();
}
fn check_feature_compatibility() {
#[cfg(all(feature = "fips", feature = "rpk"))]
compile_error!("`fips` and `rpk` features are mutually exclusive");
let no_patches_enabled = cfg!(feature = "no-patches");
let is_external_native_lib_source =
env::var("BORING_BSSL_PATH").is_err() && env::var("BORING_BSSL_SOURCE_PATH").is_err();
if no_patches_enabled && is_external_native_lib_source {
panic!(
"`no-patches` feature is supposed to be used with `BORING_BSSL_PATH`\
or `BORING_BSSL_SOURCE_PATH` env variables"
)
}
let features_with_patches_enabled = cfg!(any(feature = "rpk", feature = "pq-experimental"));
let patches_required = features_with_patches_enabled && !no_patches_enabled;
let build_from_sources_required = cfg!(feature = "fips-link-precompiled") || patches_required;
let is_precompiled_native_lib = env::var("BORING_BSSL_PATH").is_ok();
if is_precompiled_native_lib && build_from_sources_required {
panic!("precompiled BoringSSL was provided, so FIPS configuration or optional patches can't be applied");
}
}
fn main() {
println!("cargo:rerun-if-env-changed=BORING_BSSL_PATH");
println!("cargo:rerun-if-env-changed=BORING_BSSL_INCLUDE_PATH");
println!("cargo:rerun-if-env-changed=BORING_BSSL_SOURCE_PATH");
println!("cargo:rerun-if-env-changed=BORING_SSL_PRECOMPILED_BCM_O");
println!("cargo:rerun-if-env-changed=BORINGSSL_BUILD_DIR");
check_feature_compatibility();
let bssl_dir = env::var("BORING_BSSL_PATH").unwrap_or_else(|_| build_boring_from_sources());
let build_path = get_boringssl_platform_output_path();
if cfg!(feature = "fips") {
println!(
"cargo:rustc-link-search=native={}/build/crypto/{}",
bssl_dir, build_path
);
println!(
"cargo:rustc-link-search=native={}/build/ssl/{}",
bssl_dir, build_path
);
println!(
"cargo:rustc-link-search=native={}/lib/{}",
bssl_dir, build_path
);
} else {
println!(
"cargo:rustc-link-search=native={}/build/{}",
bssl_dir, build_path
);
}
if cfg!(feature = "fips-link-precompiled") {
link_in_precompiled_bcm_o(&bssl_dir);
}
println!("cargo:rustc-link-lib=static=crypto");
println!("cargo:rustc-link-lib=static=ssl");
let include_path = env::var("BORING_BSSL_INCLUDE_PATH").unwrap_or_else(|_| {
if let Ok(bssl_path) = env::var("BORING_BSSL_PATH") {
return format!("{}/include", bssl_path);
}
let src_path = get_boringssl_source_path();
if Path::new(&src_path).join("include").exists() {
format!("{}/include", &src_path)
} else {
format!("{}/src/include", &src_path)
}
});
let mut builder = bindgen::Builder::default()
.derive_copy(true)
.derive_debug(true)
.derive_default(true)
.derive_eq(true)
.default_enum_style(bindgen::EnumVariation::NewType {
is_bitfield: false,
is_global: false,
})
.default_macro_constant_type(bindgen::MacroTypeVariation::Signed)
.generate_comments(true)
.fit_macro_constants(false)
.size_t_is_usize(true)
.layout_tests(true)
.prepend_enum_name(true)
.clang_args(get_extra_clang_args_for_bindgen())
.clang_args(&["-I", &include_path]);
let target = env::var("TARGET").unwrap();
match target.as_ref() {
// bindgen produces alignment tests that cause undefined behavior [1]
// when applied to explicitly unaligned types like OSUnalignedU64.
//
// There is no way to disable these tests for only some types
// and it's not nice to suppress warnings for the entire crate,
// so let's disable all alignment tests and hope for the best.
//
// [1]: https://github.com/rust-lang/rust-bindgen/issues/1651
"aarch64-apple-ios" | "aarch64-apple-ios-sim" => {
builder = builder.layout_tests(false);
}
_ => {}
}
let headers = [
"aes.h",
"asn1_mac.h",
"asn1t.h",
"blake2.h",
"blowfish.h",
"cast.h",
"chacha.h",
"cmac.h",
"cpu.h",
"curve25519.h",
"des.h",
"dtls1.h",
"hkdf.h",
"hmac.h",
"hrss.h",
"md4.h",
"md5.h",
"obj_mac.h",
"objects.h",
"opensslv.h",
"ossl_typ.h",
"pkcs12.h",
"poly1305.h",
"rand.h",
"rc4.h",
"ripemd.h",
"siphash.h",
"srtp.h",
"trust_token.h",
"x509v3.h",
];
for header in &headers {
builder = builder.header(
Path::new(&include_path)
.join("openssl")
.join(header)
.to_str()
.unwrap(),
);
}
let bindings = builder.generate().expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}

View File

@ -18,6 +18,7 @@ pub(crate) struct Features {
pub(crate) fips_link_precompiled: bool, pub(crate) fips_link_precompiled: bool,
pub(crate) pq_experimental: bool, pub(crate) pq_experimental: bool,
pub(crate) rpk: bool, pub(crate) rpk: bool,
pub(crate) underscore_wildcards: bool,
} }
pub(crate) struct Env { pub(crate) struct Env {
@ -82,7 +83,10 @@ impl Config {
); );
} }
let features_with_patches_enabled = self.features.rpk || self.features.pq_experimental; let features_with_patches_enabled = self.features.rpk
|| self.features.pq_experimental
|| self.features.underscore_wildcards;
let patches_required = features_with_patches_enabled && !self.env.assume_patched; let patches_required = features_with_patches_enabled && !self.env.assume_patched;
let build_from_sources_required = self.features.fips_link_precompiled || patches_required; let build_from_sources_required = self.features.fips_link_precompiled || patches_required;
@ -98,12 +102,14 @@ impl Features {
let fips_link_precompiled = env::var_os("CARGO_FEATURE_FIPS_LINK_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 pq_experimental = env::var_os("CARGO_FEATURE_PQ_EXPERIMENTAL").is_some();
let rpk = env::var_os("CARGO_FEATURE_RPK").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 { Self {
fips, fips,
fips_link_precompiled, fips_link_precompiled,
pq_experimental, pq_experimental,
rpk, rpk,
underscore_wildcards,
} }
} }
} }

View File

@ -491,6 +491,11 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> {
apply_patch(config, "rpk.patch")?; apply_patch(config, "rpk.patch")?;
} }
if config.features.underscore_wildcards {
println!("cargo:warning=applying underscore wildcards patch to boringssl");
apply_patch(config, "underscore-wildcards.patch")?;
}
Ok(()) Ok(())
} }

View File

@ -0,0 +1,61 @@
https://github.com/google/boringssl/compare/master...cloudflare:boringssl:underscore-wildcards
--- a/src/crypto/x509v3/v3_utl.c
+++ b/src/crypto/x509v3/v3_utl.c
@@ -790,7 +790,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len,
// Check that the part matched by the wildcard contains only
// permitted characters and only matches a single label.
for (p = wildcard_start; p != wildcard_end; ++p) {
- if (!OPENSSL_isalnum(*p) && *p != '-') {
+ if (!OPENSSL_isalnum(*p) && *p != '-' &&
+ !(*p == '_' &&
+ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) {
return 0;
}
}
--- a/src/crypto/x509/x509_test.cc
+++ b/src/crypto/x509/x509_test.cc
@@ -4500,6 +4500,31 @@ TEST(X509Test, Names) {
/*invalid_emails=*/{},
/*flags=*/0,
},
+
+ // Underscores in DNS names are forbidden by default.
+ {
+ /*cert_subject=*/{},
+ /*cert_dns_names=*/{"*.example.com"},
+ /*cert_emails=*/{},
+ /*valid_dns_names=*/{},
+ /*invalid_dns_names=*/{"not_allowed.example.com"},
+ /*valid_emails=*/{},
+ /*invalid_emails=*/{},
+ /*flags=*/0,
+ },
+
+ // Underscores in DNS names can be allowed with the right flag.
+ {
+ /*cert_subject=*/{},
+ /*cert_dns_names=*/{"*.example.com"},
+ /*cert_emails=*/{},
+ /*valid_dns_names=*/{"now_allowed.example.com"},
+ /*invalid_dns_names=*/{},
+ /*valid_emails=*/{},
+ /*invalid_emails=*/{},
+ /*flags=*/X509_CHECK_FLAG_UNDERSCORE_WILDCARDS,
+ },
+
};
size_t i = 0;
--- a/src/include/openssl/x509c3.h
+++ b/src/include/openssl/x509v3.h
@@ -4497,6 +4497,8 @@ OPENSSL_EXPORT int X509_PURPOSE_get_id(const X509_PURPOSE *);
#define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0
// Skip the subject common name fallback if subjectAltNames is missing.
#define X509_CHECK_FLAG_NEVER_CHECK_SUBJECT 0x20
+// Allow underscores in DNS wildcard matches.
+#define X509_CHECK_FLAG_UNDERSCORE_WILDCARDS 0x40
OPENSSL_EXPORT int X509_check_host(X509 *x, const char *chk, size_t chklen,
unsigned int flags, char **peername);
--

View File

@ -12,7 +12,7 @@ categories = ["cryptography", "api-bindings"]
edition = { workspace = true } edition = { workspace = true }
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["rpk", "pq-experimental"] features = ["rpk", "pq-experimental", "underscore-wildcards"]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[features] [features]
@ -38,6 +38,11 @@ rpk = ["boring-sys/rpk"]
# `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`. # `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`.
pq-experimental = ["boring-sys/pq-experimental"] 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.
underscore-wildcards = ["boring-sys/underscore-wildcards"]
# Controlling key exchange preferences at compile time # Controlling key exchange preferences at compile time
# Choose key exchange preferences at compile time. This prevents the user from # Choose key exchange preferences at compile time. This prevents the user from

View File

@ -503,12 +503,81 @@ fn verify_valid_hostname() {
client.ctx().set_verify(SslVerifyMode::PEER); client.ctx().set_verify(SslVerifyMode::PEER);
let mut client = client.build().builder();
client.ssl().param_mut().set_host("foobar.com").unwrap();
client.connect();
}
#[test]
fn verify_valid_hostname_with_wildcard() {
let mut server = Server::builder();
server
.ctx()
.set_certificate_chain_file("test/cert-wildcard.pem")
.unwrap();
let server = server.build();
let mut client = server.client_with_root_ca();
client.ctx().set_verify(SslVerifyMode::PEER);
let mut client = client.build().builder();
client.ssl().param_mut().set_host("yes.foobar.com").unwrap();
client.connect();
}
#[test]
fn verify_reject_underscore_hostname_with_wildcard() {
let mut server = Server::builder();
server.should_error();
server
.ctx()
.set_certificate_chain_file("test/cert-wildcard.pem")
.unwrap();
let server = server.build();
let mut client = server.client_with_root_ca();
client.ctx().set_verify(SslVerifyMode::PEER);
let mut client = client.build().builder(); let mut client = client.build().builder();
client client
.ssl() .ssl()
.param_mut() .param_mut()
.set_hostflags(X509CheckFlags::NO_PARTIAL_WILDCARDS); .set_host("not_allowed.foobar.com")
client.ssl().param_mut().set_host("foobar.com").unwrap(); .unwrap();
client.connect_err();
}
#[cfg(feature = "underscore-wildcards")]
#[test]
fn verify_allow_underscore_hostname_with_wildcard() {
let mut server = Server::builder();
server
.ctx()
.set_certificate_chain_file("test/cert-wildcard.pem")
.unwrap();
let server = server.build();
let mut client = server.client_with_root_ca();
client.ctx().set_verify(SslVerifyMode::PEER);
let mut client = client.build().builder();
client
.ssl()
.param_mut()
.set_hostflags(X509CheckFlags::UNDERSCORE_WILDCARDS);
client
.ssl()
.param_mut()
.set_host("now_allowed.foobar.com")
.unwrap();
client.connect(); client.connect();
} }

View File

@ -16,6 +16,8 @@ bitflags! {
const MULTI_LABEL_WILDCARDS = ffi::X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS as _; const MULTI_LABEL_WILDCARDS = ffi::X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS as _;
const SINGLE_LABEL_SUBDOMAINS = ffi::X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS as _; const SINGLE_LABEL_SUBDOMAINS = ffi::X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS as _;
const NEVER_CHECK_SUBJECT = ffi::X509_CHECK_FLAG_NEVER_CHECK_SUBJECT as _; const NEVER_CHECK_SUBJECT = ffi::X509_CHECK_FLAG_NEVER_CHECK_SUBJECT as _;
#[cfg(feature = "underscore-wildcards")]
const UNDERSCORE_WILDCARDS = ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS as _;
#[deprecated(since = "0.10.6", note = "renamed to NO_WILDCARDS")] #[deprecated(since = "0.10.6", note = "renamed to NO_WILDCARDS")]
const FLAG_NO_WILDCARDS = ffi::X509_CHECK_FLAG_NO_WILDCARDS as _; const FLAG_NO_WILDCARDS = ffi::X509_CHECK_FLAG_NO_WILDCARDS as _;

View File

@ -0,0 +1,20 @@
notAfter=Aug 12 11:30:03 2026 GMT
-----BEGIN CERTIFICATE-----
MIIDKDCCAhACFGwwuilXOHjBjQ584FD9drp9Uh/LMA0GCSqGSIb3DQEBCwUAMEUx
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjMxMjE4MTEzMDAzWhcNMjYwODEyMTEz
MDAzWjBcMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE
BwwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRUwEwYDVQQDDAwqLmZvb2Jhci5j
b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCo9CWMRLMXo1CF/iOR
h9B4NhtJF/8tR9PlG95sNvyWuQQ/8jfev+8zErplxfLkt0pJqcoiZG8g9NU0kU6o
5T+/1QgZclCAoZaS0Jqxmoo2Yk/1Qsj16pnMBc10uSDk6V9aJSX1vKwONVNSwiHA
1MhX+i7Wf7/K0niq+k7hOkhleFkWgZtUq41gXh1VfOugka7UktYnk9mrBbAMjmal
oZNn2pMMAQxVg4ThiLm3zvuWqvXASWzUZc7IAd1GbN4AtDuhs252eqE9E4iTHk7F
14wAS1JWqv666hReGHrmZJGx0xQTM9vPD1HN5t2U3KTfhO/mTlAUWVyg9tCtOzbo
Kgs1AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHG83qKMl5bPoL2s7TaJZ909NaQO
4C69ueXlD4HJEFe7L9mkeQoDaF7RwWSBwN2RZT5hzQhghRotqLA06XwKbQHji/R7
sYYVUHunobFUHsr51tFN1BIDoAWJa0N2rm/OxbcK471eWNKjMiS2vvvPdaMxxHAx
IsjAJBJec4IxNIUNNKqCS/xNYcdiyrmmU3oFWGqb0As/eDOBw0Amd0aayasFJrRV
3KZI5OcFg/J3XvdaxMJD+RPyUysKRXg6K8jzYc/PB8LhWVXpLxjEzeO2IHCaZprh
dUTP8+Ob+ioxujvlslxc4nrrUD5EWwnpEIr7e4af27JHQVaNyHbRw6wI2uk=
-----END CERTIFICATE-----