tox_packet/onion/
onion_request_1.rs

1/*! OnionRequest1 packet with OnionRequest1Payload
2*/
3
4use super::*;
5
6use tox_binary_io::*;
7use crate::dht::*;
8
9use nom::{
10    combinator::{rest, rest_len},
11    bytes::complete::take
12};
13
14/// Encrypted payload should contain at least `IpPort` struct.
15const ONION_REQUEST_1_MIN_PAYLOAD_SIZE: usize = SIZE_IPPORT + MACBYTES;
16
17/** Second onion request packet. It's sent from the first to the second node from
18onion chain. Payload should be encrypted with temporary generated `SecretKey` and
19with DHT `PublicKey` of receiver.
20
21Serialized form:
22
23Length   | Content
24-------- | ------
25`1`      | `0x81`
26`24`     | `Nonce`
27`32`     | Temporary `PublicKey`
28variable | Payload
29`59`     | `OnionReturn`
30
31where payload is encrypted [`OnionRequest1Payload`](./struct.OnionRequest1Payload.html)
32
33*/
34#[derive(Clone, Debug, Eq, PartialEq)]
35pub struct OnionRequest1 {
36    /// Nonce for the current encrypted payload
37    pub nonce: Nonce,
38    /// Temporary `PublicKey` for the current encrypted payload
39    pub temporary_pk: PublicKey,
40    /// Encrypted payload
41    pub payload: Vec<u8>,
42    /// Return address encrypted by the first node from onion chain
43    pub onion_return: OnionReturn
44}
45
46impl FromBytes for OnionRequest1 {
47    named!(from_bytes<OnionRequest1>, do_parse!(
48        verify!(rest_len, |len| *len <= ONION_MAX_PACKET_SIZE) >>
49        tag!(&[0x81][..]) >>
50        nonce: call!(Nonce::from_bytes) >>
51        temporary_pk: call!(PublicKey::from_bytes) >>
52        rest_len: verify!(rest_len, |rest_len| *rest_len >= ONION_REQUEST_1_MIN_PAYLOAD_SIZE + ONION_RETURN_1_SIZE) >>
53        payload: call!(take(rest_len - ONION_RETURN_1_SIZE)) >>
54        onion_return: call!(OnionReturn::from_bytes) >>
55        (OnionRequest1 {
56            nonce,
57            temporary_pk,
58            payload: payload.to_vec(),
59            onion_return
60        })
61    ));
62}
63
64impl ToBytes for OnionRequest1 {
65    fn to_bytes<'a>(&self, buf: (&'a mut [u8], usize)) -> Result<(&'a mut [u8], usize), GenError> {
66        do_gen!(buf,
67            gen_cond!(
68                self.payload.len() < ONION_REQUEST_1_MIN_PAYLOAD_SIZE,
69                |buf| gen_error(buf, 0)
70            ) >>
71            gen_be_u8!(0x81) >>
72            gen_slice!(self.nonce.as_ref()) >>
73            gen_slice!(self.temporary_pk.as_ref()) >>
74            gen_slice!(self.payload.as_slice()) >>
75            gen_call!(|buf, onion_return| OnionReturn::to_bytes(onion_return, buf), &self.onion_return) >>
76            gen_len_limit(ONION_MAX_PACKET_SIZE)
77        )
78    }
79}
80
81impl OnionRequest1 {
82    /// Create new `OnionRequest1` object.
83    pub fn new(shared_secret: &PrecomputedKey, temporary_pk: &PublicKey, payload: &OnionRequest1Payload, onion_return: OnionReturn) -> OnionRequest1 {
84        let nonce = gen_nonce();
85        let mut buf = [0; ONION_MAX_PACKET_SIZE];
86        let (_, size) = payload.to_bytes((&mut buf, 0)).unwrap();
87        let payload = seal_precomputed(&buf[..size], &nonce, shared_secret);
88
89        OnionRequest1 { nonce, temporary_pk: *temporary_pk, payload, onion_return }
90    }
91
92    /** Decrypt payload and try to parse it as `OnionRequest1Payload`.
93
94    Returns `Error` in case of failure:
95
96    - fails to decrypt
97    - fails to parse as `OnionRequest1Payload`
98    */
99    pub fn get_payload(&self, shared_secret: &PrecomputedKey) -> Result<OnionRequest1Payload, GetPayloadError> {
100        let decrypted = open_precomputed(&self.payload, &self.nonce, shared_secret)
101            .map_err(|()| {
102                GetPayloadError::decrypt()
103            })?;
104        match OnionRequest1Payload::from_bytes(&decrypted) {
105            Err(error) => {
106                Err(GetPayloadError::deserialize(error, decrypted.clone()))
107            },
108            Ok((_, inner)) => {
109                Ok(inner)
110            }
111        }
112    }
113}
114
115/** Unencrypted payload of `OnionRequest1` packet.
116
117Inner payload should be sent to the next node with address from `ip_port` field.
118
119Serialized form:
120
121Length   | Content
122-------- | ------
123`19`     | `IpPort` of the next node
124`32`     | Temporary `PublicKey`
125variable | Payload
126
127where payload is encrypted [`OnionRequest2Payload`](./struct.OnionRequest2Payload.html)
128
129*/
130#[derive(Clone, Debug, Eq, PartialEq)]
131pub struct OnionRequest1Payload {
132    /// Address of the next node in the onion path
133    pub ip_port: IpPort,
134    /// Temporary `PublicKey` for the current encrypted payload
135    pub temporary_pk: PublicKey,
136    /// Inner onion payload
137    pub inner: Vec<u8>
138}
139
140impl FromBytes for OnionRequest1Payload {
141    named!(from_bytes<OnionRequest1Payload>, do_parse!(
142        ip_port: call!(IpPort::from_udp_bytes, IpPortPadding::WithPadding) >>
143        temporary_pk: call!(PublicKey::from_bytes) >>
144        inner: rest >>
145        (OnionRequest1Payload {
146            ip_port,
147            temporary_pk,
148            inner: inner.to_vec()
149        })
150    ));
151}
152
153impl ToBytes for OnionRequest1Payload {
154    fn to_bytes<'a>(&self, buf: (&'a mut [u8], usize)) -> Result<(&'a mut [u8], usize), GenError> {
155        do_gen!(buf,
156            gen_call!(|buf, ip_port| IpPort::to_udp_bytes(ip_port, buf, IpPortPadding::WithPadding), &self.ip_port) >>
157            gen_slice!(self.temporary_pk.as_ref()) >>
158            gen_slice!(self.inner.as_slice())
159        )
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    const ONION_RETURN_1_PAYLOAD_SIZE: usize = ONION_RETURN_1_SIZE - secretbox::NONCEBYTES;
168
169    encode_decode_test!(
170        tox_crypto::crypto_init().unwrap(),
171        onion_request_1_encode_decode,
172        OnionRequest1 {
173            nonce: gen_nonce(),
174            temporary_pk: gen_keypair().0,
175            payload: vec![42; ONION_REQUEST_1_MIN_PAYLOAD_SIZE],
176            onion_return: OnionReturn {
177                nonce: secretbox::gen_nonce(),
178                payload: vec![42; ONION_RETURN_1_PAYLOAD_SIZE]
179            }
180        }
181    );
182
183    encode_decode_test!(
184        tox_crypto::crypto_init().unwrap(),
185        onion_request_1_payload_encode_decode,
186        OnionRequest1Payload {
187            ip_port: IpPort {
188                protocol: ProtocolType::UDP,
189                ip_addr: "5.6.7.8".parse().unwrap(),
190                port: 12345
191            },
192            temporary_pk: gen_keypair().0,
193            inner: vec![42; ONION_REQUEST_1_MIN_PAYLOAD_SIZE]
194        }
195    );
196
197    #[test]
198    fn onion_request_1_payload_encrypt_decrypt() {
199        crypto_init().unwrap();
200        let (alice_pk, alice_sk) = gen_keypair();
201        let (bob_pk, _bob_sk) = gen_keypair();
202        let shared_secret = encrypt_precompute(&bob_pk, &alice_sk);
203        let payload = OnionRequest1Payload {
204            ip_port: IpPort {
205                protocol: ProtocolType::UDP,
206                ip_addr: "5.6.7.8".parse().unwrap(),
207                port: 12345
208            },
209            temporary_pk: gen_keypair().0,
210            inner: vec![42; ONION_REQUEST_1_MIN_PAYLOAD_SIZE]
211        };
212        let onion_return = OnionReturn {
213            nonce: secretbox::gen_nonce(),
214            payload: vec![42; ONION_RETURN_1_PAYLOAD_SIZE]
215        };
216        // encode payload with shared secret
217        let onion_packet = OnionRequest1::new(&shared_secret, &alice_pk, &payload, onion_return);
218        // decode payload with bob's secret key
219        let decoded_payload = onion_packet.get_payload(&shared_secret).unwrap();
220        // payloads should be equal
221        assert_eq!(decoded_payload, payload);
222    }
223
224    #[test]
225    fn onion_request_1_payload_encrypt_decrypt_invalid_key() {
226        crypto_init().unwrap();
227        let (alice_pk, alice_sk) = gen_keypair();
228        let (bob_pk, _bob_sk) = gen_keypair();
229        let (_eve_pk, eve_sk) = gen_keypair();
230        let shared_secret = encrypt_precompute(&bob_pk, &alice_sk);
231        let payload = OnionRequest1Payload {
232            ip_port: IpPort {
233                protocol: ProtocolType::UDP,
234                ip_addr: "5.6.7.8".parse().unwrap(),
235                port: 12345
236            },
237            temporary_pk: gen_keypair().0,
238            inner: vec![42; ONION_REQUEST_1_MIN_PAYLOAD_SIZE]
239        };
240        let onion_return = OnionReturn {
241            nonce: secretbox::gen_nonce(),
242            payload: vec![42; ONION_RETURN_1_PAYLOAD_SIZE]
243        };
244        // encode payload with shared secret
245        let onion_packet = OnionRequest1::new(&shared_secret, &alice_pk, &payload, onion_return);
246        // try to decode payload with eve's secret key
247        let eve_shared_secret = encrypt_precompute(&bob_pk, &eve_sk);
248        let decoded_payload = onion_packet.get_payload(&eve_shared_secret);
249        assert!(decoded_payload.is_err());
250    }
251
252    #[test]
253    fn onion_request_1_decrypt_invalid() {
254        crypto_init().unwrap();
255        let (_alice_pk, alice_sk) = gen_keypair();
256        let (bob_pk, _bob_sk) = gen_keypair();
257        let shared_secret = precompute(&bob_pk, &alice_sk);
258        let nonce = gen_nonce();
259        let temporary_pk = gen_keypair().0;
260        // Try long invalid array
261        let invalid_payload = [42; ONION_REQUEST_1_MIN_PAYLOAD_SIZE];
262        let invalid_payload_encoded = seal_precomputed(&invalid_payload, &nonce, &shared_secret);
263        let invalid_onion_request_1 = OnionRequest1 {
264            nonce,
265            temporary_pk,
266            payload: invalid_payload_encoded,
267            onion_return: OnionReturn {
268                nonce: secretbox::gen_nonce(),
269                payload: vec![42; ONION_RETURN_1_PAYLOAD_SIZE]
270            }
271        };
272        assert!(invalid_onion_request_1.get_payload(&shared_secret).is_err());
273        // Try short incomplete array
274        let invalid_payload = [];
275        let invalid_payload_encoded = seal_precomputed(&invalid_payload, &nonce, &shared_secret);
276        let invalid_onion_request_1 = OnionRequest1 {
277            nonce,
278            temporary_pk,
279            payload: invalid_payload_encoded,
280            onion_return: OnionReturn {
281                nonce: secretbox::gen_nonce(),
282                payload: vec![42; ONION_RETURN_1_PAYLOAD_SIZE]
283            }
284        };
285        assert!(invalid_onion_request_1.get_payload(&shared_secret).is_err());
286    }
287}