tightbeam-rs 0.9.0

A secure, high-performance messaging protocol library
Documentation
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
//! CMS-based client handshake orchestrator.
//!
//! Implements the client side of the TightBeam handshake protocol using
//! CMS builders and processors.

#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, vec::Vec};

use core::future::Future;
use core::pin::Pin;

use crate::cms::cert::{CertificateChoices, IssuerAndSerialNumber};
use crate::cms::content_info::CmsVersion;
use crate::cms::enveloped_data::{KeyAgreeRecipientIdentifier, UserKeyingMaterial};
use crate::cms::signed_data::{CertificateSet, EncapsulatedContentInfo, SignedData, SignerIdentifier, SignerInfo};
use crate::crypto::aead::KeyInit;
use crate::crypto::hash::Digest;
use crate::crypto::key::SigningKeyProvider;
use crate::crypto::profiles::{CryptoProvider, SecurityProfile, SecurityProfileDesc};
use crate::crypto::secret::Secret;
use crate::crypto::sign::elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
use crate::crypto::sign::elliptic_curve::{AffinePoint, PublicKey, SecretKey};
use crate::crypto::sign::{EcdsaSignatureVerifier, SignatureAlgorithmIdentifier};
use crate::crypto::x509::store::CertificateTrust;
use crate::crypto::x509::utils::validate_certificate_expiry;
use crate::crypto::x509::Certificate;
use crate::der::asn1::OctetString;
use crate::der::oid::AssociatedOid;
use crate::der::{Decode, Encode};
use crate::random::{generate_nonce, CryptoRngCore, OsRng, RngWrapper};
use crate::spki::{AlgorithmIdentifierOwned, EncodePublicKey, SubjectPublicKeyInfoOwned};
use crate::transport::handshake::builders::{TightBeamEnvelopedDataBuilder, TightBeamKariBuilder};
use crate::transport::handshake::error::HandshakeError;
use crate::transport::handshake::negotiation::{SecurityAccept, SecurityOffer};
use crate::transport::handshake::processors::TightBeamSignedDataProcessor;
use crate::transport::handshake::state::HandshakeInvariant;
use crate::transport::handshake::state::{ClientHandshakeState, ClientStateMachine};
use crate::transport::handshake::utils::{compute_transcript_digest, extract_verifying_key_from_cert, validate_state};
use crate::transport::handshake::{Arc, ClientHandshakeProtocol, HandshakeAlertHandler, HandshakeFinalization};

/// Client-side CMS handshake orchestrator.
///
/// Generic over `P: CryptoProvider` which defines the complete cryptographic
/// suite (curve, signature algorithm, digest, AEAD, KDF). Supports
/// cryptographic profile negotiation via optional `security_offer` field.
///
/// Manages the complete client handshake flow:
/// 1. Sends KeyExchange (EnvelopedData with KARI)
/// 2. Receives and verifies server Finished (SignedData)
/// 3. Sends client Finished (SignedData)
pub struct CmsHandshakeClient<P>
where
	P: CryptoProvider,
{
	state: ClientStateMachine,
	client_key_provider: Arc<dyn SigningKeyProvider>,
	client_certificate: Option<Arc<Certificate>>,
	server_cert: Option<Arc<Certificate>>,
	server_chain: Option<Arc<[Certificate]>>,
	transcript_hash: Option<[u8; 32]>,
	transcript_buffer: Vec<u8>,
	session_key: Option<Secret<Vec<u8>>>,
	security_offer: Option<SecurityOffer>,
	selected_profile: Option<SecurityProfileDesc>,
	provider: P,
	trust_store: Option<Arc<dyn CertificateTrust>>,
	invariants: HandshakeInvariant,
}

