use std::{fmt, fs, path::Path, str::FromStr};
use anyhow::{Result, anyhow};
use nacl::public_box;
use rand::RngCore;
use super::{decryptor::Decryptor, encryptor::Encryptor};
const KEY_SIZE: usize = 32;
const NONCE_SIZE: usize = 24;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Key(pub(crate) [u8; KEY_SIZE]);
impl Key {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
fs::read_to_string(path)?.trim().parse()
}
#[cfg(test)]
pub fn all(v: u8) -> Self {
Self([v; KEY_SIZE])
}
pub fn random() -> Self {
let mut bytes = Self::default().0;
rand::rng().fill_bytes(&mut bytes);
Self(bytes)
}
}
impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0.map(|b| format!("{:02x}", b)).join(""))
}
}
impl FromStr for Key {
type Err = anyhow::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
if s.len() != 2 * KEY_SIZE {
return Err(anyhow!("InvalidKey (bad length)"));
}
Ok(Self(bytes_from_hex(s).as_slice().try_into()?))
}
}
fn bytes_from_hex(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| {
s.get(i..i + 2)
.and_then(|sub| u8::from_str_radix(sub, 16).ok())
.unwrap()
})
.collect()
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Nonce(pub(crate) [u8; NONCE_SIZE]);
impl Nonce {
#[cfg(test)]
pub fn all(v: u8) -> Self {
Self([v; NONCE_SIZE])
}
pub fn random() -> Self {
let mut bytes = Self::default().0;
rand::rng().fill_bytes(&mut bytes);
Self(bytes)
}
}
impl fmt::Display for Nonce {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0.map(|b| format!("{:02x}", b)).join(""))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyPair {
pub(crate) public: Key,
pub(crate) private: Key,
}
impl KeyPair {
pub fn new(public: Key, private: Key) -> Self {
Self { public, private }
}
pub fn generate() -> Result<Self> {
let priv_key = Key::random();
let pub_key = public_box::generate_pubkey(&priv_key.0).map_err(|e| anyhow!(e.message))?;
Ok(Self::new(Key(pub_key.as_slice().try_into()?), priv_key))
}
pub fn public_key(&self) -> String {
self.public.to_string()
}
pub fn private_key(&self) -> String {
self.private.to_string()
}
pub fn encryptor(&self, peer_public: Key) -> Result<Encryptor> {
Encryptor::create(self.clone(), peer_public)
}
pub fn decryptor(&self) -> Decryptor {
Decryptor::new(self.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn key_from_file() {
let key_path = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("examples")
.join("data")
.join("2549b26efec29cf60e473797f5dda5f41d99460cf1c32f34f1c0247d9bd7ff5b");
assert!(Key::from_file(key_path).is_ok());
}
#[test]
fn key_serde() {
let key = Key::random();
let key_str = key.to_string();
assert_eq!(2 * KEY_SIZE, key_str.len());
let parsed = key_str.parse().unwrap();
assert_eq!(key, parsed);
}
#[test]
fn new() {
let pub_key = Key::all(1);
let priv_key = Key::all(2);
let pair = KeyPair::new(pub_key.clone(), priv_key.clone());
assert_eq!(pub_key, pair.public);
assert_eq!(priv_key, pair.private);
}
#[test]
fn generate() {
let pair = KeyPair::generate().unwrap();
assert_ne!(pair.public_key(), pair.private_key());
assert!(!pair.public_key().contains("00000"));
assert!(!pair.private_key().contains("00000"));
assert_eq!(2 * KEY_SIZE, pair.public_key().len());
assert_eq!(2 * KEY_SIZE, pair.private_key().len());
}
}