Skip to main content

tightbeam/transport/handshake/
error.rs

1#[cfg(not(feature = "std"))]
2extern crate alloc;
3#[cfg(not(feature = "std"))]
4use alloc::string::ToString;
5
6#[cfg(feature = "derive")]
7use crate::Errorizable;
8
9pub type Result<T> = core::result::Result<T, HandshakeError>;
10
11/// Errors specific to handshake operations
12#[cfg_attr(feature = "derive", derive(Errorizable))]
13#[derive(Debug)]
14pub enum HandshakeError {
15	// ---------------- Protocol & structure specific ----------------
16	// Invariant violations (non-panicking)
17	#[cfg_attr(
18		feature = "derive",
19		error("Handshake invariant violation: transcript already locked")
20	)]
21	TranscriptAlreadyLocked,
22	#[cfg_attr(
23		feature = "derive",
24		error("Handshake invariant violation: transcript not locked")
25	)]
26	TranscriptNotLocked,
27	#[cfg_attr(
28		feature = "derive",
29		error("Handshake invariant violation: AEAD key already derived")
30	)]
31	AeadAlreadyDerived,
32	#[cfg_attr(
33		feature = "derive",
34		error("Handshake invariant violation: Finished already sent")
35	)]
36	FinishedAlreadySent,
37	#[cfg_attr(
38		feature = "derive",
39		error("Handshake invariant violation: Finished before transcript lock")
40	)]
41	FinishedBeforeTranscriptLock,
42	/// Invalid client key exchange message
43	#[cfg_attr(feature = "derive", error("Invalid client key exchange message"))]
44	InvalidClientKeyExchange,
45
46	/// Invalid server key exchange message
47	#[cfg_attr(feature = "derive", error("Invalid server key exchange message"))]
48	InvalidServerKeyExchange,
49
50	/// Invalid public key in handshake
51	#[cfg_attr(feature = "derive", error("Invalid public key in handshake: {0}"))]
52	#[cfg_attr(feature = "derive", from)]
53	InvalidPublicKey(crate::crypto::sign::ecdsa::k256::elliptic_curve::Error),
54
55	/// Invalid certificate
56	#[cfg_attr(feature = "derive", error("Invalid certificate: {0}"))]
57	#[cfg_attr(feature = "derive", from)]
58	CertificateValidationError(crate::crypto::x509::error::CertificateValidationError),
59
60	/// Signature verification failed
61	#[cfg_attr(feature = "derive", error("Handshake signature verification failed"))]
62	SignatureVerificationFailed,
63
64	/// Signature error (parsing or verification)
65	#[cfg_attr(feature = "derive", error("Signature error: {0}"))]
66	#[cfg_attr(feature = "derive", from)]
67	SignatureError(crate::crypto::sign::Error),
68
69	/// Key derivation failed
70	#[cfg_attr(feature = "derive", error("Handshake key derivation failed: {0}"))]
71	#[cfg_attr(feature = "derive", from)]
72	KeyDerivationFailed(crate::crypto::aead::Error),
73
74	/// Underlying DER encode/decode error
75	#[cfg_attr(feature = "derive", error("DER error: {0}"))]
76	#[cfg_attr(feature = "derive", from)]
77	DerError(crate::der::Error),
78
79	/// SPKI (SubjectPublicKeyInfo) error
80	#[cfg_attr(feature = "derive", error("SPKI error: {0}"))]
81	#[cfg_attr(feature = "derive", from)]
82	SpkiError(crate::spki::Error),
83
84	/// Key provider error
85	#[cfg_attr(feature = "derive", error("Key provider error: {0}"))]
86	#[cfg_attr(feature = "derive", from)]
87	KeyError(crate::crypto::key::KeyError),
88
89	/// CMS builder error
90	#[cfg_attr(feature = "derive", error("CMS builder error: {0}"))]
91	#[cfg_attr(feature = "derive", from)]
92	CmsBuilderError(crate::cms::builder::Error),
93
94	/// Invalid handshake state
95	#[cfg_attr(feature = "derive", error("Invalid handshake state"))]
96	InvalidState,
97
98	/// Missing server key
99	#[cfg_attr(feature = "derive", error("Missing server key"))]
100	MissingServerKey,
101
102	/// No trust store / certificate validator configured
103	#[cfg_attr(
104		feature = "derive",
105		error("Trust store required: refusing expiry-only peer certificate validation")
106	)]
107	MissingTrustStore,
108
109	/// Missing server certificate
110	#[cfg_attr(feature = "derive", error("Missing server certificate"))]
111	MissingServerCertificate,
112
113	/// Missing client certificate
114	#[cfg_attr(feature = "derive", error("Missing client certificate"))]
115	MissingClientCertificate,
116
117	/// Invalid transcript hash length or format
118	#[cfg_attr(feature = "derive", error("Invalid transcript hash"))]
119	InvalidTranscriptHash,
120
121	/// Digest output width does not match the required transcript hash width
122	#[cfg_attr(
123		feature = "derive",
124		error("Transcript digest length invalid: expected {expected} bytes, got {received}")
125	)]
126	TranscriptDigestLength { expected: usize, received: usize },
127
128	/// Server requires mutual authentication but client has no identity configured
129	#[cfg_attr(
130		feature = "derive",
131		error("Server requires mutual authentication but client has no identity")
132	)]
133	MutualAuthRequired,
134
135	/// Peer identity mismatch during re-handshake (immutable identity violation)
136	#[cfg_attr(
137		feature = "derive",
138		error("Peer identity changed during re-handshake - connection identity is immutable")
139	)]
140	PeerIdentityMismatch,
141
142	/// Provisioned certificate chain leaf does not match the pinned server certificate
143	#[cfg_attr(
144		feature = "derive",
145		error("Provisioned certificate chain leaf does not match pinned server certificate")
146	)]
147	PinnedCertificateMismatch,
148
149	/// Missing client random
150	#[cfg_attr(feature = "derive", error("Missing client random from ClientHello"))]
151	MissingClientRandom,
152
153	/// Missing base session key
154	#[cfg_attr(feature = "derive", error("Missing base session key"))]
155	MissingBaseSessionKey,
156
157	/// Missing client random
158	#[cfg_attr(feature = "derive", error("Missing client random"))]
159	MissingClientRandomState,
160
161	/// Missing server random
162	#[cfg_attr(feature = "derive", error("Missing server random"))]
163	MissingServerRandom,
164
165	/// CMS salt (transcript hash) below minimum entropy requirement
166	#[cfg_attr(
167		feature = "derive",
168		error("CMS salt too short: {actual} bytes (minimum {minimum} required)")
169	)]
170	InsufficientSaltEntropy { actual: usize, minimum: usize },
171
172	/// Peer sent abort alert during handshake
173	#[cfg_attr(feature = "derive", error("Handshake aborted by peer: {0:?}"))]
174	AbortReceived(crate::transport::handshake::HandshakeAlert),
175
176	/// Handshake timeout
177	#[cfg_attr(feature = "derive", error("Handshake timeout"))]
178	Timeout,
179
180	/// Invalid profile selection - server selected profile not in client's offer
181	#[cfg_attr(feature = "derive", error("Server selected profile not in client's offer"))]
182	InvalidProfileSelection,
183
184	/// Negotiation error
185	#[cfg_attr(feature = "derive", error("Profile negotiation failed: {0}"))]
186	#[cfg_attr(feature = "derive", from)]
187	NegotiationError(crate::transport::handshake::negotiation::NegotiationError),
188
189	/// No mutually supported profiles found during negotiation
190	#[cfg_attr(feature = "derive", error("No mutually supported cryptographic profiles found"))]
191	NoMutualProfiles,
192
193	/// Dealer's choice failed - no supported profiles configured
194	#[cfg_attr(
195		feature = "derive",
196		error("Dealer's choice failed: no supported profiles configured")
197	)]
198	NoSupportedProfiles,
199
200	/// Profile negotiation required but no profiles configured
201	#[cfg_attr(
202		feature = "derive",
203		error("Profile negotiation required but no profiles configured on server")
204	)]
205	NegotiationRequired,
206
207	/// Certificate policy rejection
208	#[cfg_attr(feature = "derive", error("Certificate rejected by policy: {0}"))]
209	#[cfg_attr(feature = "derive", from)]
210	CertificatePolicyError(crate::crypto::policy::CryptoPolicyError),
211
212	// ---------------- Attribute / ASN.1 profile errors ----------------
213	#[cfg_attr(feature = "derive", error("Attribute must contain exactly one value"))]
214	InvalidAttributeArity,
215	#[cfg_attr(feature = "derive", error("Duplicate attribute present"))]
216	DuplicateAttribute,
217	#[cfg_attr(feature = "derive", error("Required attribute missing"))]
218	MissingAttribute,
219	#[cfg_attr(
220		feature = "derive",
221		error("Too many supported curves: {count} exceeds cap of {max}")
222	)]
223	TooManySupportedCurves { count: usize, max: usize },
224	#[cfg_attr(feature = "derive", error("Nonce value not valid OCTET STRING"))]
225	InvalidNonceEncoding,
226	#[cfg_attr(feature = "derive", error("Nonce length mismatch: {0}"))]
227	NonceLengthError(crate::error::ReceivedExpectedError<usize, usize>),
228	#[cfg_attr(feature = "derive", error("OCTET STRING length mismatch: {0}"))]
229	OctetStringLengthError(crate::error::ReceivedExpectedError<usize, usize>),
230	#[cfg_attr(feature = "derive", error("Version/alert value not valid INTEGER"))]
231	InvalidIntegerEncoding,
232	#[cfg_attr(feature = "derive", error("INTEGER out of range"))]
233	IntegerOutOfRange,
234	#[cfg_attr(feature = "derive", error("Unknown alert code: {0:?}"))]
235	UnknownAlertCode(u8),
236
237	// ---------------- Certificate time validation ----------------
238	#[cfg_attr(feature = "derive", error("Certificate not yet valid"))]
239	CertificateNotYetValid,
240	#[cfg_attr(feature = "derive", error("Certificate expired"))]
241	CertificateExpired,
242	#[cfg_attr(feature = "derive", error("Invalid timestamp"))]
243	InvalidTimestamp,
244
245	// ---------------- ECIES / encryption path ----------------
246	#[cfg(feature = "ecies")]
247	#[cfg_attr(feature = "derive", error("ECIES operation failed: {0}"))]
248	#[cfg_attr(feature = "derive", from)]
249	EciesError(crate::crypto::ecies::EciesError),
250	#[cfg_attr(feature = "derive", error("Missing encrypted content in ECIES message"))]
251	MissingEncryptedContent,
252	#[cfg_attr(feature = "derive", error("Invalid decrypted payload size"))]
253	InvalidDecryptedPayloadSize,
254	#[cfg_attr(feature = "derive", error("client_random mismatch - possible replay attack"))]
255	ClientRandomMismatchReplay,
256
257	// ---------------- Key agreement / CMS KARI ----------------
258	#[cfg_attr(feature = "derive", error("ECDH operation failed"))]
259	EcdhFailed,
260	#[cfg_attr(feature = "derive", error("KDF operation failed"))]
261	KdfError,
262	#[cfg_attr(
263		feature = "derive",
264		error("Invalid key size: expected {expected}, got {received}")
265	)]
266	InvalidKeySize { expected: usize, received: usize },
267	#[cfg_attr(
268		feature = "derive",
269		error("Ciphertext too short: {received} bytes (minimum {minimum} required)")
270	)]
271	CiphertextTooShort { minimum: usize, received: usize },
272	#[cfg_attr(feature = "derive", error("ASN.1 encoding error: {0}"))]
273	Asn1Error(der::Error),
274	#[cfg_attr(feature = "derive", error("Invalid recipient index"))]
275	InvalidRecipientIndex,
276	#[cfg_attr(feature = "derive", error("Missing UKM in KeyAgreeRecipientInfo"))]
277	MissingUkm,
278	#[cfg_attr(feature = "derive", error("Failed to parse originator public key"))]
279	InvalidOriginatorPublicKey,
280	#[cfg_attr(feature = "derive", error("Unsupported originator identifier type"))]
281	UnsupportedOriginatorIdentifier,
282	#[cfg_attr(feature = "derive", error("KARI builder already consumed"))]
283	KariBuilderConsumed,
284	#[cfg_attr(feature = "derive", error("Content encryption algorithm not set"))]
285	MissingContentEncryptionAlgorithm,
286	#[cfg_attr(
287		feature = "derive",
288		error("Key wrap algorithm not configured in security profile")
289	)]
290	MissingKeyWrapAlgorithm,
291	#[cfg_attr(
292		feature = "derive",
293		error("Negotiated key wrap algorithm unsupported (expected AES-128/192/256 key wrap)")
294	)]
295	UnsupportedKeyWrapAlgorithm,
296	#[cfg_attr(
297		feature = "derive",
298		error("Negotiated AEAD algorithm unsupported (expected AES-128/256 GCM)")
299	)]
300	UnsupportedAeadAlgorithm,
301	#[cfg_attr(
302		feature = "derive",
303		error("Peer aead_key_size {declared} does not match negotiated AEAD key size {expected}")
304	)]
305	AeadKeySizeMismatch { declared: usize, expected: usize },
306	#[cfg(all(feature = "builder", feature = "aead"))]
307	#[cfg_attr(feature = "derive", error("AES key wrap operation failed: {0}"))]
308	#[cfg_attr(feature = "derive", from)]
309	AesKeyWrap(crate::crypto::aead::aes_kw::Error),
310
311	#[cfg(feature = "kem")]
312	#[cfg_attr(
313		feature = "derive",
314		error("Hybrid key agreement integrity check failed: combined ECDH+KEM key validation error")
315	)]
316	HybridKariIntegrityCheckFailed,
317
318	// ---------------- Random generation ----------------
319	#[cfg_attr(feature = "derive", error("Random generation failed"))]
320	RandomGenerationFailed,
321
322	/// Secret material was unavailable
323	#[cfg_attr(feature = "derive", error("Secret unavailable: {0}"))]
324	#[cfg_attr(feature = "derive", from)]
325	SecretUnavailable(crate::crypto::secret::SecretError),
326
327	// ---------------- Generic octet string length (server_random/client_random etc.) ----------------
328	#[cfg_attr(feature = "derive", error("Invalid OCTET STRING length: {0}"))]
329	InvalidOctetStringLength(&'static str),
330}
331
332// Mirrors the `#[error(...)]` strings above; compiled only without `derive`.
333crate::impl_error_display!(HandshakeError {
334	TranscriptAlreadyLocked => "Handshake invariant violation: transcript already locked",
335	TranscriptNotLocked => "Handshake invariant violation: transcript not locked",
336	AeadAlreadyDerived => "Handshake invariant violation: AEAD key already derived",
337	FinishedAlreadySent => "Handshake invariant violation: Finished already sent",
338	FinishedBeforeTranscriptLock => "Handshake invariant violation: Finished before transcript lock",
339	InvalidClientKeyExchange => "Invalid client key exchange message",
340	InvalidServerKeyExchange => "Invalid server key exchange message",
341	InvalidPublicKey(e) => "Invalid public key in handshake: {e}",
342	CertificateValidationError(e) => "Invalid certificate: {e}",
343	SignatureVerificationFailed => "Handshake signature verification failed",
344	SignatureError(e) => "Signature error: {e}",
345	KeyDerivationFailed(e) => "Handshake key derivation failed: {e}",
346	DerError(e) => "DER error: {e}",
347	SpkiError(e) => "SPKI error: {e}",
348	KeyError(e) => "Key provider error: {e}",
349	CmsBuilderError(e) => "CMS builder error: {e}",
350	InvalidState => "Invalid handshake state",
351	MissingServerKey => "Missing server key",
352	MissingTrustStore => "Trust store required: refusing expiry-only peer certificate validation",
353	MissingServerCertificate => "Missing server certificate",
354	MissingClientCertificate => "Missing client certificate",
355	InvalidTranscriptHash => "Invalid transcript hash",
356	TranscriptDigestLength { expected, received } => "Transcript digest length invalid: expected {expected} bytes, got {received}",
357	MutualAuthRequired => "Server requires mutual authentication but client has no identity",
358	PeerIdentityMismatch => "Peer identity changed during re-handshake - connection identity is immutable",
359	PinnedCertificateMismatch => "Provisioned certificate chain leaf does not match pinned server certificate",
360	MissingClientRandom => "Missing client random from ClientHello",
361	MissingBaseSessionKey => "Missing base session key",
362	MissingClientRandomState => "Missing client random",
363	MissingServerRandom => "Missing server random",
364	InsufficientSaltEntropy { actual, minimum } => "CMS salt too short: {actual} bytes (minimum {minimum} required)",
365	AbortReceived(alert) => "Handshake aborted by peer: {alert:?}",
366	Timeout => "Handshake timeout",
367	InvalidProfileSelection => "Server selected profile not in client's offer",
368	NegotiationError(e) => "Profile negotiation failed: {e}",
369	NoMutualProfiles => "No mutually supported cryptographic profiles found",
370	NoSupportedProfiles => "Dealer's choice failed: no supported profiles configured",
371	NegotiationRequired => "Profile negotiation required but no profiles configured on server",
372	CertificatePolicyError(e) => "Certificate rejected by policy: {e}",
373	InvalidAttributeArity => "Attribute must contain exactly one value",
374	DuplicateAttribute => "Duplicate attribute present",
375	MissingAttribute => "Required attribute missing",
376	TooManySupportedCurves { count, max } => "Too many supported curves: {count} exceeds cap of {max}",
377	InvalidNonceEncoding => "Nonce value not valid OCTET STRING",
378	NonceLengthError(e) => "Nonce length mismatch: {e}",
379	OctetStringLengthError(e) => "OCTET STRING length mismatch: {e}",
380	InvalidIntegerEncoding => "Version/alert value not valid INTEGER",
381	IntegerOutOfRange => "INTEGER out of range",
382	UnknownAlertCode(code) => "Unknown alert code: {code:?}",
383	CertificateNotYetValid => "Certificate not yet valid",
384	CertificateExpired => "Certificate expired",
385	InvalidTimestamp => "Invalid timestamp",
386	MissingEncryptedContent => "Missing encrypted content in ECIES message",
387	InvalidDecryptedPayloadSize => "Invalid decrypted payload size",
388	ClientRandomMismatchReplay => "client_random mismatch - possible replay attack",
389	EcdhFailed => "ECDH operation failed",
390	KdfError => "KDF operation failed",
391	InvalidKeySize { expected, received } => "Invalid key size: expected {expected}, got {received}",
392	CiphertextTooShort { minimum, received } => "Ciphertext too short: {received} bytes (minimum {minimum} required)",
393	Asn1Error(e) => "ASN.1 encoding error: {e}",
394	InvalidRecipientIndex => "Invalid recipient index",
395	MissingUkm => "Missing UKM in KeyAgreeRecipientInfo",
396	InvalidOriginatorPublicKey => "Failed to parse originator public key",
397	UnsupportedOriginatorIdentifier => "Unsupported originator identifier type",
398	KariBuilderConsumed => "KARI builder already consumed",
399	MissingContentEncryptionAlgorithm => "Content encryption algorithm not set",
400	MissingKeyWrapAlgorithm => "Key wrap algorithm not configured in security profile",
401	UnsupportedKeyWrapAlgorithm => "Negotiated key wrap algorithm unsupported (expected AES-128/192/256 key wrap)",
402	UnsupportedAeadAlgorithm => "Negotiated AEAD algorithm unsupported (expected AES-128/256 GCM)",
403	AeadKeySizeMismatch { declared, expected } => "Peer aead_key_size {declared} does not match negotiated AEAD key size {expected}",
404	RandomGenerationFailed => "Random generation failed",
405	SecretUnavailable(e) => "Secret unavailable: {e}",
406	InvalidOctetStringLength(m) => "Invalid OCTET STRING length: {m}",
407
408	#[cfg(feature = "ecies")]
409	EciesError(e) => "ECIES operation failed: {e}",
410	#[cfg(all(feature = "builder", feature = "aead"))]
411	AesKeyWrap(e) => "AES key wrap operation failed: {e}",
412	#[cfg(feature = "kem")]
413	HybridKariIntegrityCheckFailed => "Hybrid key agreement integrity check failed: combined ECDH+KEM key validation error",
414});
415
416impl From<crate::crypto::kdf::KdfError> for HandshakeError {
417	fn from(_: crate::crypto::kdf::KdfError) -> Self {
418		HandshakeError::KeyDerivationFailed(crate::crypto::aead::Error)
419	}
420}
421
422/// Narrows [`TightBeamError`](crate::error::TightBeamError) into [`HandshakeError`];
423/// variants without a handshake counterpart collapse to [`HandshakeError::InvalidState`].
424impl From<crate::error::TightBeamError> for HandshakeError {
425	fn from(err: crate::error::TightBeamError) -> Self {
426		use crate::error::TightBeamError;
427		match err {
428			TightBeamError::HandshakeError(h) => h,
429			TightBeamError::SerializationError(e) => HandshakeError::DerError(e),
430			#[cfg(feature = "x509")]
431			TightBeamError::SpkiError(e) => HandshakeError::SpkiError(e),
432			#[cfg(feature = "x509")]
433			TightBeamError::CertificateValidationError(e) => HandshakeError::CertificateValidationError(e),
434			#[cfg(feature = "crypto")]
435			TightBeamError::CryptoPolicyError(e) => HandshakeError::CertificatePolicyError(e),
436			#[cfg(feature = "crypto")]
437			TightBeamError::KeyError(e) => HandshakeError::KeyError(e),
438			#[cfg(feature = "signature")]
439			TightBeamError::SignatureError(e) => HandshakeError::SignatureError(e),
440			#[cfg(feature = "ecies")]
441			TightBeamError::EciesError(e) => HandshakeError::EciesError(e),
442			#[cfg(feature = "crypto")]
443			TightBeamError::SecretUnavailable(e) => HandshakeError::SecretUnavailable(e),
444			#[cfg(feature = "random")]
445			TightBeamError::OsRngError(_) => HandshakeError::RandomGenerationFailed,
446			_ => HandshakeError::InvalidState,
447		}
448	}
449}
450
451#[cfg(all(feature = "crypto", not(feature = "derive")))]
452impl From<crate::crypto::secret::SecretError> for HandshakeError {
453	fn from(err: crate::crypto::secret::SecretError) -> Self {
454		HandshakeError::SecretUnavailable(err)
455	}
456}
457
458/// Narrows [`HandshakeError`] into the foreign [`crate::cms::builder::Error`];
459/// variants without a counterpart collapse into
460/// [`Builder`](crate::cms::builder::Error::Builder) via their `Display`.
461#[cfg(all(feature = "builder", feature = "aead"))]
462impl From<HandshakeError> for crate::cms::builder::Error {
463	fn from(err: HandshakeError) -> Self {
464		match err {
465			HandshakeError::CmsBuilderError(e) => e,
466			HandshakeError::DerError(e) => crate::cms::builder::Error::Asn1(e),
467			HandshakeError::Asn1Error(e) => crate::cms::builder::Error::Asn1(e),
468			HandshakeError::SpkiError(e) => crate::cms::builder::Error::PublicKey(e),
469			other => crate::cms::builder::Error::Builder(other.to_string()),
470		}
471	}
472}
473
474impl From<crypto_common::InvalidLength> for HandshakeError {
475	fn from(_: crypto_common::InvalidLength) -> Self {
476		HandshakeError::KeyDerivationFailed(crate::crypto::aead::Error)
477	}
478}
479
480#[cfg(not(feature = "derive"))]
481impl From<crate::crypto::key::KeyError> for HandshakeError {
482	fn from(e: crate::crypto::key::KeyError) -> Self {
483		HandshakeError::KeyError(e)
484	}
485}