#![cfg(feature = "legacy")]
use crate::constants::HARDCODED_SECRET_BYTES;
use crate::{error::Error, Encoding, Format, ObtextCodec, Scheme};
use super::constants::AES_BLOCK_SIZE;
use super::zsecret::ZSecret;
const LEGACY_PADDING_BYTE: u8 = b'=';
pub struct Legacy {
zsecret: ZSecret,
}
impl Legacy {
pub fn new(secret: &str) -> Result<Self, Error> {
Ok(Self {
zsecret: ZSecret::from_base64(secret)?,
})
}
pub(crate) fn from_master_secret(secret: &[u8; 32]) -> Result<Self, Error> {
Ok(Self {
zsecret: ZSecret::from_bytes(secret)?,
})
}
#[cfg(feature = "keyless")]
pub fn new_keyless() -> Result<Self, Error> {
Ok(Self {
zsecret: ZSecret::from_bytes(&HARDCODED_SECRET_BYTES)?,
})
}
#[cfg(feature = "hex-keys")]
pub fn from_hex_secret(secret_hex: &str) -> Result<Self, Error> {
Ok(Self {
zsecret: ZSecret::from_hex(secret_hex)?,
})
}
#[cfg(feature = "bytes-keys")]
pub fn from_bytes(secret_bytes: &[u8; 32]) -> Result<Self, Error> {
Ok(Self {
zsecret: ZSecret::from_bytes(secret_bytes)?,
})
}
pub fn secret(&self) -> String {
self.zsecret.secret_base64()
}
#[cfg(feature = "hex-keys")]
pub fn secret_hex(&self) -> String {
self.zsecret.secret_hex()
}
#[cfg(feature = "bytes-keys")]
pub fn secret_bytes(&self) -> &[u8; 32] {
self.zsecret.secret_bytes()
}
}
impl ObtextCodec for Legacy {
fn enc(&self, plaintext: &str) -> Result<String, Error> {
let plaintext_bytes = plaintext.as_bytes();
if plaintext_bytes.is_empty() {
return Err(Error::EmptyPlaintext);
}
let ciphertext = encrypt_legacy(self.zsecret.master_secret(), plaintext_bytes)?;
let mut s = crate::base32::BASE32_RFC_LOWER.encode(&ciphertext);
debug_assert!(s.is_ascii(), "encoding produced non-ASCII output");
unsafe { s.as_bytes_mut() }.reverse();
Ok(s)
}
fn dec(&self, obtext: &str) -> Result<String, Error> {
let reversed: Vec<u8> = obtext.bytes().rev().collect();
let ciphertext = crate::base32::BASE32_RFC_LOWER
.decode(&reversed)
.map_err(|_| Error::InvalidB32)?;
let plaintext_bytes = decrypt_legacy(self.zsecret.master_secret(), &ciphertext)?;
#[cfg(feature = "unchecked-utf8")]
{
Ok(unsafe { String::from_utf8_unchecked(plaintext_bytes) })
}
#[cfg(not(feature = "unchecked-utf8"))]
{
String::from_utf8(plaintext_bytes).map_err(|_| Error::InvalidUtf8)
}
}
fn format(&self) -> Format {
Format::new(Scheme::Legacy, Encoding::B32)
}
fn scheme(&self) -> Scheme {
Scheme::Legacy
}
fn encoding(&self) -> Encoding {
Encoding::B32
}
}
impl Legacy {
#[inline]
pub fn enc(&self, plaintext: &str) -> Result<String, Error> {
<Self as ObtextCodec>::enc(self, plaintext)
}
#[inline]
pub fn dec(&self, obtext: &str) -> Result<String, Error> {
<Self as ObtextCodec>::dec(self, obtext)
}
#[inline]
pub fn format(&self) -> Format {
<Self as ObtextCodec>::format(self)
}
#[inline]
pub fn scheme(&self) -> Scheme {
<Self as ObtextCodec>::scheme(self)
}
#[inline]
pub fn encoding(&self) -> Encoding {
<Self as ObtextCodec>::encoding(self)
}
}
const KEY_OFFSET: usize = 0;
const KEY_LEN: usize = 16;
const IV_OFFSET: usize = 16;
const IV_LEN: usize = 16;
#[inline(always)]
pub(crate) fn encrypt_legacy(secret: &[u8; 32], plaintext_bytes: &[u8]) -> Result<Vec<u8>, Error> {
use aes::Aes128;
use cbc::cipher::{BlockEncryptMut, KeyIvInit};
use cbc::Encryptor;
type Aes128CbcEnc = Encryptor<Aes128>;
if plaintext_bytes.is_empty() {
return Err(Error::EmptyPlaintext);
}
let data_len = plaintext_bytes.len();
let padding_size = (AES_BLOCK_SIZE - (data_len % AES_BLOCK_SIZE)) % AES_BLOCK_SIZE;
let total_len = data_len + padding_size;
let mut buffer = Vec::with_capacity(total_len);
buffer.extend_from_slice(plaintext_bytes);
buffer.resize(total_len, LEGACY_PADDING_BYTE);
let cipher = Aes128CbcEnc::new(
secret[KEY_OFFSET..KEY_OFFSET + KEY_LEN].into(),
secret[IV_OFFSET..IV_OFFSET + IV_LEN].into(),
);
cipher
.encrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer, total_len)
.map_err(|_| Error::EncryptionFailed)?;
Ok(buffer)
}
#[inline(always)]
pub(crate) fn decrypt_legacy(secret: &[u8; 32], data: &[u8]) -> Result<Vec<u8>, Error> {
use aes::Aes128;
use cbc::cipher::{BlockDecryptMut, KeyIvInit};
use cbc::Decryptor;
type Aes128CbcDec = Decryptor<Aes128>;
if data.len() % AES_BLOCK_SIZE != 0 {
return Err(Error::InvalidBlockLength);
}
let cipher = Aes128CbcDec::new(
secret[KEY_OFFSET..KEY_OFFSET + KEY_LEN].into(),
secret[IV_OFFSET..IV_OFFSET + IV_LEN].into(),
);
let mut buffer = data.to_vec();
cipher
.decrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer)
.map_err(|_| Error::DecryptionFailed)?;
let end = buffer
.iter()
.rposition(|&b| b != LEGACY_PADDING_BYTE)
.map_or(0, |i| i + 1);
buffer.truncate(end);
Ok(buffer)
}