use std::net::SocketAddr;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum BootstrapError {
#[error("invalid server public key: {0}")]
InvalidServerKey(String),
#[error("key generation failed: {0}")]
KeyGenerationFailed(String),
#[error("invalid key format: {0}")]
InvalidKeyFormat(String),
}
#[derive(Clone)]
pub struct ClientIdentity {
private_key: [u8; 32],
public_key: [u8; 32],
}
impl ClientIdentity {
pub fn generate() -> Result<Self, BootstrapError> {
use std::time::{SystemTime, UNIX_EPOCH};
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos() as u64;
let mut private_key = [0u8; 32];
let mut state = seed;
for byte in &mut private_key {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
*byte = (state >> 33) as u8;
}
let public_key = private_key;
Ok(Self {
private_key,
public_key,
})
}
pub fn from_private_key(private_key: [u8; 32]) -> Result<Self, BootstrapError> {
let public_key = private_key;
Ok(Self {
private_key,
public_key,
})
}
pub fn private_key(&self) -> &[u8; 32] {
&self.private_key
}
pub fn public_key(&self) -> &[u8; 32] {
&self.public_key
}
}
impl std::fmt::Debug for ClientIdentity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClientIdentity")
.field("public_key", &hex_preview(&self.public_key))
.field("private_key", &"[REDACTED]")
.finish()
}
}
#[derive(Debug, Clone)]
pub struct ServerInfo {
pub addr: SocketAddr,
pub public_key: [u8; 32],
}
impl ServerInfo {
pub fn new(addr: SocketAddr, public_key: [u8; 32]) -> Self {
Self { addr, public_key }
}
pub fn from_base64_key(addr: SocketAddr, key_base64: &str) -> Result<Self, BootstrapError> {
let key_bytes = decode_base64(key_base64)
.map_err(|e| BootstrapError::InvalidServerKey(e.to_string()))?;
if key_bytes.len() != 32 {
return Err(BootstrapError::InvalidServerKey(format!(
"expected 32 bytes, got {}",
key_bytes.len()
)));
}
let mut public_key = [0u8; 32];
public_key.copy_from_slice(&key_bytes);
Ok(Self { addr, public_key })
}
}
#[derive(Debug, Clone)]
pub struct BootstrapConfig {
pub identity: Option<ClientIdentity>,
pub server: ServerInfo,
pub state_type_id: String,
pub extensions: Vec<u16>,
}
#[derive(Debug)]
pub struct BootstrapResult {
pub identity: ClientIdentity,
pub extensions: Vec<u16>,
pub session_id: [u8; 6],
}
pub fn prepare_bootstrap(config: BootstrapConfig) -> Result<BootstrapResult, BootstrapError> {
let identity = match config.identity {
Some(id) => id,
None => ClientIdentity::generate()?,
};
Ok(BootstrapResult {
identity,
extensions: config.extensions,
session_id: [0u8; 6], })
}
fn hex_preview(bytes: &[u8]) -> String {
if bytes.len() <= 8 {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
} else {
format!(
"{}...",
bytes[..4].iter().map(|b| format!("{:02x}", b)).collect::<String>()
)
}
}
fn decode_base64(input: &str) -> Result<Vec<u8>, BootstrapError> {
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let input = input.trim().as_bytes();
let mut output = Vec::with_capacity(input.len() * 3 / 4);
let mut buffer = 0u32;
let mut bits = 0u32;
for &byte in input {
if byte == b'=' {
break;
}
let value = ALPHABET
.iter()
.position(|&c| c == byte)
.ok_or_else(|| BootstrapError::InvalidKeyFormat("invalid base64 character".into()))?
as u32;
buffer = (buffer << 6) | value;
bits += 6;
if bits >= 8 {
bits -= 8;
output.push((buffer >> bits) as u8);
buffer &= (1 << bits) - 1;
}
}
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_identity_generate() {
let id1 = ClientIdentity::generate().unwrap();
let id2 = ClientIdentity::generate().unwrap();
assert_ne!(id1.private_key(), id2.private_key());
}
#[test]
fn test_base64_decode() {
let result = decode_base64("SGVsbG8=").unwrap();
assert_eq!(result, b"Hello");
}
#[test]
fn test_server_info_from_base64() {
let key_b64 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
let addr: SocketAddr = "127.0.0.1:19999".parse().unwrap();
let server = ServerInfo::from_base64_key(addr, key_b64).unwrap();
assert_eq!(server.public_key, [0u8; 32]);
}
}