diff --git a/ylong_http/src/pseudo.rs b/ylong_http/src/pseudo.rs index e8afad2c31e8bfa00c1b0e45e4fa4f083f5709e4..c8f4552ea0493c0886ed519736a84fc895b68a24 100644 --- a/ylong_http/src/pseudo.rs +++ b/ylong_http/src/pseudo.rs @@ -44,7 +44,7 @@ pub struct PseudoHeaders { // TODO: 去掉冗余的方法。 impl PseudoHeaders { /// Create a new `PseudoHeaders`. - pub(crate) fn new() -> Self { + pub fn new() -> Self { Self { authority: None, method: None, diff --git a/ylong_http_client/src/async_impl/quic/mod.rs b/ylong_http_client/src/async_impl/quic/mod.rs index b7cd93a64f178e83e150480fed30fada9bd4fa5e..f822e19a19fe2d3a452b0f3e4326bfbbd7d91ab6 100644 --- a/ylong_http_client/src/async_impl/quic/mod.rs +++ b/ylong_http_client/src/async_impl/quic/mod.rs @@ -25,7 +25,7 @@ use libc::{ use ylong_runtime::fastrand::fast_random; use ylong_runtime::time::timeout; -use crate::c_openssl::ssl::verify_server_cert; +use crate::c_openssl::ssl::{verify_server_cert, verify_server_root_cert}; use crate::runtime::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use crate::util::c_openssl::ssl::Ssl; use crate::util::ConnInfo; @@ -142,14 +142,22 @@ impl QuicConn { break; } if self.is_established() { - let Some(key) = tls_config.pinning_host_match(stream.conn_data().detail().addr()) + let Some(pins_info) = + tls_config.pinning_host_match(stream.conn_data().detail().addr()) else { break; }; - if verify_server_cert(ssl.get_raw_ptr(), key.as_str()).is_ok() { + // cert pins verify + let verify_result = if pins_info.is_root() { + verify_server_root_cert(ssl.get_raw_ptr(), pins_info.get_digest()) + } else { + verify_server_cert(ssl.get_raw_ptr(), pins_info.get_digest()) + }; + if verify_result.is_ok() { return Ok(()); } + e = Err(HttpClientError::from_str( ErrorKind::Connect, "verify server cert failed", diff --git a/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs b/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs index ad92d5b62710958dc78041ffa6a8e2c54d2b16ba..235ff1cc168a44ae2f9e57fc61294d58b1d95192 100644 --- a/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs +++ b/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs @@ -17,6 +17,7 @@ use core::{future, ptr, slice}; use std::io::{self, Read, Write}; use crate::async_impl::ssl_stream::{check_io_to_poll, Wrapper}; +use crate::c_openssl::verify::PinsVerifyInfo; use crate::runtime::{AsyncRead, AsyncWrite, ReadBuf}; use crate::util::c_openssl::error::ErrorStack; use crate::util::c_openssl::ssl::{self, ShutdownResult, Ssl, SslErrorCode}; @@ -61,7 +62,7 @@ where pub(crate) fn new( ssl: Ssl, stream: S, - pinned_pubkey: Option, + pinned_pubkey: Option, ) -> Result { // This corresponds to `SSL_set_bio`. ssl::SslStream::new_base( diff --git a/ylong_http_client/src/util/c_openssl/adapter.rs b/ylong_http_client/src/util/c_openssl/adapter.rs index d2d5820a481c23c28f1685f2f25a0bc14ed9f824..fe6f46b467d197e3d7cdd8f0f884b65ef1571b13 100644 --- a/ylong_http_client/src/util/c_openssl/adapter.rs +++ b/ylong_http_client/src/util/c_openssl/adapter.rs @@ -20,7 +20,7 @@ use crate::util::c_openssl::error::ErrorStack; use crate::util::c_openssl::ssl::{ Ssl, SslContext, SslContextBuilder, SslFiletype, SslMethod, SslVersion, }; -use crate::util::c_openssl::verify::PubKeyPins; +use crate::util::c_openssl::verify::{PinsVerifyInfo, PubKeyPins}; use crate::util::c_openssl::x509::{X509Store, X509}; use crate::util::config::tls::DefaultCertVerifier; use crate::util::AlpnProtocolList; @@ -451,7 +451,7 @@ impl TlsConfig { Ok(TlsSsl(ssl)) } - pub(crate) fn pinning_host_match(&self, domain: &str) -> Option { + pub(crate) fn pinning_host_match(&self, domain: &str) -> Option { match &self.pins { None => None, Some(pins) => pins.get_pin(domain), diff --git a/ylong_http_client/src/util/c_openssl/error.rs b/ylong_http_client/src/util/c_openssl/error.rs index 389a27055069fedb5134736d6685ff93e67ac7c3..a7fff22dffd991e2a9d8ade9d1068d75743adfe0 100644 --- a/ylong_http_client/src/util/c_openssl/error.rs +++ b/ylong_http_client/src/util/c_openssl/error.rs @@ -444,7 +444,7 @@ mod ut_stack_error { fn ut_ut_error_get_func() { let code = 0x12345; let get_code = error_get_func(code); - #[cfg(feature = "c_openssl_1_1")] + #[cfg(any(feature = "c_openssl_1_1", feature = "c_boringssl"))] assert_eq!(get_code, 18); #[cfg(feature = "c_openssl_3_0")] assert_eq!(get_code, 0); @@ -452,6 +452,7 @@ mod ut_stack_error { } #[cfg(test)] +#[cfg(feature = "__c_openssl")] mod ut_error_stack { use super::*; @@ -462,7 +463,6 @@ mod ut_error_stack { /// 2. Formats the entire error stack. /// 3. Verify if the formatted message is correct. #[test] - #[cfg(feature = "__c_openssl")] fn ut_fmt() { let error1 = StackError { code: 0x00000001, diff --git a/ylong_http_client/src/util/c_openssl/ffi/ssl.rs b/ylong_http_client/src/util/c_openssl/ffi/ssl.rs index cd5c952c7986afad569c445a65e3d192fd72528b..755e762cbd905007c0cabad31b95185b30a605b4 100644 --- a/ylong_http_client/src/util/c_openssl/ffi/ssl.rs +++ b/ylong_http_client/src/util/c_openssl/ffi/ssl.rs @@ -14,7 +14,7 @@ use libc::{c_char, c_int, c_long, c_uchar, c_uint, c_void}; use super::bio::BIO; -use super::x509::{C_X509, X509_STORE, X509_STORE_CTX, X509_VERIFY_PARAM}; +use super::x509::{C_X509, STACK_X509, X509_STORE, X509_STORE_CTX, X509_VERIFY_PARAM}; /// This is the global context structure which is created by a server or client /// once per program life-time and which holds mainly default values for the @@ -177,6 +177,12 @@ extern "C" { #[cfg(any(feature = "c_openssl_1_1", feature = "c_boringssl"))] pub(crate) fn SSL_get_peer_certificate(ssl: *const SSL) -> *mut C_X509; + pub(crate) fn SSL_get_peer_cert_chain(ssl: *const SSL) -> *mut STACK_X509; + + /// Increases the reference count of all certificates in chain x and returns + /// a copy of the stack + pub(crate) fn X509_chain_up_ref(stack_x509: *mut STACK_X509) -> *mut STACK_X509; + pub(crate) fn SSL_set_bio(ssl: *mut SSL, rbio: *mut BIO, wbio: *mut BIO); pub(crate) fn SSL_get_rbio(ssl: *const SSL) -> *mut BIO; diff --git a/ylong_http_client/src/util/c_openssl/ffi/stack.rs b/ylong_http_client/src/util/c_openssl/ffi/stack.rs index ad1d7c33e58a746322fb0415bf2eebb555d6bb09..c5f9eb7c2d4e15ccf849187ff220f98464eeeb6e 100644 --- a/ylong_http_client/src/util/c_openssl/ffi/stack.rs +++ b/ylong_http_client/src/util/c_openssl/ffi/stack.rs @@ -13,14 +13,101 @@ use libc::{c_int, c_void}; +#[cfg(not(feature = "c_boringssl"))] pub(crate) enum OPENSSL_STACK {} +#[cfg(not(feature = "c_boringssl"))] extern "C" { - pub(crate) fn OPENSSL_sk_free(st: *mut OPENSSL_STACK); + fn OPENSSL_sk_free(st: *mut OPENSSL_STACK); - pub(crate) fn OPENSSL_sk_pop(st: *mut OPENSSL_STACK) -> *mut c_void; + fn OPENSSL_sk_pop(st: *mut OPENSSL_STACK) -> *mut c_void; - pub(crate) fn OPENSSL_sk_value(stack: *const OPENSSL_STACK, idx: c_int) -> *mut c_void; + fn OPENSSL_sk_value(stack: *const OPENSSL_STACK, idx: c_int) -> *mut c_void; - pub(crate) fn OPENSSL_sk_num(stack: *const OPENSSL_STACK) -> c_int; + fn OPENSSL_sk_num(stack: *const OPENSSL_STACK) -> c_int; +} + +#[cfg(feature = "c_boringssl")] +pub(crate) type STACK = *mut c_void; +#[cfg(not(feature = "c_boringssl"))] +pub(crate) type STACK = *mut OPENSSL_STACK; + +#[cfg(feature = "c_boringssl")] +extern "C" { + fn sk_free(st: STACK); + fn sk_pop(st: STACK) -> *mut c_void; + fn sk_value(st: STACK, idx: c_int) -> *mut c_void; + fn sk_num(st: STACK) -> c_int; +} + +/// Frees a stack allocated by OpenSSL or BoringSSL. +/// +/// # Safety +/// - `st` must be a valid pointer to a stack allocated by the same library +/// (OpenSSL or BoringSSL) used in this crate. +/// - The stack must not be accessed or freed elsewhere after this call. +pub(crate) unsafe fn unified_sk_free(st: STACK) { + #[cfg(feature = "c_boringssl")] + { + sk_free(st); + } + #[cfg(not(feature = "c_boringssl"))] + { + OPENSSL_sk_free(st); + } +} + +/// Retrieves a pointer to a stack element from a stack allocated by OpenSSL or BoringSSL at the specified index. +/// +/// # Safety +/// - `st` must be a valid pointer to a stack allocated by the same library +/// (OpenSSL or BoringSSL) used in this crate. +/// - `idx` must be a valid index within the bounds of the stack. +/// if the index is out of range, the function will return `null`. +pub(crate) unsafe fn unified_sk_value(st: STACK, idx: c_int) -> *mut c_void { + #[cfg(feature = "c_boringssl")] + { + sk_value(st, idx) + } + #[cfg(not(feature = "c_boringssl"))] + { + OPENSSL_sk_value(st, idx) + } +} + +/// Returns the number of elements in a stack allocated by OpenSSL or BoringSSL. +/// +/// # Safety +/// - `st` must be a valid pointer to a stack allocated by the same library +/// (OpenSSL or BoringSSL) used in this crate. +/// - The caller must ensure that `st` is not a null pointer. If `st` is `NULL`, +/// the function will return `-1`. +pub(crate) unsafe fn unified_sk_num(st: STACK) -> c_int { + #[cfg(feature = "c_boringssl")] + { + sk_num(st) + } + #[cfg(not(feature = "c_boringssl"))] + { + OPENSSL_sk_num(st) + } +} + +/// Pops a value from the top of a stack allocated by OpenSSL or BoringSSL. +/// If the stack is empty, this function returns `null`. +/// +/// # Safety +/// - `st` must be a valid pointer to a stack allocated by the same library +/// (OpenSSL or BoringSSL) used in this crate. +/// - The caller must check the return value of this function. If the stack is empty, +/// the function will return `null`. The caller must handle this case appropriately. +pub(crate) unsafe fn unified_sk_pop(st: STACK) -> *mut c_void { + #[cfg(feature = "c_boringssl")] + { + sk_pop(st) + } + #[cfg(not(feature = "c_boringssl"))] + { + OPENSSL_sk_pop(st) + } } diff --git a/ylong_http_client/src/util/c_openssl/ssl/mod.rs b/ylong_http_client/src/util/c_openssl/ssl/mod.rs index ab0dfd09d44fea7523f648902f6802c930ac475f..0b595f06cae5aee5120081335dff20eba95ea2f1 100644 --- a/ylong_http_client/src/util/c_openssl/ssl/mod.rs +++ b/ylong_http_client/src/util/c_openssl/ssl/mod.rs @@ -25,6 +25,6 @@ pub(crate) use filetype::SslFiletype; pub(crate) use method::SslMethod; pub(crate) use ssl_base::{Ssl, SslRef}; #[cfg(feature = "http3")] -pub(crate) use stream::verify_server_cert; +pub(crate) use stream::{verify_server_cert, verify_server_root_cert}; pub(crate) use stream::{MidHandshakeSslStream, ShutdownResult, SslStream}; pub(crate) use version::SslVersion; diff --git a/ylong_http_client/src/util/c_openssl/ssl/stream.rs b/ylong_http_client/src/util/c_openssl/ssl/stream.rs index 1e1263e98ed58388600f74928ae2674329a9cd56..14ed1b40b04a8e5b088efdcf9e986795f3b51e19 100644 --- a/ylong_http_client/src/util/c_openssl/ssl/stream.rs +++ b/ylong_http_client/src/util/c_openssl/ssl/stream.rs @@ -25,6 +25,8 @@ use crate::c_openssl::bio::{self, get_error, get_panic, get_stream_mut, get_stre use crate::c_openssl::error::ErrorStack; use crate::c_openssl::ffi::ssl::{SSL_connect, SSL_set_bio, SSL_shutdown}; use crate::c_openssl::foreign::Foreign; +use crate::c_openssl::verify::PinsVerifyInfo; + use crate::util::base64::encode; use crate::util::c_openssl::bio::BioMethod; use crate::util::c_openssl::error::VerifyError; @@ -37,7 +39,7 @@ use crate::util::c_openssl::verify::sha256_digest; pub struct SslStream { pub(crate) ssl: ManuallyDrop, method: ManuallyDrop, - pinned_pubkey: Option, + pinned_pubkey: Option, p: PhantomData, } @@ -141,7 +143,7 @@ impl SslStream { pub(crate) fn new_base( ssl: Ssl, stream: S, - pinned_pubkey: Option, + pinned_pubkey: Option, ) -> Result { unsafe { let (bio, method) = bio::new(stream)?; @@ -158,17 +160,18 @@ impl SslStream { pub(crate) fn connect(&mut self) -> Result<(), SslError> { let ret = unsafe { SSL_connect(self.ssl.as_ptr()) }; - if ret > 0 { - match &self.pinned_pubkey { - None => {} - Some(key) => { - verify_server_cert(self.ssl.as_ptr(), key.as_str())?; - } + if ret <= 0 { + return Err(self.get_error(ret)); + } + + if let Some(pins_info) = &self.pinned_pubkey { + if pins_info.is_root() { + verify_server_root_cert(self.ssl.as_ptr(), pins_info.get_digest())?; + } else { + verify_server_cert(self.ssl.as_ptr(), pins_info.get_digest())?; } - Ok(()) - } else { - Err(self.get_error(ret)) } + Ok(()) } pub(crate) fn shutdown(&mut self) -> Result { @@ -252,6 +255,27 @@ pub(crate) enum ShutdownResult { Received, } +pub(crate) fn verify_server_root_cert(ssl: *const SSL, pinned_key: &str) -> Result<(), SslError> { + use crate::c_openssl::ffi::{ssl::SSL_get_peer_cert_chain, ssl::X509_chain_up_ref}; + use crate::c_openssl::{stack::Stack, x509::X509}; + + let cert_chain = unsafe { X509_chain_up_ref(SSL_get_peer_cert_chain(ssl)) }; + if cert_chain.is_null() { + return Err(SslError { + code: SslErrorCode::SSL, + internal: Some(InternalError::Ssl(ErrorStack::get())), + }); + } + + let cert_chain: Stack = Stack::from_ptr(cert_chain); + let root_certificate = cert_chain.into_iter().last().ok_or_else(|| SslError { + code: SslErrorCode::SSL, + internal: Some(InternalError::Ssl(ErrorStack::get())), + })?; + + verify_pinned_pubkey(pinned_key, root_certificate.as_ptr()) +} + // TODO The SSLError thrown here is meaningless and has no information. pub(crate) fn verify_server_cert(ssl: *const SSL, pinned_key: &str) -> Result<(), SslError> { #[cfg(feature = "c_openssl_3_0")] @@ -276,18 +300,26 @@ pub(crate) fn verify_server_cert(ssl: *const SSL, pinned_key: &str) -> Result<() }); } - let size_1 = unsafe { i2d_X509_PUBKEY(X509_get_X509_PUBKEY(certificate), ptr::null_mut()) }; - if size_1 < 1 { + verify_pinned_pubkey(pinned_key, certificate) +} + +fn verify_pinned_pubkey(pinned_key: &str, certificate: *mut C_X509) -> Result<(), SslError> { + let pubkey = unsafe { X509_get_X509_PUBKEY(certificate) }; + // Get the length of the serialized data + let buf_size = unsafe { i2d_X509_PUBKEY(pubkey, ptr::null_mut()) }; + + if buf_size < 1 { unsafe { X509_free(certificate) }; return Err(SslError { code: SslErrorCode::SSL, internal: Some(InternalError::Ssl(ErrorStack::get())), }); } - let key = vec![0u8; size_1 as usize]; - let size_2 = unsafe { i2d_X509_PUBKEY(X509_get_X509_PUBKEY(certificate), &mut key.as_ptr()) }; + let key = vec![0u8; buf_size as usize]; + // The actual serialization + let serialized_data_size = unsafe { i2d_X509_PUBKEY(pubkey, &mut key.as_ptr()) }; - if size_1 != size_2 || size_2 <= 0 { + if buf_size != serialized_data_size || serialized_data_size <= 0 { unsafe { X509_free(certificate) }; return Err(SslError { code: SslErrorCode::SSL, @@ -297,7 +329,7 @@ pub(crate) fn verify_server_cert(ssl: *const SSL, pinned_key: &str) -> Result<() // sha256 length. let mut digest = [0u8; 32]; - unsafe { sha256_digest(key.as_slice(), size_2, &mut digest)? } + unsafe { sha256_digest(key.as_slice(), serialized_data_size, &mut digest)? } compare_pinned_digest(&digest, pinned_key.as_bytes(), certificate) } diff --git a/ylong_http_client/src/util/c_openssl/stack.rs b/ylong_http_client/src/util/c_openssl/stack.rs index 43e961f9d9dc0573519a9aa8a7ff41b49ce36fd2..5640b4bf40e87c5dce3555bd47eb3353ca576221 100644 --- a/ylong_http_client/src/util/c_openssl/stack.rs +++ b/ylong_http_client/src/util/c_openssl/stack.rs @@ -18,9 +18,8 @@ use core::ops::{Deref, DerefMut, Range}; use libc::c_int; -use super::ffi::stack::{ - OPENSSL_sk_free, OPENSSL_sk_num, OPENSSL_sk_pop, OPENSSL_sk_value, OPENSSL_STACK, -}; +use super::ffi::stack::{unified_sk_free, unified_sk_num, unified_sk_pop, unified_sk_value, STACK}; + use crate::c_openssl::foreign::{Foreign, ForeignRef, ForeignRefWrapper}; pub(crate) trait Stackof: Foreign { @@ -69,7 +68,7 @@ impl Drop for Stack { fn drop(&mut self) { unsafe { while self.pop().is_some() {} - OPENSSL_sk_free(self.0 as *mut _) + unified_sk_free(self.0 as *mut _) } } } @@ -89,7 +88,7 @@ impl<'a, T: Stackof> Iterator for StackRefIter<'a, T> { unsafe { self.index .next() - .map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _)) + .map(|i| T::Ref::from_ptr(unified_sk_value(self.stack.as_stack(), i) as *mut _)) } } @@ -101,12 +100,12 @@ impl<'a, T: Stackof> Iterator for StackRefIter<'a, T> { impl StackRef { #[allow(clippy::len_without_is_empty)] pub(crate) fn len(&self) -> usize { - unsafe { OPENSSL_sk_num(self.as_stack()) as usize } + unsafe { unified_sk_num(self.as_stack()) as usize } } pub(crate) fn pop(&mut self) -> Option { unsafe { - let ptr = OPENSSL_sk_pop(self.as_stack()); + let ptr = unified_sk_pop(self.as_stack()); match ptr.is_null() { true => None, false => Some(T::from_ptr(ptr as *mut _)), @@ -120,7 +119,7 @@ impl ForeignRef for StackRef { } impl StackRef { - fn as_stack(&self) -> *mut OPENSSL_STACK { + fn as_stack(&self) -> STACK { self.as_ptr() as *mut _ } } @@ -150,7 +149,7 @@ impl Iterator for IntoStackIter { unsafe { self.index .next() - .map(|i| T::from_ptr(OPENSSL_sk_value(self.stack as *mut _, i) as *mut _)) + .map(|i| T::from_ptr(unified_sk_value(self.stack as *mut _, i) as *mut _)) } } } @@ -159,7 +158,7 @@ impl Drop for IntoStackIter { fn drop(&mut self) { unsafe { while self.next().is_some() {} - OPENSSL_sk_free(self.stack as *mut _); + unified_sk_free(self.stack as *mut _); } } } diff --git a/ylong_http_client/src/util/c_openssl/verify/mod.rs b/ylong_http_client/src/util/c_openssl/verify/mod.rs index 1271ffc7f0fdb97724410caa147ec3714a294a1c..b371d1be8bac560ede2909adf109f1ad4fa73329 100644 --- a/ylong_http_client/src/util/c_openssl/verify/mod.rs +++ b/ylong_http_client/src/util/c_openssl/verify/mod.rs @@ -17,5 +17,5 @@ mod pinning; -pub(crate) use pinning::sha256_digest; +pub(crate) use pinning::{sha256_digest, PinsVerifyInfo}; pub use pinning::{PubKeyPins, PubKeyPinsBuilder}; diff --git a/ylong_http_client/src/util/c_openssl/verify/pinning.rs b/ylong_http_client/src/util/c_openssl/verify/pinning.rs index 916704fa67c15716d39bd6511eaf496df21435e9..d14bc32ae4cfe7297fe94241c9a0c12cb452a055 100644 --- a/ylong_http_client/src/util/c_openssl/verify/pinning.rs +++ b/ylong_http_client/src/util/c_openssl/verify/pinning.rs @@ -44,7 +44,36 @@ use crate::HttpClientError; /// ``` #[derive(Clone)] pub struct PubKeyPins { - pub(crate) pub_keys: HashMap, + pub(crate) pub_keys: HashMap, +} + +#[derive(Debug, PartialEq, Clone)] +pub(crate) struct PinsVerifyInfo { + strategy: PinsVerifyStrategy, + digest: String, +} + +#[derive(Debug, PartialEq, Clone)] +enum PinsVerifyStrategy { + RootCertificate, + LeafCertificate, +} + +impl PinsVerifyInfo { + fn new(strategy: PinsVerifyStrategy, digest: &str) -> Self { + Self { + strategy, + digest: digest.to_string(), + } + } + + pub fn is_root(&self) -> bool { + matches!(self.strategy, PinsVerifyStrategy::RootCertificate) + } + + pub fn get_digest(&self) -> &str { + &self.digest + } } /// A builder which is used to construct `PubKeyPins`. @@ -57,7 +86,7 @@ pub struct PubKeyPins { /// let builder = PubKeyPinsBuilder::new(); /// ``` pub struct PubKeyPinsBuilder { - pub_keys: Result, HttpClientError>, + pub_keys: Result, HttpClientError>, } impl PubKeyPinsBuilder { @@ -76,7 +105,8 @@ impl PubKeyPinsBuilder { } } - /// Sets a tuple of (server, public key digest) for `PubKeyPins`. + /// Sets a tuple of (server, public key digest) for `PubKeyPins`, using + /// the server certificate pinning strategy. /// /// # Examples /// @@ -93,20 +123,39 @@ impl PubKeyPinsBuilder { /// ``` pub fn add(mut self, uri: &str, digest: &str) -> Self { self.pub_keys = self.pub_keys.and_then(move |mut keys| { - let parsed = Uri::try_from(uri).map_err(|e| HttpClientError::from_error(Build, e))?; - let auth = match (parsed.host(), parsed.port()) { - (None, _) => { - return err_from_msg!(Build, "uri has no host"); - } - (Some(host), Some(port)) => { - format!("{}:{}", host.as_str(), port.as_str()) - } - (Some(host), None) => { - format!("{}:443", host.as_str()) - } - }; - let pub_key = String::from(digest); - let _ = keys.insert(auth, pub_key); + let auth = parse_uri(uri)?; + let info = PinsVerifyInfo::new(PinsVerifyStrategy::LeafCertificate, digest); + let _ = keys.insert(auth, info); + Ok(keys) + }); + self + } + + /// Sets a tuple of (server, public key digest) for `PubKeyPins`, using + /// the root certificate pinning strategy. + ///
+ /// Ensure that the server returns the complete certificate chain, including the root certificate; + /// otherwise, the client's public key pinning validation will fail and return an error. + ///
+ /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::PubKeyPinsBuilder; + /// + /// let pins = PubKeyPinsBuilder::new() + /// .add_with_root_strategy( + /// "https://example.com", + /// "sha256//VHQAbNl67nmkZJNESeYKvTxb5bTmd1maWnMKG/tjcAY=", + /// ) + /// .build() + /// .unwrap(); + /// ``` + pub fn add_with_root_strategy(mut self, uri: &str, digest: &str) -> Self { + self.pub_keys = self.pub_keys.and_then(move |mut keys| { + let auth = parse_uri(uri)?; + let info = PinsVerifyInfo::new(PinsVerifyStrategy::RootCertificate, digest); + let _ = keys.insert(auth, info); Ok(keys) }); self @@ -147,7 +196,9 @@ impl PubKeyPins { pub fn builder() -> PubKeyPinsBuilder { PubKeyPinsBuilder::new() } - pub(crate) fn get_pin(&self, domain: &str) -> Option { + + /// Get the Public Key Pinning for a domain + pub(crate) fn get_pin(&self, domain: &str) -> Option { self.pub_keys.get(&String::from(domain)).cloned() } } @@ -168,6 +219,22 @@ impl Default for PubKeyPinsBuilder { } } +fn parse_uri(uri: &str) -> Result { + let parsed = Uri::try_from(uri).map_err(|e| HttpClientError::from_error(Build, e))?; + let auth = match (parsed.host(), parsed.port()) { + (None, _) => { + return err_from_msg!(Build, "uri has no host"); + } + (Some(host), Some(port)) => { + format!("{}:{}", host.as_str(), port.as_str()) + } + (Some(host), None) => { + format!("{}:443", host.as_str()) + } + }; + Ok(auth) +} + // TODO The SSLError thrown here is meaningless and has no information. pub(crate) unsafe fn sha256_digest( pub_key: &[u8], @@ -205,6 +272,7 @@ mod ut_verify_pinning { use libc::c_int; + use super::{PinsVerifyInfo, PinsVerifyStrategy}; use crate::util::c_openssl::verify::sha256_digest; use crate::{PubKeyPins, PubKeyPinsBuilder}; @@ -219,11 +287,21 @@ mod ut_verify_pinning { let mut map = HashMap::new(); let _value = map.insert( "ylong_http.com:443".to_string(), - "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=".to_string(), + PinsVerifyInfo::new( + PinsVerifyStrategy::LeafCertificate, + "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=", + ), ); let pins = PubKeyPins { pub_keys: map }; let pins_clone = pins.clone(); assert_eq!(pins.pub_keys, pins_clone.pub_keys); + + let pins_info = PinsVerifyInfo::new( + PinsVerifyStrategy::RootCertificate, + "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=", + ); + let pins_info_clone = pins_info.clone(); + assert_eq!(pins_info, pins_info_clone); } /// UT test cases for `PubKeyPinsBuilder::add`. @@ -254,7 +332,24 @@ mod ut_verify_pinning { .unwrap(); assert_eq!( pins.get_pin("ylong_http.com:443"), - Some("sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=".to_string()) + Some(PinsVerifyInfo::new( + PinsVerifyStrategy::LeafCertificate, + "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=" + )) + ); + let pins = PubKeyPinsBuilder::default() + .add_with_root_strategy( + "https://ylong_http.com", + "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=", + ) + .build() + .unwrap(); + assert_eq!( + pins.get_pin("ylong_http.com:443"), + Some(PinsVerifyInfo::new( + PinsVerifyStrategy::RootCertificate, + "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=" + )) ); } diff --git a/ylong_http_client/tests/common/mod.rs b/ylong_http_client/tests/common/mod.rs index 75c728396815675f8de74ead4549963331ea0c51..bc271b61182c95cdd005c715b449ee7d39a2c715 100644 --- a/ylong_http_client/tests/common/mod.rs +++ b/ylong_http_client/tests/common/mod.rs @@ -56,12 +56,53 @@ macro_rules! start_server { Handles: $handle_vec: expr, ServeFnName: $service_fn: ident, ) => {{ + let key_path = std::path::PathBuf::from( "tests/file/key.pem"); + let cert_path = std::path::PathBuf::from("tests/file/cert.pem"); + for _i in 0..$server_num { let (tx, rx) = std::sync::mpsc::channel(); + let key = key_path.clone(); + let cert = cert_path.clone(); let server_handle = $runtime.spawn(async move { let handle = start_http_server!( HTTPS; - $service_fn + $service_fn, + key, + cert + ); + tx.send(handle) + .expect("Failed to send the handle to the test thread."); + }); + $runtime + .block_on(server_handle) + .expect("Runtime start server coroutine failed"); + let handle = rx + .recv() + .expect("Handle send channel (Server-Half) be closed unexpectedly"); + $handle_vec.push(handle); + } + }}; + ( + HTTPS; + ServerNum: $server_num: expr, + Runtime: $runtime: expr, + Handles: $handle_vec: expr, + ServeFnName: $service_fn: ident, + ServeKeyPath: $server_key_path: ident, + ServeCrtPath: $server_crt_path: ident, + ) => {{ + let key_path = std::path::PathBuf::from($server_key_path); + let cert_path = std::path::PathBuf::from($server_crt_path); + for _i in 0..$server_num { + let (tx, rx) = std::sync::mpsc::channel(); + let key_path = key_path.clone(); + let cert_path = cert_path.clone(); + let server_handle = $runtime.spawn(async move { + let handle = start_http_server!( + HTTPS; + $service_fn, + key_path, + cert_path ); tx.send(handle) .expect("Failed to send the handle to the test thread."); @@ -159,7 +200,9 @@ macro_rules! start_http_server { }}; ( HTTPS; - $service_fn: ident + $service_fn: ident, + $server_key_path: expr, + $server_cert_path: expr ) => {{ let mut port = 10000; let listener = loop { @@ -184,10 +227,10 @@ macro_rules! start_http_server { .set_session_id_context(b"test") .expect("Set session id error"); acceptor - .set_private_key_file("tests/file/key.pem", openssl::ssl::SslFiletype::PEM) + .set_private_key_file($server_key_path, openssl::ssl::SslFiletype::PEM) .expect("Set private key error"); acceptor - .set_certificate_chain_file("tests/file/cert.pem") + .set_certificate_chain_file($server_cert_path) .expect("Set cert error"); acceptor.set_alpn_protos(b"\x08http/1.1").unwrap(); acceptor.set_alpn_select_callback(|_, client| { diff --git a/ylong_http_client/tests/file/cert_chain/chain.crt.pem b/ylong_http_client/tests/file/cert_chain/chain.crt.pem new file mode 100644 index 0000000000000000000000000000000000000000..7bfad7465591ed8a0fff83c45a3074eddea2f90e --- /dev/null +++ b/ylong_http_client/tests/file/cert_chain/chain.crt.pem @@ -0,0 +1,68 @@ +-----BEGIN CERTIFICATE----- +MIID0DCCArigAwIBAgIULrhtex5WUyRfmoD7o16Y5n9POl0wDQYJKoZIhvcNAQEL +BQAwdjELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFVTU1QxDTALBgNVBAcMBFVTX0wx +FzAVBgNVBAoMDkludGVybWVkaWF0ZUNBMRcwFQYDVQQLDA5JbnRlcm1lZGlhdGVD +QTEXMBUGA1UEAwwOSW50ZXJtZWRpYXRlQ0EwHhcNMjUwMTI0MDQzODI5WhcNMjgw +MTI1MDQzODI5WjBhMQswCQYDVQQGEwJVUzENMAsGA1UECAwEVVNfUzENMAsGA1UE +BwwEVVNfTDEPMA0GA1UECgwGU2VydmVyMQ8wDQYDVQQLDAZTZXJ2ZXIxEjAQBgNV +BAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMt0 +JXSqGtplxT9G0+JpXi7u61wEeOzp/bHhbqjNUiG7sDQ1JawBHWpzDcdk5ZFC+Hjg +gZDZN4voolARzHgQ46LZ0BfPHyitBSuzvmj/OuKpHsjSp4HuRmyQvVNFDkq5xnrR +QTMd7uElkGOUn/e7cJMvvQ72OPpJQdOzIFcMzfgQJxFzotc/NqxQ8j7BDp7NyWjJ +GlHxYgrTiCUIPKUK/PZThJWo78hHZA8JTozRevwik5u7/EzXU1BLm5jxlFciSZGC +50dj6ZGt8dAOb5F+aR1XDfBNKsSKaSpCGARExlapTgYdQd4HNgSmBfXRiklCMQ37 +tnut5eDwvjk2hGJtkkcCAwEAAaNrMGkwHwYDVR0jBBgwFoAUR2LSQE92UCCBzb7f +0gTzILf7zF0wCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAwDwYDVR0RBAgwBocEfwAA +ATAdBgNVHQ4EFgQU9n+a8Qtir1wzZbmspfxbz5UwvecwDQYJKoZIhvcNAQELBQAD +ggEBADkNZGYMYwOKXFtH3HEDPeU28iGLqqzIqoYVaBAoh7XeGNTwdXDiyqNMR3Jv +raxCgPwF6FhYPGgzuvqqltTDY6JBiL5bsaU8Ur23djJ1Rs6mbt2SmhfVlNPGHX3Q +nvb4XCP4i1Dt7PIO3bPaHU6X3il965XgTnobcVuM9PldDmKeIm8ptuLPISw82dWX +lXLS++Y5BnQScAbu1vBPcnQ+mZCL72H3BY9mfYXyH24t+mizzummlA873fO44+34 +zUUf84rsjwUBfP9LA0mBvrUzGDJeIDwTWsoKMnDwbcjTP/d94Afz1ZtU+M4hhPL1 +AXVJIMlx889Ziv/jGAUzaJla4RY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIUO2/UzEeJhi8CROkpTt8Nb4tjWKMwDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFVTX0MxDTALBgNVBAcMBFVTX0wx +DzANBgNVBAoMBlJvb3RDQTEPMA0GA1UECwwGUm9vdENBMQ8wDQYDVQQDDAZSb290 +Q0EwHhcNMjUwMTI0MDQyOTM2WhcNMzUwMTIyMDQyOTM2WjB2MQswCQYDVQQGEwJV +UzENMAsGA1UECAwEVVNTVDENMAsGA1UEBwwEVVNfTDEXMBUGA1UECgwOSW50ZXJt +ZWRpYXRlQ0ExFzAVBgNVBAsMDkludGVybWVkaWF0ZUNBMRcwFQYDVQQDDA5JbnRl +cm1lZGlhdGVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKUzeddE +hYWxAulGxLMkSurw+lJCQegomFUT+jqmTX5eCb8ARt5WoLCmw8xxiCaZ2L22lVmq +mJ6kUyZpHrdc1I/Tqg68VWSYUB182CfnK8mkY7bCA3lR7Ch8T2tQZ+Mz8IaUINMt +//TXn3zaSU6WSslXizFtyO7P7VvmcpQfqhWLMngEXCUhDzzQg0iKQxgHMYXzVkSB +YoLT7cq4G2F1QTnFWR1bkcpYWZrP2PcrFx6Pr4MkZ6MhaNH+jpL4f6fKuAdKBtyc +GRkRuTW9NeQG3yH5yws7eNwqzeVdK9HXEid8gtGo7LrKoccGybnqokk6ddQq81Mj +i092ZcyO8WPVSykCAwEAAaNhMF8wDAYDVR0TBAUwAwEB/zAPBgNVHREECDAGhwR/ +AAABMB0GA1UdDgQWBBRHYtJAT3ZQIIHNvt/SBPMgt/vMXTAfBgNVHSMEGDAWgBSO +S0jW9hksmp//ngeCQWsFCIwrXjANBgkqhkiG9w0BAQsFAAOCAQEAC6XuvQAg5Q2z +/HVTgflKS07t6gjBkfDOpmKcimmyx0uUBA8f9Su7vO2ZDB4QWvFWFroeLBls8l68 +CSnOBNO+arhamxTFdNsNoazQDJRz3aSswUvREB3oEQgsb2xH4OJcWtHeRwHOdRDR +X3bTcRrb8MLo/xnbm9Hem2psKtBQ1txU8Mef8/KQFNKC0IphSdCK1VDNsipeErXG +ttqKSdG4PwxBUU2/JS/+5q3k9KHjXh3/rfS95yhaXXxc+JWq57zeNKE6RYe1rdhW +1oxSdC5CLqIoxx+3bMG43qFqTZ7IObNWvysRQCuT25GWr6EL5jKd2wQeGGrlIoBY +GTu6Nhsfcw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDnTCCAoWgAwIBAgIUFWfxvd9P5DjKGAgbcHlZbR8+vy8wDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFVTX0MxDTALBgNVBAcMBFVTX0wx +DzANBgNVBAoMBlJvb3RDQTEPMA0GA1UECwwGUm9vdENBMQ8wDQYDVQQDDAZSb290 +Q0EwHhcNMjUwMTI0MDQyNzM4WhcNMzUwMTIyMDQyNzM4WjBeMQswCQYDVQQGEwJV +UzENMAsGA1UECAwEVVNfQzENMAsGA1UEBwwEVVNfTDEPMA0GA1UECgwGUm9vdENB +MQ8wDQYDVQQLDAZSb290Q0ExDzANBgNVBAMMBlJvb3RDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBALHqioDaZ3Iz+3kNTL+aw0wBjK0nQctqByiae7sx +J5mwe0qmFip5R6pKxDK760y1eSGYnxCUijwas1DrHg5RyNWrQpnv+/MBQC3wMQEb +jyAaOj9kULKQP2VdkJYhg5d47h1YglrkXOeM0J6OOtVF9sTcPJt4+I9lMcbiSfrv +BZNXp8onCCj55FhvBQLp7IsJMbgBb7LrZn5/HaWMkJt5e7tmCVJo75mPOEm4lpKy +9FyvtXgLqNgiXIg6/I6eHz6ipJqnElM5Hw64NLM3sUXTEDvhMlR2f32LpN+0LWKC +KqD+O+oJl4iwkTyzGc7yOizp1Ktin7hV+z/hwOOSjR3a660CAwEAAaNTMFEwHQYD +VR0OBBYEFI5LSNb2GSyan/+eB4JBawUIjCteMB8GA1UdIwQYMBaAFI5LSNb2GSya +n/+eB4JBawUIjCteMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB +AH9fXSaFJs7YcAsJm0bzkvKs50Kj4BjbI7v5jdWuP8ElVNIalPMHj7mL5nTJLMGP +Dmz/Z+BtQjHWk3/5u6AJZq97Z5q/A0+NtvfZ+kEJobIfVrmlmpW40KfPQIXt98YI +WeI5WsGE/Edsh46N7Wqw6rWGXQxlS4/Lrsy6nydeogqJSfYVyPkkKecXZbQIlY+E +VpxsRrEaJqUK/yeg43kg/JuBvGIgfM9Yy7nYvRtBxqtD700p4wlPcXxWGitQ5kjb +RV8uKCQmRwg5HPz5IWQ++1vE47qRSAPhPQQ4yzxem36kvNfKkWc9WVGeHzRJzc7u ++es+8PPXjnAoviJid10ElbI= +-----END CERTIFICATE----- diff --git a/ylong_http_client/tests/file/cert_chain/rootCA.crt.pem b/ylong_http_client/tests/file/cert_chain/rootCA.crt.pem new file mode 100644 index 0000000000000000000000000000000000000000..247543b71e436819d6fa4693a24b78d0dbb749e1 --- /dev/null +++ b/ylong_http_client/tests/file/cert_chain/rootCA.crt.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDnTCCAoWgAwIBAgIUFWfxvd9P5DjKGAgbcHlZbR8+vy8wDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFVTX0MxDTALBgNVBAcMBFVTX0wx +DzANBgNVBAoMBlJvb3RDQTEPMA0GA1UECwwGUm9vdENBMQ8wDQYDVQQDDAZSb290 +Q0EwHhcNMjUwMTI0MDQyNzM4WhcNMzUwMTIyMDQyNzM4WjBeMQswCQYDVQQGEwJV +UzENMAsGA1UECAwEVVNfQzENMAsGA1UEBwwEVVNfTDEPMA0GA1UECgwGUm9vdENB +MQ8wDQYDVQQLDAZSb290Q0ExDzANBgNVBAMMBlJvb3RDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBALHqioDaZ3Iz+3kNTL+aw0wBjK0nQctqByiae7sx +J5mwe0qmFip5R6pKxDK760y1eSGYnxCUijwas1DrHg5RyNWrQpnv+/MBQC3wMQEb +jyAaOj9kULKQP2VdkJYhg5d47h1YglrkXOeM0J6OOtVF9sTcPJt4+I9lMcbiSfrv +BZNXp8onCCj55FhvBQLp7IsJMbgBb7LrZn5/HaWMkJt5e7tmCVJo75mPOEm4lpKy +9FyvtXgLqNgiXIg6/I6eHz6ipJqnElM5Hw64NLM3sUXTEDvhMlR2f32LpN+0LWKC +KqD+O+oJl4iwkTyzGc7yOizp1Ktin7hV+z/hwOOSjR3a660CAwEAAaNTMFEwHQYD +VR0OBBYEFI5LSNb2GSyan/+eB4JBawUIjCteMB8GA1UdIwQYMBaAFI5LSNb2GSya +n/+eB4JBawUIjCteMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB +AH9fXSaFJs7YcAsJm0bzkvKs50Kj4BjbI7v5jdWuP8ElVNIalPMHj7mL5nTJLMGP +Dmz/Z+BtQjHWk3/5u6AJZq97Z5q/A0+NtvfZ+kEJobIfVrmlmpW40KfPQIXt98YI +WeI5WsGE/Edsh46N7Wqw6rWGXQxlS4/Lrsy6nydeogqJSfYVyPkkKecXZbQIlY+E +VpxsRrEaJqUK/yeg43kg/JuBvGIgfM9Yy7nYvRtBxqtD700p4wlPcXxWGitQ5kjb +RV8uKCQmRwg5HPz5IWQ++1vE47qRSAPhPQQ4yzxem36kvNfKkWc9WVGeHzRJzc7u ++es+8PPXjnAoviJid10ElbI= +-----END CERTIFICATE----- diff --git a/ylong_http_client/tests/file/cert_chain/server.key.pem b/ylong_http_client/tests/file/cert_chain/server.key.pem new file mode 100644 index 0000000000000000000000000000000000000000..1300dc2fd372a1469abce378418c2943636b802f --- /dev/null +++ b/ylong_http_client/tests/file/cert_chain/server.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDLdCV0qhraZcU/ +RtPiaV4u7utcBHjs6f2x4W6ozVIhu7A0NSWsAR1qcw3HZOWRQvh44IGQ2TeL6KJQ +Ecx4EOOi2dAXzx8orQUrs75o/zriqR7I0qeB7kZskL1TRQ5KucZ60UEzHe7hJZBj +lJ/3u3CTL70O9jj6SUHTsyBXDM34ECcRc6LXPzasUPI+wQ6ezcloyRpR8WIK04gl +CDylCvz2U4SVqO/IR2QPCU6M0Xr8IpObu/xM11NQS5uY8ZRXIkmRgudHY+mRrfHQ +Dm+RfmkdVw3wTSrEimkqQhgERMZWqU4GHUHeBzYEpgX10YpJQjEN+7Z7reXg8L45 +NoRibZJHAgMBAAECggEAH68AvkBXWjeLFiWTjajXD/wJDxVrN4nhBjiTIRqIddRi +xl6YdUbfK9qrBKhDz/Fb/IcJ5mLNca5SyKFc9D29FXlSHMMWmSEIsxuUxYkpxG6N +6rxTdbqDoRiRQ0x3w09XB31a5/j7YHiXGcrldpDsIR/IE3JowSFzbOJyYNwPn1et +gtu0sRXshLp8QfYPa8T+y6j8w+RV1m4556QYxY1hxADLSIMlcA7Fmu5IOgZc/6vU +x/Aj1fI7lO82UkCIC+Z+7sVFLhZibj4ORG6XIZNSCZxtRb6M61kcFDXm1k6UT9ny +R13bcxn0NG2Jp7V7em7gJHctrR7yrIQxrQCuspE48QKBgQDqL9A7KAIs+/OKFyBB +yVchpQYfg7DSDlJ4bJnVILShkLKy7nYZs1oL5NMbPaU34tX5JFLMjD1btyr01mWh +Q3ppAj+VpUajq+P1HsdCPXzb/dTvCP9ITKMkYGq99ir+0kf2LjZ220r/+I2mP7NH +X03B0nEkPoYzUEjs2JOu5C3HtQKBgQDeZ4CZx/SBxXW2gPeA4swf/11ukWw20jZO +B9hLruzGBDDQnfUIRK4xcZoAu9GitycRRESe61d1SUrpCx3Gt/zjfNjELf1app36 +bDox458iWGKxvJ3vEA7QeB7QF38ZrdyMiRYhORSfPKm0T6ZIiN+mruQqSUebzYk2 +IdGL6ul3iwKBgQCh2QuTZrIiPrpwrEzpymmCYheaPhw9ABL1ETE7v4+2vVcTHITY +fEB2Sd2wTOlbd1SkC/uBTEa3lR6F+Yphak42NoyVMpVgVlKEPJI/cFlTfNjlnpU3 +dWemo1ACGxhZ5iA/vm57tFDgGPpkdE/FutL1aigxgVikLA9KSN/AFgihpQKBgGBw +PSFxxSJofyyOK8Slk6HkV51UTbpP2OBpIm9fAKi0tH4hoFjffzFNc1wSFUsbZENm +eOL4Zcoj5+m5ukWrDmuOfWhEEPI4AZTPTUTI0P5RmSo9AbbiHapkC+hr5984tsPx +xbjOSZTq9yOKzi3xvBlJCQMVF4oFzBO/AmBLksUVAoGBAJWU4SWuYtCoU+dACaIc +wbnBuKplnIlHX+8jVL5QZIhmnYZfpFMRkbOKY8w263v6G5Q5WZtjYX3NOiIt1fZt +HxAjZjlGaiU5ibz2C+VPVyDsd/8qOuQOLeEeGYL7t1dQ0eaxkVc4PP8tjQJ4v4nV +Sst/5ax4pWwHX01e/Y/iKXzn +-----END PRIVATE KEY----- diff --git a/ylong_http_client/tests/sdv_async_https_pinning.rs b/ylong_http_client/tests/sdv_async_https_pinning.rs index 05e85defbf0c4908e550752d8c2924a7668aa660..963d258dd50ed6c4c84bc67c1e0880aa82e0b3af 100644 --- a/ylong_http_client/tests/sdv_async_https_pinning.rs +++ b/ylong_http_client/tests/sdv_async_https_pinning.rs @@ -208,6 +208,246 @@ fn sdv_client_public_key_pinning() { } } +/// SDV test cases for `async::Client`. +/// +/// # Brief +/// 1. Starts a hyper https server with the tokio coroutine. +/// 2. Creates an async::Client that with Root public key pinning. +/// 3. The client sends a request message. +/// 4. Verifies the received request on the server. +/// 5. The server sends a response message. +/// 6. Verifies the received response on the client. +/// 7. Shuts down the server. +#[test] +fn sdv_client_public_key_root_pinning() { + define_service_handle!(HTTPS;); + set_server_fn!( + ASYNC; + ylong_server_fn, + Request: { + Method: "GET", + Header: "Content-Length", "5", + Body: "hello", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "hi!", + }, + ); + let runtime = init_test_work_runtime(1); + let mut handles_vec = vec![]; + let dir = env!("CARGO_MANIFEST_DIR"); + let root_ca_path = PathBuf::from(dir).join("tests/file/cert_chain/rootCA.crt.pem"); + let server_key_path = "tests/file/cert_chain/server.key.pem"; + let server_crt_chain_path = "tests/file/cert_chain/chain.crt.pem"; + + // Root certificate pinning. + { + start_server!( + HTTPS; + ServerNum: 1, + Runtime: runtime, + Handles: handles_vec, + ServeFnName: ylong_server_fn, + ServeKeyPath: server_key_path, + ServeCrtPath: server_crt_chain_path, + ); + let handle = handles_vec.pop().expect("No more handles !"); + + let pins = PubKeyPins::builder() + .add_with_root_strategy( + format!("https://127.0.0.1:{}", handle.port).as_str(), + "sha256//OTEKj2hCyGOWxN8Bdt2LPRMzJ4zs0e59cjgIPQgQe30=", + ) + .build() + .unwrap(); + + let client = ylong_http_client::async_impl::Client::builder() + .tls_ca_file(root_ca_path.to_str().unwrap()) + .add_public_key_pins(pins) + .danger_accept_invalid_hostnames(true) + .build() + .unwrap(); + + let shutdown_handle = runtime.spawn(async move { + async_client_assertions!( + ServerHandle: handle, + ClientRef: client, + Request: { + Method: "GET", + Host: "127.0.0.1", + Header: "Content-Length", "5", + Body: "hello", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "hi!", + }, + ); + }); + runtime + .block_on(shutdown_handle) + .expect("Runtime block on server shutdown failed"); + } + + // Server certificate pinning. + { + start_server!( + HTTPS; + ServerNum: 1, + Runtime: runtime, + Handles: handles_vec, + ServeFnName: ylong_server_fn, + ServeKeyPath: server_key_path, + ServeCrtPath: server_crt_chain_path, + ); + let handle = handles_vec.pop().expect("No more handles !"); + + // Two wrong public keys and a correct public key in the middle. + let pins = PubKeyPins::builder() + .add( + format!("https://127.0.0.1:{}", handle.port).as_str(), + "sha256//tldbIOQrcXdIACltObylTwTPzdxTm0E2VYDf3B1IQxU=", + ) + .build() + .unwrap(); + + let client = ylong_http_client::async_impl::Client::builder() + .tls_ca_file(root_ca_path.to_str().unwrap()) + .add_public_key_pins(pins) + .danger_accept_invalid_hostnames(true) + .build() + .unwrap(); + + let shutdown_handle = runtime.spawn(async move { + async_client_assertions!( + ServerHandle: handle, + ClientRef: client, + Request: { + Method: "GET", + Host: "127.0.0.1", + Header: "Content-Length", "5", + Body: "hello", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "hi!", + }, + ); + }); + runtime + .block_on(shutdown_handle) + .expect("Runtime block on server shutdown failed"); + } + + // Public keys from unrelated domains will not verify public key pinning. + { + start_server!( + HTTPS; + ServerNum: 1, + Runtime: runtime, + Handles: handles_vec, + ServeFnName: ylong_server_fn, + ServeKeyPath: server_key_path, + ServeCrtPath: server_crt_chain_path, + ); + let handle = handles_vec.pop().expect("No more handles !"); + + let pins = PubKeyPins::builder() + .add_with_root_strategy( + "https://ylong_http.test:6789", + "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=", + ) + .build() + .unwrap(); + + let client = ylong_http_client::async_impl::Client::builder() + .tls_ca_file(root_ca_path.to_str().unwrap()) + .add_public_key_pins(pins) + .danger_accept_invalid_hostnames(true) + .build() + .unwrap(); + + let shutdown_handle = runtime.spawn(async move { + async_client_assertions!( + ServerHandle: handle, + ClientRef: client, + Request: { + Method: "GET", + Host: "127.0.0.1", + Header: "Content-Length", "5", + Body: "hello", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "hi!", + }, + ); + }); + runtime + .block_on(shutdown_handle) + .expect("Runtime block on server shutdown failed"); + } + + // Root certificate pinning strategy, but using the server certificate public key hash + { + start_server!( + HTTPS; + ServerNum: 1, + Runtime: runtime, + Handles: handles_vec, + ServeFnName: ylong_server_fn, + ServeKeyPath: server_key_path, + ServeCrtPath: server_crt_chain_path, + ); + let handle = handles_vec.pop().expect("No more handles !"); + + let pins = PubKeyPins::builder() + .add_with_root_strategy( + format!("https://127.0.0.1:{}", handle.port).as_str(), + "sha256//tldbIOQrcXdIACltObylTwTPzdxTm0E2VYDf3B1IQxU=", + ) + .build() + .unwrap(); + + let client = ylong_http_client::async_impl::Client::builder() + .tls_ca_file(root_ca_path.to_str().unwrap()) + .add_public_key_pins(pins) + .danger_accept_invalid_hostnames(true) + .build() + .unwrap(); + + let shutdown_handle = runtime.spawn(async move { + let request = ylong_http_client::async_impl::Request::builder() + .method("GET") + .url(format!("{}:{}", "127.0.0.1", handle.port).as_str()) + .header("Content-Length", "5") + .body(ylong_http_client::async_impl::Body::slice("hello")) + .expect("Request build failed"); + + let response = client.request(request).await.err(); + + assert_eq!( + format!("{:?}", response.expect("response is not an error")), + "HttpClientError { ErrorKind: Connect, Cause: Custom { kind: Other, error: SslError { \ + code: SslErrorCode(1), internal: Some(User(VerifyError { ErrorKind: PubKeyPinning, \ + Cause: Pinned public key verification failed. })) } } }" + ); + }); + runtime + .block_on(shutdown_handle) + .expect("Runtime block on server shutdown failed"); + } +} + /// SDV test cases for `async::Client`. /// /// # Brief