use std::{
collections::HashSet,
io,
sync::{Arc, Mutex},
};
use age::secrecy::ExposeSecret;
use age_core::format::{FileKey, Stanza};
use zeroize::Zeroize;
pub const STANZA_TAG: &str = "tlock";
pub struct Identity {
hash: Vec<u8>,
signature: Vec<u8>,
}
impl Identity {
pub fn new(hash: &[u8], signature: &[u8]) -> Self {
Self {
hash: hash.to_vec(),
signature: signature.to_vec(),
}
}
}
impl age::Identity for Identity {
fn unwrap_stanza(&self, stanza: &Stanza) -> Option<Result<FileKey, age::DecryptError>> {
if stanza.tag != STANZA_TAG {
return None;
}
if stanza.args.len() != 2 {
return Some(Err(age::DecryptError::InvalidHeader));
}
let args: [String; 2] = [stanza.args[0].clone(), stanza.args[1].clone()];
let _round = args[0]
.parse::<u64>()
.map_err(|_| age::DecryptError::InvalidHeader)
.ok()?;
if self.hash != hex::decode(&args[1]).ok()? {
return Some(Err(age::DecryptError::InvalidHeader));
}
let dst = InMemoryWriter::new();
let decryption = tlock::decrypt(dst.to_owned(), stanza.body.as_slice(), &self.signature);
decryption
.map_err(|_| age::DecryptError::DecryptionFailed)
.ok()?;
let mut dst = dst.memory();
dst.resize(16, 0);
let file_key: [u8; 16] = dst[..].try_into().ok()?;
Some(Ok(FileKey::new(Box::new(file_key))))
}
}
pub struct HeaderIdentity {
hash: Mutex<Option<Vec<u8>>>,
round: Mutex<Option<u64>>,
}
impl HeaderIdentity {
pub fn new() -> Self {
Self {
hash: Mutex::new(None),
round: Mutex::new(None),
}
}
pub fn hash(&self) -> Option<Vec<u8>> {
self.hash.lock().unwrap().clone()
}
pub fn round(&self) -> Option<u64> {
*self.round.lock().unwrap()
}
}
impl Default for HeaderIdentity {
fn default() -> Self {
Self::new()
}
}
impl age::Identity for HeaderIdentity {
fn unwrap_stanza(&self, stanza: &Stanza) -> Option<Result<FileKey, age::DecryptError>> {
if stanza.tag != STANZA_TAG {
return None;
}
if stanza.args.len() != 2 {
return Some(Err(age::DecryptError::InvalidHeader));
}
let args: [String; 2] = [stanza.args[0].clone(), stanza.args[1].clone()];
let round = args[0]
.parse::<u64>()
.map_err(|_| age::DecryptError::InvalidHeader)
.ok()?;
let hash = hex::decode(&args[1])
.map_err(|_| age::DecryptError::InvalidHeader)
.ok()?;
*self.round.lock().unwrap() = Some(round);
*self.hash.lock().unwrap() = Some(hash);
None
}
}
pub struct Recipient {
hash: Vec<u8>,
public_key_bytes: Vec<u8>,
round: u64,
}
impl Recipient {
pub fn new(hash: &[u8], public_key_bytes: &[u8], round: u64) -> Self {
Self {
hash: hash.to_vec(),
public_key_bytes: public_key_bytes.to_vec(),
round,
}
}
}
#[derive(Clone)]
struct InMemoryWriter {
memory: Arc<Mutex<Vec<u8>>>,
}
impl InMemoryWriter {
pub fn new() -> Self {
Self {
memory: Arc::new(Mutex::new(vec![])),
}
}
pub fn memory(&self) -> Vec<u8> {
self.memory.lock().unwrap().to_owned()
}
}
impl io::Write for InMemoryWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.memory.lock().unwrap().extend(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.memory.lock().unwrap().to_owned().zeroize();
Ok(())
}
}
impl age::Recipient for Recipient {
fn wrap_file_key(
&self,
file_key: &FileKey,
) -> Result<(Vec<Stanza>, HashSet<String>), age::EncryptError> {
let src = file_key.expose_secret().as_slice();
let dst = InMemoryWriter::new();
let _ = tlock::encrypt(dst.to_owned(), src, &self.public_key_bytes, self.round);
Ok((
vec![Stanza {
tag: STANZA_TAG.to_string(),
args: vec![self.round.to_string(), hex::encode(&self.hash)],
body: dst.memory(),
}],
HashSet::new(),
))
}
}
#[cfg(test)]
mod tests {
use std::{
io::{Read, Write},
iter,
};
use drand_core::HttpClient;
use crate::{Identity, Recipient};
#[test]
fn it_works() {
let client: HttpClient =
"https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"
.try_into()
.unwrap();
let info = client.chain_info().unwrap();
let round = 100;
let beacon = client.get(round).unwrap();
let id = Identity::new(&info.hash(), &beacon.signature());
let recipient = Recipient::new(&info.hash(), &info.public_key(), round);
let mut plaintext = vec![0u8; 1000];
plaintext.fill_with(rand::random);
let encrypted = {
let encryptor =
age::Encryptor::with_recipients(iter::once(&recipient as &dyn age::Recipient))
.expect("we provided a recipient");
let mut encrypted = vec![];
let mut writer = encryptor.wrap_output(&mut encrypted).unwrap();
writer.write_all(&plaintext).unwrap();
writer.finish().unwrap();
encrypted
};
let decrypted = {
let decryptor = age::Decryptor::new(encrypted.as_slice()).unwrap();
assert!(!decryptor.is_scrypt());
let mut decrypted = vec![];
let mut reader = decryptor
.decrypt(iter::once(&id as &dyn age::Identity))
.unwrap();
reader.read_to_end(&mut decrypted).unwrap();
decrypted
};
assert_eq!(decrypted, plaintext);
}
}