porta-rs 0.1.1

Zero-trust CGNAT bypass via WireGuard tunnel
use anyhow::{Context, Result};
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use hmac::{Hmac, Mac};
use rand_core::{OsRng, RngCore};
use sha2::Sha256;
use x25519_dalek::{PublicKey, StaticSecret};

type HmacSha256 = Hmac<Sha256>;

/// WireGuard keypair
#[derive(Debug, Clone)]
pub struct Keypair {
    /// Base64-encoded private key
    pub private_key: String,
    /// Base64-encoded public key
    pub public_key: String,
}

/// Generate a new WireGuard keypair
pub fn generate_keypair() -> Result<Keypair> {
    let secret = StaticSecret::random_from_rng(OsRng);
    let public = PublicKey::from(&secret);

    let private_key = BASE64.encode(secret.to_bytes());
    let public_key = BASE64.encode(public.as_bytes());

    Ok(Keypair {
        private_key,
        public_key,
    })
}

/// Generate HMAC secret for authentication
pub fn generate_hmac_secret() -> String {
    let mut bytes = [0u8; 32];
    OsRng.fill_bytes(&mut bytes);
    BASE64.encode(bytes)
}

/// Compute HMAC-SHA256 of a message
pub fn compute_hmac(secret: &str, message: &[u8]) -> Result<String> {
    let key = BASE64
        .decode(secret)
        .context("Failed to decode HMAC secret")?;

    let mut mac = HmacSha256::new_from_slice(&key)
        .context("Failed to create HMAC")?;
    mac.update(message);

    Ok(BASE64.encode(mac.finalize().into_bytes()))
}

/// Verify HMAC-SHA256 of a message
pub fn verify_hmac(secret: &str, message: &[u8], expected: &str) -> Result<bool> {
    let key = BASE64
        .decode(secret)
        .context("Failed to decode HMAC secret")?;

    let mut mac = HmacSha256::new_from_slice(&key)
        .context("Failed to create HMAC")?;
    mac.update(message);

    let expected_bytes = BASE64
        .decode(expected)
        .context("Failed to decode expected HMAC")?;

    Ok(mac.verify_slice(&expected_bytes).is_ok())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_keypair_generation() {
        let kp = generate_keypair().unwrap();
        assert!(!kp.private_key.is_empty());
        assert!(!kp.public_key.is_empty());
    }

    #[test]
    fn test_hmac() {
        let secret = generate_hmac_secret();
        let message = b"test message";

        let hmac = compute_hmac(&secret, message).unwrap();
        assert!(verify_hmac(&secret, message, &hmac).unwrap());
        assert!(!verify_hmac(&secret, b"wrong message", &hmac).unwrap());
    }
}