#![forbid(unsafe_code)]
pub mod encoding;
pub mod store;
use x25519_dalek::{PublicKey, StaticSecret};
use zeroize::{Zeroize, ZeroizeOnDrop};
pub use encoding::{DecodeError, decode_prefixed, encode_prefixed};
pub use store::{Blob, StoreError};
pub const KEY_LEN: usize = 32;
pub trait Rng {
fn fill_bytes(&mut self, dest: &mut [u8]);
}
macro_rules! key_type {
(
$(#[$secret_doc:meta])* $secret:ident,
$(#[$public_doc:meta])* $public:ident,
$prefix:literal
) => {
$(#[$secret_doc])*
#[derive(Clone, ZeroizeOnDrop)]
pub struct $secret(StaticSecret);
impl $secret {
pub fn generate(rng: &mut impl Rng) -> Self {
let mut bytes = [0u8; KEY_LEN];
rng.fill_bytes(&mut bytes);
let key = Self::from_bytes(bytes);
bytes.zeroize();
key
}
pub fn from_bytes(bytes: [u8; KEY_LEN]) -> Self {
Self(StaticSecret::from(bytes))
}
pub fn to_bytes(&self) -> [u8; KEY_LEN] {
self.0.to_bytes()
}
pub fn public(&self) -> $public {
$public(PublicKey::from(&self.0))
}
pub fn secret(&self) -> &StaticSecret {
&self.0
}
}
impl core::fmt::Debug for $secret {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(concat!(stringify!($secret), "(redacted)"))
}
}
$(#[$public_doc])*
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct $public(PublicKey);
impl $public {
pub const PREFIX: &'static str = $prefix;
pub fn from_bytes(bytes: [u8; KEY_LEN]) -> Self {
Self(PublicKey::from(bytes))
}
pub fn as_bytes(&self) -> &[u8; KEY_LEN] {
self.0.as_bytes()
}
pub fn as_public(&self) -> &PublicKey {
&self.0
}
pub fn parse(text: &str) -> Result<Self, DecodeError> {
decode_prefixed(Self::PREFIX, text).map(Self::from_bytes)
}
pub fn encode<'o>(&self, out: &'o mut [u8]) -> Result<&'o str, DecodeError> {
encode_prefixed(Self::PREFIX, self.as_bytes(), out)
}
}
impl core::fmt::Display for $public {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(Self::PREFIX)?;
f.write_str(":")?;
for byte in self.as_bytes() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl core::fmt::Debug for $public {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}
};
}
key_type!(
MachinePrivate,
MachinePublic,
"mkey"
);
key_type!(
NodePrivate,
NodePublic,
"nodekey"
);
key_type!(
DiscoPrivate,
DiscoPublic,
"discokey"
);
#[derive(Clone, Debug)]
pub struct Identity {
pub machine: MachinePrivate,
pub node: NodePrivate,
pub disco: DiscoPrivate,
}
impl Identity {
pub fn generate(rng: &mut impl Rng) -> Self {
Self {
machine: MachinePrivate::generate(rng),
node: NodePrivate::generate(rng),
disco: DiscoPrivate::generate(rng),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Counter(u8);
impl Rng for Counter {
fn fill_bytes(&mut self, dest: &mut [u8]) {
for byte in dest {
*byte = self.0;
self.0 = self.0.wrapping_add(1);
}
}
}
#[test]
fn the_three_keys_of_an_identity_are_distinct() {
let identity = Identity::generate(&mut Counter(1));
let machine = *identity.machine.public().as_bytes();
let node = *identity.node.public().as_bytes();
let disco = *identity.disco.public().as_bytes();
assert_ne!(machine, node);
assert_ne!(node, disco);
assert_ne!(machine, disco);
}
#[test]
fn each_key_writes_its_own_prefix() {
assert_eq!(MachinePublic::PREFIX, "mkey");
assert_eq!(NodePublic::PREFIX, "nodekey");
assert_eq!(DiscoPublic::PREFIX, "discokey");
}
#[test]
fn a_secret_key_does_not_print_itself() {
struct Sink([u8; 128], usize);
impl Write for Sink {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let n = s.len().min(self.0.len() - self.1);
self.0[self.1..self.1 + n].copy_from_slice(&s.as_bytes()[..n]);
self.1 += n;
Ok(())
}
}
use core::fmt::Write;
let identity = Identity::generate(&mut Counter(7));
let mut sink = Sink([0; 128], 0);
write!(sink, "{:?}", identity.machine).unwrap();
let printed = core::str::from_utf8(&sink.0[..sink.1]).unwrap();
assert_eq!(printed, "MachinePrivate(redacted)");
let secret = identity.machine.to_bytes();
assert!(!sink.0[..sink.1].windows(4).any(|w| w == &secret[..4]));
}
#[test]
fn a_public_key_round_trips_through_its_text_form() {
let identity = Identity::generate(&mut Counter(3));
let public = identity.node.public();
let mut buffer = [0u8; 128];
let text = public.encode(&mut buffer).unwrap();
assert!(text.starts_with("nodekey:"));
assert_eq!(NodePublic::parse(text).unwrap(), public);
}
}