use std::{ffi::c_void, fmt::Display};
#[repr(C)]
#[derive(Copy, Clone)]
enum Operation {
Encrypt = 0,
Decrypt = 1,
}
#[repr(u32)]
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Mode {
ECB = 1,
CBC = 2,
CFB = 3,
CTR = 4,
OFB = 7,
XTS = 8,
CFB8 = 10,
}
#[repr(u32)]
#[derive(Copy, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Padding {
None = 0,
PKCS7 = 1,
}
type CCCryptorRef = *mut c_void;
extern "C" {
fn CCCryptorCreateWithMode(
operation: Operation,
mode: u32,
config: u32,
padding: Padding,
iv: *const c_void,
key: *const c_void,
key_length: usize,
tweak: *const c_void,
tweak_length: usize,
rounds: usize,
options: u32,
handle: *mut CCCryptorRef,
) -> Status;
fn CCCryptorRelease(handle: CCCryptorRef) -> Status;
fn CCCryptorUpdate(
handle: CCCryptorRef,
input: *const c_void,
input_len: usize,
output: *mut c_void,
output_len: usize,
written: *mut usize,
) -> Status;
fn CCCryptorFinal(
handle: CCCryptorRef,
output: *mut c_void,
output_len: usize,
written: *mut usize,
) -> Status;
fn CCCryptorGetOutputLength(handle: CCCryptorRef, input_len: usize, finishing: bool) -> usize;
}
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub enum CryptorError {
Param,
Memory,
Alignment,
Decode,
Unimplemented,
RNGFailure,
Unspecified,
CallSequence,
KeySize,
Key,
InitializationVectorPresent,
Unexpected(i32),
}
impl std::error::Error for CryptorError {}
impl Display for CryptorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Param => "illegal parameter value",
Self::Memory => "memory allocation failed",
Self::Alignment => "input size was nit aligned properly",
Self::Decode => "input data did not encode or decrypt properly",
Self::Unimplemented => "function not implemented for the current algorithm",
Self::RNGFailure => "random number generated failed",
Self::Unspecified => "an unspecified failure occurred",
Self::CallSequence => "call sequence failure",
Self::KeySize => "key size is invalid",
Self::Key => "key is invalid",
Self::InitializationVectorPresent => "ECB mode does not support initialization vectors",
Self::Unexpected(code) => {
let s = format!("unexpected error {}", code);
return f.write_str(&s);
}
};
f.write_str(s)
}
}
#[allow(dead_code)]
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(i32)]
enum Status {
Success = 0,
ParamError = -4300,
BufferTooSmall = -4301,
MemoryFailure = -4302,
AlignmentError = -4303,
DecodeError = -4304,
Unimplemented = -4305,
Overflow = -4306,
RNGFailure = -4307,
UnspecifiedError = -4308,
CallSequenceError = -4309,
KeySizeError = -4310,
InvalidKey = -4311,
}
impl Into<CryptorError> for Status {
fn into(self) -> CryptorError {
match self {
Status::Success => unreachable!(),
Status::ParamError => CryptorError::Param,
Status::MemoryFailure => CryptorError::Memory,
Status::AlignmentError => CryptorError::Alignment,
Status::DecodeError => CryptorError::Decode,
Status::Unimplemented => CryptorError::Unimplemented,
Status::RNGFailure => CryptorError::RNGFailure,
Status::UnspecifiedError => CryptorError::Unspecified,
Status::CallSequenceError => CryptorError::CallSequence,
Status::KeySizeError => CryptorError::KeySize,
Status::InvalidKey => CryptorError::Key,
_ => CryptorError::Unexpected(self as i32),
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Config<'a> {
AES128 {
mode: Mode,
iv: Option<&'a [u8; 16]>,
key: &'a [u8; 16],
},
AES192 {
mode: Mode,
iv: Option<&'a [u8; 16]>,
key: &'a [u8; 24],
},
AES256 {
mode: Mode,
iv: Option<&'a [u8; 16]>,
key: &'a [u8; 32],
},
DES {
mode: Mode,
iv: Option<&'a [u8; 8]>,
key: &'a [u8; 8],
},
TDES {
mode: Mode,
iv: Option<&'a [u8; 8]>,
key: &'a [u8; 24],
},
CAST {
mode: Mode,
iv: Option<&'a [u8; 8]>,
key: &'a [u8],
padding: Padding,
},
RC4 {
key: &'a [u8],
},
RC2 {
mode: Mode,
iv: Option<&'a [u8; 8]>,
key: &'a [u8],
},
Blowfish {
mode: Mode,
iv: Option<&'a [u8; 8]>,
key: &'a [u8],
},
}
impl<'a> From<&Config<'a>> for u32 {
fn from(config: &Config) -> Self {
match config {
Config::AES128 { .. } => 0,
Config::AES192 { .. } => 0,
Config::AES256 { .. } => 0,
Config::DES { .. } => 1,
Config::TDES { .. } => 2,
Config::CAST { .. } => 3,
Config::RC4 { .. } => 4,
Config::RC2 { .. } => 5,
Config::Blowfish { .. } => 6,
}
}
}
impl<'a> Config<'a> {
fn padding(&self) -> Padding {
match self {
Config::CAST { padding, .. } => *padding,
_ => Padding::None,
}
}
fn rounds(&self) -> usize {
0
}
const fn mode(&self) -> u32 {
match self {
Config::AES128 { mode, .. } => *mode as u32,
Config::AES192 { mode, .. } => *mode as u32,
Config::AES256 { mode, .. } => *mode as u32,
Config::DES { mode, .. } => *mode as u32,
Config::TDES { mode, .. } => *mode as u32,
Config::CAST { mode, .. } => *mode as u32,
Config::RC4 { .. } => 9,
Config::RC2 { mode, .. } => *mode as u32,
Config::Blowfish { mode, .. } => *mode as u32,
}
}
fn iv_ptr(&self) -> Result<*const u8, CryptorError> {
let (mode, ptr) = match self {
Config::AES128 { mode, iv, .. } if iv.is_some() => (mode, iv.unwrap().as_ptr()),
Config::AES192 { mode, iv, .. } if iv.is_some() => (mode, iv.unwrap().as_ptr()),
Config::AES256 { mode, iv, .. } if iv.is_some() => (mode, iv.unwrap().as_ptr()),
Config::DES { mode, iv, .. } if iv.is_some() => (mode, iv.unwrap().as_ptr()),
Config::CAST { mode, iv, .. } if iv.is_some() => (mode, iv.unwrap().as_ptr()),
Config::RC2 { mode, iv, .. } if iv.is_some() => (mode, iv.unwrap().as_ptr()),
Config::Blowfish { mode, iv, .. } if iv.is_some() => (mode, iv.unwrap().as_ptr()),
_ => return Ok(std::ptr::null()),
};
if mode == &Mode::ECB {
Err(CryptorError::InitializationVectorPresent)
} else {
Ok(ptr)
}
}
fn key(&self) -> &[u8] {
match self {
Config::AES128 { key, .. } => *key,
Config::AES192 { key, .. } => *key,
Config::AES256 { key, .. } => *key,
Config::DES { key, .. } => *key,
Config::TDES { key, .. } => *key,
Config::CAST { key, .. } => *key,
Config::RC4 { key, .. } => *key,
Config::RC2 { key, .. } => *key,
Config::Blowfish { key, .. } => *key,
}
}
}
#[derive(Debug)]
pub struct Cryptor {
handle: CCCryptorRef,
}
impl<'a> Drop for Cryptor {
fn drop(&mut self) {
unsafe {
CCCryptorRelease(self.handle);
}
}
}
impl Cryptor {
fn new<'a>(config: &Config<'a>, operation: Operation) -> Result<Cryptor, CryptorError> {
let mut handle: CCCryptorRef = std::ptr::null_mut();
let status = unsafe {
CCCryptorCreateWithMode(
operation,
config.mode(),
config.into(),
config.padding(),
config.iv_ptr()? as *const c_void,
config.key().as_ptr() as *const c_void,
config.key().len(),
std::ptr::null(),
0,
config.rounds(),
0,
&mut handle as *mut *mut c_void,
)
};
if status != Status::Success {
return Err(status.into());
}
Ok(Cryptor { handle })
}
pub fn new_encryptor<'a>(config: &Config<'a>) -> Result<Self, CryptorError> {
Self::new(config, Operation::Encrypt)
}
pub fn new_decryptor<'a>(config: &Config<'a>) -> Result<Self, CryptorError> {
Self::new(config, Operation::Decrypt)
}
pub fn update(
&self,
input: impl AsRef<[u8]>,
output: &mut Vec<u8>,
) -> Result<(), CryptorError> {
let input = input.as_ref();
let mut written = 0usize;
output.resize(
unsafe { CCCryptorGetOutputLength(self.handle, input.len(), false) },
0,
);
let status = unsafe {
CCCryptorUpdate(
self.handle,
input.as_ptr() as *const c_void,
input.len(),
output.as_mut_ptr() as *mut c_void,
output.capacity(),
&mut written as *mut usize,
)
};
if status != Status::Success {
output.clear();
return Err(status.into());
}
output.resize(written, 0);
Ok(())
}
pub fn finish(self, output: &mut Vec<u8>) -> Result<(), CryptorError> {
let mut written = 0usize;
let status = unsafe {
CCCryptorFinal(
self.handle,
output.as_mut_ptr() as *mut c_void,
output.capacity(),
&mut written as *mut usize,
)
};
if status != Status::Success {
output.clear();
return Err(status.into());
}
output.resize(written, 0);
Ok(())
}
}
impl Cryptor {
pub fn encrypt<'a>(
config: &Config<'a>,
input: impl AsRef<[u8]>,
) -> Result<Vec<u8>, CryptorError> {
let mut output = Vec::new();
Cryptor::new_encryptor(config)?.update(input, &mut output)?;
Ok(output)
}
pub fn decrypt<'a>(
config: &Config<'a>,
input: impl AsRef<[u8]>,
) -> Result<Vec<u8>, CryptorError> {
let mut output = Vec::new();
Cryptor::new_decryptor(config)?.update(input, &mut output)?;
Ok(output)
}
}