From a1b855e6c65b50fde1c5c17ca573f43b81abd19a Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Tue, 16 Jul 2024 13:32:37 -0400 Subject: [PATCH] implement X509_check_private_key Rustls 0.23.11 added a `keys_match()` function to `CertifiedKey` instances. This commit uses that new capability to implement `X509_check_private_key()`. The `EvpPkey` and `OpenSslKey` key types are both updated to enable implementing the new `sign::SigningKey` trait's optional `public_key()` fn. Following the precedent set upstream in Rustls we're permissive if we can't determine a key's SPKI and return `C_INT_SUCCESS` in this case. --- rustls-libssl/build.rs | 1 + rustls-libssl/src/entry.rs | 22 +++++++++++ rustls-libssl/src/evp_pkey.rs | 69 +++++++++++++++++++++++++++++++++-- rustls-libssl/src/sign.rs | 32 ++++++++++++++-- rustls-libssl/tests/client.c | 1 + rustls-libssl/tests/server.c | 1 + 6 files changed, 119 insertions(+), 7 deletions(-) diff --git a/rustls-libssl/build.rs b/rustls-libssl/build.rs index 641bd2e..a53a1b1 100644 --- a/rustls-libssl/build.rs +++ b/rustls-libssl/build.rs @@ -214,4 +214,5 @@ const ENTRYPOINTS: &[&str] = &[ "TLS_client_method", "TLS_method", "TLS_server_method", + "X509_check_private_key", ]; diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index 53733d5..8c151b7 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -27,6 +27,7 @@ use crate::ffi::{ try_slice, try_slice_int, try_str, Castable, OwnershipArc, OwnershipBox, OwnershipRef, }; use crate::not_thread_safe::NotThreadSafe; +use crate::sign::OpenSslCertifiedKey; use crate::x509::{load_certs, OwnedX509, OwnedX509Stack}; use crate::{conf, HandshakeState, ShutdownResult}; @@ -1840,6 +1841,27 @@ impl Castable for SSL_CONF_CTX { type RustType = NotThreadSafe; } +entry! { + pub fn _X509_check_private_key(cert: *mut X509, pkey: *mut EVP_PKEY) -> c_int { + if cert.is_null() || pkey.is_null() { + return 0; + } + + let chain = vec![CertificateDer::from( + OwnedX509::new_incref(cert).der_bytes(), + )]; + let Ok(certified_key) = OpenSslCertifiedKey::new(chain, EvpPkey::new_incref(pkey)) else { + return 0; + }; + + if certified_key.keys_match() { + C_INT_SUCCESS + } else { + 0 + } + } +} + /// Normal OpenSSL return value convention success indicator. /// /// Compare [`crate::ffi::MysteriouslyOppositeReturnValue`]. diff --git a/rustls-libssl/src/evp_pkey.rs b/rustls-libssl/src/evp_pkey.rs index 1e5d417..7e5c3dd 100644 --- a/rustls-libssl/src/evp_pkey.rs +++ b/rustls-libssl/src/evp_pkey.rs @@ -1,11 +1,13 @@ use core::ffi::{c_char, c_int, c_long, CStr}; use core::{fmt, ptr}; +use std::slice; use openssl_sys::{ - d2i_AutoPrivateKey, EVP_DigestSign, EVP_DigestSignInit, EVP_MD_CTX_free, EVP_MD_CTX_new, - EVP_PKEY_CTX_set_rsa_padding, EVP_PKEY_CTX_set_rsa_pss_saltlen, EVP_PKEY_CTX_set_signature_md, - EVP_PKEY_free, EVP_PKEY_up_ref, EVP_sha256, EVP_sha384, EVP_sha512, EVP_MD, EVP_MD_CTX, - EVP_PKEY, EVP_PKEY_CTX, RSA_PKCS1_PADDING, RSA_PKCS1_PSS_PADDING, + d2i_AutoPrivateKey, i2d_PUBKEY, EVP_DigestSign, EVP_DigestSignInit, EVP_MD_CTX_free, + EVP_MD_CTX_new, EVP_PKEY_CTX_set_rsa_padding, EVP_PKEY_CTX_set_rsa_pss_saltlen, + EVP_PKEY_CTX_set_signature_md, EVP_PKEY_free, EVP_PKEY_up_ref, EVP_sha256, EVP_sha384, + EVP_sha512, OPENSSL_free, EVP_MD, EVP_MD_CTX, EVP_PKEY, EVP_PKEY_CTX, RSA_PKCS1_PADDING, + RSA_PKCS1_PSS_PADDING, }; use rustls::pki_types::PrivateKeyDer; @@ -60,6 +62,26 @@ impl EvpPkey { } } + /// Return the Subject Public Key Info bytes for this key. + pub fn subject_public_key_info(&self) -> Vec { + let (ptr, len) = unsafe { + let mut ptr = ptr::null_mut(); + let len = i2d_PUBKEY(self.pkey, &mut ptr); + (ptr, len) + }; + + if len <= 0 { + return vec![]; + } + let len = len as usize; + + let mut v = Vec::with_capacity(len); + v.extend_from_slice(unsafe { slice::from_raw_parts(ptr, len) }); + + unsafe { OPENSSL_free(ptr as *mut _) }; + v + } + /// Caller borrows our reference. pub fn borrow_ref(&self) -> *mut EVP_PKEY { self.pkey as *mut EVP_PKEY @@ -305,6 +327,7 @@ extern "C" { #[cfg(all(test, not(miri)))] mod tests { use super::*; + use std::io::Cursor; #[test] fn supports_rsaencryption_keys() { @@ -346,4 +369,42 @@ mod tests { 256 ); } + + #[test] + fn pkey_spki() { + for (key_path, cert_path) in &[ + ("test-ca/rsa/server.key", "test-ca/rsa/server.cert"), + ( + "test-ca/ecdsa-p256/server.key", + "test-ca/ecdsa-p256/server.cert", + ), + ( + "test-ca/ecdsa-p384/server.key", + "test-ca/ecdsa-p384/server.cert", + ), + ( + "test-ca/ecdsa-p521/server.key", + "test-ca/ecdsa-p521/server.cert", + ), + ("test-ca/ed25519/server.key", "test-ca/ed25519/server.cert"), + ] { + let key_der = std::fs::read(key_path).unwrap(); + let cert_der = std::fs::read(cert_path).unwrap(); + + let key_der = rustls_pemfile::private_key(&mut Cursor::new(key_der)) + .unwrap() + .unwrap(); + let key = EvpPkey::new_from_der_bytes(key_der).unwrap(); + + let cert_der = rustls_pemfile::certs(&mut Cursor::new(cert_der)) + .next() + .unwrap() + .unwrap(); + let parsed_cert = rustls::server::ParsedCertificate::try_from(&cert_der).unwrap(); + + let cert_spki = parsed_cert.subject_public_key_info(); + let key_spki = key.subject_public_key_info(); + assert_eq!(&key_spki, cert_spki.as_ref()); + } + } } diff --git a/rustls-libssl/src/sign.rs b/rustls-libssl/src/sign.rs index 89f6414..9187be6 100644 --- a/rustls-libssl/src/sign.rs +++ b/rustls-libssl/src/sign.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use openssl_sys::{EVP_PKEY, X509}; use rustls::client::ResolvesClientCert; -use rustls::pki_types::CertificateDer; +use rustls::pki_types::{CertificateDer, SubjectPublicKeyInfoDer}; use rustls::server::{ClientHello, ResolvesServerCert}; use rustls::sign; use rustls::{SignatureAlgorithm, SignatureScheme}; @@ -88,14 +88,17 @@ impl CertifiedKeySet { } #[derive(Clone, Debug)] -struct OpenSslCertifiedKey { +pub(super) struct OpenSslCertifiedKey { key: EvpPkey, openssl_chain: OwnedX509Stack, rustls_chain: Vec>, } impl OpenSslCertifiedKey { - fn new(chain: Vec>, key: EvpPkey) -> Result { + pub(super) fn new( + chain: Vec>, + key: EvpPkey, + ) -> Result { Ok(Self { key, openssl_chain: OwnedX509Stack::from_rustls(&chain)?, @@ -103,6 +106,23 @@ impl OpenSslCertifiedKey { }) } + pub(super) fn keys_match(&self) -> bool { + match sign::CertifiedKey::new( + self.rustls_chain.clone(), + Arc::new(OpenSslKey(self.key.clone())), + ) + .keys_match() + { + // Note: we allow "Unknown" to be treated as success here. This is returned + // when it wasn't possible to get the SPKI for the private key, and so we + // aren't certain if it matches or not. + Ok(()) | Err(rustls::Error::InconsistentKeys(rustls::InconsistentKeys::Unknown)) => { + true + } + _ => false, + } + } + fn borrow_cert(&self) -> *mut X509 { self.openssl_chain.borrow_top_ref() } @@ -245,6 +265,12 @@ impl sign::SigningKey for OpenSslKey { } } + fn public_key(&self) -> Option> { + Some(SubjectPublicKeyInfoDer::from( + self.0.subject_public_key_info(), + )) + } + fn algorithm(&self) -> SignatureAlgorithm { self.0.algorithm() } diff --git a/rustls-libssl/tests/client.c b/rustls-libssl/tests/client.c index 4026904..3d4155a 100644 --- a/rustls-libssl/tests/client.c +++ b/rustls-libssl/tests/client.c @@ -79,6 +79,7 @@ int main(int argc, char **argv) { TRACE(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM)); client_key = SSL_CTX_get0_privatekey(ctx); client_cert = SSL_CTX_get0_certificate(ctx); + TRACE(X509_check_private_key(client_cert, client_key)); } TRACE(SSL_CTX_set_alpn_protos(ctx, (const uint8_t *)"\x02hi\x05world", 9)); diff --git a/rustls-libssl/tests/server.c b/rustls-libssl/tests/server.c index f1a6a29..8d56836 100644 --- a/rustls-libssl/tests/server.c +++ b/rustls-libssl/tests/server.c @@ -184,6 +184,7 @@ int main(int argc, char **argv) { TRACE(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM)); server_key = SSL_CTX_get0_privatekey(ctx); server_cert = SSL_CTX_get0_certificate(ctx); + TRACE(X509_check_private_key(server_cert, server_key)); printf("SSL_CTX_get_max_early_data default %lu\n", (unsigned long)SSL_CTX_get_max_early_data(ctx));