From 57024f4fb44b897985078231a2c419931e19fc4e Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Sun, 23 Jun 2024 14:11:02 -0400 Subject: [PATCH] conf: implement Certificate/PrivateKey commands --- rustls-libssl/src/conf.rs | 64 ++++++++++++++++++++++++++++- rustls-libssl/src/entry.rs | 56 ++++++++++++++----------- rustls-libssl/tests/config.c | 58 +++++++++++++++++++++++++- rustls-libssl/tests/nginx_1_24.conf | 8 +++- rustls-libssl/tests/runner.rs | 2 + 5 files changed, 158 insertions(+), 30 deletions(-) diff --git a/rustls-libssl/src/conf.rs b/rustls-libssl/src/conf.rs index 6b4893b..f86c433 100644 --- a/rustls-libssl/src/conf.rs +++ b/rustls-libssl/src/conf.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use rustls::ProtocolVersion; +use crate::entry::{use_cert_chain_file, use_private_key_file, FILETYPE_PEM}; use crate::error::Error; use crate::not_thread_safe::NotThreadSafe; use crate::{Ssl, SslContext, VerifyMode}; @@ -174,6 +175,51 @@ impl SslConfigCtx { }) } + fn certificate(&mut self, path: Option<&str>) -> Result { + let path = match path { + Some(path) => path, + None => return Ok(ActionResult::ValueRequired), + }; + let cert_chain = use_cert_chain_file(path)?; + + Ok(match &self.state { + State::Validating => ActionResult::Applied, + State::ApplyingToCtx(ctx) => { + // the "Certificate" command after `SSL_CONF_CTX_set_ssl_ctx` is documented as using + // `SSL_CTX_use_certificate_chain_file`. + ctx.get_mut().stage_certificate_chain(cert_chain); + ActionResult::Applied + } + State::ApplyingToSsl(_) => { + // the "Certificate" command after `SSL_CONF_CTX_set_ssl` is documented as using + // `SSL_use_certificate_file` - this is NYI for rustls-libssl. + return Err(Error::not_supported( + "Certificate with SSL structure not supported", + )); + } + }) + } + + fn private_key(&mut self, path: Option<&str>) -> Result { + let path = match path { + Some(path) => path, + None => return Ok(ActionResult::ValueRequired), + }; + let key = use_private_key_file(path, FILETYPE_PEM)?; + + match &self.state { + State::Validating => Ok(ActionResult::Applied), + State::ApplyingToCtx(ctx) => { + ctx.get_mut().commit_private_key(key)?; + Ok(ActionResult::Applied) + } + State::ApplyingToSsl(ssl) => { + ssl.get_mut().commit_private_key(key)?; + Ok(ActionResult::Applied) + } + } + } + fn parse_protocol_version(proto: Option<&str>) -> Option { Some(match proto { Some("None") => 0, @@ -226,8 +272,8 @@ pub(super) enum ValueType { Unknown = 0x0, /// The option value is a string without any specific structure. String = 0x1, - // The option value is a filename. - //File = 0x2, + /// The option value is a filename. + File = 0x2, // The option value is a directory name. //Dir = 0x3, // The option value is not used. @@ -400,4 +446,18 @@ const SUPPORTED_COMMANDS: &[Command] = &[ value_type: ValueType::String, action: SslConfigCtx::verify_mode, }, + Command { + name_file: Some("Certificate"), + name_cmdline: Some("cert"), + flags: Flags(Flags::CERTIFICATE), + value_type: ValueType::File, + action: SslConfigCtx::certificate, + }, + Command { + name_file: Some("PrivateKey"), + name_cmdline: Some("key"), + flags: Flags(Flags::CERTIFICATE), + value_type: ValueType::File, + action: SslConfigCtx::private_key, + }, ]; diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index fe67600..56c6e4c 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -416,36 +416,42 @@ entry! { 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 chain = match use_cert_chain_file(try_str!(file_name)) { + Ok(chain) => chain, + Err(err) => return err.raise().into(), }; - let mut chain = Vec::new(); + ctx.get_mut().stage_certificate_chain(chain); + C_INT_SUCCESS + } +} - 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(); - } - }; +pub(crate) fn use_cert_chain_file(file_name: &str) -> Result>, Error> { + let mut file_reader = match fs::File::open(file_name) { + Ok(content) => io::BufReader::new(content), + Err(err) => return Err(Error::from_io(err)), + }; - 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(); - } + 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 Err(Error::from_io(err)); } - } + }; - ctx.get_mut().stage_certificate_chain(chain); - C_INT_SUCCESS + match OwnedX509::parse_der(cert.as_ref()) { + Some(_) => chain.push(cert), + None => { + log::trace!("Failed to parse DER certificate"); + return Err(Error::bad_data("certificate")); + } + } } + + Ok(chain) } entry! { @@ -464,7 +470,7 @@ entry! { } } -fn use_private_key_file(file_name: &str, file_type: c_int) -> Result { +pub(crate) fn use_private_key_file(file_name: &str, file_type: c_int) -> Result { let der_data = match file_type { FILETYPE_PEM => { let mut file_reader = match fs::File::open(file_name) { @@ -1806,7 +1812,7 @@ impl Castable for SSL_CONF_CTX { /// Compare [`crate::ffi::MysteriouslyOppositeReturnValue`]. const C_INT_SUCCESS: c_int = 1; -const FILETYPE_PEM: c_int = 1; +pub(crate) const FILETYPE_PEM: c_int = 1; const FILETYPE_DER: c_int = 2; const SSL_MAX_SID_CTX_LENGTH: usize = 32; diff --git a/rustls-libssl/tests/config.c b/rustls-libssl/tests/config.c index b5c4863..637d64a 100644 --- a/rustls-libssl/tests/config.c +++ b/rustls-libssl/tests/config.c @@ -22,7 +22,12 @@ static const char *supported_cmds[] = { "MaxProtocol", CUSTOM_PREFIX "MaxProtocol", "VerifyMode", CUSTOM_PREFIX "VerifyMode", -}; + + "-cert", CUSTOM_PREFIX "cert", + "Certificate", CUSTOM_PREFIX "Certificate", + + "-key", CUSTOM_PREFIX "key", + "PrivateKey", CUSTOM_PREFIX "PrivateKey"}; #define NUM_SUPPORTED_CMDS (sizeof(supported_cmds) / sizeof(supported_cmds[0])) @@ -230,6 +235,54 @@ void test_verify_mode(void) { SSL_free(ssl); } +void set_cert_and_key(SSL_CONF_CTX *cctx) { + // Note: we don't test invalid values here - our implementation diverges + // slightly due to early processing of the cert/key pair. + printf("\t\tcmd Certificate NULL returns %d\n", + SSL_CONF_cmd(cctx, "Certificate", NULL)); + printf("\t\tcmd Certificate 'test-ca/rsa/server.cert' returns %d\n", + SSL_CONF_cmd(cctx, "Certificate", "test-ca/rsa/server.cert")); + + printf("\t\tcmd PrivateKey NULL returns %d\n", + SSL_CONF_cmd(cctx, "PrivateKey", NULL)); + printf("\t\tcmd PrivateKey 'test-ca/rsa/server.key' returns %d\n", + SSL_CONF_cmd(cctx, "PrivateKey", "test-ca/rsa/server.key")); +} + +void test_certificate_and_private_key(void) { + SSL_CONF_CTX *cctx = SSL_CONF_CTX_new(); + assert(cctx != NULL); + + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE); + printf("\tPre-ctx (not certificate flag):\n"); + set_cert_and_key(cctx); + + printf("\tPre-ctx (certificate flag):\n"); + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CERTIFICATE); + set_cert_and_key(cctx); + SSL_CONF_CTX_clear_flags(cctx, SSL_CONF_FLAG_CERTIFICATE); + + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); + assert(ctx != NULL); + SSL_CONF_CTX_set_ssl_ctx(cctx, ctx); + + printf("\tWith ctx (not certificate flag):\n"); + set_cert_and_key(cctx); + + printf("\tWith ctx (certificate flag):\n"); + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CERTIFICATE); + set_cert_and_key(cctx); + SSL_CONF_CTX_clear_flags(cctx, SSL_CONF_FLAG_CERTIFICATE); + + // Note: we do not test with `SSL_CONF_set_ssl()` here - we lack + // support for the `Certificate` command updating an `SSL` + // struct at this time. + + assert(SSL_CONF_CTX_finish(cctx)); + SSL_CONF_CTX_free(cctx); + SSL_CTX_free(ctx); +} + int main(void) { printf("Supported commands:\n"); printf("no base flags, default prefix:\n"); @@ -255,4 +308,7 @@ int main(void) { printf("VerifyMode:\n"); test_verify_mode(); + + printf("Certificate/PrivateKey:\n"); + test_certificate_and_private_key(); } diff --git a/rustls-libssl/tests/nginx_1_24.conf b/rustls-libssl/tests/nginx_1_24.conf index 1bcf8b3..019b1d2 100644 --- a/rustls-libssl/tests/nginx_1_24.conf +++ b/rustls-libssl/tests/nginx_1_24.conf @@ -13,11 +13,15 @@ http { server { # Custom configuration w/ ssl_conf_command: # * TLS 1.3 or greater only + # * Certificate override of ssl_certificate + # * PrivateKey override of ssl_certificate_key listen 8447 ssl; - ssl_certificate ../../../test-ca/rsa/server.cert; - ssl_certificate_key ../../../test-ca/rsa/server.key; + ssl_certificate ../../../test-ca/ed25519/server.cert; + ssl_certificate_key ../../../test-ca/ed25519/server.key; server_name localhost; + ssl_conf_command Certificate ../../../test-ca/rsa/server.cert; + ssl_conf_command PrivateKey ../../../test-ca/rsa/server.key; ssl_conf_command MinProtocol TLSv1.3; location = / { diff --git a/rustls-libssl/tests/runner.rs b/rustls-libssl/tests/runner.rs index a9bab0e..2fb2b7a 100644 --- a/rustls-libssl/tests/runner.rs +++ b/rustls-libssl/tests/runner.rs @@ -613,6 +613,8 @@ fn nginx_1_24() { 35 ); // TLS 1.3 to the TLS 1.3 only port should succeed. + // The RSA CA cert should allow verification to succeed, showing the overrides of + // the ED25519 ssl_certificate/ssl_certificate_key directives worked. assert_eq!( Command::new("curl") .env("LD_LIBRARY_PATH", "")