use std::net::Ipv6Addr;
use std::path::PathBuf;
use anyhow::{anyhow, bail, Context, Result};
use ring::signature::{Ed25519KeyPair, KeyPair, UnparsedPublicKey, ED25519};
use sha2::{Digest, Sha256};
const PREFIX: [u8; 6] = [0xfd, 0xf1, 0x1a, 0xf7, 0xc3, 0x0d];
const PREFIX_LEN: u8 = 48;
const ADDR_DOMAIN: &[u8] = b"filament/overlay-addr/v1\0";
const BIND_DOMAIN: &[u8] = b"filament/overlay-bind/v1\0";
pub fn prefix_cidr() -> String {
let net = Ipv6Addr::from([
PREFIX[0], PREFIX[1], PREFIX[2], PREFIX[3], PREFIX[4], PREFIX[5], 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,
]);
format!("{net}/{PREFIX_LEN}")
}
pub fn addr_from_pubkey(pubkey: &[u8; 32]) -> Ipv6Addr {
let mut h = Sha256::new();
h.update(ADDR_DOMAIN);
h.update(pubkey);
let digest = h.finalize();
let mut octets = [0u8; 16];
octets[..6].copy_from_slice(&PREFIX);
octets[6..16].copy_from_slice(&digest[..10]);
Ipv6Addr::from(octets)
}
const V4_PREFIX: [u8; 4] = [198, 18, 0, 0];
const V4_PREFIX_LEN: u8 = 15;
const V4_HOST_MASK: u32 = 0x0001_FFFF;
const ADDR_V4_DOMAIN: &[u8] = b"filament/overlay-v4-addr/v1\0";
pub fn prefix_v4_cidr() -> String {
format!("{}/{}", std::net::Ipv4Addr::from(V4_PREFIX), V4_PREFIX_LEN)
}
pub fn addr_v4_from_pubkey(pubkey: &[u8; 32]) -> std::net::Ipv4Addr {
let mut h = Sha256::new();
h.update(ADDR_V4_DOMAIN);
h.update(pubkey);
let digest = h.finalize();
let host = u32::from_be_bytes([digest[0], digest[1], digest[2], digest[3]]) & V4_HOST_MASK;
std::net::Ipv4Addr::from(u32::from_be_bytes(V4_PREFIX) | host)
}
fn key_path() -> PathBuf {
crate::platform::Paths::config_path("overlay.ed25519")
}
pub struct Identity {
keypair: Ed25519KeyPair,
pubkey: [u8; 32],
addr: Ipv6Addr,
}
impl Identity {
pub fn load_or_create() -> Result<Identity> {
let path = key_path();
let pkcs8 = match std::fs::read(&path) {
Ok(bytes) => bytes,
Err(_) => {
let rng = ring::rand::SystemRandom::new();
let doc = Ed25519KeyPair::generate_pkcs8(&rng)
.map_err(|_| anyhow!("overlay key generation failed"))?;
crate::platform::SecretFile::write(&path, doc.as_ref())
.context("write overlay key")?;
doc.as_ref().to_vec()
}
};
Self::from_pkcs8(&pkcs8)
}
fn from_pkcs8(pkcs8: &[u8]) -> Result<Identity> {
let keypair = Ed25519KeyPair::from_pkcs8(pkcs8)
.map_err(|_| anyhow!("overlay key is corrupt (bad PKCS8)"))?;
let mut pubkey = [0u8; 32];
pubkey.copy_from_slice(keypair.public_key().as_ref());
let addr = addr_from_pubkey(&pubkey);
Ok(Identity { keypair, pubkey, addr })
}
pub fn pubkey(&self) -> [u8; 32] {
self.pubkey
}
pub fn addr(&self) -> Ipv6Addr {
self.addr
}
pub fn addr_v4(&self) -> std::net::Ipv4Addr {
addr_v4_from_pubkey(&self.pubkey)
}
pub fn announce(&self, seq: u64, cb: &[u8]) -> Announce {
let msg = bind_message(&self.addr, seq, cb);
let sig = self.keypair.sign(&msg);
let mut sig64 = [0u8; 64];
sig64.copy_from_slice(sig.as_ref());
Announce { pubkey: self.pubkey, addr: self.addr, seq, sig: sig64 }
}
}
fn bind_message(addr: &Ipv6Addr, seq: u64, cb: &[u8]) -> Vec<u8> {
let mut msg = Vec::with_capacity(BIND_DOMAIN.len() + 16 + 8 + cb.len());
msg.extend_from_slice(BIND_DOMAIN);
msg.extend_from_slice(&addr.octets());
msg.extend_from_slice(&seq.to_be_bytes());
msg.extend_from_slice(cb);
msg
}
#[derive(Clone)]
pub struct Announce {
pub pubkey: [u8; 32],
pub addr: Ipv6Addr,
pub seq: u64,
pub sig: [u8; 64],
}
impl Announce {
pub fn addr_v4(&self) -> std::net::Ipv4Addr {
addr_v4_from_pubkey(&self.pubkey)
}
pub fn to_json(&self) -> serde_json::Value {
serde_json::json!({
"type": "l3-announce",
"pubkey": b64(&self.pubkey),
"addr": self.addr.to_string(),
"addr4": self.addr_v4().to_string(),
"seq": self.seq,
"sig": b64(&self.sig),
})
}
pub fn from_json(v: &serde_json::Value) -> Result<Announce> {
let pubkey: [u8; 32] = unb64(v["pubkey"].as_str().unwrap_or_default())?
.try_into()
.map_err(|_| anyhow!("announce pubkey not 32 bytes"))?;
let sig: [u8; 64] = unb64(v["sig"].as_str().unwrap_or_default())?
.try_into()
.map_err(|_| anyhow!("announce sig not 64 bytes"))?;
let addr: Ipv6Addr = v["addr"]
.as_str()
.and_then(|s| s.parse().ok())
.ok_or_else(|| anyhow!("announce addr invalid"))?;
let seq = v["seq"].as_u64().unwrap_or(0);
Ok(Announce { pubkey, addr, seq, sig })
}
pub fn verify(&self, cb: &[u8]) -> Result<Ipv6Addr> {
if self.addr != addr_from_pubkey(&self.pubkey) {
bail!("l3-announce: address does not match public key");
}
let msg = bind_message(&self.addr, self.seq, cb);
UnparsedPublicKey::new(&ED25519, &self.pubkey)
.verify(&msg, &self.sig)
.map_err(|_| anyhow!("l3-announce: signature or channel-binding mismatch"))?;
Ok(self.addr)
}
}
const B64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
fn b64(data: &[u8]) -> String {
let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
for chunk in data.chunks(3) {
let b = [chunk[0], *chunk.get(1).unwrap_or(&0), *chunk.get(2).unwrap_or(&0)];
let n = u32::from_be_bytes([0, b[0], b[1], b[2]]);
out.push(B64[(n >> 18) as usize & 63] as char);
out.push(B64[(n >> 12) as usize & 63] as char);
out.push(if chunk.len() > 1 { B64[(n >> 6) as usize & 63] as char } else { '=' });
out.push(if chunk.len() > 2 { B64[n as usize & 63] as char } else { '=' });
}
out
}
fn unb64(s: &str) -> Result<Vec<u8>> {
fn val(c: u8) -> Result<u32> {
match c {
b'A'..=b'Z' => Ok((c - b'A') as u32),
b'a'..=b'z' => Ok((c - b'a' + 26) as u32),
b'0'..=b'9' => Ok((c - b'0' + 52) as u32),
b'+' => Ok(62),
b'/' => Ok(63),
_ => bail!("bad base64 char"),
}
}
let s = s.trim_end_matches('=').as_bytes();
let mut out = Vec::with_capacity(s.len() / 4 * 3);
for chunk in s.chunks(4) {
let mut n = 0u32;
for (i, &c) in chunk.iter().enumerate() {
n |= val(c)? << (18 - 6 * i);
}
out.push((n >> 16) as u8);
if chunk.len() > 2 {
out.push((n >> 8) as u8);
}
if chunk.len() > 3 {
out.push(n as u8);
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn ident() -> Identity {
let rng = ring::rand::SystemRandom::new();
let doc = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
Identity::from_pkcs8(doc.as_ref()).unwrap()
}
#[test]
fn addr_is_ula_and_deterministic() {
let pk = [7u8; 32];
let a = addr_from_pubkey(&pk);
assert_eq!(a, addr_from_pubkey(&pk), "deterministic");
assert_eq!(a.octets()[..6], PREFIX, "carries the filament ULA prefix");
}
#[test]
fn distinct_keys_distinct_addrs() {
assert_ne!(addr_from_pubkey(&[1u8; 32]), addr_from_pubkey(&[2u8; 32]));
}
#[test]
fn v4_addr_in_benchmark_range_deterministic_and_distinct() {
let pk = [7u8; 32];
let a = addr_v4_from_pubkey(&pk);
assert_eq!(a, addr_v4_from_pubkey(&pk), "deterministic");
let o = a.octets();
assert_eq!(o[0], 198, "must carry the benchmark prefix: {a}");
assert!(o[1] == 18 || o[1] == 19, "must be within /15: {a}");
assert_ne!(a, addr_v4_from_pubkey(&[8u8; 32]), "distinct keys -> distinct addrs");
assert_eq!(prefix_v4_cidr(), "198.18.0.0/15");
}
#[test]
fn announce_roundtrips_and_verifies() {
let id = ident();
let cb = b"link-channel-binding-xyz";
let ann = id.announce(1, cb);
let wire = ann.to_json();
assert_eq!(wire["addr4"].as_str().unwrap(), id.addr_v4().to_string());
let parsed = Announce::from_json(&wire).unwrap();
let addr = parsed.verify(cb).expect("verifies under the same cb");
assert_eq!(addr, id.addr());
assert_eq!(parsed.addr_v4(), id.addr_v4());
}
#[test]
fn rejects_wrong_channel_binding() {
let id = ident();
let ann = id.announce(1, b"cb-of-link-A");
assert!(ann.verify(b"cb-of-link-B").is_err());
}
#[test]
fn rejects_hijacked_address() {
let id = ident();
let cb = b"cb";
let mut ann = id.announce(1, cb);
ann.addr = addr_from_pubkey(&[99u8; 32]); assert!(ann.verify(cb).is_err());
}
#[test]
fn rejects_forged_signature() {
let id = ident();
let cb = b"cb";
let mut ann = id.announce(1, cb);
ann.sig[0] ^= 0xff; assert!(ann.verify(cb).is_err());
}
#[test]
fn rejects_key_substitution() {
let a = ident();
let b = ident();
let cb = b"cb";
let mut ann = a.announce(1, cb);
ann.pubkey = b.pubkey();
assert!(ann.verify(cb).is_err());
}
#[test]
fn base64_roundtrip() {
for len in [0usize, 1, 2, 3, 31, 32, 64, 100] {
let data: Vec<u8> = (0..len).map(|i| (i * 7 + 3) as u8).collect();
assert_eq!(unb64(&b64(&data)).unwrap(), data, "len {len}");
}
}
}