use aes::{Aes128, Aes192, Aes256};
use aes_gcm::{
Aes128Gcm, Aes256Gcm, AesGcm, Nonce,
aead::{Aead, KeyInit, consts::U12},
};
use base64::{Engine, engine::general_purpose::STANDARD};
use cbc::Encryptor as CbcEncryptor;
use cipher::{BlockEncryptMut, KeyIvInit, block_padding::Pkcs7};
use ecb::Encryptor as EcbEncryptor;
use crate::error::{Error, Result};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EncryptionAlgorithm {
AES128,
AES192,
AES256,
}
impl EncryptionAlgorithm {
pub const fn key_len(self) -> usize {
match self {
Self::AES128 => 16,
Self::AES192 => 24,
Self::AES256 => 32,
}
}
pub const fn as_bark_str(self) -> &'static str {
match self {
Self::AES128 => "AES128",
Self::AES192 => "AES192",
Self::AES256 => "AES256",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EncryptionMode {
CBC,
ECB,
GCM,
}
impl EncryptionMode {
pub const fn iv_len(self) -> Option<usize> {
match self {
Self::CBC => Some(16),
Self::ECB => None,
Self::GCM => Some(12),
}
}
pub const fn as_bark_str(self) -> &'static str {
match self {
Self::CBC => "CBC",
Self::ECB => "ECB",
Self::GCM => "GCM",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Device {
token: String,
encryption_algorithm: Option<EncryptionAlgorithm>,
encryption_mode: Option<EncryptionMode>,
encryption_key: Option<String>,
}
impl Device {
pub fn new<T>(token: T) -> Self
where
T: Into<String>,
{
Self {
token: normalize_device_token(token.into()),
encryption_algorithm: None,
encryption_mode: None,
encryption_key: None,
}
}
pub fn encrypt<K>(
mut self,
algorithm: EncryptionAlgorithm,
mode: EncryptionMode,
key: K,
) -> Result<Self>
where
K: Into<String>,
{
let key = key.into();
let expected = algorithm.key_len();
let actual = key.len();
if actual != expected {
return Err(Error::InvalidKeyLength {
algorithm: algorithm.as_bark_str(),
expected,
actual,
});
}
self.encryption_algorithm = Some(algorithm);
self.encryption_mode = Some(mode);
self.encryption_key = Some(key);
Ok(self)
}
pub fn token(&self) -> &str {
&self.token
}
pub(crate) fn has_encryption(&self) -> bool {
self.encryption().is_some()
}
pub(crate) fn encrypt_bark_json(&self, plaintext: &[u8]) -> Result<EncryptedPayload> {
let (algorithm, mode, key) =
self.encryption()
.ok_or_else(|| Error::MissingDeviceEncryption {
device: self.token.clone(),
})?;
let iv = match mode.iv_len() {
Some(len) => random_ascii_iv(len)?,
None => String::new(),
};
let encrypted = match (algorithm, mode) {
(EncryptionAlgorithm::AES128, EncryptionMode::CBC) => {
CbcEncryptor::<Aes128>::new_from_slices(key.as_bytes(), iv.as_bytes())
.map_err(|_| Error::Encryption)?
.encrypt_padded_vec_mut::<Pkcs7>(plaintext)
}
(EncryptionAlgorithm::AES192, EncryptionMode::CBC) => {
CbcEncryptor::<Aes192>::new_from_slices(key.as_bytes(), iv.as_bytes())
.map_err(|_| Error::Encryption)?
.encrypt_padded_vec_mut::<Pkcs7>(plaintext)
}
(EncryptionAlgorithm::AES256, EncryptionMode::CBC) => {
CbcEncryptor::<Aes256>::new_from_slices(key.as_bytes(), iv.as_bytes())
.map_err(|_| Error::Encryption)?
.encrypt_padded_vec_mut::<Pkcs7>(plaintext)
}
(EncryptionAlgorithm::AES128, EncryptionMode::ECB) => {
EcbEncryptor::<Aes128>::new_from_slice(key.as_bytes())
.map_err(|_| Error::Encryption)?
.encrypt_padded_vec_mut::<Pkcs7>(plaintext)
}
(EncryptionAlgorithm::AES192, EncryptionMode::ECB) => {
EcbEncryptor::<Aes192>::new_from_slice(key.as_bytes())
.map_err(|_| Error::Encryption)?
.encrypt_padded_vec_mut::<Pkcs7>(plaintext)
}
(EncryptionAlgorithm::AES256, EncryptionMode::ECB) => {
EcbEncryptor::<Aes256>::new_from_slice(key.as_bytes())
.map_err(|_| Error::Encryption)?
.encrypt_padded_vec_mut::<Pkcs7>(plaintext)
}
(EncryptionAlgorithm::AES128, EncryptionMode::GCM) => {
let cipher =
Aes128Gcm::new_from_slice(key.as_bytes()).map_err(|_| Error::Encryption)?;
cipher
.encrypt(Nonce::from_slice(iv.as_bytes()), plaintext)
.map_err(|_| Error::Encryption)?
}
(EncryptionAlgorithm::AES192, EncryptionMode::GCM) => {
let cipher = AesGcm::<Aes192, U12>::new_from_slice(key.as_bytes())
.map_err(|_| Error::Encryption)?;
cipher
.encrypt(Nonce::from_slice(iv.as_bytes()), plaintext)
.map_err(|_| Error::Encryption)?
}
(EncryptionAlgorithm::AES256, EncryptionMode::GCM) => {
let cipher =
Aes256Gcm::new_from_slice(key.as_bytes()).map_err(|_| Error::Encryption)?;
cipher
.encrypt(Nonce::from_slice(iv.as_bytes()), plaintext)
.map_err(|_| Error::Encryption)?
}
};
Ok(EncryptedPayload {
ciphertext: STANDARD.encode(encrypted),
iv: mode.iv_len().map(|_| iv),
})
}
fn encryption(&self) -> Option<(EncryptionAlgorithm, EncryptionMode, &str)> {
Some((
self.encryption_algorithm?,
self.encryption_mode?,
self.encryption_key.as_deref()?,
))
}
}
pub(crate) struct EncryptedPayload {
pub(crate) ciphertext: String,
pub(crate) iv: Option<String>,
}
fn random_ascii_iv(len: usize) -> Result<String> {
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let mut bytes = vec![0; len];
getrandom::getrandom(&mut bytes)?;
Ok(bytes
.into_iter()
.map(|byte| ALPHABET[usize::from(byte) % ALPHABET.len()] as char)
.collect())
}
fn normalize_device_token(device: String) -> String {
device
.trim()
.trim_start_matches('<')
.trim_end_matches('>')
.chars()
.filter(|ch| !ch.is_ascii_whitespace())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalizes_device_tokens() {
let device = Device::new("<aa bb>");
assert_eq!(device.token(), "aabb");
}
#[test]
fn keeps_device_encryption() {
let device = Device::new("aabb")
.encrypt(
EncryptionAlgorithm::AES128,
EncryptionMode::CBC,
"1234567890123456",
)
.unwrap();
assert_eq!(
device.encryption_algorithm,
Some(EncryptionAlgorithm::AES128)
);
assert_eq!(device.encryption_mode, Some(EncryptionMode::CBC));
assert_eq!(device.encryption_key.as_deref(), Some("1234567890123456"));
assert!(device.has_encryption());
}
#[test]
fn validates_encryption_key_length() {
let err = Device::new("aabb")
.encrypt(EncryptionAlgorithm::AES128, EncryptionMode::CBC, "short")
.unwrap_err();
assert!(matches!(
err,
crate::Error::InvalidKeyLength {
algorithm: "AES128",
expected: 16,
actual: 5
}
));
}
#[test]
fn generates_mode_specific_payload_iv() {
let cbc = Device::new("aabb")
.encrypt(
EncryptionAlgorithm::AES128,
EncryptionMode::CBC,
"1234567890123456",
)
.unwrap();
let gcm = Device::new("aabb")
.encrypt(
EncryptionAlgorithm::AES128,
EncryptionMode::GCM,
"1234567890123456",
)
.unwrap();
let ecb = Device::new("aabb")
.encrypt(
EncryptionAlgorithm::AES128,
EncryptionMode::ECB,
"1234567890123456",
)
.unwrap();
assert_eq!(
cbc.encrypt_bark_json(b"test").unwrap().iv.unwrap().len(),
16
);
assert_eq!(
gcm.encrypt_bark_json(b"test").unwrap().iv.unwrap().len(),
12
);
assert_eq!(ecb.encrypt_bark_json(b"test").unwrap().iv, None);
}
#[test]
fn generates_fresh_iv_for_each_encrypted_payload() {
let device = Device::new("aabb")
.encrypt(
EncryptionAlgorithm::AES128,
EncryptionMode::CBC,
"1234567890123456",
)
.unwrap();
let first = device.encrypt_bark_json(b"test").unwrap();
let second = device.encrypt_bark_json(b"test").unwrap();
assert_ne!(first.iv, second.iv);
assert_ne!(first.ciphertext, second.ciphertext);
}
}