#![forbid(unsafe_code)]
#![warn(missing_docs)]
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HashAlgorithm {
Sha256,
Sha384,
Sha512,
Sha3_256,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvidenceRecord {
pub version: u32,
pub digest_algorithms: Vec<HashAlgorithm>,
pub archive_time_stamp_sequences: Vec<ArchiveTimeStampSequence>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchiveTimeStampSequence {
pub sequence_number: u32,
pub reduced_hash_tree: Vec<[u8; 32]>,
pub time_stamp: TimeStamp,
pub applied_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeStamp {
pub tsa_id: String,
pub token: Vec<u8>,
pub hashed_message: [u8; 32],
}
#[derive(Debug, thiserror::Error)]
pub enum ErsError {
#[error("hash algorithm mismatch: expected {expected:?}, got {actual:?}")]
HashMismatch {
expected: HashAlgorithm,
actual: HashAlgorithm,
},
#[error("TSA not trusted: {0}")]
UntrustedTsa(String),
#[error("hash chain broken at sequence {0}")]
BrokenChain(u32),
}
pub fn build_initial_evidence_record(
artifact_hash: [u8; 32],
algorithm: HashAlgorithm,
tsa_id: impl Into<String>,
timestamp_token: Vec<u8>,
) -> EvidenceRecord {
let ts = TimeStamp {
tsa_id: tsa_id.into(),
token: timestamp_token,
hashed_message: artifact_hash,
};
let seq = ArchiveTimeStampSequence {
sequence_number: 0,
reduced_hash_tree: vec![artifact_hash],
time_stamp: ts,
applied_at: Utc::now(),
};
EvidenceRecord {
version: 1,
digest_algorithms: vec![algorithm],
archive_time_stamp_sequences: vec![seq],
}
}
pub fn renew_evidence_record(
existing: &mut EvidenceRecord,
new_algorithm: HashAlgorithm,
new_artifact_hash: [u8; 32],
tsa_id: impl Into<String>,
timestamp_token: Vec<u8>,
) {
let next_seq = existing.archive_time_stamp_sequences.len() as u32;
let ts = TimeStamp {
tsa_id: tsa_id.into(),
token: timestamp_token,
hashed_message: new_artifact_hash,
};
let seq = ArchiveTimeStampSequence {
sequence_number: next_seq,
reduced_hash_tree: vec![new_artifact_hash],
time_stamp: ts,
applied_at: Utc::now(),
};
existing.digest_algorithms.push(new_algorithm);
existing.archive_time_stamp_sequences.push(seq);
}
pub fn renewal_count(record: &EvidenceRecord) -> u32 {
record.archive_time_stamp_sequences.len() as u32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_initial_record() {
let r = build_initial_evidence_record(
[1u8; 32],
HashAlgorithm::Sha256,
"tsa.example.com",
vec![0u8; 100],
);
assert_eq!(renewal_count(&r), 1);
assert_eq!(r.digest_algorithms, vec![HashAlgorithm::Sha256]);
}
#[test]
fn renew_adds_sequence() {
let mut r = build_initial_evidence_record(
[1u8; 32],
HashAlgorithm::Sha256,
"tsa",
vec![],
);
renew_evidence_record(
&mut r,
HashAlgorithm::Sha384,
[2u8; 32],
"tsa2",
vec![],
);
assert_eq!(renewal_count(&r), 2);
assert_eq!(r.digest_algorithms.len(), 2);
}
}