use crate::GatewardenError;
use base64::{engine::general_purpose::STANDARD, Engine};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use once_cell::sync::OnceCell;
use std::collections::HashMap;
use std::sync::RwLock;
#[derive(Debug, Clone)]
pub struct ParsedSignatureHeader {
pub key_id: Option<String>,
pub algorithm: String,
pub signature: String,
pub headers: Vec<String>,
}
pub fn parse_signature_header(header: &str) -> Result<ParsedSignatureHeader, GatewardenError> {
let mut parts: HashMap<String, String> = HashMap::new();
for part in header.split(',') {
let part = part.trim();
if let Some(eq_pos) = part.find('=') {
let key = part[..eq_pos].trim().to_lowercase();
let value = part[eq_pos + 1..].trim();
let value = value
.strip_prefix('"')
.and_then(|v| v.strip_suffix('"'))
.unwrap_or(value);
parts.insert(key, value.to_string());
}
}
let algorithm = parts
.get("algorithm")
.ok_or_else(|| {
GatewardenError::ProtocolError("Missing algorithm in signature header".to_string())
})?
.clone();
if algorithm != "ed25519" {
return Err(GatewardenError::ProtocolError(format!(
"Unsupported signature algorithm: {} (expected ed25519)",
algorithm
)));
}
let signature = parts
.get("signature")
.ok_or_else(|| {
GatewardenError::ProtocolError("Missing signature in signature header".to_string())
})?
.clone();
let headers = parts
.get("headers")
.map(|h| h.split_whitespace().map(String::from).collect())
.unwrap_or_default();
Ok(ParsedSignatureHeader {
key_id: parts.get("keyid").cloned(),
algorithm,
signature,
headers,
})
}
static KEY_CACHE: OnceCell<RwLock<HashMap<String, VerifyingKey>>> = OnceCell::new();
pub fn decode_public_key(hex_key: &str) -> Result<VerifyingKey, GatewardenError> {
let cache = KEY_CACHE.get_or_init(|| RwLock::new(HashMap::new()));
if let Ok(guard) = cache.read() {
if let Some(key) = guard.get(hex_key) {
return Ok(*key);
}
}
let bytes = hex::decode(hex_key)
.map_err(|e| GatewardenError::ConfigError(format!("Invalid public key hex: {}", e)))?;
let key_array: [u8; 32] = bytes
.try_into()
.map_err(|_| GatewardenError::ConfigError("Public key must be 32 bytes".to_string()))?;
let verifying_key = VerifyingKey::from_bytes(&key_array)
.map_err(|e| GatewardenError::ConfigError(format!("Invalid Ed25519 public key: {}", e)))?;
if let Ok(mut guard) = cache.write() {
guard.insert(hex_key.to_string(), verifying_key);
}
Ok(verifying_key)
}
pub fn verify_ed25519(
signature_b64: &str,
signing_string: &str,
verifying_key: &VerifyingKey,
) -> Result<(), GatewardenError> {
let sig_bytes = STANDARD
.decode(signature_b64)
.map_err(|e| GatewardenError::ProtocolError(format!("Invalid signature base64: {}", e)))?;
let sig_array: [u8; 64] = sig_bytes
.try_into()
.map_err(|_| GatewardenError::SignatureInvalid)?;
let signature = Signature::from_bytes(&sig_array);
verifying_key
.verify(signing_string.as_bytes(), &signature)
.map_err(|_| GatewardenError::SignatureInvalid)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_valid_signature_header() {
let header = r#"keyid="test-id", algorithm="ed25519", signature="dGVzdA==", headers="(request-target) host date digest""#;
let parsed = parse_signature_header(header).unwrap();
assert_eq!(parsed.key_id, Some("test-id".to_string()));
assert_eq!(parsed.algorithm, "ed25519");
assert_eq!(parsed.signature, "dGVzdA==");
assert_eq!(
parsed.headers,
vec!["(request-target)", "host", "date", "digest"]
);
}
#[test]
fn test_parse_signature_header_missing_algorithm() {
let header = r#"keyid="test-id", signature="dGVzdA==""#;
let result = parse_signature_header(header);
assert!(matches!(result, Err(GatewardenError::ProtocolError(_))));
}
#[test]
fn test_parse_signature_header_wrong_algorithm() {
let header = r#"algorithm="rsa-sha256", signature="dGVzdA==""#;
let result = parse_signature_header(header);
assert!(matches!(result, Err(GatewardenError::ProtocolError(_))));
}
#[test]
fn test_parse_signature_header_missing_signature() {
let header = r#"algorithm="ed25519", keyid="test""#;
let result = parse_signature_header(header);
assert!(matches!(result, Err(GatewardenError::ProtocolError(_))));
}
#[test]
fn test_decode_public_key_valid() {
let hex_key = "799efc7752286e6c3815b13358d98fc0f0b566764458adcb48f1be2c10a55906";
let result = decode_public_key(hex_key);
assert!(result.is_ok());
}
#[test]
fn test_decode_public_key_invalid_hex() {
let hex_key = "not-valid-hex";
let result = decode_public_key(hex_key);
assert!(matches!(result, Err(GatewardenError::ConfigError(_))));
}
#[test]
fn test_decode_public_key_wrong_length() {
let hex_key = "0000"; let result = decode_public_key(hex_key);
assert!(matches!(result, Err(GatewardenError::ConfigError(_))));
}
#[test]
fn test_verify_ed25519_invalid_base64() {
let hex_key = "799efc7752286e6c3815b13358d98fc0f0b566764458adcb48f1be2c10a55906";
let key = decode_public_key(hex_key).unwrap();
let result = verify_ed25519("not-valid-base64!!!", "test", &key);
assert!(matches!(result, Err(GatewardenError::ProtocolError(_))));
}
#[test]
fn test_verify_ed25519_wrong_signature_length() {
let hex_key = "799efc7752286e6c3815b13358d98fc0f0b566764458adcb48f1be2c10a55906";
let key = decode_public_key(hex_key).unwrap();
let result = verify_ed25519("dGVzdA==", "test", &key);
assert!(matches!(result, Err(GatewardenError::SignatureInvalid)));
}
#[test]
fn test_verify_ed25519_invalid_signature() {
let hex_key = "799efc7752286e6c3815b13358d98fc0f0b566764458adcb48f1be2c10a55906";
let key = decode_public_key(hex_key).unwrap();
let fake_sig = STANDARD.encode([0u8; 64]);
let result = verify_ed25519(&fake_sig, "test signing string", &key);
assert!(matches!(result, Err(GatewardenError::SignatureInvalid)));
}
}