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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! Error type for `matter-crypto`.
use thiserror::Error;
use crate::pase::PaseMessageKind;
/// All errors `matter-crypto` can produce in M3 (PASE). M4 (CASE) will
/// extend this enum; `#[non_exhaustive]` keeps that addition non-breaking.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
// --- Spec-defined PASE status codes — transmissible on the wire. ---
/// Spec §3.10.5 status code: peer's parameter was malformed or out of range.
#[error("PASE: invalid parameter")]
InvalidParameter,
/// Spec §3.10.5 status code: peer's signature or confirmation tag did not validate.
#[error("PASE: invalid signature or tag")]
InvalidSignatureOrTag,
/// Spec §3.10.5 status code: referenced session does not exist.
#[error("PASE: session not found")]
SessionNotFound,
/// Spec §3.10.5 status code: referenced session has expired.
#[error("PASE: session expired")]
SessionExpired,
/// Spec §3.10.5 status code: peer is busy.
#[error("PASE: busy")]
Busy,
// --- Internal errors — never on the wire. ---
/// TLV codec error propagated from `matter-codec`.
#[error("TLV codec error: {0}")]
Codec(#[from] matter_codec::Error),
/// SPAKE2+ scalar was zero or out of range after sampling/reduction.
#[error("invalid SPAKE2+ scalar (out of range or all-zero)")]
InvalidScalar,
/// Confirmation tag did not match in constant-time compare.
///
/// We never tell the peer which tag failed — that itself would be
/// a side-channel. Local-only abort.
#[error("confirmation tag did not match (constant-time compare)")]
ConfirmationTagMismatch,
/// Caller invoked a state-machine method that doesn't match the
/// current expected message (e.g., `handle_pake3` before `handle_pake2`).
#[error("unexpected PASE message: expected {expected:?}, got {got:?}")]
UnexpectedMessage {
/// The next message kind the state machine was expecting.
expected: PaseMessageKind,
/// The kind the caller tried to feed.
got: PaseMessageKind,
},
/// PIN-to-w0/w1 derivation failed.
#[error("PIN derivation failed")]
PinDerivationFailed,
/// Generic HKDF/KDF operation failed (e.g., ring returned an error for an
/// output-length or expand call that should succeed for all valid inputs).
///
/// Used by operational identity derivations such as
/// [`crate::derive_compressed_fabric_id`].
#[error("key derivation failed")]
KeyDerivationFailed,
/// PBKDF iteration count below the Matter spec minimum (1000).
#[error("PBKDF iteration count {0} below spec minimum 1000")]
PbkdfIterationsTooLow(u32),
/// PBKDF iteration count above the accepted ceiling.
///
/// The iteration count is peer-controlled and is fed directly into
/// PBKDF2-HMAC-SHA256 (one HMAC pass per iteration). A malicious or
/// spoofed PASE responder could advertise an inflated count
/// (up to `u32::MAX`) to force the commissioner into billions of HMAC
/// rounds — a single-threaded CPU denial-of-service per handshake. We
/// reject anything above the Matter spec's published maximum (100000)
/// *before* any key derivation runs.
#[error("PBKDF iteration count {iterations} above accepted maximum {max}")]
PbkdfIterationsTooHigh {
/// The (rejected) iteration count advertised by the peer.
iterations: u32,
/// The maximum iteration count we accept.
max: u32,
},
/// PBKDF salt length outside [16, 32] bytes per Matter spec §3.10.3.
#[error("PBKDF salt length {0} not in [16, 32]")]
PbkdfSaltLengthInvalid(usize),
/// `finish()` called before the handshake completed all phases.
#[error("`finish` called before handshake complete")]
HandshakeIncomplete,
// --- CASE-specific internal errors (M4) ---
/// Peer's NOC chain failed validation against the trusted RCAC roots.
#[error("CASE: invalid peer NOC chain — {0}")]
InvalidPeerNocChain(#[source] matter_cert::Error),
/// Peer's NOC carried a `FabricId` attribute that does not match the
/// `FabricId` we expected (i.e., the peer is on a different fabric).
#[error("CASE: peer NOC's fabric_id ({peer}) doesn't match local fabric_id ({local})")]
FabricIdMismatch {
/// `FabricId` carried by the peer's NOC.
peer: u64,
/// `FabricId` we expected (from our own credentials).
local: u64,
},
/// Peer's NOC carried a `NodeId` attribute that does not match the
/// `NodeId` we expected.
#[error("CASE: peer NOC's node_id ({0}) doesn't match expected ({1})")]
PeerNodeIdMismatch(u64, u64),
/// Ephemeral P-256 keypair generation failed (RNG failure or
/// repeated zero scalars).
#[error("CASE: ephemeral key generation failed")]
EphemeralKeyGenerationFailed,
/// AEAD decryption of an encrypted blob (Sigma2 or Sigma3 ciphertext)
/// failed — corrupt ciphertext, wrong key, or wrong nonce.
#[error("CASE: AEAD decryption of encrypted blob failed")]
EncryptedBlobDecryptionFailed,
/// AEAD encryption failed — cipher initialisation rejected the key, or
/// the underlying AES-CCM encrypt call failed. Not expected in practice
/// for the spec-bounded key and message sizes.
#[error("AEAD encryption failed")]
EncryptionFailed,
/// Peer's ECDSA signature over the SIGMA transcript did not verify.
#[error("CASE: peer signature did not verify")]
PeerSignatureInvalid,
/// Resumption MAC tag (`sigma2_resume_mic` / `sigma3_resume_mic`) did
/// not verify in constant time.
#[error("CASE: resumption MAC tag did not verify")]
ResumptionMacMismatch,
/// Secure random generation failed.
#[error("secure random generation failed")]
Rng,
/// `CaseSigner` returned a `SignerError`.
#[error("CASE: signing failed — {0}")]
SigningFailed(#[source] crate::case::signer::SignerError),
/// Caller fed a CASE state machine a message that doesn't match its
/// current expected-inbound kind (e.g., calling `handle_sigma2` before
/// `start` has been invoked).
///
/// A separate variant from [`Error::UnexpectedMessage`] keeps M3 PASE
/// callers unchanged while surfacing the correct
/// [`crate::case::CaseMessageKind`].
#[error("CASE: unexpected message: expected {expected:?}, got {got:?}")]
UnexpectedCaseMessage {
/// The message kind the state machine was waiting for.
expected: crate::case::CaseMessageKind,
/// The kind the caller tried to supply.
got: crate::case::CaseMessageKind,
},
}
/// `Result<T, Error>` for convenience.
pub type Result<T> = core::result::Result<T, Error>;