1use crate::compiler::prelude::*;
2use crate::stdlib::ip_utils::to_key;
3use ipcrypt_rs::{Ipcrypt, IpcryptPfx};
4use std::net::IpAddr;
5
6fn decrypt_ip(ip: &Value, key: Value, mode: &Value) -> Resolved {
7 let ip_str = ip.try_bytes_utf8_lossy()?;
8 let ip_addr: IpAddr = ip_str
9 .parse()
10 .map_err(|err| format!("unable to parse IP address: {err}"))?;
11
12 let mode_str = mode.try_bytes_utf8_lossy()?;
13
14 let ip_ver_label = match ip_addr {
15 IpAddr::V4(_) => "IPv4",
16 IpAddr::V6(_) => "IPv6",
17 };
18
19 let decrypted_ip = match mode_str.as_ref() {
20 "aes128" => match ip_addr {
21 IpAddr::V4(ipv4) => {
22 let key = to_key::<16>(key, "aes128", ip_ver_label)?;
23 let ipcrypt = Ipcrypt::new(key);
24 ipcrypt.decrypt_ipaddr(IpAddr::V4(ipv4))
25 }
26 IpAddr::V6(ipv6) => {
27 let key = to_key::<16>(key, "aes128", ip_ver_label)?;
28 let ipcrypt = Ipcrypt::new(key);
29 ipcrypt.decrypt_ipaddr(IpAddr::V6(ipv6))
30 }
31 },
32 "pfx" => match ip_addr {
33 IpAddr::V4(ipv4) => {
34 let key = to_key::<32>(key, "pfx", ip_ver_label)?;
35 let ipcrypt_pfx = IpcryptPfx::new(key);
36 ipcrypt_pfx.decrypt_ipaddr(IpAddr::V4(ipv4))
37 }
38 IpAddr::V6(ipv6) => {
39 let key = to_key::<32>(key, "pfx", ip_ver_label)?;
40 let ipcrypt_pfx = IpcryptPfx::new(key);
41 ipcrypt_pfx.decrypt_ipaddr(IpAddr::V6(ipv6))
42 }
43 },
44 _ => {
45 return Err(format!("Invalid mode '{mode_str}'. Must be 'aes128' or 'pfx'").into());
46 }
47 };
48
49 Ok(decrypted_ip.to_string().into())
50}
51
52#[derive(Clone, Copy, Debug)]
53pub struct DecryptIp;
54
55impl Function for DecryptIp {
56 fn identifier(&self) -> &'static str {
57 "decrypt_ip"
58 }
59
60 fn usage(&self) -> &'static str {
61 indoc! {"
62 Decrypts an IP address that was previously encrypted, restoring the original IP address.
63
64 Supported Modes:
65
66 * AES128 - Decrypts an IP address that was scrambled using AES-128 encryption. Can transform between IPv4 and IPv6.
67 * PFX (Prefix-preserving) - Decrypts an IP address that was encrypted with prefix-preserving mode, where network hierarchy was maintained.
68 "}
69 }
70
71 fn category(&self) -> &'static str {
72 Category::Ip.as_ref()
73 }
74
75 fn internal_failure_reasons(&self) -> &'static [&'static str] {
76 &[
77 "`ip` is not a valid IP address.",
78 "`mode` is not a supported mode (must be `aes128` or `pfx`).",
79 "`key` length does not match the requirements for the specified mode (16 bytes for `aes128`, 32 bytes for `pfx`).",
80 ]
81 }
82
83 fn return_kind(&self) -> u16 {
84 kind::BYTES
85 }
86
87 fn notices(&self) -> &'static [&'static str] {
88 &[indoc! {"
89 The `aes128` mode implements the `ipcrypt-deterministic` algorithm from the IPCrypt
90 specification, while the `pfx` mode implements the `ipcrypt-pfx` algorithm. This
91 function reverses the encryption performed by `encrypt_ip` - the same key and algorithm
92 that were used for encryption must be used for decryption.
93 "}]
94 }
95
96 fn parameters(&self) -> &'static [Parameter] {
97 const PARAMETERS: &[Parameter] = &[
98 Parameter::required(
99 "ip",
100 kind::BYTES,
101 "The encrypted IP address to decrypt (v4 or v6).",
102 ),
103 Parameter::required(
104 "key",
105 kind::BYTES,
106 "The decryption key in raw bytes (not encoded). Must be the same key that was used for encryption. For AES128 mode, the key must be exactly 16 bytes. For PFX mode, the key must be exactly 32 bytes.",
107 ),
108 Parameter::required(
109 "mode",
110 kind::BYTES,
111 "The decryption mode to use. Must match the mode used for encryption: either `aes128` or `pfx`.",
112 ),
113 ];
114 PARAMETERS
115 }
116
117 fn examples(&self) -> &'static [Example] {
118 &[
119 example! {
120 title: "Decrypt IPv4 address with AES128",
121 source: r#"decrypt_ip!("72b9:a747:f2e9:72af:76ca:5866:6dcf:c3b0", "sixteen byte key", "aes128")"#,
122 result: Ok("192.168.1.1"),
123 },
124 example! {
125 title: "Decrypt IPv6 address with AES128",
126 source: r#"decrypt_ip!("c0e6:eb35:6887:f554:4c65:8ace:17ca:6c6a", "sixteen byte key", "aes128")"#,
127 result: Ok("2001:db8::1"),
128 },
129 example! {
130 title: "Decrypt IPv4 address with prefix-preserving mode",
131 source: r#"decrypt_ip!("33.245.248.61", "thirty-two bytes key for pfx use", "pfx")"#,
132 result: Ok("192.168.1.1"),
133 },
134 example! {
135 title: "Decrypt IPv6 address with prefix-preserving mode",
136 source: r#"decrypt_ip!("88bd:d2bf:8865:8c4d:84b:44f6:6077:72c9", "thirty-two bytes key for ipv6pfx", "pfx")"#,
137 result: Ok("2001:db8::1"),
138 },
139 example! {
140 title: "Round-trip encryption and decryption",
141 source: indoc! {r#"
142 original_ip = "192.168.1.100"
143 key = "sixteen byte key"
144 mode = "aes128"
145
146 encrypted = encrypt_ip!(original_ip, key, mode)
147 decrypt_ip!(encrypted, key, mode)
148 "#},
149 result: Ok("192.168.1.100"),
150 },
151 ]
152 }
153
154 fn compile(
155 &self,
156 _state: &TypeState,
157 _ctx: &mut FunctionCompileContext,
158 arguments: ArgumentList,
159 ) -> Compiled {
160 let ip = arguments.required("ip");
161 let key = arguments.required("key");
162 let mode = arguments.required("mode");
163
164 Ok(DecryptIpFn { ip, key, mode }.as_expr())
165 }
166}
167
168#[derive(Debug, Clone)]
169struct DecryptIpFn {
170 ip: Box<dyn Expression>,
171 key: Box<dyn Expression>,
172 mode: Box<dyn Expression>,
173}
174
175impl FunctionExpression for DecryptIpFn {
176 fn resolve(&self, ctx: &mut Context) -> Resolved {
177 let ip = self.ip.resolve(ctx)?;
178 let key = self.key.resolve(ctx)?;
179 let mode = self.mode.resolve(ctx)?;
180 decrypt_ip(&ip, key, &mode)
181 }
182
183 fn type_def(&self, _: &TypeState) -> TypeDef {
184 TypeDef::bytes().fallible()
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::value;
192
193 test_function![
194 decrypt_ip => DecryptIp;
195
196 ipv4_aes128 {
197 args: func_args![
198 ip: "a6d8:a149:6bcf:b175:bad6:3e56:d72d:4fdb",
199 key: value!(b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"),
200 mode: "aes128"
201 ],
202 want: Ok(value!("192.168.1.1")),
203 tdef: TypeDef::bytes().fallible(),
204 }
205
206 ipv4_pfx {
207 args: func_args![
208 ip: "194.20.195.96",
209 key: value!(b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"),
210 mode: "pfx"
211 ],
212 want: Ok(value!("192.168.1.1")),
213 tdef: TypeDef::bytes().fallible(),
214 }
215
216 invalid_mode {
217 args: func_args![
218 ip: "192.168.1.1",
219 key: value!(b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"),
220 mode: "invalid"
221 ],
222 want: Err("Invalid mode 'invalid'. Must be 'aes128' or 'pfx'"),
223 tdef: TypeDef::bytes().fallible(),
224 }
225
226 invalid_ip {
227 args: func_args![
228 ip: "not an ip",
229 key: value!(b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"),
230 mode: "aes128"
231 ],
232 want: Err("unable to parse IP address: invalid IP address syntax"),
233 tdef: TypeDef::bytes().fallible(),
234 }
235
236 invalid_key_size_ipv4_aes128 {
237 args: func_args![
238 ip: "192.168.1.1",
239 key: value!(b"short"),
240 mode: "aes128"
241 ],
242 want: Err("aes128 mode requires a 16-byte key for IPv4"),
243 tdef: TypeDef::bytes().fallible(),
244 }
245
246 invalid_key_size_ipv4_pfx {
247 args: func_args![
248 ip: "192.168.1.1",
249 key: value!(b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"),
250 mode: "pfx"
251 ],
252 want: Err("pfx mode requires a 32-byte key for IPv4"),
253 tdef: TypeDef::bytes().fallible(),
254 }
255 ];
256}