impl<P> CmsHandshakeClient<P>
where
	P: CryptoProvider,
	P::Curve: elliptic_curve::Curve + elliptic_curve::CurveArithmetic,
	<P::Curve as elliptic_curve::Curve>::FieldBytesSize: ModulusSize,
	AffinePoint<P::Curve>: FromEncodedPoint<P::Curve> + ToEncodedPoint<P::Curve>,
	PublicKey<P::Curve>: EncodePublicKey,
	P::VerifyingKey: From<PublicKey<P::Curve>> + EncodePublicKey + signature::Verifier<P::Signature> + 'static,
	P::Signature: 'static,
	P::Digest: Send + 'static,
	P::AeadCipher: KeyInit,
{
	/// Create a new CMS handshake client.
	///
	/// # Parameters
	/// - `provider`: The cryptographic provider defining the security profile
	/// - `client_key_provider`: The client's key provider for authentication
	/// - `server_cert`: The server's certificate (for key agreement)
	///
	/// # Transcript Hash
	/// The transcript hash is computed internally from handshake messages.
	/// If you need to provide an external transcript hash (for testing),
	/// use `with_transcript_hash()` after construction.
	pub fn new(provider: P, client_key_provider: Arc<dyn SigningKeyProvider>, server_cert: Arc<Certificate>) -> Self {
		Self::with_identity(provider, client_key_provider, Some(server_cert), None)
	}

	/// Create a new CMS handshake client from a server certificate chain.
	///
	/// The chain leaf is the encryption target, borrowed in place: no
	/// separate leaf certificate is cloned out of the chain. Path validation
	/// runs over the whole chain during key exchange.
	pub fn from_chain(
		provider: P,
		client_key_provider: Arc<dyn SigningKeyProvider>,
		chain: Arc<[Certificate]>,
	) -> Self {
		Self::with_identity(provider, client_key_provider, None, Some(chain))
	}

	fn with_identity(
		provider: P,
		client_key_provider: Arc<dyn SigningKeyProvider>,
		server_cert: Option<Arc<Certificate>>,
		server_chain: Option<Arc<[Certificate]>>,
	) -> Self {
		Self {
			state: ClientStateMachine::default(),
			client_key_provider,
			client_certificate: None,
			server_cert,
			server_chain,
			transcript_hash: None,
			transcript_buffer: Vec::new(),
			session_key: None,
			security_offer: None,
			selected_profile: None,
			provider,
			trust_store: None,
			invariants: HandshakeInvariant::default(),
		}
	}

	/// Set an external transcript hash (for testing or custom protocols).
	///
	/// When set, the internal transcript buffer is not used.
	#[must_use]
	pub fn with_transcript_hash(mut self, hash: [u8; 32]) -> Self {
		self.transcript_hash = Some(hash);
		self
	}

	/// Set trust store for server certificate validation.
	#[must_use]
	pub fn with_trust_store(mut self, store: Arc<dyn CertificateTrust>) -> Self {
		self.trust_store = Some(store);
		self
	}

	/// Provision the server certificate chain, ordered root to leaf.
	///
	/// When set, server authentication validates the full chain against the
	/// trust store (RFC 5280 §6.1) instead of evaluating the bare certificate.
	#[must_use]
	pub fn with_server_certificate_chain(mut self, chain: Arc<[Certificate]>) -> Self {
		self.server_chain = Some(chain);
		self
	}

	/// Set client certificate for mutual authentication.
	///
	/// The certificate is embedded in the client Finished message so the
	/// server can authenticate the client from the wire.
	pub fn with_client_certificate(mut self, certificate: impl Into<Arc<Certificate>>) -> Self {
		self.client_certificate = Some(certificate.into());
		self
	}

	/// Configures the security offer for negotiation.
	///
	/// When configured, the client will send this offer to the server,
	/// and the server will select a mutually supported profile.
	#[must_use]
	pub fn with_security_offer(mut self, offer: SecurityOffer) -> Self {
		self.security_offer = Some(offer);
		self
	}

	/// Get the selected security profile after negotiation.
	///
	/// Returns `None` if no negotiation occurred or not yet determined.
	pub fn selected_profile(&self) -> Option<SecurityProfileDesc> {
		self.selected_profile
	}

	/// Validate that the current state matches the expected state.
	fn validate_expected_state(&self, expected: ClientHandshakeState) -> Result<(), HandshakeError> {
		validate_state(self.state.state(), expected)
	}

	/// The server certificate the session key is encrypted to: the pinned
	/// certificate when set, otherwise the provisioned chain's leaf.
	fn server_leaf(&self) -> Result<&Certificate, HandshakeError> {
		if let Some(cert) = &self.server_cert {
			return Ok(cert);
		}

		self.server_chain
			.as_ref()
			.and_then(|chain| chain.last())
			.ok_or(HandshakeError::MissingServerCertificate)
	}

	/// Validate state and server certificate for key exchange.
	///
	/// Fail-closed (CWE-295): a configured trust store is mandatory. Expiry
	/// alone authenticates nobody, so a missing store aborts the handshake
	/// instead of silently degrading.
	///
	/// With a provisioned chain, the full path is validated (RFC 5280 §6.1)
	/// and the leaf must be the configured server certificate; otherwise the
	/// bare certificate is evaluated against the store directly.
	fn validate_state_and_certificate(&self) -> Result<(), HandshakeError> {
		self.validate_expected_state(ClientHandshakeState::Init)?;

		let store = self.trust_store.as_ref().ok_or(HandshakeError::MissingTrustStore)?;
		validate_certificate_expiry(self.server_leaf()?)?;

		match (&self.server_chain, &self.server_cert) {
			(Some(chain), pinned) => {
				store.verify_chain(chain)?;
				let leaf = chain.last().ok_or(HandshakeError::MissingServerCertificate)?;
				if pinned.as_ref().is_some_and(|cert| *leaf != **cert) {
					return Err(HandshakeError::PinnedCertificateMismatch);
				}
			}
			(None, Some(cert)) => store.evaluate(cert)?,
			(None, None) => return Err(HandshakeError::MissingServerCertificate),
		}

		Ok(())
	}

	/// Extract the server's public key from certificate.
	fn extract_server_public_key(&self) -> Result<PublicKey<P::Curve>, HandshakeError> {
		Ok(PublicKey::<P::Curve>::from_sec1_bytes(
			self.server_leaf()?
				.tbs_certificate
				.subject_public_key_info
				.subject_public_key
				.raw_bytes(),
		)?)
	}

	/// Create ephemeral keypair for the sender.
	fn create_ephemeral_keypair(
		&self,
		rng: &mut dyn CryptoRngCore,
	) -> Result<(SecretKey<P::Curve>, SubjectPublicKeyInfoOwned), HandshakeError> {
		let sender_ephemeral = SecretKey::<P::Curve>::random(&mut RngWrapper(rng));
		let sender_public = sender_ephemeral.public_key();
		let sender_pub_spki = sender_public.to_public_key_der()?;
		let sender_pub_spki = SubjectPublicKeyInfoOwned::from_der(sender_pub_spki.as_bytes())?;

		Ok((sender_ephemeral, sender_pub_spki))
	}

	/// Build the recipient identifier from server certificate.
	fn build_recipient_identifier(&self) -> Result<KeyAgreeRecipientIdentifier, HandshakeError> {
		let leaf = self.server_leaf()?;

		// Cloning here is cheaper than Arc
		Ok(KeyAgreeRecipientIdentifier::IssuerAndSerialNumber(IssuerAndSerialNumber {
			issuer: leaf.tbs_certificate.issuer.clone(),
			serial_number: leaf.tbs_certificate.serial_number.clone(),
		}))
	}

	/// Extract the server's verifying key from a certificate or similar.
	fn extract_server_verifying_key(&self, server_cert: &Certificate) -> Result<P::VerifyingKey, HandshakeError> {
		let server_public_key = extract_verifying_key_from_cert::<P::Curve>(server_cert)?;
		Ok(P::VerifyingKey::from(server_public_key))
	}

	/// Compute the signer identifier from the server's verifying key.
	fn compute_signer_identifier(&self, verifying_key: &P::VerifyingKey) -> Result<SignerIdentifier, HandshakeError> {
		Ok(crate::crypto::x509::utils::compute_signer_identifier::<P::Digest, _>(
			verifying_key,
		)?)
	}

	/// Compute transcript hash from the accumulated buffer.
	///
	/// Uses the provider's digest algorithm for consistency with signatures.
	fn compute_transcript_hash(&self) -> Result<[u8; 32], HandshakeError> {
		compute_transcript_digest::<P::Digest>(&self.transcript_buffer)
	}

	/// Verify the signature and content of the SignedData.
	fn verify_signature(
		&self,
		signed_data_der: &[u8],
		server_verifying_key: P::VerifyingKey,
		expected_sid: SignerIdentifier,
	) -> Result<Vec<u8>, HandshakeError> {
		let verifier = EcdsaSignatureVerifier::<P::VerifyingKey, P::Signature, P::Digest>::from_verifying_key_with_sid(
			server_verifying_key,
			expected_sid,
		);
		let processor = TightBeamSignedDataProcessor::new(verifier);

		// Verify content matches our transcript hash
		let digest_oid = P::Digest::OID;
		let verified_content = processor.process_der(signed_data_der, &digest_oid)?;

		let expected_hash = self.transcript_hash.ok_or(HandshakeError::InvalidState)?;
		if verified_content.len() != 32 || verified_content.as_slice() != expected_hash {
			Err(HandshakeError::SignatureVerificationFailed)
		} else {
			Ok(verified_content)
		}
	}

	/// Build KeyExchange message (EnvelopedData with KARI containing session key).
	///
	/// # Parameters
	/// - `session_key`: The session key to wrap and send
	/// - `rng`: Optional CSPRNG for the ephemeral key, UKM, CEK, and content
	///   nonce. `None` defaults to `OsRng`; supply one on `no_std` targets
	///   without an OS-backed `getrandom`.
	///
	/// # Returns
	/// DER-encoded EnvelopedData
	pub fn build_key_exchange(
		&mut self,
		session_key: Vec<u8>,
		rng: Option<&mut dyn CryptoRngCore>,
	) -> Result<Vec<u8>, HandshakeError> {
		// 1. Validate state and certificate
		self.validate_key_exchange_prerequisites()?;

		// 2. Resolve the RNG once (defaulting to OsRng) then reborrow it for
		//    each randomness draw in the key-exchange path.
		let mut os = OsRng;
		let rng: &mut dyn CryptoRngCore = rng.unwrap_or(&mut os);

		// 3. Extract cryptographic material
		let (server_public_key, sender_ephemeral, sender_pub_spki) = self.extract_key_exchange_crypto_material(rng)?;

		// 4. Create UKM and recipient identifier
		let ukm = self.create_user_keying_material(rng)?;
		let rid = self.build_recipient_identifier()?;

		// 5. Build KARI structure
		let kari_builder = self.build_kari_structure(sender_ephemeral, sender_pub_spki, server_public_key, rid, ukm)?;

		// 6. Create EnvelopedData with optional security offer
		let enveloped_data_der = self.build_enveloped_data(kari_builder, &session_key, rng)?;

		// 7. Update transcript and state
		self.finalize_key_exchange(&enveloped_data_der, session_key)?;

		Ok(enveloped_data_der)
	}

	/// Process server Finished message (SignedData over transcript hash).
	///
	/// # Parameters
	/// - `signed_data_der`: DER-encoded SignedData from server
	///
	/// # Returns
	/// Verified transcript hash
	pub fn process_server_finished(&mut self, signed_data_der: &[u8]) -> Result<Vec<u8>, HandshakeError> {
		// 1. Validation
		self.validate_expected_state(ClientHandshakeState::KeyExchangeSent)?;

		// 2. Extract the server's SecurityAccept before hashing: the accept
		//    bytes are part of the signed transcript, so the hash must cover
		//    them to match the server's (CWE-345). A tampered attribute
		//    diverges the hashes and fails signature verification below.
		let accept = extract_security_accept_attr(signed_data_der)?;
		if self.transcript_hash.is_none() {
			if let Some(ref accept) = accept {
				let accept_bytes = crate::transport::handshake::attributes::security_accept_transcript_bytes(accept)?;
				self.transcript_buffer.extend_from_slice(&accept_bytes);
			}
			self.transcript_hash = Some(self.compute_transcript_hash()?);
		}

		// 3. Extract cryptographic material
		let server_verifying_key = self.extract_server_verifying_key(self.server_leaf()?)?;
		let expected_signer_identifier = self.compute_signer_identifier(&server_verifying_key)?;

		// 4. Verify signature and content
		let verified_content =
			self.verify_signature(signed_data_der, server_verifying_key, expected_signer_identifier)?;

		// 5. Validate the selection against our own offer and store it
		self.apply_security_accept(accept)?;

		// 6. Add server finished to transcript AFTER verification
		self.transcript_buffer.extend_from_slice(signed_data_der);

		// 7. Transition state & lock transcript (transcript hash verified)
		self.state.transition(ClientHandshakeState::ServerFinishedReceived)?;
		self.invariants.lock_transcript()?;

		Ok(verified_content)
	}

	/// Validate the server's `SecurityAccept` selection and store the profile.
	///
	/// # Validation
	/// - Offer sent: accepted profile must be a member of the offer
	/// - No offer (dealer's choice): any accepted profile is stored
	/// - No attribute present: selection stays `None` (trait-level `complete()`
	///   then fails closed rather than proceeding with an unknown profile)
	fn apply_security_accept(&mut self, accept: Option<SecurityAccept>) -> Result<(), HandshakeError> {
		match (accept, &self.security_offer) {
			(Some(accept), Some(offer)) => {
				if !offer.profiles.contains(&accept.profile) {
					return Err(HandshakeError::InvalidProfileSelection);
				}
				self.selected_profile = Some(accept.profile);
			}
			(Some(accept), None) => {
				// Dealer's choice: accept the server's selection
				self.selected_profile = Some(accept.profile);
			}
			(None, Some(_)) => {
				// We offered profiles but the server did not answer
				return Err(HandshakeError::InvalidProfileSelection);
			}
			(None, None) => {}
		}

		Ok(())
	}

	/// Build client Finished message (SignedData over transcript hash).
	///
	/// # Returns
	/// DER-encoded SignedData
	pub async fn build_client_finished(&mut self) -> Result<Vec<u8>, HandshakeError> {
		// 1. Validate state
		self.validate_client_finished_prerequisites()?;

		// 2. Get transcript hash and prepare digest
		let (transcript_hash, digest) = self.prepare_finished_digest()?;

		// 3. Sign the digest
		let signature_bytes = self.sign_finished_digest(&digest).await?;

		// 4. Build cryptographic components
		let (signer_id, digest_alg, signature_alg) = self.build_finished_crypto_components().await?;

		// 5. Build SignedData structure
		let signed_data_der =
			self.build_signed_data(transcript_hash, &signature_bytes, signer_id, digest_alg, signature_alg)?;

		// 6. Transition state
		self.finalize_client_finished()?;

		Ok(signed_data_der)
	}

	/// Complete the handshake.
	pub fn complete(&mut self) -> Result<(), HandshakeError> {
		// 1. Validation
		self.validate_expected_state(ClientHandshakeState::ClientFinishedSent)?;

		// 2. Transition to complete
		self.state.transition(ClientHandshakeState::Completed)?;

		Ok(())
	}

	/// Get the current handshake state.
	pub fn state(&self) -> ClientHandshakeState {
		self.state.state()
	}

	/// Check if handshake is complete.
	pub fn is_complete(&self) -> bool {
		self.state.state().is_completed()
	}

	/// Get the session key (if available).
	///
	/// Returns a reference to the Secret-wrapped session key bytes.
	pub fn session_key(&self) -> Option<&Secret<Vec<u8>>> {
		self.session_key.as_ref()
	}

	/// Validate state and certificate for key exchange.
	fn validate_key_exchange_prerequisites(&self) -> Result<(), HandshakeError> {
		// Accept both Init (fresh) or HelloSent (if future hello phase added)
		if self.state.state() == ClientHandshakeState::Init {
			self.validate_state_and_certificate()?;
		} else if self.state.state() != ClientHandshakeState::HelloSent {
			return Err(HandshakeError::InvalidState);
		}

		Ok(())
	}

	/// Extract cryptographic material needed for key exchange.
	#[allow(clippy::type_complexity)]
	fn extract_key_exchange_crypto_material(
		&self,
		rng: &mut dyn CryptoRngCore,
	) -> Result<(PublicKey<P::Curve>, SecretKey<P::Curve>, SubjectPublicKeyInfoOwned), HandshakeError> {
		let server_public_key = self.extract_server_public_key()?;
		let (sender_ephemeral, sender_pub_spki) = self.create_ephemeral_keypair(rng)?;
		Ok((server_public_key, sender_ephemeral, sender_pub_spki))
	}

	/// Create user keying material for the key agreement.
	fn create_user_keying_material(&self, rng: &mut dyn CryptoRngCore) -> Result<UserKeyingMaterial, HandshakeError> {
		let ukm_bytes = generate_nonce::<64>(Some(rng))?;
		UserKeyingMaterial::new(ukm_bytes.to_vec()).map_err(Into::into)
	}

	/// Build KARI structure with all required components.
	fn build_kari_structure(
		&self,
		sender_ephemeral: SecretKey<P::Curve>,
		sender_pub_spki: SubjectPublicKeyInfoOwned,
		server_public_key: PublicKey<P::Curve>,
		rid: KeyAgreeRecipientIdentifier,
		ukm: UserKeyingMaterial,
	) -> Result<TightBeamKariBuilder<P>, HandshakeError> {
		let key_wrap_oid =
			<P::Profile as SecurityProfile>::KEY_WRAP_OID.ok_or(HandshakeError::MissingKeyWrapAlgorithm)?;
		let key_enc_alg = AlgorithmIdentifierOwned { oid: key_wrap_oid, parameters: None };

		let kari_builder = TightBeamKariBuilder::new(self.provider)
			.with_sender_priv(sender_ephemeral)
			.with_sender_pub_spki(sender_pub_spki)
			.with_recipient_pub(server_public_key)
			.with_recipient_rid(rid)
			.with_ukm(ukm)
			.with_key_enc_alg(key_enc_alg);

		Ok(kari_builder)
	}

	/// Build EnvelopedData with optional security offer.
	fn build_enveloped_data(
		&self,
		kari_builder: TightBeamKariBuilder<P>,
		session_key: &[u8],
		rng: &mut dyn CryptoRngCore,
	) -> Result<Vec<u8>, HandshakeError> {
		let mut enveloped_builder = TightBeamEnvelopedDataBuilder::new(kari_builder);

		// Add SecurityOffer as unprotected attribute if configured
		if let Some(ref offer) = self.security_offer {
			let offer_attr = crate::transport::handshake::attributes::encode_security_offer(offer)?;
			enveloped_builder = enveloped_builder.with_unprotected_attr(offer_attr);
		}

		let enveloped_data = enveloped_builder.build(session_key, None, Some(rng))?;
		enveloped_data.to_der().map_err(Into::into)
	}

	/// Finalize key exchange by updating transcript and state.
	fn finalize_key_exchange(&mut self, enveloped_data_der: &[u8], session_key: Vec<u8>) -> Result<(), HandshakeError> {
		// Add to transcript if we're computing it internally
		if self.transcript_hash.is_none() {
			self.transcript_buffer.extend_from_slice(enveloped_data_der);
		}

		// Store session key and transition state
		self.session_key = Some(Secret::from(session_key));
		// Transition directly from Init -> KeyExchangeSent (CMS path) or HelloSent -> KeyExchangeSent
		self.state.transition(ClientHandshakeState::KeyExchangeSent)?;

		Ok(())
	}

	/// Validate prerequisites for building client finished message.
	fn validate_client_finished_prerequisites(&self) -> Result<(), HandshakeError> {
		self.validate_expected_state(ClientHandshakeState::ServerFinishedReceived)
	}

	/// Prepare transcript hash and compute digest for signing.
	fn prepare_finished_digest(&self) -> Result<([u8; 32], Vec<u8>), HandshakeError> {
		let transcript_hash = self.transcript_hash.ok_or(HandshakeError::InvalidState)?;

		let mut hasher = P::Digest::new();
		hasher.update(transcript_hash);
		let digest = hasher.finalize();
		let digest_bytes = digest.to_vec();

		Ok((transcript_hash, digest_bytes))
	}

	/// Sign the finished digest using the client key provider.
	async fn sign_finished_digest(&self, digest: &[u8]) -> Result<Vec<u8>, HandshakeError> {
		let signature_bytes = self.client_key_provider.sign_prehash(digest).await?;
		Ok(signature_bytes)
	}

	/// Build cryptographic components needed for SignedData.
	async fn build_finished_crypto_components(
		&self,
	) -> Result<(SignerIdentifier, AlgorithmIdentifierOwned, AlgorithmIdentifierOwned), HandshakeError> {
		use crate::crypto::x509::utils::compute_signer_identifier_from_der;

		let public_key_bytes = self.client_key_provider.to_public_key_bytes().await?;
		let signer_id = compute_signer_identifier_from_der::<P::Digest>(&public_key_bytes)?;
		let digest_alg = AlgorithmIdentifierOwned { oid: P::Digest::OID, parameters: None };
		let signature_alg = AlgorithmIdentifierOwned { oid: P::Signature::ALGORITHM_OID, parameters: None };

		Ok((signer_id, digest_alg, signature_alg))
	}

	/// Build the complete SignedData structure.
	///
	/// A configured client certificate is embedded in the `certificates`
	/// field so the server can authenticate the client from the wire.
	fn build_signed_data(
		&self,
		transcript_hash: [u8; 32],
		signature_bytes: &[u8],
		signer_id: SignerIdentifier,
		digest_alg: AlgorithmIdentifierOwned,
		signature_alg: AlgorithmIdentifierOwned,
	) -> Result<Vec<u8>, HandshakeError> {
		let signer_info = SignerInfo {
			version: CmsVersion::V1,
			sid: signer_id,
			digest_alg: digest_alg.clone(),
			signed_attrs: None,
			signature_algorithm: signature_alg,
			signature: OctetString::new(signature_bytes)?,
			unsigned_attrs: None,
		};

		let octet_string = OctetString::new(transcript_hash)?;
		let econtent_der = octet_string.to_der()?;
		let econtent_any = crate::der::Any::from_der(&econtent_der)?;
		let encap_content_info =
			EncapsulatedContentInfo { econtent_type: crate::oids::DATA, econtent: Some(econtent_any) };

		let certificates = self
			.client_certificate
			.as_ref()
			.map(|cert| {
				let choice = CertificateChoices::Certificate(cert.as_ref().clone());
				Ok::<_, HandshakeError>(CertificateSet(vec![choice].try_into()?))
			})
			.transpose()?;

		let signed_data = SignedData {
			version: CmsVersion::V1,
			digest_algorithms: vec![digest_alg].try_into()?,
			encap_content_info,
			certificates,
			crls: None,
			signer_infos: vec![signer_info].try_into()?,
		};

		signed_data.to_der().map_err(Into::into)
	}

	/// Finalize client finished by transitioning state and marking invariant.
	fn finalize_client_finished(&mut self) -> Result<(), HandshakeError> {
		self.state.transition(ClientHandshakeState::ClientFinishedSent)?;
		self.invariants.mark_finished_sent()?;
		Ok(())
	}
}

