fix(integrity): return AttestationStatus::Unavailable for key version mismatch
This commit is contained in:
@@ -63,6 +63,9 @@ pub enum Error {
|
||||
|
||||
#[error("Broken database")]
|
||||
BrokenDatabase,
|
||||
|
||||
#[error("Integrity key version mismatch: envelope uses key {envelope}, current key is {current}")]
|
||||
KeyVersionMismatch { envelope: i32, current: i32 },
|
||||
}
|
||||
|
||||
struct Unsealed {
|
||||
@@ -393,7 +396,10 @@ impl Vault {
|
||||
} = Self::expect_unsealed(&mut self.state)?;
|
||||
|
||||
if *root_key_history_id != key_version {
|
||||
return Ok(false);
|
||||
return Err(Error::KeyVersionMismatch {
|
||||
envelope: key_version,
|
||||
current: *root_key_history_id,
|
||||
});
|
||||
}
|
||||
|
||||
let mut hmac = root_key.0.read_inline(|k| {
|
||||
|
||||
@@ -192,7 +192,10 @@ pub async fn verify_entity<E: Integrable>(
|
||||
Ok(false) => Err(Error::MacMismatch {
|
||||
entity_kind: E::KIND,
|
||||
}),
|
||||
Err(SendError::HandlerError(vault::Error::Sealed)) => Ok(AttestationStatus::Unavailable),
|
||||
Err(SendError::HandlerError(vault::Error::Sealed))
|
||||
| Err(SendError::HandlerError(vault::Error::KeyVersionMismatch { .. })) => {
|
||||
Ok(AttestationStatus::Unavailable)
|
||||
}
|
||||
Err(_) => Err(Error::VaultSend),
|
||||
}
|
||||
}
|
||||
@@ -331,4 +334,47 @@ mod tests {
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, Error::MacMismatch { .. }));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn key_version_mismatch_returns_unavailable_not_mac_mismatch() {
|
||||
use crate::db::schema::integrity_envelope;
|
||||
use super::AttestationStatus;
|
||||
|
||||
const ENTITY_ID: &[u8] = b"entity-id-rotation-test";
|
||||
|
||||
let db = db::create_test_pool().await;
|
||||
let vault = bootstrapped_vault(&db).await;
|
||||
let mut conn = db.get().await.unwrap();
|
||||
|
||||
let entity = DummyEntity {
|
||||
payload_version: 1,
|
||||
payload: b"payload-v1".to_vec(),
|
||||
};
|
||||
|
||||
sign_entity(&mut conn, &vault, &entity, ENTITY_ID)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Simulate key rotation: update the stored key_version to a stale value.
|
||||
// After real rotation the vault's root_key_history_id would advance, but
|
||||
// here we achieve the same mismatch by back-dating the envelope's key_version.
|
||||
diesel::update(integrity_envelope::table)
|
||||
.filter(integrity_envelope::entity_kind.eq("dummy_entity"))
|
||||
.filter(integrity_envelope::entity_id.eq(ENTITY_ID))
|
||||
.set(integrity_envelope::key_version.eq(0))
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Must NOT error — version mismatch is Unavailable, not tampered.
|
||||
let status = verify_entity(&mut conn, &vault, &entity, ENTITY_ID)
|
||||
.await
|
||||
.expect("key version mismatch must not be treated as an error");
|
||||
|
||||
assert_eq!(
|
||||
status,
|
||||
AttestationStatus::Unavailable,
|
||||
"stale key_version must yield Unavailable, not MacMismatch"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user