From 8280dcff11a78b50ecdf53c92a055623b033d83c Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Wed, 10 Apr 2024 09:57:52 +0100 Subject: [PATCH 01/19] Correct ownership semantics for `Bio::new_pair` Requiring use of `Bio::new_pair(Some(a), Some(a))` to donate two refs of `a` is not what is required by `SSL_set_bio`. Instead make an object backed by `BIO_s_null()`, and then call `update()` on it. --- rustls-libssl/src/bio.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/rustls-libssl/src/bio.rs b/rustls-libssl/src/bio.rs index b7236c6..074a36f 100644 --- a/rustls-libssl/src/bio.rs +++ b/rustls-libssl/src/bio.rs @@ -33,13 +33,16 @@ impl Bio { /// Absent pointers are silently replaced with a `BIO_s_null()`. /// `Some(ptr::null_mut())` is illegal. /// - /// The caller donates their references, even if they point to the same - /// object. In other words: if rbio and wbio are both `Some` and contain - /// the same pointer, the underlying reference count must be at least 2! + /// The caller donates their references, using the rules for `update()`. pub fn new_pair(rbio: Option<*mut BIO>, wbio: Option<*mut BIO>) -> Self { - let read = rbio.unwrap_or_else(|| unsafe { BIO_new(BIO_s_null()) }); - let write = wbio.unwrap_or_else(|| unsafe { BIO_new(BIO_s_null()) }); - Self { read, write } + let null_2 = unsafe { BIO_new(BIO_s_null()) }; + unsafe { BIO_up_ref(null_2) }; + let mut ret = Self { + read: null_2, + write: null_2, + }; + ret.update(rbio, wbio); + ret } /// Update this object with a pair of raw BIO pointers. From 77f592cfa4d7e958ef1ecaacbf6493e7149e7e4e Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Wed, 10 Apr 2024 15:04:52 +0100 Subject: [PATCH 02/19] Correct non-blocking writes We mishandled non-blocking writes in several respects: - `SSL_get_error()` would say to retry the read if the write BIO was the same as the read BIO. - raise the correct `io::Error` in `impl io::Write for Bio`. - the current IO `Want` was was ignored: give `Want::write` primacy because rustls tends to always want to read in idle conditions (for alert receipt). --- rustls-libssl/src/bio.rs | 25 +++++++++++++++++++------ rustls-libssl/src/lib.rs | 8 +++++--- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/rustls-libssl/src/bio.rs b/rustls-libssl/src/bio.rs index 074a36f..92bbef0 100644 --- a/rustls-libssl/src/bio.rs +++ b/rustls-libssl/src/bio.rs @@ -160,11 +160,11 @@ impl Bio { } pub fn read_would_block(&self) -> bool { - bio_should_retry(self.read) + bio_should_retry_read(self.read) } pub fn write_would_block(&self) -> bool { - bio_should_retry(self.write) + bio_should_retry_write(self.write) } } @@ -185,7 +185,7 @@ impl io::Read for Bio { _ => { if bio_in_eof(self.read) { Ok(0) - } else if bio_should_retry(self.read) { + } else if bio_should_retry_read(self.read) { Err(io::ErrorKind::WouldBlock.into()) } else { Err(io::Error::other("BIO_read_ex failed")) @@ -209,7 +209,13 @@ impl io::Write for Bio { match rc { 1 => Ok(written_bytes), - _ => Err(io::Error::other("BIO_write_ex failed")), + _ => { + if bio_should_retry_write(self.write) { + Err(io::ErrorKind::WouldBlock.into()) + } else { + Err(io::Error::other("BIO_write_ex failed")) + } + } } } @@ -310,9 +316,16 @@ pub type BIO = OpaqueBio; #[allow(clippy::upper_case_acronyms)] pub type BIO_METHOD = bio_method_st; -fn bio_should_retry(b: *const BIO) -> bool { +fn bio_should_retry_read(b: *const BIO) -> bool { + const BIO_FLAGS_READ: c_int = 0x01; + const BIO_SHOULD_RETRY: c_int = 0x08; + unsafe { BIO_test_flags(b, BIO_SHOULD_RETRY | BIO_FLAGS_READ) != 0 } +} + +fn bio_should_retry_write(b: *const BIO) -> bool { + const BIO_FLAGS_WRITE: c_int = 0x02; const BIO_SHOULD_RETRY: c_int = 0x08; - unsafe { BIO_test_flags(b, BIO_SHOULD_RETRY) != 0 } + unsafe { BIO_test_flags(b, BIO_SHOULD_RETRY | BIO_FLAGS_WRITE) != 0 } } fn bio_in_eof(b: *const BIO) -> bool { diff --git a/rustls-libssl/src/lib.rs b/rustls-libssl/src/lib.rs index a25ac1a..6004627 100644 --- a/rustls-libssl/src/lib.rs +++ b/rustls-libssl/src/lib.rs @@ -646,11 +646,13 @@ impl Ssl { return SSL_ERROR_SSL; } + let want = self.want(); + if let Some(bio) = self.bio.as_ref() { - if bio.read_would_block() { - return SSL_ERROR_WANT_READ; - } else if bio.write_would_block() { + if want.write && bio.write_would_block() { return SSL_ERROR_WANT_WRITE; + } else if want.read && bio.read_would_block() { + return SSL_ERROR_WANT_READ; } } From 13bb74b4d0c54354ed2e6b11e8fcd9d26faff0fe Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Thu, 7 Mar 2024 17:42:03 +0000 Subject: [PATCH 03/19] Implement Clone for OwnedX509Stack --- rustls-libssl/src/x509.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/rustls-libssl/src/x509.rs b/rustls-libssl/src/x509.rs index e167124..bc0ebee 100644 --- a/rustls-libssl/src/x509.rs +++ b/rustls-libssl/src/x509.rs @@ -4,8 +4,8 @@ use std::path::PathBuf; use std::{fs, io}; use openssl_sys::{ - d2i_X509, stack_st_X509, OPENSSL_sk_new_null, OPENSSL_sk_push, X509_STORE_free, X509_STORE_new, - X509_free, OPENSSL_STACK, X509, X509_STORE, + d2i_X509, stack_st_X509, OPENSSL_sk_new_null, OPENSSL_sk_num, OPENSSL_sk_push, + OPENSSL_sk_value, X509_STORE_free, X509_STORE_new, X509_free, OPENSSL_STACK, X509, X509_STORE, }; use rustls::pki_types::CertificateDer; @@ -42,6 +42,31 @@ impl OwnedX509Stack { pub fn pointer(&self) -> *mut stack_st_X509 { self.raw } + + /// Plain, borrowed pointer to the item at `index`. + fn borrowed_item(&self, index: usize) -> *mut X509 { + unsafe { OPENSSL_sk_value(self.raw as *const OPENSSL_STACK, index as c_int) as *mut X509 } + } + + fn len(&self) -> usize { + match unsafe { OPENSSL_sk_num(self.raw as *const OPENSSL_STACK) } { + -1 => 0, + x => x as usize, + } + } +} + +impl Clone for OwnedX509Stack { + fn clone(&self) -> Self { + // up-ref each item + for i in 0..self.len() { + unsafe { X509_up_ref(self.borrowed_item(i)) }; + } + // then shallow copy the stack + Self { + raw: unsafe { OPENSSL_sk_dup(self.raw as *const OPENSSL_STACK) as *mut stack_st_X509 }, + } + } } impl Drop for OwnedX509Stack { @@ -156,5 +181,6 @@ extern "C" { st: *mut OPENSSL_STACK, func: Option, ); + fn OPENSSL_sk_dup(st: *const OPENSSL_STACK) -> *mut OPENSSL_STACK; fn X509_up_ref(x: *mut X509) -> c_int; } From 6520d4e8438fd56b5cbe8b2982500aa22ac089e5 Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Tue, 5 Mar 2024 15:29:27 +0000 Subject: [PATCH 04/19] Implement certificate/key loading for client auth Because `SSL_CTX_use_PrivateKey` implies we have to use an `EVP_PKEY` as-is, `evp_pkey.rs` implements a wrapper over OpenSSL `EVP_PKEY`s, and `sign.rs` uses that wrapper to implement `rustls::sign::SigningKey` and `Signer` traits. --- rustls-libssl/MATRIX.md | 18 +- rustls-libssl/build.rs | 2 + rustls-libssl/src/entry.rs | 235 +++++++++++++++++--- rustls-libssl/src/evp_pkey.rs | 300 ++++++++++++++++++++++++++ rustls-libssl/src/lib.rs | 61 +++++- rustls-libssl/src/sign.rs | 213 ++++++++++++++++++ rustls-libssl/src/x509.rs | 83 ++++++- rustls-libssl/test-ca/rsa/client.cert | 81 +++++++ rustls-libssl/test-ca/rsa/client.key | 28 +++ rustls-libssl/tests/client.c | 30 ++- rustls-libssl/tests/runner.rs | 99 ++++++++- 11 files changed, 1097 insertions(+), 53 deletions(-) create mode 100644 rustls-libssl/src/evp_pkey.rs create mode 100644 rustls-libssl/src/sign.rs create mode 100644 rustls-libssl/test-ca/rsa/client.cert create mode 100644 rustls-libssl/test-ca/rsa/client.key diff --git a/rustls-libssl/MATRIX.md b/rustls-libssl/MATRIX.md index 00bcaa8..393d0ac 100644 --- a/rustls-libssl/MATRIX.md +++ b/rustls-libssl/MATRIX.md @@ -68,7 +68,7 @@ | `SSL_CTX_add_server_custom_ext` | | | | | `SSL_CTX_add_session` | | | | | `SSL_CTX_callback_ctrl` | | :white_check_mark: | | -| `SSL_CTX_check_private_key` | :white_check_mark: | | :exclamation: [^stub] | +| `SSL_CTX_check_private_key` | :white_check_mark: | | :white_check_mark: | | `SSL_CTX_clear_options` | | :white_check_mark: | :white_check_mark: | | `SSL_CTX_config` | | | | | `SSL_CTX_ct_is_enabled` [^ct] | | | | @@ -81,10 +81,10 @@ | `SSL_CTX_flush_sessions` | | | | | `SSL_CTX_free` | :white_check_mark: | :white_check_mark: | :white_check_mark: | | `SSL_CTX_get0_CA_list` | | | | -| `SSL_CTX_get0_certificate` | | | | +| `SSL_CTX_get0_certificate` | | | :white_check_mark: | | `SSL_CTX_get0_ctlog_store` [^ct] | | | | | `SSL_CTX_get0_param` | | | | -| `SSL_CTX_get0_privatekey` | | | | +| `SSL_CTX_get0_privatekey` | | | :white_check_mark: | | `SSL_CTX_get0_security_ex_data` | | | | | `SSL_CTX_get_cert_store` | :white_check_mark: | :white_check_mark: | :white_check_mark: | | `SSL_CTX_get_ciphers` | | | | @@ -200,16 +200,16 @@ | `SSL_CTX_set_verify` | :white_check_mark: | :white_check_mark: | :white_check_mark: | | `SSL_CTX_set_verify_depth` | | :white_check_mark: | | | `SSL_CTX_up_ref` | | | :white_check_mark: | -| `SSL_CTX_use_PrivateKey` | :white_check_mark: | :white_check_mark: | :exclamation: [^stub] | +| `SSL_CTX_use_PrivateKey` | :white_check_mark: | :white_check_mark: | :white_check_mark: | | `SSL_CTX_use_PrivateKey_ASN1` | | | | -| `SSL_CTX_use_PrivateKey_file` | :white_check_mark: | | :exclamation: [^stub] | +| `SSL_CTX_use_PrivateKey_file` | :white_check_mark: | | :white_check_mark: | | `SSL_CTX_use_RSAPrivateKey` [^deprecatedin_3_0] | | | | | `SSL_CTX_use_RSAPrivateKey_ASN1` [^deprecatedin_3_0] | | | | | `SSL_CTX_use_RSAPrivateKey_file` [^deprecatedin_3_0] | | | | | `SSL_CTX_use_cert_and_key` | | | | -| `SSL_CTX_use_certificate` | :white_check_mark: | :white_check_mark: | :exclamation: [^stub] | +| `SSL_CTX_use_certificate` | :white_check_mark: | :white_check_mark: | :white_check_mark: | | `SSL_CTX_use_certificate_ASN1` | | | | -| `SSL_CTX_use_certificate_chain_file` | :white_check_mark: | | :exclamation: [^stub] | +| `SSL_CTX_use_certificate_chain_file` | :white_check_mark: | | :white_check_mark: | | `SSL_CTX_use_certificate_file` | :white_check_mark: | | :exclamation: [^stub] | | `SSL_CTX_use_psk_identity_hint` [^psk] | | | | | `SSL_CTX_use_serverinfo` | | | | @@ -321,7 +321,7 @@ | `SSL_get_SSL_CTX` | | | | | `SSL_get_all_async_fds` | | | | | `SSL_get_async_status` | | | | -| `SSL_get_certificate` | :white_check_mark: | :white_check_mark: | :exclamation: [^stub] | +| `SSL_get_certificate` | :white_check_mark: | :white_check_mark: | :white_check_mark: | | `SSL_get_changed_async_fds` | | | | | `SSL_get_cipher_list` | | | | | `SSL_get_ciphers` | | | | @@ -349,7 +349,7 @@ | `SSL_get_peer_finished` | | | | | `SSL_get_peer_signature_type_nid` | | | | | `SSL_get_pending_cipher` | | | | -| `SSL_get_privatekey` | :white_check_mark: | | :exclamation: [^stub] | +| `SSL_get_privatekey` | :white_check_mark: | | :white_check_mark: | | `SSL_get_psk_identity` [^psk] | | | | | `SSL_get_psk_identity_hint` [^psk] | | | | | `SSL_get_quiet_shutdown` | | | | diff --git a/rustls-libssl/build.rs b/rustls-libssl/build.rs index 2f73581..1c7dbe4 100644 --- a/rustls-libssl/build.rs +++ b/rustls-libssl/build.rs @@ -62,6 +62,8 @@ const ENTRYPOINTS: &[&str] = &[ "SSL_CTX_clear_options", "SSL_CTX_ctrl", "SSL_CTX_free", + "SSL_CTX_get0_certificate", + "SSL_CTX_get0_privatekey", "SSL_CTX_get_cert_store", "SSL_CTX_get_ex_data", "SSL_CTX_get_options", diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index 63d9113..0fbaf06 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -4,6 +4,7 @@ //! the safe APIs implemented elsewhere. use core::{mem, ptr}; +use std::io::{self, Read}; use std::os::raw::{c_char, c_int, c_long, c_uchar, c_uint, c_void}; use std::sync::Mutex; use std::{fs, path::PathBuf}; @@ -12,14 +13,16 @@ use openssl_sys::{ stack_st_X509, OPENSSL_malloc, EVP_PKEY, X509, X509_STORE, X509_STORE_CTX, X509_V_ERR_UNSPECIFIED, }; +use rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; use crate::bio::{Bio, BIO, BIO_METHOD}; use crate::error::{ffi_panic_boundary, Error, MysteriouslyOppositeReturnValue}; +use crate::evp_pkey::EvpPkey; use crate::ffi::{ free_arc, str_from_cstring, to_arc_mut_ptr, try_clone_arc, try_from, try_mut_slice_int, try_ref_from_ptr, try_slice, try_slice_int, try_str, Castable, OwnershipArc, OwnershipRef, }; -use crate::x509::load_certs; +use crate::x509::{load_certs, OwnedX509}; use crate::ShutdownResult; /// Makes a entry function definition. @@ -330,6 +333,181 @@ entry! { } } +entry! { + pub fn _SSL_CTX_use_certificate_chain_file( + ctx: *mut SSL_CTX, + file_name: *const c_char, + ) -> c_int { + let ctx = try_clone_arc!(ctx); + let file_name = try_str!(file_name); + + let mut file_reader = match fs::File::open(file_name) { + Ok(content) => io::BufReader::new(content), + Err(err) => return Error::from_io(err).raise().into(), + }; + + let mut chain = Vec::new(); + + for cert in rustls_pemfile::certs(&mut file_reader) { + let cert = match cert { + Ok(cert) => cert, + Err(err) => { + log::trace!("Failed to parse {file_name:?}: {err:?}"); + return Error::from_io(err).raise().into(); + } + }; + + match OwnedX509::parse_der(cert.as_ref()) { + Some(_) => chain.push(cert), + None => { + log::trace!("Failed to parse DER certificate"); + return Error::bad_data("certificate").raise().into(); + } + } + } + + match ctx + .lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ctx| ctx.stage_certificate_chain(chain)) + { + Err(e) => e.raise().into(), + Ok(()) => C_INT_SUCCESS, + } + } +} + +entry! { + pub fn _SSL_CTX_use_certificate(ctx: *mut SSL_CTX, x: *mut X509) -> c_int { + let ctx = try_clone_arc!(ctx); + + if x.is_null() { + return Error::null_pointer().raise().into(); + } + + let x509 = OwnedX509::new_incref(x); + let ee = CertificateDer::from(x509.der_bytes()); + + match ctx + .lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ctx| ctx.stage_certificate_end_entity(ee)) + { + Err(e) => e.raise().into(), + Ok(()) => C_INT_SUCCESS, + } + } +} + +entry! { + pub fn _SSL_CTX_use_PrivateKey_file( + ctx: *mut SSL_CTX, + file_name: *const c_char, + file_type: c_int, + ) -> c_int { + let ctx = try_clone_arc!(ctx); + let file_name = try_str!(file_name); + + let der_data = match file_type { + FILETYPE_PEM => { + let mut file_reader = match fs::File::open(file_name) { + Ok(content) => io::BufReader::new(content), + Err(err) => return Error::from_io(err).raise().into(), + }; + + match rustls_pemfile::private_key(&mut file_reader) { + Ok(Some(key)) => key, + Ok(None) => { + log::trace!("No keys found in {file_name:?}"); + return Error::bad_data("pem file").raise().into(); + } + Err(err) => { + log::trace!("Failed to read {file_name:?}: {err:?}"); + return Error::from_io(err).raise().into(); + } + } + } + FILETYPE_DER => { + let mut data = vec![]; + match fs::File::open(file_name).and_then(|mut f| f.read_to_end(&mut data)) { + Ok(_) => PrivatePkcs8KeyDer::from(data).into(), + Err(err) => { + log::trace!("Failed to read {file_name:?}: {err:?}"); + return Error::from_io(err).raise().into(); + } + } + } + _ => { + return Error::not_supported("file_type not in (PEM, DER)") + .raise() + .into(); + } + }; + + let key = match EvpPkey::new_from_der_bytes(der_data) { + None => return Error::not_supported("invalid key format").raise().into(), + Some(key) => key, + }; + + match ctx + .lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ctx| ctx.commit_private_key(key)) + { + Err(e) => e.raise().into(), + Ok(()) => C_INT_SUCCESS, + } + } +} + +entry! { + pub fn _SSL_CTX_use_PrivateKey(ctx: *mut SSL_CTX, pkey: *mut EVP_PKEY) -> c_int { + let ctx = try_clone_arc!(ctx); + + if pkey.is_null() { + return Error::null_pointer().raise().into(); + } + + let pkey = EvpPkey::new_incref(pkey); + + match ctx + .lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ctx| ctx.commit_private_key(pkey)) + { + Err(e) => e.raise().into(), + Ok(()) => C_INT_SUCCESS, + } + } +} + +entry! { + pub fn _SSL_CTX_get0_certificate(ctx: *const SSL_CTX) -> *mut X509 { + let ctx = try_clone_arc!(ctx); + ctx.lock() + .ok() + .map(|ctx| ctx.get_certificate()) + .unwrap_or(ptr::null_mut()) + } +} + +entry! { + pub fn _SSL_CTX_get0_privatekey(ctx: *const SSL_CTX) -> *mut EVP_PKEY { + let ctx = try_clone_arc!(ctx); + ctx.lock() + .ok() + .map(|ctx| ctx.get_privatekey()) + .unwrap_or(ptr::null_mut()) + } +} + +entry! { + pub fn _SSL_CTX_check_private_key(_ctx: *const SSL_CTX) -> c_int { + log::trace!("not implemented: _SSL_CTX_check_private_key, returning success"); + C_INT_SUCCESS + } +} + impl Castable for SSL_CTX { type Ownership = OwnershipArc; type RustType = Mutex; @@ -785,6 +963,26 @@ entry! { } } +entry! { + pub fn _SSL_get_certificate(ssl: *const SSL) -> *mut X509 { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|ssl| ssl.get_certificate()) + .unwrap_or(ptr::null_mut()) + } +} + +entry! { + pub fn _SSL_get_privatekey(ssl: *const SSL) -> *mut EVP_PKEY { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|ssl| ssl.get_privatekey()) + .unwrap_or(ptr::null_mut()) + } +} + impl Castable for SSL { type Ownership = OwnershipArc; type RustType = Mutex; @@ -898,6 +1096,9 @@ impl Castable for SSL_CIPHER { /// Compare [`crate::ffi::MysteriouslyOppositeReturnValue`]. const C_INT_SUCCESS: c_int = 1; +const FILETYPE_PEM: c_int = 1; +const FILETYPE_DER: c_int = 2; + /// Define an enum that can round trip through a c_int, with no /// UB for unknown values. macro_rules! num_enum { @@ -979,14 +1180,6 @@ entry_stub! { pub fn _SSL_get_ex_data(_ssl: *const SSL, _idx: c_int) -> *mut c_void; } -entry_stub! { - pub fn _SSL_get_certificate(_ssl: *const SSL) -> *mut X509; -} - -entry_stub! { - pub fn _SSL_get_privatekey(_ssl: *const SSL) -> *mut EVP_PKEY; -} - entry_stub! { pub fn _SSL_set_session(_ssl: *mut SSL, _session: *mut SSL_SESSION) -> c_int; } @@ -1002,10 +1195,6 @@ entry_stub! { pub fn _SSL_CTX_add_client_CA(_ctx: *mut SSL_CTX, _x: *mut X509) -> c_int; } -entry_stub! { - pub fn _SSL_CTX_check_private_key(_ctx: *const SSL_CTX) -> c_int; -} - entry_stub! { pub fn _SSL_CTX_sess_set_new_cb(_ctx: *mut SSL_CTX, _new_session_cb: SSL_CTX_new_session_cb); } @@ -1021,26 +1210,6 @@ entry_stub! { pub fn _SSL_CTX_set_ciphersuites(_ctx: *mut SSL_CTX, _s: *const c_char) -> c_int; } -entry_stub! { - pub fn _SSL_CTX_use_PrivateKey(_ctx: *mut SSL_CTX, _pkey: *mut EVP_PKEY) -> c_int; -} - -entry_stub! { - pub fn _SSL_CTX_use_PrivateKey_file( - _ctx: *mut SSL_CTX, - _file: *const c_char, - _type: c_int, - ) -> c_int; -} - -entry_stub! { - pub fn _SSL_CTX_use_certificate(_ctx: *mut SSL_CTX, _x: *mut X509) -> c_int; -} - -entry_stub! { - pub fn _SSL_CTX_use_certificate_chain_file(_ctx: *mut SSL_CTX, _file: *const c_char) -> c_int; -} - entry_stub! { pub fn _SSL_CTX_use_certificate_file( _ctx: *mut SSL_CTX, diff --git a/rustls-libssl/src/evp_pkey.rs b/rustls-libssl/src/evp_pkey.rs new file mode 100644 index 0000000..c32e45c --- /dev/null +++ b/rustls-libssl/src/evp_pkey.rs @@ -0,0 +1,300 @@ +use core::ffi::{c_char, c_int, c_long, CStr}; +use core::{fmt, ptr}; + +use openssl_sys::{ + d2i_AutoPrivateKey, EVP_DigestSign, EVP_DigestSignInit, EVP_MD_CTX_free, EVP_MD_CTX_new, + EVP_PKEY_CTX_set_rsa_padding, EVP_PKEY_CTX_set_rsa_pss_saltlen, EVP_PKEY_CTX_set_signature_md, + EVP_PKEY_free, EVP_PKEY_up_ref, EVP_sha256, EVP_sha384, EVP_sha512, EVP_MD, EVP_MD_CTX, + EVP_PKEY, EVP_PKEY_CTX, RSA_PKCS1_PADDING, RSA_PKCS1_PSS_PADDING, +}; +use rustls::pki_types::PrivateKeyDer; + +/// Safe, owning wrapper around an OpenSSL EVP_PKEY. +#[derive(Debug)] +pub struct EvpPkey { + pkey: *const EVP_PKEY, +} + +impl EvpPkey { + /// Use a pre-existing private key, incrementing ownership. + /// + /// `pkey` continues to belong to the caller. + pub fn new_incref(pkey: *mut EVP_PKEY) -> Self { + debug_assert!(!pkey.is_null()); + unsafe { EVP_PKEY_up_ref(pkey) }; + Self { pkey } + } + + /// Parse a key from DER bytes. + pub fn new_from_der_bytes(data: PrivateKeyDer<'static>) -> Option { + let mut old_ptr = ptr::null_mut(); + let mut data_ptr = data.secret_der().as_ptr(); + let data_len = data.secret_der().len(); + let pkey = unsafe { d2i_AutoPrivateKey(&mut old_ptr, &mut data_ptr, data_len as c_long) }; + + if pkey.is_null() { + None + } else { + Some(Self { pkey }) + } + } + + /// Sign a message, returning the signature. + pub fn sign(&self, scheme: &dyn EvpScheme, message: &[u8]) -> Result, ()> { + let mut ctx = SignCtx::new(scheme.digest(), self.pkey as *mut EVP_PKEY).ok_or(())?; + scheme.configure_ctx(&mut ctx).ok_or(())?; + ctx.sign(message) + } + + pub fn algorithm(&self) -> rustls::SignatureAlgorithm { + if self.is_rsa_type() { + rustls::SignatureAlgorithm::RSA + } else if self.is_ecdsa_type() { + rustls::SignatureAlgorithm::ECDSA + } else if self.is_ed25519_type() { + rustls::SignatureAlgorithm::ED25519 + } else if self.is_ed448_type() { + rustls::SignatureAlgorithm::ED448 + } else { + rustls::SignatureAlgorithm::Unknown(0) + } + } + + /// Caller borrows our reference. + pub fn borrow_ref(&self) -> *mut EVP_PKEY { + self.pkey as *mut EVP_PKEY + } + + fn is_rsa_type(&self) -> bool { + self.is_a(c"RSA") || self.is_a(c"RSA-PSS") + } + + fn is_ecdsa_type(&self) -> bool { + self.is_a(c"EC") + } + + fn is_ed25519_type(&self) -> bool { + self.is_a(c"ED25519") + } + + fn is_ed448_type(&self) -> bool { + self.is_a(c"ED448") + } + + fn is_a(&self, which: &CStr) -> bool { + unsafe { EVP_PKEY_is_a(self.pkey, which.as_ptr()) == 1 } + } +} + +impl Clone for EvpPkey { + fn clone(&self) -> Self { + unsafe { EVP_PKEY_up_ref(self.pkey as *mut EVP_PKEY) }; + Self { pkey: self.pkey } + } +} + +impl Drop for EvpPkey { + fn drop(&mut self) { + // safety: cast to *mut is safe, because refcounting is assumed atomic + unsafe { EVP_PKEY_free(self.pkey as *mut EVP_PKEY) }; + } +} + +// We assume read-only (const *EVP_PKEY) functions on EVP_PKEYs are thread safe, +// and refcounting is atomic. The actual facts are not documented. +unsafe impl Sync for EvpPkey {} +unsafe impl Send for EvpPkey {} + +pub trait EvpScheme: fmt::Debug { + fn digest(&self) -> *mut EVP_MD; + fn configure_ctx(&self, ctx: &mut SignCtx) -> Option<()>; +} + +pub fn rsa_pkcs1_sha256() -> Box { + Box::new(RsaPkcs1(unsafe { EVP_sha256() })) +} + +pub fn rsa_pkcs1_sha384() -> Box { + Box::new(RsaPkcs1(unsafe { EVP_sha384() })) +} + +pub fn rsa_pkcs1_sha512() -> Box { + Box::new(RsaPkcs1(unsafe { EVP_sha512() })) +} + +#[derive(Debug)] +struct RsaPkcs1(*const EVP_MD); + +impl EvpScheme for RsaPkcs1 { + fn digest(&self) -> *mut EVP_MD { + self.0 as *mut EVP_MD + } + + fn configure_ctx(&self, ctx: &mut SignCtx) -> Option<()> { + ctx.set_signature_md(self.0) + .and_then(|_| ctx.set_rsa_padding(RSA_PKCS1_PADDING)) + } +} + +unsafe impl Sync for RsaPkcs1 {} +unsafe impl Send for RsaPkcs1 {} + +pub fn rsa_pss_sha256() -> Box { + Box::new(RsaPss(unsafe { EVP_sha256() })) +} + +pub fn rsa_pss_sha384() -> Box { + Box::new(RsaPss(unsafe { EVP_sha384() })) +} + +pub fn rsa_pss_sha512() -> Box { + Box::new(RsaPss(unsafe { EVP_sha512() })) +} + +#[derive(Debug)] +struct RsaPss(*const EVP_MD); + +impl EvpScheme for RsaPss { + fn digest(&self) -> *mut EVP_MD { + self.0 as *mut EVP_MD + } + + fn configure_ctx(&self, ctx: &mut SignCtx) -> Option<()> { + const RSA_PSS_SALTLEN_DIGEST: c_int = -1; + ctx.set_signature_md(self.0) + .and_then(|_| ctx.set_rsa_padding(RSA_PKCS1_PSS_PADDING)) + .and_then(|_| ctx.set_pss_saltlen(RSA_PSS_SALTLEN_DIGEST)) + } +} + +unsafe impl Sync for RsaPss {} +unsafe impl Send for RsaPss {} + +/// Owning wrapper for a signing `EVP_MD_CTX` +pub(crate) struct SignCtx { + md_ctx: *mut EVP_MD_CTX, + // owned by `md_ctx` + pkey_ctx: *mut EVP_PKEY_CTX, +} + +impl SignCtx { + fn new(md: *mut EVP_MD, pkey: *mut EVP_PKEY) -> Option { + let md_ctx = unsafe { EVP_MD_CTX_new() }; + let mut pkey_ctx = ptr::null_mut(); + + match unsafe { EVP_DigestSignInit(md_ctx, &mut pkey_ctx, md, ptr::null_mut(), pkey) } { + 1 => {} + _ => { + unsafe { EVP_MD_CTX_free(md_ctx) }; + return None; + } + }; + + Some(SignCtx { md_ctx, pkey_ctx }) + } + + fn set_signature_md(&mut self, md: *const EVP_MD) -> Option<()> { + unsafe { EVP_PKEY_CTX_set_signature_md(self.pkey_ctx, md) == 1 }.then_some(()) + } + + fn set_rsa_padding(&mut self, pad: c_int) -> Option<()> { + unsafe { EVP_PKEY_CTX_set_rsa_padding(self.pkey_ctx, pad) == 1 }.then_some(()) + } + + fn set_pss_saltlen(&mut self, saltlen: c_int) -> Option<()> { + unsafe { EVP_PKEY_CTX_set_rsa_pss_saltlen(self.pkey_ctx, saltlen) == 1 }.then_some(()) + } + + fn sign(self, data: &[u8]) -> Result, ()> { + // determine length + let mut max_len = 0; + match unsafe { + EVP_DigestSign( + self.md_ctx, + ptr::null_mut(), + &mut max_len, + data.as_ptr(), + data.len(), + ) + } { + 1 => {} + _ => return Err(()), + }; + + // do the signature + let mut out = vec![0u8; max_len]; + let mut actual_len = max_len; + + match unsafe { + EVP_DigestSign( + self.md_ctx, + out.as_mut_ptr(), + &mut actual_len, + data.as_ptr(), + data.len(), + ) + } { + 1 => {} + _ => return Err(()), + } + + out.truncate(actual_len); + Ok(out) + } +} + +impl Drop for SignCtx { + fn drop(&mut self) { + unsafe { EVP_MD_CTX_free(self.md_ctx) }; + } +} + +extern "C" { + pub fn EVP_PKEY_is_a(pkey: *const EVP_PKEY, name: *const c_char) -> c_int; +} + +#[cfg(all(test, not(miri)))] +mod tests { + use super::*; + + #[test] + fn supports_rsaencryption_keys() { + let der = + rustls_pemfile::private_key(&mut &include_bytes!("../test-ca/rsa/server.key")[..]) + .unwrap() + .unwrap(); + let key = EvpPkey::new_from_der_bytes(der).unwrap(); + println!("{key:?}"); + assert_eq!(key.algorithm(), rustls::SignatureAlgorithm::RSA); + assert_eq!( + key.sign(rsa_pkcs1_sha256().as_ref(), b"hello") + .unwrap() + .len(), + 256 + ); + assert_eq!( + key.sign(rsa_pkcs1_sha384().as_ref(), b"hello") + .unwrap() + .len(), + 256 + ); + assert_eq!( + key.sign(rsa_pkcs1_sha512().as_ref(), b"hello") + .unwrap() + .len(), + 256 + ); + assert_eq!( + key.sign(rsa_pss_sha256().as_ref(), b"hello").unwrap().len(), + 256 + ); + assert_eq!( + key.sign(rsa_pss_sha384().as_ref(), b"hello").unwrap().len(), + 256 + ); + assert_eq!( + key.sign(rsa_pss_sha512().as_ref(), b"hello").unwrap().len(), + 256 + ); + } +} diff --git a/rustls-libssl/src/lib.rs b/rustls-libssl/src/lib.rs index 6004627..b4fba7d 100644 --- a/rustls-libssl/src/lib.rs +++ b/rustls-libssl/src/lib.rs @@ -6,8 +6,8 @@ use std::sync::{Arc, Mutex}; use openssl_probe::ProbeResult; use openssl_sys::{ - SSL_ERROR_NONE, SSL_ERROR_SSL, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, X509_STORE, - X509_V_ERR_UNSPECIFIED, + EVP_PKEY, SSL_ERROR_NONE, SSL_ERROR_SSL, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, X509, + X509_STORE, X509_V_ERR_UNSPECIFIED, }; use rustls::crypto::aws_lc_rs as provider; use rustls::pki_types::{CertificateDer, ServerName}; @@ -26,12 +26,14 @@ mod constants; )] mod entry; mod error; +mod evp_pkey; #[macro_use] #[allow(unused_macros, dead_code, unused_imports)] mod ffi; #[cfg(miri)] #[allow(non_camel_case_types, dead_code)] mod miri; +mod sign; mod verifier; mod x509; @@ -210,6 +212,7 @@ pub struct SslContext { alpn: Vec>, default_cert_file: Option, default_cert_dir: Option, + auth_keys: sign::CertifiedKeySet, } impl SslContext { @@ -223,6 +226,7 @@ impl SslContext { alpn: vec![], default_cert_file: None, default_cert_dir: None, + auth_keys: sign::CertifiedKeySet::default(), } } @@ -282,6 +286,26 @@ impl SslContext { fn set_alpn_offer(&mut self, alpn: Vec>) { self.alpn = alpn; } + + fn stage_certificate_end_entity(&mut self, end: CertificateDer<'static>) { + self.auth_keys.stage_certificate_end_entity(end) + } + + fn stage_certificate_chain(&mut self, chain: Vec>) { + self.auth_keys.stage_certificate_chain(chain) + } + + fn commit_private_key(&mut self, key: evp_pkey::EvpPkey) -> Result<(), error::Error> { + self.auth_keys.commit_private_key(key) + } + + fn get_certificate(&self) -> *mut X509 { + self.auth_keys.borrow_current_cert() + } + + fn get_privatekey(&self) -> *mut EVP_PKEY { + self.auth_keys.borrow_current_key() + } } /// Parse the ALPN wire format (which is used in the openssl API) @@ -320,6 +344,7 @@ struct Ssl { peer_cert: Option, peer_cert_chain: Option, shutdown_flags: ShutdownFlags, + auth_keys: sign::CertifiedKeySet, } impl Ssl { @@ -339,6 +364,7 @@ impl Ssl { peer_cert: None, peer_cert_chain: None, shutdown_flags: ShutdownFlags::default(), + auth_keys: inner.auth_keys.clone(), }) } @@ -375,6 +401,18 @@ impl Ssl { self.mode == ConnMode::Server } + fn stage_certificate_end_entity(&mut self, end: CertificateDer<'static>) { + self.auth_keys.stage_certificate_end_entity(end) + } + + fn stage_certificate_chain(&mut self, chain: Vec>) { + self.auth_keys.stage_certificate_chain(chain) + } + + fn commit_private_key(&mut self, key: evp_pkey::EvpPkey) -> Result<(), error::Error> { + self.auth_keys.commit_private_key(key) + } + fn set_verify_hostname(&mut self, hostname: Option<&str>) -> bool { match hostname { // If name is NULL or the empty string, the list of hostnames is @@ -445,12 +483,17 @@ impl Ssl { )); self.verifier = Some(verifier.clone()); - let mut config = ClientConfig::builder_with_provider(provider) + let wants_resolver = ClientConfig::builder_with_provider(provider) .with_protocol_versions(method.client_versions) .map_err(error::Error::from_rustls)? .dangerous() - .with_custom_certificate_verifier(verifier) - .with_no_client_auth(); + .with_custom_certificate_verifier(verifier); + + let mut config = if let Some(resolver) = self.auth_keys.resolver() { + wants_resolver.with_client_cert_resolver(resolver) + } else { + wants_resolver.with_no_client_auth() + }; config.alpn_protocols.clone_from(&self.alpn); @@ -689,6 +732,14 @@ impl Ssl { Ok(verify_roots) } + + fn get_certificate(&self) -> *mut X509 { + self.auth_keys.borrow_current_cert() + } + + fn get_privatekey(&self) -> *mut EVP_PKEY { + self.auth_keys.borrow_current_key() + } } #[derive(Default)] diff --git a/rustls-libssl/src/sign.rs b/rustls-libssl/src/sign.rs new file mode 100644 index 0000000..1240baa --- /dev/null +++ b/rustls-libssl/src/sign.rs @@ -0,0 +1,213 @@ +use std::ptr; +use std::sync::Arc; + +use openssl_sys::{EVP_PKEY, X509}; +use rustls::client::ResolvesClientCert; +use rustls::pki_types::CertificateDer; +use rustls::sign; +use rustls::{SignatureAlgorithm, SignatureScheme}; + +use crate::error; +use crate::evp_pkey::{ + rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, rsa_pss_sha256, rsa_pss_sha384, + rsa_pss_sha512, EvpPkey, EvpScheme, +}; +use crate::x509::OwnedX509Stack; + +/// This matches up to the implied state machine in `SSL_CTX_use_certificate_chain_file` +/// and `SSL_CTX_use_PrivateKey_file`, and matching man pages. +#[derive(Clone, Default, Debug)] +pub struct CertifiedKeySet { + /// Last `SSL_CTX_use_certificate_chain_file` result, pending a matching + /// `SSL_CTX_use_PrivateKey_file`. + pending_cert_chain: Option>>, + + /// Last `SSL_CTX_use_certificate` result, prepended to chain during commit. + /// May be absent. + pending_cert_end_entity: Option>, + + /// The key and certificate we're currently using. + /// + /// TODO: support multiple key types, and demultiplex them by type. + current_key: Option, +} + +impl CertifiedKeySet { + pub fn stage_certificate_chain(&mut self, chain: Vec>) { + self.pending_cert_chain = Some(chain); + } + + pub fn stage_certificate_end_entity(&mut self, end: CertificateDer<'static>) { + self.pending_cert_end_entity = Some(end); + } + + pub fn commit_private_key(&mut self, key: EvpPkey) -> Result<(), error::Error> { + let chain = match ( + self.pending_cert_end_entity.take(), + self.pending_cert_chain.take(), + ) { + (Some(end_entity), Some(mut chain)) => { + chain.insert(0, end_entity); + chain + } + (None, Some(chain)) => chain, + (Some(end_entity), None) => vec![end_entity], + (None, None) => { + return Err(error::Error::bad_data("no certificate found for key")); + } + }; + + self.current_key = Some(OpenSslCertifiedKey::new(chain, key)?); + Ok(()) + } + + pub fn resolver(&self) -> Option> { + self.current_key.as_ref().map(|ck| ck.resolver()) + } + + /// For `SSL_get_certificate` + pub fn borrow_current_cert(&self) -> *mut X509 { + self.current_key + .as_ref() + .map(|ck| ck.borrow_cert()) + .unwrap_or(ptr::null_mut()) + } + + /// For `SSL_get_privatekey` + pub fn borrow_current_key(&self) -> *mut EVP_PKEY { + self.current_key + .as_ref() + .map(|ck| ck.borrow_key()) + .unwrap_or(ptr::null_mut()) + } +} + +#[derive(Clone, Debug)] +struct OpenSslCertifiedKey { + key: EvpPkey, + openssl_chain: OwnedX509Stack, + rustls_chain: Vec>, +} + +impl OpenSslCertifiedKey { + fn new(chain: Vec>, key: EvpPkey) -> Result { + Ok(Self { + key, + openssl_chain: OwnedX509Stack::from_rustls(&chain)?, + rustls_chain: chain, + }) + } + + fn borrow_cert(&self) -> *mut X509 { + self.openssl_chain.borrow_top_ref() + } + + fn borrow_key(&self) -> *mut EVP_PKEY { + self.key.borrow_ref() + } + + fn resolver(&self) -> Arc { + Arc::new(AlwaysResolvesClientCert(Arc::new(sign::CertifiedKey::new( + self.rustls_chain.clone(), + Arc::new(OpenSslKey(self.key.clone())), + )))) + } +} + +#[derive(Debug)] +struct AlwaysResolvesClientCert(Arc); + +impl ResolvesClientCert for AlwaysResolvesClientCert { + fn has_certs(&self) -> bool { + true + } + + fn resolve( + &self, + _root_hint_subjects: &[&[u8]], + _schemes: &[SignatureScheme], + ) -> Option> { + Some(Arc::clone(&self.0)) + } +} + +#[derive(Debug)] +struct OpenSslKey(EvpPkey); + +impl sign::SigningKey for OpenSslKey { + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { + match self.0.algorithm() { + SignatureAlgorithm::RSA => { + if offered.contains(&SignatureScheme::RSA_PSS_SHA512) { + return Some(Box::new(OpenSslSigner { + pkey: self.0.clone(), + pscheme: rsa_pss_sha512(), + scheme: SignatureScheme::RSA_PSS_SHA512, + })); + } + if offered.contains(&SignatureScheme::RSA_PSS_SHA384) { + return Some(Box::new(OpenSslSigner { + pkey: self.0.clone(), + pscheme: rsa_pss_sha384(), + scheme: SignatureScheme::RSA_PSS_SHA384, + })); + } + if offered.contains(&SignatureScheme::RSA_PSS_SHA256) { + return Some(Box::new(OpenSslSigner { + pkey: self.0.clone(), + pscheme: rsa_pss_sha256(), + scheme: SignatureScheme::RSA_PSS_SHA256, + })); + } + + if offered.contains(&SignatureScheme::RSA_PKCS1_SHA512) { + return Some(Box::new(OpenSslSigner { + pkey: self.0.clone(), + pscheme: rsa_pkcs1_sha512(), + scheme: SignatureScheme::RSA_PKCS1_SHA512, + })); + } + if offered.contains(&SignatureScheme::RSA_PKCS1_SHA384) { + return Some(Box::new(OpenSslSigner { + pkey: self.0.clone(), + pscheme: rsa_pkcs1_sha384(), + scheme: SignatureScheme::RSA_PKCS1_SHA384, + })); + } + if offered.contains(&SignatureScheme::RSA_PKCS1_SHA256) { + return Some(Box::new(OpenSslSigner { + pkey: self.0.clone(), + pscheme: rsa_pkcs1_sha256(), + scheme: SignatureScheme::RSA_PKCS1_SHA256, + })); + } + + None + } + _ => None, + } + } + + fn algorithm(&self) -> SignatureAlgorithm { + self.0.algorithm() + } +} + +#[derive(Debug)] +struct OpenSslSigner { + pkey: EvpPkey, + pscheme: Box, + scheme: SignatureScheme, +} + +impl sign::Signer for OpenSslSigner { + fn sign(&self, message: &[u8]) -> Result, rustls::Error> { + self.pkey + .sign(self.pscheme.as_ref(), message) + .map_err(|_| rustls::Error::General("signing failed".to_string())) + } + + fn scheme(&self) -> SignatureScheme { + self.scheme + } +} diff --git a/rustls-libssl/src/x509.rs b/rustls-libssl/src/x509.rs index bc0ebee..55d4fff 100644 --- a/rustls-libssl/src/x509.rs +++ b/rustls-libssl/src/x509.rs @@ -1,11 +1,12 @@ use core::ffi::{c_int, c_long, c_void}; -use core::ptr; +use core::{ptr, slice}; use std::path::PathBuf; use std::{fs, io}; use openssl_sys::{ - d2i_X509, stack_st_X509, OPENSSL_sk_new_null, OPENSSL_sk_num, OPENSSL_sk_push, - OPENSSL_sk_value, X509_STORE_free, X509_STORE_new, X509_free, OPENSSL_STACK, X509, X509_STORE, + d2i_X509, i2d_X509, stack_st_X509, OPENSSL_free, OPENSSL_sk_new_null, OPENSSL_sk_num, + OPENSSL_sk_push, OPENSSL_sk_value, X509_STORE_free, X509_STORE_new, X509_free, OPENSSL_STACK, + X509, X509_STORE, }; use rustls::pki_types::CertificateDer; @@ -14,17 +15,30 @@ use crate::error::Error; /// Safe, owning wrapper around an OpenSSL `STACK_OF(X509)` object. /// /// The items are owned by the stack. +#[derive(Debug)] pub struct OwnedX509Stack { raw: *mut stack_st_X509, } impl OwnedX509Stack { + /// Make an empty stack. pub fn empty() -> Self { Self { raw: unsafe { OPENSSL_sk_new_null() as *mut stack_st_X509 }, } } + pub fn from_rustls(certs: &Vec>) -> Result { + let mut r = Self::empty(); + for c in certs { + let item = OwnedX509::parse_der(c.as_ref()) + .ok_or_else(|| Error::bad_data("cannot parse certificate"))?; + r.push(&item); + } + Ok(r) + } + + /// Add the given cert to the top (end) of the stack. pub fn push(&mut self, cert: &OwnedX509) { unsafe { OPENSSL_sk_push( @@ -43,6 +57,37 @@ impl OwnedX509Stack { self.raw } + /// Leak the first X509* to the caller. + /// + /// null is returned if the stack is empty, or itself null. + pub fn borrow_top_ref(&self) -> *mut X509 { + self.borrowed_item(0) + } + + #[allow(dead_code)] // delete me later if unused + /// Convert contents to rustls's representation. + /// + /// This copies the whole chain. + pub fn to_rustls(&self) -> Vec> { + let len = self.len(); + let mut r = Vec::with_capacity(len); + + for i in 0..len { + let item = self.item(i); + r.push(CertificateDer::from(item.der_bytes())); + } + + r + } + + #[allow(dead_code)] // delete me later if unused + /// Owned reference of item at `index`. + fn item(&self, index: usize) -> OwnedX509 { + let donate = self.borrowed_item(index); + unsafe { X509_up_ref(donate) }; + OwnedX509::new(donate) + } + /// Plain, borrowed pointer to the item at `index`. fn borrowed_item(&self, index: usize) -> *mut X509 { unsafe { OPENSSL_sk_value(self.raw as *const OPENSSL_STACK, index as c_int) as *mut X509 } @@ -100,6 +145,38 @@ impl OwnedX509 { } } + /// Create a new one, from a (donated) existing ref. + pub fn new(raw: *mut X509) -> Self { + Self { raw } + } + + /// Create a new one, by incrementing an existing ref. + pub fn new_incref(raw: *mut X509) -> Self { + debug_assert!(!raw.is_null()); + unsafe { X509_up_ref(raw) }; + Self { raw } + } + + /// Return the DER-encoded bytes for this object. + pub fn der_bytes(&self) -> Vec { + let (ptr, len) = unsafe { + let mut ptr = ptr::null_mut(); + let len = i2d_X509(self.raw, &mut ptr); + (ptr, len) + }; + + if len <= 0 { + return vec![]; + } + let len = len as usize; + + let mut v = Vec::with_capacity(len); + v.extend_from_slice(unsafe { slice::from_raw_parts(ptr, len) }); + + unsafe { OPENSSL_free(ptr as *mut _) }; + v + } + /// Give out our reference. /// /// This DOES NOT take a reference. See `SSL_get0_peer_certificate`. diff --git a/rustls-libssl/test-ca/rsa/client.cert b/rustls-libssl/test-ca/rsa/client.cert new file mode 100644 index 0000000..3f5e874 --- /dev/null +++ b/rustls-libssl/test-ca/rsa/client.cert @@ -0,0 +1,81 @@ +-----BEGIN CERTIFICATE----- +MIID3DCCAkSgAwIBAgICAxUwDQYJKoZIhvcNAQELBQAwLDEqMCgGA1UEAwwhcG9u +eXRvd24gUlNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTIzMTIyMTE3MjMxNVoX +DTI5MDYxMjE3MjMxNVowGjEYMBYGA1UEAwwPcG9ueXRvd24gY2xpZW50MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjKiPnwCtC2OgSrWGT66eBMXcdGqj +IZ4U/pi7LeZQd2nR3tY0S/vY3lKobDa0XoXVrxZPiVqg5OSYghab4QLL9bnX9lcJ +98nhBOgJ0ILYE7Y0YW/mi6V6QEO3cG909Y8VmsS6wKetA2Zh2fo5azCRydbO7zOb +7KWkLRQseYC/0FCflT04heKp6E5yzQbbv13p4j3p/GTgEXhdxlcPLHqGkBRkUeNE +e+N2v+9yh1xxb9L8gOVM4bM5/6rFsaqEI7g9/SLnVi38/lvZIYk2sNLv7+p7wrxy +WcAOwn/+y/m/FDdjIQOpNsu+Epms3mqT41AFpXbd1dvKxZmnIp5xw4qzCQIDAQAB +o4GZMIGWMAwGA1UdEwEB/wQCMAAwCwYDVR0PBAQDAgbAMBYGA1UdJQEB/wQMMAoG +CCsGAQUFBwMCMB0GA1UdDgQWBBQtH6waKqkaP10N5HDKgH1+C87DsTBCBgNVHSME +OzA5gBSQodd0RX6RcmeS0UfgOp3UUb7xmqEepBwwGjEYMBYGA1UEAwwPcG9ueXRv +d24gUlNBIENBggF7MA0GCSqGSIb3DQEBCwUAA4IBgQC1LWY2dfmtAZABD2lYny/N +c0DT7rmWepXssc1TpWvwlnXgdWMMxtLx46XjIqITCItR/QKqYdf/JAEoKFyO17NY +h6VYUUznLmjmuKGUiLlKn8lEdK4UTgMRiFJ7tRkhRm4dLCyJ1mVD4sQgGlQm74qN +qGNHucVSVyYfiFraQOZu9PHE8WOuUPyp9xmQiCAFGWVGO3ElmaBl/80GYCT8yccm +jSwejGVQoOpSTY/ouiwGee3Jy4yJ4TtQd1ltgFuZQDoYe8BiqkMwg76GnyzEK6GL +uEraJNh/1oW2RcQ+PP+7SQLaqd624U9KQaC2m4ZuiMll4CegxSSYsuKNAxGj1s+s +psSSbFMae2KaH2dg4v4n2BLLt17ZSxF9zFhdbrfs8iuVcapr4JnJYaTCN3ar9m0o +NntFnXXCg9a5YIlvPDjfWMVjhrq4NXeIyvfiQ4tpgI2E5QTgexodHYmmmVg5T9G3 +4KqpAVxduyka59Z9XqZMRTct5+bV23pvrVeWdyQJBWg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEwDCCAqigAwIBAgIBezANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDDA9wb255 +dG93biBSU0EgQ0EwHhcNMjMxMjIxMTcyMzE1WhcNMzMxMjE4MTcyMzE1WjAsMSow +KAYDVQQDDCFwb255dG93biBSU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwggGiMA0G +CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDC9IRIszo5zkkxlz+pkU8otrZ2Jwlf +BQ4j5heoPkpmvFss2fDElZZCVhZt54ehk2+oRuZhfgldfmuT0KCQEMnx8iN7K+pk +2LgaAVGzT4X/NBv+qamgkzRu9UvrS5NrlWutHsPPRt2TldVVJ1UEiLuWrrFMQwi+ +JATjgBHz6PhhD+UnPszZM/SJaBmtMXT99rO/sS6aaQhkZJCSDVVOnnecXafshkEF +tlMkKDRTTxxTOiTGu2NSH5MMzB3F952AiG8ZDONRSyBtxh/kpRV6+idO/4ufIQ3w +ZUPjLlRZIF9cDIGJXRU+cjYvMSV6yPzM2rP+67dPS9N7gQS1AFiMOlLQRbp3Sz9e +R6eetX/ggaHPcIzNv+pLp0L4+8PINZWhcJnZUlgkNR9Gg25mdPC6BLpWH20NH37V +VfSs40ytxHyw5QRokwwjcGUmlzXSJf0R+eUhXkJAmR+bgKbQKRbCW6M+byNdphfu +c3R2irNvRbYkwTOP3FvFkcC+cYyMIHyKihMCAwEAAaN/MH0wHQYDVR0OBBYEFJCh +13RFfpFyZ5LRR+A6ndRRvvGaMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEF +BQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAfBgNVHSMEGDAWgBRCEI9X +B595ntLEaruio2xG9pG/KzANBgkqhkiG9w0BAQsFAAOCAgEAxyqRDyGCkF07q/2P +44Mkwg+olQdT7moiO7V7MauwLZmCNqkr4hQcAk1NKi1QdYw/xCgd/x7PQ/INVMYN +oAG/xxr4nh0whygSsPGk9LkzoeG4dfeVv7tbsYw+4o7wU9kgCM1v0c2vMskyHh3F +vdMJV+5hWZqHZLUOZY1l9ziJysz/aSD4WpMtXdwT5fFgbJ8zggcMADkIESSBPrK5 +ykjFqFnoryK938IUw8fHEdU5ZdjM+1li4Q6P3YT6ovY9aA9gXbD/xb4mUb5kG+ug +tmGV+MDvi6Qgyt1O9ZgaW0tLdbjdxzTjEgU0KwUDpK6AZ9ebcyL5PGj3JA15ZPvS +36AHH/3N+u3w1Poyxb8NxyOgNY7AX3hRQax9G1/43F3VZ1C991xVrwWL++mRD+Ai +5FhMKjZ258+8DKgYaT2JIExwNWA5taafmR2CKpxgVWSFLha/WogJH3kyyTJHXLjU +Bm5qvwqWAvS3Px+WkSbtqFKRDCs+oaj2wGGuwxqEEEriMJ26AC3Si2n9k0a17TOj +lezKgblBHlpokEgcqOkRDB8k1g/Hkx7eRX4RlBRJ4PVRFT6qSTyy3dESsWhb7Sz2 ++uB8SQIYH+5QXwD3MpNrg2BILQYtcciPiGmLNyQB3ZvJUKcj0n63CjxAfcSnbkUF +AnF6iUVbZu9AMRaBDiRdNLGnBms= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFFTCCAv2gAwIBAgIUZBEaAuV4ORnPH4GxeJGyEiqXUN8wDQYJKoZIhvcNAQEL +BQAwGjEYMBYGA1UEAwwPcG9ueXRvd24gUlNBIENBMB4XDTIzMTIyMTE3MjMxNFoX +DTMzMTIxODE3MjMxNFowGjEYMBYGA1UEAwwPcG9ueXRvd24gUlNBIENBMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzvK/b5WhfthXBMVIHboJJuR9XuG9 ++ioSrlzwT9DW7QV31UpgBUZyf1nvT7CmDplNiWZtpqSdJ9pjskBIj5dv4m5cX8A9 +fK1IATdkd6j5/c2ZFkqi5k9iPeJa5rZY6SoGKgvBEr/Y5oiO8HZJZOFetafSr6zV +WRAsKlagrmiNS0oiWC0P0yPVWZyhlHHbtYrHtF/CuWEJ9HqzUk9KeTPwgjfphlYJ +YM0bCZzqN8TEbWPksU1WnmU15YbTgjwI0bNjUXA7W9LmMvbW7EXFJ2+LI+oiF3mk +TQEXqhfdTL9NtqAikD+cfAM1y5e5QSpi8dQuexBteFtXphRZzFk8M9DVKHyngKTH +/QZo6B4Gj9VPrNRPlbPkpbnu8JWD7hO/22VLU4YhghsdwQ/833pfokdV89NMoLo4 +JOUzbTTGtjH0bq6LWTMtLifuQ4H0D1WLtdy/EGgKptnTaeYaXNYT7+v+NNcBHaW8 +W3Orbx0s9IXgQnZTk1u03RbRdIxNxqm+HYEM8gT6S9IUymNZkzDCfZC0bC/saevd +zVE2xpZmuLOfhDl+EcalDYNPrM72+NzkAwRPFGec+bcUEhBxhvxpav+SxDiRC1gD +43qFU7hVfuqVH/EFp0lR3I3Xo8TZ5OIgEyJ5vQH5Ne1+C+sqdCqdGoqf1TZuIE80 +ZwKYcMnRwDXpiGsCAwEAAaNTMFEwHQYDVR0OBBYEFEIQj1cHn3me0sRqu6KjbEb2 +kb8rMB8GA1UdIwQYMBaAFEIQj1cHn3me0sRqu6KjbEb2kb8rMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggIBAEKDnm8x+NOaWNrPNH5kIlGD0tGdDJvw +/KvBZmkMcgFkb4zhbbeUmlu0j5CX4+6Lck0uw62zRgP4Nw3x36W3DL7pmoV4MYHC +djNG1ruNoojvgZyyGgMaSabto0nSHSE9opAorrSXB9RoOv2WcBuQSBNl72eaCQ1F +4kAYjKN6ZwPxEdTsdEmqWyUyEPy6E5kNoM0jW1uI2ZBxzbIOYeePvQ3muUSIMtmC +jShiEOOpmYpzENsAMouY3ZN+CWVS5kB5umnYSviQlAVEKSjC764FD9vMLL+rNhfP +fz+y6EhKcnnYy7mdXIRY73uh5eMyCLUO0yr2Y2ophhD8D79f2w7KtYjaSKfAch0L +lETe9Ch+fGDxUCph3J1IuR/3n01ZjB47WXu/yDZ6s7SHGXIgPaptzP+nZkDnmlZX +bvjB5s6r4U2spuqeLxrwd/1Jin7It+LOYLVmkihpbta9+/KKiXOuSYN1rSiQ2XKp +n1ZN0XxhcZzsALklBIU+Lm11b8gPVS7rXqll/sDmaAH9Iw+AXwUYjCb62Gy58yzu +uk3Q+msRr3oVI9bBhmEXmZxyENYJrw305qOlI3+tHBoJLUSP6zQ214aEu4trJr5K +kmbF7DZRG9MSBXeRk7e5ojK13xI1/XCjgIOTkGxF4rEFbVwhc0B8zS/2x3zw0fkE +M4J0J+gz0QYr +-----END CERTIFICATE----- diff --git a/rustls-libssl/test-ca/rsa/client.key b/rustls-libssl/test-ca/rsa/client.key new file mode 100644 index 0000000..0d39ec3 --- /dev/null +++ b/rustls-libssl/test-ca/rsa/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCMqI+fAK0LY6BK +tYZPrp4Exdx0aqMhnhT+mLst5lB3adHe1jRL+9jeUqhsNrRehdWvFk+JWqDk5JiC +FpvhAsv1udf2Vwn3yeEE6AnQgtgTtjRhb+aLpXpAQ7dwb3T1jxWaxLrAp60DZmHZ ++jlrMJHJ1s7vM5vspaQtFCx5gL/QUJ+VPTiF4qnoTnLNBtu/XeniPen8ZOAReF3G +Vw8seoaQFGRR40R743a/73KHXHFv0vyA5Uzhszn/qsWxqoQjuD39IudWLfz+W9kh +iTaw0u/v6nvCvHJZwA7Cf/7L+b8UN2MhA6k2y74SmazeapPjUAWldt3V28rFmaci +nnHDirMJAgMBAAECggEAHGJTgSmYweibyxewf9nj52CqKQ/v1XPaFrppY0zLxh0j +jc06Bm9PByY0+IldgomNYmSlLjmMqEP9BptbX1+6Gt8i1oIf79HcR6oveNU+l1O4 +ZEU5h8qfzeIcXWMQfhEesfmrGf98KWh6rIsTFS9a7Bkd7yVB/NI8PCCLDQXPL1EZ +HCrfZjtUhiT/FSYjNU4eC4mEuDMRHEDxIViYd6JiejLbvgw8zcDOtItRRz4Zkq6w +ixplEF9drNrIK2wDg/IgAlTdN7fKyf/IrdJQVvCt0ewWYwh1RKEhCNGP4T/8PdKp +j4Z+qqK4h2KV+CUZhvDYh19Ik53r/HGEMk2MBNJFpQKBgQC/hGV+0mfm/WfeF6mU +hyzOHaUOr+A6aiBc4e8p7YgEFl9iMyvYcyWFu4LhL13KxHrNpT+gCbgY8K5W9Tan +zvR5aj/mkEaHTpzo4rzOL04p61YKOWXf7Etj9ULVnzxcQeiGhXmFrlkeXGtUgX7W +alocH/4CpfFl+WJjlms+T32Z0wKBgQC8BHRQlswa++fapLtxxw/EuZdcdMQdFPaI +O5O0YjgYz+YFpRNkLN7DNg5DNPhYGjLRsge8ZF75GdE6BbdhTAqiv24m4FOeTVd6 +48fbLGx3JQs2ugtJI6OEsDECo1gtOZS77cmggEV/tkaN5BxaRALfWsYI301gDFFE +hN5aEF/6MwKBgQCRhsI03x7SuAWgDmzujtSt/nq4sU36NUBIM+ou+u5a5MEv9mA/ +xidh+j0WbY6gkDIcZ4/0RM5eLSzcqNISK0E7rU/HHCRrloHGuNvs9Kc5VTj45eqS +f5Q97VUOzEPqeq584ZmYygWv+1wXR5sgxImaS3kRfBT1fs2TjO4K2A5BvwKBgQCA +rjPFbE/pL/txY0l/B5S9OaBkgO4wUUah2tSuooJuSOvPdTmeWC9mP7rnOHu4IMYj +SsuMns15g7f1FDB8AQVOeeIz7ViNgbWbwAXq9a6OpOXV4OMUfbXOfKAuhAk3eq9X +J9nVZbUrQV9sgXD+PooQwBnFvL9CO2vrj1x3G7n0jQKBgClGEuZrhBPrIWphUdB3 +lyczpS6NjrQeLjAVt29BU6/F06WsUiK+iakU3ifX6ZIwWjYwWCvu375SkjeRjEpm +/mKN226RHhuvXKRA4AK0Uqrb6XofyV2p0JgdqEnc8L4PeQZL3+c+mfOI355PIy2f +ddTbABcoaSbKNAUFMP6lb4gB +-----END PRIVATE KEY----- diff --git a/rustls-libssl/tests/client.c b/rustls-libssl/tests/client.c index 9080f3a..134cf15 100644 --- a/rustls-libssl/tests/client.c +++ b/rustls-libssl/tests/client.c @@ -41,12 +41,19 @@ static void dump_openssl_error_stack(void) { } int main(int argc, char **argv) { - if (argc != 4) { - printf("%s \n\n", argv[0]); + if (argc != 4 && argc != 6) { + printf("%s |insecure [ " + "]\n\n", + argv[0]); return 1; } const char *host = argv[1], *port = argv[2], *cacert = argv[3]; + const char *keyfile = NULL, *certfile = NULL; + if (argc == 6) { + keyfile = argv[4]; + certfile = argv[5]; + } struct addrinfo *result = NULL; TRACE(getaddrinfo(host, port, NULL, &result)); @@ -72,16 +79,35 @@ int main(int argc, char **argv) { TRACE(SSL_CTX_load_verify_file(ctx, cacert)); dump_openssl_error_stack(); } + + X509 *client_cert = NULL; + EVP_PKEY *client_key = NULL; + if (keyfile) { + TRACE(SSL_CTX_use_certificate_chain_file(ctx, certfile)); + TRACE(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM)); + client_key = SSL_CTX_get0_privatekey(ctx); + client_cert = SSL_CTX_get0_certificate(ctx); + } + TRACE(SSL_CTX_set_alpn_protos(ctx, (const uint8_t *)"\x02hi\x05world", 9)); dump_openssl_error_stack(); + SSL *ssl = SSL_new(ctx); dump_openssl_error_stack(); + printf("SSL_new: SSL_get_privatekey %s SSL_CTX_get0_privatekey\n", + SSL_get_privatekey(ssl) == client_key ? "same as" : "differs to"); + printf("SSL_new: SSL_get_certificate %s SSL_CTX_get0_certificate\n", + SSL_get_certificate(ssl) == client_cert ? "same as" : "differs to"); TRACE(SSL_set1_host(ssl, host)); dump_openssl_error_stack(); TRACE(SSL_set_fd(ssl, sock)); dump_openssl_error_stack(); TRACE(SSL_connect(ssl)); dump_openssl_error_stack(); + printf("SSL_connect: SSL_get_privatekey %s SSL_CTX_get0_privatekey\n", + SSL_get_privatekey(ssl) == client_key ? "same as" : "differs to"); + printf("SSL_connect: SSL_get_certificate %s SSL_CTX_get0_certificate\n", + SSL_get_certificate(ssl) == client_cert ? "same as" : "differs to"); // check the alpn (also sees that SSL_connect completed handshake) const uint8_t *alpn_ptr = NULL; diff --git a/rustls-libssl/tests/runner.rs b/rustls-libssl/tests/runner.rs index 0c83b67..1c904c1 100644 --- a/rustls-libssl/tests/runner.rs +++ b/rustls-libssl/tests/runner.rs @@ -29,7 +29,7 @@ use std::{net, thread, time}; #[test] #[ignore] -fn client() { +fn client_unauthenticated() { let _server = KillOnDrop( Command::new("openssl") .args([ @@ -53,6 +53,7 @@ fn client() { wait_for_port(4443); + // server is unauthenticated let openssl_insecure_output = Command::new("tests/maybe-valgrind.sh") .env("LD_LIBRARY_PATH", "") .args(["target/client", "localhost", "4443", "insecure"]) @@ -70,6 +71,7 @@ fn client() { assert_eq!(openssl_insecure_output, rustls_insecure_output); + // server is authenticated, client has no creds let openssl_secure_output = Command::new("tests/maybe-valgrind.sh") .env("LD_LIBRARY_PATH", "") .args(["target/client", "localhost", "4443", "test-ca/rsa/ca.cert"]) @@ -86,6 +88,101 @@ fn client() { .unwrap(); assert_eq!(openssl_secure_output, rustls_secure_output); + + // server is authenticated, client has creds but server doesn't ask for them + let openssl_offered_output = Command::new("tests/maybe-valgrind.sh") + .env("LD_LIBRARY_PATH", "") + .args([ + "target/client", + "localhost", + "4443", + "test-ca/rsa/ca.cert", + "test-ca/rsa/client.key", + "test-ca/rsa/client.cert", + ]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + let rustls_offered_output = Command::new("tests/maybe-valgrind.sh") + .args([ + "target/client", + "localhost", + "4443", + "test-ca/rsa/ca.cert", + "test-ca/rsa/client.key", + "test-ca/rsa/client.cert", + ]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + assert_eq!(openssl_offered_output, rustls_offered_output); +} + +#[test] +#[ignore] +fn client_auth() { + let _server = KillOnDrop( + Command::new("openssl") + .args([ + "s_server", + "-cert", + "test-ca/rsa/end.cert", + "-cert_chain", + "test-ca/rsa/inter.cert", + "-key", + "test-ca/rsa/end.key", + "-alpn", + "hello,world", + "-Verify", + "1", + "-CAfile", + "test-ca/rsa/ca.cert", + "-accept", + "localhost:4444", + "-rev", + ]) + .env("LD_LIBRARY_PATH", "") + .spawn() + .expect("failed to start openssl s_server"), + ); + + wait_for_port(4444); + + // mutual auth + let openssl_authed_output = Command::new("tests/maybe-valgrind.sh") + .env("LD_LIBRARY_PATH", "") + .args([ + "target/client", + "localhost", + "4444", + "test-ca/rsa/ca.cert", + "test-ca/rsa/client.key", + "test-ca/rsa/client.cert", + ]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + let rustls_authed_output = Command::new("tests/maybe-valgrind.sh") + .args([ + "target/client", + "localhost", + "4444", + "test-ca/rsa/ca.cert", + "test-ca/rsa/client.key", + "test-ca/rsa/client.cert", + ]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + assert_eq!(openssl_authed_output, rustls_authed_output); } #[test] From 8690e8ecaf5a0ac96107021fb6f5a7069c2ef7e8 Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Mon, 18 Mar 2024 14:46:43 +0000 Subject: [PATCH 05/19] Emit correct error code on alert receipt --- rustls-libssl/src/error.rs | 48 ++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/rustls-libssl/src/error.rs b/rustls-libssl/src/error.rs index 5c6a805..85e6620 100644 --- a/rustls-libssl/src/error.rs +++ b/rustls-libssl/src/error.rs @@ -3,6 +3,7 @@ use core::ptr; use std::ffi::{CStr, CString}; use openssl_sys::{ERR_new, ERR_set_error, ERR_RFLAGS_OFFSET, ERR_RFLAG_FATAL}; +use rustls::AlertDescription; // See openssl/err.h for the source of these magic numbers. @@ -19,13 +20,29 @@ enum Lib { const ERR_RFLAG_COMMON: i32 = 0x2i32 << ERR_RFLAGS_OFFSET; #[derive(Copy, Clone, Debug)] -#[repr(i32)] enum Reason { - PassedNullParameter = (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 258, - InternalError = (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 259, - UnableToGetWriteLock = (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 272, - OperationFailed = (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 263, - Unsupported = ERR_RFLAG_COMMON | 268, + PassedNullParameter, + InternalError, + UnableToGetWriteLock, + OperationFailed, + Unsupported, + Alert(AlertDescription), +} + +impl From for c_int { + fn from(r: Reason) -> c_int { + use Reason::*; + match r { + // see `err.h.in` for magic numbers. + PassedNullParameter => (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 258, + InternalError => (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 259, + UnableToGetWriteLock => (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 272, + OperationFailed => (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 263, + Unsupported => ERR_RFLAG_COMMON | 268, + // `sslerr.h` + Alert(alert) => 1000 + u8::from(alert) as c_int, + } + } } #[derive(Debug)] @@ -77,10 +94,17 @@ impl Error { } pub fn from_rustls(err: rustls::Error) -> Self { - Self { - lib: Lib::User, - reason: Reason::OperationFailed, - string: Some(err.to_string()), + match err { + rustls::Error::AlertReceived(alert) => Self { + lib: Lib::Ssl, + reason: Reason::Alert(alert), + string: Some(format!("SSL alert number {}", u8::from(alert))), + }, + _ => Self { + lib: Lib::User, + reason: Reason::OperationFailed, + string: Some(err.to_string()), + }, } } @@ -109,12 +133,12 @@ impl Error { #[cfg(not(miri))] ERR_set_error( self.lib as c_int, - self.reason as c_int, + self.reason.into(), fmt.as_ptr(), cstr.as_ptr(), ); #[cfg(miri)] - crate::miri::ERR_set_error(self.lib as c_int, self.reason as c_int, cstr.as_ptr()); + crate::miri::ERR_set_error(self.lib as c_int, self.reason.into(), cstr.as_ptr()); } self } From d164c451504ce3904a306fba7e50ff0efac2cdb1 Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Wed, 10 Apr 2024 15:26:38 +0100 Subject: [PATCH 06/19] Quieten `WouldBlock` errors These should not be put on the openssl error stack, and should not be logged either. --- rustls-libssl/src/error.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/rustls-libssl/src/error.rs b/rustls-libssl/src/error.rs index 85e6620..83b1c52 100644 --- a/rustls-libssl/src/error.rs +++ b/rustls-libssl/src/error.rs @@ -19,13 +19,14 @@ enum Lib { const ERR_RFLAG_COMMON: i32 = 0x2i32 << ERR_RFLAGS_OFFSET; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] enum Reason { PassedNullParameter, InternalError, UnableToGetWriteLock, OperationFailed, Unsupported, + WouldBlock, Alert(AlertDescription), } @@ -39,6 +40,7 @@ impl From for c_int { UnableToGetWriteLock => (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 272, OperationFailed => (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 263, Unsupported => ERR_RFLAG_COMMON | 268, + WouldBlock => 0, // `sslerr.h` Alert(alert) => 1000 + u8::from(alert) as c_int, } @@ -109,15 +111,26 @@ impl Error { } pub fn from_io(err: std::io::Error) -> Self { - Self { - lib: Lib::User, - reason: Reason::OperationFailed, - string: Some(err.to_string()), + match err.kind() { + std::io::ErrorKind::WouldBlock => Self { + lib: Lib::User, + reason: Reason::WouldBlock, + string: None, + }, + _ => Self { + lib: Lib::User, + reason: Reason::OperationFailed, + string: Some(err.to_string()), + }, } } /// Add this error to the openssl error stack. pub fn raise(self) -> Self { + if self.quiet() { + return self; + } + log::error!("raising {self:?}"); let cstr = CString::new( self.string @@ -142,6 +155,13 @@ impl Error { } self } + + /// `WouldBlock` errors never make it on the error stack. + /// + /// They are usual in the use of non-blocking BIOs. + fn quiet(&self) -> bool { + self.reason == Reason::WouldBlock + } } // These conversions determine how errors are reported from entry point From 2b48fc7eb20044f15962fdb1c8ff7f7288da2a1a Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Wed, 10 Apr 2024 15:13:31 +0100 Subject: [PATCH 07/19] error.rs: eliminate an `unsafe` using c"" strings --- rustls-libssl/src/error.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rustls-libssl/src/error.rs b/rustls-libssl/src/error.rs index 83b1c52..57565eb 100644 --- a/rustls-libssl/src/error.rs +++ b/rustls-libssl/src/error.rs @@ -1,6 +1,6 @@ use core::ffi::{c_int, c_long}; use core::ptr; -use std::ffi::{CStr, CString}; +use std::ffi::CString; use openssl_sys::{ERR_new, ERR_set_error, ERR_RFLAGS_OFFSET, ERR_RFLAG_FATAL}; use rustls::AlertDescription; @@ -138,8 +138,6 @@ impl Error { .unwrap_or_else(|| format!("{:?}", self.reason)), ) .unwrap(); - // safety: b"%s\0" satisfies requirements of from_bytes_with_nul_unchecked. - let fmt = unsafe { CStr::from_bytes_with_nul_unchecked(b"%s\0") }; unsafe { ERR_new(); // nb. miri cannot do variadic functions, so we define a miri-only equivalent @@ -147,7 +145,7 @@ impl Error { ERR_set_error( self.lib as c_int, self.reason.into(), - fmt.as_ptr(), + c"%s".as_ptr(), cstr.as_ptr(), ); #[cfg(miri)] From ccd24c9fcb76961bca8884a14ee375077a37884a Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Mon, 18 Mar 2024 14:55:33 +0000 Subject: [PATCH 08/19] Validate client error behaviour on alert receipt Engineer a typical case: the server requires auth, but the client has no credentials. To make this work: - match `SSL_read` return code (docs say <= 0, implementation does 0, we chose -1 previously). - print (and therefore validate against openssl) ERR_peek_error(). - ensure the error from rustls is the one that is propagated (by retrieving it from `process_new_packets()`) rather than one already wrapped in `std::io::Error`. --- rustls-libssl/src/entry.rs | 2 +- rustls-libssl/src/lib.rs | 8 +++++++- rustls-libssl/tests/client.c | 14 +++++++++----- rustls-libssl/tests/runner.rs | 19 +++++++++++++++++++ 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index 0fbaf06..f500105 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -763,7 +763,7 @@ entry! { entry! { pub fn _SSL_read(ssl: *mut SSL, buf: *mut c_void, num: c_int) -> c_int { - const ERROR: c_int = -1; + const ERROR: c_int = 0; let ssl = try_clone_arc!(ssl, ERROR); let slice = try_mut_slice_int!(buf as *mut u8, num, ERROR); diff --git a/rustls-libssl/src/lib.rs b/rustls-libssl/src/lib.rs index b4fba7d..04e6c72 100644 --- a/rustls-libssl/src/lib.rs +++ b/rustls-libssl/src/lib.rs @@ -534,9 +534,10 @@ impl Ssl { // no data available, go around again. continue; } - Err(err) => { + Err(err) if late_err.is_ok() => { return Err(error::Error::from_io(err)); } + Err(_) => break (late_err, 0), }, None => break (late_err, 0), }; @@ -562,6 +563,11 @@ impl Ssl { match conn.complete_io(bio) { Ok(_) => {} Err(e) => { + // obtain underlying TLS protocol error (if any), and let it stamp + // out the one wrapped in io::Error. + if let Some(tls_err) = conn.process_new_packets().err() { + return Err(error::Error::from_rustls(tls_err)); + } return Err(error::Error::from_io(e)); } }; diff --git a/rustls-libssl/tests/client.c b/rustls-libssl/tests/client.c index 134cf15..cb2af62 100644 --- a/rustls-libssl/tests/client.c +++ b/rustls-libssl/tests/client.c @@ -35,8 +35,8 @@ static void hexdump(const char *label, const void *buf, int n) { static void dump_openssl_error_stack(void) { if (ERR_peek_error() != 0) { - printf("openssl error: "); - ERR_print_errors_fp(stdout); + printf("openssl error: %08lx\n", ERR_peek_error()); + ERR_print_errors_fp(stderr); } } @@ -163,9 +163,13 @@ int main(int argc, char **argv) { dump_openssl_error_stack(); TRACE(SSL_has_pending(ssl)); dump_openssl_error_stack(); - int rd2 = TRACE(SSL_read(ssl, buf + 1, sizeof(buf) - 1)); - hexdump("result", buf, rd + rd2); - assert(memcmp(buf, "olleh\n", 6) == 0); + if (rd == 0) { + printf("nothing read\n"); + } else { + int rd2 = TRACE(SSL_read(ssl, buf + 1, sizeof(buf) - 1)); + hexdump("result", buf, rd + rd2); + assert(memcmp(buf, "olleh\n", 6) == 0); + } cleanup: close(sock); diff --git a/rustls-libssl/tests/runner.rs b/rustls-libssl/tests/runner.rs index 1c904c1..dac055e 100644 --- a/rustls-libssl/tests/runner.rs +++ b/rustls-libssl/tests/runner.rs @@ -183,6 +183,25 @@ fn client_auth() { .unwrap(); assert_eq!(openssl_authed_output, rustls_authed_output); + + // failed auth + let openssl_failed_output = Command::new("tests/maybe-valgrind.sh") + .env("LD_LIBRARY_PATH", "") + .args(["target/client", "localhost", "4444", "test-ca/rsa/ca.cert"]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + let rustls_failed_output = Command::new("tests/maybe-valgrind.sh") + .args(["target/client", "localhost", "4444", "test-ca/rsa/ca.cert"]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + // nb. only stdout need match; stderr contains full error details (filenames, line numbers, etc.) + assert_eq!(openssl_failed_output.stdout, rustls_failed_output.stdout); } #[test] From a29a9efe2830481d75456ab930e19616e1c1f9c9 Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Tue, 19 Mar 2024 12:21:28 +0000 Subject: [PATCH 09/19] Stub out SSL_SESSION management functions --- rustls-libssl/MATRIX.md | 24 ++++++------ rustls-libssl/build.rs | 12 ++++++ rustls-libssl/src/entry.rs | 76 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 12 deletions(-) diff --git a/rustls-libssl/MATRIX.md b/rustls-libssl/MATRIX.md index 393d0ac..dd7042f 100644 --- a/rustls-libssl/MATRIX.md +++ b/rustls-libssl/MATRIX.md @@ -115,13 +115,13 @@ | `SSL_CTX_load_verify_store` | | | | | `SSL_CTX_new` | :white_check_mark: | :white_check_mark: | :white_check_mark: | | `SSL_CTX_new_ex` | | | | -| `SSL_CTX_remove_session` | | :white_check_mark: | | +| `SSL_CTX_remove_session` | | :white_check_mark: | :exclamation: [^stub] | | `SSL_CTX_sess_get_get_cb` | | | | | `SSL_CTX_sess_get_new_cb` | | | | | `SSL_CTX_sess_get_remove_cb` | | | | -| `SSL_CTX_sess_set_get_cb` | | :white_check_mark: | | +| `SSL_CTX_sess_set_get_cb` | | :white_check_mark: | :exclamation: [^stub] | | `SSL_CTX_sess_set_new_cb` | :white_check_mark: | :white_check_mark: | :exclamation: [^stub] | -| `SSL_CTX_sess_set_remove_cb` | | :white_check_mark: | | +| `SSL_CTX_sess_set_remove_cb` | | :white_check_mark: | :exclamation: [^stub] | | `SSL_CTX_sessions` | | | | | `SSL_CTX_set0_CA_list` | | | | | `SSL_CTX_set0_ctlog_store` [^ct] | | | | @@ -179,7 +179,7 @@ | `SSL_CTX_set_recv_max_early_data` | | | | | `SSL_CTX_set_security_callback` | | | | | `SSL_CTX_set_security_level` | | | | -| `SSL_CTX_set_session_id_context` | | :white_check_mark: | | +| `SSL_CTX_set_session_id_context` | | :white_check_mark: | :exclamation: [^stub] | | `SSL_CTX_set_session_ticket_cb` | | | | | `SSL_CTX_set_srp_cb_arg` [^deprecatedin_3_0] [^srp] | | | | | `SSL_CTX_set_srp_client_pwd_callback` [^deprecatedin_3_0] [^srp] | | | | @@ -226,7 +226,7 @@ | `SSL_SESSION_get0_ticket_appdata` | | | | | `SSL_SESSION_get_compress_id` | | | | | `SSL_SESSION_get_ex_data` | | | | -| `SSL_SESSION_get_id` | | :white_check_mark: | | +| `SSL_SESSION_get_id` | | :white_check_mark: | :exclamation: [^stub] | | `SSL_SESSION_get_master_key` | | | | | `SSL_SESSION_get_max_early_data` | | | | | `SSL_SESSION_get_max_fragment_length` | | | | @@ -252,7 +252,7 @@ | `SSL_SESSION_set_protocol_version` | | | | | `SSL_SESSION_set_time` | | | | | `SSL_SESSION_set_timeout` | | | | -| `SSL_SESSION_up_ref` | | :white_check_mark: | | +| `SSL_SESSION_up_ref` | | :white_check_mark: | :exclamation: [^stub] | | `SSL_SRP_CTX_free` [^deprecatedin_3_0] [^srp] | | | | | `SSL_SRP_CTX_init` [^deprecatedin_3_0] [^srp] | | | | | `SSL_accept` | | | | @@ -316,7 +316,7 @@ | `SSL_get0_security_ex_data` | | | | | `SSL_get0_verified_chain` | | | :white_check_mark: | | `SSL_get1_peer_certificate` | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| `SSL_get1_session` | | :white_check_mark: | | +| `SSL_get1_session` | | :white_check_mark: | :exclamation: [^stub] | | `SSL_get1_supported_ciphers` | | | | | `SSL_get_SSL_CTX` | | | | | `SSL_get_all_async_fds` | | | | @@ -364,7 +364,7 @@ | `SSL_get_server_random` | | | | | `SSL_get_servername` | | :white_check_mark: | | | `SSL_get_servername_type` | | | | -| `SSL_get_session` | | :white_check_mark: | | +| `SSL_get_session` | | :white_check_mark: | :exclamation: [^stub] | | `SSL_get_shared_ciphers` | | | | | `SSL_get_shared_sigalgs` | | | | | `SSL_get_shutdown` | :white_check_mark: | :white_check_mark: | :white_check_mark: | @@ -410,7 +410,7 @@ | `SSL_rstate_string_long` | | | | | `SSL_select_next_proto` | | :white_check_mark: | | | `SSL_sendfile` | | | | -| `SSL_session_reused` | | :white_check_mark: | | +| `SSL_session_reused` | | :white_check_mark: | :exclamation: [^stub] | | `SSL_set0_CA_list` | | | | | `SSL_set0_rbio` | | | :white_check_mark: | | `SSL_set0_security_ex_data` | | | | @@ -461,7 +461,7 @@ | `SSL_set_security_callback` | | | | | `SSL_set_security_level` | | | | | `SSL_set_session` | :white_check_mark: | :white_check_mark: | :exclamation: [^stub] | -| `SSL_set_session_id_context` | | | | +| `SSL_set_session_id_context` | | | :exclamation: [^stub] | | `SSL_set_session_secret_cb` | | | | | `SSL_set_session_ticket_ext` | | | | | `SSL_set_session_ticket_ext_cb` | | | | @@ -519,8 +519,8 @@ | `TLSv1_client_method` [^deprecatedin_1_1_0] [^tls1_method] | | | | | `TLSv1_method` [^deprecatedin_1_1_0] [^tls1_method] | | | | | `TLSv1_server_method` [^deprecatedin_1_1_0] [^tls1_method] | | | | -| `d2i_SSL_SESSION` | | :white_check_mark: | | -| `i2d_SSL_SESSION` | | :white_check_mark: | | +| `d2i_SSL_SESSION` | | :white_check_mark: | :exclamation: [^stub] | +| `i2d_SSL_SESSION` | | :white_check_mark: | :exclamation: [^stub] | [^stub]: symbol exists, but just returns an error. [^deprecatedin_1_1_0]: deprecated in openssl 1.1.0 diff --git a/rustls-libssl/build.rs b/rustls-libssl/build.rs index 1c7dbe4..1b01eae 100644 --- a/rustls-libssl/build.rs +++ b/rustls-libssl/build.rs @@ -43,6 +43,8 @@ fn write_version_file() -> String { const ENTRYPOINTS: &[&str] = &[ "BIO_f_ssl", + "d2i_SSL_SESSION", + "i2d_SSL_SESSION", "OPENSSL_init_ssl", "SSL_alert_desc_string", "SSL_alert_desc_string_long", @@ -70,7 +72,10 @@ const ENTRYPOINTS: &[&str] = &[ "SSL_CTX_load_verify_dir", "SSL_CTX_load_verify_file", "SSL_CTX_new", + "SSL_CTX_remove_session", + "SSL_CTX_sess_set_get_cb", "SSL_CTX_sess_set_new_cb", + "SSL_CTX_sess_set_remove_cb", "SSL_CTX_set_alpn_protos", "SSL_CTX_set_cipher_list", "SSL_CTX_set_ciphersuites", @@ -86,6 +91,7 @@ const ENTRYPOINTS: &[&str] = &[ "SSL_CTX_set_next_proto_select_cb", "SSL_CTX_set_options", "SSL_CTX_set_post_handshake_auth", + "SSL_CTX_set_session_id_context", "SSL_CTX_set_srp_password", "SSL_CTX_set_srp_username", "SSL_CTX_set_verify", @@ -100,6 +106,7 @@ const ENTRYPOINTS: &[&str] = &[ "SSL_get0_peer_certificate", "SSL_get0_verified_chain", "SSL_get1_peer_certificate", + "SSL_get1_session", "SSL_get_certificate", "SSL_get_current_cipher", "SSL_get_error", @@ -107,6 +114,7 @@ const ENTRYPOINTS: &[&str] = &[ "SSL_get_options", "SSL_get_peer_cert_chain", "SSL_get_privatekey", + "SSL_get_session", "SSL_get_shutdown", "SSL_get_verify_result", "SSL_get_version", @@ -116,6 +124,9 @@ const ENTRYPOINTS: &[&str] = &[ "SSL_pending", "SSL_read", "SSL_SESSION_free", + "SSL_SESSION_get_id", + "SSL_session_reused", + "SSL_SESSION_up_ref", "SSL_set0_rbio", "SSL_set0_wbio", "SSL_set1_host", @@ -128,6 +139,7 @@ const ENTRYPOINTS: &[&str] = &[ "SSL_set_options", "SSL_set_post_handshake_auth", "SSL_set_session", + "SSL_set_session_id_context", "SSL_set_shutdown", "SSL_shutdown", "SSL_up_ref", diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index f500105..9d6768e 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -1184,6 +1184,62 @@ entry_stub! { pub fn _SSL_set_session(_ssl: *mut SSL, _session: *mut SSL_SESSION) -> c_int; } +entry_stub! { + pub fn _SSL_session_reused(_ssl: *const SSL) -> c_int; +} + +entry_stub! { + pub fn _SSL_get1_session(_ssl: *mut SSL) -> *mut SSL_SESSION; +} + +entry_stub! { + pub fn _SSL_get_session(_ssl: *const SSL) -> *mut SSL_SESSION; +} + +entry_stub! { + pub fn _SSL_CTX_remove_session(_ssl: *const SSL, _session: *mut SSL_SESSION) -> c_int; +} + +entry_stub! { + pub fn _SSL_CTX_sess_set_get_cb(_ctx: *mut SSL_CTX, _get_session_cb: SSL_CTX_sess_get_cb); +} + +pub type SSL_CTX_sess_get_cb = Option< + unsafe extern "C" fn( + ssl: *mut SSL, + data: *const c_uchar, + len: c_int, + copy: *mut c_int, + ) -> *mut SSL_SESSION, +>; + +entry_stub! { + pub fn _SSL_CTX_sess_set_remove_cb( + _ctx: *mut SSL_CTX, + _remove_session_cb: SSL_CTX_sess_remove_cb, + ); +} + +pub type SSL_CTX_sess_remove_cb = + Option; + +entry_stub! { + pub fn _SSL_CTX_set_session_id_context( + _ctx: *mut SSL_CTX, + _sid_ctx: *const c_uchar, + _sid_ctx_len: c_uint, + ) -> c_int; +} + +entry_stub! { + + pub fn _SSL_set_session_id_context( + _ssl: *mut SSL, + _sid_ctx: *const c_uchar, + _sid_ctx_len: c_uint, + ) -> c_int; +} + entry_stub! { pub fn _SSL_CTX_set_keylog_callback(_ctx: *mut SSL_CTX, _cb: SSL_CTX_keylog_cb_func); } @@ -1202,6 +1258,26 @@ entry_stub! { pub type SSL_CTX_new_session_cb = Option c_int>; +entry_stub! { + pub fn _SSL_SESSION_get_id(_s: *const SSL_SESSION, _len: *mut c_uint) -> *const c_uchar; +} + +entry_stub! { + pub fn _SSL_SESSION_up_ref(_ses: *mut SSL_SESSION) -> c_int; +} + +entry_stub! { + pub fn _d2i_SSL_SESSION( + _a: *mut *mut SSL_SESSION, + _pp: *mut *const c_uchar, + _length: c_long, + ) -> *mut SSL_SESSION; +} + +entry_stub! { + pub fn _i2d_SSL_SESSION(_in: *const SSL_SESSION, _pp: *mut *mut c_uchar) -> c_int; +} + entry_stub! { pub fn _SSL_CTX_set_cipher_list(_ctx: *mut SSL_CTX, _s: *const c_char) -> c_int; } From 72afcbaf6b307934b1f7e980e7fdd6e23b4cc500 Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Tue, 19 Mar 2024 14:10:55 +0000 Subject: [PATCH 10/19] Implement `SSL_get_state` and associated --- rustls-libssl/MATRIX.md | 8 ++--- rustls-libssl/build.rs | 4 +++ rustls-libssl/src/entry.rs | 43 +++++++++++++++++++++++- rustls-libssl/src/lib.rs | 64 ++++++++++++++++++++++++++++++++++-- rustls-libssl/tests/client.c | 10 ++++++ 5 files changed, 122 insertions(+), 7 deletions(-) diff --git a/rustls-libssl/MATRIX.md b/rustls-libssl/MATRIX.md index dd7042f..41322c1 100644 --- a/rustls-libssl/MATRIX.md +++ b/rustls-libssl/MATRIX.md @@ -376,7 +376,7 @@ | `SSL_get_srp_username` [^deprecatedin_3_0] [^srp] | | | | | `SSL_get_srtp_profiles` [^srtp] | | | | | `SSL_get_ssl_method` | | | | -| `SSL_get_state` | | | | +| `SSL_get_state` | | | :white_check_mark: | | `SSL_get_verify_callback` | | | | | `SSL_get_verify_depth` | | | | | `SSL_get_verify_mode` | | | | @@ -387,10 +387,10 @@ | `SSL_group_to_name` | | | | | `SSL_has_matching_session_id` | | | | | `SSL_has_pending` | | | :white_check_mark: | -| `SSL_in_before` | | | | -| `SSL_in_init` | | :white_check_mark: | | +| `SSL_in_before` | | | :white_check_mark: | +| `SSL_in_init` | | :white_check_mark: | :white_check_mark: | | `SSL_is_dtls` | | | | -| `SSL_is_init_finished` | | :white_check_mark: | | +| `SSL_is_init_finished` | | :white_check_mark: | :white_check_mark: | | `SSL_is_server` | | | :white_check_mark: | | `SSL_key_update` | | | | | `SSL_load_client_CA_file` | | :white_check_mark: | | diff --git a/rustls-libssl/build.rs b/rustls-libssl/build.rs index 1b01eae..e0c948d 100644 --- a/rustls-libssl/build.rs +++ b/rustls-libssl/build.rs @@ -116,9 +116,13 @@ const ENTRYPOINTS: &[&str] = &[ "SSL_get_privatekey", "SSL_get_session", "SSL_get_shutdown", + "SSL_get_state", "SSL_get_verify_result", "SSL_get_version", "SSL_has_pending", + "SSL_in_before", + "SSL_in_init", + "SSL_is_init_finished", "SSL_is_server", "SSL_new", "SSL_pending", diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index 9d6768e..2f56999 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -23,7 +23,7 @@ use crate::ffi::{ try_ref_from_ptr, try_slice, try_slice_int, try_str, Castable, OwnershipArc, OwnershipRef, }; use crate::x509::{load_certs, OwnedX509}; -use crate::ShutdownResult; +use crate::{HandshakeState, ShutdownResult}; /// Makes a entry function definition. /// @@ -983,6 +983,47 @@ entry! { } } +entry! { + // nb. 0 is a reasonable OSSL_HANDSHAKE_STATE, it is OSSL_HANDSHAKE_STATE_TLS_ST_BEFORE + pub fn _SSL_get_state(ssl: *const SSL) -> c_uint { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|mut ssl| ssl.handshake_state().into()) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_in_init(ssl: *const SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|mut ssl| ssl.handshake_state().in_init()) + .unwrap_or_default() as c_int + } +} + +entry! { + pub fn _SSL_in_before(ssl: *const SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|mut ssl| ssl.handshake_state() == HandshakeState::Before) + .unwrap_or_default() as c_int + } +} + +entry! { + pub fn _SSL_is_init_finished(ssl: *const SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|mut ssl| ssl.handshake_state() == HandshakeState::Finished) + .unwrap_or_default() as c_int + } +} + impl Castable for SSL { type Ownership = OwnershipArc; type RustType = Mutex; diff --git a/rustls-libssl/src/lib.rs b/rustls-libssl/src/lib.rs index 04e6c72..faaffe7 100644 --- a/rustls-libssl/src/lib.rs +++ b/rustls-libssl/src/lib.rs @@ -1,4 +1,4 @@ -use core::ffi::{c_int, CStr}; +use core::ffi::{c_int, c_uint, CStr}; use std::fs; use std::io::{ErrorKind, Read, Write}; use std::path::PathBuf; @@ -746,6 +746,66 @@ impl Ssl { fn get_privatekey(&self) -> *mut EVP_PKEY { self.auth_keys.borrow_current_key() } + + fn handshake_state(&mut self) -> HandshakeState { + match &mut self.conn { + Some(ref mut conn) => { + if conn.process_new_packets().is_err() { + return HandshakeState::Error; + } + + match (&self.mode, conn.is_handshaking()) { + (ConnMode::Server, true) => HandshakeState::ServerAwaitingClientHello, + (ConnMode::Client, true) => HandshakeState::ClientAwaitingServerHello, + (ConnMode::Unknown, true) => HandshakeState::Before, + (_, false) => HandshakeState::Finished, + } + } + None => HandshakeState::Before, + } + } +} + +/// This is a reduced-fidelity version of `OSSL_HANDSHAKE_STATE`. +/// +/// We don't track all the individual message states (rustls doesn't expose that detail). +#[derive(Debug, PartialEq)] +enum HandshakeState { + Before, + Finished, + Error, + ClientAwaitingServerHello, + ServerAwaitingClientHello, +} + +impl HandshakeState { + fn in_init(&self) -> bool { + match self { + // nb. + // 1. openssl 3 behaviour for SSL_in_before or SSL_in_init does not match docs + // 2. SSL_in_init becomes 1 on sending a fatal alert + HandshakeState::Before + | HandshakeState::Error + | HandshakeState::ClientAwaitingServerHello + | HandshakeState::ServerAwaitingClientHello => true, + _ => false, + } + } +} + +impl From for c_uint { + fn from(hs: HandshakeState) -> c_uint { + match hs { + HandshakeState::Before => 0, + HandshakeState::Finished => 1, + // error resets openssl state machine to the start + HandshakeState::Error => 1, + // aka OSSL_HANDSHAKE_STATE_TLS_ST_CR_SRVR_HELLO + HandshakeState::ClientAwaitingServerHello => 3, + // aka OSSL_HANDSHAKE_STATE_TLS_ST_SR_CLNT_HELLO + HandshakeState::ServerAwaitingClientHello => 22, + } + } } #[derive(Default)] @@ -754,7 +814,7 @@ struct Want { write: bool, } -#[derive(PartialEq)] +#[derive(PartialEq, Debug)] enum ConnMode { Unknown, Client, diff --git a/rustls-libssl/tests/client.c b/rustls-libssl/tests/client.c index cb2af62..b27503c 100644 --- a/rustls-libssl/tests/client.c +++ b/rustls-libssl/tests/client.c @@ -40,6 +40,12 @@ static void dump_openssl_error_stack(void) { } } +static void state(const SSL *s) { + OSSL_HANDSHAKE_STATE st = SSL_get_state(s); + printf("state: %d (before:%d, init:%d, fin:%d)\n", st, SSL_in_before(s), + SSL_in_init(s), SSL_is_init_finished(s)); +} + int main(int argc, char **argv) { if (argc != 4 && argc != 6) { printf("%s |insecure [ " @@ -98,12 +104,15 @@ int main(int argc, char **argv) { SSL_get_privatekey(ssl) == client_key ? "same as" : "differs to"); printf("SSL_new: SSL_get_certificate %s SSL_CTX_get0_certificate\n", SSL_get_certificate(ssl) == client_cert ? "same as" : "differs to"); + state(ssl); TRACE(SSL_set1_host(ssl, host)); dump_openssl_error_stack(); TRACE(SSL_set_fd(ssl, sock)); dump_openssl_error_stack(); + state(ssl); TRACE(SSL_connect(ssl)); dump_openssl_error_stack(); + state(ssl); printf("SSL_connect: SSL_get_privatekey %s SSL_CTX_get0_privatekey\n", SSL_get_privatekey(ssl) == client_key ? "same as" : "differs to"); printf("SSL_connect: SSL_get_certificate %s SSL_CTX_get0_certificate\n", @@ -170,6 +179,7 @@ int main(int argc, char **argv) { hexdump("result", buf, rd + rd2); assert(memcmp(buf, "olleh\n", 6) == 0); } + state(ssl); cleanup: close(sock); From 312043ec28fe2f8da9b971283e8d45e87f865e01 Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Tue, 19 Mar 2024 14:34:14 +0000 Subject: [PATCH 11/19] Implemented undocumented `SSL_set_SSL_CTX` "SSL_set_SSL_CTX is a bad idea" says the openssl issue tracker :) --- rustls-libssl/MATRIX.md | 2 +- rustls-libssl/build.rs | 1 + rustls-libssl/src/entry.rs | 14 ++++++++++++++ rustls-libssl/src/lib.rs | 12 ++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/rustls-libssl/MATRIX.md b/rustls-libssl/MATRIX.md index 41322c1..56af95b 100644 --- a/rustls-libssl/MATRIX.md +++ b/rustls-libssl/MATRIX.md @@ -418,7 +418,7 @@ | `SSL_set0_wbio` | | | :white_check_mark: | | `SSL_set1_host` | | | :white_check_mark: | | `SSL_set1_param` | | | | -| `SSL_set_SSL_CTX` | | :white_check_mark: | | +| `SSL_set_SSL_CTX` | | :white_check_mark: | :white_check_mark: | | `SSL_set_accept_state` | | :white_check_mark: | :white_check_mark: | | `SSL_set_allow_early_data_cb` | | | | | `SSL_set_alpn_protos` | | | :white_check_mark: | diff --git a/rustls-libssl/build.rs b/rustls-libssl/build.rs index e0c948d..fa7bb8f 100644 --- a/rustls-libssl/build.rs +++ b/rustls-libssl/build.rs @@ -145,6 +145,7 @@ const ENTRYPOINTS: &[&str] = &[ "SSL_set_session", "SSL_set_session_id_context", "SSL_set_shutdown", + "SSL_set_SSL_CTX", "SSL_shutdown", "SSL_up_ref", "SSL_want", diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index 2f56999..4820c15 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -1024,6 +1024,20 @@ entry! { } } +entry! { + pub fn _SSL_set_SSL_CTX(ssl: *mut SSL, ctx_ptr: *mut SSL_CTX) -> *mut SSL_CTX { + let ssl = try_clone_arc!(ssl); + let ctx = try_clone_arc!(ctx_ptr); + ssl.lock() + .ok() + .map(|mut ssl| { + ssl.set_ctx(ctx); + ctx_ptr + }) + .unwrap_or_else(ptr::null_mut) + } +} + impl Castable for SSL { type Ownership = OwnershipArc; type RustType = Mutex; diff --git a/rustls-libssl/src/lib.rs b/rustls-libssl/src/lib.rs index faaffe7..9c77188 100644 --- a/rustls-libssl/src/lib.rs +++ b/rustls-libssl/src/lib.rs @@ -368,6 +368,18 @@ impl Ssl { }) } + fn set_ctx(&mut self, ctx: Arc>) { + // there are no docs for `SSL_set_SSL_CTX`. it seems the only + // meaningful reason to use this is key/certificate switching + // (eg, based on SNI). So only bother updating `auth_keys` + self.ctx = ctx.clone(); + self.auth_keys = ctx + .lock() + .ok() + .map(|ctx| ctx.auth_keys.clone()) + .unwrap_or_default(); + } + fn get_options(&self) -> u64 { self.raw_options } From d58d3f29989db24f5d52e7ccb4d0539da5e10dc1 Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Tue, 19 Mar 2024 15:05:06 +0000 Subject: [PATCH 12/19] Implement `SSL_get_wbio` & `SSL_get_rbio` --- rustls-libssl/MATRIX.md | 4 ++-- rustls-libssl/build.rs | 2 ++ rustls-libssl/src/bio.rs | 16 ++++++++++++++++ rustls-libssl/src/entry.rs | 20 ++++++++++++++++++++ rustls-libssl/src/lib.rs | 15 +++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/rustls-libssl/MATRIX.md b/rustls-libssl/MATRIX.md index 56af95b..9e169c5 100644 --- a/rustls-libssl/MATRIX.md +++ b/rustls-libssl/MATRIX.md @@ -353,7 +353,7 @@ | `SSL_get_psk_identity` [^psk] | | | | | `SSL_get_psk_identity_hint` [^psk] | | | | | `SSL_get_quiet_shutdown` | | | | -| `SSL_get_rbio` | | :white_check_mark: | | +| `SSL_get_rbio` | | :white_check_mark: | :white_check_mark: | | `SSL_get_read_ahead` | | | | | `SSL_get_record_padding_callback_arg` | | | | | `SSL_get_recv_max_early_data` | | | | @@ -382,7 +382,7 @@ | `SSL_get_verify_mode` | | | | | `SSL_get_verify_result` | :white_check_mark: | :white_check_mark: | :white_check_mark: | | `SSL_get_version` | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| `SSL_get_wbio` | | :white_check_mark: | | +| `SSL_get_wbio` | | :white_check_mark: | :white_check_mark: | | `SSL_get_wfd` | | | | | `SSL_group_to_name` | | | | | `SSL_has_matching_session_id` | | | | diff --git a/rustls-libssl/build.rs b/rustls-libssl/build.rs index fa7bb8f..994adf9 100644 --- a/rustls-libssl/build.rs +++ b/rustls-libssl/build.rs @@ -114,11 +114,13 @@ const ENTRYPOINTS: &[&str] = &[ "SSL_get_options", "SSL_get_peer_cert_chain", "SSL_get_privatekey", + "SSL_get_rbio", "SSL_get_session", "SSL_get_shutdown", "SSL_get_state", "SSL_get_verify_result", "SSL_get_version", + "SSL_get_wbio", "SSL_has_pending", "SSL_in_before", "SSL_in_init", diff --git a/rustls-libssl/src/bio.rs b/rustls-libssl/src/bio.rs index 92bbef0..8136314 100644 --- a/rustls-libssl/src/bio.rs +++ b/rustls-libssl/src/bio.rs @@ -166,6 +166,22 @@ impl Bio { pub fn write_would_block(&self) -> bool { bio_should_retry_write(self.write) } + + /// Returns `read`. + /// + /// See `SSL_get_rbio` docs for semantics, and confirmation + /// that this API is const-incorrect. + pub fn borrow_read(&self) -> *mut BIO { + self.read + } + + /// Returns `write`. + /// + /// See `SSL_get_wbio` docs for semantics, and confirmation + /// that this API is const-incorrect. + pub fn borrow_write(&self) -> *mut BIO { + self.write + } } impl io::Read for Bio { diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index 4820c15..a179e4d 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -723,6 +723,26 @@ entry! { } } +entry! { + pub fn _SSL_get_rbio(ssl: *const SSL) -> *mut BIO { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|ssl| ssl.get_rbio()) + .unwrap_or_else(ptr::null_mut) + } +} + +entry! { + pub fn _SSL_get_wbio(ssl: *const SSL) -> *mut BIO { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|ssl| ssl.get_wbio()) + .unwrap_or_else(ptr::null_mut) + } +} + entry! { pub fn _SSL_connect(ssl: *mut SSL) -> c_int { let ssl = try_clone_arc!(ssl); diff --git a/rustls-libssl/src/lib.rs b/rustls-libssl/src/lib.rs index 9c77188..a12782f 100644 --- a/rustls-libssl/src/lib.rs +++ b/rustls-libssl/src/lib.rs @@ -1,4 +1,5 @@ use core::ffi::{c_int, c_uint, CStr}; +use core::ptr; use std::fs; use std::io::{ErrorKind, Read, Write}; use std::path::PathBuf; @@ -465,6 +466,20 @@ impl Ssl { } } + fn get_rbio(&self) -> *mut bio::BIO { + self.bio + .as_ref() + .map(|b| b.borrow_read()) + .unwrap_or_else(ptr::null_mut) + } + + fn get_wbio(&self) -> *mut bio::BIO { + self.bio + .as_ref() + .map(|b| b.borrow_write()) + .unwrap_or_else(ptr::null_mut) + } + fn connect(&mut self) -> Result<(), error::Error> { self.set_client_mode(); if self.conn.is_none() { From ff4fec67215dea4fc71bdd96b6bc436197a21cb7 Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Tue, 2 Apr 2024 13:17:41 +0100 Subject: [PATCH 13/19] Take rustls 0.23.4 This is for new APIs around alerts from server `Acceptor`. --- rustls-libssl/Cargo.lock | 4 ++-- rustls-libssl/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rustls-libssl/Cargo.lock b/rustls-libssl/Cargo.lock index 3b03eb4..0746dce 100644 --- a/rustls-libssl/Cargo.lock +++ b/rustls-libssl/Cargo.lock @@ -409,9 +409,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.2" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbdb5ddfafe3040e01fe9dced711e27b5336ac97d4a9b2089f0066a04b5846" +checksum = "8c4d6d8ad9f2492485e13453acbb291dd08f64441b6609c491f1c2cd2c6b4fe1" dependencies = [ "aws-lc-rs", "log", diff --git a/rustls-libssl/Cargo.toml b/rustls-libssl/Cargo.toml index 96ea8c9..27be41d 100644 --- a/rustls-libssl/Cargo.toml +++ b/rustls-libssl/Cargo.toml @@ -14,5 +14,5 @@ env_logger = "0.10" log = "0.4" openssl-probe = "0.1" openssl-sys = "0.9.98" -rustls = "0.23" +rustls = "0.23.4" rustls-pemfile = "2" From 756d755d101efaf58f174b801266f6edd1849eb0 Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Wed, 20 Mar 2024 16:21:06 +0000 Subject: [PATCH 14/19] Implement `SSL_accept` and associated server support --- rustls-libssl/MATRIX.md | 2 +- rustls-libssl/build.rs | 1 + rustls-libssl/src/entry.rs | 16 +++ rustls-libssl/src/lib.rs | 192 +++++++++++++++++++++++++++------- rustls-libssl/src/sign.rs | 27 ++++- rustls-libssl/src/verifier.rs | 150 ++++++++++++++++++++++---- 6 files changed, 322 insertions(+), 66 deletions(-) diff --git a/rustls-libssl/MATRIX.md b/rustls-libssl/MATRIX.md index 9e169c5..4e4288e 100644 --- a/rustls-libssl/MATRIX.md +++ b/rustls-libssl/MATRIX.md @@ -255,7 +255,7 @@ | `SSL_SESSION_up_ref` | | :white_check_mark: | :exclamation: [^stub] | | `SSL_SRP_CTX_free` [^deprecatedin_3_0] [^srp] | | | | | `SSL_SRP_CTX_init` [^deprecatedin_3_0] [^srp] | | | | -| `SSL_accept` | | | | +| `SSL_accept` | | | :white_check_mark: | | `SSL_add1_host` | | | | | `SSL_add1_to_CA_list` | | | | | `SSL_add_client_CA` | | | | diff --git a/rustls-libssl/build.rs b/rustls-libssl/build.rs index 994adf9..9aa5635 100644 --- a/rustls-libssl/build.rs +++ b/rustls-libssl/build.rs @@ -46,6 +46,7 @@ const ENTRYPOINTS: &[&str] = &[ "d2i_SSL_SESSION", "i2d_SSL_SESSION", "OPENSSL_init_ssl", + "SSL_accept", "SSL_alert_desc_string", "SSL_alert_desc_string_long", "SSL_CIPHER_description", diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index a179e4d..f609c94 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -759,6 +759,22 @@ entry! { } } +entry! { + pub fn _SSL_accept(ssl: *mut SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + + match ssl + .lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ssl| ssl.accept()) + .map_err(|err| err.raise()) + { + Err(e) => e.into(), + Ok(()) => C_INT_SUCCESS, + } + } +} + entry! { pub fn _SSL_write(ssl: *mut SSL, buf: *const c_void, num: c_int) -> c_int { const ERROR: c_int = -1; diff --git a/rustls-libssl/src/lib.rs b/rustls-libssl/src/lib.rs index a12782f..50f96aa 100644 --- a/rustls-libssl/src/lib.rs +++ b/rustls-libssl/src/lib.rs @@ -1,5 +1,5 @@ use core::ffi::{c_int, c_uint, CStr}; -use core::ptr; +use core::{mem, ptr}; use std::fs; use std::io::{ErrorKind, Read, Write}; use std::path::PathBuf; @@ -12,7 +12,10 @@ use openssl_sys::{ }; use rustls::crypto::aws_lc_rs as provider; use rustls::pki_types::{CertificateDer, ServerName}; -use rustls::{CipherSuite, ClientConfig, ClientConnection, Connection, RootCertStore}; +use rustls::server::{Accepted, Acceptor}; +use rustls::{ + CipherSuite, ClientConfig, ClientConnection, Connection, RootCertStore, ServerConfig, +}; mod bio; #[macro_use] @@ -340,14 +343,22 @@ struct Ssl { alpn: Vec>, sni_server_name: Option>, bio: Option, - conn: Option, - verifier: Option>, + conn: ConnState, peer_cert: Option, peer_cert_chain: Option, shutdown_flags: ShutdownFlags, auth_keys: sign::CertifiedKeySet, } +#[allow(clippy::large_enum_variant)] +enum ConnState { + Nothing, + Client(Connection, Arc), + Accepting(Acceptor), + Accepted(Accepted), + Server(Connection, Arc), +} + impl Ssl { fn new(ctx: Arc>, inner: &SslContext) -> Result { Ok(Self { @@ -360,8 +371,7 @@ impl Ssl { alpn: inner.alpn.clone(), sni_server_name: None, bio: None, - conn: None, - verifier: None, + conn: ConnState::Nothing, peer_cert: None, peer_cert_chain: None, shutdown_flags: ShutdownFlags::default(), @@ -481,10 +491,14 @@ impl Ssl { } fn connect(&mut self) -> Result<(), error::Error> { - self.set_client_mode(); - if self.conn.is_none() { + if let ConnMode::Unknown = self.mode { + self.set_client_mode(); + } + + if matches!(self.conn, ConnState::Nothing) { self.init_client_conn()?; } + self.try_io() } @@ -508,15 +522,14 @@ impl Ssl { self.verify_mode, &self.verify_server_name, )); - self.verifier = Some(verifier.clone()); let wants_resolver = ClientConfig::builder_with_provider(provider) .with_protocol_versions(method.client_versions) .map_err(error::Error::from_rustls)? .dangerous() - .with_custom_certificate_verifier(verifier); + .with_custom_certificate_verifier(verifier.clone()); - let mut config = if let Some(resolver) = self.auth_keys.resolver() { + let mut config = if let Some(resolver) = self.auth_keys.client_resolver() { wants_resolver.with_client_cert_resolver(resolver) } else { wants_resolver.with_no_client_auth() @@ -527,23 +540,101 @@ impl Ssl { let client_conn = ClientConnection::new(Arc::new(config), sni_server_name.clone()) .map_err(error::Error::from_rustls)?; - self.conn = Some(client_conn.into()); + self.conn = ConnState::Client(client_conn.into(), verifier); Ok(()) } + fn accept(&mut self) -> Result<(), error::Error> { + if let ConnMode::Unknown = self.mode { + self.set_server_mode(); + } + + if matches!(self.conn, ConnState::Nothing) { + self.conn = ConnState::Accepting(Acceptor::default()); + } + + self.try_io()?; + + if let ConnState::Accepted(_) = self.conn { + self.init_server_conn()?; + } + + self.try_io() + } + + fn init_server_conn(&mut self) -> Result<(), error::Error> { + let method = self + .ctx + .lock() + .map(|ctx| ctx.method) + .map_err(|_| error::Error::cannot_lock())?; + + let provider = Arc::new(provider::default_provider()); + let verifier = Arc::new( + verifier::ClientVerifier::new( + self.verify_roots.clone().into(), + provider.clone(), + self.verify_mode, + ) + .map_err(error::Error::from_rustls)?, + ); + + let resolver = self + .auth_keys + .server_resolver() + .ok_or_else(|| error::Error::bad_data("missing server keys"))?; + + let config = ServerConfig::builder_with_provider(provider) + .with_protocol_versions(method.server_versions) + .map_err(error::Error::from_rustls)? + .with_client_cert_verifier(verifier.clone()) + .with_cert_resolver(resolver); + + let accepted = match mem::replace(&mut self.conn, ConnState::Nothing) { + ConnState::Accepted(accepted) => accepted, + _ => unreachable!(), + }; + + // TODO: send alert + let server_conn = accepted + .into_connection(Arc::new(config)) + .map_err(|(err, _alert)| error::Error::from_rustls(err))?; + + self.conn = ConnState::Server(server_conn.into(), verifier); + Ok(()) + } + + fn conn(&self) -> Option<&Connection> { + match &self.conn { + ConnState::Client(conn, _) | ConnState::Server(conn, _) => Some(conn), + _ => None, + } + } + + fn conn_mut(&mut self) -> Option<&mut Connection> { + match &mut self.conn { + ConnState::Client(conn, _) | ConnState::Server(conn, _) => Some(conn), + _ => None, + } + } + fn want(&self) -> Want { match &self.conn { - Some(conn) => Want { + ConnState::Client(conn, _) | ConnState::Server(conn, _) => Want { read: conn.wants_read(), write: conn.wants_write(), }, - None => Want::default(), + ConnState::Accepting(_) => Want { + read: true, + write: false, + }, + _ => Want::default(), } } fn write(&mut self, slice: &[u8]) -> Result { - let written = match &mut self.conn { - Some(ref mut conn) => conn.writer().write(slice).map_err(error::Error::from_io)?, + let written = match self.conn_mut() { + Some(conn) => conn.writer().write(slice).map_err(error::Error::from_io)?, None => 0, }; self.try_io()?; @@ -554,8 +645,8 @@ impl Ssl { let (late_err, read_count) = loop { let late_err = self.try_io(); - match &mut self.conn { - Some(ref mut conn) => match conn.reader().read(slice) { + match self.conn_mut() { + Some(conn) => match conn.reader().read(slice) { Ok(read) => break (late_err, read), Err(err) if err.kind() == ErrorKind::WouldBlock && late_err.is_ok() => { // no data available, go around again. @@ -586,7 +677,7 @@ impl Ssl { }; match &mut self.conn { - Some(ref mut conn) => { + ConnState::Client(conn, _) | ConnState::Server(conn, _) => { match conn.complete_io(bio) { Ok(_) => {} Err(e) => { @@ -606,15 +697,31 @@ impl Ssl { } Ok(()) } - None => Ok(()), + ConnState::Accepting(acceptor) => { + if let Err(e) = acceptor.read_tls(bio) { + return Err(error::Error::from_io(e)); + }; + + match acceptor.accept() { + Ok(None) => Ok(()), + Ok(Some(accepted)) => { + self.conn = ConnState::Accepted(accepted); + Ok(()) + } + Err((error, mut alert)) => { + alert.write_all(bio).map_err(error::Error::from_io)?; + Err(error::Error::from_rustls(error)) + } + } + } + _ => Ok(()), } } fn try_shutdown(&mut self) -> Result { if !self.shutdown_flags.is_sent() { - match &mut self.conn { - Some(ref mut conn) => conn.send_close_notify(), - None => (), + if let Some(conn) = self.conn_mut() { + conn.send_close_notify(); }; self.shutdown_flags.set_sent(); @@ -637,7 +744,7 @@ impl Ssl { } fn get_pending_plaintext(&mut self) -> usize { - self.conn + self.conn_mut() .as_mut() .and_then(|conn| { let io_state = conn.process_new_packets().ok()?; @@ -647,11 +754,11 @@ impl Ssl { } fn get_agreed_alpn(&mut self) -> Option<&[u8]> { - self.conn.as_ref().and_then(|conn| conn.alpn_protocol()) + self.conn().and_then(|conn| conn.alpn_protocol()) } fn init_peer_cert(&mut self) { - let conn = match &self.conn { + let conn = match self.conn() { Some(conn) => conn, None => return, }; @@ -662,6 +769,8 @@ impl Ssl { }; let mut stack = x509::OwnedX509Stack::empty(); + let mut peer_cert = None; + for (i, cert) in certs.iter().enumerate() { let converted = match x509::OwnedX509::parse_der(cert.as_ref()) { Some(converted) => converted, @@ -676,12 +785,13 @@ impl Ssl { // certificate must be obtained separately" stack.push(&converted); } - self.peer_cert = Some(converted); + peer_cert = Some(converted); } else { stack.push(&converted); } } + self.peer_cert = peer_cert; self.peer_cert_chain = Some(stack); } @@ -700,23 +810,22 @@ impl Ssl { } fn get_negotiated_cipher_suite_id(&self) -> Option { - self.conn - .as_ref() + self.conn() .and_then(|conn| conn.negotiated_cipher_suite()) .map(|suite| suite.suite()) } fn get_last_verification_result(&self) -> i64 { - if let Some(verifier) = &self.verifier { - verifier.last_result() - } else { - X509_V_ERR_UNSPECIFIED as i64 + match &self.conn { + ConnState::Client(_, verifier) => verifier.last_result(), + ConnState::Server(_, verifier) => verifier.last_result(), + _ => X509_V_ERR_UNSPECIFIED as i64, } } fn get_error(&mut self) -> c_int { - match &mut self.conn { - Some(ref mut conn) => { + match self.conn_mut() { + Some(conn) => { if let Err(e) = conn.process_new_packets() { error::Error::from_rustls(e).raise(); return SSL_ERROR_SSL; @@ -775,13 +884,14 @@ impl Ssl { } fn handshake_state(&mut self) -> HandshakeState { - match &mut self.conn { - Some(ref mut conn) => { + let mode = self.mode; + match self.conn_mut() { + Some(conn) => { if conn.process_new_packets().is_err() { return HandshakeState::Error; } - match (&self.mode, conn.is_handshaking()) { + match (mode, conn.is_handshaking()) { (ConnMode::Server, true) => HandshakeState::ServerAwaitingClientHello, (ConnMode::Client, true) => HandshakeState::ClientAwaitingServerHello, (ConnMode::Unknown, true) => HandshakeState::Before, @@ -841,7 +951,7 @@ struct Want { write: bool, } -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone, Copy)] enum ConnMode { Unknown, Client, @@ -895,6 +1005,10 @@ impl VerifyMode { self.0 & VerifyMode::PEER == VerifyMode::PEER } + pub fn server_must_attempt_client_auth(&self) -> bool { + self.0 & VerifyMode::PEER == VerifyMode::PEER + } + pub fn server_must_verify_client(&self) -> bool { let bitmap = VerifyMode::PEER | VerifyMode::FAIL_IF_NO_PEER_CERT; self.0 & bitmap == bitmap diff --git a/rustls-libssl/src/sign.rs b/rustls-libssl/src/sign.rs index 1240baa..c852587 100644 --- a/rustls-libssl/src/sign.rs +++ b/rustls-libssl/src/sign.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use openssl_sys::{EVP_PKEY, X509}; use rustls::client::ResolvesClientCert; use rustls::pki_types::CertificateDer; +use rustls::server::{ClientHello, ResolvesServerCert}; use rustls::sign; use rustls::{SignatureAlgorithm, SignatureScheme}; @@ -61,8 +62,12 @@ impl CertifiedKeySet { Ok(()) } - pub fn resolver(&self) -> Option> { - self.current_key.as_ref().map(|ck| ck.resolver()) + pub fn client_resolver(&self) -> Option> { + self.current_key.as_ref().map(|ck| ck.client_resolver()) + } + + pub fn server_resolver(&self) -> Option> { + self.current_key.as_ref().map(|ck| ck.server_resolver()) } /// For `SSL_get_certificate` @@ -106,12 +111,19 @@ impl OpenSslCertifiedKey { self.key.borrow_ref() } - fn resolver(&self) -> Arc { + fn client_resolver(&self) -> Arc { Arc::new(AlwaysResolvesClientCert(Arc::new(sign::CertifiedKey::new( self.rustls_chain.clone(), Arc::new(OpenSslKey(self.key.clone())), )))) } + + fn server_resolver(&self) -> Arc { + Arc::new(AlwaysResolvesServerCert(Arc::new(sign::CertifiedKey::new( + self.rustls_chain.clone(), + Arc::new(OpenSslKey(self.key.clone())), + )))) + } } #[derive(Debug)] @@ -131,6 +143,15 @@ impl ResolvesClientCert for AlwaysResolvesClientCert { } } +#[derive(Debug)] +struct AlwaysResolvesServerCert(Arc); + +impl ResolvesServerCert for AlwaysResolvesServerCert { + fn resolve(&self, _client_hello: ClientHello) -> Option> { + Some(Arc::clone(&self.0)) + } +} + #[derive(Debug)] struct OpenSslKey(EvpPkey); diff --git a/rustls-libssl/src/verifier.rs b/rustls-libssl/src/verifier.rs index 4d7a0a7..6dd72d1 100644 --- a/rustls-libssl/src/verifier.rs +++ b/rustls-libssl/src/verifier.rs @@ -14,8 +14,10 @@ use rustls::{ }, crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}, pki_types::{CertificateDer, ServerName, UnixTime}, - server::ParsedCertificate, - CertificateError, DigitallySignedStruct, Error, RootCertStore, SignatureScheme, + server::danger::{ClientCertVerified, ClientCertVerifier}, + server::{ParsedCertificate, WebPkiClientVerifier}, + CertificateError, DigitallySignedStruct, DistinguishedName, Error, RootCertStore, + SignatureScheme, }; use crate::VerifyMode; @@ -96,27 +98,7 @@ impl ServerCertVerifier for ServerVerifier { ) -> Result { let result = self.verify_server_cert_inner(end_entity, intermediates, now); - let openssl_rv = match &result { - Ok(()) => X509_V_OK, - Err(Error::InvalidCertificate(CertificateError::UnknownIssuer)) => { - X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY - } - Err(Error::InvalidCertificate(CertificateError::NotValidYet)) => { - X509_V_ERR_CERT_NOT_YET_VALID - } - Err(Error::InvalidCertificate(CertificateError::Expired)) => { - X509_V_ERR_CERT_HAS_EXPIRED - } - Err(Error::InvalidCertificate(CertificateError::Revoked)) => X509_V_ERR_CERT_REVOKED, - Err(Error::InvalidCertificate(CertificateError::InvalidPurpose)) => { - X509_V_ERR_INVALID_PURPOSE - } - Err(Error::InvalidCertificate(CertificateError::NotValidForName)) => { - X509_V_ERR_HOSTNAME_MISMATCH - } - // TODO: more mappings can go here - Err(_) => X509_V_ERR_UNSPECIFIED, - }; + let openssl_rv = translate_verify_result(&result); self.last_result.store(openssl_rv as i64, Ordering::Release); // Call it success if it succeeded, or the `mode` says not to care. @@ -161,3 +143,125 @@ impl ServerCertVerifier for ServerVerifier { .supported_schemes() } } + +#[derive(Debug)] +pub struct ClientVerifier { + parent: Arc, + mode: VerifyMode, + last_result: AtomicI64, +} + +impl ClientVerifier { + pub fn new( + root_store: Arc, + provider: Arc, + mode: VerifyMode, + ) -> Result { + let (parent, initial_result) = if !mode.server_must_attempt_client_auth() { + (Ok(WebPkiClientVerifier::no_client_auth()), X509_V_OK) + } else { + let builder = WebPkiClientVerifier::builder_with_provider(root_store, provider); + + if mode.server_must_verify_client() { + (builder.build(), X509_V_ERR_UNSPECIFIED) + } else { + ( + builder.allow_unauthenticated().build(), + X509_V_ERR_UNSPECIFIED, + ) + } + }; + + let parent = parent.map_err(|err| Error::General(err.to_string()))?; + + Ok(Self { + parent, + mode, + last_result: AtomicI64::new(initial_result as i64), + }) + } + + pub fn last_result(&self) -> i64 { + self.last_result.load(Ordering::Acquire) + } +} + +impl ClientCertVerifier for ClientVerifier { + fn offer_client_auth(&self) -> bool { + self.mode.server_must_attempt_client_auth() + } + + fn client_auth_mandatory(&self) -> bool { + self.mode.server_must_verify_client() + } + + fn verify_client_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + now: UnixTime, + ) -> Result { + let result = self + .parent + .verify_client_cert(end_entity, intermediates, now) + .map(|_| ()); + + let openssl_rv = translate_verify_result(&result); + self.last_result.store(openssl_rv as i64, Ordering::Release); + + // Call it success if it succeeded, or the `mode` says not to care. + if openssl_rv == X509_V_OK || !self.mode.server_must_verify_client() { + Ok(ClientCertVerified::assertion()) + } else { + Err(result.unwrap_err()) + } + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + self.parent.verify_tls12_signature(message, cert, dss) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + self.parent.verify_tls13_signature(message, cert, dss) + } + + fn supported_verify_schemes(&self) -> Vec { + self.parent.supported_verify_schemes() + } + + fn root_hint_subjects(&self) -> &[DistinguishedName] { + self.parent.root_hint_subjects() + } +} + +fn translate_verify_result(result: &Result<(), Error>) -> i32 { + match result { + Ok(()) => X509_V_OK, + Err(Error::InvalidCertificate(CertificateError::UnknownIssuer)) => { + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY + } + Err(Error::InvalidCertificate(CertificateError::NotValidYet)) => { + X509_V_ERR_CERT_NOT_YET_VALID + } + Err(Error::InvalidCertificate(CertificateError::Expired)) => X509_V_ERR_CERT_HAS_EXPIRED, + Err(Error::InvalidCertificate(CertificateError::Revoked)) => X509_V_ERR_CERT_REVOKED, + Err(Error::InvalidCertificate(CertificateError::InvalidPurpose)) => { + X509_V_ERR_INVALID_PURPOSE + } + Err(Error::InvalidCertificate(CertificateError::NotValidForName)) => { + X509_V_ERR_HOSTNAME_MISMATCH + } + // TODO: more mappings can go here + Err(_) => X509_V_ERR_UNSPECIFIED, + } +} From 4c8238fddd4e758a8849587e9e479979a56ac91b Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Tue, 19 Mar 2024 16:48:16 +0000 Subject: [PATCH 15/19] Test server behaviour --- rustls-libssl/Makefile | 5 +- rustls-libssl/test-ca/rsa/server.cert | 82 ++++++++++++ rustls-libssl/test-ca/rsa/server.key | 28 ++++ rustls-libssl/tests/runner.rs | 102 +++++++++++++- rustls-libssl/tests/server.c | 185 ++++++++++++++++++++++++++ 5 files changed, 394 insertions(+), 8 deletions(-) create mode 100644 rustls-libssl/test-ca/rsa/server.cert create mode 100644 rustls-libssl/test-ca/rsa/server.key create mode 100644 rustls-libssl/tests/server.c diff --git a/rustls-libssl/Makefile b/rustls-libssl/Makefile index 8c6a5ab..1092470 100644 --- a/rustls-libssl/Makefile +++ b/rustls-libssl/Makefile @@ -19,7 +19,7 @@ ifneq (,$(TARGET)) CARGOFLAGS += --target $(TARGET) endif -all: target/ciphers target/client target/constants target/$(PROFILE)/libssl.so.3 +all: target/ciphers target/client target/constants target/server target/$(PROFILE)/libssl.so.3 test: all ${CARGO} test $(CARGOFLAGS) @@ -48,6 +48,9 @@ target/client: target/client.o target/constants: target/constants.o $(CC) -o $@ $^ $(LDFLAGS) $(shell pkg-config --libs openssl) +target/server: target/server.o + $(CC) -o $@ $^ $(LDFLAGS) $(shell pkg-config --libs openssl) + clean: rm -rf target diff --git a/rustls-libssl/test-ca/rsa/server.cert b/rustls-libssl/test-ca/rsa/server.cert new file mode 100644 index 0000000..960366a --- /dev/null +++ b/rustls-libssl/test-ca/rsa/server.cert @@ -0,0 +1,82 @@ +-----BEGIN CERTIFICATE----- +MIIEGDCCAoCgAwIBAgICAcgwDQYJKoZIhvcNAQELBQAwLDEqMCgGA1UEAwwhcG9u +eXRvd24gUlNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTIzMTIyMTE3MjMxNVoX +DTI5MDYxMjE3MjMxNVowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDI1plwQmA+rr6Evlvn2hzIB/zYKlx +Tm14SPzomxpaf3OpzXzHuOn34yVvU1vTDijUl/YJbcnx052m0075SYeuW08VQB/p +zjhLrFp1ULSD272IddbB88T8Jq/VZ5dAxBB1q6Tm0vGBYQ8eIcmJv+fJQTbTXHKQ +KPHQRmVyqXVkwvwcgEJi9gpOcEEpJ6SDGdyGh1ck3wGnhrSpmsu+hwUVi7las+Ka +QUlvkmD+UGQKo8Ta91Xu8ja25QLTpjVcYxbi719rtA6I9DpX4+3aeIy/0s1xk0Xi +yMXIjSl3dn47gp3IZFRCECuoTdOwOZ9+y9ENnpHvZ84jCBXddU09qheJAgMBAAGj +gdYwgdMwDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFAUefby7 +fPnK64BnGdIizLlWDkbPMEIGA1UdIwQ7MDmAFJCh13RFfpFyZ5LRR+A6ndRRvvGa +oR6kHDAaMRgwFgYDVQQDDA9wb255dG93biBSU0EgQ0GCAXswUwYDVR0RBEwwSoIO +dGVzdHNlcnZlci5jb22HBMYzZAGCFXNlY29uZC50ZXN0c2VydmVyLmNvbYcQIAEN +uAAAAAAAAAAAAAAAAYIJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBgQC2gxGT +c3aayDPR5/ICnuK8aVSsM67xRsGsWvzBxdyyXaB+qSGxa+sCkw8guuAp2W7MrrO/ +zu7yhXDI2nyav6ZP6NvLABFYZ6pXokV/Hj4rQpCDVxvvDVh/E/rW/wx6z580zfdo +auHqUCD9QfR97nENwtGv7ESLN6qFeU0CsJd2GE3y3pnadlpCW3AYHeLX4crm7poj +SfG1F4ipqM1i0knQN86KBzG5PO49azGJGmcsu5lPFQgTRvvR7W+Niq/kIQPu00AW +so8aSB1gp2lypUGHqwsrd51yrQ374jKvXSDt1ptc0xDI9004TuYre9XQsyjxq4Qn +VjmpnKLCbumkrlELf93oomWxT5g6Nb/vu1TUgsrdWgDAW0AZ+rfcWorrZAjbvYw9 +4MJ1PdAmeg+sk51xoh2KF7syHPaMcNCaoSBtXrB5LqrAxixdmB6ORM/KlQU7h9A7 +X+WysEwMowV2MsGr8VsHxYTOpoiE8nUPfnRmbrGPpUU24bzvqTxtxxqQU14= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEwDCCAqigAwIBAgIBezANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDDA9wb255 +dG93biBSU0EgQ0EwHhcNMjMxMjIxMTcyMzE1WhcNMzMxMjE4MTcyMzE1WjAsMSow +KAYDVQQDDCFwb255dG93biBSU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwggGiMA0G +CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDC9IRIszo5zkkxlz+pkU8otrZ2Jwlf +BQ4j5heoPkpmvFss2fDElZZCVhZt54ehk2+oRuZhfgldfmuT0KCQEMnx8iN7K+pk +2LgaAVGzT4X/NBv+qamgkzRu9UvrS5NrlWutHsPPRt2TldVVJ1UEiLuWrrFMQwi+ +JATjgBHz6PhhD+UnPszZM/SJaBmtMXT99rO/sS6aaQhkZJCSDVVOnnecXafshkEF +tlMkKDRTTxxTOiTGu2NSH5MMzB3F952AiG8ZDONRSyBtxh/kpRV6+idO/4ufIQ3w +ZUPjLlRZIF9cDIGJXRU+cjYvMSV6yPzM2rP+67dPS9N7gQS1AFiMOlLQRbp3Sz9e +R6eetX/ggaHPcIzNv+pLp0L4+8PINZWhcJnZUlgkNR9Gg25mdPC6BLpWH20NH37V +VfSs40ytxHyw5QRokwwjcGUmlzXSJf0R+eUhXkJAmR+bgKbQKRbCW6M+byNdphfu +c3R2irNvRbYkwTOP3FvFkcC+cYyMIHyKihMCAwEAAaN/MH0wHQYDVR0OBBYEFJCh +13RFfpFyZ5LRR+A6ndRRvvGaMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEF +BQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAfBgNVHSMEGDAWgBRCEI9X +B595ntLEaruio2xG9pG/KzANBgkqhkiG9w0BAQsFAAOCAgEAxyqRDyGCkF07q/2P +44Mkwg+olQdT7moiO7V7MauwLZmCNqkr4hQcAk1NKi1QdYw/xCgd/x7PQ/INVMYN +oAG/xxr4nh0whygSsPGk9LkzoeG4dfeVv7tbsYw+4o7wU9kgCM1v0c2vMskyHh3F +vdMJV+5hWZqHZLUOZY1l9ziJysz/aSD4WpMtXdwT5fFgbJ8zggcMADkIESSBPrK5 +ykjFqFnoryK938IUw8fHEdU5ZdjM+1li4Q6P3YT6ovY9aA9gXbD/xb4mUb5kG+ug +tmGV+MDvi6Qgyt1O9ZgaW0tLdbjdxzTjEgU0KwUDpK6AZ9ebcyL5PGj3JA15ZPvS +36AHH/3N+u3w1Poyxb8NxyOgNY7AX3hRQax9G1/43F3VZ1C991xVrwWL++mRD+Ai +5FhMKjZ258+8DKgYaT2JIExwNWA5taafmR2CKpxgVWSFLha/WogJH3kyyTJHXLjU +Bm5qvwqWAvS3Px+WkSbtqFKRDCs+oaj2wGGuwxqEEEriMJ26AC3Si2n9k0a17TOj +lezKgblBHlpokEgcqOkRDB8k1g/Hkx7eRX4RlBRJ4PVRFT6qSTyy3dESsWhb7Sz2 ++uB8SQIYH+5QXwD3MpNrg2BILQYtcciPiGmLNyQB3ZvJUKcj0n63CjxAfcSnbkUF +AnF6iUVbZu9AMRaBDiRdNLGnBms= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFFTCCAv2gAwIBAgIUZBEaAuV4ORnPH4GxeJGyEiqXUN8wDQYJKoZIhvcNAQEL +BQAwGjEYMBYGA1UEAwwPcG9ueXRvd24gUlNBIENBMB4XDTIzMTIyMTE3MjMxNFoX +DTMzMTIxODE3MjMxNFowGjEYMBYGA1UEAwwPcG9ueXRvd24gUlNBIENBMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzvK/b5WhfthXBMVIHboJJuR9XuG9 ++ioSrlzwT9DW7QV31UpgBUZyf1nvT7CmDplNiWZtpqSdJ9pjskBIj5dv4m5cX8A9 +fK1IATdkd6j5/c2ZFkqi5k9iPeJa5rZY6SoGKgvBEr/Y5oiO8HZJZOFetafSr6zV +WRAsKlagrmiNS0oiWC0P0yPVWZyhlHHbtYrHtF/CuWEJ9HqzUk9KeTPwgjfphlYJ +YM0bCZzqN8TEbWPksU1WnmU15YbTgjwI0bNjUXA7W9LmMvbW7EXFJ2+LI+oiF3mk +TQEXqhfdTL9NtqAikD+cfAM1y5e5QSpi8dQuexBteFtXphRZzFk8M9DVKHyngKTH +/QZo6B4Gj9VPrNRPlbPkpbnu8JWD7hO/22VLU4YhghsdwQ/833pfokdV89NMoLo4 +JOUzbTTGtjH0bq6LWTMtLifuQ4H0D1WLtdy/EGgKptnTaeYaXNYT7+v+NNcBHaW8 +W3Orbx0s9IXgQnZTk1u03RbRdIxNxqm+HYEM8gT6S9IUymNZkzDCfZC0bC/saevd +zVE2xpZmuLOfhDl+EcalDYNPrM72+NzkAwRPFGec+bcUEhBxhvxpav+SxDiRC1gD +43qFU7hVfuqVH/EFp0lR3I3Xo8TZ5OIgEyJ5vQH5Ne1+C+sqdCqdGoqf1TZuIE80 +ZwKYcMnRwDXpiGsCAwEAAaNTMFEwHQYDVR0OBBYEFEIQj1cHn3me0sRqu6KjbEb2 +kb8rMB8GA1UdIwQYMBaAFEIQj1cHn3me0sRqu6KjbEb2kb8rMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggIBAEKDnm8x+NOaWNrPNH5kIlGD0tGdDJvw +/KvBZmkMcgFkb4zhbbeUmlu0j5CX4+6Lck0uw62zRgP4Nw3x36W3DL7pmoV4MYHC +djNG1ruNoojvgZyyGgMaSabto0nSHSE9opAorrSXB9RoOv2WcBuQSBNl72eaCQ1F +4kAYjKN6ZwPxEdTsdEmqWyUyEPy6E5kNoM0jW1uI2ZBxzbIOYeePvQ3muUSIMtmC +jShiEOOpmYpzENsAMouY3ZN+CWVS5kB5umnYSviQlAVEKSjC764FD9vMLL+rNhfP +fz+y6EhKcnnYy7mdXIRY73uh5eMyCLUO0yr2Y2ophhD8D79f2w7KtYjaSKfAch0L +lETe9Ch+fGDxUCph3J1IuR/3n01ZjB47WXu/yDZ6s7SHGXIgPaptzP+nZkDnmlZX +bvjB5s6r4U2spuqeLxrwd/1Jin7It+LOYLVmkihpbta9+/KKiXOuSYN1rSiQ2XKp +n1ZN0XxhcZzsALklBIU+Lm11b8gPVS7rXqll/sDmaAH9Iw+AXwUYjCb62Gy58yzu +uk3Q+msRr3oVI9bBhmEXmZxyENYJrw305qOlI3+tHBoJLUSP6zQ214aEu4trJr5K +kmbF7DZRG9MSBXeRk7e5ojK13xI1/XCjgIOTkGxF4rEFbVwhc0B8zS/2x3zw0fkE +M4J0J+gz0QYr +-----END CERTIFICATE----- diff --git a/rustls-libssl/test-ca/rsa/server.key b/rustls-libssl/test-ca/rsa/server.key new file mode 100644 index 0000000..cb236b6 --- /dev/null +++ b/rustls-libssl/test-ca/rsa/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDI1plwQmA+rr6 +Evlvn2hzIB/zYKlxTm14SPzomxpaf3OpzXzHuOn34yVvU1vTDijUl/YJbcnx052m +0075SYeuW08VQB/pzjhLrFp1ULSD272IddbB88T8Jq/VZ5dAxBB1q6Tm0vGBYQ8e +IcmJv+fJQTbTXHKQKPHQRmVyqXVkwvwcgEJi9gpOcEEpJ6SDGdyGh1ck3wGnhrSp +msu+hwUVi7las+KaQUlvkmD+UGQKo8Ta91Xu8ja25QLTpjVcYxbi719rtA6I9DpX +4+3aeIy/0s1xk0XiyMXIjSl3dn47gp3IZFRCECuoTdOwOZ9+y9ENnpHvZ84jCBXd +dU09qheJAgMBAAECggEABbzZYJqPc/prWwUJzo1qXdA5AEf8U3eR4nKK9S/yU2zh +8sE3BQxb3M0SAbb6wTbuXmnlcxuGT5UAUrJt5QiTc739kktjZNWKdDcqJb7sv9/L +L+L/II7RYPSmQOkd2mqpbTxRyfOz5DD9Z85ohaNd5l4Dha13NOPvUEdxnjB7Yi4I +YbUYZ/Zq6MUosFRObS0XPBvk6d+zDI3WUZatVTNfuS7fqI1BvkXs5EkV34DQXgQs +LKb1LFAoEZoh64UnfkONIT9oG4OTbbaQ9gCbfQPpKw07GuaMdtjp0QD0yldOKvL4 +V4crSZyj2f3LnPCqCjUwcz6quKSUqUgosVApH+JCcQKBgQDpaGXy3laYTK3zbauh ++eHY5Ia+7fM2ETZx4LfwAYA1K5E84T98swpO571lZVrbOKjBukpUrbHdOcEVcBRH +BTvCh1vL1AXXWR0cCvWtp/WAbu90rDqgu6mxaD+V8wGq29URTWOCRG1WA7zeNHPB +0XAZPLQVeqeSvHGLSqyPf4aoHQKBgQDWBqvl5To7P4ZT6VFl18v0EB3zgzpRuyC3 +xKKz5mGw4tuvspdMU2XaOGQsRl5emMijGeII7JUweHbBdkq44S2FZ9wyn4+I+8Oi +Atu4Nce06ARnw+5RRcJlSs/LrExfOtxF3xp8EQqpL/jEO03n7G5cwcFwuWTKoVTI +0RwcuU4JXQKBgDBMCvRzb2W6UDBT3DT7GPGhcARoBnCEpUhxIH6IQPg/mKEJVvK9 +tX9YUod9row4MCtOGf1lp61IOxztgTSk75W0HpmRuNezt+NKnUWewJ0f12rEDKmf +y2BLWwTzMMAjFvaqldGpyRoIUfeE0QMlDFYcioL7S1uApNoWzJgw4jM9AoGBANIk +osuLku1xphbl08JHbD4rRP1AMBbnwWwuagJxhiID3OhaVivfBvaIv/Ko9Se0o+th +EorooGODJDc4So3Uqrl+DLq36Fr7uE5uuAXa6Ec8OHcZ7flmoUSLfBPjDOnEBVul +f3+py+nq7Drgb9H0VzhEFgb0QX6jgXfbudqKJ5ERAoGAR5Q6wQEoT9VT53nuKprl +3K/6agd+4wlpVQW0W1LImdrJHRHUXO7KJe7Bo5rtjL3lw8dCl3olHlLPJKg9frMn +ZWvJ2t0zca18S76rNcsPew2BecJxNRFlGwdcE1BBA2p/yzBhsZbIO7eqfh+dK5va +rnlrPNbWDhylxMaU4/CoU7k= +-----END PRIVATE KEY----- diff --git a/rustls-libssl/tests/runner.rs b/rustls-libssl/tests/runner.rs index dac055e..ff3792a 100644 --- a/rustls-libssl/tests/runner.rs +++ b/rustls-libssl/tests/runner.rs @@ -1,3 +1,4 @@ +use std::io::Read; use std::process::{Child, Command, Output, Stdio}; use std::{net, thread, time}; @@ -30,7 +31,7 @@ use std::{net, thread, time}; #[test] #[ignore] fn client_unauthenticated() { - let _server = KillOnDrop( + let _server = KillOnDrop(Some( Command::new("openssl") .args([ "s_server", @@ -49,7 +50,7 @@ fn client_unauthenticated() { .env("LD_LIBRARY_PATH", "") .spawn() .expect("failed to start openssl s_server"), - ); + )); wait_for_port(4443); @@ -125,7 +126,7 @@ fn client_unauthenticated() { #[test] #[ignore] fn client_auth() { - let _server = KillOnDrop( + let _server = KillOnDrop(Some( Command::new("openssl") .args([ "s_server", @@ -148,7 +149,7 @@ fn client_auth() { .env("LD_LIBRARY_PATH", "") .spawn() .expect("failed to start openssl s_server"), - ); + )); wait_for_port(4444); @@ -269,12 +270,77 @@ fn ciphers() { assert_eq!(openssl_output, rustls_output); } -struct KillOnDrop(Child); +#[test] +#[ignore] +fn server() { + fn curl() { + Command::new("curl") + .env("LD_LIBRARY_PATH", "") + .args([ + "-v", + "--cacert", + "test-ca/rsa/ca.cert", + "https://localhost:5555/", + ]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + } + + let mut openssl_server = KillOnDrop(Some( + Command::new("tests/maybe-valgrind.sh") + .env("LD_LIBRARY_PATH", "") + .args([ + "target/server", + "5555", + "test-ca/rsa/server.key", + "test-ca/rsa/server.cert", + "unauth", + ]) + .stdout(Stdio::piped()) + .spawn() + .unwrap(), + )); + wait_for_stdout(openssl_server.0.as_mut().unwrap(), b"listening\n"); + curl(); + + let openssl_output = print_output(openssl_server.take_inner().wait_with_output().unwrap()); + + let mut rustls_server = KillOnDrop(Some( + Command::new("tests/maybe-valgrind.sh") + .args([ + "target/server", + "5555", + "test-ca/rsa/server.key", + "test-ca/rsa/server.cert", + "unauth", + ]) + .stdout(Stdio::piped()) + .spawn() + .unwrap(), + )); + wait_for_stdout(rustls_server.0.as_mut().unwrap(), b"listening\n"); + curl(); + + let rustls_output = print_output(rustls_server.take_inner().wait_with_output().unwrap()); + assert_eq!(openssl_output, rustls_output); +} + +struct KillOnDrop(Option); + +impl KillOnDrop { + fn take_inner(&mut self) -> Child { + self.0.take().unwrap() + } +} impl Drop for KillOnDrop { fn drop(&mut self) { - self.0.kill().expect("failed to kill subprocess"); - self.0.wait().expect("failed to wait for killed subprocess"); + if let Some(mut child) = self.0.take() { + child.kill().expect("failed to kill subprocess"); + child.wait().expect("failed to wait for killed subprocess"); + } } } @@ -306,3 +372,25 @@ fn wait_for_port(port: u16) -> Option<()> { } } } + +/// Read from the `Child`'s `stdout` until the string `expected` is seen. +/// +/// To ensure this function can be used several times in succession +/// on a given `Child`, this must not read bytes from its `stdout` +/// that appear after `expected`. +fn wait_for_stdout(stream: &mut Child, expected: &[u8]) { + let stdout = stream.stdout.as_mut().unwrap(); + + let mut buffer = Vec::with_capacity(1024); + + loop { + let mut input = [0u8]; + let new = stdout.read(&mut input).unwrap(); + assert_eq!(new, 1); + buffer.push(input[0]); + + if buffer.ends_with(expected) { + return; + } + } +} diff --git a/rustls-libssl/tests/server.c b/rustls-libssl/tests/server.c new file mode 100644 index 0000000..cc86dc2 --- /dev/null +++ b/rustls-libssl/tests/server.c @@ -0,0 +1,185 @@ +/** + * Simple server test program. + * + * Listens on the given port, and accepts one connection on it. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static int trace(int rc, const char *str) { + printf("%s: %d\n", str, rc); + return rc; +} + +#define TRACE(fn) trace((fn), #fn) + +static void hexdump(const char *label, const void *buf, int n) { + const uint8_t *ubuf = (const uint8_t *)buf; + printf("%s (%d bytes): ", label, n); + for (int i = 0; i < n; i++) { + printf("%02x", ubuf[i]); + } + printf("\n"); +} + +static void dump_openssl_error_stack(void) { + if (ERR_peek_error() != 0) { + printf("openssl error: %08lx\n", ERR_peek_error()); + ERR_print_errors_fp(stderr); + } +} + +static void state(const SSL *s) { + OSSL_HANDSHAKE_STATE st = SSL_get_state(s); + printf("state: %d (before:%d, init:%d, fin:%d)\n", st, SSL_in_before(s), + SSL_in_init(s), SSL_is_init_finished(s)); +} + +int main(int argc, char **argv) { + if (argc != 5) { + printf("%s |unauth\n\n", + argv[0]); + return 1; + } + + const char *port = argv[1], *keyfile = argv[2], *certfile = argv[3], + *cacert = argv[4]; + + int listener = TRACE(socket(AF_INET, SOCK_STREAM, 0)); + struct sockaddr_in us, them; + memset(&us, 0, sizeof(us)); + us.sin_family = AF_INET; + us.sin_addr.s_addr = htonl(INADDR_ANY); + us.sin_port = htons(atoi(port)); + TRACE(bind(listener, (struct sockaddr *)&us, sizeof(us))); + TRACE(listen(listener, 5)); + printf("listening\n"); + fflush(stdout); + socklen_t them_len = sizeof(them); + int sock = TRACE(accept(listener, (struct sockaddr *)&them, &them_len)); + + TRACE(OPENSSL_init_ssl(0, NULL)); + dump_openssl_error_stack(); + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); + dump_openssl_error_stack(); + if (strcmp(cacert, "unauth") != 0) { + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + dump_openssl_error_stack(); + TRACE(SSL_CTX_load_verify_file(ctx, cacert)); + dump_openssl_error_stack(); + } else { + printf("client auth disabled\n"); + } + + X509 *server_cert = NULL; + EVP_PKEY *server_key = NULL; + TRACE(SSL_CTX_use_certificate_chain_file(ctx, certfile)); + TRACE(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM)); + server_key = SSL_CTX_get0_privatekey(ctx); + server_cert = SSL_CTX_get0_certificate(ctx); + + TRACE(SSL_CTX_set_alpn_protos(ctx, (const uint8_t *)"\x08http/1.1", 9)); + dump_openssl_error_stack(); + + SSL *ssl = SSL_new(ctx); + dump_openssl_error_stack(); + printf("SSL_new: SSL_get_privatekey %s SSL_CTX_get0_privatekey\n", + SSL_get_privatekey(ssl) == server_key ? "same as" : "differs to"); + printf("SSL_new: SSL_get_certificate %s SSL_CTX_get0_certificate\n", + SSL_get_certificate(ssl) == server_cert ? "same as" : "differs to"); + state(ssl); + TRACE(SSL_set_fd(ssl, sock)); + dump_openssl_error_stack(); + state(ssl); + TRACE(SSL_accept(ssl)); + dump_openssl_error_stack(); + state(ssl); + printf("SSL_accept: SSL_get_privatekey %s SSL_CTX_get0_privatekey\n", + SSL_get_privatekey(ssl) == server_key ? "same as" : "differs to"); + printf("SSL_accept: SSL_get_certificate %s SSL_CTX_get0_certificate\n", + SSL_get_certificate(ssl) == server_cert ? "same as" : "differs to"); + + // check the alpn (also sees that SSL_accept completed handshake) + const uint8_t *alpn_ptr = NULL; + unsigned int alpn_len = 0; + SSL_get0_alpn_selected(ssl, &alpn_ptr, &alpn_len); + hexdump("alpn", alpn_ptr, alpn_len); + + printf("version: %s\n", SSL_get_version(ssl)); + printf("verify-result: %ld\n", SSL_get_verify_result(ssl)); + printf("cipher: %s\n", SSL_CIPHER_standard_name(SSL_get_current_cipher(ssl))); + + // check the peer certificate and chain + X509 *cert = SSL_get1_peer_certificate(ssl); + if (cert) { + char *name = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + printf("client subject: %s\n", name); + free(name); + } else { + printf("client cert absent\n"); + } + X509_free(cert); + + STACK_OF(X509) *chain = SSL_get_peer_cert_chain(ssl); + if (chain) { + printf("%d certs in client chain\n", sk_X509_num(chain)); + for (int i = 0; i < sk_X509_num(chain); i++) { + X509 *cert = sk_X509_value(chain, i); + char *name = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + printf(" %d: %s\n", i, name); + free(name); + } + } else { + printf("client cert chain absent\n"); + } + + // read "request" + while (1) { + char buf[128] = {0}; + int rd = TRACE(SSL_read(ssl, buf, sizeof(buf))); + dump_openssl_error_stack(); + TRACE(SSL_pending(ssl)); + dump_openssl_error_stack(); + TRACE(SSL_has_pending(ssl)); + dump_openssl_error_stack(); + if (rd == 0) { + printf("nothing read\n"); + break; + } else { + hexdump("result", buf, rd); + } + state(ssl); + + if (!TRACE(SSL_has_pending(ssl))) { + break; + } + } + + // write some data and close + const char response[] = "HTTP/1.0 200 OK\r\n\r\nhello\r\n"; + int wr = TRACE(SSL_write(ssl, response, sizeof(response) - 1)); + dump_openssl_error_stack(); + assert(wr == sizeof(response) - 1); + TRACE(SSL_shutdown(ssl)); + dump_openssl_error_stack(); + + close(sock); + close(listener); + SSL_free(ssl); + SSL_CTX_free(ctx); + + printf("PASS\n\n"); + return 0; +} From 37653e86679311e8dc925b481ca0c2b96c02eba7 Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Wed, 10 Apr 2024 17:02:30 +0100 Subject: [PATCH 16/19] `SSL_{CTX_,}ctrl`: hoist locking outside of match --- rustls-libssl/src/entry.rs | 106 +++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index f609c94..4f27f3b 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -159,34 +159,36 @@ entry! { } entry! { - pub fn _SSL_CTX_ctrl( - _ctx: *mut SSL_CTX, - cmd: c_int, - larg: c_long, - _parg: *mut c_void, - ) -> c_long { - match SslCtrl::try_from(cmd) { - Ok(SslCtrl::Mode) => { - log::warn!("unimplemented SSL_CTX_set_mode()"); - 0 - } - Ok(SslCtrl::SetMsgCallbackArg) => { - log::warn!("unimplemented SSL_CTX_set_msg_callback_arg()"); - 0 - } - Ok(SslCtrl::SetMaxProtoVersion) => { - log::warn!("unimplemented SSL_CTX_set_max_proto_version()"); - 1 - } - Ok(SslCtrl::SetTlsExtHostname) => { - // not a defined operation in the OpenSSL API - 0 - } - Err(()) => { - log::warn!("unimplemented _SSL_CTX_ctrl(..., {cmd}, {larg}, ...)"); - 0 + pub fn _SSL_CTX_ctrl(ctx: *mut SSL_CTX, cmd: c_int, larg: c_long, parg: *mut c_void) -> c_long { + let ctx = try_clone_arc!(ctx); + + let result = if let Ok(mut _inner) = ctx.lock() { + match SslCtrl::try_from(cmd) { + Ok(SslCtrl::Mode) => { + log::warn!("unimplemented SSL_CTX_set_mode()"); + 0 + } + Ok(SslCtrl::SetMsgCallbackArg) => { + log::warn!("unimplemented SSL_CTX_set_msg_callback_arg()"); + 0 + } + Ok(SslCtrl::SetMaxProtoVersion) => { + log::warn!("unimplemented SSL_CTX_set_max_proto_version()"); + 1 + } + Ok(SslCtrl::SetTlsExtHostname) => { + // not a defined operation in the OpenSSL API + 0 + } + Err(()) => { + log::warn!("unimplemented _SSL_CTX_ctrl(..., {cmd}, {larg}, ...)"); + 0 + } } - } + } else { + 0 + }; + result } } @@ -551,31 +553,33 @@ entry! { pub fn _SSL_ctrl(ssl: *mut SSL, cmd: c_int, larg: c_long, parg: *mut c_void) -> c_long { let ssl = try_clone_arc!(ssl); - match SslCtrl::try_from(cmd) { - Ok(SslCtrl::Mode) => { - log::warn!("unimplemented SSL_set_mode()"); - 0 - } - Ok(SslCtrl::SetMsgCallbackArg) => { - log::warn!("unimplemented SSL_set_msg_callback_arg()"); - 0 - } - Ok(SslCtrl::SetMaxProtoVersion) => { - log::warn!("unimplemented SSL_set_max_proto_version()"); - 1 - } - Ok(SslCtrl::SetTlsExtHostname) => { - let hostname = try_str!(parg as *const c_char); - ssl.lock() - .ok() - .map(|mut ssl| ssl.set_sni_hostname(hostname)) - .unwrap_or_default() as c_long - } - Err(()) => { - log::warn!("unimplemented _SSL_ctrl(..., {cmd}, {larg}, ...)"); - 0 + let result = if let Ok(mut inner) = ssl.lock() { + match SslCtrl::try_from(cmd) { + Ok(SslCtrl::Mode) => { + log::warn!("unimplemented SSL_set_mode()"); + 0 + } + Ok(SslCtrl::SetMsgCallbackArg) => { + log::warn!("unimplemented SSL_set_msg_callback_arg()"); + 0 + } + Ok(SslCtrl::SetMaxProtoVersion) => { + log::warn!("unimplemented SSL_set_max_proto_version()"); + 1 + } + Ok(SslCtrl::SetTlsExtHostname) => { + let hostname = try_str!(parg as *const c_char); + inner.set_sni_hostname(hostname) as c_long + } + Err(()) => { + log::warn!("unimplemented _SSL_ctrl(..., {cmd}, {larg}, ...)"); + 0 + } } - } + } else { + 0 + }; + result } } From 5715fe269ddeb30a855d44b62117817329a9ce46 Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Wed, 10 Apr 2024 17:04:04 +0100 Subject: [PATCH 17/19] Implement `SSL_set[01]_chain` & `SSL_clear_chain_certs` Plus the `SSL_CTX` equivalents. --- rustls-libssl/src/entry.rs | 38 ++++++++++++++++++++++++++++++++++++-- rustls-libssl/src/x509.rs | 31 +++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index 4f27f3b..76ae738 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -22,7 +22,7 @@ use crate::ffi::{ free_arc, str_from_cstring, to_arc_mut_ptr, try_clone_arc, try_from, try_mut_slice_int, try_ref_from_ptr, try_slice, try_slice_int, try_str, Castable, OwnershipArc, OwnershipRef, }; -use crate::x509::{load_certs, OwnedX509}; +use crate::x509::{load_certs, OwnedX509, OwnedX509Stack}; use crate::{HandshakeState, ShutdownResult}; /// Makes a entry function definition. @@ -162,7 +162,7 @@ entry! { pub fn _SSL_CTX_ctrl(ctx: *mut SSL_CTX, cmd: c_int, larg: c_long, parg: *mut c_void) -> c_long { let ctx = try_clone_arc!(ctx); - let result = if let Ok(mut _inner) = ctx.lock() { + let result = if let Ok(mut inner) = ctx.lock() { match SslCtrl::try_from(cmd) { Ok(SslCtrl::Mode) => { log::warn!("unimplemented SSL_CTX_set_mode()"); @@ -180,6 +180,23 @@ entry! { // not a defined operation in the OpenSSL API 0 } + Ok(SslCtrl::SetChain) => { + let chain = if parg.is_null() { + // this is `SSL_CTX_clear_chain_certs` + vec![] + } else { + match larg { + // this is `SSL_CTX_set1_chain` (incs ref) + 1 => OwnedX509Stack::new_copy(parg as *mut stack_st_X509).to_rustls(), + // this is `SSL_CTX_set0_chain` (retain ref) + _ => OwnedX509Stack::new(parg as *mut stack_st_X509).to_rustls(), + } + }; + + inner.stage_certificate_chain(chain); + C_INT_SUCCESS as i64 + } + Err(()) => { log::warn!("unimplemented _SSL_CTX_ctrl(..., {cmd}, {larg}, ...)"); 0 @@ -571,6 +588,22 @@ entry! { let hostname = try_str!(parg as *const c_char); inner.set_sni_hostname(hostname) as c_long } + Ok(SslCtrl::SetChain) => { + let chain = if parg.is_null() { + // this is `SSL_clear_chain_certs` + vec![] + } else { + match larg { + // this is `SSL_set1_chain` (incs ref) + 1 => OwnedX509Stack::new_copy(parg as *mut stack_st_X509).to_rustls(), + // this is `SSL_set0_chain` (retain ref) + _ => OwnedX509Stack::new(parg as *mut stack_st_X509).to_rustls(), + } + }; + + inner.stage_certificate_chain(chain); + C_INT_SUCCESS as i64 + } Err(()) => { log::warn!("unimplemented _SSL_ctrl(..., {cmd}, {larg}, ...)"); 0 @@ -1231,6 +1264,7 @@ num_enum! { Mode = 33, SetMsgCallbackArg = 16, SetTlsExtHostname = 55, + SetChain = 88, SetMaxProtoVersion = 124, } } diff --git a/rustls-libssl/src/x509.rs b/rustls-libssl/src/x509.rs index 55d4fff..10d96e2 100644 --- a/rustls-libssl/src/x509.rs +++ b/rustls-libssl/src/x509.rs @@ -28,6 +28,35 @@ impl OwnedX509Stack { } } + /// Make one by adopting ownership of an existing pointer. + pub fn new(raw: *mut stack_st_X509) -> Self { + Self { raw } + } + + /// Make one by copying existing stack. + pub fn new_copy(other: *const stack_st_X509) -> Self { + let mut ret = Self::empty(); + + let len = match unsafe { OPENSSL_sk_num(other as *const OPENSSL_STACK) } { + -1 => 0, + x => x as usize, + }; + + for index in 0..len { + let item_ptr = unsafe { + OPENSSL_sk_value(other as *const OPENSSL_STACK, index as c_int) as *mut X509 + }; + // item_ptr belongs to caller. + let item = OwnedX509::new(item_ptr); + // item belongs to `OwnedX509` -- ensure caller's ref is not stolen + item.up_ref(); + // `ret` takes its own ref + ret.push(&item); + } + + ret + } + pub fn from_rustls(certs: &Vec>) -> Result { let mut r = Self::empty(); for c in certs { @@ -64,7 +93,6 @@ impl OwnedX509Stack { self.borrowed_item(0) } - #[allow(dead_code)] // delete me later if unused /// Convert contents to rustls's representation. /// /// This copies the whole chain. @@ -80,7 +108,6 @@ impl OwnedX509Stack { r } - #[allow(dead_code)] // delete me later if unused /// Owned reference of item at `index`. fn item(&self, index: usize) -> OwnedX509 { let donate = self.borrowed_item(index); From 0a50edfcf4debb65701c146fc7e8876e53132a2d Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Wed, 10 Apr 2024 16:58:11 +0100 Subject: [PATCH 18/19] Implement `SSL_use_PrivateKey` & `SSL_use_certificate` --- rustls-libssl/MATRIX.md | 4 ++-- rustls-libssl/build.rs | 2 ++ rustls-libssl/src/entry.rs | 43 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/rustls-libssl/MATRIX.md b/rustls-libssl/MATRIX.md index 4e4288e..6d9dbf8 100644 --- a/rustls-libssl/MATRIX.md +++ b/rustls-libssl/MATRIX.md @@ -485,14 +485,14 @@ | `SSL_test_functions` [^unit_test] | | | | | `SSL_trace` [^ssl_trace] | | | | | `SSL_up_ref` | | | :white_check_mark: | -| `SSL_use_PrivateKey` | | :white_check_mark: | | +| `SSL_use_PrivateKey` | | :white_check_mark: | :white_check_mark: | | `SSL_use_PrivateKey_ASN1` | | | | | `SSL_use_PrivateKey_file` | | | | | `SSL_use_RSAPrivateKey` [^deprecatedin_3_0] | | | | | `SSL_use_RSAPrivateKey_ASN1` [^deprecatedin_3_0] | | | | | `SSL_use_RSAPrivateKey_file` [^deprecatedin_3_0] | | | | | `SSL_use_cert_and_key` | | | | -| `SSL_use_certificate` | | :white_check_mark: | | +| `SSL_use_certificate` | | :white_check_mark: | :white_check_mark: | | `SSL_use_certificate_ASN1` | | | | | `SSL_use_certificate_chain_file` | | | | | `SSL_use_certificate_file` | | | | diff --git a/rustls-libssl/build.rs b/rustls-libssl/build.rs index 9aa5635..905d4e5 100644 --- a/rustls-libssl/build.rs +++ b/rustls-libssl/build.rs @@ -151,6 +151,8 @@ const ENTRYPOINTS: &[&str] = &[ "SSL_set_SSL_CTX", "SSL_shutdown", "SSL_up_ref", + "SSL_use_certificate", + "SSL_use_PrivateKey", "SSL_want", "SSL_write", "TLS_client_method", diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index 76ae738..c833168 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -1111,6 +1111,49 @@ entry! { } } +entry! { + pub fn _SSL_use_certificate(ssl: *mut SSL, x: *mut X509) -> c_int { + let ssl = try_clone_arc!(ssl); + + if x.is_null() { + return Error::null_pointer().raise().into(); + } + + let x509 = OwnedX509::new_incref(x); + let ee = CertificateDer::from(x509.der_bytes()); + + match ssl + .lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ssl| ssl.stage_certificate_end_entity(ee)) + { + Err(e) => e.raise().into(), + Ok(()) => C_INT_SUCCESS, + } + } +} + +entry! { + pub fn _SSL_use_PrivateKey(ssl: *mut SSL, pkey: *mut EVP_PKEY) -> c_int { + let ssl = try_clone_arc!(ssl); + + if pkey.is_null() { + return Error::null_pointer().raise().into(); + } + + let pkey = EvpPkey::new_incref(pkey); + + match ssl + .lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ssl| ssl.commit_private_key(pkey)) + { + Err(e) => e.raise().into(), + Ok(()) => C_INT_SUCCESS, + } + } +} + impl Castable for SSL { type Ownership = OwnershipArc; type RustType = Mutex; From 410be052ca44729cbeedad362e927bf84481c4cf Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Mon, 15 Apr 2024 15:11:06 +0100 Subject: [PATCH 19/19] Factor out commonality between client.c & server.c --- rustls-libssl/tests/client.c | 54 ++--------------------------- rustls-libssl/tests/helpers.h | 65 +++++++++++++++++++++++++++++++++++ rustls-libssl/tests/server.c | 54 ++--------------------------- 3 files changed, 69 insertions(+), 104 deletions(-) create mode 100644 rustls-libssl/tests/helpers.h diff --git a/rustls-libssl/tests/client.c b/rustls-libssl/tests/client.c index b27503c..4d57886 100644 --- a/rustls-libssl/tests/client.c +++ b/rustls-libssl/tests/client.c @@ -13,38 +13,10 @@ #include #include -#include #include #include -static int trace(int rc, const char *str) { - printf("%s: %d\n", str, rc); - return rc; -} - -#define TRACE(fn) trace((fn), #fn) - -static void hexdump(const char *label, const void *buf, int n) { - const uint8_t *ubuf = (const uint8_t *)buf; - printf("%s (%d bytes): ", label, n); - for (int i = 0; i < n; i++) { - printf("%02x", ubuf[i]); - } - printf("\n"); -} - -static void dump_openssl_error_stack(void) { - if (ERR_peek_error() != 0) { - printf("openssl error: %08lx\n", ERR_peek_error()); - ERR_print_errors_fp(stderr); - } -} - -static void state(const SSL *s) { - OSSL_HANDSHAKE_STATE st = SSL_get_state(s); - printf("state: %d (before:%d, init:%d, fin:%d)\n", st, SSL_in_before(s), - SSL_in_init(s), SSL_is_init_finished(s)); -} +#include "helpers.h" int main(int argc, char **argv) { if (argc != 4 && argc != 6) { @@ -128,29 +100,7 @@ int main(int argc, char **argv) { printf("verify-result: %ld\n", SSL_get_verify_result(ssl)); printf("cipher: %s\n", SSL_CIPHER_standard_name(SSL_get_current_cipher(ssl))); - // check the peer certificate and chain - X509 *cert = SSL_get1_peer_certificate(ssl); - if (cert) { - char *name = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); - printf("server subject: %s\n", name); - free(name); - } else { - printf("server cert absent\n"); - } - X509_free(cert); - - STACK_OF(X509) *chain = SSL_get_peer_cert_chain(ssl); - if (chain) { - printf("%d certs in server chain\n", sk_X509_num(chain)); - for (int i = 0; i < sk_X509_num(chain); i++) { - X509 *cert = sk_X509_value(chain, i); - char *name = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); - printf(" %d: %s\n", i, name); - free(name); - } - } else { - printf("server cert chain absent\n"); - } + show_peer_certificate("server", ssl); if (getenv("NO_ECHO")) { printf("NO_ECHO set, skipping echo test\n"); diff --git a/rustls-libssl/tests/helpers.h b/rustls-libssl/tests/helpers.h new file mode 100644 index 0000000..4a9392a --- /dev/null +++ b/rustls-libssl/tests/helpers.h @@ -0,0 +1,65 @@ +#ifndef TESTS_COMMON_H +#define TESTS_COMMON_H + +#include + +#include +#include +#include + +static int trace(int rc, const char *str) { + printf("%s: %d\n", str, rc); + return rc; +} + +#define TRACE(fn) trace((fn), #fn) + +static void hexdump(const char *label, const void *buf, int n) { + const uint8_t *ubuf = (const uint8_t *)buf; + printf("%s (%d bytes): ", label, n); + for (int i = 0; i < n; i++) { + printf("%02x", ubuf[i]); + } + printf("\n"); +} + +static void dump_openssl_error_stack(void) { + if (ERR_peek_error() != 0) { + printf("openssl error: %08lx\n", ERR_peek_error()); + ERR_print_errors_fp(stderr); + } +} + +static void state(const SSL *s) { + OSSL_HANDSHAKE_STATE st = SSL_get_state(s); + printf("state: %d (before:%d, init:%d, fin:%d)\n", st, SSL_in_before(s), + SSL_in_init(s), SSL_is_init_finished(s)); +} + +static void show_peer_certificate(const char *peer_name, const SSL *ssl) { + // check the peer certificate and chain + X509 *cert = SSL_get1_peer_certificate(ssl); + if (cert) { + char *name = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + printf("%s subject: %s\n", peer_name, name); + free(name); + } else { + printf("%s cert absent\n", peer_name); + } + X509_free(cert); + + STACK_OF(X509) *chain = SSL_get_peer_cert_chain(ssl); + if (chain) { + printf("%d certs in %s chain\n", sk_X509_num(chain), peer_name); + for (int i = 0; i < sk_X509_num(chain); i++) { + X509 *cert = sk_X509_value(chain, i); + char *name = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + printf(" %d: %s\n", i, name); + free(name); + } + } else { + printf("%s cert chain absent\n", peer_name); + } +} + +#endif // TESTS_COMMON_H diff --git a/rustls-libssl/tests/server.c b/rustls-libssl/tests/server.c index cc86dc2..3fbf152 100644 --- a/rustls-libssl/tests/server.c +++ b/rustls-libssl/tests/server.c @@ -14,38 +14,10 @@ #include #include -#include #include #include -static int trace(int rc, const char *str) { - printf("%s: %d\n", str, rc); - return rc; -} - -#define TRACE(fn) trace((fn), #fn) - -static void hexdump(const char *label, const void *buf, int n) { - const uint8_t *ubuf = (const uint8_t *)buf; - printf("%s (%d bytes): ", label, n); - for (int i = 0; i < n; i++) { - printf("%02x", ubuf[i]); - } - printf("\n"); -} - -static void dump_openssl_error_stack(void) { - if (ERR_peek_error() != 0) { - printf("openssl error: %08lx\n", ERR_peek_error()); - ERR_print_errors_fp(stderr); - } -} - -static void state(const SSL *s) { - OSSL_HANDSHAKE_STATE st = SSL_get_state(s); - printf("state: %d (before:%d, init:%d, fin:%d)\n", st, SSL_in_before(s), - SSL_in_init(s), SSL_is_init_finished(s)); -} +#include "helpers.h" int main(int argc, char **argv) { if (argc != 5) { @@ -121,29 +93,7 @@ int main(int argc, char **argv) { printf("verify-result: %ld\n", SSL_get_verify_result(ssl)); printf("cipher: %s\n", SSL_CIPHER_standard_name(SSL_get_current_cipher(ssl))); - // check the peer certificate and chain - X509 *cert = SSL_get1_peer_certificate(ssl); - if (cert) { - char *name = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); - printf("client subject: %s\n", name); - free(name); - } else { - printf("client cert absent\n"); - } - X509_free(cert); - - STACK_OF(X509) *chain = SSL_get_peer_cert_chain(ssl); - if (chain) { - printf("%d certs in client chain\n", sk_X509_num(chain)); - for (int i = 0; i < sk_X509_num(chain); i++) { - X509 *cert = sk_X509_value(chain, i); - char *name = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); - printf(" %d: %s\n", i, name); - free(name); - } - } else { - printf("client cert chain absent\n"); - } + show_peer_certificate("client", ssl); // read "request" while (1) {