/// Extract the server's `SecurityAccept` from a Finished message's unsigned
/// attributes, if present.
fn extract_security_accept_attr(signed_data_der: &[u8]) -> Result<Option<SecurityAccept>, HandshakeError> {
	let signed_data = SignedData::from_der(signed_data_der)?;
	signed_data
		.signer_infos
		.0
		.iter()
		.filter_map(|signer_info| signer_info.unsigned_attrs.as_ref())
		.flat_map(|attrs| attrs.iter())
		.find(|attr| attr.oid == crate::oids::HANDSHAKE_SECURITY_ACCEPT)
		.map(|attr| {
			let handshake_attr = crate::transport::handshake::attributes::HandshakeAttribute::from(attr);
			crate::transport::handshake::attributes::extract_security_accept(&handshake_attr)
		})
		.transpose()
}

// ============================================================================
// Common Handshake Trait Implementations
// ============================================================================

impl<P> HandshakeFinalization<P> for CmsHandshakeClient<P>
where
	P: CryptoProvider,
{
	fn selected_profile(&self) -> Option<SecurityProfileDesc> {
		self.selected_profile
	}
}

impl<P> HandshakeAlertHandler for CmsHandshakeClient<P> where P: CryptoProvider {}

// ============================================================================
// ClientHandshakeProtocol Implementation
// ============================================================================

