use base64ct::{Base64, Encoding};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use rand;
use super::errors::AuthError;
use crate::Entry;
pub const ED25519_PUBLIC_KEY_SIZE: usize = 32;
pub const ED25519_PRIVATE_KEY_SIZE: usize = 32;
pub const ED25519_SIGNATURE_SIZE: usize = 64;
pub const CHALLENGE_SIZE: usize = 32;
pub fn parse_public_key(key_str: impl AsRef<str>) -> Result<VerifyingKey, AuthError> {
let key_str = key_str.as_ref();
if !key_str.starts_with("ed25519:") {
return Err(AuthError::InvalidKeyFormat {
reason: "Key must start with 'ed25519:' prefix".to_string(),
});
}
let key_data = &key_str[8..];
let key_bytes = Base64::decode_vec(key_data).map_err(|e| AuthError::InvalidKeyFormat {
reason: format!("Invalid base64 for key: {e}"),
})?;
if key_bytes.len() != ED25519_PUBLIC_KEY_SIZE {
return Err(AuthError::InvalidKeyFormat {
reason: format!("Ed25519 public key must be {ED25519_PUBLIC_KEY_SIZE} bytes"),
});
}
let key_array: [u8; ED25519_PUBLIC_KEY_SIZE] =
key_bytes
.try_into()
.map_err(|_| AuthError::InvalidKeyFormat {
reason: "Invalid key length after base64 decoding".to_string(),
})?;
VerifyingKey::from_bytes(&key_array).map_err(|e| AuthError::KeyParsingFailed { source: e })
}
pub fn format_public_key(key: &VerifyingKey) -> String {
let key_bytes = key.to_bytes();
let encoded = Base64::encode_string(&key_bytes);
format!("ed25519:{encoded}")
}
pub fn generate_keypair() -> (SigningKey, VerifyingKey) {
let mut rng = rand::rngs::OsRng;
let signing_key = SigningKey::generate(&mut rng);
let verifying_key = signing_key.verifying_key();
(signing_key, verifying_key)
}
pub fn sign_entry(entry: &Entry, signing_key: &SigningKey) -> Result<String, crate::Error> {
let signing_bytes = entry.signing_bytes()?;
let signature = signing_key.sign(&signing_bytes);
Ok(Base64::encode_string(&signature.to_bytes()))
}
pub fn verify_entry_signature(
entry: &Entry,
verifying_key: &VerifyingKey,
) -> Result<bool, AuthError> {
let signature_base64 = entry.sig.sig.as_ref().ok_or(AuthError::InvalidSignature)?;
let signature_bytes =
Base64::decode_vec(signature_base64).map_err(|_| AuthError::InvalidSignature)?;
if signature_bytes.len() != ED25519_SIGNATURE_SIZE {
return Err(AuthError::InvalidSignature);
}
let signature_array: [u8; ED25519_SIGNATURE_SIZE] = signature_bytes
.try_into()
.map_err(|_| AuthError::InvalidSignature)?;
let signature = Signature::from_bytes(&signature_array);
let signing_bytes = entry
.signing_bytes()
.map_err(|e| AuthError::InvalidAuthConfiguration {
reason: format!("Failed to get signing bytes: {e}"),
})?;
match verifying_key.verify(&signing_bytes, &signature) {
Ok(()) => Ok(true),
Err(_) => Ok(false),
}
}
pub fn sign_data(data: impl AsRef<[u8]>, signing_key: &SigningKey) -> String {
let signature = signing_key.sign(data.as_ref());
Base64::encode_string(&signature.to_bytes())
}
pub fn verify_signature(
data: impl AsRef<[u8]>,
signature_base64: impl AsRef<str>,
verifying_key: &VerifyingKey,
) -> Result<bool, AuthError> {
let signature_bytes =
Base64::decode_vec(signature_base64.as_ref()).map_err(|_| AuthError::InvalidSignature)?;
if signature_bytes.len() != ED25519_SIGNATURE_SIZE {
return Err(AuthError::InvalidSignature);
}
let signature_array: [u8; ED25519_SIGNATURE_SIZE] = signature_bytes
.try_into()
.map_err(|_| AuthError::InvalidSignature)?;
let signature = Signature::from_bytes(&signature_array);
match verifying_key.verify(data.as_ref(), &signature) {
Ok(()) => Ok(true),
Err(_) => Ok(false),
}
}
pub fn generate_challenge() -> Vec<u8> {
use rand::Rng;
let mut rng = rand::rngs::OsRng;
let mut challenge = vec![0u8; CHALLENGE_SIZE];
rng.fill(&mut challenge[..]);
challenge
}
pub fn create_challenge_response(challenge: impl AsRef<[u8]>, signing_key: &SigningKey) -> Vec<u8> {
let signature = signing_key.sign(challenge.as_ref());
signature.to_bytes().to_vec()
}
pub fn verify_challenge_response(
challenge: impl AsRef<[u8]>,
response: impl AsRef<[u8]>,
public_key_str: impl AsRef<str>,
) -> Result<bool, AuthError> {
let verifying_key = parse_public_key(public_key_str)?;
let signature_b64 = Base64::encode_string(response.as_ref());
verify_signature(challenge, signature_b64, &verifying_key)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keypair_generation() {
let (signing_key, verifying_key) = generate_keypair();
let test_data = b"hello world";
let signature = sign_data(test_data, &signing_key);
assert!(verify_signature(test_data, &signature, &verifying_key).unwrap());
let wrong_data = b"goodbye world";
assert!(!verify_signature(wrong_data, &signature, &verifying_key).unwrap());
}
#[test]
fn test_key_formatting() {
let (_, verifying_key) = generate_keypair();
let formatted = format_public_key(&verifying_key);
assert!(formatted.starts_with("ed25519:"));
let parsed = parse_public_key(&formatted);
assert!(parsed.is_ok());
assert_eq!(parsed.unwrap(), verifying_key);
}
#[test]
fn test_entry_signing() {
let (signing_key, verifying_key) = generate_keypair();
let mut entry = Entry::root_builder()
.build()
.expect("Root entry should build successfully");
entry.sig = crate::auth::types::SigInfo::builder()
.key(crate::auth::types::SigKey::Direct("KEY_LAPTOP".to_string()))
.build();
let signature = sign_entry(&entry, &signing_key).unwrap();
entry.sig.sig = Some(signature);
assert!(verify_entry_signature(&entry, &verifying_key).unwrap());
let (_, wrong_key) = generate_keypair();
assert!(!verify_entry_signature(&entry, &wrong_key).unwrap());
}
#[test]
fn test_challenge_generation() {
let challenge1 = generate_challenge();
let challenge2 = generate_challenge();
assert_eq!(challenge1.len(), CHALLENGE_SIZE);
assert_eq!(challenge2.len(), CHALLENGE_SIZE);
assert_ne!(challenge1, challenge2);
}
#[test]
fn test_challenge_response() {
let (signing_key, verifying_key) = generate_keypair();
let public_key_str = format_public_key(&verifying_key);
let challenge = generate_challenge();
let response = create_challenge_response(&challenge, &signing_key);
assert_eq!(response.len(), ED25519_SIGNATURE_SIZE);
assert!(verify_challenge_response(&challenge, &response, &public_key_str).unwrap());
let wrong_challenge = generate_challenge();
assert!(!verify_challenge_response(&wrong_challenge, &response, &public_key_str).unwrap());
let (_, wrong_verifying_key) = generate_keypair();
let wrong_public_key_str = format_public_key(&wrong_verifying_key);
assert!(!verify_challenge_response(&challenge, &response, &wrong_public_key_str).unwrap());
}
}