Merge remote-tracking branch 'upstream/master'

This commit is contained in:
0x676e67 2024-09-22 07:57:47 +08:00
commit b0fe60dcf6
25 changed files with 2726 additions and 2567 deletions

View File

@ -322,7 +322,8 @@ jobs:
working-directory: ${{ runner.temp }}/llvm/bin
run: ln -s clang clang++-12
- name: Install ${{ matrix.target }} toolchain
run: brew tap messense/macos-cross-toolchains && brew install --overwrite python@3.11 && brew install ${{ matrix.target }}
# TODO(rmehra): find a better way to overwrite the python3 version without specifying version
run: brew tap messense/macos-cross-toolchains && brew install --overwrite python@3.12 && brew install ${{ matrix.target }}
- name: Set BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN
run: echo "BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN=$(brew --prefix ${{ matrix.target }})/toolchain" >> $GITHUB_ENV
shell: bash
@ -360,3 +361,5 @@ jobs:
name: Run `rpk,underscore-wildcards` tests
- run: cargo test --features pq-experimental,rpk,underscore-wildcards
name: Run `pq-experimental,rpk,underscore-wildcards` tests
- run: cargo test -p hyper-boring --features hyper1
name: Run hyper 1.0 tests for hyper-boring

View File

@ -8,7 +8,7 @@ members = [
resolver = "2"
[workspace.package]
version = "4.9.1"
version = "4.10.2"
repository = "https://github.com/cloudflare/boring"
edition = "2021"
@ -19,11 +19,12 @@ tag-prefix = ""
publish = false
[workspace.dependencies]
boring-sys = { version = "4.9.1", path = "./boring-sys", package = "rboring-sys" }
boring = { version = "4.9.1", path = "./boring", package = "rboring"}
tokio-boring = { version = "4.9.1", path = "./tokio-boring", package = "tokio-rboring"}
boring-sys = { version = "4.10.2", path = "./rboring-sys" }
boring = { version = "4.10.2", path = "./rboring" }
tokio-boring = { version = "4.10.2", path = "./rtokio-boring" }
bindgen = { version = "0.68.1", default-features = false, features = ["runtime"] }
bindgen = { version = "0.70.1", default-features = false, features = ["runtime"] }
bytes = "1"
cmake = "0.1.18"
fs_extra = "1.3.0"
fslock = "0.2"
@ -36,9 +37,15 @@ futures = "0.3"
tokio = "1"
anyhow = "1"
antidote = "1.0.0"
http = "0.2"
hyper = { package = "rhyper", version = "0.14", default-features = false }
http = "1"
http-body-util = "0.1.2"
http_old = { package = "http", version = "0.2" }
hyper = "1"
hyper-util = "0.1.6"
hyper_old = { package = "rhyper", version = "0.14", default-features = false }
linked_hash_set = "0.1"
once_cell = "1.0"
openssl-macros = "0.1.1"
tower = "0.4"
tower-layer = "0.3"
tower-service = "0.3"

View File

@ -1,3 +1,25 @@
4.10.2
- 2024-09-18 boring-pq.patch Fix by not updating crypto_test_data.cc
4.10.1
- 2024-09-18 Don't support X25519MLKEM768 by default (yet)
4.10.0
- 2024-09-18 Implement optional Hyper 1 support in hyper-boring (#246)
- 2024-09-17 Add post-quantum key agreement X25519MLKEM768
- 2024-09-10 Revert "PQ: fix timing sidechannels and add IPDWing"
- 2024-09-17 Update bindgen to 0.70.1
- 2024-09-17 Expose SSL(_CTX)_set1_curves_list (#270)
- 2024-09-11 Expose SSL_CTX_set_info_callback (#266)
- 2024-09-03 Use ForeignType::into_ptr wherever applicable
- 2024-08-19 Expose RSAPSS public key Id type
- 2024-08-15 Fix macos FIPS crossbuild
- 2024-08-15 Add tests for X509Ref::subject_key_id, X509Ref::authority_key_id, and X509NameRef::print_ex
- 2024-08-14 Expose X509NameRef::print_ex
- 2024-08-13 Introduce `corresponds` macro from openssl-macros
- 2024-08-14 Introduce ForeignTypeExt and ForeignTypeRefExt
- 2024-08-09 Expose mTLS related APIs
- 2024-08-14 chore(boring-sys): Fix git apply patch on Windows (#261)
4.9.1
- 2024-08-04 Properly handle `Option<i32>` in `SslRef::set_curves`

View File

@ -81,3 +81,6 @@ bindgen = { workspace = true }
cmake = { workspace = true }
fs_extra = { workspace = true }
fslock = { workspace = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(const_fn)'] }

View File

@ -504,6 +504,14 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> {
fn apply_patch(config: &Config, patch_name: &str) -> io::Result<()> {
let src_path = get_boringssl_source_path(config);
#[cfg(not(windows))]
let cmd_path = config
.manifest_dir
.join("patches")
.join(patch_name)
.canonicalize()?;
#[cfg(windows)]
let cmd_path = config.manifest_dir.join("patches").join(patch_name);
let mut args = vec!["apply", "-v", "--whitespace=fix"];
@ -683,6 +691,7 @@ fn main() {
});
let mut builder = bindgen::Builder::default()
.rust_target(bindgen::RustTarget::Stable_1_68) // bindgen MSRV is 1.70, so this is enough
.derive_copy(true)
.derive_debug(true)
.derive_default(true)

File diff suppressed because it is too large Load Diff

View File

@ -69,6 +69,7 @@ kx-client-nist-required = ["kx-safe-default"]
bitflags = { workspace = true }
foreign-types = { workspace = true }
once_cell = { workspace = true }
openssl-macros = { workspace = true }
libc = { workspace = true }
boring-sys = { workspace = true }

View File

@ -280,8 +280,7 @@ impl Dsa<Public> {
let dsa = Dsa::from_ptr(cvt_p(ffi::DSA_new())?);
cvt(DSA_set0_pqg(dsa.0, p.as_ptr(), q.as_ptr(), g.as_ptr()))?;
mem::forget((p, q, g));
cvt(DSA_set0_key(dsa.0, pub_key.as_ptr(), ptr::null_mut()))?;
mem::forget(pub_key);
cvt(DSA_set0_key(dsa.0, pub_key.into_ptr(), ptr::null_mut()))?;
Ok(dsa)
}
}

View File

@ -74,19 +74,17 @@
//! support by turning on `post-quantum` compilation feature.
//!
//! Upstream BoringSSL support the post-quantum hybrid key agreement `X25519Kyber768Draft00`. Most
//! users should stick to that one. Enabling this feature, adds a few other post-quantum key
//! users should stick to that one for now. Enabling this feature, adds a few other post-quantum key
//! agreements:
//!
//! - `X25519MLKEM768` is the successor of `X25519Kyber768Draft00`. We expect servers to switch
//! 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.
//! - `P256Kyber768Draft00`. Similar again to `X25519Kyber768Draft00`, but uses P256 as classical
//! part. It uses a non-standard codepoint. Not recommended.
//! - `IPDWing`. A preliminary version of
//! [X-Wing](https://datatracker.ietf.org/doc/draft-connolly-cfrg-xwing-kem/02/).
//! Similar to `X25519Kyber768Draft00Old`, but uses a newer (but not yet final) version of Kyber
//! called ML-KEM-ipd. Not recommended.
//!
//! Presently all these key agreements are deployed by Cloudflare, but we do not guarantee continued
//! support for them.

View File

@ -72,6 +72,7 @@ pub struct Id(c_int);
impl Id {
pub const RSA: Id = Id(ffi::EVP_PKEY_RSA);
pub const RSAPSS: Id = Id(ffi::EVP_PKEY_RSA_PSS);
pub const DSA: Id = Id(ffi::EVP_PKEY_DSA);
pub const DH: Id = Id(ffi::EVP_PKEY_DH);
pub const EC: Id = Id(ffi::EVP_PKEY_EC);
@ -303,6 +304,7 @@ impl<T> fmt::Debug for PKey<T> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let alg = match self.id() {
Id::RSA => "RSA",
Id::RSAPSS => "RSAPSS",
Id::DSA => "DSA",
Id::DH => "DH",
Id::EC => "EC",

View File

@ -2,8 +2,9 @@
use super::{
AlpnError, ClientHello, GetSessionPendingError, PrivateKeyMethod, PrivateKeyMethodError,
SelectCertError, SniError, Ssl, SslAlert, SslContext, SslContextRef, SslRef, SslSession,
SslSessionRef, SslSignatureAlgorithm, SslVerifyError, SESSION_CTX_INDEX,
SelectCertError, SniError, Ssl, SslAlert, SslContext, SslContextRef, SslInfoCallbackAlert,
SslInfoCallbackMode, SslInfoCallbackValue, SslRef, SslSession, SslSessionRef,
SslSignatureAlgorithm, SslVerifyError, SESSION_CTX_INDEX,
};
use crate::error::ErrorStack;
use crate::ffi;
@ -521,3 +522,32 @@ where
Err(err) => err.0,
}
}
pub(super) unsafe extern "C" fn raw_info_callback<F>(
ssl: *const ffi::SSL,
mode: c_int,
value: c_int,
) where
F: Fn(&SslRef, SslInfoCallbackMode, SslInfoCallbackValue) + Send + Sync + 'static,
{
// Due to FFI signature requirements we have to pass a *const SSL into this function, but
// foreign-types requires a *mut SSL to get the Rust SslRef
let mut_ref = ssl as *mut ffi::SSL;
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr(mut_ref) };
let ssl_context = ssl.ssl_context();
let callback = ssl_context
.ex_data(SslContext::cached_ex_index::<F>())
.expect("BUG: info callback missing");
let value = match mode {
ffi::SSL_CB_READ_ALERT | ffi::SSL_CB_WRITE_ALERT => {
SslInfoCallbackValue::Alert(SslInfoCallbackAlert(value))
}
_ => SslInfoCallbackValue::Unit,
};
callback(ssl, SslInfoCallbackMode(mode), value);
}

File diff suppressed because it is too large Load Diff

View File

@ -1052,3 +1052,17 @@ fn drop_ex_data_in_ssl() {
assert_eq!(ssl.replace_ex_data(index, "camembert"), Some("comté"));
assert_eq!(ssl.replace_ex_data(index, "raclette"), Some("camembert"));
}
#[test]
fn test_info_callback() {
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
let server = Server::builder().build();
let mut client = server.client();
client.ctx().set_info_callback(move |_, _, _| {
CALLED_BACK.store(true, Ordering::Relaxed);
});
client.connect();
assert!(CALLED_BACK.load(Ordering::Relaxed));
}

View File

@ -1,10 +1,10 @@
use crate::error::ErrorStack;
use foreign_types::{ForeignType, ForeignTypeRef};
use libc::{c_char, c_int, c_void};
use std::any::Any;
use std::panic::{self, AssertUnwindSafe};
use std::slice;
use crate::error::ErrorStack;
/// Wraps a user-supplied callback and a slot for panics thrown inside the callback (while FFI
/// frames are on the stack).
///
@ -65,3 +65,30 @@ where
}
}
}
#[allow(dead_code)]
pub trait ForeignTypeExt: ForeignType {
unsafe fn from_ptr_opt(ptr: *mut Self::CType) -> Option<Self> {
if ptr.is_null() {
None
} else {
Some(Self::from_ptr(ptr))
}
}
}
impl<FT: ForeignType> ForeignTypeExt for FT {}
pub trait ForeignTypeRefExt: ForeignTypeRef {
unsafe fn from_const_ptr<'a>(ptr: *const Self::CType) -> &'a Self {
Self::from_ptr(ptr as *mut Self::CType)
}
unsafe fn from_const_ptr_opt<'a>(ptr: *const Self::CType) -> Option<&'a Self> {
if ptr.is_null() {
None
} else {
Some(Self::from_const_ptr(ptr as *mut Self::CType))
}
}
}
impl<FT: ForeignTypeRef> ForeignTypeRefExt for FT {}

View File

@ -9,6 +9,7 @@
use foreign_types::{ForeignType, ForeignTypeRef};
use libc::{c_int, c_long, c_void};
use openssl_macros::corresponds;
use std::convert::TryInto;
use std::error::Error;
use std::ffi::{CStr, CString};
@ -25,7 +26,7 @@ use crate::asn1::{
Asn1BitStringRef, Asn1IntegerRef, Asn1Object, Asn1ObjectRef, Asn1StringRef, Asn1TimeRef,
Asn1Type,
};
use crate::bio::MemBioSlice;
use crate::bio::{MemBio, MemBioSlice};
use crate::conf::ConfRef;
use crate::error::ErrorStack;
use crate::ex_data::Index;
@ -36,6 +37,7 @@ use crate::pkey::{HasPrivate, HasPublic, PKey, PKeyRef, Public};
use crate::ssl::SslRef;
use crate::stack::{Stack, StackRef, Stackable};
use crate::string::OpensslString;
use crate::util::ForeignTypeRefExt;
use crate::x509::verify::X509VerifyParamRef;
use crate::{cvt, cvt_n, cvt_p};
@ -57,15 +59,13 @@ foreign_type_and_impl_send_sync! {
impl X509StoreContext {
/// Returns the index which can be used to obtain a reference to the `Ssl` associated with a
/// context.
#[corresponds(SSL_get_ex_data_X509_STORE_CTX_idx)]
pub fn ssl_idx() -> Result<Index<X509StoreContext, SslRef>, ErrorStack> {
unsafe { cvt_n(ffi::SSL_get_ex_data_X509_STORE_CTX_idx()).map(|idx| Index::from_raw(idx)) }
}
/// Creates a new `X509StoreContext` instance.
///
/// This corresponds to [`X509_STORE_CTX_new`].
///
/// [`X509_STORE_CTX_new`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_STORE_CTX_new.html
#[corresponds(X509_STORE_CTX_new)]
pub fn new() -> Result<X509StoreContext, ErrorStack> {
unsafe {
ffi::init();
@ -76,10 +76,7 @@ impl X509StoreContext {
impl X509StoreContextRef {
/// Returns application data pertaining to an `X509` store context.
///
/// This corresponds to [`X509_STORE_CTX_get_ex_data`].
///
/// [`X509_STORE_CTX_get_ex_data`]: https://www.openssl.org/docs/man1.0.2/crypto/X509_STORE_CTX_get_ex_data.html
#[corresponds(X509_STORE_CTX_get_ex_data)]
pub fn ex_data<T>(&self, index: Index<X509StoreContext, T>) -> Option<&T> {
unsafe {
let data = ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw());
@ -92,10 +89,7 @@ impl X509StoreContextRef {
}
/// Returns the verify result of the context.
///
/// This corresponds to [`X509_STORE_CTX_get_error`].
///
/// [`X509_STORE_CTX_get_error`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_STORE_CTX_get_error.html
#[corresponds(X509_STORE_CTX_get_error)]
pub fn verify_result(&self) -> X509VerifyResult {
unsafe { X509VerifyError::from_raw(ffi::X509_STORE_CTX_get_error(self.as_ptr())) }
}
@ -149,10 +143,7 @@ impl X509StoreContextRef {
}
/// Returns a mutable reference to the X509 verification configuration.
///
/// This corresponds to [`X509_STORE_CTX_get0_param`].
///
/// [`SSL_get0_param`]: https://www.openssl.org/docs/manmaster/man3/X509_STORE_CTX_get0_param.html
#[corresponds(X509_STORE_CTX_get0_param)]
pub fn verify_param_mut(&mut self) -> &mut X509VerifyParamRef {
unsafe { X509VerifyParamRef::from_ptr_mut(ffi::X509_STORE_CTX_get0_param(self.as_ptr())) }
}
@ -163,19 +154,13 @@ impl X509StoreContextRef {
/// validation error if the certificate was not valid.
///
/// This will only work inside of a call to `init`.
///
/// This corresponds to [`X509_verify_cert`].
///
/// [`X509_verify_cert`]: https://www.openssl.org/docs/man1.0.2/crypto/X509_verify_cert.html
#[corresponds(X509_verify_cert)]
pub fn verify_cert(&mut self) -> Result<bool, ErrorStack> {
unsafe { cvt_n(ffi::X509_verify_cert(self.as_ptr())).map(|n| n != 0) }
}
/// Set the verify result of the context.
///
/// This corresponds to [`X509_STORE_CTX_set_error`].
///
/// [`X509_STORE_CTX_set_error`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_STORE_CTX_set_error.html
#[corresponds(X509_STORE_CTX_set_error)]
pub fn set_error(&mut self, result: X509VerifyResult) {
unsafe {
ffi::X509_STORE_CTX_set_error(
@ -190,10 +175,7 @@ impl X509StoreContextRef {
/// Returns a reference to the certificate which caused the error or None if
/// no certificate is relevant to the error.
///
/// This corresponds to [`X509_STORE_CTX_get_current_cert`].
///
/// [`X509_STORE_CTX_get_current_cert`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_STORE_CTX_get_current_cert.html
#[corresponds(X509_STORE_CTX_get_current_cert)]
pub fn current_cert(&self) -> Option<&X509Ref> {
unsafe {
let ptr = ffi::X509_STORE_CTX_get_current_cert(self.as_ptr());
@ -209,19 +191,13 @@ impl X509StoreContextRef {
/// chain where the error occurred. If it is zero it occurred in the end
/// entity certificate, one if it is the certificate which signed the end
/// entity certificate and so on.
///
/// This corresponds to [`X509_STORE_CTX_get_error_depth`].
///
/// [`X509_STORE_CTX_get_error_depth`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_STORE_CTX_get_error_depth.html
#[corresponds(X509_STORE_CTX_get_error_depth)]
pub fn error_depth(&self) -> u32 {
unsafe { ffi::X509_STORE_CTX_get_error_depth(self.as_ptr()) as u32 }
}
/// Returns a reference to a complete valid `X509` certificate chain.
///
/// This corresponds to [`X509_STORE_CTX_get0_chain`].
///
/// [`X509_STORE_CTX_get0_chain`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_STORE_CTX_get0_chain.html
#[corresponds(X509_STORE_CTX_get0_chain)]
pub fn chain(&self) -> Option<&StackRef<X509>> {
unsafe {
let chain = X509_STORE_CTX_get0_chain(self.as_ptr());
@ -240,6 +216,7 @@ pub struct X509Builder(X509);
impl X509Builder {
/// Creates a new builder.
#[corresponds(X509_new)]
pub fn new() -> Result<X509Builder, ErrorStack> {
unsafe {
ffi::init();
@ -248,11 +225,13 @@ impl X509Builder {
}
/// Sets the notAfter constraint on the certificate.
#[corresponds(X509_set1_notAfter)]
pub fn set_not_after(&mut self, not_after: &Asn1TimeRef) -> Result<(), ErrorStack> {
unsafe { cvt(X509_set1_notAfter(self.0.as_ptr(), not_after.as_ptr())).map(|_| ()) }
}
/// Sets the notBefore constraint on the certificate.
#[corresponds(X509_set1_notBefore)]
pub fn set_not_before(&mut self, not_before: &Asn1TimeRef) -> Result<(), ErrorStack> {
unsafe { cvt(X509_set1_notBefore(self.0.as_ptr(), not_before.as_ptr())).map(|_| ()) }
}
@ -261,11 +240,13 @@ impl X509Builder {
///
/// Note that the version is zero-indexed; that is, a certificate corresponding to version 3 of
/// the X.509 standard should pass `2` to this method.
#[corresponds(X509_set_version)]
pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::X509_set_version(self.0.as_ptr(), version.into())).map(|_| ()) }
}
/// Sets the serial number of the certificate.
#[corresponds(X509_set_serialNumber)]
pub fn set_serial_number(&mut self, serial_number: &Asn1IntegerRef) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_set_serialNumber(
@ -277,6 +258,7 @@ impl X509Builder {
}
/// Sets the issuer name of the certificate.
#[corresponds(X509_set_issuer_name)]
pub fn set_issuer_name(&mut self, issuer_name: &X509NameRef) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_set_issuer_name(
@ -305,6 +287,7 @@ impl X509Builder {
/// let mut x509 = boring::x509::X509::builder().unwrap();
/// x509.set_subject_name(&x509_name).unwrap();
/// ```
#[corresponds(X509_set_subject_name)]
pub fn set_subject_name(&mut self, subject_name: &X509NameRef) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_set_subject_name(
@ -316,6 +299,7 @@ impl X509Builder {
}
/// Sets the public key associated with the certificate.
#[corresponds(X509_set_pubkey)]
pub fn set_pubkey<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack>
where
T: HasPublic,
@ -326,6 +310,7 @@ impl X509Builder {
/// Returns a context object which is needed to create certain X509 extension values.
///
/// Set `issuer` to `None` if the certificate will be self-signed.
#[corresponds(X509V3_set_ctx)]
pub fn x509v3_context<'a>(
&'a self,
issuer: Option<&'a X509Ref>,
@ -365,10 +350,7 @@ impl X509Builder {
}
/// Adds an X509 extension value to the certificate.
///
/// This corresponds to [`X509_add_ext`].
///
/// [`X509_add_ext`]: https://www.openssl.org/docs/man1.1.0/man3/X509_get_ext.html
#[corresponds(X509_add_ext)]
pub fn append_extension2(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_add_ext(self.0.as_ptr(), extension.as_ptr(), -1))?;
@ -377,6 +359,7 @@ impl X509Builder {
}
/// Signs the certificate with a private key.
#[corresponds(X509_sign)]
pub fn sign<T>(&mut self, key: &PKeyRef<T>, hash: MessageDigest) -> Result<(), ErrorStack>
where
T: HasPrivate,
@ -400,43 +383,22 @@ foreign_type_and_impl_send_sync! {
impl X509Ref {
/// Returns this certificate's subject name.
///
/// This corresponds to [`X509_get_subject_name`].
///
/// [`X509_get_subject_name`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_get_subject_name.html
#[corresponds(X509_get_subject_name)]
pub fn subject_name(&self) -> &X509NameRef {
unsafe {
let name = ffi::X509_get_subject_name(self.as_ptr());
assert!(!name.is_null());
X509NameRef::from_ptr(name)
X509NameRef::from_const_ptr_opt(name).expect("issuer name must not be null")
}
}
/// Returns the hash of the certificates subject
///
/// This corresponds to `X509_subject_name_hash`.
#[corresponds(X509_subject_name_hash)]
pub fn subject_name_hash(&self) -> u32 {
unsafe { ffi::X509_subject_name_hash(self.as_ptr()) as u32 }
}
/// Returns this certificate's issuer name.
///
/// This corresponds to [`X509_get_issuer_name`].
///
/// [`X509_get_issuer_name`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_get_subject_name.html
pub fn issuer_name(&self) -> &X509NameRef {
unsafe {
let name = ffi::X509_get_issuer_name(self.as_ptr());
assert!(!name.is_null());
X509NameRef::from_ptr(name)
}
}
/// Returns this certificate's subject alternative name entries, if they exist.
///
/// This corresponds to [`X509_get_ext_d2i`] called with `NID_subject_alt_name`.
///
/// [`X509_get_ext_d2i`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_get_ext_d2i.html
#[corresponds(X509_get_ext_d2i)]
pub fn subject_alt_names(&self) -> Option<Stack<GeneralName>> {
unsafe {
let stack = ffi::X509_get_ext_d2i(
@ -453,11 +415,17 @@ impl X509Ref {
}
}
/// Returns this certificate's issuer name.
#[corresponds(X509_get_issuer_name)]
pub fn issuer_name(&self) -> &X509NameRef {
unsafe {
let name = ffi::X509_get_issuer_name(self.as_ptr());
X509NameRef::from_const_ptr_opt(name).expect("issuer name must not be null")
}
}
/// Returns this certificate's issuer alternative name entries, if they exist.
///
/// This corresponds to [`X509_get_ext_d2i`] called with `NID_issuer_alt_name`.
///
/// [`X509_get_ext_d2i`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_get_ext_d2i.html
#[corresponds(X509_get_ext_d2i)]
pub fn issuer_alt_names(&self) -> Option<Stack<GeneralName>> {
unsafe {
let stack = ffi::X509_get_ext_d2i(
@ -474,6 +442,24 @@ impl X509Ref {
}
}
/// Returns this certificate's subject key id, if it exists.
#[corresponds(X509_get0_subject_key_id)]
pub fn subject_key_id(&self) -> Option<&Asn1StringRef> {
unsafe {
let data = ffi::X509_get0_subject_key_id(self.as_ptr());
Asn1StringRef::from_const_ptr_opt(data)
}
}
/// Returns this certificate's authority key id, if it exists.
#[corresponds(X509_get0_authority_key_id)]
pub fn authority_key_id(&self) -> Option<&Asn1StringRef> {
unsafe {
let data = ffi::X509_get0_authority_key_id(self.as_ptr());
Asn1StringRef::from_const_ptr_opt(data)
}
}
pub fn public_key(&self) -> Result<PKey<Public>, ErrorStack> {
unsafe {
let pkey = cvt_p(ffi::X509_get_pubkey(self.as_ptr()))?;
@ -1058,6 +1044,20 @@ impl X509NameRef {
}
}
/// Returns an owned String representing the X509 name configurable via incoming flags.
///
/// This function will return `None` if the underlying string contains invalid utf-8.
#[corresponds(X509_NAME_print_ex)]
pub fn print_ex(&self, flags: i32) -> Option<String> {
unsafe {
let bio = MemBio::new().ok()?;
ffi::X509_NAME_print_ex(bio.as_ptr(), self.as_ptr(), 0, flags as _);
let buf = bio.get_buf().to_vec();
let res = String::from_utf8(buf);
res.ok()
}
}
to_der! {
/// Serializes the certificate into a DER-encoded X509 name structure.
///
@ -1584,9 +1584,7 @@ impl GeneralName {
ffi::init();
let gn = cvt_p(ffi::GENERAL_NAME_new())?;
(*gn).type_ = ffi::GEN_RID;
(*gn).d.registeredID = oid.as_ptr();
mem::forget(oid);
(*gn).d.registeredID = oid.into_ptr();
Ok(GeneralName::from_ptr(gn))
}

View File

@ -179,6 +179,49 @@ fn test_subject_alt_name_iter() {
assert!(subject_alt_names_iter.next().is_none());
}
#[test]
fn test_subject_key_id() {
// nid_test_cert_pem has SKI, but no AKI
let cert = include_bytes!("../../../test/nid_test_cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ski = cert.subject_key_id().expect("unable to extract SKI");
assert_eq!(
ski.as_slice(),
[
80, 107, 158, 237, 95, 61, 235, 100, 212, 115, 249, 244, 219, 163, 124, 55, 141, 2, 76,
5
]
);
let aki = cert.authority_key_id();
assert!(aki.is_none());
}
#[test]
fn test_x509_name_print_ex() {
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let name_no_flags = cert
.subject_name()
.print_ex(0)
.expect("failed to print cert subject name");
assert_eq!(
name_no_flags,
"C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=foobar.com"
);
let name_rfc2253 = cert
.subject_name()
.print_ex(ffi::XN_FLAG_RFC2253)
.expect("failed to print cert subject name");
assert_eq!(
name_rfc2253,
"CN=foobar.com,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU"
);
}
#[test]
fn x509_builder() {
let pkey = pkey();

View File

@ -17,7 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["runtime"]
runtime = ["hyper/runtime"]
runtime = ["hyper_old/runtime"]
# Use a FIPS-validated version of boringssl.
fips = ["tokio-boring/fips"]
@ -28,19 +28,30 @@ fips-link-precompiled = ["tokio-boring/fips-link-precompiled"]
# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/)
pq-experimental = ["tokio-boring/pq-experimental"]
# Enable Hyper 1 support
hyper1 = ["dep:http", "dep:hyper", "dep:hyper-util", "dep:tower-service"]
[dependencies]
antidote = { workspace = true }
http = { workspace = true }
hyper = { workspace = true, features = ["client"] }
http = { workspace = true, optional = true }
http_old = { workspace = true }
hyper = { workspace = true, optional = true }
hyper-util = { workspace = true, optional = true, features = ["client", "client-legacy"] }
hyper_old = { workspace = true, features = ["client"] }
linked_hash_set = { workspace = true }
once_cell = { workspace = true }
boring = { workspace = true }
tokio = { workspace = true }
tokio-boring = { workspace = true }
tower-layer = { workspace = true }
tower-service = { workspace = true, optional = true }
[dev-dependencies]
hyper = { workspace = true, features = [ "full" ] }
bytes = { workspace = true }
http-body-util = { workspace = true }
hyper-util = { workspace = true, features = ["http1", "http2", "service", "tokio"] }
hyper = { workspace = true, features = ["server"] }
hyper_old = { workspace = true, features = [ "full" ] }
tokio = { workspace = true, features = [ "full" ] }
tower = { workspace = true, features = ["util"] }
futures = { workspace = true }

View File

@ -2,91 +2,27 @@
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use crate::cache::{SessionCache, SessionKey};
use antidote::Mutex;
use crate::cache::SessionKey;
use boring::error::ErrorStack;
use boring::ex_data::Index;
use boring::ssl::{
ConnectConfiguration, Ssl, SslConnector, SslConnectorBuilder, SslMethod, SslRef,
SslSessionCacheMode,
};
use http::uri::Scheme;
use hyper::client::connect::{Connected, Connection};
#[cfg(feature = "runtime")]
use hyper::client::HttpConnector;
use hyper::service::Service;
use hyper::Uri;
use boring::ssl::Ssl;
use once_cell::sync::OnceCell;
use std::fmt::Debug;
use std::future::Future;
use std::io;
use std::net;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::{error::Error, fmt};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use std::fmt;
use tokio_boring::SslStream;
use tower_layer::Layer;
mod cache;
#[cfg(test)]
mod test;
mod v0;
/// Hyper 1 support.
#[cfg(feature = "hyper1")]
pub mod v1;
pub use self::v0::*;
fn key_index() -> Result<Index<Ssl, SessionKey>, ErrorStack> {
static IDX: OnceCell<Index<Ssl, SessionKey>> = OnceCell::new();
IDX.get_or_try_init(Ssl::new_ex_index).copied()
}
#[derive(Clone)]
struct Inner {
ssl: SslConnector,
cache: Arc<Mutex<SessionCache>>,
callback: Option<Callback>,
ssl_callback: Option<SslCallback>,
}
type Callback =
Arc<dyn Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + Sync + Send>;
type SslCallback = Arc<dyn Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + Sync + Send>;
impl Inner {
fn setup_ssl(&self, uri: &Uri, host: &str) -> Result<Ssl, ErrorStack> {
let mut conf = self.ssl.configure()?;
if let Some(ref callback) = self.callback {
callback(&mut conf, uri)?;
}
let key = SessionKey {
host: host.to_string(),
port: uri.port_u16().unwrap_or(443),
};
if let Some(session) = self.cache.lock().get(&key) {
unsafe {
conf.set_session(&session)?;
}
}
let idx = key_index()?;
conf.set_ex_data(idx, key);
let mut ssl = conf.into_ssl(host)?;
if let Some(ref ssl_callback) = self.ssl_callback {
ssl_callback(&mut ssl, uri)?;
}
Ok(ssl)
}
}
/// A layer which wraps services in an `HttpsConnector`.
pub struct HttpsLayer {
inner: Inner,
}
/// Settings for [`HttpsLayer`]
pub struct HttpsLayerSettings {
session_cache_capacity: usize,
@ -123,214 +59,6 @@ impl HttpsLayerSettingsBuilder {
}
}
impl HttpsLayer {
/// Creates a new `HttpsLayer` with default settings.
///
/// ALPN is configured to support both HTTP/1 and HTTP/1.1.
pub fn new() -> Result<HttpsLayer, ErrorStack> {
let mut ssl = SslConnector::builder(SslMethod::tls())?;
ssl.set_alpn_protos(b"\x02h2\x08http/1.1")?;
Self::with_connector(ssl)
}
/// Creates a new `HttpsLayer`.
///
/// The session cache configuration of `ssl` will be overwritten.
pub fn with_connector(ssl: SslConnectorBuilder) -> Result<HttpsLayer, ErrorStack> {
Self::with_connector_and_settings(ssl, Default::default())
}
/// Creates a new `HttpsLayer` with settings
pub fn with_connector_and_settings(
mut ssl: SslConnectorBuilder,
settings: HttpsLayerSettings,
) -> Result<HttpsLayer, ErrorStack> {
let cache = Arc::new(Mutex::new(SessionCache::with_capacity(
settings.session_cache_capacity,
)));
ssl.set_session_cache_mode(SslSessionCacheMode::CLIENT);
ssl.set_new_session_callback({
let cache = cache.clone();
move |ssl, session| {
if let Some(key) = key_index().ok().and_then(|idx| ssl.ex_data(idx)) {
cache.lock().insert(key.clone(), session);
}
}
});
Ok(HttpsLayer {
inner: Inner {
ssl: ssl.build(),
cache,
callback: None,
ssl_callback: None,
},
})
}
/// Registers a callback which can customize the configuration of each connection.
///
/// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`),
/// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`]
/// instead.
pub fn set_callback<F>(&mut self, callback: F)
where
F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.callback = Some(Arc::new(callback));
}
/// Registers a callback which can customize the `Ssl` of each connection.
pub fn set_ssl_callback<F>(&mut self, callback: F)
where
F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.ssl_callback = Some(Arc::new(callback));
}
}
impl<S> Layer<S> for HttpsLayer {
type Service = HttpsConnector<S>;
fn layer(&self, inner: S) -> HttpsConnector<S> {
HttpsConnector {
http: inner,
inner: self.inner.clone(),
}
}
}
/// A Connector using OpenSSL to support `http` and `https` schemes.
#[derive(Clone)]
pub struct HttpsConnector<T> {
http: T,
inner: Inner,
}
#[cfg(feature = "runtime")]
impl HttpsConnector<HttpConnector> {
/// Creates a a new `HttpsConnector` using default settings.
///
/// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both
/// HTTP/2 and HTTP/1.1.
///
/// Requires the `runtime` Cargo feature.
pub fn new() -> Result<HttpsConnector<HttpConnector>, ErrorStack> {
let mut http = HttpConnector::new();
http.enforce_http(false);
HttpsLayer::new().map(|l| l.layer(http))
}
}
impl<S, T> HttpsConnector<S>
where
S: Service<Uri, Response = T> + Send,
S::Error: Into<Box<dyn Error + Send + Sync>>,
S::Future: Unpin + Send + 'static,
T: AsyncRead + AsyncWrite + Connection + Unpin + Debug + Sync + Send + 'static,
{
/// Creates a new `HttpsConnector`.
///
/// The session cache configuration of `ssl` will be overwritten.
pub fn with_connector(
http: S,
ssl: SslConnectorBuilder,
) -> Result<HttpsConnector<S>, ErrorStack> {
HttpsLayer::with_connector(ssl).map(|l| l.layer(http))
}
/// Registers a callback which can customize the configuration of each connection.
///
/// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`),
/// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`]
/// instead.
pub fn set_callback<F>(&mut self, callback: F)
where
F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.callback = Some(Arc::new(callback));
}
/// Registers a callback which can customize the `Ssl` of each connection.
pub fn set_ssl_callback<F>(&mut self, callback: F)
where
F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.ssl_callback = Some(Arc::new(callback));
}
}
impl<S> Service<Uri> for HttpsConnector<S>
where
S: Service<Uri> + Send,
S::Error: Into<Box<dyn Error + Send + Sync>>,
S::Future: Unpin + Send + 'static,
S::Response: AsyncRead + AsyncWrite + Connection + Unpin + Debug + Sync + Send + 'static,
{
type Response = MaybeHttpsStream<S::Response>;
type Error = Box<dyn Error + Sync + Send>;
#[allow(clippy::type_complexity)]
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.http.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, uri: Uri) -> Self::Future {
let is_tls_scheme = uri
.scheme()
.map(|s| s == &Scheme::HTTPS || s.as_str() == "wss")
.unwrap_or(false);
let tls_setup = if is_tls_scheme {
Some((self.inner.clone(), uri.clone()))
} else {
None
};
let connect = self.http.call(uri);
let f = async {
let conn = connect.await.map_err(Into::into)?;
let (inner, uri) = match tls_setup {
Some((inner, uri)) => (inner, uri),
None => return Ok(MaybeHttpsStream::Http(conn)),
};
let mut host = uri.host().ok_or("URI missing host")?;
// If `host` is an IPv6 address, we must strip away the square brackets that surround
// it (otherwise, boring will fail to parse the host as an IP address, eventually
// causing the handshake to fail due a hostname verification error).
if !host.is_empty() {
let last = host.len() - 1;
let mut chars = host.chars();
if let (Some('['), Some(']')) = (chars.next(), chars.last()) {
if host[1..last].parse::<net::Ipv6Addr>().is_ok() {
host = &host[1..last];
}
}
}
let ssl = inner.setup_ssl(&uri, host)?;
let stream = tokio_boring::SslStreamBuilder::new(ssl, conn)
.connect()
.await?;
Ok(MaybeHttpsStream::Https(stream))
};
Box::pin(f)
}
}
/// A stream which may be wrapped with TLS.
pub enum MaybeHttpsStream<T> {
/// A raw HTTP stream.
@ -339,72 +67,6 @@ pub enum MaybeHttpsStream<T> {
Https(SslStream<T>),
}
impl<T> AsyncRead for MaybeHttpsStream<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
fn poll_read(
mut self: Pin<&mut Self>,
ctx: &mut Context<'_>,
buf: &mut ReadBuf,
) -> Poll<io::Result<()>> {
match &mut *self {
MaybeHttpsStream::Http(s) => Pin::new(s).poll_read(ctx, buf),
MaybeHttpsStream::Https(s) => Pin::new(s).poll_read(ctx, buf),
}
}
}
impl<T> AsyncWrite for MaybeHttpsStream<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
fn poll_write(
mut self: Pin<&mut Self>,
ctx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
match &mut *self {
MaybeHttpsStream::Http(s) => Pin::new(s).poll_write(ctx, buf),
MaybeHttpsStream::Https(s) => Pin::new(s).poll_write(ctx, buf),
}
}
fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut *self {
MaybeHttpsStream::Http(s) => Pin::new(s).poll_flush(ctx),
MaybeHttpsStream::Https(s) => Pin::new(s).poll_flush(ctx),
}
}
fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut *self {
MaybeHttpsStream::Http(s) => Pin::new(s).poll_shutdown(ctx),
MaybeHttpsStream::Https(s) => Pin::new(s).poll_shutdown(ctx),
}
}
}
impl<T> Connection for MaybeHttpsStream<T>
where
T: Connection,
{
fn connected(&self) -> Connected {
match self {
MaybeHttpsStream::Http(s) => s.connected(),
MaybeHttpsStream::Https(s) => {
let mut connected = s.get_ref().connected();
if s.ssl().selected_alpn_protocol() == Some(b"h2") {
connected = connected.negotiated_h2();
}
connected
}
}
}
}
impl<T> fmt::Debug for MaybeHttpsStream<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {

345
hyper-boring/src/v0.rs Normal file
View File

@ -0,0 +1,345 @@
use crate::cache::{SessionCache, SessionKey};
use crate::{key_index, HttpsLayerSettings, MaybeHttpsStream};
use antidote::Mutex;
use boring::error::ErrorStack;
use boring::ssl::{
ConnectConfiguration, Ssl, SslConnector, SslConnectorBuilder, SslMethod, SslRef,
SslSessionCacheMode,
};
use http_old::uri::Scheme;
use hyper_old::client::connect::{Connected, Connection};
use hyper_old::client::HttpConnector;
use hyper_old::service::Service;
use hyper_old::Uri;
use std::error::Error;
use std::future::Future;
use std::net;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::{fmt, io};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tower_layer::Layer;
/// A Connector using OpenSSL to support `http` and `https` schemes.
#[derive(Clone)]
pub struct HttpsConnector<T> {
http: T,
inner: Inner,
}
#[cfg(feature = "runtime")]
impl HttpsConnector<HttpConnector> {
/// Creates a a new `HttpsConnector` using default settings.
///
/// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both
/// HTTP/2 and HTTP/1.1.
///
/// Requires the `runtime` Cargo feature.
pub fn new() -> Result<HttpsConnector<HttpConnector>, ErrorStack> {
let mut http = HttpConnector::new();
http.enforce_http(false);
HttpsLayer::new().map(|l| l.layer(http))
}
}
impl<S, T> HttpsConnector<S>
where
S: Service<Uri, Response = T> + Send,
S::Error: Into<Box<dyn Error + Send + Sync>>,
S::Future: Unpin + Send + 'static,
T: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static,
{
/// Creates a new `HttpsConnector`.
///
/// The session cache configuration of `ssl` will be overwritten.
pub fn with_connector(
http: S,
ssl: SslConnectorBuilder,
) -> Result<HttpsConnector<S>, ErrorStack> {
HttpsLayer::with_connector(ssl).map(|l| l.layer(http))
}
/// Registers a callback which can customize the configuration of each connection.
///
/// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`),
/// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`]
/// instead.
pub fn set_callback<F>(&mut self, callback: F)
where
F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.callback = Some(Arc::new(callback));
}
/// Registers a callback which can customize the `Ssl` of each connection.
pub fn set_ssl_callback<F>(&mut self, callback: F)
where
F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.ssl_callback = Some(Arc::new(callback));
}
}
/// A layer which wraps services in an `HttpsConnector`.
pub struct HttpsLayer {
inner: Inner,
}
#[derive(Clone)]
struct Inner {
ssl: SslConnector,
cache: Arc<Mutex<SessionCache>>,
callback: Option<Callback>,
ssl_callback: Option<SslCallback>,
}
type Callback =
Arc<dyn Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + Sync + Send>;
type SslCallback = Arc<dyn Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + Sync + Send>;
impl HttpsLayer {
/// Creates a new `HttpsLayer` with default settings.
///
/// ALPN is configured to support both HTTP/1 and HTTP/1.1.
pub fn new() -> Result<HttpsLayer, ErrorStack> {
let mut ssl = SslConnector::builder(SslMethod::tls())?;
ssl.set_alpn_protos(b"\x02h2\x08http/1.1")?;
Self::with_connector(ssl)
}
/// Creates a new `HttpsLayer`.
///
/// The session cache configuration of `ssl` will be overwritten.
pub fn with_connector(ssl: SslConnectorBuilder) -> Result<HttpsLayer, ErrorStack> {
Self::with_connector_and_settings(ssl, Default::default())
}
/// Creates a new `HttpsLayer` with settings
pub fn with_connector_and_settings(
mut ssl: SslConnectorBuilder,
settings: HttpsLayerSettings,
) -> Result<HttpsLayer, ErrorStack> {
let cache = Arc::new(Mutex::new(SessionCache::with_capacity(
settings.session_cache_capacity,
)));
ssl.set_session_cache_mode(SslSessionCacheMode::CLIENT);
ssl.set_new_session_callback({
let cache = cache.clone();
move |ssl, session| {
if let Some(key) = key_index().ok().and_then(|idx| ssl.ex_data(idx)) {
cache.lock().insert(key.clone(), session);
}
}
});
Ok(HttpsLayer {
inner: Inner {
ssl: ssl.build(),
cache,
callback: None,
ssl_callback: None,
},
})
}
/// Registers a callback which can customize the configuration of each connection.
///
/// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`),
/// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`]
/// instead.
pub fn set_callback<F>(&mut self, callback: F)
where
F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.callback = Some(Arc::new(callback));
}
/// Registers a callback which can customize the `Ssl` of each connection.
pub fn set_ssl_callback<F>(&mut self, callback: F)
where
F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.ssl_callback = Some(Arc::new(callback));
}
}
impl<S> Layer<S> for HttpsLayer {
type Service = HttpsConnector<S>;
fn layer(&self, inner: S) -> HttpsConnector<S> {
HttpsConnector {
http: inner,
inner: self.inner.clone(),
}
}
}
impl Inner {
fn setup_ssl(&self, uri: &Uri, host: &str) -> Result<Ssl, ErrorStack> {
let mut conf = self.ssl.configure()?;
if let Some(ref callback) = self.callback {
callback(&mut conf, uri)?;
}
let key = SessionKey {
host: host.to_string(),
port: uri.port_u16().unwrap_or(443),
};
if let Some(session) = self.cache.lock().get(&key) {
unsafe {
conf.set_session(&session)?;
}
}
let idx = key_index()?;
conf.set_ex_data(idx, key);
let mut ssl = conf.into_ssl(host)?;
if let Some(ref ssl_callback) = self.ssl_callback {
ssl_callback(&mut ssl, uri)?;
}
Ok(ssl)
}
}
impl<S> Service<Uri> for HttpsConnector<S>
where
S: Service<Uri> + Send,
S::Error: Into<Box<dyn Error + Send + Sync>>,
S::Future: Unpin + Send + 'static,
S::Response: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static,
{
type Response = MaybeHttpsStream<S::Response>;
type Error = Box<dyn Error + Sync + Send>;
#[allow(clippy::type_complexity)]
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.http.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, uri: Uri) -> Self::Future {
let is_tls_scheme = uri
.scheme()
.map(|s| s == &Scheme::HTTPS || s.as_str() == "wss")
.unwrap_or(false);
let tls_setup = if is_tls_scheme {
Some((self.inner.clone(), uri.clone()))
} else {
None
};
let connect = self.http.call(uri);
let f = async {
let conn = connect.await.map_err(Into::into)?;
let (inner, uri) = match tls_setup {
Some((inner, uri)) => (inner, uri),
None => return Ok(MaybeHttpsStream::Http(conn)),
};
let mut host = uri.host().ok_or("URI missing host")?;
// If `host` is an IPv6 address, we must strip away the square brackets that surround
// it (otherwise, boring will fail to parse the host as an IP address, eventually
// causing the handshake to fail due a hostname verification error).
if !host.is_empty() {
let last = host.len() - 1;
let mut chars = host.chars();
if let (Some('['), Some(']')) = (chars.next(), chars.last()) {
if host[1..last].parse::<net::Ipv6Addr>().is_ok() {
host = &host[1..last];
}
}
}
let ssl = inner.setup_ssl(&uri, host)?;
let stream = tokio_boring::SslStreamBuilder::new(ssl, conn)
.connect()
.await?;
Ok(MaybeHttpsStream::Https(stream))
};
Box::pin(f)
}
}
impl<T> Connection for MaybeHttpsStream<T>
where
T: Connection,
{
fn connected(&self) -> Connected {
match self {
MaybeHttpsStream::Http(s) => s.connected(),
MaybeHttpsStream::Https(s) => {
let mut connected = s.get_ref().connected();
if s.ssl().selected_alpn_protocol() == Some(b"h2") {
connected = connected.negotiated_h2();
}
connected
}
}
}
}
impl<T> AsyncRead for MaybeHttpsStream<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
fn poll_read(
mut self: Pin<&mut Self>,
ctx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
match &mut *self {
MaybeHttpsStream::Http(s) => Pin::new(s).poll_read(ctx, buf),
MaybeHttpsStream::Https(s) => Pin::new(s).poll_read(ctx, buf),
}
}
}
impl<T> AsyncWrite for MaybeHttpsStream<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
fn poll_write(
mut self: Pin<&mut Self>,
ctx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
match &mut *self {
MaybeHttpsStream::Http(s) => Pin::new(s).poll_write(ctx, buf),
MaybeHttpsStream::Https(s) => Pin::new(s).poll_write(ctx, buf),
}
}
fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut *self {
MaybeHttpsStream::Http(s) => Pin::new(s).poll_flush(ctx),
MaybeHttpsStream::Https(s) => Pin::new(s).poll_flush(ctx),
}
}
fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut *self {
MaybeHttpsStream::Http(s) => Pin::new(s).poll_shutdown(ctx),
MaybeHttpsStream::Https(s) => Pin::new(s).poll_shutdown(ctx),
}
}
}

350
hyper-boring/src/v1.rs Normal file
View File

@ -0,0 +1,350 @@
use crate::cache::{SessionCache, SessionKey};
use crate::{key_index, HttpsLayerSettings, MaybeHttpsStream};
use antidote::Mutex;
use boring::error::ErrorStack;
use boring::ssl::{
ConnectConfiguration, Ssl, SslConnector, SslConnectorBuilder, SslMethod, SslRef,
SslSessionCacheMode,
};
use http::uri::Scheme;
use http::Uri;
use hyper::rt::{Read, ReadBufCursor, Write};
use hyper_util::client::legacy::connect::{Connected, Connection, HttpConnector};
use hyper_util::rt::TokioIo;
use std::error::Error;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::{io, net};
use tokio::io::{AsyncRead, AsyncWrite};
use tower_layer::Layer;
use tower_service::Service;
/// A Connector using BoringSSL to support `http` and `https` schemes.
#[derive(Clone)]
pub struct HttpsConnector<T> {
http: T,
inner: Inner,
}
#[cfg(feature = "runtime")]
impl HttpsConnector<HttpConnector> {
/// Creates a a new `HttpsConnector` using default settings.
///
/// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both
/// HTTP/2 and HTTP/1.1.
///
/// Requires the `runtime` Cargo feature.
pub fn new() -> Result<HttpsConnector<HttpConnector>, ErrorStack> {
let mut http = HttpConnector::new();
http.enforce_http(false);
HttpsLayer::new().map(|l| l.layer(http))
}
}
impl<S, T> HttpsConnector<S>
where
S: Service<Uri, Response = TokioIo<T>> + Send,
S::Error: Into<Box<dyn Error + Send + Sync>>,
S::Future: Unpin + Send + 'static,
T: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static,
{
/// Creates a new `HttpsConnector`.
///
/// The session cache configuration of `ssl` will be overwritten.
pub fn with_connector(
http: S,
ssl: SslConnectorBuilder,
) -> Result<HttpsConnector<S>, ErrorStack> {
HttpsLayer::with_connector(ssl).map(|l| l.layer(http))
}
/// Registers a callback which can customize the configuration of each connection.
///
/// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`),
/// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`]
/// instead.
pub fn set_callback<F>(&mut self, callback: F)
where
F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.callback = Some(Arc::new(callback));
}
/// Registers a callback which can customize the `Ssl` of each connection.
pub fn set_ssl_callback<F>(&mut self, callback: F)
where
F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.ssl_callback = Some(Arc::new(callback));
}
}
/// A layer which wraps services in an `HttpsConnector`.
pub struct HttpsLayer {
inner: Inner,
}
#[derive(Clone)]
struct Inner {
ssl: SslConnector,
cache: Arc<Mutex<SessionCache>>,
callback: Option<Callback>,
ssl_callback: Option<SslCallback>,
}
type Callback =
Arc<dyn Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + Sync + Send>;
type SslCallback = Arc<dyn Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + Sync + Send>;
impl HttpsLayer {
/// Creates a new `HttpsLayer` with default settings.
///
/// ALPN is configured to support both HTTP/1 and HTTP/1.1.
pub fn new() -> Result<HttpsLayer, ErrorStack> {
let mut ssl = SslConnector::builder(SslMethod::tls())?;
ssl.set_alpn_protos(b"\x02h2\x08http/1.1")?;
Self::with_connector(ssl)
}
/// Creates a new `HttpsLayer`.
///
/// The session cache configuration of `ssl` will be overwritten.
pub fn with_connector(ssl: SslConnectorBuilder) -> Result<HttpsLayer, ErrorStack> {
Self::with_connector_and_settings(ssl, Default::default())
}
/// Creates a new `HttpsLayer` with settings
pub fn with_connector_and_settings(
mut ssl: SslConnectorBuilder,
settings: HttpsLayerSettings,
) -> Result<HttpsLayer, ErrorStack> {
let cache = Arc::new(Mutex::new(SessionCache::with_capacity(
settings.session_cache_capacity,
)));
ssl.set_session_cache_mode(SslSessionCacheMode::CLIENT);
ssl.set_new_session_callback({
let cache = cache.clone();
move |ssl, session| {
if let Some(key) = key_index().ok().and_then(|idx| ssl.ex_data(idx)) {
cache.lock().insert(key.clone(), session);
}
}
});
Ok(HttpsLayer {
inner: Inner {
ssl: ssl.build(),
cache,
callback: None,
ssl_callback: None,
},
})
}
/// Registers a callback which can customize the configuration of each connection.
///
/// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`),
/// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`]
/// instead.
pub fn set_callback<F>(&mut self, callback: F)
where
F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.callback = Some(Arc::new(callback));
}
/// Registers a callback which can customize the `Ssl` of each connection.
pub fn set_ssl_callback<F>(&mut self, callback: F)
where
F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.ssl_callback = Some(Arc::new(callback));
}
}
impl<S> Layer<S> for HttpsLayer {
type Service = HttpsConnector<S>;
fn layer(&self, inner: S) -> HttpsConnector<S> {
HttpsConnector {
http: inner,
inner: self.inner.clone(),
}
}
}
impl Inner {
fn setup_ssl(&self, uri: &Uri, host: &str) -> Result<Ssl, ErrorStack> {
let mut conf = self.ssl.configure()?;
if let Some(ref callback) = self.callback {
callback(&mut conf, uri)?;
}
let key = SessionKey {
host: host.to_string(),
port: uri.port_u16().unwrap_or(443),
};
if let Some(session) = self.cache.lock().get(&key) {
unsafe {
conf.set_session(&session)?;
}
}
let idx = key_index()?;
conf.set_ex_data(idx, key);
let mut ssl = conf.into_ssl(host)?;
if let Some(ref ssl_callback) = self.ssl_callback {
ssl_callback(&mut ssl, uri)?;
}
Ok(ssl)
}
}
impl<T, S> Service<Uri> for HttpsConnector<S>
where
S: Service<Uri, Response = TokioIo<T>> + Send,
S::Error: Into<Box<dyn Error + Send + Sync>>,
S::Future: Unpin + Send + 'static,
T: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static,
{
type Response = MaybeHttpsStream<T>;
type Error = Box<dyn Error + Sync + Send>;
#[allow(clippy::type_complexity)]
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.http.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, uri: Uri) -> Self::Future {
let is_tls_scheme = uri
.scheme()
.map(|s| s == &Scheme::HTTPS || s.as_str() == "wss")
.unwrap_or(false);
let tls_setup = if is_tls_scheme {
Some((self.inner.clone(), uri.clone()))
} else {
None
};
let connect = self.http.call(uri);
let f = async {
let conn = connect.await.map_err(Into::into)?.into_inner();
let (inner, uri) = match tls_setup {
Some((inner, uri)) => (inner, uri),
None => return Ok(MaybeHttpsStream::Http(conn)),
};
let mut host = uri.host().ok_or("URI missing host")?;
// If `host` is an IPv6 address, we must strip away the square brackets that surround
// it (otherwise, boring will fail to parse the host as an IP address, eventually
// causing the handshake to fail due a hostname verification error).
if !host.is_empty() {
let last = host.len() - 1;
let mut chars = host.chars();
if let (Some('['), Some(']')) = (chars.next(), chars.last()) {
if host[1..last].parse::<net::Ipv6Addr>().is_ok() {
host = &host[1..last];
}
}
}
let ssl = inner.setup_ssl(&uri, host)?;
let stream = tokio_boring::SslStreamBuilder::new(ssl, conn)
.connect()
.await?;
Ok(MaybeHttpsStream::Https(stream))
};
Box::pin(f)
}
}
impl<T> Connection for MaybeHttpsStream<T>
where
T: Connection,
{
fn connected(&self) -> Connected {
match self {
MaybeHttpsStream::Http(s) => s.connected(),
MaybeHttpsStream::Https(s) => {
let mut connected = s.get_ref().connected();
if s.ssl().selected_alpn_protocol() == Some(b"h2") {
connected = connected.negotiated_h2();
}
connected
}
}
}
}
impl<T> Read for MaybeHttpsStream<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: ReadBufCursor<'_>,
) -> Poll<Result<(), std::io::Error>> {
match &mut *self {
MaybeHttpsStream::Http(inner) => Pin::new(&mut TokioIo::new(inner)).poll_read(cx, buf),
MaybeHttpsStream::Https(inner) => Pin::new(&mut TokioIo::new(inner)).poll_read(cx, buf),
}
}
}
impl<T> Write for MaybeHttpsStream<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
fn poll_write(
mut self: Pin<&mut Self>,
ctx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
match &mut *self {
MaybeHttpsStream::Http(inner) => {
Pin::new(&mut TokioIo::new(inner)).poll_write(ctx, buf)
}
MaybeHttpsStream::Https(inner) => {
Pin::new(&mut TokioIo::new(inner)).poll_write(ctx, buf)
}
}
}
fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut *self {
MaybeHttpsStream::Http(inner) => Pin::new(&mut TokioIo::new(inner)).poll_flush(ctx),
MaybeHttpsStream::Https(inner) => Pin::new(&mut TokioIo::new(inner)).poll_flush(ctx),
}
}
fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut *self {
MaybeHttpsStream::Http(inner) => Pin::new(&mut TokioIo::new(inner)).poll_shutdown(ctx),
MaybeHttpsStream::Https(inner) => Pin::new(&mut TokioIo::new(inner)).poll_shutdown(ctx),
}
}
}

View File

@ -1,12 +1,12 @@
use super::*;
use boring::ssl::{SslAcceptor, SslFiletype, SslMethod};
use boring::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod};
use futures::StreamExt;
use hyper::client::HttpConnector;
use hyper::server::conn::Http;
use hyper::{service, Response};
use hyper::{Body, Client};
use hyper_boring::HttpsConnector;
use hyper_old::client::HttpConnector;
use hyper_old::server::conn::Http;
use hyper_old::{service, Response};
use hyper_old::{Body, Client};
use std::convert::Infallible;
use std::iter;
use std::{io, iter};
use tokio::net::TcpListener;
#[tokio::test]
@ -37,10 +37,10 @@ async fn localhost() {
let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
acceptor.set_session_id_context(b"test").unwrap();
acceptor
.set_private_key_file("test/key.pem", SslFiletype::PEM)
.set_private_key_file("tests/test/key.pem", SslFiletype::PEM)
.unwrap();
acceptor
.set_certificate_chain_file("test/cert.pem")
.set_certificate_chain_file("tests/test/cert.pem")
.unwrap();
let acceptor = acceptor.build();
@ -69,7 +69,7 @@ async fn localhost() {
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
ssl.set_ca_file("test/root-ca.pem").unwrap();
ssl.set_ca_file("tests/test/root-ca.pem").unwrap();
use std::fs::File;
use std::io::Write;
@ -104,10 +104,10 @@ async fn alpn_h2() {
let server = async move {
let mut acceptor = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap();
acceptor
.set_certificate_chain_file("test/cert.pem")
.set_certificate_chain_file("tests/test/cert.pem")
.unwrap();
acceptor
.set_private_key_file("test/key.pem", SslFiletype::PEM)
.set_private_key_file("tests/test/key.pem", SslFiletype::PEM)
.unwrap();
acceptor.set_alpn_select_callback(|_, client| {
ssl::select_next_proto(b"\x02h2", client).ok_or(AlpnError::NOACK)
@ -138,7 +138,7 @@ async fn alpn_h2() {
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
ssl.set_ca_file("test/root-ca.pem").unwrap();
ssl.set_ca_file("tests/test/root-ca.pem").unwrap();
let mut ssl = HttpsConnector::with_connector(connector, ssl).unwrap();

160
hyper-boring/tests/v1.rs Normal file
View File

@ -0,0 +1,160 @@
#![cfg(feature = "hyper1")]
use boring::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod};
use bytes::Bytes;
use futures::StreamExt;
use http_body_util::{BodyStream, Empty};
use hyper::{service, Response};
use hyper_boring::v1::HttpsConnector;
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_util::client::legacy::Client;
use hyper_util::rt::{TokioExecutor, TokioIo};
use std::convert::Infallible;
use std::{io, iter};
use tokio::net::TcpListener;
#[tokio::test]
async fn google() {
let ssl = HttpsConnector::new().unwrap();
let client = Client::builder(TokioExecutor::new())
.pool_max_idle_per_host(0)
.build::<_, Empty<Bytes>>(ssl);
for _ in 0..3 {
let resp = client
.get("https://www.google.com".parse().unwrap())
.await
.expect("connection should succeed");
let mut body = BodyStream::new(resp.into_body());
while body.next().await.transpose().unwrap().is_some() {}
}
}
#[tokio::test]
async fn localhost() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let port = addr.port();
let server = async move {
let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
acceptor.set_session_id_context(b"test").unwrap();
acceptor
.set_private_key_file("tests/test/key.pem", SslFiletype::PEM)
.unwrap();
acceptor
.set_certificate_chain_file("tests/test/cert.pem")
.unwrap();
let acceptor = acceptor.build();
for _ in 0..3 {
let stream = listener.accept().await.unwrap().0;
let stream = tokio_boring::accept(&acceptor, stream).await.unwrap();
let service = service::service_fn(|_| async {
Ok::<_, io::Error>(Response::new(<Empty<Bytes>>::new()))
});
hyper::server::conn::http1::Builder::new()
.keep_alive(false)
.serve_connection(TokioIo::new(stream), service)
.await
.unwrap();
}
};
tokio::spawn(server);
let resolver =
tower::service_fn(move |_name| async move { Ok::<_, Infallible>(iter::once(addr)) });
let mut connector = HttpConnector::new_with_resolver(resolver);
connector.enforce_http(false);
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
ssl.set_ca_file("tests/test/root-ca.pem").unwrap();
use std::fs::File;
use std::io::Write;
let file = File::create("../target/keyfile.log").unwrap();
ssl.set_keylog_callback(move |_, line| {
let _ = writeln!(&file, "{}", line);
});
let ssl = HttpsConnector::with_connector(connector, ssl).unwrap();
let client = Client::builder(TokioExecutor::new()).build::<_, Empty<Bytes>>(ssl);
for _ in 0..3 {
let resp = client
.get(format!("https://foobar.com:{}", port).parse().unwrap())
.await
.unwrap();
assert!(resp.status().is_success(), "{}", resp.status());
let mut body = BodyStream::new(resp.into_body());
while body.next().await.transpose().unwrap().is_some() {}
}
}
#[tokio::test]
async fn alpn_h2() {
use boring::ssl::{self, AlpnError};
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let port = addr.port();
let server = async move {
let mut acceptor = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap();
acceptor
.set_certificate_chain_file("tests/test/cert.pem")
.unwrap();
acceptor
.set_private_key_file("tests/test/key.pem", SslFiletype::PEM)
.unwrap();
acceptor.set_alpn_select_callback(|_, client| {
ssl::select_next_proto(b"\x02h2", client).ok_or(AlpnError::NOACK)
});
let acceptor = acceptor.build();
let stream = listener.accept().await.unwrap().0;
let stream = tokio_boring::accept(&acceptor, stream).await.unwrap();
assert_eq!(stream.ssl().selected_alpn_protocol().unwrap(), b"h2");
let service = service::service_fn(|_| async {
Ok::<_, io::Error>(Response::new(<Empty<Bytes>>::new()))
});
hyper::server::conn::http2::Builder::new(TokioExecutor::new())
.serve_connection(TokioIo::new(stream), service)
.await
.unwrap();
};
tokio::spawn(server);
let resolver =
tower::service_fn(move |_name| async move { Ok::<_, Infallible>(iter::once(addr)) });
let mut connector = HttpConnector::new_with_resolver(resolver);
connector.enforce_http(false);
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
ssl.set_ca_file("tests/test/root-ca.pem").unwrap();
let mut ssl = HttpsConnector::with_connector(connector, ssl).unwrap();
ssl.set_ssl_callback(|ssl, _| ssl.set_alpn_protos(b"\x02h2\x08http/1.1"));
let client = Client::builder(TokioExecutor::new()).build::<_, Empty<Bytes>>(ssl);
let resp = client
.get(format!("https://foobar.com:{}", port).parse().unwrap())
.await
.unwrap();
assert!(resp.status().is_success(), "{}", resp.status());
let mut body = BodyStream::new(resp.into_body());
while body.next().await.transpose().unwrap().is_some() {}
}