impl<P> ClientHandshakeProtocol for CmsHandshakeClient<P>
where
	P: CryptoProvider + Send + Sync + 'static,
	P::Curve: elliptic_curve::Curve + elliptic_curve::CurveArithmetic,
	<P::Curve as elliptic_curve::Curve>::FieldBytesSize: ModulusSize,
	AffinePoint<P::Curve>: FromEncodedPoint<P::Curve> + ToEncodedPoint<P::Curve>,
	PublicKey<P::Curve>: EncodePublicKey,
	P::VerifyingKey: From<PublicKey<P::Curve>> + EncodePublicKey + signature::Verifier<P::Signature> + 'static,
	P::Signature: 'static,
	P::Digest: Send + 'static,
	P::AeadCipher: Send + Sync + KeyInit,
{
	type Error = HandshakeError;

	fn start<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, Self::Error>> + Send + 'a>> {
		Box::pin(async move {
			// Fresh random session key per handshake: a constant key
			// would make every session trivially decryptable (CWE-321).
			let session_key = crate::zeroize::Zeroizing::new(generate_nonce::<32>(None)?);
			self.build_key_exchange(session_key.to_vec(), None)
		})
	}

	fn handle_response<'a, 'b>(
		&'a mut self,
		msg: &'b [u8],
	) -> Pin<Box<dyn Future<Output = Result<Option<Vec<u8>>, Self::Error>> + Send + 'a>>
	where
		'b: 'a,
	{
		Box::pin(async move {
			// Process server finished
			self.process_server_finished(msg)?;

			// Build client finished
			let client_finished = self.build_client_finished().await?;
			Ok(Some(client_finished))
		})
	}

	#[cfg(feature = "aead")]
	fn complete<'a>(
		&'a mut self,
	) -> Pin<Box<dyn Future<Output = Result<crate::crypto::aead::RuntimeAead, Self::Error>> + Send + 'a>> {
		Box::pin(async move {
			// 1. Validate state
			if self.state.state() != ClientHandshakeState::ClientFinishedSent {
				return Err(HandshakeError::InvalidState);
			}

			// 2. Get CEK (session_key) and profile
			let cek = self.session_key.as_ref().ok_or(HandshakeError::InvalidState)?;
			let profile = self.selected_profile.ok_or(HandshakeError::InvalidState)?;
			let aead_oid = profile.aead.ok_or(HandshakeError::InvalidState)?;
			let transcript_hash = self.transcript_hash.ok_or(HandshakeError::InvalidState)?;

			// 3. Derive final session key as P::AeadCipher
			let cipher = cek.with(|key_bytes| self.derive_session_aead(key_bytes, &transcript_hash))??;

			// 4. Transition to complete
			self.state.transition(ClientHandshakeState::Completed)?;

			// 5. Wrap cipher in RuntimeAead with negotiated OID
			Ok(crate::crypto::aead::RuntimeAead::new(cipher, aead_oid))
		})
	}

	fn is_complete(&self) -> bool {
		self.is_complete()
	}

	fn selected_profile(&self) -> Option<SecurityProfileDesc> {
		self.selected_profile
	}
}

