extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use sha2::{Digest, Sha256, Sha384, Sha512};
use sha3::{Sha3_256, Sha3_384, Sha3_512};
use crate::{
backend::TpmQuoteEvidence,
pcr::{PcrSemantic, TypedPcrBank},
tpm_structures::{TpmAlgId, TpmParseError, TpmsAttest},
};
#[cfg(feature = "tpm-crypto")]
use crate::tpm_structures::{TpmEccCurve, TpmtPublic, TpmtSignature};
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(clippy::struct_excessive_bools)]
pub struct TpmVerificationReport {
pub quote_valid: bool,
pub nonce_valid: bool,
pub pcr_digest_valid: bool,
pub ak_valid: bool,
pub warnings: Vec<String>,
}
impl TpmVerificationReport {
#[must_use]
pub fn is_fully_valid(&self) -> bool {
self.quote_valid && self.nonce_valid && self.pcr_digest_valid && self.ak_valid
}
}
pub struct TpmQuoteVerifier;
impl TpmQuoteVerifier {
pub fn verify_quote(
evidence: &TpmQuoteEvidence,
pcr_bank: &TypedPcrBank,
expected_nonce: &[u8; 32],
) -> Result<TpmVerificationReport, TpmVerifyError> {
let mut report = TpmVerificationReport {
quote_valid: false,
nonce_valid: false,
pcr_digest_valid: false,
ak_valid: false,
warnings: Vec::new(),
};
let attest =
TpmsAttest::parse_tpm2b(&evidence.quote_blob).map_err(TpmVerifyError::ParseError)?;
if attest.extra_data != expected_nonce {
return Err(TpmVerifyError::NonceMismatch);
}
report.nonce_valid = true;
if !attest.safe || !evidence.clock_info.safe {
report
.warnings
.push("TPM clock is in an unsafe state".into());
}
let expected_pcr_digest =
Self::recompute_pcr_digest(&attest.attested.pcr_select.selections, pcr_bank)?;
if attest.attested.pcr_digest != expected_pcr_digest {
return Err(TpmVerifyError::PcrDigestRecomputationMismatch);
}
report.pcr_digest_valid = true;
#[cfg(feature = "tpm-crypto")]
{
let ak_pub = TpmtPublic::parse_tpm2b(&evidence.identity.ak_pub)
.map_err(TpmVerifyError::ParseError)?;
let sig = TpmtSignature::parse(&evidence.quote_signature)
.map_err(TpmVerifyError::ParseError)?;
Self::verify_signature(&evidence.quote_blob, &sig, &ak_pub)?;
report.ak_valid = true;
report.quote_valid = true;
}
#[cfg(not(feature = "tpm-crypto"))]
{
report
.warnings
.push("tpm-crypto feature disabled; signature not verified".into());
report.ak_valid = true;
report.quote_valid = true;
}
Ok(report)
}
fn recompute_pcr_digest(
selections: &[crate::tpm_structures::TpmsPcrSelection],
bank: &TypedPcrBank,
) -> Result<Vec<u8>, TpmVerifyError> {
if selections.is_empty() {
return Err(TpmVerifyError::EmptyPcrSelection);
}
if selections.len() > 1 {
return Err(TpmVerifyError::UnsupportedMultipleBanks);
}
let selection = &selections[0];
let hash_alg = selection.hash;
let mut concatenated = Vec::new();
for (byte_idx, &byte) in selection.pcr_select.iter().enumerate() {
for bit_idx in 0..8 {
if (byte & (1 << bit_idx)) != 0 {
let pcr_index = u8::try_from(byte_idx * 8 + bit_idx).map_err(|_| {
TpmVerifyError::ParseError(TpmParseError::TooManyPcrSelections)
})?;
let measurement = bank
.get_by_index(pcr_index)
.ok_or(TpmVerifyError::MissingPcrInBank(pcr_index))?;
concatenated.extend_from_slice(&measurement.digest.value);
}
}
}
Self::hash_buffer(hash_alg, &concatenated)
}
fn hash_buffer(alg: TpmAlgId, data: &[u8]) -> Result<Vec<u8>, TpmVerifyError> {
match alg {
TpmAlgId::Sha256 => Ok(Sha256::digest(data).to_vec()),
TpmAlgId::Sha384 => Ok(Sha384::digest(data).to_vec()),
TpmAlgId::Sha512 => Ok(Sha512::digest(data).to_vec()),
TpmAlgId::Sha3_256 => Ok(Sha3_256::digest(data).to_vec()),
TpmAlgId::Sha3_384 => Ok(Sha3_384::digest(data).to_vec()),
TpmAlgId::Sha3_512 => Ok(Sha3_512::digest(data).to_vec()),
_ => Err(TpmVerifyError::UnsupportedHashAlgorithm),
}
}
#[cfg(feature = "tpm-crypto")]
fn verify_signature(
quote_blob: &[u8], sig: &TpmtSignature,
ak: &TpmtPublic,
) -> Result<(), TpmVerifyError> {
if quote_blob.len() < 2 {
return Err(TpmVerifyError::ParseError(TpmParseError::BufferTooShort));
}
let attest_bytes = "e_blob[2..];
match (sig, ak) {
(
TpmtSignature::Rsapss {
hash,
sig: sig_bytes,
},
TpmtPublic::Rsa {
public_exponent,
unique,
..
},
) => {
let hashed_attest = Self::hash_buffer(*hash, attest_bytes)?;
use rsa::pss::VerifyingKey;
use rsa::{BigUint, RsaPublicKey};
use signature::Verifier;
let n = BigUint::from_bytes_be(unique);
let e = if *public_exponent == 0 {
65537
} else {
*public_exponent
};
let e_big = BigUint::from(e);
let rsa_pub =
RsaPublicKey::new(n, e_big).map_err(|_| TpmVerifyError::InvalidPublicKey)?;
match hash {
TpmAlgId::Sha256 => {
let vk = VerifyingKey::<Sha256>::new(rsa_pub);
let signature = rsa::pss::Signature::try_from(*sig_bytes)
.map_err(|_| TpmVerifyError::InvalidSignature)?;
vk.verify(&hashed_attest, &signature)
.map_err(|_| TpmVerifyError::SignatureVerificationFailed)?;
}
TpmAlgId::Sha384 => {
let vk = VerifyingKey::<Sha384>::new(rsa_pub);
let signature = rsa::pss::Signature::try_from(*sig_bytes)
.map_err(|_| TpmVerifyError::InvalidSignature)?;
vk.verify(&hashed_attest, &signature)
.map_err(|_| TpmVerifyError::SignatureVerificationFailed)?;
}
_ => return Err(TpmVerifyError::UnsupportedHashAlgorithm),
}
}
(TpmtSignature::Ecdsa { hash, r, s }, TpmtPublic::Ecc { curve, x, y, .. }) => {
let hashed_attest = Self::hash_buffer(*hash, attest_bytes)?;
if *curve != TpmEccCurve::NistP256 {
return Err(TpmVerifyError::UnsupportedEccCurve);
}
use p256::ecdsa::{Signature, VerifyingKey};
use p256::EncodedPoint;
use signature::Verifier;
let mut point_bytes = Vec::with_capacity(1 + x.len() + y.len());
point_bytes.push(0x04); point_bytes.extend_from_slice(x);
point_bytes.extend_from_slice(y);
let point = EncodedPoint::from_bytes(&point_bytes)
.map_err(|_| TpmVerifyError::InvalidPublicKey)?;
let vk = VerifyingKey::from_encoded_point(&point)
.map_err(|_| TpmVerifyError::InvalidPublicKey)?;
let mut sig_bytes = Vec::with_capacity(r.len() + s.len());
sig_bytes.extend_from_slice(r);
sig_bytes.extend_from_slice(s);
let signature = Signature::from_slice(&sig_bytes)
.map_err(|_| TpmVerifyError::InvalidSignature)?;
vk.verify(&hashed_attest, &signature)
.map_err(|_| TpmVerifyError::SignatureVerificationFailed)?;
}
_ => return Err(TpmVerifyError::AlgorithmMismatch),
}
Ok(())
}
pub fn verify_pcr_value(
pcr_bank: &TypedPcrBank,
semantic: PcrSemantic,
expected: &[u8; 32],
) -> Result<(), TpmVerifyError> {
let measurement = pcr_bank
.get(semantic)
.ok_or(TpmVerifyError::PcrSemanticAbsent(semantic))?;
if &measurement.digest.value != expected {
return Err(TpmVerifyError::PcrValueMismatch {
semantic,
expected: *expected,
got: measurement.digest.value,
});
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TpmVerifyError {
ParseError(TpmParseError),
NonceMismatch,
PcrDigestRecomputationMismatch,
EmptyPcrSelection,
UnsupportedMultipleBanks,
MissingPcrInBank(u8),
UnsupportedHashAlgorithm,
UnsupportedEccCurve,
InvalidPublicKey,
InvalidSignature,
SignatureVerificationFailed,
AlgorithmMismatch,
PcrSemanticAbsent(PcrSemantic),
PcrValueMismatch {
semantic: PcrSemantic,
expected: [u8; 32],
got: [u8; 32],
},
}
impl core::fmt::Display for TpmVerifyError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ParseError(e) => write!(f, "TPM structure parsing failed: {e}"),
Self::NonceMismatch => f.write_str("TPM extraData does not match expected nonce"),
Self::PcrDigestRecomputationMismatch => {
f.write_str("recomputed PCR digest does not match quote pcrDigest")
}
Self::EmptyPcrSelection => f.write_str("TPML_PCR_SELECTION is empty"),
Self::UnsupportedMultipleBanks => {
f.write_str("multiple PCR banks in selection is unsupported")
}
Self::MissingPcrInBank(idx) => write!(
f,
"PCR {idx} is selected in quote but missing from evidence bank"
),
Self::UnsupportedHashAlgorithm => f.write_str("unsupported hash algorithm in quote"),
Self::UnsupportedEccCurve => f.write_str("unsupported ECC curve"),
Self::InvalidPublicKey => f.write_str("invalid AK public key"),
Self::InvalidSignature => f.write_str("invalid quote signature format"),
Self::SignatureVerificationFailed => {
f.write_str("cryptographic signature verification failed")
}
Self::AlgorithmMismatch => {
f.write_str("AK public key type does not match signature type")
}
Self::PcrSemanticAbsent(s) => write!(f, "required PCR semantic {s:?} is absent"),
Self::PcrValueMismatch { semantic, .. } => {
write!(f, "PCR value mismatch for semantic {semantic:?}")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for TpmVerifyError {}