From 721b6fca2ed9c50eefe6f8cdab2de5b49ba2fb6a Mon Sep 17 00:00:00 2001 From: Rushil Mehra <84047965+rushilmehra@users.noreply.github.com> Date: Mon, 31 Mar 2025 12:34:29 -0700 Subject: [PATCH 01/18] Add fips-precompiled feature to support newer versions of FIPS (#338) Newer versions of FIPS don't need any special casing in our bindings, unlike the submoduled boringssl-fips. In addition, many users currently use FIPS by precompiling BoringSSL with the proper build tools and passing that in to the bindings. Until we adopt the Update Stream pattern for FIPS, there are two main use cases: 1. Passing an unmodified, precompiled FIPS validated version of boringssl (fips-precompiled) 2. Passing a custom source directory of boringssl meant to be linked with a FIPS validated bcm.o. This is mainly useful if you carry custom patches but still want to use a FIPS validated BoringCrypto. (fips-link-precompiled) This commit introduces the `fips-precompiled` feature and removes the `fips-no-compat` feature. --- boring-sys/Cargo.toml | 12 +++++++++- boring-sys/build/config.rs | 24 +++++++++++++++---- boring-sys/build/main.rs | 5 ++-- boring/Cargo.toml | 31 +++++++++++++------------ boring/src/fips.rs | 4 ++-- boring/src/lib.rs | 2 +- boring/src/ssl/mod.rs | 47 +++++++++++++++++++++++++++----------- boring/src/ssl/test/mod.rs | 6 ++--- hyper-boring/Cargo.toml | 2 +- tokio-boring/Cargo.toml | 2 +- 10 files changed, 92 insertions(+), 43 deletions(-) diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index c0a45aec..5cd8e214 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -57,9 +57,19 @@ features = ["rpk", "pq-experimental", "underscore-wildcards"] rustdoc-args = ["--cfg", "docsrs"] [features] -# Use a FIPS-validated version of boringssl. +# Compile boringssl using the FIPS build flag if building boringssl from +# scratch. +# +# See +# https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/FIPS.md +# 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 = [] diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index 19a63f52..353f4d20 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -16,6 +16,7 @@ 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, @@ -47,11 +48,7 @@ impl Config { let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); let features = Features::from_env(); - let env = Env::from_env( - &host, - &target, - features.fips || features.fips_link_precompiled, - ); + let env = Env::from_env(&host, &target, features.is_fips_like()); let mut is_bazel = false; if let Some(src_path) = &env.source_path { @@ -80,6 +77,10 @@ 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(); @@ -103,15 +104,22 @@ impl Config { ); } + // 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(); @@ -119,12 +127,17 @@ impl Features { 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 + } } impl Env { @@ -138,6 +151,7 @@ impl Env { let target_var = |name: &str| { let kind = if host == target { "HOST" } else { "TARGET" }; + // TODO(rmehra): look for just `name` first, as most people just set that var(&format!("{}_{}", name, target)) .or_else(|| var(&format!("{}_{}", name, target_with_underscores))) .or_else(|| var(&format!("{}_{}", kind, name))) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 06756a7d..0f0b94c1 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -662,13 +662,14 @@ fn main() { let bssl_dir = built_boring_source_path(&config); let build_path = get_boringssl_platform_output_path(&config); - if config.is_bazel || (config.features.fips && config.env.path.is_some()) { + if config.is_bazel || (config.features.is_fips_like() && config.env.path.is_some()) { println!( "cargo:rustc-link-search=native={}/lib/{}", bssl_dir.display(), build_path ); } else { + // todo(rmehra): clean this up, I think these are pretty redundant println!( "cargo:rustc-link-search=native={}/build/crypto/{}", bssl_dir.display(), @@ -760,7 +761,7 @@ fn main() { "des.h", "dtls1.h", "hkdf.h", - #[cfg(not(any(feature = "fips", feature = "fips-no-compat")))] + #[cfg(not(feature = "fips"))] "hpke.h", "hmac.h", "hrss.h", diff --git a/boring/Cargo.toml b/boring/Cargo.toml index 491b1702..f9a3527b 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -19,24 +19,27 @@ rustdoc-args = ["--cfg", "docsrs"] [features] # Controlling the build -# Use a FIPS-validated version of BoringSSL. This feature sets "fips-compat". +# 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"] -# 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-no-compat = ["boring-sys/fips"] - -# Build with compatibility for the BoringSSL FIPS version, without enabling the -# `fips` feature itself (useful e.g. if `fips-link-precompiled` is used with an -# older BoringSSL version). +# 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 = [] +# 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"] diff --git a/boring/src/fips.rs b/boring/src/fips.rs index 29dbfae0..23fbefdf 100644 --- a/boring/src/fips.rs +++ b/boring/src/fips.rs @@ -16,13 +16,13 @@ pub fn enabled() -> bool { fn is_enabled() { #[cfg(any( feature = "fips", - feature = "fips-no-compat", + feature = "fips-precompiled", feature = "fips-link-precompiled" ))] assert!(enabled()); #[cfg(not(any( feature = "fips", - feature = "fips-no-compat", + feature = "fips-precompiled", feature = "fips-link-precompiled" )))] assert!(!enabled()); diff --git a/boring/src/lib.rs b/boring/src/lib.rs index a54e2bfa..e7b03ddb 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -135,7 +135,7 @@ pub mod error; pub mod ex_data; pub mod fips; pub mod hash; -#[cfg(not(any(feature = "fips", feature = "fips-no-compat")))] +#[cfg(not(feature = "fips"))] pub mod hpke; pub mod memcmp; pub mod nid; diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 3eaa2e83..66a11fbc 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -104,7 +104,7 @@ pub use self::async_callbacks::{ pub use self::connector::{ ConnectConfiguration, SslAcceptor, SslAcceptorBuilder, SslConnector, SslConnectorBuilder, }; -#[cfg(not(any(feature = "fips", feature = "fips-no-compat")))] +#[cfg(not(feature = "fips"))] pub use self::ech::{SslEchKeys, SslEchKeysRef}; pub use self::error::{Error, ErrorCode, HandshakeError}; @@ -112,7 +112,7 @@ mod async_callbacks; mod bio; mod callbacks; mod connector; -#[cfg(not(any(feature = "fips", feature = "fips-no-compat")))] +#[cfg(not(feature = "fips"))] mod ech; mod error; mod mut_only; @@ -714,19 +714,28 @@ impl SslCurve { pub const X25519: SslCurve = SslCurve(ffi::SSL_CURVE_X25519 as _); - #[cfg(not(any(feature = "fips", feature = "fips-no-compat")))] + #[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(feature = "fips"), feature = "pq-experimental"))] + #[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(feature = "fips"), feature = "pq-experimental"))] + #[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(feature = "fips"), feature = "pq-experimental"))] + #[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 _); /// Returns the curve name @@ -759,15 +768,27 @@ impl SslCurve { 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-no-compat")))] + #[cfg(not(any(feature = "fips", feature = "fips-precompiled")))] ffi::SSL_CURVE_X25519_KYBER768_DRAFT00 => Some(ffi::NID_X25519Kyber768Draft00), - #[cfg(all(not(feature = "fips"), feature = "pq-experimental"))] + #[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(feature = "fips"), feature = "pq-experimental"))] + #[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(feature = "fips"), feature = "pq-experimental"))] + #[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(feature = "fips"), feature = "pq-experimental"))] + #[cfg(all( + not(any(feature = "fips", feature = "fips-precompiled")), + feature = "pq-experimental" + ))] ffi::SSL_CURVE_X25519_MLKEM768 => Some(ffi::NID_X25519MLKEM768), _ => None, } @@ -2010,7 +2031,7 @@ 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(any(feature = "fips", feature = "fips-no-compat")))] + #[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(|_| ()) } @@ -2267,7 +2288,7 @@ 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(any(feature = "fips", feature = "fips-no-compat")))] + #[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(|_| ()) } diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index 69d0dbf6..4566b73c 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -21,13 +21,13 @@ use crate::ssl::{ use crate::x509::verify::X509CheckFlags; use crate::x509::{X509Name, X509}; -#[cfg(not(any(feature = "fips", feature = "fips-no-compat")))] +#[cfg(not(feature = "fips"))] use super::CompliancePolicy; mod cert_compressor; mod cert_verify; mod custom_verify; -#[cfg(not(any(feature = "fips", feature = "fips-no-compat")))] +#[cfg(not(feature = "fips"))] mod ech; mod private_key_method; mod server; @@ -990,7 +990,7 @@ fn test_get_ciphers() { } #[test] -#[cfg(not(any(feature = "fips", feature = "fips-no-compat")))] +#[cfg(not(feature = "fips"))] fn test_set_compliance() { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_compliance_policy(CompliancePolicy::FIPS_202205) diff --git a/hyper-boring/Cargo.toml b/hyper-boring/Cargo.toml index 7c9921f2..91f74424 100644 --- a/hyper-boring/Cargo.toml +++ b/hyper-boring/Cargo.toml @@ -31,7 +31,7 @@ fips = ["tokio-boring/fips"] # # TODO(cjpatton) Delete this feature and modify "fips" so that it doesn't imply # "fips-compat". -fips-no-compat = ["tokio-boring/fips-no-compat"] +fips-precompiled = ["tokio-boring/fips-precompiled"] # Link with precompiled FIPS-validated `bcm.o` module. fips-link-precompiled = ["tokio-boring/fips-link-precompiled"] diff --git a/tokio-boring/Cargo.toml b/tokio-boring/Cargo.toml index 2aed8fb5..75c64129 100644 --- a/tokio-boring/Cargo.toml +++ b/tokio-boring/Cargo.toml @@ -27,7 +27,7 @@ fips = ["boring/fips", "boring-sys/fips"] # # TODO(cjpatton) Delete this feature and modify "fips" so that it doesn't imply # "fips-compat". -fips-no-compat = ["boring/fips-no-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"] From 20ad2665b2affe354cf84e0514bedc609d618efe Mon Sep 17 00:00:00 2001 From: Rushil Mehra <84047965+rushilmehra@users.noreply.github.com> Date: Wed, 2 Apr 2025 18:26:29 -0700 Subject: [PATCH 02/18] Release 4.16.0 (#341) --- Cargo.toml | 8 ++++---- RELEASE_NOTES | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 719505ea..e8c3fd2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "4.15.0" +version = "4.16.0" repository = "https://github.com/cloudflare/boring" edition = "2021" @@ -19,9 +19,9 @@ tag-prefix = "" publish = false [workspace.dependencies] -boring-sys = { version = "4.15.0", path = "./boring-sys" } -boring = { version = "4.15.0", path = "./boring" } -tokio-boring = { version = "4.15.0", path = "./tokio-boring" } +boring-sys = { version = "4.16.0", path = "./boring-sys" } +boring = { version = "4.16.0", path = "./boring" } +tokio-boring = { version = "4.16.0", path = "./tokio-boring" } bindgen = { version = "0.70.1", default-features = false, features = ["runtime"] } bytes = "1" diff --git a/RELEASE_NOTES b/RELEASE_NOTES index c1ea9cf1..ca99ee4d 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,4 +1,16 @@ +4.16.0 +- 2025-03-31 Add fips-precompiled feature to support newer versions of FIPS (#338) +- 2025-03-18 Document linking to C++ standard library (#335) +- 2025-03-18 Revert "Remove "fips-no-compat", decouple "fips-compat" from "fips"" (#334) +- 2025-03-11 boring: Disable `SslCurve` API with "fips" feature +- 2025-03-11 boring-sys: Ignore patches when boringSSL is precompiled +- 2025-03-13 Remove "fips-no-compat", decouple "fips-compat" from "fips" +- 2025-03-14 Add feature "fips-no-compat" +- 2025-03-10 Advertise X25519MLKEM768 with "kx-client-pq-preferred" (#329) +- 2025-03-10 Update to actions/cache@v4 (#328) +- 2025-02-28 Add missing release notes entry (#324) + 4.15.0 - 2025-02-27 Expose API to enable certificate compression. (#241) - 2025-02-23 Fix lifetimes in ssl::select_next_proto From 49a8d0906a5b96ac0e06f6110c3fc49889c2f995 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Tue, 8 Apr 2025 01:05:27 +0800 Subject: [PATCH 03/18] feat(x509): Implement `Clone` for `X509Store` (#339) * boring(x509): impl Clone of X509Store --- boring/src/x509/store.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/boring/src/x509/store.rs b/boring/src/x509/store.rs index 0f626838..76edac15 100644 --- a/boring/src/x509/store.rs +++ b/boring/src/x509/store.rs @@ -125,6 +125,23 @@ foreign_type_and_impl_send_sync! { pub struct X509Store; } +impl Clone for X509Store { + fn clone(&self) -> Self { + (**self).to_owned() + } +} + +impl ToOwned for X509StoreRef { + type Owned = X509Store; + + fn to_owned(&self) -> Self::Owned { + unsafe { + ffi::X509_STORE_up_ref(self.as_ptr()); + X509Store::from_ptr(self.as_ptr()) + } + } +} + impl X509StoreRef { /// **Warning: this method is unsound** /// From 220bedf23965c52b56d7aa883c575cbac26141f7 Mon Sep 17 00:00:00 2001 From: Shih-Chiang Chien Date: Tue, 15 Apr 2025 11:49:47 +0900 Subject: [PATCH 04/18] expose SSL_set_compliance_policy --- boring/src/ssl/mod.rs | 7 ++++++ boring/src/ssl/test/mod.rs | 49 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 66a11fbc..b75d6975 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -3784,6 +3784,13 @@ impl SslRef { ffi::SSL_set_enable_ech_grease(self.as_ptr(), enable); } } + + /// 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(|_| ()) } + } } /// An SSL stream midway through the handshake process. diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index 4566b73c..fdcb7096 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -1070,3 +1070,52 @@ fn test_info_callback() { client.connect(); 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(); + let mut ssl = Ssl::new(&ctx).unwrap(); + ssl.set_compliance_policy(CompliancePolicy::FIPS_202205) + .unwrap(); + + assert_eq!(ssl.max_proto_version().unwrap(), SslVersion::TLS1_3); + assert_eq!(ssl.min_proto_version().unwrap(), SslVersion::TLS1_2); + + const FIPS_CIPHERS: [&str; 4] = [ + "ECDHE-ECDSA-AES128-GCM-SHA256", + "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-ECDSA-AES256-GCM-SHA384", + "ECDHE-RSA-AES256-GCM-SHA384", + ]; + + let ciphers = ssl.ciphers(); + assert_eq!(ciphers.len(), FIPS_CIPHERS.len()); + + for cipher in ciphers.into_iter().zip(FIPS_CIPHERS) { + assert_eq!(cipher.0.name(), cipher.1) + } + + let ctx = SslContext::builder(SslMethod::tls()).unwrap().build(); + let mut ssl = Ssl::new(&ctx).unwrap(); + ssl.set_compliance_policy(CompliancePolicy::WPA3_192_202304) + .unwrap(); + + assert_eq!(ssl.max_proto_version().unwrap(), SslVersion::TLS1_3); + assert_eq!(ssl.min_proto_version().unwrap(), SslVersion::TLS1_2); + + const WPA3_192_CIPHERS: [&str; 2] = [ + "ECDHE-ECDSA-AES256-GCM-SHA384", + "ECDHE-RSA-AES256-GCM-SHA384", + ]; + + let ciphers = ssl.ciphers(); + assert_eq!(ciphers.len(), WPA3_192_CIPHERS.len()); + + for cipher in ciphers.into_iter().zip(WPA3_192_CIPHERS) { + assert_eq!(cipher.0.name(), cipher.1) + } + + ssl.set_compliance_policy(CompliancePolicy::NONE) + .expect_err("Testing expect err if set compliance policy to NONE"); +} From b29537e08f42d1c0d2f2d55ee21a57db9a5c8eda Mon Sep 17 00:00:00 2001 From: Shih-Chiang Chien Date: Wed, 16 Apr 2025 09:38:47 +0900 Subject: [PATCH 05/18] fix clippy error --- boring/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/boring/src/lib.rs b/boring/src/lib.rs index e7b03ddb..aaa268b6 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -88,10 +88,10 @@ //! before the end of 2024. //! - `X25519Kyber768Draft00Old` is the same as `X25519Kyber768Draft00`, but under its old codepoint. //! - `X25519Kyber512Draft00`. Similar to `X25519Kyber768Draft00`, but uses level 1 parameter set for -//! Kyber. Not recommended. It's useful to test whether the shorter ClientHello upsets fewer middle -//! boxes. +//! Kyber. Not recommended. It's useful to test whether the shorter ClientHello upsets fewer middle +//! boxes. //! - `P256Kyber768Draft00`. Similar again to `X25519Kyber768Draft00`, but uses P256 as classical -//! part. It uses a non-standard codepoint. Not recommended. +//! part. It uses a non-standard codepoint. Not recommended. //! //! Presently all these key agreements are deployed by Cloudflare, but we do not guarantee continued //! support for them. From 9c4ea22f728871b33737931cb8957da1bc81c613 Mon Sep 17 00:00:00 2001 From: Rushil Mehra Date: Wed, 16 Apr 2025 18:23:35 -0700 Subject: [PATCH 06/18] Use ubuntu-latest for all ci jobs ubuntu 20.04 is now deprecated: https://github.com/actions/runner-images/issues/11101 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0910a482..56e636e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -242,7 +242,7 @@ jobs: test-fips: name: Test FIPS integration - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: @@ -342,7 +342,7 @@ jobs: test-features: name: Test features - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: From 9b34d3524bfeecb97449119ed130c7eb4be416d4 Mon Sep 17 00:00:00 2001 From: Eric Rosenberg Date: Thu, 1 May 2025 15:07:14 +0000 Subject: [PATCH 07/18] add SslCurve::X25519_MLKEM768 constant --- boring/src/ssl/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index b75d6975..e1253e3b 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -738,6 +738,12 @@ impl SslCurve { ))] 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)] pub fn name(&self) -> Option<&'static str> { From 23863ffd1b4297818e4a6bcc504d893ec900d5ee Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 21 May 2025 00:02:16 +0100 Subject: [PATCH 08/18] Clippy --- boring-sys/build/main.rs | 2 +- boring/src/error.rs | 2 +- boring/src/ssl/mod.rs | 8 ++------ tokio-boring/src/lib.rs | 2 +- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 0f0b94c1..e14b932f 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -541,7 +541,7 @@ fn run_command(command: &mut Command) -> io::Result { None => format!("{:?} was terminated by signal", command), }; - return Err(io::Error::new(io::ErrorKind::Other, err)); + return Err(io::Error::other(err)); } Ok(out) diff --git a/boring/src/error.rs b/boring/src/error.rs index 9cdc878b..1f951341 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -79,7 +79,7 @@ impl error::Error for ErrorStack {} impl From for io::Error { fn from(e: ErrorStack) -> io::Error { - io::Error::new(io::ErrorKind::Other, e) + io::Error::other(e) } } diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index e1253e3b..762cb2d7 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -3943,9 +3943,7 @@ impl SslStream { } Err(ref e) if e.code() == ErrorCode::WANT_READ && e.io_error().is_none() => {} Err(e) => { - return Err(e - .into_io_error() - .unwrap_or_else(|e| io::Error::new(io::ErrorKind::Other, e))); + return Err(e.into_io_error().unwrap_or_else(io::Error::other)); } } } @@ -4167,9 +4165,7 @@ impl Write for SslStream { Ok(n) => return Ok(n), Err(ref e) if e.code() == ErrorCode::WANT_READ && e.io_error().is_none() => {} Err(e) => { - return Err(e - .into_io_error() - .unwrap_or_else(|e| io::Error::new(io::ErrorKind::Other, e))); + return Err(e.into_io_error().unwrap_or_else(io::Error::other)); } } } diff --git a/tokio-boring/src/lib.rs b/tokio-boring/src/lib.rs index f1593ed8..d0d1502e 100644 --- a/tokio-boring/src/lib.rs +++ b/tokio-boring/src/lib.rs @@ -242,7 +242,7 @@ where Err(e) => { return Poll::Ready(Err(e .into_io_error() - .unwrap_or_else(|e| io::Error::new(io::ErrorKind::Other, e)))); + .unwrap_or_else(io::Error::other))); } } From 0327dd03c63b72bb7b7a4c92f95227b10eb73072 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 20 May 2025 23:58:04 +0100 Subject: [PATCH 09/18] Fix linking SystemFunction036 from advapi32 in Rust 1.87 --- boring-sys/build/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index e14b932f..e397e710 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -701,6 +701,11 @@ fn main() { println!("cargo:rustc-link-lib=static=crypto"); println!("cargo:rustc-link-lib=static=ssl"); + if config.target_os == "windows" { + // Rust 1.87.0 compat - https://github.com/rust-lang/rust/pull/138233 + println!("cargo:rustc-link-lib=advapi32"); + } + let include_path = config.env.include_path.clone().unwrap_or_else(|| { if let Some(bssl_path) = &config.env.path { return bssl_path.join("include"); From 3ab8b53532edf5a399b63b221ed78a419ccbb3f6 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 21 May 2025 00:24:48 +0100 Subject: [PATCH 10/18] rustfmt ;( --- tokio-boring/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tokio-boring/src/lib.rs b/tokio-boring/src/lib.rs index d0d1502e..89d10127 100644 --- a/tokio-boring/src/lib.rs +++ b/tokio-boring/src/lib.rs @@ -240,9 +240,7 @@ where return Poll::Pending; } Err(e) => { - return Poll::Ready(Err(e - .into_io_error() - .unwrap_or_else(io::Error::other))); + return Poll::Ready(Err(e.into_io_error().unwrap_or_else(io::Error::other))); } } From eb48ab9a26887d0cbcb060129f60c2eacb162877 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Fri, 14 Feb 2025 02:29:06 +0800 Subject: [PATCH 11/18] build: Fix the build for 32-bit Linux platform --- boring-sys/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index 5cd8e214..ea91e6dc 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -18,6 +18,7 @@ include = [ "/*.toml", "/LICENSE-MIT", "/cmake/*.cmake", + "/deps/boringssl/src/util/32-bit-toolchain.cmake", # boringssl (non-FIPS) "/deps/boringssl/src/util/32-bit-toolchain.cmake", "/deps/boringssl/**/*.[chS]", From 15281c77e2fa4f9b1ac0872e3264847620ec0e08 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Fri, 14 Feb 2025 10:36:34 +0800 Subject: [PATCH 12/18] Update Cargo.toml --- boring-sys/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index ea91e6dc..5cd8e214 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -18,7 +18,6 @@ include = [ "/*.toml", "/LICENSE-MIT", "/cmake/*.cmake", - "/deps/boringssl/src/util/32-bit-toolchain.cmake", # boringssl (non-FIPS) "/deps/boringssl/src/util/32-bit-toolchain.cmake", "/deps/boringssl/**/*.[chS]", From 6e35abb2cd132658d041df4202fab687c8e2e0c6 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sun, 18 May 2025 19:02:13 +0800 Subject: [PATCH 13/18] boring(ssl): use `corresponds` macro in `add_certificate_compression_algorithm` --- boring/src/ssl/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 762cb2d7..c6dd92e7 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -1633,9 +1633,8 @@ impl SslContextBuilder { /// Registers a certificate compression algorithm. /// - /// Corresponds to [`SSL_CTX_add_cert_compression_alg`]. - /// /// [`SSL_CTX_add_cert_compression_alg`]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_CTX_add_cert_compression_alg + #[corresponds(SSL_CTX_add_cert_compression_alg)] pub fn add_certificate_compression_algorithm( &mut self, compressor: C, From eefc7b72659dd4666b9d16ecebb1332a5d3680e3 Mon Sep 17 00:00:00 2001 From: James Larisch Date: Mon, 19 May 2025 11:02:56 -0400 Subject: [PATCH 14/18] Add `X509_STORE_CTX_get0_cert` interface This method reliably retrieves the certificate the `X509_STORE_CTX` is verifying, unlike `X509_STORE_CTX_get_current_cert`, which may return the "problematic" cert when verification fails. --- boring/src/ssl/test/cert_verify.rs | 22 +++++++++++++++++----- boring/src/ssl/test/server.rs | 18 ++++++++++++++++++ boring/src/x509/mod.rs | 14 ++++++++++++++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/boring/src/ssl/test/cert_verify.rs b/boring/src/ssl/test/cert_verify.rs index 929db48c..e8e54061 100644 --- a/boring/src/ssl/test/cert_verify.rs +++ b/boring/src/ssl/test/cert_verify.rs @@ -56,17 +56,29 @@ fn no_error_when_trusted_and_callback_returns_true() { #[test] fn callback_receives_correct_certificate() { - let server = Server::builder().build(); + // Server sends the full chain (leaf + root)... + let server = Server::builder_full_chain().build(); + // but client doesn't load the root as trusted. + // So we expect an error. let mut client = server.client(); - let expected = "59172d9313e84459bcff27f967e79e6e9217e584"; + let leaf_sha1 = "59172d9313e84459bcff27f967e79e6e9217e584"; + let root_sha1 = "c0cbdf7cdd03c9773e5468e1f6d2da7d5cbb1875"; client.ctx().set_verify(SslVerifyMode::PEER); client.ctx().set_cert_verify_callback(move |x509| { assert!(!x509.verify_cert().unwrap()); + // This is set to the root, since that's the problematic cert. assert!(x509.current_cert().is_some()); + // This is set to the leaf, since that's the cert we're verifying. + assert!(x509.cert().is_some()); assert!(x509.verify_result().is_err()); - let cert = x509.current_cert().unwrap(); - let digest = cert.digest(MessageDigest::sha1()).unwrap(); - assert_eq!(hex::encode(digest), expected); + + let root = x509.current_cert().unwrap(); + let digest = root.digest(MessageDigest::sha1()).unwrap(); + assert_eq!(hex::encode(digest), root_sha1); + + let leaf = x509.cert().unwrap(); + let digest = leaf.digest(MessageDigest::sha1()).unwrap(); + assert_eq!(hex::encode(digest), leaf_sha1); true }); diff --git a/boring/src/ssl/test/server.rs b/boring/src/ssl/test/server.rs index e5c0497c..5436469d 100644 --- a/boring/src/ssl/test/server.rs +++ b/boring/src/ssl/test/server.rs @@ -36,6 +36,24 @@ impl Server { } } + /// Serves the leaf and the root together. + pub fn builder_full_chain() -> Builder { + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + // Uses certs.pem instead of cert.pem. + ctx.set_certificate_chain_file("test/certs.pem").unwrap(); + ctx.set_private_key_file("test/key.pem", SslFiletype::PEM) + .unwrap(); + + Builder { + ctx, + ssl_cb: Box::new(|_| {}), + io_cb: Box::new(|_| {}), + err_cb: Box::new(|_| {}), + should_error: false, + expected_connections_count: 1, + } + } + pub fn client(&self) -> ClientBuilder { ClientBuilder { ctx: SslContext::builder(SslMethod::tls()).unwrap(), diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index f4a44ee5..62c2c8df 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -209,6 +209,20 @@ impl X509StoreContextRef { } } } + + /// Returns a reference to the certificate being verified. + /// May return None if a raw public key is being verified. + #[corresponds(X509_STORE_CTX_get0_cert)] + pub fn cert(&self) -> Option<&X509Ref> { + unsafe { + let ptr = ffi::X509_STORE_CTX_get0_cert(self.as_ptr()); + if ptr.is_null() { + None + } else { + Some(X509Ref::from_ptr(ptr)) + } + } + } } /// A builder used to construct an `X509`. From 4ea82a2e1ba5f60bc20186416868275f4dfffe18 Mon Sep 17 00:00:00 2001 From: Yury Yarashevich Date: Wed, 14 May 2025 12:29:33 +0200 Subject: [PATCH 15/18] Update bindgen from 0.70.1 -> 0.71.1. --- Cargo.toml | 2 +- boring-sys/build/main.rs | 5 ++++- boring-sys/src/lib.rs | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e8c3fd2f..4c2a29dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ boring-sys = { version = "4.16.0", path = "./boring-sys" } boring = { version = "4.16.0", path = "./boring" } tokio-boring = { version = "4.16.0", path = "./tokio-boring" } -bindgen = { version = "0.70.1", default-features = false, features = ["runtime"] } +bindgen = { version = "0.71.1", default-features = false, features = ["runtime"] } bytes = "1" cmake = "0.1.18" fs_extra = "1.3.0" diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index e397e710..6cdcad66 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -724,9 +724,12 @@ fn main() { // 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 mut builder = bindgen::Builder::default() - .rust_target(bindgen::RustTarget::Stable_1_68) // bindgen MSRV is 1.70, so this is enough + .rust_target(target_rust_version) // bindgen MSRV is 1.70, so this is enough .derive_copy(true) .derive_debug(true) .derive_default(true) diff --git a/boring-sys/src/lib.rs b/boring-sys/src/lib.rs index 094221ff..404a9008 100644 --- a/boring-sys/src/lib.rs +++ b/boring-sys/src/lib.rs @@ -19,6 +19,7 @@ use std::os::raw::{c_char, c_int, c_uint, c_ulong}; #[allow( clippy::useless_transmute, clippy::derive_partial_eq_without_eq, + clippy::ptr_offset_with_cast, dead_code )] mod generated { From 560925293ba89d6f7ce2d16587795c574d55bde7 Mon Sep 17 00:00:00 2001 From: Anthony Ramine <123095+nox@users.noreply.github.com> Date: Tue, 27 May 2025 18:19:35 +0200 Subject: [PATCH 16/18] Revert "feat(x509): Implement `Clone` for `X509Store` (#339)" (#353) * Revert "feat(x509): Implement `Clone` for `X509Store` (#339)" This reverts commit 49a8d0906a5b96ac0e06f6110c3fc49889c2f995. See . * Ensure Clone is not added to X509Store * Add comment about why X509Store must not implement Clone --------- Co-authored-by: Kornel --- boring/src/x509/store.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/boring/src/x509/store.rs b/boring/src/x509/store.rs index 76edac15..d5669920 100644 --- a/boring/src/x509/store.rs +++ b/boring/src/x509/store.rs @@ -125,23 +125,6 @@ foreign_type_and_impl_send_sync! { pub struct X509Store; } -impl Clone for X509Store { - fn clone(&self) -> Self { - (**self).to_owned() - } -} - -impl ToOwned for X509StoreRef { - type Owned = X509Store; - - fn to_owned(&self) -> Self::Owned { - unsafe { - ffi::X509_STORE_up_ref(self.as_ptr()); - X509Store::from_ptr(self.as_ptr()) - } - } -} - impl X509StoreRef { /// **Warning: this method is unsound** /// @@ -164,3 +147,14 @@ impl X509StoreRef { self.objects().len() } } + +#[test] +#[allow(dead_code)] +// X509Store must not implement Clone because `SslContextBuilder::cert_store_mut` lets +// you get a mutable reference to a store that could have been cloned before being +// passed to `SslContextBuilder::set_cert_store`. +fn no_clone_for_x509store() { + trait MustNotImplementClone {} + impl MustNotImplementClone for T {} + impl MustNotImplementClone for X509Store {} +} From 5e8aaf63f043f681aefd7b45d8a770b7e1610a53 Mon Sep 17 00:00:00 2001 From: Anthony Ramine <123095+nox@users.noreply.github.com> Date: Wed, 28 May 2025 11:53:09 +0200 Subject: [PATCH 17/18] Release 4.17.0 (#354) --- Cargo.toml | 8 ++++---- RELEASE_NOTES | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4c2a29dd..10d71bf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "4.16.0" +version = "4.17.0" repository = "https://github.com/cloudflare/boring" edition = "2021" @@ -19,9 +19,9 @@ tag-prefix = "" publish = false [workspace.dependencies] -boring-sys = { version = "4.16.0", path = "./boring-sys" } -boring = { version = "4.16.0", path = "./boring" } -tokio-boring = { version = "4.16.0", path = "./tokio-boring" } +boring-sys = { version = "4.17.0", path = "./boring-sys" } +boring = { version = "4.17.0", path = "./boring" } +tokio-boring = { version = "4.17.0", path = "./tokio-boring" } bindgen = { version = "0.71.1", default-features = false, features = ["runtime"] } bytes = "1" diff --git a/RELEASE_NOTES b/RELEASE_NOTES index ca99ee4d..17c57f82 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,4 +1,20 @@ +4.17.0 +- 2025-05-27 Revert "feat(x509): Implement `Clone` for `X509Store` (#339)" (#353) +- 2025-05-14 Update bindgen from 0.70.1 -> 0.71.1. +- 2025-05-19 Add `X509_STORE_CTX_get0_cert` interface +- 2025-05-18 boring(ssl): use `corresponds` macro in `add_certificate_compression_algorithm` +- 2025-02-14 Update Cargo.toml +- 2025-02-13 build: Fix the build for 32-bit Linux platform +- 2025-05-20 rustfmt ;( +- 2025-05-20 Fix linking SystemFunction036 from advapi32 in Rust 1.87 +- 2025-05-20 Clippy +- 2025-05-01 add SslCurve::X25519_MLKEM768 constant +- 2025-04-17 Use ubuntu-latest for all ci jobs +- 2025-04-16 fix clippy error +- 2025-04-15 expose SSL_set_compliance_policy +- 2025-04-07 feat(x509): Implement `Clone` for `X509Store` (#339) + 4.16.0 - 2025-03-31 Add fips-precompiled feature to support newer versions of FIPS (#338) - 2025-03-18 Document linking to C++ standard library (#335) From e99d162891884895e871ad2358c4d49420bfeeaf Mon Sep 17 00:00:00 2001 From: James Larisch Date: Thu, 29 May 2025 20:06:30 -0400 Subject: [PATCH 18/18] Add set_verify_param --- boring/src/x509/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index 62c2c8df..bfd7fdd5 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -148,6 +148,12 @@ impl X509StoreContextRef { unsafe { X509VerifyParamRef::from_ptr_mut(ffi::X509_STORE_CTX_get0_param(self.as_ptr())) } } + /// Sets the X509 verifification configuration on the X509_STORE_CTX. + #[corresponds(X509_STORE_CTX_set0_param)] + pub fn set_verify_param(&mut self, param: &mut X509VerifyParamRef) { + unsafe { ffi::X509_STORE_CTX_set0_param(self.as_ptr(), param.as_ptr()) } + } + /// Verifies the stored certificate. /// /// Returns `true` if verification succeeds. The `error` method will return the specific