#[cfg(test)]
mod tests {
	use crate::cms::enveloped_data::EnvelopedData;
	use crate::crypto::profiles::DefaultCryptoProvider;
	use crate::crypto::sign::elliptic_curve::SecretKey;
	use crate::der::{Decode, Encode};
	use crate::oids::{HASH_SHA3_256, SIGNER_ECDSA_WITH_SHA3_256};
	use crate::spki::AlgorithmIdentifierOwned;
	use crate::transport::handshake::builders::TightBeamSignedDataBuilder;
	use crate::transport::handshake::error::HandshakeError;
	use crate::transport::handshake::processors::{TightBeamEnvelopedDataProcessor, TightBeamKariRecipient};
	use crate::transport::handshake::state::ClientHandshakeState;
	use crate::transport::handshake::tests::*;

	#[tokio::test]
	async fn test_client_state_flow() -> Result<(), Box<dyn std::error::Error>> {
		// Given: A CMS client in init state with a server certificate
		let transcript_hash = [1u8; 32];
		let server_test_cert = create_test_certificate();
		let server_cert = server_test_cert.certificate.clone();
		let mut client = TestCmsClientBuilder::new()
			.with_server_cert(server_cert)
			.with_transcript_hash(transcript_hash)
			.build()?;
		assert_eq!(client.state(), ClientHandshakeState::Init);

		// When: Client builds a valid key exchange
		let session_key = vec![2u8; 32];
		let key_exchange = client.build_key_exchange(session_key.clone(), None)?;
		assert_eq!(client.state(), ClientHandshakeState::KeyExchangeSent);
		// Verify session key is stored
		assert!(client.session_key().is_some());

		// Then: Server should be able to decrypt it using the matching private key
		let enveloped_data = EnvelopedData::from_der(&key_exchange)?;
		let server_secret = SecretKey::from(server_test_cert.signing_key.clone());
		let provider = DefaultCryptoProvider::default();
		let kari_processor = TightBeamKariRecipient::new(provider, server_secret);
		let processor = TightBeamEnvelopedDataProcessor::<DefaultCryptoProvider>::new(kari_processor);
		let decrypted = processor.process(&enveloped_data)?;
		let decrypted = crate::crypto::secret::ToInsecure::to_insecure(decrypted)?;
		assert_eq!(&decrypted[..], &session_key[..]);

		// When: Client processes server Finished
		let digest_alg = AlgorithmIdentifierOwned { oid: HASH_SHA3_256, parameters: None };
		let signature_alg = AlgorithmIdentifierOwned { oid: SIGNER_ECDSA_WITH_SHA3_256, parameters: None };
		let server_finished_builder = TightBeamSignedDataBuilder::<DefaultCryptoProvider, _>::new(
			&server_test_cert.signing_key,
			digest_alg,
			signature_alg,
		)?;
		let server_finished = server_finished_builder.build(&transcript_hash)?;
		let server_finished = server_finished.to_der()?;

		let verified = client.process_server_finished(&server_finished)?;
		assert_eq!(verified, transcript_hash);
		assert_eq!(client.state(), ClientHandshakeState::ServerFinishedReceived);

		// Build client Finished
		let _client_finished = client.build_client_finished().await?;
		assert_eq!(client.state(), ClientHandshakeState::ClientFinishedSent);

		// Complete
		client.complete()?;
		assert!(client.is_complete());
		assert_eq!(client.state(), ClientHandshakeState::Completed);

		Ok(())
	}

