use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use crate::error::{last_error, Error, Result};
use crate::ffi::Ssl;
use crate::ktls::cipher::KtlsCipher;
#[derive(Debug, Clone)]
pub struct KtlsEligibility {
tls_version: String,
cipher: String,
compatible: bool,
}
impl KtlsEligibility {
#[must_use]
pub fn tls_version(&self) -> &str {
&self.tls_version
}
#[must_use]
pub fn cipher(&self) -> &str {
&self.cipher
}
#[must_use]
pub fn is_compatible(&self) -> bool {
self.compatible
}
pub(crate) unsafe fn from_ssl(ssl: &Ssl) -> Self {
let tls_version = unsafe { cstr_to_string(aws_lc_sys::SSL_get_version(ssl.as_ptr())) }
.unwrap_or_else(|| "(unknown)".into());
let cipher_ptr = unsafe { aws_lc_sys::SSL_get_current_cipher(ssl.as_ptr()) };
let cipher = if cipher_ptr.is_null() {
String::new()
} else {
unsafe { cstr_to_string(aws_lc_sys::SSL_CIPHER_get_name(cipher_ptr)) }
.unwrap_or_default()
};
Self {
tls_version,
cipher,
compatible: KtlsCipher::detect(ssl).is_some(),
}
}
}
#[derive(Debug, Clone)]
pub struct NegotiatedSession {
version: String,
cipher: String,
alpn: Option<Vec<u8>>,
sni: Option<String>,
}
impl NegotiatedSession {
#[must_use]
pub fn version(&self) -> &str {
&self.version
}
#[must_use]
pub fn cipher(&self) -> &str {
&self.cipher
}
#[must_use]
pub fn alpn(&self) -> Option<&[u8]> {
self.alpn.as_deref()
}
#[must_use]
pub fn sni(&self) -> Option<&str> {
self.sni.as_deref()
}
pub(crate) unsafe fn from_ssl(ssl: *mut aws_lc_sys::SSL) -> Self {
let version = unsafe { cstr_to_string(aws_lc_sys::SSL_get_version(ssl)) }
.unwrap_or_else(|| "(unknown)".into());
let cipher_ptr = unsafe { aws_lc_sys::SSL_get_current_cipher(ssl) };
let cipher = if cipher_ptr.is_null() {
String::new()
} else {
unsafe { cstr_to_string(aws_lc_sys::SSL_CIPHER_get_name(cipher_ptr)) }
.unwrap_or_default()
};
let mut alpn_ptr: *const u8 = std::ptr::null();
let mut alpn_len: u32 = 0;
unsafe {
aws_lc_sys::SSL_get0_alpn_selected(ssl, &raw mut alpn_ptr, &raw mut alpn_len);
}
let alpn = if alpn_ptr.is_null() || alpn_len == 0 {
None
} else {
let slice = unsafe { std::slice::from_raw_parts(alpn_ptr, alpn_len as usize) };
Some(slice.to_vec())
};
let sni_ptr = unsafe {
aws_lc_sys::SSL_get_servername(ssl, aws_lc_sys::TLSEXT_NAMETYPE_host_name as c_int)
};
let sni = if sni_ptr.is_null() {
None
} else {
unsafe { cstr_to_string(sni_ptr) }
};
Self {
version,
cipher,
alpn,
sni,
}
}
}
unsafe fn cstr_to_string(p: *const c_char) -> Option<String> {
if p.is_null() {
return None;
}
let s = unsafe { CStr::from_ptr(p) };
Some(s.to_string_lossy().into_owned())
}
pub(crate) unsafe fn export_keying_material(
ssl: *mut aws_lc_sys::SSL,
out: &mut [u8],
label: &[u8],
context: Option<&[u8]>,
) -> Result<()> {
let (ctx_ptr, ctx_len, use_ctx) = match context {
Some(c) => (c.as_ptr(), c.len(), 1),
None => (std::ptr::null(), 0usize, 0),
};
let ok = unsafe {
aws_lc_sys::SSL_export_keying_material(
ssl,
out.as_mut_ptr(),
out.len(),
label.as_ptr().cast(),
label.len(),
ctx_ptr,
ctx_len,
use_ctx,
)
};
if ok == 1 {
Ok(())
} else {
Err(Error::Handshake(format!(
"SSL_export_keying_material: {}",
last_error()
)))
}
}