Skip to content

Commit

Permalink
Expose API to enable certificate compression.
Browse files Browse the repository at this point in the history
  • Loading branch information
mstyura authored and yyaroshevich committed Nov 27, 2024
1 parent 2b75e1e commit 2245501
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ tower = "0.4"
tower-layer = "0.3"
tower-service = "0.3"
autocfg = "1.3.0"
brotli = "6.0"
1 change: 1 addition & 0 deletions boring/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,4 @@ boring-sys = { workspace = true }
[dev-dependencies]
hex = { workspace = true }
rusty-hook = { workspace = true }
brotli = { workspace = true }
93 changes: 92 additions & 1 deletion boring/src/ssl/callbacks.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![forbid(unsafe_op_in_unsafe_fn)]

use super::{
AlpnError, ClientHello, GetSessionPendingError, PrivateKeyMethod, PrivateKeyMethodError,
AlpnError, CertificateCompressor, ClientHello, GetSessionPendingError, PrivateKeyMethod, PrivateKeyMethodError,
SelectCertError, SniError, Ssl, SslAlert, SslContext, SslContextRef, SslInfoCallbackAlert,
SslInfoCallbackMode, SslInfoCallbackValue, SslRef, SslSession, SslSessionRef,
SslSignatureAlgorithm, SslVerifyError, SESSION_CTX_INDEX,
Expand Down Expand Up @@ -579,3 +579,94 @@ pub(super) unsafe extern "C" fn raw_info_callback<F>(

callback(ssl, SslInfoCallbackMode(mode), value);
}

pub(super) unsafe extern "C" fn raw_ssl_cert_compress<C>(
ssl: *mut ffi::SSL,
out: *mut ffi::CBB,
input: *const u8,
input_len: usize,
) -> ::std::os::raw::c_int
where
C: CertificateCompressor,
{
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr_mut(ssl) };

let ssl_context = ssl.ssl_context().to_owned();
let compressor = ssl_context
.ex_data(SslContext::cached_ex_index::<C>())
.expect("BUG: certificate compression missed");

if !compressor.can_compress() {
return 0;
}
let input_slice = unsafe { std::slice::from_raw_parts(input, input_len) };
let mut writer = CryptoByteBuilder(out);
if compressor.compress(input_slice, &mut writer).is_err() {
return 0;
}

return 1;
}

pub(super) unsafe extern "C" fn raw_ssl_cert_decompress<C>(
ssl: *mut ffi::SSL,
out: *mut *mut ffi::CRYPTO_BUFFER,
uncompressed_len: usize,
input: *const u8,
input_len: usize,
) -> ::std::os::raw::c_int
where
C: CertificateCompressor,
{
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr_mut(ssl) };

let ssl_context = ssl.ssl_context().to_owned();
let compressor = ssl_context
.ex_data(SslContext::cached_ex_index::<C>())
.expect("BUG: certificate compression missed");

let mut data: *mut u8 = std::ptr::null_mut();

let decompressed = unsafe { boring_sys::CRYPTO_BUFFER_alloc(&mut data, uncompressed_len) };

if decompressed.is_null() {
return 0;
}

let input_slice = unsafe { std::slice::from_raw_parts(input, input_len) };

let output_slice = unsafe { std::slice::from_raw_parts_mut(data, uncompressed_len) };

let mut cursor = std::io::Cursor::new(output_slice);
if compressor.decompress(input_slice, &mut cursor).is_err() {
return 0;
}
if cursor.position() != uncompressed_len as u64 {
return 0;
}

unsafe { *out = decompressed };
1
}

struct CryptoByteBuilder(*mut ffi::CBB);

impl std::io::Write for CryptoByteBuilder {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let success = unsafe { ffi::CBB_add_bytes(self.0, buf.as_ptr(), buf.len()) == 1 };
if !success {
return Err(std::io::Error::other("CBB_add_bytes failed"));
}
Ok(buf.len())
}

fn flush(&mut self) -> std::io::Result<()> {
let success = unsafe { ffi::CBB_flush(self.0) == 1 };
if !success {
return Err(std::io::Error::other("CBB_flush failed"));
}
Ok(())
}
}
84 changes: 84 additions & 0 deletions boring/src/ssl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,16 @@ impl CompliancePolicy {
Self(ffi::ssl_compliance_policy_t::ssl_compliance_policy_wpa3_192_202304);
}

// IANA assigned identifier of compression algorithm. See https://www.rfc-editor.org/rfc/rfc8879.html#name-compression-algorithms
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CertificateCompressionAlgorithm(u16);

impl CertificateCompressionAlgorithm {
pub const ZLIB: Self = Self(ffi::TLSEXT_cert_compression_zlib as u16);

pub const BROTLI: Self = Self(ffi::TLSEXT_cert_compression_brotli as u16);
}

/// A standard implementation of protocol selection for Application Layer Protocol Negotiation
/// (ALPN).
///
Expand Down Expand Up @@ -1594,6 +1604,46 @@ impl SslContextBuilder {
}
}