	/// A client without a trust store must abort instead of degrading to
	/// expiry-only server authentication (CWE-295).
	#[test]
	fn test_missing_trust_store_fails_closed() -> Result<(), Box<dyn std::error::Error>> {
		let server_cert = create_test_certificate().certificate;
		let test_cert = create_test_certificate();
		let server_cert = std::sync::Arc::new(server_cert);
		let provider = into_provider(test_cert.signing_key);
		let mut client = super::CmsHandshakeClient::<DefaultCryptoProvider>::new(
			DefaultCryptoProvider::default(),
			provider,
			server_cert,
		);

		let result = client.build_key_exchange(vec![2u8; 32], None);
		assert!(matches!(result, Err(HandshakeError::MissingTrustStore)));
		Ok(())
	}

	fn chain_client(
		chain: std::sync::Arc<[crate::x509::Certificate]>,
		store_root: Option<crate::x509::Certificate>,
	) -> Result<super::CmsHandshakeClient<DefaultCryptoProvider>, Box<dyn std::error::Error>> {
		use crate::crypto::hash::Sha3_256;
		use crate::crypto::policy::Secp256k1Policy;
		use crate::crypto::x509::store::{CertificateTrust, CertificateTrustBuilder, TrustBuilder};

		let mut builder = CertificateTrustBuilder::<Sha3_256>::from(Secp256k1Policy);
		if let Some(root) = store_root {
			builder = builder.with_certificate(root)?;
		}

		let store: std::sync::Arc<dyn CertificateTrust> = std::sync::Arc::new(builder.build());
		let client = super::CmsHandshakeClient::<DefaultCryptoProvider>::from_chain(
			DefaultCryptoProvider::default(),
			into_provider(create_test_certificate().signing_key),
			chain,
		)
		.with_trust_store(store);

		Ok(client)
	}

