summaryrefslogtreecommitdiffhomepage
path: root/mullvad-encrypted-dns-proxy/src/config/xor.rs
blob: eb4f314e13c81ac6271480f8469ed16730b5f385 (plain)
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
use core::fmt;
use std::net::{Ipv4Addr, SocketAddrV4};

use serde::{Deserialize, Serialize};

/// Parse a proxy config that XORs all traffic with the given key.
///
/// A Xor configuration is represented by the proxy type `ProxyType::XorV2`. There used to be a `XorV1`, but it
/// is deprecated and should not be used.
///
/// The following bytes of an IPv6 address are interpreted to derive a Xor configuration:
/// bytes 2-4 - u16le - proxy type - must be 0x03
/// bytes 4-8 - [u8; 4] - 4 bytes representing the proxy IPv4 address
/// bytes 8-10 - u16le - port on which the proxy is listening
/// bytes 10-16 - [u8; 6] - xor key bytes. 0x00 marks a premature end of the key
/// Given the above, `2001:300:b9d5:9a75:3a04:eafd:1100:ad9e` will have the second hexlet (0x0300)
/// represent the proxy type, the next 2 hexlets (0xb9d5,0x9a75) represent the IPv4 address for the
/// proxy endpoint, the next hexlet (`3a04`) represents the port for the proxy endpoint, and
/// the final 3 hexlets `eafd:1100:ad9e` represent the xor key (0xEA, 0xFD, 0x11).
pub fn parse_xor(data: [u8; 12]) -> Result<super::ProxyConfig, super::Error> {
    let (ip_bytes, tail) = data.split_first_chunk::<4>().unwrap();
    let (port_bytes, key_bytes) = tail.split_first_chunk::<2>().unwrap();
    let key_bytes = <[u8; 6]>::try_from(key_bytes).unwrap();

    let ip = Ipv4Addr::from(*ip_bytes);
    let port = u16::from_le_bytes(*port_bytes);
    if port == 0 {
        return Err(super::Error::InvalidPort(port));
    }
    let addr = SocketAddrV4::new(ip, port);

    let key = XorKey::try_from(key_bytes)?;

    Ok(super::ProxyConfig {
        addr,
        obfuscation: Some(super::ObfuscationConfig::XorV2(key)),
    })
}

/// A bunch of bytes, representing a "key" Simply meaning a slice of bytes that the data
/// will be XORed with.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct XorKey {
    data: [u8; 6],
    len: usize,
}

impl XorKey {
    /// Return the XOR key material. Will always have at least length 1.
    pub fn key_data(&self) -> &[u8] {
        &self.data[0..self.len]
    }
}

impl fmt::Debug for XorKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "0x")?;
        for byte in self.key_data() {
            write!(f, "{byte:0>2x}")?;
        }
        Ok(())
    }
}

impl TryFrom<[u8; 6]> for XorKey {
    type Error = super::Error;

    fn try_from(mut key_bytes: [u8; 6]) -> Result<Self, Self::Error> {
        let key_len = key_bytes
            .iter()
            .position(|b| *b == 0x00)
            .unwrap_or(key_bytes.len());
        if key_len == 0 {
            return Err(super::Error::EmptyXorKey);
        }

        // Reset bytes after terminating null to zeros.
        // Allows simpler implementations of Eq and Hash
        key_bytes[key_len..].fill(0);

        Ok(Self {
            data: key_bytes,
            len: key_len,
        })
    }
}

#[derive(Debug)]
pub struct XorObfuscator {
    key: XorKey,
    key_index: usize,
}

impl XorObfuscator {
    pub fn new(key: XorKey) -> Self {
        Self { key, key_index: 0 }
    }
}

impl super::Obfuscator for XorObfuscator {
    fn obfuscate(&mut self, buffer: &mut [u8]) {
        let key_data = self.key.key_data();
        for byte in buffer {
            *byte ^= key_data[self.key_index % key_data.len()];
            self.key_index = (self.key_index + 1) % key_data.len();
        }
    }
}

#[cfg(test)]
mod tests {
    use std::net::{Ipv6Addr, SocketAddrV4};

    use crate::config::xor::{XorKey, XorObfuscator};
    use crate::config::{Error, ObfuscationConfig, Obfuscator, ProxyConfig};

    #[test]
    fn xor_parsing() {
        struct Test {
            input: Ipv6Addr,
            expected: Result<ProxyConfig, Error>,
        }
        let tests = vec![
            Test {
                input: "2001:300:7f00:1:3905:0102:304:506"
                    .parse::<Ipv6Addr>()
                    .unwrap(),
                expected: Ok(ProxyConfig {
                    addr: "127.0.0.1:1337".parse::<SocketAddrV4>().unwrap(),
                    obfuscation: Some(ObfuscationConfig::XorV2(
                        XorKey::try_from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]).unwrap(),
                    )),
                }),
            },
            Test {
                input: "2001:300:7f00:1:3905:0100:304:506"
                    .parse::<Ipv6Addr>()
                    .unwrap(),
                expected: Ok(ProxyConfig {
                    addr: "127.0.0.1:1337".parse::<SocketAddrV4>().unwrap(),
                    obfuscation: Some(ObfuscationConfig::XorV2(
                        XorKey::try_from([0x01, 0, 0, 0, 0, 0]).unwrap(),
                    )),
                }),
            },
            Test {
                input: "2001:300:c0a8:101:bb01:ff04:204:0"
                    .parse::<Ipv6Addr>()
                    .unwrap(),
                expected: Ok(ProxyConfig {
                    addr: "192.168.1.1:443".parse::<SocketAddrV4>().unwrap(),
                    obfuscation: Some(ObfuscationConfig::XorV2(
                        XorKey::try_from([0xff, 0x04, 0x02, 0x04, 0, 0]).unwrap(),
                    )),
                }),
            },
        ];

        for t in tests {
            let parsed = ProxyConfig::try_from(t.input);
            assert_eq!(parsed, t.expected);
        }
    }

    #[test]
    fn obfuscation() {
        const INPUT: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        let mut payload = INPUT.to_vec();

        let xor_key = XorKey::try_from([0xff, 0x04, 0x02, 0x04, 0x00, 0x00]).unwrap();

        let mut xor_obfuscator = XorObfuscator::new(xor_key);
        let mut xor_deobfuscator = XorObfuscator::new(xor_key);

        xor_obfuscator.obfuscate(&mut payload);

        assert_eq!(
            payload,
            &[0xfe, 0x06, 0x01, 0x00, 0xfa, 0x02, 0x05, 0x0c, 0xf6, 0x0e]
        );

        xor_deobfuscator.obfuscate(&mut payload);
        assert_eq!(INPUT, payload.as_slice());
    }

    // Before XOR-v2 there was XOR-v1, which is now deprecated. This test verifies that the old Xor
    // config does not deserialize.
    #[test]
    fn old_xor_addr() {
        match ProxyConfig::try_from(
            "2001:200:7f00:1:3905:0102:304:506"
                .parse::<Ipv6Addr>()
                .unwrap(),
        ) {
            Err(Error::XorV1Unsupported) => (),
            anything_else => panic!("Unexpected proxy config parse result: {anything_else:?}"),
        }
    }

    #[test]
    fn xor_key_debug_fmt() {
        let key = XorKey::try_from([0x01, 0xff, 0x31, 0x00, 0x00, 0x00]).unwrap();
        let key_str = format!("{key:?}");
        assert_eq!(key_str, "0x01ff31");
    }
}