use std::time::{SystemTime, UNIX_EPOCH};
use base64::engine::general_purpose::STANDARD as B64;
use base64::Engine;
use ed25519_dalek::{Signature, Verifier as _, VerifyingKey, SIGNATURE_LENGTH};
use serde::{Deserialize, Serialize};
use crate::error::AttestationError;
use crate::hash::canonical_hash;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct Attestation {
pub algorithm: String,
pub signed_hash: String,
pub signature: String,
pub key_url: String,
pub signed_at: String,
}
impl Attestation {
pub fn new(signed_hash: String, signature_bytes: &[u8], key_url: String) -> Self {
Self {
algorithm: "ed25519".to_string(),
signed_hash,
signature: B64.encode(signature_bytes),
key_url,
signed_at: now_iso(),
}
}
pub fn verify<T: Serialize>(
&self,
verifying_key: &VerifyingKey,
body: &T,
) -> Result<(), AttestationError> {
if self.algorithm != "ed25519" {
return Err(AttestationError::UnsupportedAlgorithm(
self.algorithm.clone(),
));
}
let actual = canonical_hash(body)?;
if actual != self.signed_hash {
return Err(AttestationError::HashMismatch {
expected: self.signed_hash.clone(),
actual,
});
}
let bytes = B64.decode(&self.signature)?;
if bytes.len() != SIGNATURE_LENGTH {
return Err(AttestationError::WrongSignatureLength(bytes.len()));
}
let mut arr = [0u8; SIGNATURE_LENGTH];
arr.copy_from_slice(&bytes);
let sig = Signature::from_bytes(&arr);
verifying_key
.verify(self.signed_hash.as_bytes(), &sig)
.map_err(|_| AttestationError::BadSignature)
}
}
fn now_iso() -> String {
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |d| d.as_secs());
jiffy::format_utc(secs)
}
mod jiffy {
pub(super) fn format_utc(secs: u64) -> String {
let days = (secs / 86_400) as i64;
let mut rem = secs % 86_400;
let hh = rem / 3600;
rem %= 3600;
let mm = rem / 60;
let ss = rem % 60;
let (y, mo, d) = civil_from_days(days + 719_468);
format!("{y:04}-{mo:02}-{d:02}T{hh:02}:{mm:02}:{ss:02}Z")
}
fn civil_from_days(z: i64) -> (i32, u32, u32) {
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = (z - era * 146_097) as u64; let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
let y = if m <= 2 { y + 1 } else { y };
(y as i32, m, d)
}
}