	/// A chain-provisioned client path-validates the chain and encrypts to
	/// its leaf; no separate pinned certificate is needed.
	#[test]
	fn from_chain_validates_and_targets_leaf() -> Result<(), Box<dyn std::error::Error>> {
		let chain = crate::testing::utils::create_test_certificate_chain()?;
		let mut client = chain_client(
			std::sync::Arc::from(vec![chain.root.clone(), chain.intermediate, chain.leaf.clone()]),
			Some(chain.root),
		)?;

		client.build_key_exchange(vec![2u8; 32], None)?;
		assert_eq!(client.state(), ClientHandshakeState::KeyExchangeSent);
		assert_eq!(client.server_leaf()?, &chain.leaf);
		Ok(())
	}

	#[test]
	fn from_chain_rejects_untrusted_chain() -> Result<(), Box<dyn std::error::Error>> {
		let chain = crate::testing::utils::create_test_certificate_chain()?;
		let mut client = chain_client(std::sync::Arc::from(vec![chain.root, chain.intermediate, chain.leaf]), None)?;

		let result = client.build_key_exchange(vec![2u8; 32], None);
		assert!(matches!(result, Err(HandshakeError::CertificateValidationError(_))));
		Ok(())
	}

	/// A pinned server certificate that differs from the provisioned chain
	/// leaf is a configuration mismatch, distinct from a re-handshake
	/// identity violation.
	#[test]
	fn pinned_certificate_mismatch_rejected() -> Result<(), Box<dyn std::error::Error>> {
		use crate::crypto::hash::Sha3_256;
		use crate::crypto::policy::Secp256k1Policy;
		use crate::crypto::x509::store::{CertificateTrust, CertificateTrustBuilder, TrustBuilder};

		let chain = crate::testing::utils::create_test_certificate_chain()?;
		let store: std::sync::Arc<dyn CertificateTrust> = std::sync::Arc::new(
			CertificateTrustBuilder::<Sha3_256>::from(Secp256k1Policy)
				.with_certificate(chain.root.clone())?
				.build(),
		);
		let pinned = std::sync::Arc::new(create_test_certificate().certificate);
		let mut client = super::CmsHandshakeClient::<DefaultCryptoProvider>::new(
			DefaultCryptoProvider::default(),
			into_provider(create_test_certificate().signing_key),
			pinned,
		)
		.with_server_certificate_chain(std::sync::Arc::from(vec![chain.root, chain.intermediate, chain.leaf]))
		.with_trust_store(store);

		let result = client.build_key_exchange(vec![2u8; 32], None);
		assert!(matches!(result, Err(HandshakeError::PinnedCertificateMismatch)));
		Ok(())
	}