/// Registers a certificate compression algorithm.
///
/// Corresponds to [`SSL_CTX_add_cert_compression_alg`].
///
/// [`SSL_CTX_add_cert_compression_alg`]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_CTX_add_cert_compression_alg
pub fn add_certificate_compression_algorithm<C>(
&mut self,
compressor: C,
) -> Result<(), ErrorStack>
where
C: CertificateCompressor,
{
let algo = compressor.algorithm();
let (can_compress, can_decompress) =
(compressor.can_compress(), compressor.can_decompress());
assert!(can_compress || can_decompress);
let success = unsafe {
self.replace_ex_data(SslContext::cached_ex_index::<C>(), compressor);

ffi::SSL_CTX_add_cert_compression_alg(
self.as_ptr(),
algo.0,
if can_compress {
Some(callbacks::raw_ssl_cert_compress::<C>)
} else {
None
},
if can_decompress {
Some(callbacks::raw_ssl_cert_decompress::<C>)
} else {
None
},
) == 1
};
if !success {
return Err(ErrorStack::get());
}
Ok(())
}

/// Configures a custom private key method on the context.
///
/// See [`PrivateKeyMethod`] for more details.
Expand Down Expand Up @@ -4260,6 +4310,40 @@ impl PrivateKeyMethodError {
pub const RETRY: Self = Self(ffi::ssl_private_key_result_t::ssl_private_key_retry);
}

/// Describes certificate compression algorithm. Implementation MUST implement transformation at least in one direction.
pub trait CertificateCompressor: Send + Sync + 'static {
/// An IANA assigned identifier of compression algorithm
fn algorithm(&self) -> CertificateCompressionAlgorithm;

/// Indicates if compressor support compression
fn can_compress(&self) -> bool {
false
}

/// Indicates if compressor support decompression
fn can_decompress(&self) -> bool {
false
}

/// Perform compression of `input` buffer and write compressed data to `output`.
#[allow(unused_variables)]
fn compress<W>(&self, input: &[u8], output: &mut W) -> std::io::Result<()>
where
W: std::io::Write,
{
Err(std::io::Error::other("not implemented"))
}

/// Perform decompression of `input` buffer and write compressed data to `output`.
#[allow(unused_variables)]
fn decompress<W>(&self, input: &[u8], output: &mut W) -> std::io::Result<()>
where
W: std::io::Write,
{
Err(std::io::Error::other("not implemented"))
}
}

use crate::ffi::{SSL_CTX_up_ref, SSL_SESSION_get_master_key, SSL_SESSION_up_ref, SSL_is_server};

use crate::ffi::{DTLS_method, TLS_client_method, TLS_method, TLS_server_method};
Expand Down
107 changes: 107 additions & 0 deletions boring/src/ssl/test/cert_compressor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use std::io::Write as _;

use super::server::Server;
use crate::ssl::CertificateCompressor;
use crate::x509::store::X509StoreBuilder;
use crate::x509::X509;

struct BrotliCompressor {
q: u32,
lgwin: u32,
}

impl Default for BrotliCompressor {
fn default() -> Self {
Self { q: 11, lgwin: 32 }
}
}

impl CertificateCompressor for BrotliCompressor {
fn algorithm(&self) -> crate::ssl::CertificateCompressionAlgorithm {
crate::ssl::CertificateCompressionAlgorithm(1234)
}

fn can_compress(&self) -> bool {
true
}

fn can_decompress(&self) -> bool {
true
}

fn compress<W>(&self, input: &[u8], output: &mut W) -> std::io::Result<()>
where
W: std::io::Write,
{
let mut writer = brotli::CompressorWriter::new(output, 1024, self.q, self.lgwin);
writer.write_all(&input)?;
Ok(())
}

fn decompress<W>(&self, input: &[u8], output: &mut W) -> std::io::Result<()>
where
W: std::io::Write,
{
brotli::BrotliDecompress(&mut std::io::Cursor::new(input), output)?;
Ok(())
}
}

#[test]
fn server_only_cert_compression() {
let mut server_builder = Server::builder();
server_builder
.ctx()
.add_certificate_compression_algorithm(BrotliCompressor::default())
.unwrap();

let server = server_builder.build();

let mut store = X509StoreBuilder::new().unwrap();
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
store.add_cert(x509).unwrap();

let client = server.client();

client.connect();
}

#[test]
fn client_only_cert_compression() {
let server_builder = Server::builder().build();

let mut store = X509StoreBuilder::new().unwrap();
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
store.add_cert(x509).unwrap();

let mut client = server_builder.client();
client
.ctx()
.add_certificate_compression_algorithm(BrotliCompressor::default())
.unwrap();

client.connect();
}

#[test]
fn client_and_server_cert_compression() {
let mut server = Server::builder();
server
.ctx()
.add_certificate_compression_algorithm(BrotliCompressor::default())
.unwrap();

let server = server.build();

let mut store = X509StoreBuilder::new().unwrap();
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
store.add_cert(x509).unwrap();

let mut client = server.client();
client
.ctx()
.add_certificate_compression_algorithm(BrotliCompressor::default())
.unwrap();

client.connect();
}
1 change: 1 addition & 0 deletions boring/src/ssl/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::x509::{X509Name, X509};
#[cfg(not(feature = "fips"))]
use super::CompliancePolicy;

mod cert_compressor;
mod cert_verify;
mod custom_verify;
mod private_key_method;
Expand Down

0 comments on commit 2245501

Please sign in to comment.