rings_core/message/handlers/
e2e.rs1use async_trait::async_trait;
2
3use crate::error::Result;
4use crate::message::e2e::E2eHandshakeRequest;
5use crate::message::e2e::E2eHandshakeResponse;
6use crate::message::e2e::E2eStreamFrame;
7use crate::message::effects::CoreEffect;
8use crate::message::HandleMsg;
9use crate::message::Message;
10use crate::message::MessageHandler;
11use crate::message::MessagePayload;
12use crate::message::MessageVerificationExt;
13use crate::message::PayloadSender;
14
15fn e2e_local_or_forward_effects<'payload>(
16 local: crate::dht::Did,
17 ctx: &'payload MessagePayload,
18) -> Option<CoreEffect<'payload>> {
19 if ctx.should_forward_from(local) {
20 Some(CoreEffect::forward_payload(ctx, None))
21 } else {
22 None
23 }
24}
25
26async fn run_e2e_local_or_forward<'payload>(
27 handler: &MessageHandler,
28 ctx: &'payload MessagePayload,
29 local_effects: impl FnOnce() -> Result<Vec<CoreEffect<'payload>>>,
30) -> Result<()> {
31 if let Some(effect) = e2e_local_or_forward_effects(handler.dht.did, ctx) {
32 return handler.run_effects([effect]).await;
33 }
34
35 handler.run_effects(local_effects()?).await
36}
37
38fn e2e_handshake_response_effect<'payload>(
39 ctx: &MessagePayload,
40 msg: &E2eHandshakeRequest,
41 responder_public_key: crate::ecc::PublicKey<33>,
42) -> Result<CoreEffect<'payload>> {
43 msg.verify_requester(ctx.signer())?;
44 Ok(CoreEffect::send_message(
45 Message::E2eHandshakeResponse(E2eHandshakeResponse::new(responder_public_key)),
46 ctx.signer(),
47 ))
48}
49
50#[cfg_attr(all(feature = "wasm", target_family = "wasm"), async_trait(?Send))]
51#[cfg_attr(not(all(feature = "wasm", target_family = "wasm")), async_trait)]
52impl HandleMsg<E2eHandshakeRequest> for MessageHandler {
53 async fn handle(&self, ctx: &MessagePayload, msg: &E2eHandshakeRequest) -> Result<()> {
54 run_e2e_local_or_forward(self, ctx, || {
55 let responder_public_key = self.transport.session_sk().session().account_pubkey()?;
56 Ok(vec![e2e_handshake_response_effect(
57 ctx,
58 msg,
59 responder_public_key,
60 )?])
61 })
62 .await
63 }
64}
65
66#[cfg_attr(all(feature = "wasm", target_family = "wasm"), async_trait(?Send))]
67#[cfg_attr(not(all(feature = "wasm", target_family = "wasm")), async_trait)]
68impl HandleMsg<E2eHandshakeResponse> for MessageHandler {
69 async fn handle(&self, ctx: &MessagePayload, msg: &E2eHandshakeResponse) -> Result<()> {
70 run_e2e_local_or_forward(self, ctx, || {
71 msg.verify_responder(ctx.signer())?;
72 Ok(Vec::new())
73 })
74 .await
75 }
76}
77
78#[cfg_attr(all(feature = "wasm", target_family = "wasm"), async_trait(?Send))]
79#[cfg_attr(not(all(feature = "wasm", target_family = "wasm")), async_trait)]
80impl HandleMsg<E2eStreamFrame> for MessageHandler {
81 async fn handle(&self, ctx: &MessagePayload, msg: &E2eStreamFrame) -> Result<()> {
82 run_e2e_local_or_forward(self, ctx, || {
83 msg.verify_sender(ctx.signer())?;
84 Ok(Vec::new())
85 })
86 .await
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use rand::SeedableRng;
93
94 use super::*;
95 use crate::ecc::SecretKey;
96 use crate::error::Error;
97 use crate::message::e2e::encrypt_stream_with_rng;
98 use crate::message::e2e::E2eHandshakeRequest;
99 use crate::session::SessionSk;
100
101 fn e2e_payload(destination: crate::dht::Did) -> Result<MessagePayload> {
102 let sender = SecretKey::random();
103 let recipient = SecretKey::random();
104 let session_sk = SessionSk::new_with_seckey(&sender)?;
105 let mut rng = rand_hc::Hc128Rng::from_entropy();
106 let mut frames = encrypt_stream_with_rng(
107 b"hello",
108 uuid::Uuid::new_v4(),
109 sender.pubkey(),
110 recipient.pubkey(),
111 16,
112 &mut rng,
113 )?;
114 let encrypted = frames
115 .pop()
116 .ok_or_else(|| Error::InvalidMessage("expected one E2E stream frame".to_string()))?;
117 MessagePayload::new_send(
118 Message::E2eStreamFrame(encrypted),
119 &session_sk,
120 destination,
121 destination,
122 )
123 }
124
125 fn signed_handshake_request(
126 signer: &SecretKey,
127 request: E2eHandshakeRequest,
128 destination: crate::dht::Did,
129 ) -> Result<MessagePayload> {
130 let session_sk = SessionSk::new_with_seckey(signer)?;
131 MessagePayload::new_send(
132 Message::E2eHandshakeRequest(request),
133 &session_sk,
134 destination,
135 destination,
136 )
137 }
138
139 #[test]
140 fn test_local_handshake_request_sends_responder_key_to_signer() -> Result<()> {
141 let requester = SecretKey::random();
142 let responder = SecretKey::random();
143 let request = E2eHandshakeRequest::new(requester.pubkey());
144 let payload = signed_handshake_request(&requester, request, responder.address().into())?;
145 let effect = e2e_handshake_response_effect(&payload, &request, responder.pubkey())?;
146
147 match effect {
148 CoreEffect::SendMessage { msg, destination } => {
149 assert_eq!(destination, requester.address().into());
150 match *msg {
151 Message::E2eHandshakeResponse(response) => {
152 assert_eq!(response.responder_public_key, responder.pubkey());
153 response.verify_responder(responder.address().into())?;
154 }
155 msg => {
156 return Err(Error::InvalidMessage(format!(
157 "expected E2eHandshakeResponse, got {msg:?}"
158 )))
159 }
160 }
161 }
162 effect => {
163 return Err(Error::InvalidMessage(format!(
164 "expected SendMessage effect, got {effect:?}"
165 )))
166 }
167 }
168 Ok(())
169 }
170
171 #[test]
172 fn test_local_handshake_request_rejects_key_not_owned_by_signer() -> Result<()> {
173 let requester = SecretKey::random();
174 let responder = SecretKey::random();
175 let request = E2eHandshakeRequest::new(responder.pubkey());
176 let payload = signed_handshake_request(&requester, request, responder.address().into())?;
177
178 assert!(matches!(
179 e2e_handshake_response_effect(&payload, &request, responder.pubkey()),
180 Err(Error::E2ePublicKeyDidMismatch { .. })
181 ));
182 Ok(())
183 }
184
185 #[test]
186 fn test_remote_e2e_message_forwards_payload() -> Result<()> {
187 let local = SecretKey::random().address().into();
188 let remote = SecretKey::random().address().into();
189 let payload = e2e_payload(remote)?;
190 let effect = e2e_local_or_forward_effects(local, &payload)
191 .ok_or_else(|| Error::InvalidMessage("expected ForwardPayload effect".to_string()))?;
192
193 match effect {
194 CoreEffect::ForwardPayload {
195 payload: forwarded,
196 next_hop,
197 } => {
198 assert!(std::ptr::eq(forwarded, &payload));
199 assert_eq!(next_hop, None);
200 }
201 effect => {
202 return Err(Error::InvalidMessage(format!(
203 "expected ForwardPayload, got {effect:?}"
204 )))
205 }
206 }
207 Ok(())
208 }
209}