rings_node/onion/circuit/
shell.rs1use std::sync::Arc;
2
3use bytes::Bytes;
4use rings_core::dht::Did;
5use rings_core::ecc::PublicKey;
6use rings_core::session::SessionSk;
7use rings_core::utils::get_epoch_ms;
8
9use super::cell::open_cell;
10use super::cell::seal_encoded_message;
11use super::codec::encode_local_message;
12use super::codec::OnionLocalMessage;
13use super::crypto::decrypt_client_payload;
14use super::crypto::decrypt_forward_layer;
15use super::limiter::OnionCryptoGate;
16use super::send_outbox::OnionLinkSender;
17#[cfg(all(test, rings_native))]
18use super::send_outbox::OnionSendTestHook;
19use super::OnionAuthenticatedPayload;
20use super::OnionCellBucket;
21use super::OnionCircuitEffect;
22use super::OnionCircuitId;
23use super::OnionCircuitPayload;
24use super::OnionClientReturn;
25use super::OnionForwardNonce;
26use super::OnionForwardSequence;
27use crate::error::Error;
28use crate::error::Result;
29use crate::extension::ext::EffectScope;
30use crate::extension::ext::Interpret;
31use crate::extension::ext::Scope;
32use crate::extension::transport::platform::spawn_detached;
33
34pub struct OnionCircuitShell<H> {
36 session_sk: SessionSk,
37 crypto_gate: OnionCryptoGate,
38 link_sender: OnionLinkSender,
39 handler: Arc<H>,
40}
41
42impl<H> OnionCircuitShell<H> {
43 pub fn new(session_sk: SessionSk, handler: H) -> Self {
45 Self {
46 session_sk,
47 crypto_gate: OnionCryptoGate::default(),
48 link_sender: OnionLinkSender::default(),
49 handler: Arc::new(handler),
50 }
51 }
52
53 #[cfg(all(test, rings_native))]
54 pub(super) fn new_with_send_test_hook(
55 session_sk: SessionSk,
56 handler: H,
57 test_hook: Arc<OnionSendTestHook>,
58 ) -> Self {
59 Self {
60 session_sk,
61 crypto_gate: OnionCryptoGate::default(),
62 link_sender: OnionLinkSender::with_test_hook(test_hook),
63 handler: Arc::new(handler),
64 }
65 }
66
67 pub(crate) fn with_link_sender(
69 session_sk: SessionSk,
70 handler: H,
71 link_sender: OnionLinkSender,
72 ) -> Self {
73 Self {
74 session_sk,
75 crypto_gate: OnionCryptoGate::default(),
76 link_sender,
77 handler: Arc::new(handler),
78 }
79 }
80
81 fn admit_crypto(&self, from: Did, now_ms: u128, visible_cell_bytes: u64) -> Result<()> {
82 self.crypto_gate.admit(from, now_ms, visible_cell_bytes)
83 }
84
85 fn decrypt_cell_reinject(
86 &self,
87 from: Did,
88 bucket: OnionCellBucket,
89 sealed: &rings_core::ecc::elgamal::impls::secp256k1::AeadCiphertext,
90 ) -> Result<Option<Bytes>> {
91 let received_at_ms = get_epoch_ms();
92 let visible_cell_bytes = u64::try_from(bucket.plaintext_len())
93 .map_err(|_| Error::OnionRouteError(crate::onion::OnionRouteError::InvalidCell))?;
94 match self.admit_crypto(from, received_at_ms, visible_cell_bytes) {
95 Ok(()) => {}
96 Err(Error::NoPermission) => {
97 drop_bad_crypto("forward admission denied", Error::NoPermission);
98 return Ok(None);
99 }
100 Err(error) => return Err(error),
101 }
102 let message = match open_cell(&self.session_sk, bucket, sealed) {
103 Ok(message) => message,
104 Err(error) => {
105 drop_bad_crypto("cell decrypt", error);
106 return Ok(None);
107 }
108 };
109 encode_local_message(OnionLocalMessage::CellReady {
110 from,
111 received_at_ms,
112 bucket,
113 message,
114 })
115 .map(Some)
116 }
117
118 fn decrypt_forward_reinject(
119 &self,
120 from: Did,
121 received_at_ms: u128,
122 bucket: OnionCellBucket,
123 circuit_id: OnionCircuitId,
124 payload: &rings_core::ecc::elgamal::impls::secp256k1::AeadCiphertext,
125 ) -> Result<Option<Bytes>> {
126 match self.admit_crypto(from, received_at_ms, 0) {
127 Ok(()) => {}
128 Err(Error::NoPermission) => {
129 drop_bad_crypto("forward admission denied", Error::NoPermission);
130 return Ok(None);
131 }
132 Err(error) => return Err(error),
133 }
134 let layer = match decrypt_forward_layer(&self.session_sk, circuit_id, payload) {
135 Ok(layer) => layer,
136 Err(error) => {
137 drop_bad_crypto("forward decrypt", error);
138 return Ok(None);
139 }
140 };
141 encode_local_message(OnionLocalMessage::ForwardReady {
142 from,
143 received_at_ms,
144 bucket,
145 circuit_id,
146 layer,
147 })
148 .map(Some)
149 }
150
151 fn decrypt_client_payload(
152 &self,
153 from: Did,
154 payload: &rings_core::ecc::elgamal::impls::secp256k1::AeadCiphertext,
155 ) -> Result<Option<OnionAuthenticatedPayload>> {
156 let received_at_ms = get_epoch_ms();
157 match self.admit_crypto(from, received_at_ms, 0) {
158 Ok(()) => {}
159 Err(Error::NoPermission) => {
160 drop_bad_crypto("client admission denied", Error::NoPermission);
161 return Ok(None);
162 }
163 Err(error) => return Err(error),
164 }
165 match decrypt_client_payload(&self.session_sk, payload) {
166 Ok(payload) => Ok(Some(payload)),
167 Err(error) => {
168 drop_bad_crypto("client decrypt", error);
169 Ok(None)
170 }
171 }
172 }
173}
174
175#[cfg_attr(rings_browser, async_trait::async_trait(?Send))]
176#[cfg_attr(rings_native, async_trait::async_trait)]
177impl<H> Interpret for OnionCircuitShell<H>
178where H: OnionCircuitHandler + crate::extension::ext::MaybeSend + 'static
179{
180 type Effect = OnionCircuitEffect;
181
182 async fn run(&self, scope: &EffectScope, effect: OnionCircuitEffect) -> Result<Vec<Bytes>> {
183 match effect {
184 OnionCircuitEffect::DecryptCell {
185 from,
186 bucket,
187 sealed,
188 } => Ok(self
189 .decrypt_cell_reinject(from, bucket, &sealed)?
190 .into_iter()
191 .collect()),
192 OnionCircuitEffect::DecryptForward {
193 from,
194 received_at_ms,
195 bucket,
196 circuit_id,
197 payload,
198 } => Ok(self
199 .decrypt_forward_reinject(from, received_at_ms, bucket, circuit_id, &payload)?
200 .into_iter()
201 .collect()),
202 OnionCircuitEffect::SealAndSend {
203 to,
204 recipient,
205 bucket,
206 encoded_message,
207 } => {
208 let payload = seal_encoded_message(&encoded_message, recipient, Some(bucket))?;
209 self.link_sender.enqueue_sealed(
210 scope.lifecycle(),
211 super::OnionLink::new(to, recipient),
212 payload,
213 )?;
214 Ok(Vec::new())
215 }
216 OnionCircuitEffect::Exit {
217 from,
218 circuit_id,
219 return_peer,
220 return_session_public_key,
221 client,
222 forward_nonce,
223 forward_sequence,
224 payload,
225 } => {
226 let lifecycle = scope.lifecycle();
227 let handler = Arc::clone(&self.handler);
228 spawn_detached(async move {
229 let result = handler
230 .handle_exit(&lifecycle, OnionCircuitExitFrame {
231 from,
232 circuit_id,
233 return_peer,
234 return_session_public_key,
235 client,
236 forward_nonce,
237 forward_sequence,
238 payload,
239 })
240 .await;
241 if let Err(error) = result {
242 tracing::warn!(%error, "onion exit effect failed");
243 }
244 });
245 Ok(Vec::new())
246 }
247 OnionCircuitEffect::DecryptClient {
248 from,
249 circuit_id,
250 payload,
251 } => {
252 if let Some(payload) = self.decrypt_client_payload(from, &payload)? {
253 let lifecycle = scope.lifecycle();
254 self.handler
255 .handle_client(&lifecycle, from, circuit_id, payload)
256 .await?;
257 }
258 Ok(Vec::new())
259 }
260 }
261 }
262}
263
264#[derive(Clone, Debug)]
266pub struct OnionCircuitExitFrame {
267 pub from: Did,
269 pub circuit_id: OnionCircuitId,
271 pub return_peer: Did,
273 pub return_session_public_key: PublicKey<33>,
275 pub client: OnionClientReturn,
277 pub forward_nonce: OnionForwardNonce,
279 pub forward_sequence: OnionForwardSequence,
281 pub payload: OnionCircuitPayload,
283}
284
285#[cfg_attr(rings_browser, async_trait::async_trait(?Send))]
287#[cfg_attr(rings_native, async_trait::async_trait)]
288pub trait OnionCircuitHandler {
289 async fn handle_exit(&self, scope: &Scope, frame: OnionCircuitExitFrame) -> Result<()>;
291
292 async fn handle_client(
294 &self,
295 scope: &Scope,
296 from: Did,
297 circuit_id: OnionCircuitId,
298 payload: OnionAuthenticatedPayload,
299 ) -> Result<()>;
300}
301
302fn drop_bad_crypto(context: &str, error: Error) {
303 tracing::debug!("drop onion circuit message after {context}: {error}");
304}