1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/// Detailed outcome from receipt verification against the current store.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ReceiptVerification {
/// The receipt signature verified against a configured signing key.
Signed,
/// The receipt was intentionally unsigned and the store has no verifying
/// key registry, so unsigned admission is valid for this store.
UnsignedAccepted,
/// The receipt does not match the store's committed index/signing state.
Invalid(ReceiptVerificationError),
}
impl ReceiptVerification {
/// Return true when the receipt is valid for the current store state.
#[must_use]
pub fn is_valid(&self) -> bool {
matches!(self, Self::Signed | Self::UnsignedAccepted)
}
/// Return true only when the receipt carried a signature that verified
/// against a configured signing key.
///
/// Unlike [`is_valid`](Self::is_valid), an unsigned receipt that was
/// accepted only because the store has no verifying keys returns `false`
/// here. Callers that require cryptographic proof of authenticity (rather
/// than "valid under this store's signing policy") must use this method.
#[must_use]
pub fn is_signed(&self) -> bool {
matches!(self, Self::Signed)
}
/// Return the rejection reason, if verification failed.
#[must_use]
pub fn error(&self) -> Option<&ReceiptVerificationError> {
if let Self::Invalid(error) = self {
Some(error)
} else {
None
}
}
}
/// Reason a receipt failed verification.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ReceiptVerificationError {
/// The receipt's event id is absent from the current store index.
MissingCommittedEvent,
/// The receipt event id differs from the committed index entry.
EventIdMismatch,
/// The receipt sequence differs from the committed index entry.
SequenceMismatch,
/// The receipt disk position differs from the committed index entry.
DiskPositionMismatch,
/// The receipt content hash differs from the committed index entry.
ContentHashMismatch,
/// The receipt extension cargo differs from the committed index entry.
ExtensionsMismatch,
/// A denial receipt points at an index entry that is not a system denial.
DenialKindMismatch,
/// The receipt is unsigned, but this store has verifying keys configured.
UnsignedReceiptRejected,
/// The receipt omitted a signature while naming a non-sentinel key id.
MissingSignature,
/// The receipt carried a signature while naming the unsigned sentinel key.
ZeroKeyWithSignature,
/// The receipt key id is not in this store's verifying-key registry.
UnknownSigningKey,
/// The receipt signature did not verify against its cover bytes.
InvalidSignature,
/// The signature cover could not be rebuilt from the committed entry.
CoverBuildFailed {
/// Human-readable encoding failure returned while rebuilding the cover.
reason: String,
},
}
#[cfg(test)]
mod tests {
use super::{ReceiptVerification, ReceiptVerificationError};
#[test]
fn is_signed_distinguishes_cryptographic_proof_from_mere_validity() {
// A genuinely signed receipt is both signed and valid.
assert!(ReceiptVerification::Signed.is_signed());
assert!(ReceiptVerification::Signed.is_valid());
// An unsigned receipt accepted only because the store has no verifying
// keys is VALID for the store but carries NO cryptographic proof, so a
// caller demanding authenticity must not be fooled by `is_valid`.
assert!(!ReceiptVerification::UnsignedAccepted.is_signed());
assert!(ReceiptVerification::UnsignedAccepted.is_valid());
// A rejected receipt is neither signed nor valid.
let invalid = ReceiptVerification::Invalid(ReceiptVerificationError::MissingSignature);
assert!(!invalid.is_signed());
assert!(!invalid.is_valid());
}
#[test]
fn error_returns_the_rejection_reason_for_invalid_only() {
// Kills `error -> None`: an Invalid receipt must surface its exact reason.
let invalid = ReceiptVerification::Invalid(ReceiptVerificationError::UnknownSigningKey);
assert_eq!(
invalid.error(),
Some(&ReceiptVerificationError::UnknownSigningKey),
"Invalid must return its rejection reason, never None"
);
// Accepting variants carry no error.
assert_eq!(ReceiptVerification::Signed.error(), None);
assert_eq!(ReceiptVerification::UnsignedAccepted.error(), None);
}
}