diff --git a/.github/workflows/libssl.yaml b/.github/workflows/libssl.yaml index 789e771..247f4f4 100644 --- a/.github/workflows/libssl.yaml +++ b/.github/workflows/libssl.yaml @@ -48,6 +48,8 @@ jobs: - uses: actions/checkout@v4 with: persist-credentials: false + - name: Install rust toolchain + uses: dtolnay/rust-toolchain@stable - name: Install valgrind run: sudo apt-get update && sudo apt-get install -y valgrind - name: Install build dependencies @@ -84,9 +86,8 @@ jobs: with: persist-credentials: false - name: Install rust toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.67.1 components: rustfmt - name: Check Rust formatting run: cargo fmt --all -- --check diff --git a/rustls-libssl/Cargo.lock b/rustls-libssl/Cargo.lock index 4e8f674..9b7c53c 100644 --- a/rustls-libssl/Cargo.lock +++ b/rustls-libssl/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "cc" version = "1.0.86" @@ -172,6 +178,17 @@ dependencies = [ "log", "openssl-sys", "rustls", + "rustls-pemfile", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c333bb734fcdedcea57de1602543590f545f127dc8b533324318fd492c5c70b" +dependencies = [ + "base64", + "rustls-pki-types", ] [[package]] diff --git a/rustls-libssl/Cargo.toml b/rustls-libssl/Cargo.toml index 2fac7a5..c81317d 100644 --- a/rustls-libssl/Cargo.toml +++ b/rustls-libssl/Cargo.toml @@ -3,6 +3,7 @@ name = "rustls-libssl" version = "0.1.0" edition = "2021" build = "build.rs" +rust-version = "1.77" [lib] name = "ssl" @@ -13,3 +14,4 @@ env_logger = "0.10" log = "0.4" openssl-sys = "0.9.98" rustls = "0.22" +rustls-pemfile = "2" diff --git a/rustls-libssl/Makefile b/rustls-libssl/Makefile index 0a2248e..52bcee8 100644 --- a/rustls-libssl/Makefile +++ b/rustls-libssl/Makefile @@ -19,7 +19,7 @@ ifneq (,$(TARGET)) CARGOFLAGS += --target $(TARGET) endif -all: target/$(PROFILE)/libssl.so.3 +all: target/ciphers target/client target/constants target/$(PROFILE)/libssl.so.3 test: all ${CARGO} test --locked @@ -36,6 +36,18 @@ target/$(PROFILE)/libssl.so.3: target/$(PROFILE)/libssl.so target/$(PROFILE)/libssl.so: *.rs src/*.rs Cargo.toml ${CARGO} build $(CARGOFLAGS) +target/%.o: tests/%.c | target + $(CC) -o $@ -c $< $(CFLAGS) + +target/ciphers: target/ciphers.o + $(CC) -o $@ $^ $(LDFLAGS) $(shell pkg-config --libs openssl) + +target/client: target/client.o + $(CC) -o $@ $^ $(LDFLAGS) $(shell pkg-config --libs openssl) + +target/constants: target/constants.o + $(CC) -o $@ $^ $(LDFLAGS) $(shell pkg-config --libs openssl) + clean: rm -rf target diff --git a/rustls-libssl/admin/format b/rustls-libssl/admin/format index aecb4e6..177280c 100755 --- a/rustls-libssl/admin/format +++ b/rustls-libssl/admin/format @@ -10,7 +10,9 @@ # `entry!` with `mod entry`, and then restore it back afterwards. sed -i -e 's/^entry! {/mod entry {/g' src/entry.rs +sed -i -e 's/^entry_stub! {/mod entry_stub {/g' src/entry.rs cargo fmt "$@" rc=$? sed -i -e 's/^mod entry {/entry! {/g' src/entry.rs +sed -i -e 's/^mod entry_stub {/entry_stub! {/g' src/entry.rs exit $rc diff --git a/rustls-libssl/build.rs b/rustls-libssl/build.rs index 500758f..3889cf6 100644 --- a/rustls-libssl/build.rs +++ b/rustls-libssl/build.rs @@ -42,13 +42,91 @@ fn write_version_file() -> String { } const ENTRYPOINTS: &[&str] = &[ + "BIO_f_ssl", "OPENSSL_init_ssl", + "SSL_alert_desc_string", + "SSL_alert_desc_string_long", + "SSL_CIPHER_description", + "SSL_CIPHER_find", + "SSL_CIPHER_get_bits", + "SSL_CIPHER_get_id", + "SSL_CIPHER_get_name", + "SSL_CIPHER_get_protocol_id", + "SSL_CIPHER_get_version", + "SSL_CIPHER_standard_name", + "SSL_clear_options", + "SSL_connect", + "SSL_ctrl", + "SSL_CTX_add_client_CA", + "SSL_CTX_check_private_key", + "SSL_CTX_clear_options", + "SSL_CTX_ctrl", "SSL_CTX_free", + "SSL_CTX_get_cert_store", + "SSL_CTX_get_ex_data", + "SSL_CTX_get_options", + "SSL_CTX_load_verify_dir", + "SSL_CTX_load_verify_file", "SSL_CTX_new", + "SSL_CTX_sess_set_new_cb", + "SSL_CTX_set_alpn_protos", + "SSL_CTX_set_cipher_list", + "SSL_CTX_set_ciphersuites", + "SSL_CTX_set_default_passwd_cb", + "SSL_CTX_set_default_passwd_cb_userdata", + "SSL_CTX_set_ex_data", + "SSL_CTX_set_keylog_callback", + "SSL_CTX_set_msg_callback", + "SSL_CTX_set_next_proto_select_cb", + "SSL_CTX_set_options", + "SSL_CTX_set_post_handshake_auth", + "SSL_CTX_set_srp_password", + "SSL_CTX_set_srp_username", + "SSL_CTX_set_verify", "SSL_CTX_up_ref", + "SSL_CTX_use_certificate", + "SSL_CTX_use_certificate_chain_file", + "SSL_CTX_use_certificate_file", + "SSL_CTX_use_PrivateKey", + "SSL_CTX_use_PrivateKey_file", "SSL_free", + "SSL_get0_alpn_selected", + "SSL_get0_peer_certificate", + "SSL_get0_verified_chain", + "SSL_get1_peer_certificate", + "SSL_get_certificate", + "SSL_get_current_cipher", + "SSL_get_error", + "SSL_get_ex_data", + "SSL_get_options", + "SSL_get_peer_cert_chain", + "SSL_get_privatekey", + "SSL_get_shutdown", + "SSL_get_verify_result", + "SSL_get_version", + "SSL_has_pending", + "SSL_is_server", "SSL_new", + "SSL_pending", + "SSL_read", + "SSL_SESSION_free", + "SSL_set0_rbio", + "SSL_set0_wbio", + "SSL_set1_host", + "SSL_set_accept_state", + "SSL_set_alpn_protos", + "SSL_set_bio", + "SSL_set_connect_state", + "SSL_set_ex_data", + "SSL_set_fd", + "SSL_set_options", + "SSL_set_post_handshake_auth", + "SSL_set_session", + "SSL_set_shutdown", + "SSL_shutdown", "SSL_up_ref", + "SSL_want", + "SSL_write", "TLS_client_method", "TLS_method", "TLS_server_method", diff --git a/rustls-libssl/src/bio.rs b/rustls-libssl/src/bio.rs new file mode 100644 index 0000000..b7236c6 --- /dev/null +++ b/rustls-libssl/src/bio.rs @@ -0,0 +1,329 @@ +use core::ffi::{c_char, c_int, c_long, c_void, CStr}; +use std::io; + +// nb. cannot use any BIO types from openssl_sys: it doesn't +// have the internal type for BIO_METHOD, and once we provide +// it here their opaque type doesn't match ours. + +/// Safe, owning wrapper around an OpenSSL BIO pair. +/// +/// This owns references to both BIOs, even if they +/// are the same pointer. That means `drop()` can +/// be straight-forward. +pub struct Bio { + read: *mut BIO, + write: *mut BIO, +} + +impl Bio { + /// Use a pre-existing file descriptor. + /// + /// Does not (and cannot) validate the file descriptor. + pub fn new_fd_no_close(fd: c_int) -> Self { + let (read, write) = unsafe { + let bio = BIO_new_fd(fd, 0); + BIO_up_ref(bio); + (bio, bio) + }; + Self { read, write } + } + + /// Use a pair of raw BIO pointers. + /// + /// 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! + 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 } + } + + /// Update this object with a pair of raw BIO pointers. + /// + pub fn update(&mut self, rbio: Option<*mut BIO>, wbio: Option<*mut BIO>) { + match (rbio, wbio) { + (Some(rbio), Some(wbio)) => { + // See `SSL_set_bio` for the overcomplex ownership rules when both + // `rbio` and `wbio` are `Some`: + // + // + // + // If neither the rbio or wbio have changed from their + // previous values then nothing is done. + if rbio == self.read && wbio == self.write { + return; + } + + // If the rbio and wbio parameters are different and both are + // different to their previously set values then one reference + // is consumed for the rbio and one reference is consumed for + // the wbio. + if rbio != wbio && rbio != self.read && wbio != self.write { + self.set_read(rbio); + self.set_write(wbio); + return; + } + + // If the rbio and wbio parameters are the same and the rbio + // is not the same as the previously set value then one reference + // is consumed for the rbio. + if rbio == wbio && rbio != self.read { + unsafe { + BIO_up_ref(rbio); + } + self.set_read(rbio); + self.set_write(wbio); + return; + } + + // If the rbio and wbio parameters are the same and the rbio + // is the same as the previously set value, then no additional + // references are consumed. + if rbio == wbio && rbio == self.read { + // (er, what about self.write though?) + return; + } + + // If the rbio and wbio parameters are different and the rbio + // is the same as the previously set value then one reference + // is consumed for the wbio and no references are consumed for + // the rbio. + if rbio != wbio && rbio == self.read { + self.set_write(wbio); + return; + } + + // If the rbio and wbio parameters are different and the wbio + // is the same as the previously set value and the old rbio and + // wbio values were the same as each other then one reference + // is consumed for the rbio and no references are consumed for + // the wbio. + if rbio != wbio && wbio == self.write && self.read == self.write { + self.set_read(rbio); + return; + } + + // If the rbio and wbio parameters are different and the wbio + // is the same as the previously set value and the old rbio and + // wbio values were different to each other, then one reference + // is consumed for the rbio and one reference is consumed for the wbio. + if rbio != wbio && wbio == self.write && self.read != self.write { + self.set_read(rbio); + self.set_write(wbio); + } + } + (Some(rbio), None) => { + self.set_read(rbio); + } + (None, Some(wbio)) => { + self.set_write(wbio); + } + (None, None) => {} + } + } + + /// Sets `write` to `wbio`. + /// + /// Frees the old `write` if needed. + /// Consumes the `wbio` reference unconditionally. + /// + /// `wbio` must be non-NULL. + fn set_write(&mut self, wbio: *mut BIO) { + if wbio != self.write { + unsafe { BIO_free_all(self.write) }; + self.write = wbio; + } else { + unsafe { BIO_free_all(wbio) }; + } + } + + /// Sets `read` to `rbio`. + /// + /// Frees the old `read` if needed. + /// Consumes the `rbio` reference unconditionally. + /// + /// `rbio` must be non-NULL. + fn set_read(&mut self, rbio: *mut BIO) { + if rbio != self.read { + unsafe { BIO_free_all(self.read) }; + self.read = rbio; + } else { + unsafe { BIO_free_all(rbio) }; + } + } + + pub fn read_would_block(&self) -> bool { + bio_should_retry(self.read) + } + + pub fn write_would_block(&self) -> bool { + bio_should_retry(self.write) + } +} + +impl io::Read for Bio { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut read_bytes = 0; + let rc = unsafe { + BIO_read_ex( + self.read, + buf.as_mut_ptr() as *mut c_void, + buf.len(), + &mut read_bytes, + ) + }; + + match rc { + 1 => Ok(read_bytes), + _ => { + if bio_in_eof(self.read) { + Ok(0) + } else if bio_should_retry(self.read) { + Err(io::ErrorKind::WouldBlock.into()) + } else { + Err(io::Error::other("BIO_read_ex failed")) + } + } + } + } +} + +impl io::Write for Bio { + fn write(&mut self, buf: &[u8]) -> io::Result { + let mut written_bytes = 0; + let rc = unsafe { + BIO_write_ex( + self.write, + buf.as_ptr() as *const c_void, + buf.len(), + &mut written_bytes, + ) + }; + + match rc { + 1 => Ok(written_bytes), + _ => Err(io::Error::other("BIO_write_ex failed")), + } + } + + fn flush(&mut self) -> io::Result<()> { + // nb. BIO_flush "in some cases it is used to signal EOF and + // that no more data will be written." so is not a good match. + Ok(()) + } +} + +impl Drop for Bio { + fn drop(&mut self) { + unsafe { + BIO_free_all(self.read); + BIO_free_all(self.write); + } + } +} + +static NAME: &CStr = c"ssl"; +const BIO_TYPE_SSL: i32 = 0x0200 | 7; + +pub static SSL_BIO_METHOD: bio_method_st = bio_method_st { + type_: BIO_TYPE_SSL, + name: NAME.as_ptr(), + bwrite: None, + bwrite_old: None, + bread: None, + bread_old: None, + bputs: None, + bgets: None, + ctrl: None, + create: None, + destroy: None, + callback_ctrl: None, +}; + +// This is a public interface between libcrypto and libssl, but is +// defined in `internal/bio.h`. Hmm. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct bio_method_st { + pub type_: c_int, + pub name: *const c_char, + pub bwrite: Option< + unsafe extern "C" fn( + arg1: *mut BIO, + arg2: *const c_char, + arg3: usize, + arg4: *mut usize, + ) -> c_int, + >, + pub bwrite_old: + Option c_int>, + pub bread: Option< + unsafe extern "C" fn( + arg1: *mut BIO, + arg2: *mut c_char, + arg3: usize, + arg4: *mut usize, + ) -> c_int, + >, + pub bread_old: + Option c_int>, + pub bputs: Option c_int>, + pub bgets: + Option c_int>, + pub ctrl: Option< + unsafe extern "C" fn( + arg1: *mut BIO, + arg2: c_int, + arg3: c_long, + arg4: *mut c_void, + ) -> c_long, + >, + pub create: Option c_int>, + pub destroy: Option c_int>, + pub callback_ctrl: + Option c_long>, +} + +unsafe impl Send for bio_method_st {} +unsafe impl Sync for bio_method_st {} + +#[allow(non_camel_case_types)] +pub type BIO_info_cb = + Option c_int>; + +#[repr(C)] +pub struct OpaqueBio { + _private: [u8; 0], +} + +#[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] +pub type BIO = OpaqueBio; +#[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] +pub type BIO_METHOD = bio_method_st; + +fn bio_should_retry(b: *const BIO) -> bool { + const BIO_SHOULD_RETRY: c_int = 0x08; + unsafe { BIO_test_flags(b, BIO_SHOULD_RETRY) != 0 } +} + +fn bio_in_eof(b: *const BIO) -> bool { + const BIO_IN_EOF: c_int = 0x800; + unsafe { BIO_test_flags(b, BIO_IN_EOF) != 0 } +} + +extern "C" { + fn BIO_new(meth: *const BIO_METHOD) -> *mut BIO; + fn BIO_free_all(b: *mut BIO); + fn BIO_new_fd(fd: c_int, close_flag: c_int) -> *mut BIO; + fn BIO_read_ex(b: *mut BIO, data: *mut c_void, dlen: usize, readbytes: *mut usize) -> c_int; + fn BIO_write_ex(b: *mut BIO, data: *const c_void, dlen: usize, written: *mut usize) -> c_int; + fn BIO_up_ref(b: *mut BIO) -> c_int; + fn BIO_test_flags(b: *const BIO, flags: c_int) -> c_int; + fn BIO_s_null() -> *const BIO_METHOD; +} diff --git a/rustls-libssl/src/constants.rs b/rustls-libssl/src/constants.rs new file mode 100644 index 0000000..9ec539e --- /dev/null +++ b/rustls-libssl/src/constants.rs @@ -0,0 +1,85 @@ +use core::ffi::{c_int, CStr}; + +use rustls::AlertDescription; + +pub fn alert_desc_to_long_string(value: c_int) -> &'static CStr { + match AlertDescription::from(value as u8) { + AlertDescription::CloseNotify => c"close notify", + AlertDescription::UnexpectedMessage => c"unexpected_message", + AlertDescription::BadRecordMac => c"bad record mac", + AlertDescription::DecryptionFailed => c"decryption failed", + AlertDescription::RecordOverflow => c"record overflow", + AlertDescription::DecompressionFailure => c"decompression failure", + AlertDescription::HandshakeFailure => c"handshake failure", + AlertDescription::NoCertificate => c"no certificate", + AlertDescription::BadCertificate => c"bad certificate", + AlertDescription::UnsupportedCertificate => c"unsupported certificate", + AlertDescription::CertificateRevoked => c"certificate revoked", + AlertDescription::CertificateExpired => c"certificate expired", + AlertDescription::CertificateUnknown => c"certificate unknown", + AlertDescription::IllegalParameter => c"illegal parameter", + AlertDescription::UnknownCA => c"unknown CA", + AlertDescription::AccessDenied => c"access denied", + AlertDescription::DecodeError => c"decode error", + AlertDescription::DecryptError => c"decrypt error", + AlertDescription::ExportRestriction => c"export restriction", + AlertDescription::ProtocolVersion => c"protocol version", + AlertDescription::InsufficientSecurity => c"insufficient security", + AlertDescription::InternalError => c"internal error", + AlertDescription::UserCanceled => c"user canceled", + AlertDescription::NoRenegotiation => c"no renegotiation", + AlertDescription::UnsupportedExtension => c"unsupported extension", + AlertDescription::CertificateUnobtainable => c"certificate unobtainable", + AlertDescription::UnrecognisedName => c"unrecognized name", + AlertDescription::BadCertificateStatusResponse => c"bad certificate status response", + AlertDescription::BadCertificateHashValue => c"bad certificate hash value", + AlertDescription::UnknownPSKIdentity => c"unknown PSK identity", + AlertDescription::NoApplicationProtocol => c"no application protocol", + // these are not supported by openssl: + // AlertDescription::InappropriateFallback => c"inappropriate fallback", + // AlertDescription::MissingExtension => c"missing extension", + // AlertDescription::CertificateRequired => c"certificate required", + _ => c"unknown", + } +} + +pub fn alert_desc_to_short_string(value: c_int) -> &'static CStr { + match AlertDescription::from(value as u8) { + AlertDescription::CloseNotify => c"CN", + AlertDescription::UnexpectedMessage => c"UM", + AlertDescription::BadRecordMac => c"BM", + AlertDescription::DecryptionFailed => c"DC", + AlertDescription::RecordOverflow => c"RO", + AlertDescription::DecompressionFailure => c"DF", + AlertDescription::HandshakeFailure => c"HF", + AlertDescription::NoCertificate => c"NC", + AlertDescription::BadCertificate => c"BC", + AlertDescription::UnsupportedCertificate => c"UC", + AlertDescription::CertificateRevoked => c"CR", + AlertDescription::CertificateExpired => c"CE", + AlertDescription::CertificateUnknown => c"CU", + AlertDescription::IllegalParameter => c"IP", + AlertDescription::UnknownCA => c"CA", + AlertDescription::AccessDenied => c"AD", + AlertDescription::DecodeError => c"DE", + AlertDescription::DecryptError => c"CY", + AlertDescription::ExportRestriction => c"ER", + AlertDescription::ProtocolVersion => c"PV", + AlertDescription::InsufficientSecurity => c"IS", + AlertDescription::InternalError => c"IE", + AlertDescription::UserCanceled => c"US", + AlertDescription::NoRenegotiation => c"NR", + AlertDescription::UnsupportedExtension => c"UE", + AlertDescription::CertificateUnobtainable => c"CO", + AlertDescription::UnrecognisedName => c"UN", + AlertDescription::BadCertificateStatusResponse => c"BR", + AlertDescription::BadCertificateHashValue => c"BH", + AlertDescription::UnknownPSKIdentity => c"UP", + // these are not supported by openssl: + // AlertDescription::NoApplicationProtocol => c"no application protocol", + // AlertDescription::InappropriateFallback => c"inappropriate fallback", + // AlertDescription::MissingExtension => c"missing extension", + // AlertDescription::CertificateRequired => c"certificate required", + _ => c"UK", + } +} diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index 9702cc5..1dcc7d3 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -3,14 +3,23 @@ //! It should mainly be concerned with mapping these calls up to //! the safe APIs implemented elsewhere. -use core::mem; -use std::os::raw::c_int; +use core::{mem, ptr}; +use std::os::raw::{c_char, c_int, c_long, c_uchar, c_uint, c_void}; use std::sync::Mutex; +use std::{fs, io, path::PathBuf}; -use crate::error::{ffi_panic_boundary, Error}; +use openssl_sys::{ + stack_st_X509, OPENSSL_malloc, EVP_PKEY, X509, X509_STORE, X509_STORE_CTX, + X509_V_ERR_UNSPECIFIED, +}; + +use crate::bio::{Bio, BIO, BIO_METHOD}; +use crate::error::{ffi_panic_boundary, Error, MysteriouslyOppositeReturnValue}; use crate::ffi::{ - free_arc, to_arc_mut_ptr, try_clone_arc, try_ref_from_ptr, Castable, OwnershipArc, OwnershipRef, + 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::ShutdownResult; /// Makes a entry function definition. /// @@ -49,6 +58,24 @@ entry! { } } +entry! { + pub fn _SSL_alert_desc_string_long(value: c_int) -> *const c_char { + crate::constants::alert_desc_to_long_string(value).as_ptr() as *const c_char + } +} + +entry! { + pub fn _SSL_alert_desc_string(value: c_int) -> *const c_char { + crate::constants::alert_desc_to_short_string(value).as_ptr() as *const c_char + } +} + +entry! { + pub fn _BIO_f_ssl() -> *const BIO_METHOD { + &crate::bio::SSL_BIO_METHOD + } +} + type SSL_METHOD = crate::SslMethod; entry! { @@ -97,6 +124,182 @@ entry! { } } +entry! { + pub fn _SSL_CTX_get_options(ctx: *const SSL_CTX) -> u64 { + let ctx = try_clone_arc!(ctx); + ctx.lock() + .ok() + .map(|ctx| ctx.get_options()) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_CTX_clear_options(ctx: *mut SSL_CTX, op: u64) -> u64 { + let ctx = try_clone_arc!(ctx); + ctx.lock() + .ok() + .map(|mut ctx| ctx.clear_options(op)) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_CTX_set_options(ctx: *mut SSL_CTX, op: u64) -> u64 { + let ctx = try_clone_arc!(ctx); + ctx.lock() + .ok() + .map(|mut ctx| ctx.set_options(op)) + .unwrap_or_default() + } +} + +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 + } + } + } +} + +entry! { + pub fn _SSL_CTX_set_verify(ctx: *mut SSL_CTX, mode: c_int, callback: SSL_verify_cb) { + let ctx = try_clone_arc!(ctx); + + if callback.is_some() { + // supporting verify callbacks would mean we need to fully use + // the openssl certificate verifier, because X509_STORE and + // X509_STORE_CTX are both in libcrypto. + return Error::not_supported("verify callback").raise().into(); + } + + ctx.lock() + .ok() + .map(|mut ctx| ctx.set_verify(crate::VerifyMode::from(mode))) + .unwrap_or_default(); + } +} + +pub type SSL_verify_cb = + Option c_int>; + +entry! { + pub fn _SSL_CTX_get_cert_store(ctx: *const SSL_CTX) -> *mut X509_STORE { + let ctx = try_clone_arc!(ctx); + ctx.lock() + .ok() + .map(|ctx| ctx.get_x509_store()) + .unwrap_or(ptr::null_mut()) + } +} + +fn load_verify_files(ctx: &Mutex, file_names: impl Iterator) -> c_int { + let mut certs = Vec::new(); + for file_name in file_names { + let mut file_reader = match fs::File::open(file_name.clone()) { + Ok(content) => io::BufReader::new(content), + Err(err) => return Error::from_io(err).raise().into(), + }; + + for cert in rustls_pemfile::certs(&mut file_reader) { + match cert { + Ok(cert) => certs.push(cert), + Err(err) => { + log::trace!("Failed to parse {file_name:?}: {err:?}"); + return Error::from_io(err).raise().into(); + } + }; + } + } + + match ctx + .lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ctx| ctx.add_trusted_certs(certs)) + { + Err(e) => e.raise().into(), + Ok(()) => C_INT_SUCCESS, + } +} + +entry! { + pub fn _SSL_CTX_load_verify_file(ctx: *mut SSL_CTX, ca_file: *const c_char) -> c_int { + let ctx = try_clone_arc!(ctx); + let ca_file = try_str!(ca_file); + let path_buf = PathBuf::from(ca_file); + load_verify_files(ctx.as_ref(), [path_buf].into_iter()) + } +} + +entry! { + pub fn _SSL_CTX_load_verify_dir(ctx: *mut SSL_CTX, ca_dir: *const c_char) -> c_int { + let ctx = try_clone_arc!(ctx); + let ca_dir = try_str!(ca_dir); + + let entries = match fs::read_dir(ca_dir) { + Ok(iter) => iter, + Err(err) => return Error::from_io(err).raise().into(), + } + .filter_map(|entry| entry.ok()) + .map(|dir_entry| dir_entry.path()); + + load_verify_files(ctx.as_ref(), entries) + } +} + +entry! { + pub fn _SSL_CTX_set_alpn_protos( + ctx: *mut SSL_CTX, + protos: *const c_uchar, + protos_len: c_uint, + ) -> MysteriouslyOppositeReturnValue { + let ctx = try_clone_arc!(ctx); + let slice = try_slice!(protos, protos_len); + + let alpn = match crate::parse_alpn(slice) { + Some(alpn) => alpn, + None => { + // nb. openssl doesn't add anything to the error stack + // in this case. + return Error::bad_data("invalid alpn protocols").raise().into(); + } + }; + + match ctx + .lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ctx| ctx.set_alpn_offer(alpn)) + { + Err(e) => e.raise().into(), + Ok(()) => MysteriouslyOppositeReturnValue::Success, + } + } +} + impl Castable for SSL_CTX { type Ownership = OwnershipArc; type RustType = Mutex; @@ -107,7 +310,11 @@ type SSL = crate::Ssl; entry! { pub fn _SSL_new(ctx: *mut SSL_CTX) -> *mut SSL { let ctx = try_clone_arc!(ctx); - to_arc_mut_ptr(Mutex::new(crate::Ssl::new(ctx))) + + ctx.lock() + .ok() + .map(|c| to_arc_mut_ptr(Mutex::new(crate::Ssl::new(ctx.clone(), &c)))) + .unwrap_or_else(ptr::null_mut) } } @@ -125,14 +332,772 @@ entry! { } } +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 + } + } + } +} + +entry! { + pub fn _SSL_get_options(ssl: *const SSL) -> u64 { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|ssl| ssl.get_options()) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_clear_options(ssl: *mut SSL, op: u64) -> u64 { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|mut ssl| ssl.clear_options(op)) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_set_options(ssl: *mut SSL, op: u64) -> u64 { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|mut ssl| ssl.set_options(op)) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_set_alpn_protos( + ssl: *mut SSL, + protos: *const c_uchar, + protos_len: c_uint, + ) -> MysteriouslyOppositeReturnValue { + let ssl = try_clone_arc!(ssl); + let slice = try_slice!(protos, protos_len); + + let alpn = match crate::parse_alpn(slice) { + Some(alpn) => alpn, + None => { + // nb. openssl doesn't add anything to the error stack + // in this case. + return Error::bad_data("invalid alpn protocols").raise().into(); + } + }; + + match ssl + .lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ssl| ssl.set_alpn_offer(alpn)) + { + Err(e) => e.raise().into(), + Ok(()) => MysteriouslyOppositeReturnValue::Success, + } + } +} + +entry! { + pub fn _SSL_set_connect_state(ssl: *mut SSL) { + let ssl = try_clone_arc!(ssl); + let _ = ssl.lock().ok().map(|mut ssl| ssl.set_client_mode()); + } +} + +entry! { + pub fn _SSL_set_accept_state(ssl: *mut SSL) { + let ssl = try_clone_arc!(ssl); + let _ = ssl.lock().ok().map(|mut ssl| ssl.set_server_mode()); + } +} + +entry! { + pub fn _SSL_is_server(ssl: *const SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|ssl| ssl.is_server()) + .unwrap_or_default() as c_int + } +} + +entry! { + pub fn _SSL_set1_host(ssl: *mut SSL, hostname: *const c_char) -> c_int { + let ssl = try_clone_arc!(ssl); + let maybe_hostname = str_from_cstring(hostname); + ssl.lock() + .ok() + .map(|mut ssl| ssl.set_verify_hostname(maybe_hostname)) + .unwrap_or_default() as c_int + } +} + +entry! { + pub fn _SSL_set_fd(ssl: *mut SSL, fd: c_int) -> c_int { + let ssl = try_clone_arc!(ssl); + let bio = Bio::new_fd_no_close(fd); + ssl.lock() + .ok() + .map(|mut ssl| { + ssl.set_bio(bio); + true + }) + .unwrap_or_default() as c_int + } +} + +entry! { + pub fn _SSL_set_bio(ssl: *mut SSL, rbio: *mut BIO, wbio: *mut BIO) { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|mut ssl| ssl.set_bio_pair(Some(rbio), Some(wbio))) + .unwrap_or_default(); + } +} + +entry! { + pub fn _SSL_set0_rbio(ssl: *mut SSL, rbio: *mut BIO) { + let ssl = try_clone_arc!(ssl); + if rbio.is_null() { + return; + } + ssl.lock() + .ok() + .map(|mut ssl| ssl.set_bio_pair(Some(rbio), None)) + .unwrap_or_default(); + } +} + +entry! { + pub fn _SSL_set0_wbio(ssl: *mut SSL, wbio: *mut BIO) { + let ssl = try_clone_arc!(ssl); + if wbio.is_null() { + return; + } + ssl.lock() + .ok() + .map(|mut ssl| ssl.set_bio_pair(None, Some(wbio))) + .unwrap_or_default(); + } +} + +entry! { + pub fn _SSL_connect(ssl: *mut SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + + match ssl + .lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ssl| ssl.connect()) + .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; + let ssl = try_clone_arc!(ssl, ERROR); + let slice = try_slice_int!(buf as *const u8, num, ERROR); + + if slice.is_empty() { + return ERROR; + } + + match ssl + .lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ssl| ssl.write(slice)) + .map_err(|err| err.raise()) + { + Err(_e) => ERROR, + Ok(written) => written as c_int, + } + } +} + +entry! { + pub fn _SSL_read(ssl: *mut SSL, buf: *mut c_void, num: c_int) -> c_int { + const ERROR: c_int = -1; + let ssl = try_clone_arc!(ssl, ERROR); + let slice = try_mut_slice_int!(buf as *mut u8, num, ERROR); + + match ssl + .lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ssl| ssl.read(slice)) + .map_err(|err| err.raise()) + { + Err(_e) => ERROR, + Ok(read) => read as c_int, + } + } +} + +entry! { + pub fn _SSL_want(ssl: *const SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + let want = ssl.lock().ok().map(|ssl| ssl.want()).unwrap_or_default(); + + if want.read { + SSL_READING + } else if want.write { + SSL_WRITING + } else { + SSL_NOTHING + } + } +} + +pub const SSL_NOTHING: i32 = 1; +pub const SSL_WRITING: i32 = 2; +pub const SSL_READING: i32 = 3; + +entry! { + pub fn _SSL_shutdown(ssl: *mut SSL) -> c_int { + const ERROR: c_int = -1; + let ssl = try_clone_arc!(ssl, ERROR); + + match ssl + .lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ssl| ssl.try_shutdown()) + .map_err(|err| err.raise()) + { + Err(_e) => ERROR, + Ok(result) => match result { + ShutdownResult::Sent => 0 as c_int, + ShutdownResult::Received => 1 as c_int, + }, + } + } +} + +entry! { + pub fn _SSL_get_shutdown(ssl: *const SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + + ssl.lock().map(|ssl| ssl.get_shutdown()).unwrap_or_default() + } +} + +entry! { + pub fn _SSL_set_shutdown(ssl: *mut SSL, flags: c_int) { + let ssl = try_clone_arc!(ssl); + + ssl.lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ssl| ssl.set_shutdown(flags)) + .map_err(|err| err.raise()) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_pending(ssl: *const SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + + ssl.lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ssl| ssl.get_pending_plaintext() as c_int) + .map_err(|err| err.raise()) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_has_pending(ssl: *const SSL) -> c_int { + (_SSL_pending(ssl) > 0) as c_int + } +} + +entry! { + pub fn _SSL_get_error(ssl: *const SSL, _ret_code: c_int) -> c_int { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ssl| ssl.get_error() as c_int) + .map_err(|err| err.raise()) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_get0_alpn_selected(ssl: *const SSL, data: *mut *const c_uchar, len: *mut c_uint) { + if data.is_null() || len.is_null() { + return; + } + + let ssl = try_clone_arc!(ssl); + + match ssl.lock().ok().and_then(|mut ssl| { + ssl.get_agreed_alpn().map(|proto| { + unsafe { + // nb. alpn protocols are limited to 255 octets + ptr::write(len, proto.len() as u32); + ptr::write(data, proto.as_ptr()); + }; + }) + }) { + Some(()) => {} + None => unsafe { + ptr::write(len, 0); + ptr::write(data, ptr::null()); + }, + } + } +} + +entry! { + pub fn _SSL_get_peer_cert_chain(ssl: *const SSL) -> *mut stack_st_X509 { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .and_then(|mut ssl| ssl.get_peer_cert_chain().map(|x509| x509.pointer())) + .unwrap_or_else(ptr::null_mut) + } +} + +entry! { + pub fn _SSL_get0_verified_chain(ssl: *const SSL) -> *mut stack_st_X509 { + _SSL_get_peer_cert_chain(ssl) + } +} + +entry! { + pub fn _SSL_get0_peer_certificate(ssl: *const SSL) -> *mut X509 { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .and_then(|mut ssl| ssl.get_peer_cert().map(|x509| x509.borrow_ref())) + .unwrap_or_else(ptr::null_mut) + } +} + +entry! { + pub fn _SSL_get1_peer_certificate(ssl: *const SSL) -> *mut X509 { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .and_then(|mut ssl| ssl.get_peer_cert().map(|x509| x509.up_ref())) + .unwrap_or_else(ptr::null_mut) + } +} + +entry! { + pub fn _SSL_get_current_cipher(ssl: *const SSL) -> *const SSL_CIPHER { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .and_then(|ssl| ssl.get_negotiated_cipher_suite_id()) + .and_then(crate::SslCipher::find_by_id) + .map(|cipher| cipher as *const SSL_CIPHER) + .unwrap_or_else(ptr::null) + } +} + +entry! { + pub fn _SSL_get_version(ssl: *const SSL) -> *const c_char { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .and_then(|ssl| ssl.get_negotiated_cipher_suite_id()) + .and_then(crate::SslCipher::find_by_id) + .map(|cipher| cipher.version.as_ptr()) + .unwrap_or_else(ptr::null) + } +} + +entry! { + pub fn _SSL_get_verify_result(ssl: *const SSL) -> c_long { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|ssl| ssl.get_last_verification_result()) + .unwrap_or(X509_V_ERR_UNSPECIFIED as i64) + } +} + impl Castable for SSL { type Ownership = OwnershipArc; type RustType = Mutex; } +type SSL_CIPHER = crate::SslCipher; + +entry! { + pub fn _SSL_CIPHER_find(_ssl: *const SSL, ptr: *const c_uchar) -> *const SSL_CIPHER { + let slice = try_slice!(ptr, 2); + let id = (slice[0] as u16) << 8 | (slice[1] as u16); + crate::SslCipher::find_by_id(rustls::CipherSuite::from(id)) + .map(|cipher| cipher as *const SSL_CIPHER) + .unwrap_or_else(ptr::null) + } +} + +entry! { + pub fn _SSL_CIPHER_get_bits(cipher: *const SSL_CIPHER, alg_bits: *mut c_int) -> c_int { + let cipher = try_ref_from_ptr!(cipher); + let bits = cipher.bits as c_int; + if !alg_bits.is_null() { + unsafe { ptr::write(alg_bits, bits) }; + } + bits + } +} + +entry! { + pub fn _SSL_CIPHER_get_version(cipher: *const SSL_CIPHER) -> *const c_char { + match try_from(cipher) { + Some(cipher) => cipher.version, + None => c"(NONE)", + } + .as_ptr() + } +} + +entry! { + pub fn _SSL_CIPHER_get_name(cipher: *const SSL_CIPHER) -> *const c_char { + match try_from(cipher) { + Some(cipher) => cipher.openssl_name, + None => c"(NONE)", + } + .as_ptr() + } +} + +entry! { + pub fn _SSL_CIPHER_standard_name(cipher: *const SSL_CIPHER) -> *const c_char { + match try_from(cipher) { + Some(cipher) => cipher.standard_name, + None => c"(NONE)", + } + .as_ptr() + } +} + +entry! { + pub fn _SSL_CIPHER_get_id(cipher: *const SSL_CIPHER) -> u32 { + let cipher = try_ref_from_ptr!(cipher); + cipher.openssl_id() + } +} + +entry! { + pub fn _SSL_CIPHER_get_protocol_id(cipher: *const SSL_CIPHER) -> u16 { + let cipher = try_ref_from_ptr!(cipher); + cipher.protocol_id() + } +} + +entry! { + pub fn _SSL_CIPHER_description( + cipher: *const SSL_CIPHER, + mut buf: *mut c_char, + mut size: c_int, + ) -> *mut c_char { + let cipher = try_ref_from_ptr!(cipher); + let required_len = cipher.description.to_bytes_with_nul().len(); + + if buf.is_null() { + // safety: `required_len` is a compile-time constant, and is + // a reasonable quantity to ask `OPENSSL_malloc` for. + // In C cast rules, any `*mut c_void` can be viewed as a + // `*mut c_char`. + let allocd = unsafe { OPENSSL_malloc(required_len) as *mut c_char }; + if allocd.is_null() { + return allocd; + } + buf = allocd; + size = required_len as i32; + } else if size < (required_len as i32) { + return ptr::null_mut(); + } + + unsafe { + ptr::copy_nonoverlapping(cipher.description.as_ptr(), buf, required_len as usize); + }; + buf + } +} + +impl Castable for SSL_CIPHER { + type Ownership = OwnershipRef; + type RustType = SSL_CIPHER; +} + /// Normal OpenSSL return value convention success indicator. +/// +/// Compare [`crate::ffi::MysteriouslyOppositeReturnValue`]. const C_INT_SUCCESS: c_int = 1; +/// Define an enum that can round trip through a c_int, with no +/// UB for unknown values. +macro_rules! num_enum { + ($enum_vis:vis enum $enum_name:ident + { $( $enum_var:ident = $enum_val:expr ),* $(,)? } + ) => { + #[derive(Debug, PartialEq, Clone, Copy)] + $enum_vis enum $enum_name { + $( $enum_var),* + } + + impl From<$enum_name> for c_int { + fn from(item: $enum_name) -> Self { + match item { + $( $enum_name::$enum_var => $enum_val),* + } + } + } + + impl TryFrom for $enum_name { + type Error = (); + fn try_from(i: c_int) -> Result { + match i { + $( $enum_val => Ok(Self::$enum_var), )* + _ => Err(()), + } + } + } + } +} + +// See `ssl.h` for macros starting `SSL_CTRL_`, eg. `SSL_CTRL_SET_TLSEXT_HOSTNAME` +num_enum! { + enum SslCtrl { + Mode = 33, + SetMsgCallbackArg = 16, + SetTlsExtHostname = 55, + SetMaxProtoVersion = 124, + } +} + +// --- unimplemented stubs below here --- + +macro_rules! entry_stub { + (pub fn $name:ident($($args:tt)*);) => { + #[no_mangle] + pub extern "C" fn $name($($args)*) { + ffi_panic_boundary! { + Error::not_supported(stringify!($name)).raise().into() + } + } + }; + (pub fn $name:ident($($args:tt)*) -> $ret:ty;) => { + #[no_mangle] + pub extern "C" fn $name($($args)*) -> $ret { + ffi_panic_boundary! { + Error::not_supported(stringify!($name)).raise().into() + } + } + }; +} + +// things we support and should be able to implement to +// some extent: + +entry_stub! { + pub fn _SSL_CTX_set_ex_data(_ssl: *mut SSL_CTX, _idx: c_int, _data: *mut c_void) -> c_int; +} + +entry_stub! { + pub fn _SSL_CTX_get_ex_data(_ssl: *const SSL_CTX, _idx: c_int) -> *mut c_void; +} + +entry_stub! { + pub fn _SSL_set_ex_data(_ssl: *mut SSL, _idx: c_int, _data: *mut c_void) -> c_int; +} + +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; +} + +entry_stub! { + pub fn _SSL_CTX_set_keylog_callback(_ctx: *mut SSL_CTX, _cb: SSL_CTX_keylog_cb_func); +} + +pub type SSL_CTX_keylog_cb_func = + Option; + +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); +} + +pub type SSL_CTX_new_session_cb = + Option c_int>; + +entry_stub! { + pub fn _SSL_CTX_set_cipher_list(_ctx: *mut SSL_CTX, _s: *const c_char) -> c_int; +} + +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, + _file: *const c_char, + _type_: c_int, + ) -> c_int; +} + +pub struct SSL_SESSION; + +entry_stub! { + pub fn _SSL_SESSION_free(_sess: *mut SSL_SESSION); +} + +// no individual message logging + +entry_stub! { + pub fn _SSL_CTX_set_msg_callback(_ctx: *mut SSL_CTX, _cb: SSL_CTX_msg_cb_func); +} + +pub type SSL_CTX_msg_cb_func = Option< + unsafe extern "C" fn( + write_p: c_int, + version: c_int, + content_type: c_int, + buf: *const c_void, + len: usize, + ssl: *mut SSL, + arg: *mut c_void, + ), +>; + +// no NPN (obsolete precursor to ALPN) + +entry_stub! { + pub fn _SSL_CTX_set_next_proto_select_cb( + _ctx: *mut SSL_CTX, + _cb: SSL_CTX_npn_select_cb_func, + _arg: *mut c_void, + ); +} + +pub type SSL_CTX_npn_select_cb_func = Option< + unsafe extern "C" fn( + s: *mut SSL, + out: *mut *mut c_uchar, + outlen: *mut c_uchar, + in_: *const c_uchar, + inlen: c_uint, + arg: *mut c_void, + ) -> c_int, +>; + +// no password-protected key loading + +entry_stub! { + pub fn _SSL_CTX_set_default_passwd_cb(_ctx: *mut SSL_CTX, _cb: pem_password_cb); +} + +pub type pem_password_cb = Option< + unsafe extern "C" fn( + buf: *mut c_char, + size: c_int, + rwflag: c_int, + userdata: *mut c_void, + ) -> c_int, +>; + +entry_stub! { + pub fn _SSL_CTX_set_default_passwd_cb_userdata(_ctx: *mut SSL_CTX, _u: *mut c_void); +} + +// no SRP + +entry_stub! { + pub fn _SSL_CTX_set_srp_password(_ctx: *mut SSL_CTX, _password: *mut c_char) -> c_int; +} + +entry_stub! { + pub fn _SSL_CTX_set_srp_username(_ctx: *mut SSL_CTX, _name: *mut c_char) -> c_int; +} + +// no post-handshake auth + +entry_stub! { + pub fn _SSL_CTX_set_post_handshake_auth(_ctx: *mut SSL_CTX, _val: c_int); +} + +entry_stub! { + pub fn _SSL_set_post_handshake_auth(_s: *mut SSL, _val: c_int); +} + +// --------------------- + #[cfg(test)] mod tests { use super::*; @@ -174,4 +1139,80 @@ mod tests { _SSL_free(ssl); // ref 1 _SSL_CTX_free(ctx); } + + #[test] + fn test_SSL_CTX_set_alpn_protos_works() { + let ctx = _SSL_CTX_new(_TLS_method()); + assert_eq!( + _SSL_CTX_set_alpn_protos(ctx, b"\x05hello" as *const u8, 6) as i32, + 0i32 + ); + _SSL_CTX_free(ctx); + } + + #[test] + fn test_SSL_CTX_set_alpn_protos_null_ctx() { + assert_eq!( + _SSL_CTX_set_alpn_protos(ptr::null_mut(), b"\x05hello" as *const u8, 6) as i32, + 1i32 + ); + } + + #[test] + fn test_SSL_CTX_set_alpn_protos_null_proto() { + let ctx = _SSL_CTX_new(_TLS_method()); + assert_eq!(_SSL_CTX_set_alpn_protos(ctx, ptr::null(), 6) as i32, 1i32); + _SSL_CTX_free(ctx); + } + + #[test] + fn test_SSL_CTX_set_alpn_protos_invalid_proto() { + let ctx = _SSL_CTX_new(_TLS_method()); + assert_eq!( + _SSL_CTX_set_alpn_protos(ctx, b"\x05hell" as *const u8, 5) as i32, + 1i32 + ); + _SSL_CTX_free(ctx); + } + + #[test] + fn test_SSL_set_alpn_protos_works() { + let ctx = _SSL_CTX_new(_TLS_method()); + let ssl = _SSL_new(ctx); + assert_eq!( + _SSL_set_alpn_protos(ssl, b"\x05hello" as *const u8, 6) as i32, + 0i32 + ); + _SSL_free(ssl); + _SSL_CTX_free(ctx); + } + + #[test] + fn test_SSL_set_alpn_protos_null_ssl() { + assert_eq!( + _SSL_set_alpn_protos(ptr::null_mut(), b"\x05hello" as *const u8, 6) as i32, + 1i32 + ); + } + + #[test] + fn test_SSL_set_alpn_protos_null_proto() { + let ctx = _SSL_CTX_new(_TLS_method()); + let ssl = _SSL_new(ctx); + assert_eq!(_SSL_set_alpn_protos(ssl, ptr::null(), 6) as i32, 1i32); + _SSL_free(ssl); + _SSL_CTX_free(ctx); + } + + #[test] + fn test_SSL_set_alpn_protos_invalid_proto() { + let ctx = _SSL_CTX_new(_TLS_method()); + let ssl = _SSL_new(ctx); + assert_eq!( + _SSL_set_alpn_protos(ssl, b"\x05hell" as *const u8, 5) as i32, + 1i32 + ); + _SSL_free(ssl); + _SSL_CTX_free(ctx); + } } diff --git a/rustls-libssl/src/error.rs b/rustls-libssl/src/error.rs index a10ab69..5c6a805 100644 --- a/rustls-libssl/src/error.rs +++ b/rustls-libssl/src/error.rs @@ -142,6 +142,13 @@ impl From for c_int { } } +impl From for MysteriouslyOppositeReturnValue { + fn from(_: Error) -> Self { + // for a small subset of OpenSSL functions (return 1 on error) + MysteriouslyOppositeReturnValue::Error + } +} + impl From for c_long { fn from(_: Error) -> Self { // ditto @@ -192,3 +199,16 @@ macro_rules! ffi_panic_boundary { } pub(crate) use ffi_panic_boundary; + +/// An entry point that yields this type marks it as one where +/// `0` is returned on success, `1` on error. +/// +/// That is opposite to other OpenSSL functions which return 1 on success. +/// +/// It has the same representation as `c_int`. +#[repr(i32)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum MysteriouslyOppositeReturnValue { + Success = 0, + Error = 1, +} diff --git a/rustls-libssl/src/lib.rs b/rustls-libssl/src/lib.rs index 75c273e..94ead95 100644 --- a/rustls-libssl/src/lib.rs +++ b/rustls-libssl/src/lib.rs @@ -1,5 +1,18 @@ +use core::ffi::{c_int, CStr}; +use std::io::{ErrorKind, Read, Write}; use std::sync::{Arc, Mutex}; +use openssl_sys::{ + SSL_ERROR_NONE, SSL_ERROR_SSL, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, X509_STORE, + X509_V_ERR_UNSPECIFIED, +}; +use rustls::crypto::ring as provider; +use rustls::pki_types::{CertificateDer, ServerName}; +use rustls::{CipherSuite, ClientConfig, ClientConnection, Connection, RootCertStore}; + +mod bio; +#[macro_use] +mod constants; #[allow( // relax naming convention lints for openssl API non_camel_case_types, @@ -16,6 +29,8 @@ mod ffi; #[cfg(miri)] #[allow(non_camel_case_types, dead_code)] mod miri; +mod verifier; +mod x509; /// `SSL_METHOD` underlying type. /// @@ -26,6 +41,19 @@ pub struct SslMethod { server_versions: &'static [&'static rustls::SupportedProtocolVersion], } +impl SslMethod { + fn mode(&self) -> ConnMode { + match ( + self.client_versions.is_empty(), + self.server_versions.is_empty(), + ) { + (true, false) => ConnMode::Server, + (false, true) => ConnMode::Client, + (_, _) => ConnMode::Unknown, + } + } +} + static TLS_CLIENT_METHOD: SslMethod = SslMethod { client_versions: rustls::ALL_VERSIONS, server_versions: &[], @@ -39,22 +67,665 @@ static TLS_METHOD: SslMethod = SslMethod { server_versions: rustls::ALL_VERSIONS, }; +/// `SSL_CIPHER` underlying type. +/// +/// # Lifetime +/// Functions that return `SSL_CIPHER` give static-lifetime pointers. +pub struct SslCipher { + pub bits: usize, + pub openssl_name: &'static CStr, + pub standard_name: &'static CStr, + pub version: &'static CStr, + pub description: &'static CStr, + rustls: &'static rustls::SupportedCipherSuite, +} + +impl SslCipher { + pub fn find_by_id(id: CipherSuite) -> Option<&'static SslCipher> { + match id { + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => { + Some(&TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + } + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => { + Some(&TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) + } + CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => { + Some(&TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256) + } + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => { + Some(&TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) + } + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => { + Some(&TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) + } + CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => { + Some(&TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) + } + CipherSuite::TLS13_AES_128_GCM_SHA256 => Some(&TLS13_AES_128_GCM_SHA256), + CipherSuite::TLS13_AES_256_GCM_SHA384 => Some(&TLS13_AES_256_GCM_SHA384), + CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => Some(&TLS13_CHACHA20_POLY1305_SHA256), + _ => None, + } + } + + pub fn protocol_id(&self) -> u16 { + self.rustls.suite().get_u16() + } + + pub fn openssl_id(&self) -> u32 { + 0x03000000u32 | (self.protocol_id() as u32) + } +} + +static TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + bits: 128, + openssl_name: c"ECDHE-ECDSA-AES128-GCM-SHA256", + standard_name: c"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + version: c"TLSv1.2", + description: c"ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD\n", +}; + +static TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + bits: 256, + openssl_name: c"ECDHE-ECDSA-AES256-GCM-SHA384", + standard_name: c"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + version: c"TLSv1.2", + description: c"ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD\n", +}; + +static TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + bits: 256, + openssl_name: c"ECDHE-ECDSA-CHACHA20-POLY1305", + standard_name: c"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + version: c"TLSv1.2", + description: c"ECDHE-ECDSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=ECDSA Enc=CHACHA20/POLY1305(256) Mac=AEAD\n", +}; + +static TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + bits: 128, + openssl_name: c"ECDHE-RSA-AES128-GCM-SHA256", + standard_name: c"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + version: c"TLSv1.2", + description: c"ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD\n", +}; + +static TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + bits: 256, + openssl_name: c"ECDHE-RSA-AES256-GCM-SHA384", + standard_name: c"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + version: c"TLSv1.2", + description: c"ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD\n", +}; + +static TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + bits: 256, + openssl_name: c"ECDHE-RSA-CHACHA20-POLY1305", + standard_name: c"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + version: c"TLSv1.2", + description: c"ECDHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=RSA Enc=CHACHA20/POLY1305(256) Mac=AEAD\n", +}; + +static TLS13_AES_128_GCM_SHA256: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS13_AES_128_GCM_SHA256, + bits: 128, + openssl_name: c"TLS_AES_128_GCM_SHA256", + standard_name: c"TLS_AES_128_GCM_SHA256", + version: c"TLSv1.3", + description: c"TLS_AES_128_GCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESGCM(128) Mac=AEAD\n", +}; + +static TLS13_AES_256_GCM_SHA384: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS13_AES_256_GCM_SHA384, + bits: 256, + openssl_name: c"TLS_AES_256_GCM_SHA384", + standard_name: c"TLS_AES_256_GCM_SHA384", + version: c"TLSv1.3", + description: c"TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD\n", +}; + +static TLS13_CHACHA20_POLY1305_SHA256: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, + bits: 256, + openssl_name: c"TLS_CHACHA20_POLY1305_SHA256", + standard_name: c"TLS_CHACHA20_POLY1305_SHA256", + version: c"TLSv1.3", + description: c"TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any Au=any Enc=CHACHA20/POLY1305(256) Mac=AEAD\n", +}; + pub struct SslContext { method: &'static SslMethod, + raw_options: u64, + verify_mode: VerifyMode, + verify_roots: RootCertStore, + verify_x509_store: x509::OwnedX509Store, + alpn: Vec>, } impl SslContext { fn new(method: &'static SslMethod) -> Self { - Self { method } + Self { + method, + raw_options: 0, + verify_mode: VerifyMode::default(), + verify_roots: RootCertStore::empty(), + verify_x509_store: x509::OwnedX509Store::new(), + alpn: vec![], + } + } + + fn get_options(&self) -> u64 { + self.raw_options + } + + fn set_options(&mut self, set: u64) -> u64 { + self.raw_options |= set; + self.raw_options + } + + fn clear_options(&mut self, clear: u64) -> u64 { + self.raw_options &= !clear; + self.raw_options + } + + fn set_verify(&mut self, mode: VerifyMode) { + self.verify_mode = mode; + } + + fn add_trusted_certs( + &mut self, + certs: Vec>, + ) -> Result<(), error::Error> { + for c in certs { + self.verify_roots + .add(c) + .map_err(error::Error::from_rustls)?; + } + Ok(()) + } + + fn get_x509_store(&self) -> *mut X509_STORE { + self.verify_x509_store.pointer() + } + + fn set_alpn_offer(&mut self, alpn: Vec>) { + self.alpn = alpn; } } +/// Parse the ALPN wire format (which is used in the openssl API) +/// to rustls's internal representation. +/// +/// For an empty `slice`, returns `Some(vec![])`. +/// For a slice with invalid contents, returns `None`. +pub fn parse_alpn(mut slice: &[u8]) -> Option>> { + let mut out = vec![]; + + while !slice.is_empty() { + let len = *slice.first()? as usize; + if len == 0 { + return None; + } + let body = slice.get(1..1 + len)?; + out.push(body.to_vec()); + slice = &slice[1 + len..]; + } + + Some(out) +} + struct Ssl { ctx: Arc>, + raw_options: u64, + mode: ConnMode, + verify_mode: VerifyMode, + verify_roots: RootCertStore, + verify_server_name: Option>, + alpn: Vec>, + sni_server_name: Option>, + bio: Option, + conn: Option, + verifier: Option>, + peer_cert: Option, + peer_cert_chain: Option, + shutdown_flags: ShutdownFlags, } impl Ssl { - fn new(ctx: Arc>) -> Self { - Self { ctx } + fn new(ctx: Arc>, inner: &SslContext) -> Self { + Self { + ctx, + raw_options: inner.raw_options, + mode: inner.method.mode(), + verify_mode: inner.verify_mode, + verify_roots: inner.verify_roots.clone(), + verify_server_name: None, + alpn: inner.alpn.clone(), + sni_server_name: None, + bio: None, + conn: None, + verifier: None, + peer_cert: None, + peer_cert_chain: None, + shutdown_flags: ShutdownFlags::default(), + } + } + + fn get_options(&self) -> u64 { + self.raw_options + } + + fn set_options(&mut self, set: u64) -> u64 { + self.raw_options |= set; + self.raw_options + } + + fn clear_options(&mut self, clear: u64) -> u64 { + self.raw_options &= !clear; + self.raw_options + } + + fn set_alpn_offer(&mut self, alpn: Vec>) { + self.alpn = alpn; + } + + fn set_client_mode(&mut self) { + // nb. don't fill in `conn` until the last minute. + // SSL_set_connect_state() .. SSL_set1_host() .. SSL_connect() is a valid + // sequence of calls. + self.mode = ConnMode::Client; + } + + fn set_server_mode(&mut self) { + self.mode = ConnMode::Server; + } + + fn is_server(&self) -> bool { + self.mode == ConnMode::Server + } + + 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 + // cleared and name checks are not performed on the peer certificate. + None | Some("") => { + self.verify_server_name = None; + true + } + Some(hostname) => match ServerName::try_from(hostname).ok() { + Some(server_name) => { + self.verify_server_name = Some(server_name.to_owned()); + true + } + None => false, + }, + } + } + + fn set_sni_hostname(&mut self, hostname: &str) -> bool { + match ServerName::try_from(hostname).ok() { + Some(server_name) => { + self.sni_server_name = Some(server_name.to_owned()); + true + } + None => false, + } + } + + fn set_bio(&mut self, bio: bio::Bio) { + self.bio = Some(bio); + } + + fn set_bio_pair(&mut self, rbio: Option<*mut bio::BIO>, wbio: Option<*mut bio::BIO>) { + if let Some(bio) = &mut self.bio { + bio.update(rbio, wbio); + } else { + self.bio = Some(bio::Bio::new_pair(rbio, wbio)); + } + } + + fn connect(&mut self) -> Result<(), error::Error> { + self.set_client_mode(); + if self.conn.is_none() { + self.init_client_conn()?; + } + self.try_io() + } + + fn init_client_conn(&mut self) -> Result<(), error::Error> { + // if absent, use a dummy IP address which disables SNI. + let sni_server_name = match &self.sni_server_name { + Some(sni_name) => sni_name.clone(), + None => ServerName::try_from("0.0.0.0").unwrap(), + }; + + 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::ServerVerifier::new( + self.verify_roots.clone().into(), + provider.clone(), + self.verify_mode, + &self.verify_server_name, + )); + self.verifier = Some(verifier.clone()); + + let mut config = 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(); + + config.alpn_protocols.clone_from(&self.alpn); + + let client_conn = ClientConnection::new(Arc::new(config), sni_server_name.clone()) + .map_err(error::Error::from_rustls)?; + + self.conn = Some(client_conn.into()); + Ok(()) + } + + fn want(&self) -> Want { + match &self.conn { + Some(conn) => Want { + read: conn.wants_read(), + write: conn.wants_write(), + }, + None => 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)?, + None => 0, + }; + self.try_io()?; + Ok(written) + } + + fn read(&mut self, slice: &mut [u8]) -> Result { + 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) { + Ok(read) => break (late_err, read), + Err(err) if err.kind() == ErrorKind::WouldBlock && late_err.is_ok() => { + // no data available, go around again. + continue; + } + Err(err) => { + return Err(error::Error::from_io(err)); + } + }, + None => break (late_err, 0), + }; + }; + + if read_count > 0 { + Ok(read_count) + } else { + // Only raise IO errors after all data has been read. + late_err?; + Ok(0) + } + } + + fn try_io(&mut self) -> Result<(), error::Error> { + let bio = match self.bio.as_mut() { + Some(bio) => bio, + None => return Ok(()), // investigate OpenSSL behaviour without a BIO + }; + + match &mut self.conn { + Some(ref mut conn) => { + match conn.complete_io(bio) { + Ok(_) => {} + Err(e) => { + return Err(error::Error::from_io(e)); + } + }; + let io_state = conn + .process_new_packets() + .map_err(error::Error::from_rustls)?; + if io_state.peer_has_closed() { + self.shutdown_flags.set_received(); + } + Ok(()) + } + None => 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 => (), + }; + + self.shutdown_flags.set_sent(); + } + + self.try_io()?; + Ok(if self.shutdown_flags.is_received() { + ShutdownResult::Received + } else { + ShutdownResult::Sent + }) + } + + fn get_shutdown(&self) -> i32 { + self.shutdown_flags.0 + } + + fn set_shutdown(&mut self, flags: i32) { + self.shutdown_flags.set(flags); + } + + fn get_pending_plaintext(&mut self) -> usize { + self.conn + .as_mut() + .and_then(|conn| { + let io_state = conn.process_new_packets().ok()?; + Some(io_state.plaintext_bytes_to_read()) + }) + .unwrap_or_default() + } + + fn get_agreed_alpn(&mut self) -> Option<&[u8]> { + self.conn.as_ref().and_then(|conn| conn.alpn_protocol()) + } + + fn init_peer_cert(&mut self) { + let conn = match &self.conn { + Some(conn) => conn, + None => return, + }; + + let certs = match conn.peer_certificates() { + Some(certs) => certs, + None => return, + }; + + let mut stack = x509::OwnedX509Stack::empty(); + for (i, cert) in certs.iter().enumerate() { + let converted = match x509::OwnedX509::parse_der(cert.as_ref()) { + Some(converted) => converted, + None => return, + }; + + if i == 0 { + if !self.is_server() { + // See docs for `SSL_get_peer_cert_chain`: + // "If called on the client side, the stack also contains + // the peer's certificate; if called on the server side, the peer's + // certificate must be obtained separately" + stack.push(&converted); + } + self.peer_cert = Some(converted); + } else { + stack.push(&converted); + } + } + + self.peer_cert_chain = Some(stack); + } + + fn get_peer_cert(&mut self) -> Option<&x509::OwnedX509> { + if self.peer_cert.is_none() { + self.init_peer_cert(); + } + self.peer_cert.as_ref() + } + + fn get_peer_cert_chain(&mut self) -> Option<&x509::OwnedX509Stack> { + if self.peer_cert_chain.is_none() { + self.init_peer_cert(); + } + self.peer_cert_chain.as_ref() + } + + fn get_negotiated_cipher_suite_id(&self) -> Option { + self.conn + .as_ref() + .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 + } + } + + fn get_error(&mut self) -> c_int { + match &mut self.conn { + Some(ref mut conn) => { + if let Err(e) = conn.process_new_packets() { + error::Error::from_rustls(e).raise(); + return SSL_ERROR_SSL; + } + + if let Some(bio) = self.bio.as_ref() { + if bio.read_would_block() { + return SSL_ERROR_WANT_READ; + } else if bio.write_would_block() { + return SSL_ERROR_WANT_WRITE; + } + } + + SSL_ERROR_NONE + } + None => SSL_ERROR_SSL, + } + } +} + +#[derive(Default)] +struct Want { + read: bool, + write: bool, +} + +#[derive(PartialEq)] +enum ConnMode { + Unknown, + Client, + Server, +} + +#[repr(i32)] +enum ShutdownResult { + Sent = 0, + Received = 1, +} + +#[derive(Default)] +struct ShutdownFlags(i32); + +impl ShutdownFlags { + const SENT: i32 = 1; + const RECEIVED: i32 = 2; + + fn is_sent(&self) -> bool { + self.0 & ShutdownFlags::SENT == ShutdownFlags::SENT + } + + fn is_received(&self) -> bool { + self.0 & ShutdownFlags::RECEIVED == ShutdownFlags::RECEIVED + } + + fn set_sent(&mut self) { + self.0 |= ShutdownFlags::SENT; + } + + fn set_received(&mut self) { + self.0 |= ShutdownFlags::RECEIVED; + } + + fn set(&mut self, flags: i32) { + self.0 |= flags & (ShutdownFlags::SENT | ShutdownFlags::RECEIVED); + } +} + +#[derive(Default, Debug, Clone, Copy)] +pub struct VerifyMode(i32); + +impl VerifyMode { + const _NONE: i32 = 0x0; + const PEER: i32 = 0x1; + const FAIL_IF_NO_PEER_CERT: i32 = 0x2; + // other flags not mentioned here are not implemented. + + pub fn client_must_verify_server(&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 + } + + pub fn server_should_verify_client_but_allow_anon(&self) -> bool { + self.0 & (VerifyMode::PEER | VerifyMode::FAIL_IF_NO_PEER_CERT) == VerifyMode::PEER + } +} + +impl From for VerifyMode { + fn from(i: i32) -> Self { + Self(i) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_parse_alpn() { + assert_eq!(Some(vec![]), parse_alpn(&[])); + assert_eq!(Some(vec![b"hi".to_vec()]), parse_alpn(&b"\x02hi"[..])); + assert_eq!( + Some(vec![b"hi".to_vec(), b"world".to_vec()]), + parse_alpn(&b"\x02hi\x05world"[..]) + ); + + assert_eq!(None, parse_alpn(&[0])); + assert_eq!(None, parse_alpn(&[1])); + assert_eq!(None, parse_alpn(&[1, 1, 1])); + assert_eq!(None, parse_alpn(&[255])); } } diff --git a/rustls-libssl/src/miri.rs b/rustls-libssl/src/miri.rs index 98d202e..9429525 100644 --- a/rustls-libssl/src/miri.rs +++ b/rustls-libssl/src/miri.rs @@ -1,6 +1,21 @@ /// Shims for functions we call, written in rust so they are visible to miri. use std::ffi::{c_char, c_int, CStr}; +pub struct X509_STORE(()); + +#[no_mangle] +pub extern "C" fn X509_STORE_new() -> *mut X509_STORE { + Box::into_raw(Box::new(X509_STORE(()))) +} + +#[no_mangle] +pub extern "C" fn X509_STORE_free(ptr: *mut X509_STORE) { + if ptr.is_null() { + return; + } + drop(unsafe { Box::from_raw(ptr) }); +} + #[no_mangle] pub extern "C" fn ERR_new() { eprintln!("ERR_new()"); diff --git a/rustls-libssl/src/verifier.rs b/rustls-libssl/src/verifier.rs new file mode 100644 index 0000000..4d7a0a7 --- /dev/null +++ b/rustls-libssl/src/verifier.rs @@ -0,0 +1,163 @@ +use core::sync::atomic::{AtomicI64, Ordering}; +use std::sync::Arc; + +use openssl_sys::{ + X509_V_ERR_CERT_HAS_EXPIRED, X509_V_ERR_CERT_NOT_YET_VALID, X509_V_ERR_CERT_REVOKED, + X509_V_ERR_HOSTNAME_MISMATCH, X509_V_ERR_INVALID_PURPOSE, + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, X509_V_ERR_UNSPECIFIED, X509_V_OK, +}; + +use rustls::{ + client::{ + danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + verify_server_cert_signed_by_trust_anchor, verify_server_name, + }, + crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}, + pki_types::{CertificateDer, ServerName, UnixTime}, + server::ParsedCertificate, + CertificateError, DigitallySignedStruct, Error, RootCertStore, SignatureScheme, +}; + +use crate::VerifyMode; + +/// This is a verifier that implements the selection of bad ideas from OpenSSL: +/// +/// - that the SNI name and verified certificate server name are unrelated +/// - that the server name can be empty, and that implicitly disables hostname verification +/// - that the behaviour defaults to verifying nothing +#[derive(Debug)] +pub struct ServerVerifier { + root_store: Arc, + + provider: Arc, + + /// Expected server name. + /// + /// `None` means server name verification is disabled. + verify_hostname: Option>, + + mode: VerifyMode, + + last_result: AtomicI64, +} + +impl ServerVerifier { + pub fn new( + root_store: Arc, + provider: Arc, + mode: VerifyMode, + hostname: &Option>, + ) -> Self { + Self { + root_store, + provider, + verify_hostname: hostname.clone(), + mode, + last_result: AtomicI64::new(X509_V_ERR_UNSPECIFIED as i64), + } + } + + pub fn last_result(&self) -> i64 { + self.last_result.load(Ordering::Acquire) + } + + fn verify_server_cert_inner( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + now: UnixTime, + ) -> Result<(), Error> { + let end_entity = ParsedCertificate::try_from(end_entity)?; + + verify_server_cert_signed_by_trust_anchor( + &end_entity, + &self.root_store, + intermediates, + now, + self.provider.signature_verification_algorithms.all, + )?; + + if let Some(server_name) = &self.verify_hostname { + verify_server_name(&end_entity, server_name)?; + } + + Ok(()) + } +} + +impl ServerCertVerifier for ServerVerifier { + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + _ignored_server_name: &ServerName<'_>, + _ocsp_response: &[u8], + now: UnixTime, + ) -> 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, + }; + 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.client_must_verify_server() { + Ok(ServerCertVerified::assertion()) + } else { + Err(result.unwrap_err()) + } + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls12_signature( + message, + cert, + dss, + &self.provider.signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls13_signature( + message, + cert, + dss, + &self.provider.signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + self.provider + .signature_verification_algorithms + .supported_schemes() + } +} diff --git a/rustls-libssl/src/x509.rs b/rustls-libssl/src/x509.rs new file mode 100644 index 0000000..7f65d9c --- /dev/null +++ b/rustls-libssl/src/x509.rs @@ -0,0 +1,127 @@ +use core::ffi::{c_int, c_long, c_void}; +use core::ptr; + +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, +}; + +/// Safe, owning wrapper around an OpenSSL `STACK_OF(X509)` object. +pub struct OwnedX509Stack { + raw: *mut stack_st_X509, +} + +impl OwnedX509Stack { + pub fn empty() -> Self { + Self { + raw: unsafe { OPENSSL_sk_new_null() as *mut stack_st_X509 }, + } + } + + pub fn push(&mut self, cert: &OwnedX509) { + unsafe { + OPENSSL_sk_push( + self.raw as *mut OPENSSL_STACK, + cert.up_ref() as *const c_void, + ); + } + } + + /// Leaks our pointer to the caller. + /// + /// We retain ownership. The caller could modify the returned + /// object under our feet; that is an inherent property of the + /// OpenSSL `SSL_get_peer_cert_chain` API. + pub fn pointer(&self) -> *mut stack_st_X509 { + self.raw + } +} + +impl Drop for OwnedX509Stack { + fn drop(&mut self) { + unsafe { + OPENSSL_sk_free(self.raw as *mut OPENSSL_STACK); + } + } +} + +/// Safe, owning wrapper around an OpenSSL X509 object. +pub struct OwnedX509 { + raw: *mut X509, +} + +impl OwnedX509 { + /// Create a new one, from parsing DER certificate data. + pub fn parse_der(data: &[u8]) -> Option { + let raw = unsafe { + d2i_X509( + ptr::null_mut(), + &mut data.as_ptr() as *mut *const u8, + data.len() as c_long, + ) + }; + + if raw.is_null() { + None + } else { + Some(Self { raw }) + } + } + + /// Give out our reference. + /// + /// This DOES NOT take a reference. See `SSL_get0_peer_certificate`. + pub fn borrow_ref(&self) -> *mut X509 { + self.raw + } + + /// Give out a new reference. + /// + /// See `SSL_get1_peer_certificate`. + pub fn up_ref(&self) -> *mut X509 { + unsafe { + if !self.raw.is_null() { + X509_up_ref(self.raw); + } + } + self.raw + } +} + +impl Drop for OwnedX509 { + fn drop(&mut self) { + unsafe { + X509_free(self.raw); + } + } +} + +pub struct OwnedX509Store { + raw: *mut X509_STORE, +} + +impl OwnedX509Store { + pub fn new() -> Self { + Self { + raw: unsafe { X509_STORE_new() }, + } + } + + pub fn pointer(&self) -> *mut X509_STORE { + self.raw + } +} + +impl Drop for OwnedX509Store { + fn drop(&mut self) { + unsafe { + X509_STORE_free(self.raw); + } + } +} + +extern "C" { + /// XXX: these missing from openssl-sys(?) investigate why that is. + fn OPENSSL_sk_free(st: *mut OPENSSL_STACK); + fn X509_up_ref(x: *mut X509) -> c_int; +} diff --git a/rustls-libssl/test-ca/rsa/ca.cert b/rustls-libssl/test-ca/rsa/ca.cert new file mode 100644 index 0000000..5dac1c9 --- /dev/null +++ b/rustls-libssl/test-ca/rsa/ca.cert @@ -0,0 +1,30 @@ +-----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/end.cert b/rustls-libssl/test-ca/rsa/end.cert new file mode 100644 index 0000000..fc36be9 --- /dev/null +++ b/rustls-libssl/test-ca/rsa/end.cert @@ -0,0 +1,24 @@ +-----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----- diff --git a/rustls-libssl/test-ca/rsa/end.key b/rustls-libssl/test-ca/rsa/end.key new file mode 100644 index 0000000..cb236b6 --- /dev/null +++ b/rustls-libssl/test-ca/rsa/end.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/test-ca/rsa/inter.cert b/rustls-libssl/test-ca/rsa/inter.cert new file mode 100644 index 0000000..c46d110 --- /dev/null +++ b/rustls-libssl/test-ca/rsa/inter.cert @@ -0,0 +1,28 @@ +-----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----- diff --git a/rustls-libssl/tests/ciphers.c b/rustls-libssl/tests/ciphers.c new file mode 100644 index 0000000..b593de7 --- /dev/null +++ b/rustls-libssl/tests/ciphers.c @@ -0,0 +1,67 @@ +/** + * Exercises SSL_CIPHER functions like `SSL_CIPHER_get_protocol_id` + */ + +#include + +#include + +static void print_cipher(const SSL_CIPHER *cipher) { + if (cipher) { + printf("openssl_id=0x%08x protocol_id=0x%08x ", SSL_CIPHER_get_id(cipher), + SSL_CIPHER_get_protocol_id(cipher)); + } else { + // SSL_CIPHER_get_id(NULL) and SSL_CIPHER_get_protocol_id(NULL) both + // segfault + printf("openssl_id=undef protocol_id=undef "); + } + int alg_bits = -1; + printf("bits=%d ", SSL_CIPHER_get_bits(cipher, &alg_bits)); + printf("alg_bits=%d\n", alg_bits); + + printf("name='%s' standard_name='%s' version='%s'\n", + SSL_CIPHER_get_name(cipher), SSL_CIPHER_standard_name(cipher), + SSL_CIPHER_get_version(cipher)); + if (cipher) { + char *desc = SSL_CIPHER_description(cipher, NULL, 0); + printf("desc='%s'\n", desc); + OPENSSL_free(desc); + } else { + // SSL_CIPHER_description(NULL) documented as working, it actually segfaults + printf("desc=undef\n"); + } +} + +static void cipher(SSL *ssl, uint16_t protocol_id) { + const uint8_t id_bytes[2] = { + (protocol_id & 0xff00) >> 8, + protocol_id & 0xff, + }; + const SSL_CIPHER *const cipher = SSL_CIPHER_find(ssl, id_bytes); + if (!cipher) { + printf("Nothing found for 0x%04x\n", protocol_id); + return; + } + printf("Found for 0x%04x\n", protocol_id); + print_cipher(cipher); +} + +int main(void) { + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); + SSL *ssl = SSL_new(ctx); + // We only care about SSL_CIPHERs representing suites implemented + // by rustls: this is not exhaustive. + cipher(ssl, 0xc02b); // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + cipher(ssl, 0xc02c); // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + cipher(ssl, 0xcca9); // TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + cipher(ssl, 0xc02f); // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + cipher(ssl, 0xc030); // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + cipher(ssl, 0xcca8); // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + cipher(ssl, 0x1301); // TLS13_AES_128_GCM_SHA256 + cipher(ssl, 0x1302); // TLS13_AES_256_GCM_SHA384 + cipher(ssl, 0x1303); // TLS13_CHACHA20_POLY1305_SHA256 + SSL_free(ssl); + SSL_CTX_free(ctx); + print_cipher(NULL); + return 0; +} diff --git a/rustls-libssl/tests/client.c b/rustls-libssl/tests/client.c new file mode 100644 index 0000000..a6e500e --- /dev/null +++ b/rustls-libssl/tests/client.c @@ -0,0 +1,141 @@ +/** + * Simple client test program. + * + * Expects to connect to an `openssl s_server -rev` server. + */ + +#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: "); + ERR_print_errors_fp(stdout); + } +} + +int main(int argc, char **argv) { + if (argc != 4) { + printf("%s \n\n", argv[0]); + return 1; + } + + const char *host = argv[1], *port = argv[2], *cacert = argv[3]; + + struct addrinfo *result = NULL; + TRACE(getaddrinfo(host, port, NULL, &result)); + + int sock = TRACE( + socket(result->ai_family, result->ai_socktype, result->ai_protocol)); + TRACE(connect(sock, result->ai_addr, result->ai_addrlen)); + freeaddrinfo(result); + + 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, "insecure") != 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("certificate verification disabled\n"); + } + 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(); + 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(); + + // check the alpn (also sees that SSL_connect 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("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"); + } + + // write some data and close + int wr = TRACE(SSL_write(ssl, "hello", 5)); + dump_openssl_error_stack(); + assert(wr == 5); + TRACE(SSL_shutdown(ssl)); + dump_openssl_error_stack(); + + // read back data, using SSL_pending on the way + char buf[10] = {0}; + int rd = TRACE(SSL_read(ssl, buf, 1)); + dump_openssl_error_stack(); + TRACE(SSL_pending(ssl)); + 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); + + close(sock); + SSL_free(ssl); + SSL_CTX_free(ctx); + + printf("PASS\n\n"); + return 0; +} diff --git a/rustls-libssl/tests/constants.c b/rustls-libssl/tests/constants.c new file mode 100644 index 0000000..df08867 --- /dev/null +++ b/rustls-libssl/tests/constants.c @@ -0,0 +1,15 @@ +/** + * Exercises openssl functions like `SSL_alert_desc_string_long` + */ + +#include + +#include + +int main(void) { + for (int i = -1; i < 260; i++) { + printf("%d: '%s' '%s'\n", i, SSL_alert_desc_string(i), + SSL_alert_desc_string_long(i)); + } + return 0; +} diff --git a/rustls-libssl/tests/runner.rs b/rustls-libssl/tests/runner.rs new file mode 100644 index 0000000..8ec13c6 --- /dev/null +++ b/rustls-libssl/tests/runner.rs @@ -0,0 +1,150 @@ +use std::process::{Child, Command, Output, Stdio}; +use std::{net, thread, time}; + +/* Note: + * + * In the tests below, we are relying on the fact that cargo sets + * `LD_LIBRARY_PATH` (or equivalent) so that artifacts it built + * are preferred to others. This means processes we run from here + * that depend on OpenSSL will use our libssl.so. + * + * We set LD_LIBRARY_PATH="" to disable this where we want + * to actually use OpenSSL's libssl. + */ + +#[test] +#[ignore] +fn client() { + 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", + "-accept", + "localhost:4443", + "-rev", + ]) + .env("LD_LIBRARY_PATH", "") + .spawn() + .expect("failed to start openssl s_server"), + ); + + wait_for_port(4443); + + let openssl_insecure_output = Command::new("target/client") + .env("LD_LIBRARY_PATH", "") + .args(&["localhost", "4443", "insecure"]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + let rustls_insecure_output = Command::new("target/client") + .args(&["localhost", "4443", "insecure"]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + assert_eq!(openssl_insecure_output, rustls_insecure_output); + + let openssl_secure_output = Command::new("target/client") + .env("LD_LIBRARY_PATH", "") + .args(&["localhost", "4443", "test-ca/rsa/ca.cert"]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + let rustls_secure_output = Command::new("target/client") + .args(&["localhost", "4443", "test-ca/rsa/ca.cert"]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + assert_eq!(openssl_secure_output, rustls_secure_output); +} + +#[test] +#[ignore] +fn constants() { + let openssl_output = Command::new("target/constants") + .env("LD_LIBRARY_PATH", "") + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + let rustls_output = Command::new("target/constants") + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + assert_eq!(openssl_output, rustls_output); +} + +#[test] +#[ignore] +fn ciphers() { + let openssl_output = Command::new("target/ciphers") + .env("LD_LIBRARY_PATH", "") + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + let rustls_output = Command::new("target/ciphers") + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + assert_eq!(openssl_output, rustls_output); +} + +struct KillOnDrop(Child); + +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"); + } +} + +fn print_output(out: Output) -> Output { + println!("status: {:?}\n", out.status); + println!( + "stdout:\n{}\n", + String::from_utf8(out.stdout.clone()).unwrap() + ); + println!( + "stderr:\n{}\n", + String::from_utf8(out.stderr.clone()).unwrap() + ); + out +} + +/// Wait until we can connect to localhost:port. +fn wait_for_port(port: u16) -> Option<()> { + let mut count = 0; + loop { + thread::sleep(time::Duration::from_millis(500)); + if net::TcpStream::connect(("localhost", port)).is_ok() { + return Some(()); + } + println!("waiting for port {port}"); + count += 1; + if count == 10 { + return None; + } + } +}