use alloc::{string::String, vec::Vec};
use core::{error::Error, fmt::Debug};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{
KeyNumber, TagTamperStatusReadout, key_diversification::diversify_ntag424, sdm::Verifier,
types::FileSettingsError,
};
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum VerificationError<E: Error + Debug> {
#[error("tag validation failed: {0}")]
TagValidationFailed(#[from] FileSettingsError),
#[error("verifier error: {0}")]
SdmError(#[from] crate::sdm::SdmError),
#[error("verification store error: {0}")]
StoreError(E),
#[error("read counter is not greater than stored: stored {stored}, got {actual}")]
ReadCounterTooLow { stored: u32, actual: u32 },
#[error("input does not contain a UID")]
NoUid,
#[error("input prefix does not match expected prefix")]
PrefixMismatch,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ApplicationVerifier {
pub url_template: String,
pub verifier: Verifier,
pub prefix: Option<Vec<u8>>,
pub system_identifier: Vec<u8>,
}
pub trait ReadCounterStorage {
type Error: Error + Debug;
fn get(&mut self, uid: &[u8; 7]) -> impl Future<Output = Result<u32, Self::Error>>;
fn set(
&mut self,
uid: &[u8; 7],
read_counter: u32,
) -> impl Future<Output = Result<(), Self::Error>>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerifiedTagReadout {
pub uid: [u8; 7],
pub read_ctr: Option<u32>,
pub tamper_status: Option<TagTamperStatusReadout>,
}
impl ApplicationVerifier {
pub async fn verify<S: ReadCounterStorage>(
&self,
master_key: &[u8; 16],
input: &[u8],
read_counter: &mut S,
) -> Result<VerifiedTagReadout, VerificationError<S::Error>> {
if let Some(prefix) = &self.prefix
&& !input.starts_with(prefix.as_slice())
{
return Err(VerificationError::PrefixMismatch);
}
self.verifier.validate()?;
let picc_meta_key = super::picc_key(master_key);
let (uid, _) = self.verifier.decrypt_picc_data(&picc_meta_key, input)?;
let Some(uid) = uid else {
return Err(VerificationError::NoUid);
};
let file_read_key = get_key(
master_key,
self.verifier.file_read_key(),
&uid,
&self.system_identifier,
);
let verification =
self.verifier
.verify_with_meta_key(input, &file_read_key, &picc_meta_key)?;
if let Some(counter) = verification.read_ctr {
let stored = read_counter
.get(&uid)
.await
.map_err(VerificationError::StoreError)?;
if counter <= stored {
return Err(VerificationError::ReadCounterTooLow {
stored,
actual: counter,
});
}
read_counter
.set(&uid, counter)
.await
.map_err(VerificationError::StoreError)?;
}
Ok(VerifiedTagReadout {
uid,
read_ctr: verification.read_ctr,
tamper_status: verification.tamper_status,
})
}
}
fn get_key(
master_key: &[u8; 16],
key_number: KeyNumber,
uid: &[u8; 7],
system_identifier: &[u8],
) -> [u8; 16] {
match key_number {
KeyNumber::Key1 => super::picc_key(master_key),
k @ (KeyNumber::Key0 | KeyNumber::Key2 | KeyNumber::Key3 | KeyNumber::Key4) => {
diversify_ntag424(master_key, uid, k, system_identifier)
}
}
}