use chrono::Utc;
use hex;
use sha2::{Digest, Sha256};
use crate::models::Manifest;
use crate::error::{FingerprintError, FingerprintResult};
use crate::models::SignatureMetadata;
pub const ALGORITHM_HMAC_SHA256: &str = "HMAC-SHA256";
#[derive(Clone)]
pub struct SigningKey {
pub key_id: String,
secret: Vec<u8>,
}
impl SigningKey {
pub fn new(key_id: impl Into<String>, secret: impl AsRef<[u8]>) -> Self {
Self {
key_id: key_id.into(),
secret: secret.as_ref().to_vec(),
}
}
pub fn generate(key_id: impl Into<String>) -> Self {
use rand::RngExt;
let mut rng = rand::rng();
let mut secret = vec![0u8; 32];
rng.fill(&mut secret[..]);
Self {
key_id: key_id.into(),
secret,
}
}
pub fn from_hex(key_id: impl Into<String>, hex_secret: &str) -> FingerprintResult<Self> {
let secret = hex::decode(hex_secret).map_err(|e| {
FingerprintError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid hex key: {e}"),
))
})?;
Ok(Self {
key_id: key_id.into(),
secret,
})
}
pub fn to_hex(&self) -> String {
hex::encode(&self.secret)
}
pub fn sign(&self, data: &[u8]) -> SignatureMetadata {
let signature = self.compute_signature(data);
SignatureMetadata {
algorithm: ALGORITHM_HMAC_SHA256.to_string(),
key_id: self.key_id.clone(),
signature: hex::encode(&signature),
signed_at: Utc::now(),
}
}
pub fn verify(&self, data: &[u8], signature: &SignatureMetadata) -> FingerprintResult<bool> {
if signature.algorithm != ALGORITHM_HMAC_SHA256 {
return Err(FingerprintError::InvalidFormat(format!(
"Unsupported signature algorithm: {}",
signature.algorithm
)));
}
let expected_signature = hex::decode(&signature.signature).map_err(|e| {
FingerprintError::InvalidFormat(format!("Invalid signature encoding: {e}"))
})?;
let actual_signature = self.compute_signature(data);
Ok(constant_time_compare(
&actual_signature,
&expected_signature,
))
}
fn compute_signature(&self, data: &[u8]) -> Vec<u8> {
let block_size = 64;
let mut key = self.secret.clone();
if key.len() > block_size {
let mut hasher = Sha256::new();
hasher.update(&key);
key = hasher.finalize().to_vec();
}
key.resize(block_size, 0);
let ipad: Vec<u8> = key.iter().map(|b| b ^ 0x36).collect();
let opad: Vec<u8> = key.iter().map(|b| b ^ 0x5c).collect();
let mut inner_hasher = Sha256::new();
inner_hasher.update(&ipad);
inner_hasher.update(data);
let inner_hash = inner_hasher.finalize();
let mut outer_hasher = Sha256::new();
outer_hasher.update(&opad);
outer_hasher.update(inner_hash);
outer_hasher.finalize().to_vec()
}
}
impl std::fmt::Debug for SigningKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SigningKey")
.field("key_id", &self.key_id)
.field("secret", &"[REDACTED]")
.finish()
}
}
fn constant_time_compare(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut result = 0u8;
for (x, y) in a.iter().zip(b.iter()) {
result |= x ^ y;
}
result == 0
}
pub fn canonical_manifest_json(manifest: &Manifest) -> String {
let mut manifest = manifest.clone();
manifest.signature = None;
let value = serde_json::to_value(&manifest)
.expect("Manifest serialization to serde_json::Value should not fail");
let sorted = sort_json_value(value);
serde_json::to_string(&sorted)
.expect("Sorted JSON Value serialization to string should not fail")
}
fn sort_json_value(value: serde_json::Value) -> serde_json::Value {
use serde_json::Value;
use std::collections::BTreeMap;
match value {
Value::Object(map) => {
let sorted: BTreeMap<String, Value> = map
.into_iter()
.map(|(k, v)| (k, sort_json_value(v)))
.collect();
Value::Object(sorted.into_iter().collect())
}
Value::Array(arr) => Value::Array(arr.into_iter().map(sort_json_value).collect()),
other => other,
}
}
#[derive(Debug, Clone)]
pub struct DsfSigner {
key: SigningKey,
}
impl DsfSigner {
pub fn new(key: SigningKey) -> Self {
Self { key }
}
pub fn sign_manifest(&self, manifest: &Manifest) -> SignatureMetadata {
let canonical = canonical_manifest_json(manifest);
self.key.sign(canonical.as_bytes())
}
pub fn sign(&self, manifest_data: &[u8]) -> SignatureMetadata {
self.key.sign(manifest_data)
}
pub fn key_id(&self) -> &str {
&self.key.key_id
}
}
#[derive(Debug, Clone)]
pub struct DsfVerifier {
key: SigningKey,
}
impl DsfVerifier {
pub fn new(key: SigningKey) -> Self {
Self { key }
}
pub fn verify_manifest(&self, manifest: &Manifest) -> FingerprintResult<bool> {
let signature = match &manifest.signature {
Some(sig) => sig,
None => return Ok(false),
};
if signature.key_id != self.key.key_id {
return Ok(false);
}
let canonical = canonical_manifest_json(manifest);
self.key.verify(canonical.as_bytes(), signature)
}
pub fn verify(
&self,
manifest_data: &[u8],
signature: &SignatureMetadata,
) -> FingerprintResult<bool> {
if signature.key_id != self.key.key_id {
return Ok(false);
}
self.key.verify(manifest_data, signature)
}
pub fn key_id(&self) -> &str {
&self.key.key_id
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sign_and_verify() {
let key = SigningKey::generate("test-key");
let data = b"Hello, World!";
let signature = key.sign(data);
assert_eq!(signature.algorithm, ALGORITHM_HMAC_SHA256);
assert_eq!(signature.key_id, "test-key");
let result = key.verify(data, &signature).unwrap();
assert!(result, "Signature should be valid");
}
#[test]
fn test_verify_wrong_data() {
let key = SigningKey::generate("test-key");
let data = b"Hello, World!";
let wrong_data = b"Hello, World";
let signature = key.sign(data);
let result = key.verify(wrong_data, &signature).unwrap();
assert!(!result, "Signature should be invalid for wrong data");
}
#[test]
fn test_key_from_hex() {
let original = SigningKey::generate("test-key");
let hex_secret = original.to_hex();
let restored = SigningKey::from_hex("test-key", &hex_secret).unwrap();
assert_eq!(restored.key_id, original.key_id);
let data = b"test data";
let sig1 = original.sign(data);
let sig2 = restored.sign(data);
assert_eq!(sig1.signature, sig2.signature);
}
#[test]
fn test_signer_verifier() {
let key = SigningKey::generate("my-key");
let signer = DsfSigner::new(key.clone());
let verifier = DsfVerifier::new(key);
let manifest_data = b"{\"version\":\"1.0\",\"format\":\"dsf\"}";
let signature = signer.sign(manifest_data);
let is_valid = verifier.verify(manifest_data, &signature).unwrap();
assert!(is_valid, "Signature should be valid");
}
}