1#[cfg(feature = "server")]
28use std::sync::Arc;
29
30use bytes::{BufMut, Bytes, BytesMut};
31use data_encoding::BASE32HEX_NOPAD as HEX;
32#[cfg(not(wasm_browser))]
33use http::HeaderValue;
34#[cfg(feature = "server")]
35use iroh_base::Signature;
36use iroh_base::{PublicKey, SecretKey};
37use n0_error::{AnyError, anyerr, e, ensure, stack_error};
38use n0_future::{SinkExt, TryStreamExt};
39#[cfg(feature = "server")]
40use rand::CryptoRng;
41use tracing::trace;
42
43use super::{
44 common::{FrameType, FrameTypeError},
45 streams::BytesStreamSink,
46};
47use crate::ExportKeyingMaterial;
48#[cfg(feature = "server")]
49use crate::server::{Access, ClientRequest, DynAccessControl, OnDisconnectGuard};
50
51const DOMAIN_SEP_CHALLENGE: &str = "iroh-relay handshake v1 challenge signature";
53
54#[cfg(not(wasm_browser))]
56const DOMAIN_SEP_TLS_EXPORT_LABEL: &[u8] = b"iroh-relay handshake v1";
57
58#[derive(derive_more::Debug, serde::Serialize, serde::Deserialize)]
60#[cfg_attr(wasm_browser, allow(unused))]
61pub(crate) struct KeyMaterialClientAuth {
62 pub(crate) public_key: PublicKey,
64 #[serde(with = "serde_bytes")]
66 #[debug("{}", HEX.encode(signature))]
67 pub(crate) signature: [u8; 64],
68 #[debug("{}", HEX.encode(key_material_suffix))]
72 pub(crate) key_material_suffix: [u8; 16],
73}
74
75#[derive(derive_more::Debug, serde::Serialize, serde::Deserialize)]
77pub(crate) struct ServerChallenge {
78 #[debug("{}", HEX.encode(challenge))]
81 pub(crate) challenge: [u8; 16],
82}
83
84#[derive(derive_more::Debug, serde::Serialize, serde::Deserialize)]
88pub(crate) struct ClientAuth {
89 pub(crate) public_key: PublicKey,
91 #[serde(with = "serde_bytes")]
95 #[debug("{}", HEX.encode(signature))]
96 pub(crate) signature: [u8; 64],
97}
98
99#[derive(derive_more::Debug, serde::Serialize, serde::Deserialize)]
101pub(crate) struct ServerConfirmsAuth;
102
103#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
105pub(crate) struct ServerDeniesAuth {
106 reason: String,
107}
108
109trait Frame {
116 const TAG: FrameType;
118}
119
120impl<T: Frame> Frame for &T {
121 const TAG: FrameType = T::TAG;
122}
123
124impl Frame for ServerChallenge {
125 const TAG: FrameType = FrameType::ServerChallenge;
126}
127
128impl Frame for ClientAuth {
129 const TAG: FrameType = FrameType::ClientAuth;
130}
131
132impl Frame for ServerConfirmsAuth {
133 const TAG: FrameType = FrameType::ServerConfirmsAuth;
134}
135
136impl Frame for ServerDeniesAuth {
137 const TAG: FrameType = FrameType::ServerDeniesAuth;
138}
139
140#[stack_error(derive, add_meta)]
141#[allow(missing_docs)]
142#[non_exhaustive]
143pub enum Error {
144 #[error("WebSocket error")]
151 Websocket { source: AnyError },
152 #[error("Handshake stream ended prematurely")]
153 UnexpectedEnd {},
154 #[error(transparent)]
155 FrameTypeError {
156 #[error(from)]
157 source: FrameTypeError,
158 },
159 #[error("The relay denied our authentication ({reason})")]
160 ServerDeniedAuth { reason: String },
161 #[error("Unexpected tag, got {frame_type:?}, but expected one of {expected_types:?}")]
162 UnexpectedFrameType {
163 frame_type: FrameType,
164 expected_types: Vec<FrameType>,
165 },
166 #[error("Handshake failed while deserializing {frame_type:?} frame")]
167 DeserializationError {
168 frame_type: FrameType,
169 source: AnyError,
170 },
171 #[cfg(feature = "server")]
172 ClientAuthHeaderInvalid { value: HeaderValue },
174}
175
176#[cfg(feature = "server")]
177#[stack_error(derive, add_meta)]
178#[non_exhaustive]
179pub(crate) enum VerificationError {
180 #[error("Couldn't export TLS keying material on our end")]
181 NoKeyingMaterial,
182 #[error(
183 "Client didn't extract the same keying material, the suffix mismatched: expected {expected:X?} but got {actual:X?}"
184 )]
185 MismatchedSuffix {
186 expected: [u8; 16],
187 actual: [u8; 16],
188 },
189 #[error(
190 "Client signature {signature:X?} for message {message:X?} invalid for public key {public_key}"
191 )]
192 SignatureInvalid {
193 source: iroh_base::SignatureError,
194 message: Vec<u8>,
195 signature: [u8; 64],
196 public_key: PublicKey,
197 },
198}
199
200impl ServerChallenge {
201 #[cfg(feature = "server")]
203 pub(crate) fn new<R: CryptoRng + ?Sized>(rng: &mut R) -> Self {
204 let mut challenge = [0u8; 16];
205 rng.fill_bytes(&mut challenge);
206 Self { challenge }
207 }
208
209 fn message_to_sign(&self) -> [u8; 32] {
211 blake3::derive_key(DOMAIN_SEP_CHALLENGE, &self.challenge)
220 }
221}
222
223impl ClientAuth {
224 pub(crate) fn new(secret_key: &SecretKey, challenge: &ServerChallenge) -> Self {
226 Self {
227 public_key: secret_key.public(),
228 signature: secret_key.sign(&challenge.message_to_sign()).to_bytes(),
229 }
230 }
231
232 #[cfg(feature = "server")]
234 pub(crate) fn verify(&self, challenge: &ServerChallenge) -> Result<(), Box<VerificationError>> {
235 let message = challenge.message_to_sign();
236 self.public_key
237 .verify(&message, &Signature::from_bytes(&self.signature))
238 .map_err(|err| {
239 e!(VerificationError::SignatureInvalid {
240 source: err,
241 message: message.to_vec(),
242 signature: self.signature,
243 public_key: self.public_key
244 })
245 })
246 .map_err(Box::new)
247 }
248}
249
250#[cfg(not(wasm_browser))]
251impl KeyMaterialClientAuth {
252 pub(crate) fn new(secret_key: &SecretKey, io: &impl ExportKeyingMaterial) -> Option<Self> {
255 let public_key = secret_key.public();
256 let key_material = io.export_keying_material(
257 [0u8; 32],
258 DOMAIN_SEP_TLS_EXPORT_LABEL,
259 Some(secret_key.public().as_bytes()),
260 )?;
261 let (message, suffix) = key_material.split_at(16);
264 Some(Self {
265 public_key,
266 signature: secret_key.sign(message).to_bytes(),
267 key_material_suffix: suffix.try_into().expect("hardcoded length"),
268 })
269 }
270
271 pub(crate) fn into_header_value(self) -> HeaderValue {
273 HeaderValue::from_str(
274 &data_encoding::BASE64URL_NOPAD
275 .encode(&postcard::to_allocvec(&self).expect("encoding never fails")),
276 )
277 .expect("BASE64URL_NOPAD encoding contained invisible ascii characters")
278 }
279
280 #[cfg(feature = "server")]
288 pub(crate) fn verify(
289 &self,
290 io: &impl ExportKeyingMaterial,
291 ) -> Result<(), Box<VerificationError>> {
292 let key_material = io
293 .export_keying_material(
294 [0u8; 32],
295 DOMAIN_SEP_TLS_EXPORT_LABEL,
296 Some(self.public_key.as_bytes()),
297 )
298 .ok_or_else(|| e!(VerificationError::NoKeyingMaterial))?;
299 let (message, suffix) = key_material.split_at(16);
308 let suffix: [u8; 16] = suffix.try_into().expect("hardcoded length");
309 ensure!(
310 suffix == self.key_material_suffix,
311 VerificationError::MismatchedSuffix {
312 expected: self.key_material_suffix,
313 actual: suffix
314 }
315 );
316 self.public_key
320 .verify(message, &Signature::from_bytes(&self.signature))
321 .map_err(|err| {
322 e!(VerificationError::SignatureInvalid {
323 source: err,
324 message: message.to_vec(),
325 public_key: self.public_key,
326 signature: self.signature
327 })
328 })
329 .map_err(Box::new)
330 }
331}
332
333pub(crate) async fn clientside(
341 io: &mut (impl BytesStreamSink + ExportKeyingMaterial),
342 secret_key: &SecretKey,
343) -> Result<ServerConfirmsAuth, Error> {
344 let (tag, frame) = read_frame(
345 io,
346 &[
347 ServerChallenge::TAG,
348 ServerConfirmsAuth::TAG,
349 ServerDeniesAuth::TAG,
350 ],
351 )
352 .await?;
353
354 let (tag, frame) = if tag == ServerChallenge::TAG {
355 let challenge: ServerChallenge = deserialize_frame(frame)?;
356
357 let client_info = ClientAuth::new(secret_key, &challenge);
358 write_frame(io, client_info).await?;
359
360 read_frame(io, &[ServerConfirmsAuth::TAG, ServerDeniesAuth::TAG]).await?
361 } else {
362 (tag, frame)
363 };
364
365 match tag {
366 FrameType::ServerConfirmsAuth => {
367 let confirmation: ServerConfirmsAuth = deserialize_frame(frame)?;
368 Ok(confirmation)
369 }
370 FrameType::ServerDeniesAuth => {
371 let denial: ServerDeniesAuth = deserialize_frame(frame)?;
372 Err(e!(Error::ServerDeniedAuth {
373 reason: denial.reason
374 }))
375 }
376 _ => unreachable!(),
377 }
378}
379
380#[cfg(feature = "server")]
386#[derive(Debug)]
387#[must_use = "the protocol is not finished unless `authorize_if` is called"]
388pub struct SuccessfulAuthentication {
389 pub client_key: PublicKey,
391 pub mechanism: Mechanism,
393}
394
395#[cfg(feature = "server")]
397#[derive(Debug, Clone, Copy, PartialEq, Eq)]
398#[non_exhaustive]
399pub enum Mechanism {
400 SignedChallenge,
402 SignedKeyMaterial,
404}
405
406#[cfg(feature = "server")]
421pub async fn serverside(
422 io: &mut (impl BytesStreamSink + ExportKeyingMaterial),
423 client_auth_header: Option<HeaderValue>,
424) -> Result<SuccessfulAuthentication, Error> {
425 if let Some(client_auth_header) = client_auth_header {
426 let client_auth_bytes = data_encoding::BASE64URL_NOPAD
427 .decode(client_auth_header.as_ref())
428 .map_err(|_| {
429 e!(Error::ClientAuthHeaderInvalid {
430 value: client_auth_header.clone()
431 })
432 })?;
433
434 let client_auth: KeyMaterialClientAuth =
435 postcard::from_bytes(&client_auth_bytes).map_err(|_| {
436 e!(Error::ClientAuthHeaderInvalid {
437 value: client_auth_header.clone()
438 })
439 })?;
440
441 if client_auth.verify(io).is_ok() {
442 trace!(?client_auth.public_key, "authentication succeeded via keying material");
443 return Ok(SuccessfulAuthentication {
444 client_key: client_auth.public_key,
445 mechanism: Mechanism::SignedKeyMaterial,
446 });
447 }
448 }
451
452 let challenge = ServerChallenge::new(&mut rand::rng());
453 write_frame(io, &challenge).await?;
454
455 let (_, frame) = read_frame(io, &[ClientAuth::TAG]).await?;
456 let client_auth: ClientAuth = deserialize_frame(frame)?;
457
458 if let Err(err) = client_auth.verify(&challenge) {
459 trace!(?client_auth.public_key, ?err, "authentication failed");
460 let denial = ServerDeniesAuth {
461 reason: "signature invalid".into(),
462 };
463 write_frame(io, denial.clone()).await?;
464 Err(e!(Error::ServerDeniedAuth {
465 reason: denial.reason
466 }))
467 } else {
468 trace!(?client_auth.public_key, "authentication succeeded via challenge");
469 Ok(SuccessfulAuthentication {
470 client_key: client_auth.public_key,
471 mechanism: Mechanism::SignedChallenge,
472 })
473 }
474}
475
476#[cfg(feature = "server")]
477impl SuccessfulAuthentication {
478 pub async fn authorize_with(
490 self,
491 request: &ClientRequest,
492 access_control: &Arc<dyn DynAccessControl>,
493 io: &mut (impl BytesStreamSink + ExportKeyingMaterial),
494 ) -> Result<OnDisconnectGuard, Error> {
495 match access_control.on_connect(request).await {
496 Access::Allow => {
497 let guard = OnDisconnectGuard::for_access_control(access_control.clone(), request);
498 self.accept(io).await?;
499 Ok(guard)
500 }
501 Access::Deny { reason } => Err(self.deny(reason, io).await),
502 }
503 }
504 pub async fn authorize_if(
518 self,
519 access: Access,
520 io: &mut (impl BytesStreamSink + ExportKeyingMaterial),
521 ) -> Result<PublicKey, Error> {
522 match access {
523 Access::Allow => self.accept(io).await,
524 Access::Deny { reason } => Err(self.deny(reason, io).await),
525 }
526 }
527
528 async fn accept(
529 self,
530 io: &mut (impl BytesStreamSink + ExportKeyingMaterial),
531 ) -> Result<PublicKey, Error> {
532 trace!("authorizing client");
533 write_frame(io, ServerConfirmsAuth).await?;
534 Ok(self.client_key)
535 }
536
537 async fn deny(
538 self,
539 reason: Option<String>,
540 io: &mut (impl BytesStreamSink + ExportKeyingMaterial),
541 ) -> Error {
542 trace!("denying client auth");
543 let reason = reason.unwrap_or_else(|| "not authorized".into());
544 let denial = ServerDeniesAuth { reason };
545 match write_frame(io, denial.clone()).await {
546 Err(err) => err,
547 Ok(()) => e!(Error::ServerDeniedAuth {
548 reason: denial.reason
549 }),
550 }
551 }
552}
553
554async fn write_frame<F: serde::Serialize + Frame>(
555 io: &mut impl BytesStreamSink,
556 frame: F,
557) -> Result<(), Error> {
558 let mut bytes = BytesMut::new();
559 trace!(frame_type = ?F::TAG, "Writing frame");
560 F::TAG.write_to(&mut bytes);
561 let bytes = postcard::to_io(&frame, bytes.writer())
562 .expect("serialization failed") .into_inner()
564 .freeze();
565 io.send(bytes)
566 .await
567 .map_err(|err| e!(Error::Websocket, anyerr!(err)))?;
568 io.flush()
569 .await
570 .map_err(|err| e!(Error::Websocket, anyerr!(err)))?;
571 Ok(())
572}
573
574async fn read_frame(
575 io: &mut impl BytesStreamSink,
576 expected_types: &[FrameType],
577) -> Result<(FrameType, Bytes), Error> {
578 let mut payload = io
579 .try_next()
580 .await
581 .map_err(|err| e!(Error::Websocket, anyerr!(err)))?
582 .ok_or_else(|| e!(Error::UnexpectedEnd))?;
583
584 let frame_type = FrameType::from_bytes(&mut payload)?;
585 trace!(?frame_type, "Reading frame");
586 ensure!(
587 expected_types.contains(&frame_type),
588 Error::UnexpectedFrameType {
589 frame_type,
590 expected_types: expected_types.to_vec()
591 }
592 );
593
594 Ok((frame_type, payload))
595}
596
597fn deserialize_frame<F: Frame + serde::de::DeserializeOwned>(frame: Bytes) -> Result<F, Error> {
598 postcard::from_bytes(&frame).map_err(|err| {
599 e!(Error::DeserializationError {
600 frame_type: F::TAG,
601 source: anyerr!(err)
602 })
603 })
604}
605
606#[cfg(all(test, feature = "server"))]
607mod tests {
608 use bytes::BytesMut;
609 use iroh_base::{PublicKey, SecretKey};
610 use n0_error::{AnyError, Result, StackResultExt, StdResultExt};
611 use n0_future::{Sink, SinkExt, Stream, TryStreamExt};
612 use n0_tracing_test::traced_test;
613 use rand::{RngExt, SeedableRng};
614 use tokio_util::codec::{Framed, LengthDelimitedCodec};
615 use tracing::{Instrument, info_span};
616
617 use super::{
618 ClientAuth, KeyMaterialClientAuth, Mechanism, ServerChallenge, ServerConfirmsAuth,
619 };
620 use crate::{ExportKeyingMaterial, server::Access};
621
622 struct TestKeyingMaterial<IO> {
623 shared_secret: Option<u64>,
624 inner: IO,
625 }
626
627 trait WithTlsSharedSecret: Sized {
628 fn with_shared_secret(self, shared_secret: Option<u64>) -> TestKeyingMaterial<Self>;
629 }
630
631 impl<T: Sized> WithTlsSharedSecret for T {
632 fn with_shared_secret(self, shared_secret: Option<u64>) -> TestKeyingMaterial<Self> {
633 TestKeyingMaterial {
634 shared_secret,
635 inner: self,
636 }
637 }
638 }
639
640 impl<IO> ExportKeyingMaterial for TestKeyingMaterial<IO> {
641 fn export_keying_material<T: AsMut<[u8]>>(
642 &self,
643 mut output: T,
644 label: &[u8],
645 context: Option<&[u8]>,
646 ) -> Option<T> {
647 let label_key = blake3::hash(label);
650 let context_key = blake3::keyed_hash(label_key.as_bytes(), context.unwrap_or(&[]));
651 let mut hasher = blake3::Hasher::new_keyed(context_key.as_bytes());
652 hasher.update(&self.shared_secret?.to_le_bytes());
653 hasher.finalize_xof().fill(output.as_mut());
654
655 Some(output)
656 }
657 }
658
659 impl<V, IO: Stream<Item = V> + Unpin> Stream for TestKeyingMaterial<IO> {
660 type Item = V;
661
662 fn poll_next(
663 mut self: std::pin::Pin<&mut Self>,
664 cx: &mut std::task::Context<'_>,
665 ) -> std::task::Poll<Option<Self::Item>> {
666 std::pin::Pin::new(&mut self.inner).poll_next(cx)
667 }
668 }
669
670 impl<V, E, IO: Sink<V, Error = E> + Unpin> Sink<V> for TestKeyingMaterial<IO> {
671 type Error = E;
672
673 fn poll_ready(
674 mut self: std::pin::Pin<&mut Self>,
675 cx: &mut std::task::Context<'_>,
676 ) -> std::task::Poll<Result<(), Self::Error>> {
677 std::pin::Pin::new(&mut self.inner).poll_ready(cx)
678 }
679
680 fn start_send(mut self: std::pin::Pin<&mut Self>, item: V) -> Result<(), Self::Error> {
681 std::pin::Pin::new(&mut self.inner).start_send(item)
682 }
683
684 fn poll_flush(
685 mut self: std::pin::Pin<&mut Self>,
686 cx: &mut std::task::Context<'_>,
687 ) -> std::task::Poll<Result<(), Self::Error>> {
688 std::pin::Pin::new(&mut self.inner).poll_flush(cx)
689 }
690
691 fn poll_close(
692 mut self: std::pin::Pin<&mut Self>,
693 cx: &mut std::task::Context<'_>,
694 ) -> std::task::Poll<Result<(), Self::Error>> {
695 std::pin::Pin::new(&mut self.inner).poll_close(cx)
696 }
697 }
698
699 async fn simulate_handshake(
700 secret_key: &SecretKey,
701 client_shared_secret: Option<u64>,
702 server_shared_secret: Option<u64>,
703 restricted_to: Option<PublicKey>,
704 ) -> (Result<ServerConfirmsAuth>, Result<(PublicKey, Mechanism)>) {
705 let (client, server) = tokio::io::duplex(1024);
706
707 let mut client_io = Framed::new(client, LengthDelimitedCodec::new())
708 .map_ok(BytesMut::freeze)
709 .map_err(AnyError::from_std)
710 .sink_map_err(AnyError::from_std)
711 .with_shared_secret(client_shared_secret);
712 let mut server_io = Framed::new(server, LengthDelimitedCodec::new())
713 .map_ok(BytesMut::freeze)
714 .map_err(AnyError::from_std)
715 .sink_map_err(AnyError::from_std)
716 .with_shared_secret(server_shared_secret);
717
718 let client_auth_header = KeyMaterialClientAuth::new(secret_key, &client_io)
719 .map(KeyMaterialClientAuth::into_header_value);
720
721 n0_future::future::zip(
722 async {
723 super::clientside(&mut client_io, secret_key)
724 .await
725 .context("clientside")
726 }
727 .instrument(info_span!("clientside")),
728 async {
729 let auth_n = super::serverside(&mut server_io, client_auth_header)
730 .await
731 .context("serverside")?;
732 let mechanism = auth_n.mechanism;
733 let is_authorized = match restricted_to.is_none_or(|key| key == auth_n.client_key) {
734 true => Access::Allow,
735 false => Access::Deny { reason: None },
736 };
737 let key = auth_n.authorize_if(is_authorized, &mut server_io).await?;
738 Ok((key, mechanism))
739 }
740 .instrument(info_span!("serverside")),
741 )
742 .await
743 }
744
745 #[tokio::test]
746 #[traced_test]
747 async fn test_handshake_via_shared_secrets() -> Result {
748 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
749
750 let secret_key = SecretKey::from_bytes(&rng.random());
751 let (client, server) = simulate_handshake(&secret_key, Some(42), Some(42), None).await;
752 client?;
753 let (public_key, auth) = server?;
754 assert_eq!(public_key, secret_key.public());
755 assert_eq!(auth, Mechanism::SignedKeyMaterial); Ok(())
757 }
758
759 #[tokio::test]
760 #[traced_test]
761 async fn test_handshake_via_challenge() -> Result {
762 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
763
764 let secret_key = SecretKey::from_bytes(&rng.random());
765 let (client, server) = simulate_handshake(&secret_key, None, None, None).await;
766 client?;
767 let (public_key, auth) = server?;
768 assert_eq!(public_key, secret_key.public());
769 assert_eq!(auth, Mechanism::SignedChallenge);
770 Ok(())
771 }
772
773 #[tokio::test]
774 #[traced_test]
775 async fn test_handshake_mismatching_shared_secrets() -> Result {
776 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
777
778 let secret_key = SecretKey::from_bytes(&rng.random());
779 let (client, server) = simulate_handshake(&secret_key, Some(10), Some(99), None).await;
781 client?;
782 let (public_key, auth) = server?;
783 assert_eq!(public_key, secret_key.public());
784 assert_eq!(auth, Mechanism::SignedChallenge);
785 Ok(())
786 }
787
788 #[tokio::test]
789 #[traced_test]
790 async fn test_handshake_challenge_fallback() -> Result {
791 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
792 let secret_key = SecretKey::from_bytes(&rng.random());
793 let (client, server) = simulate_handshake(&secret_key, None, Some(99), None).await;
795 client?;
796 let (public_key, auth) = server?;
797 assert_eq!(public_key, secret_key.public());
798 assert_eq!(auth, Mechanism::SignedChallenge);
799 Ok(())
800 }
801
802 #[tokio::test]
803 #[traced_test]
804 async fn test_handshake_with_auth_positive() -> Result {
805 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
806 let secret_key = SecretKey::from_bytes(&rng.random());
807 let public_key = secret_key.public();
808 let (client, server) = simulate_handshake(&secret_key, None, None, Some(public_key)).await;
809 client?;
810 let (public_key, _) = server?;
811 assert_eq!(public_key, secret_key.public());
812 Ok(())
813 }
814
815 #[tokio::test]
816 #[traced_test]
817 async fn test_handshake_with_auth_negative() -> Result {
818 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
819 let secret_key = SecretKey::from_bytes(&rng.random());
820 let public_key = secret_key.public();
821 let wrong_secret_key = SecretKey::from_bytes(&rng.random());
822 let (client, server) =
823 simulate_handshake(&wrong_secret_key, None, None, Some(public_key)).await;
824 assert!(client.is_err());
825 assert!(server.is_err());
826 Ok(())
827 }
828
829 #[tokio::test]
830 #[traced_test]
831 async fn test_handshake_via_shared_secret_with_auth_negative() -> Result {
832 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
833 let secret_key = SecretKey::from_bytes(&rng.random());
834 let public_key = secret_key.public();
835 let wrong_secret_key = SecretKey::from_bytes(&rng.random());
836 let (client, server) =
837 simulate_handshake(&wrong_secret_key, Some(42), Some(42), Some(public_key)).await;
838 assert!(client.is_err());
839 assert!(server.is_err());
840 Ok(())
841 }
842
843 #[test]
844 fn test_client_auth_roundtrip() -> Result {
845 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
846 let secret_key = SecretKey::from_bytes(&rng.random());
847 let challenge = ServerChallenge::new(&mut rng);
848 let client_auth = ClientAuth::new(&secret_key, &challenge);
849
850 let bytes = postcard::to_allocvec(&client_auth).anyerr()?;
851 let decoded: ClientAuth = postcard::from_bytes(&bytes).anyerr()?;
852
853 assert_eq!(client_auth.public_key, decoded.public_key);
854 assert_eq!(client_auth.signature, decoded.signature);
855
856 Ok(())
857 }
858
859 #[test]
860 fn test_km_client_auth_roundtrip() -> Result {
861 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
862 let secret_key = SecretKey::from_bytes(&rng.random());
863 let client_auth = KeyMaterialClientAuth::new(
864 &secret_key,
865 &TestKeyingMaterial {
866 inner: (),
867 shared_secret: Some(42),
868 },
869 )
870 .anyerr()?;
871
872 let bytes = postcard::to_allocvec(&client_auth).anyerr()?;
873 let decoded: KeyMaterialClientAuth = postcard::from_bytes(&bytes).anyerr()?;
874
875 assert_eq!(client_auth.public_key, decoded.public_key);
876 assert_eq!(client_auth.signature, decoded.signature);
877
878 Ok(())
879 }
880
881 #[test]
882 fn test_challenge_verification() -> Result {
883 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
884 let secret_key = SecretKey::from_bytes(&rng.random());
885 let challenge = ServerChallenge::new(&mut rng);
886 let client_auth = ClientAuth::new(&secret_key, &challenge);
887 assert!(client_auth.verify(&challenge).is_ok());
888
889 Ok(())
890 }
891
892 #[test]
893 fn test_key_material_verification() -> Result {
894 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
895 let secret_key = SecretKey::from_bytes(&rng.random());
896 let io = TestKeyingMaterial {
897 inner: (),
898 shared_secret: Some(42),
899 };
900 let client_auth = KeyMaterialClientAuth::new(&secret_key, &io).anyerr()?;
901 assert!(client_auth.verify(&io).is_ok());
902
903 Ok(())
904 }
905}