use crate::ffi::errors::{ErrorHandle, Status};
use crate::ffi::{Buffer, Slice};
use crate::tls::{ECHConfigList, ECHKeys, Identity};
#[repr(C)]
#[derive(Clone, Copy)]
pub struct TLSConfig {
pub ciphers: Slice,
pub groups: Slice,
pub signature_algorithms: Slice,
pub prefer_server_ciphers: bool,
pub session_tickets: bool,
pub early_data: bool,
pub certificate_compression: bool,
}
impl TLSConfig {
pub unsafe fn parse(&self) -> Option<crate::tls::TLSConfig> {
let text = |slice: Slice| match slice.data.is_null() {
true => Some(None),
false => unsafe { Slice::borrow_text(slice.data, slice.len) }.map(|text| Some(text.to_owned())),
};
Some(crate::tls::TLSConfig {
ciphers: text(self.ciphers)?,
groups: text(self.groups)?,
signature_algorithms: text(self.signature_algorithms)?,
prefer_server_ciphers: self.prefer_server_ciphers,
session_tickets: self.session_tickets,
early_data: self.early_data,
certificate_compression: self.certificate_compression,
})
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_tls_config_default() -> TLSConfig {
let config = crate::tls::TLSConfig::default();
TLSConfig {
ciphers: Slice::ABSENT,
groups: Slice::ABSENT,
signature_algorithms: Slice::ABSENT,
prefer_server_ciphers: config.prefer_server_ciphers,
session_tickets: config.session_tickets,
early_data: config.early_data,
certificate_compression: config.certificate_compression,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_identity_new(certificates: *const Slice, certificate_count: usize, key: *const u8, key_len: usize) -> *mut Identity {
if certificates.is_null() {
return std::ptr::null_mut();
}
let Some(key) = (unsafe { Slice::borrow(key, key_len) }) else {
return std::ptr::null_mut();
};
let mut chain = Vec::with_capacity(certificate_count);
for index in 0..certificate_count {
let slice = unsafe { *certificates.add(index) };
let Some(blob) = (unsafe { Slice::borrow(slice.data, slice.len) }) else {
return std::ptr::null_mut();
};
chain.push(blob.to_vec());
}
Box::into_raw(Box::new(Identity::new(chain, key.to_vec())))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_identity_from_pkcs12(data: *const u8, data_len: usize, passphrase: *const u8, passphrase_len: usize, out: *mut *mut Identity, error: *mut *mut ErrorHandle) -> Status {
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
let (Some(data), Some(passphrase)) = (unsafe { Slice::borrow(data, data_len) }, unsafe { Slice::borrow_text(passphrase, passphrase_len) }) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
match Identity::from_pkcs12(data, passphrase) {
Ok(identity) => {
unsafe { *out = Box::into_raw(Box::new(identity)) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_identity_free(identity: *mut Identity) {
if !identity.is_null() {
drop(unsafe { Box::from_raw(identity) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_ech_keys_generate(public_name: *const u8, public_name_len: usize, config_id: u8, out: *mut *mut ECHKeys, error: *mut *mut ErrorHandle) -> Status {
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
let Some(public_name) = (unsafe { Slice::borrow_text(public_name, public_name_len) }) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
if public_name.len() > 255 {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
match ECHKeys::generate(public_name, config_id) {
Ok(keys) => {
unsafe { *out = Box::into_raw(Box::new(keys)) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_ech_keys_new(config: *const u8, config_len: usize, private_key: *const u8, private_key_len: usize) -> *mut ECHKeys {
let (Some(config), Some(private_key)) = (unsafe { Slice::borrow(config, config_len) }, unsafe { Slice::borrow(private_key, private_key_len) }) else {
return std::ptr::null_mut();
};
Box::into_raw(Box::new(ECHKeys { config: config.to_vec(), private_key: private_key.to_vec() }))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_ech_keys_free(keys: *mut ECHKeys) {
if !keys.is_null() {
drop(unsafe { Box::from_raw(keys) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_ech_keys_config(keys: *const ECHKeys) -> Slice {
match unsafe { keys.as_ref() } {
Some(keys) => Slice::new(&keys.config),
None => Slice::ABSENT,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_ech_keys_private_key(keys: *const ECHKeys) -> Slice {
match unsafe { keys.as_ref() } {
Some(keys) => Slice::new(&keys.private_key),
None => Slice::ABSENT,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_ech_keys_config_list(keys: *const ECHKeys) -> Buffer {
match unsafe { keys.as_ref() } {
Some(keys) => Buffer::new(keys.config_list()),
None => Buffer::EMPTY,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_ech_config_list_parse(data: *const u8, data_len: usize, out: *mut *mut ECHConfigList, error: *mut *mut ErrorHandle) -> Status {
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
let Some(data) = (unsafe { Slice::borrow(data, data_len) }) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
match ECHConfigList::parse(data) {
Ok(list) => {
unsafe { *out = Box::into_raw(Box::new(list)) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_ech_config_list_free(list: *mut ECHConfigList) {
if !list.is_null() {
drop(unsafe { Box::from_raw(list) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_ech_config_list_count(list: *const ECHConfigList) -> usize {
unsafe { list.as_ref() }.map_or(0, |list| list.configs.len())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_ech_config_version(list: *const ECHConfigList, index: usize) -> u16 {
unsafe { list.as_ref() }.and_then(|list| list.configs.get(index)).map_or(0, |config| config.version)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_ech_config_public_name(list: *const ECHConfigList, index: usize) -> Slice {
Slice::maybe(unsafe { list.as_ref() }.and_then(|list| list.configs.get(index)).map(|config| config.public_name.as_str()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_ech_config_maximum_name_length(list: *const ECHConfigList, index: usize) -> i32 {
unsafe { list.as_ref() }
.and_then(|list| list.configs.get(index))
.map_or(-1, |config| config.maximum_name_length as i32)
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ECHEntry {
pub host: Slice,
pub config_list: Slice,
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_tls_version_1_3() -> u16 {
crate::tls::TLSVersion::V1_3.0
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Format {
DER = 0,
PEM = 1,
}
impl Format {
pub fn build(format: crate::tls::Format) -> Self {
match format {
crate::tls::Format::DER => Self::DER,
crate::tls::Format::PEM => Self::PEM,
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_format_sequence() -> u8 {
crate::tls::Format::SEQUENCE
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_format_of(raw: *const u8, raw_len: usize) -> Format {
Format::build(crate::tls::Format::of(unsafe { Slice::borrow(raw, raw_len) }.unwrap_or_default()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_format_certificate_count(raw: *const u8, raw_len: usize) -> isize {
let raw = unsafe { Slice::borrow(raw, raw_len) }.unwrap_or_default();
match crate::tls::Format::certificates(raw) {
Ok(certificates) => certificates.len() as isize,
Err(_) => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_format_certificate(raw: *const u8, raw_len: usize, index: usize) -> Buffer {
let raw = unsafe { Slice::borrow(raw, raw_len) }.unwrap_or_default();
let Ok(certificates) = crate::tls::Format::certificates(raw) else {
return Buffer::EMPTY;
};
match certificates.get(index).and_then(|certificate| certificate.to_der().ok()) {
Some(der) => Buffer::new(der),
None => Buffer::EMPTY,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_format_private_key(raw: *const u8, raw_len: usize) -> Buffer {
let raw = unsafe { Slice::borrow(raw, raw_len) }.unwrap_or_default();
match crate::tls::Format::private_key(raw).and_then(|key| key.private_key_to_der().map_err(crate::errors::Error::tls)) {
Ok(der) => Buffer::new(der),
Err(_) => Buffer::EMPTY,
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Security {
pub secure: bool,
pub early_data: bool,
pub tls: bool,
pub tls_version: i32,
pub tls_group: i32,
pub tls_cipher: i32,
pub quic: bool,
pub quic_version: i64,
}
impl Security {
pub fn build(security: &crate::tls::Security) -> Self {
Self {
secure: security.secure,
early_data: security.early_data,
tls: security.tls,
tls_version: security.tls_version.map_or(-1, |version| version.0 as i32),
tls_group: security.tls_group.map_or(-1, |group| group.0 as i32),
tls_cipher: security.tls_cipher.map_or(-1, |cipher| cipher.0 as i32),
quic: security.quic,
quic_version: security.quic_version.map_or(-1, |version| version as i64),
}
}
pub fn parse(&self) -> crate::tls::Security {
crate::tls::Security {
secure: self.secure,
early_data: self.early_data,
tls: self.tls,
tls_version: u16::try_from(self.tls_version).ok().map(crate::tls::TLSVersion),
tls_group: u16::try_from(self.tls_group).ok().map(crate::tls::TLSGroup),
tls_cipher: u16::try_from(self.tls_cipher).ok().map(crate::tls::TLSCipher),
quic: self.quic,
quic_version: u32::try_from(self.quic_version).ok(),
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_security_default() -> Security {
Security::build(&crate::tls::Security::default())
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_security_quic(quic_version: i64) -> Security {
Security::build(&crate::tls::Security::quic(u32::try_from(quic_version).ok()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_security_apply(security: *const Security, message: *mut crate::models::Message) -> bool {
let (Some(security), Some(message)) = (unsafe { security.as_ref() }, unsafe { message.as_mut() }) else {
return false;
};
security.parse().apply(message);
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_security(message: *const crate::models::Message) -> Security {
match unsafe { message.as_ref() } {
Some(message) => Security::build(&message.security),
None => Security::build(&crate::tls::Security::default()),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_ech_config_supported_version() -> u16 {
crate::tls::ECHConfig::VERSION
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_ech_kem_x25519_hkdf_sha256() -> u16 {
ECHKeys::KEM_X25519_HKDF_SHA256
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_ech_kdf_hkdf_sha256() -> u16 {
ECHKeys::KDF_HKDF_SHA256
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_ech_aead_aes_128_gcm() -> u16 {
ECHKeys::AEAD_AES_128_GCM
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_ech_maximum_name_length() -> u8 {
ECHKeys::MAXIMUM_NAME_LENGTH
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_ech_keys_encode(public_name: *const u8, public_name_len: usize, config_id: u8, public_key: *const u8, public_key_len: usize) -> Buffer {
let Some(public_name) = (unsafe { Slice::borrow_text(public_name, public_name_len) }) else {
return Buffer::EMPTY;
};
let public_key = unsafe { Slice::borrow(public_key, public_key_len) }.unwrap_or_default();
match ECHKeys::encode(public_name, config_id, public_key) {
Ok(config) => Buffer::new(config),
Err(_) => Buffer::EMPTY,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_identity_certificate_count(identity: *const Identity) -> isize {
let Some(identity) = (unsafe { identity.as_ref() }) else {
return -1;
};
match identity.chain() {
Ok(chain) => chain.len() as isize,
Err(_) => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_identity_certificate(identity: *const Identity, index: usize) -> Buffer {
let Some(identity) = (unsafe { identity.as_ref() }) else {
return Buffer::EMPTY;
};
let Ok(chain) = identity.chain() else {
return Buffer::EMPTY;
};
match chain.get(index).and_then(|certificate| certificate.to_der().ok()) {
Some(der) => Buffer::new(der),
None => Buffer::EMPTY,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_identity_private_key(identity: *const Identity) -> Buffer {
let Some(identity) = (unsafe { identity.as_ref() }) else {
return Buffer::EMPTY;
};
match identity.private_key().and_then(|key| key.private_key_to_der().map_err(crate::errors::Error::tls)) {
Ok(der) => Buffer::new(der),
Err(_) => Buffer::EMPTY,
}
}