	#[test]
	fn from_chain_rejects_empty_chain() -> Result<(), Box<dyn std::error::Error>> {
		let chain = crate::testing::utils::create_test_certificate_chain()?;
		let mut client = chain_client(std::sync::Arc::from(Vec::new()), Some(chain.root))?;

		let result = client.build_key_exchange(vec![2u8; 32], None);
		assert!(matches!(result, Err(HandshakeError::MissingServerCertificate)));
		Ok(())
	}

	#[tokio::test]
	async fn test_invalid_state_transitions() -> Result<(), Box<dyn std::error::Error>> {
		// Given: A CMS client in init state
		let mut client = TestCmsClientBuilder::new().build()?;

		// When: Trying to process server finished before sending key exchange
		let result = client.process_server_finished(&[]);
		assert!(result.is_err());

		// When: Trying to build client finished before processing server finished
		let result = client.build_client_finished().await;
		assert!(result.is_err());

		Ok(())
	}

	#[test]
	fn test_process_security_accept_rejects_unoffered_profile() -> Result<(), Box<dyn std::error::Error>> {
		use crate::crypto::sign::ecdsa::Secp256k1SigningKey;
		use crate::oids::{HANDSHAKE_SECURITY_ACCEPT, HASH_SHA3_256, SIGNER_ECDSA_WITH_SHA3_256};
		use crate::transport::handshake::attributes::encode_security_accept;
		use crate::transport::handshake::negotiation::{SecurityAccept, SecurityOffer};
		use crate::x509::attr::{Attribute, Attributes};

		let offered = create_default_test_profile();
		let mut unoffered = create_default_test_profile();
		unoffered.aead_key_size = Some(16);

		let build_finished_with_accept = |profile| -> Result<Vec<u8>, Box<dyn std::error::Error>> {
			let signing_key = Secp256k1SigningKey::random(&mut crate::random::OsRng);
			let digest_alg = AlgorithmIdentifierOwned { oid: HASH_SHA3_256, parameters: None };
			let signature_alg = AlgorithmIdentifierOwned { oid: SIGNER_ECDSA_WITH_SHA3_256, parameters: None };
			let builder =
				TightBeamSignedDataBuilder::<DefaultCryptoProvider, _>::new(&signing_key, digest_alg, signature_alg)?;
			let mut signed_data = builder.build(&[7u8; 32])?;

			let accept_attr = encode_security_accept(&SecurityAccept::new(profile))?;
			let x509_attr = Attribute {
				oid: HANDSHAKE_SECURITY_ACCEPT,
				values: crate::der::asn1::SetOfVec::try_from(accept_attr.attr_values)?,
			};

			let attrs = Attributes::try_from(vec![x509_attr])?;
			let mut signer_infos: Vec<_> = signed_data.signer_infos.0.iter().cloned().collect();

			signer_infos[0].unsigned_attrs = Some(attrs);
			signed_data.signer_infos = signer_infos.try_into()?;

			Ok(signed_data.to_der()?)
		};

		let offer = SecurityOffer::new(vec![offered]);
		let mut client = TestCmsClientBuilder::new().build()?.with_security_offer(offer);

		let accepted = build_finished_with_accept(offered)?;
		client.apply_security_accept(super::extract_security_accept_attr(&accepted)?)?;
		assert_eq!(client.selected_profile, Some(offered));

		let rejected = build_finished_with_accept(unoffered)?;
		let attrs = super::extract_security_accept_attr(&rejected)?;
		let result = client.apply_security_accept(attrs);
		assert!(matches!(result, Err(HandshakeError::InvalidProfileSelection)));

		Ok(())
	}
}