use crate::dht::Keypair;
use ring::hkdf::{HKDF_SHA256, Salt};
use std::path::Path;
pub(crate) const KEY: &str = "rendezvous.pub";
pub(crate) const SALT: &str = "pairing.salt";
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Pairing {
pub(crate) key: [u8; 32],
pub(crate) salt: [u8; 32],
}
impl Pairing {
pub(crate) fn inbox_keypair(&self) -> Result<Keypair, String> {
Keypair::from_seed(self.derive(b"inbox key"))
}
pub(crate) fn presence_salt(&self) -> Vec<u8> {
self.derive(b"presence salt").to_vec()
}
pub(crate) fn inbox_salt(&self) -> Vec<u8> {
self.derive(b"inbox salt").to_vec()
}
pub(crate) fn seal_key(&self) -> [u8; 32] {
self.derive(b"seal key")
}
fn derive(&self, label: &[u8]) -> [u8; 32] {
let mut out = [0u8; 32];
if let Ok(okm) = Salt::new(HKDF_SHA256, b"yog rendezvous")
.extract(&self.salt)
.expand(&[label], HKDF_SHA256)
{
let _ = okm.fill(&mut out);
}
out
}
}
pub(crate) fn read_dir(dir: &Path) -> Result<Option<Pairing>, String> {
let (key, salt) = (dir.join(KEY), dir.join(SALT));
match (key.is_file(), salt.is_file()) {
(false, false) => Ok(None),
(true, true) => match (hex_file(&key), hex_file(&salt)) {
(Some(key), Some(salt)) => Ok(Some(Pairing { key, salt })),
_ => Err(format!(
"{} holds rendezvous material that does not read: {KEY} and {SALT} are \
each exactly 32 bytes of hex — {}",
dir.display(),
crate::channel::material::REMEDY
)),
},
_ => Err(format!(
"{} is half-provisioned for rendezvous: {KEY} and {SALT} are carried \
together — {}",
dir.display(),
crate::channel::material::REMEDY
)),
}
}
fn hex_file(path: &Path) -> Option<[u8; 32]> {
let text = std::fs::read_to_string(path).ok()?;
let bytes = unhex(text.trim())?;
<[u8; 32]>::try_from(bytes).ok()
}
#[cfg(test)]
pub(crate) fn hex(bytes: &[u8]) -> String {
use std::fmt::Write;
bytes.iter().fold(String::new(), |mut out, b| {
let _ = write!(out, "{b:02x}");
out
})
}
pub(crate) fn unhex(text: &str) -> Option<Vec<u8>> {
if !text.len().is_multiple_of(2) {
return None;
}
text.as_bytes()
.chunks(2)
.map(|pair| {
let pair = std::str::from_utf8(pair).ok()?;
u8::from_str_radix(pair, 16).ok()
})
.collect()
}
#[cfg(test)]
mod tests;