vrl/stdlib/
decrypt_ip.rs

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}