use crate::crypto::{
aes_128_cbc_decrypt, aes_128_cbc_encrypt, constant_time_eq, hmac_sha1, rand_bytes,
};
use crate::errors::{Error, ErrorKind};
use radix64::STD as BASE64;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::BTreeMap;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
pub const KEY_COUNT: usize = 2;
pub const KEY_LENGTH: usize = 128 / 8;
pub const PBKDF2_ROUNDS: usize = 256_000;
pub const IV_SIZE: usize = KEY_LENGTH;
pub const SCHEMA_VERSION: u32 = 3;
pub const HMAC_SIZE: usize = 160 / 8;
#[derive(Serialize, Deserialize, Debug)]
pub struct Vault {
pub version: u32,
#[serde(serialize_with = "to_base64", deserialize_with = "iv_from_base64")]
pub iv: [u8; IV_SIZE],
pub sentinel: Option<EncryptedBlob>,
pub secrets: BTreeMap<String, EncryptedBlob>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct EncryptedBlob {
#[serde(serialize_with = "to_base64", deserialize_with = "iv_from_base64")]
pub iv: [u8; IV_SIZE],
#[serde(serialize_with = "to_base64", deserialize_with = "hmac_from_base64")]
pub hmac: [u8; HMAC_SIZE],
#[serde(serialize_with = "to_base64", deserialize_with = "vec_from_base64")]
pub payload: Vec<u8>,
}
fn to_base64<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: AsRef<[u8]>,
S: Serializer,
{
serializer.serialize_str(&BASE64.encode(value.as_ref()))
}
fn iv_from_base64<'de, D>(deserializer: D) -> Result<[u8; IV_SIZE], D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let b64: String = Deserialize::deserialize(deserializer)?;
let mut result = [0u8; IV_SIZE];
BASE64
.decode_slice(&b64, &mut result)
.map_err(|e| Error::custom(e.to_string()))?;
Ok(result)
}
fn hmac_from_base64<'de, D>(deserializer: D) -> Result<[u8; HMAC_SIZE], D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let b64: String = Deserialize::deserialize(deserializer)?;
let mut result = [0u8; HMAC_SIZE];
BASE64
.decode_slice(&b64, &mut result)
.map_err(|e| Error::custom(e.to_string()))?;
Ok(result)
}
fn vec_from_base64<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let s: String = Deserialize::deserialize(deserializer)?;
BASE64.decode(&s).map_err(|e| Error::custom(e.to_string()))
}
impl Vault {
pub fn new() -> Self {
let mut iv = [0u8; IV_SIZE];
rand_bytes(&mut iv);
Vault {
version: SCHEMA_VERSION,
iv,
secrets: Default::default(),
sentinel: None,
}
}
fn validate(vault: Self) -> Result<Self, Error> {
if vault.version != SCHEMA_VERSION {
return ErrorKind::UnsupportedVaultVersion.into();
}
Ok(vault)
}
#[allow(unused)]
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let path = path.as_ref();
let mut file = File::open(path)?;
Self::load(&mut file)
}
pub fn load<'a, R: Read>(source: &'a mut R) -> Result<Self, Error> {
let vault = serde_json::from_reader(source)?;
Self::validate(vault)
}
pub fn save<W: Write>(&self, dest: W) -> Result<(), Error> {
serde_json::to_writer_pretty(dest, &self)?;
Ok(())
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct CryptoKeys {
pub encryption: [u8; KEY_LENGTH],
pub hmac: [u8; KEY_LENGTH],
}
impl CryptoKeys {
pub fn export<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
let mut file = File::create(path)?;
file.write_all(b"-----BEGIN PRIVATE KEY-----\n")
.map_err(|e| Error::from_inner(ErrorKind::IoError, e))?;
let mut data = [0u8; KEY_LENGTH * 2];
data[0..KEY_LENGTH].copy_from_slice(&self.encryption);
data[KEY_LENGTH..].copy_from_slice(&self.hmac);
let encoded = BASE64.encode(&data);
let mut encoded = encoded.as_bytes();
loop {
let line_bytes = match encoded.len() {
0 => break,
65.. => &encoded[0..63],
_ => encoded,
};
file.write_all(line_bytes)
.and_then(|_| file.write_all(b"\n"))
.map_err(|e| Error::from_inner(ErrorKind::IoError, e))?;
encoded = &encoded[line_bytes.len()..];
}
file.write_all(b"-----END PRIVATE KEY-----\n")
.map_err(|e| Error::from_inner(ErrorKind::IoError, e))?;
Ok(())
}
pub fn import<R: Read>(mut source: R) -> Result<Self, Error> {
const MAX_READ: usize = 4096;
let mut buffer = vec![0u8; 128];
let mut total_read = 0;
loop {
let bytes_read = match source.read(&mut buffer[total_read..]) {
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => return Err(Error::from_inner(ErrorKind::IoError, e)),
Ok(0) => break,
Ok(count) => count,
};
total_read += bytes_read;
if total_read > MAX_READ {
return Err(ErrorKind::InvalidKeyfile.into());
} else if total_read == buffer.len() {
buffer.resize(buffer.len() + 128, 0u8);
}
}
let buffer = &buffer[..total_read];
if buffer.len() == KEY_LENGTH * KEY_COUNT {
Self::import_binary(buffer)
} else {
use std::io::{BufRead, BufReader};
#[derive(PartialEq)]
enum ParseState {
WaitingStart,
WaitingEnd,
Complete,
}
let mut encoded = String::new();
let mut state = ParseState::WaitingStart;
let source = BufReader::new(buffer);
for line in source.lines() {
let line = line.map_err(|e| Error::from_inner(ErrorKind::InvalidKeyfile, e))?;
let line = line.trim();
if state == ParseState::WaitingStart {
if line == "-----BEGIN PRIVATE KEY-----" {
state = ParseState::WaitingEnd;
}
continue;
} else if line == "-----END PRIVATE KEY-----" {
state = ParseState::Complete;
break;
}
encoded.push_str(line);
}
if state != ParseState::Complete {
return Err(ErrorKind::InvalidKeyfile.into());
}
let decoded = BASE64
.decode(&encoded)
.map_err(|e| Error::from_inner(ErrorKind::InvalidKeyfile, e))?;
if decoded.len() != KEY_COUNT * KEY_LENGTH {
return Err(ErrorKind::InvalidKeyfile.into());
}
Self::import_binary(&decoded)
}
}
fn import_binary(buffer: &[u8]) -> Result<Self, Error> {
use std::convert::TryInto;
if buffer.len() != KEY_COUNT * KEY_LENGTH {
return ErrorKind::InvalidKeyfile.into();
}
Ok(CryptoKeys {
encryption: buffer[0..KEY_LENGTH].try_into().unwrap(),
hmac: buffer[KEY_LENGTH..][..KEY_LENGTH].try_into().unwrap(),
})
}
}
impl EncryptedBlob {
pub fn encrypt<'a>(keys: &CryptoKeys, secret: &'a [u8]) -> EncryptedBlob {
let mut iv = [0u8; KEY_LENGTH];
rand_bytes(&mut iv);
let payload = aes_128_cbc_encrypt(&keys.encryption, &iv, secret);
EncryptedBlob {
hmac: Self::calculate_hmac(&keys.hmac, &iv, &payload),
iv,
payload,
}
}
pub fn decrypt(&self, keys: &CryptoKeys) -> Result<Vec<u8>, Error> {
if !self.authenticate(&keys.hmac) {
return ErrorKind::DecryptionFailure.into();
}
aes_128_cbc_decrypt(&keys.encryption, &self.iv, &self.payload)
}
fn calculate_hmac(
hmac_key: &[u8; KEY_LENGTH],
iv: &[u8; IV_SIZE],
encrypted: &[u8],
) -> [u8; HMAC_SIZE] {
hmac_sha1(hmac_key, &[iv, encrypted])
}
pub fn authenticate(&self, hmac_key: &[u8; KEY_LENGTH]) -> bool {
let hmac = Self::calculate_hmac(hmac_key, &self.iv, &self.payload);
constant_time_eq(&hmac, &self.hmac)
}
}