matter_crypto/error.rs
1//! Error type for `matter-crypto`.
2
3use thiserror::Error;
4
5use crate::pase::PaseMessageKind;
6
7/// All errors `matter-crypto` can produce in M3 (PASE). M4 (CASE) will
8/// extend this enum; `#[non_exhaustive]` keeps that addition non-breaking.
9#[derive(Debug, Error)]
10#[non_exhaustive]
11pub enum Error {
12 // --- Spec-defined PASE status codes — transmissible on the wire. ---
13 /// Spec §3.10.5 status code: peer's parameter was malformed or out of range.
14 #[error("PASE: invalid parameter")]
15 InvalidParameter,
16
17 /// Spec §3.10.5 status code: peer's signature or confirmation tag did not validate.
18 #[error("PASE: invalid signature or tag")]
19 InvalidSignatureOrTag,
20
21 /// Spec §3.10.5 status code: referenced session does not exist.
22 #[error("PASE: session not found")]
23 SessionNotFound,
24
25 /// Spec §3.10.5 status code: referenced session has expired.
26 #[error("PASE: session expired")]
27 SessionExpired,
28
29 /// Spec §3.10.5 status code: peer is busy.
30 #[error("PASE: busy")]
31 Busy,
32
33 // --- Internal errors — never on the wire. ---
34 /// TLV codec error propagated from `matter-codec`.
35 #[error("TLV codec error: {0}")]
36 Codec(#[from] matter_codec::Error),
37
38 /// SPAKE2+ scalar was zero or out of range after sampling/reduction.
39 #[error("invalid SPAKE2+ scalar (out of range or all-zero)")]
40 InvalidScalar,
41
42 /// Confirmation tag did not match in constant-time compare.
43 ///
44 /// We never tell the peer which tag failed — that itself would be
45 /// a side-channel. Local-only abort.
46 #[error("confirmation tag did not match (constant-time compare)")]
47 ConfirmationTagMismatch,
48
49 /// Caller invoked a state-machine method that doesn't match the
50 /// current expected message (e.g., `handle_pake3` before `handle_pake2`).
51 #[error("unexpected PASE message: expected {expected:?}, got {got:?}")]
52 UnexpectedMessage {
53 /// The next message kind the state machine was expecting.
54 expected: PaseMessageKind,
55 /// The kind the caller tried to feed.
56 got: PaseMessageKind,
57 },
58
59 /// PIN-to-w0/w1 derivation failed.
60 #[error("PIN derivation failed")]
61 PinDerivationFailed,
62
63 /// Generic HKDF/KDF operation failed (e.g., ring returned an error for an
64 /// output-length or expand call that should succeed for all valid inputs).
65 ///
66 /// Used by operational identity derivations such as
67 /// [`crate::derive_compressed_fabric_id`].
68 #[error("key derivation failed")]
69 KeyDerivationFailed,
70
71 /// PBKDF iteration count below the Matter spec minimum (1000).
72 #[error("PBKDF iteration count {0} below spec minimum 1000")]
73 PbkdfIterationsTooLow(u32),
74
75 /// PBKDF iteration count above the accepted ceiling.
76 ///
77 /// The iteration count is peer-controlled and is fed directly into
78 /// PBKDF2-HMAC-SHA256 (one HMAC pass per iteration). A malicious or
79 /// spoofed PASE responder could advertise an inflated count
80 /// (up to `u32::MAX`) to force the commissioner into billions of HMAC
81 /// rounds — a single-threaded CPU denial-of-service per handshake. We
82 /// reject anything above the Matter spec's published maximum (100000)
83 /// *before* any key derivation runs.
84 #[error("PBKDF iteration count {iterations} above accepted maximum {max}")]
85 PbkdfIterationsTooHigh {
86 /// The (rejected) iteration count advertised by the peer.
87 iterations: u32,
88 /// The maximum iteration count we accept.
89 max: u32,
90 },
91
92 /// PBKDF salt length outside [16, 32] bytes per Matter spec §3.10.3.
93 #[error("PBKDF salt length {0} not in [16, 32]")]
94 PbkdfSaltLengthInvalid(usize),
95
96 /// `finish()` called before the handshake completed all phases.
97 #[error("`finish` called before handshake complete")]
98 HandshakeIncomplete,
99
100 // --- CASE-specific internal errors (M4) ---
101 /// Peer's NOC chain failed validation against the trusted RCAC roots.
102 #[error("CASE: invalid peer NOC chain — {0}")]
103 InvalidPeerNocChain(#[source] matter_cert::Error),
104
105 /// Peer's NOC carried a `FabricId` attribute that does not match the
106 /// `FabricId` we expected (i.e., the peer is on a different fabric).
107 #[error("CASE: peer NOC's fabric_id ({peer}) doesn't match local fabric_id ({local})")]
108 FabricIdMismatch {
109 /// `FabricId` carried by the peer's NOC.
110 peer: u64,
111 /// `FabricId` we expected (from our own credentials).
112 local: u64,
113 },
114
115 /// Peer's NOC carried a `NodeId` attribute that does not match the
116 /// `NodeId` we expected.
117 #[error("CASE: peer NOC's node_id ({0}) doesn't match expected ({1})")]
118 PeerNodeIdMismatch(u64, u64),
119
120 /// Ephemeral P-256 keypair generation failed (RNG failure or
121 /// repeated zero scalars).
122 #[error("CASE: ephemeral key generation failed")]
123 EphemeralKeyGenerationFailed,
124
125 /// AEAD decryption of an encrypted blob (Sigma2 or Sigma3 ciphertext)
126 /// failed — corrupt ciphertext, wrong key, or wrong nonce.
127 #[error("CASE: AEAD decryption of encrypted blob failed")]
128 EncryptedBlobDecryptionFailed,
129
130 /// AEAD encryption failed — cipher initialisation rejected the key, or
131 /// the underlying AES-CCM encrypt call failed. Not expected in practice
132 /// for the spec-bounded key and message sizes.
133 #[error("AEAD encryption failed")]
134 EncryptionFailed,
135
136 /// Peer's ECDSA signature over the SIGMA transcript did not verify.
137 #[error("CASE: peer signature did not verify")]
138 PeerSignatureInvalid,
139
140 /// Resumption MAC tag (`sigma2_resume_mic` / `sigma3_resume_mic`) did
141 /// not verify in constant time.
142 #[error("CASE: resumption MAC tag did not verify")]
143 ResumptionMacMismatch,
144
145 /// Secure random generation failed.
146 #[error("secure random generation failed")]
147 Rng,
148
149 /// `CaseSigner` returned a `SignerError`.
150 #[error("CASE: signing failed — {0}")]
151 SigningFailed(#[source] crate::case::signer::SignerError),
152
153 /// Caller fed a CASE state machine a message that doesn't match its
154 /// current expected-inbound kind (e.g., calling `handle_sigma2` before
155 /// `start` has been invoked).
156 ///
157 /// A separate variant from [`Error::UnexpectedMessage`] keeps M3 PASE
158 /// callers unchanged while surfacing the correct
159 /// [`crate::case::CaseMessageKind`].
160 #[error("CASE: unexpected message: expected {expected:?}, got {got:?}")]
161 UnexpectedCaseMessage {
162 /// The message kind the state machine was waiting for.
163 expected: crate::case::CaseMessageKind,
164 /// The kind the caller tried to supply.
165 got: crate::case::CaseMessageKind,
166 },
167}
168
169/// `Result<T, Error>` for convenience.
170pub type Result<T> = core::result::Result<T, Error>;