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>;
#[derive(Debug, Clone)]
pub struct Keypair {
pub private_key: String,
pub public_key: String,
}
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,
})
}
pub fn generate_hmac_secret() -> String {
let mut bytes = [0u8; 32];
OsRng.fill_bytes(&mut bytes);
BASE64.encode(bytes)
}
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()))
}
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());
}
}