use std::path::Path;
use ring::hkdf::{HKDF_SHA256, Salt};
use crate::dht::Keypair;
pub const PUBLIC: &str = "rendezvous.pub";
pub const SALT: &str = "pairing.salt";
pub mod item;
pub mod punch;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pairing {
pub engine: [u8; 32],
pub salt: [u8; 32],
}
impl Pairing {
pub fn inbox_keypair(&self) -> Result<Keypair, String> {
Keypair::from_seed(self.derive(b"inbox key"))
}
pub fn presence_salt(&self) -> Vec<u8> {
self.derive(b"presence salt").to_vec()
}
pub fn inbox_salt(&self) -> Vec<u8> {
self.derive(b"inbox salt").to_vec()
}
pub 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 fn read_dir(dir: &Path) -> Result<Option<Pairing>, String> {
match (hex_file(&dir.join(PUBLIC)), hex_file(&dir.join(SALT))) {
(None, None) => Ok(None),
(Some(engine), Some(salt)) => Ok(Some(Pairing { engine, salt })),
_ => Err(format!(
"{} holds half its rendezvous material: {PUBLIC} and {SALT} are carried together \
— each 32 bytes of hex, the engine's public rendezvous key and the pairing salt \
its wire directory holds beside ca.pem",
dir.display()
)),
}
}
fn hex_file(path: &Path) -> Option<[u8; 32]> {
let text = std::fs::read_to_string(path).ok()?;
<[u8; 32]>::try_from(unhex(text.trim())?).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| u8::from_str_radix(std::str::from_utf8(pair).ok()?, 16).ok())
.collect()
}
#[cfg(test)]
pub(crate) mod tests;