Skip to main content

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>;