1use crate::error::{Error, Result};
12use std::net::{IpAddr, Ipv4Addr, SocketAddr};
13
14pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
18 let mut diff = (a.len() ^ b.len()) as u8;
19 for i in 0..a.len().max(b.len()) {
20 diff |= a.get(i).copied().unwrap_or(0) ^ b.get(i).copied().unwrap_or(0);
21 }
22 std::hint::black_box(diff) == 0
23}
24
25pub fn random_bytes(len: usize) -> Result<Vec<u8>> {
27 let mut buf = vec![0u8; len];
28 getrandom::fill(&mut buf)
29 .map_err(|e| Error::internal(format!("Random number generator unavailable: {}", e)))?;
30 Ok(buf)
31}
32
33pub fn random_token(bytes: usize) -> Result<String> {
36 let mut out = String::with_capacity(bytes * 2);
37 for b in random_bytes(bytes)? {
38 out.push(char::from_digit(u32::from(b >> 4), 16).unwrap_or('0'));
39 out.push(char::from_digit(u32::from(b & 0xf), 16).unwrap_or('0'));
40 }
41 Ok(out)
42}
43
44pub fn random_alphanumeric(len: usize) -> Result<String> {
48 const ALPHABET: &[u8; 62] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
49 let mut out = String::with_capacity(len);
50 while out.len() < len {
51 for b in random_bytes(len.max(8))? {
52 if b < 248 && out.len() < len {
54 out.push(ALPHABET[usize::from(b) % 62] as char);
55 }
56 }
57 }
58 Ok(out)
59}
60
61pub fn is_public_ip(ip: IpAddr) -> bool {
66 fn public_v4(v4: Ipv4Addr) -> bool {
67 let o = v4.octets();
68 !(v4.is_loopback()
69 || v4.is_private()
70 || v4.is_link_local()
71 || v4.is_unspecified()
72 || v4.is_broadcast()
73 || v4.is_multicast()
74 || v4.is_documentation()
75 || o[0] == 0 || o[0] >= 240 || (o[0] == 100 && (64..128).contains(&o[1])) || (o[0] == 198 && (o[1] == 18 || o[1] == 19)) || (o[0] == 192 && o[1] == 0 && o[2] == 0)) }
81 match ip {
82 IpAddr::V4(v4) => public_v4(v4),
83 IpAddr::V6(v6) => {
84 let seg = v6.segments();
85 let embedded =
86 |a: u16, b: u16| Ipv4Addr::new((a >> 8) as u8, a as u8, (b >> 8) as u8, b as u8);
87 if let Some(v4) = v6.to_ipv4_mapped() {
88 return public_v4(v4);
89 }
90 if seg[..6] == [0x64, 0xff9b, 0, 0, 0, 0] {
92 return public_v4(embedded(seg[6], seg[7]));
93 }
94 if seg[0] == 0x2002 {
96 return public_v4(embedded(seg[1], seg[2]));
97 }
98 if seg[..6] == [0, 0, 0, 0, 0, 0] {
100 return false;
101 }
102 !(v6.is_loopback()
103 || v6.is_unspecified()
104 || v6.is_multicast()
105 || (seg[0] & 0xfe00) == 0xfc00 || (seg[0] & 0xffc0) == 0xfe80 || (seg[0] == 0x2001 && seg[1] == 0x0db8)) }
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
114pub enum ResolveError {
115 Resolution,
117 NotPublic(IpAddr),
119}
120
121impl std::fmt::Display for ResolveError {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 match self {
124 ResolveError::Resolution => f.write_str("name resolution failed"),
125 ResolveError::NotPublic(ip) => write!(f, "target address {} is not public", ip),
126 }
127 }
128}
129
130impl std::error::Error for ResolveError {}
131
132pub async fn resolve_public(
147 host: &str,
148 port: u16,
149 allow_private: bool,
150) -> std::result::Result<SocketAddr, ResolveError> {
151 let addrs: Vec<SocketAddr> = tokio::net::lookup_host((host, port))
152 .await
153 .map_err(|_| ResolveError::Resolution)?
154 .collect();
155 let first = addrs.first().copied().ok_or(ResolveError::Resolution)?;
156 if !allow_private {
157 if let Some(bad) = addrs.iter().find(|a| !is_public_ip(a.ip())) {
158 return Err(ResolveError::NotPublic(bad.ip()));
159 }
160 }
161 Ok(first)
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use std::collections::HashSet;
168
169 #[test]
170 fn constant_time_eq_semantics() {
171 assert!(constant_time_eq(b"", b""));
172 assert!(constant_time_eq(b"secret", b"secret"));
173 assert!(!constant_time_eq(b"secret", b"secreT"));
174 assert!(!constant_time_eq(b"secret", b"secre"));
175 assert!(!constant_time_eq(b"", b"x"));
176 assert!(!constant_time_eq(b"abc", b"abc\0"));
178 }
179
180 #[test]
181 fn public_and_non_public_addresses() {
182 let ip = |s: &str| s.parse::<IpAddr>().unwrap();
183 for public in [
184 "8.8.8.8",
185 "1.1.1.1",
186 "93.184.216.34",
187 "172.32.0.1",
188 "100.63.255.255",
189 "100.128.0.1",
190 "198.17.0.1",
191 "198.20.0.1",
192 "2606:4700:4700::1111",
193 "2a00:1450:4001::1",
194 "64:ff9b::808:808",
195 "2002:0808:0808::1",
196 "::ffff:8.8.8.8",
197 ] {
198 assert!(is_public_ip(ip(public)), "{public} should be public");
199 }
200 for private in [
201 "127.0.0.1",
202 "127.255.255.254",
203 "10.0.0.1",
204 "172.16.0.1",
205 "172.31.255.255",
206 "192.168.1.1",
207 "169.254.169.254",
208 "0.0.0.0",
209 "0.1.2.3",
210 "255.255.255.255",
211 "100.64.0.1",
212 "100.127.255.255",
213 "224.0.0.1",
214 "239.255.255.255",
215 "240.0.0.1",
216 "192.0.2.1",
217 "198.51.100.1",
218 "203.0.113.1",
219 "198.18.0.1",
220 "198.19.255.255",
221 "192.0.0.1",
222 "::1",
223 "::",
224 "::1234",
225 "fe80::1",
226 "fd00::1",
227 "fc00::1",
228 "ff02::1",
229 "2001:db8::1",
230 "::ffff:127.0.0.1",
231 "::ffff:10.0.0.1",
232 "::ffff:169.254.169.254",
233 "64:ff9b::7f00:1",
234 "64:ff9b::a00:1",
235 "2002:7f00:1::1",
236 "2002:c0a8:101::1",
237 ] {
238 assert!(!is_public_ip(ip(private)), "{private} should not be public");
239 }
240 }
241
242 #[tokio::test]
243 async fn resolve_public_vets_literals_and_names() {
244 assert_eq!(
245 resolve_public("93.184.216.34", 443, false).await,
246 Ok("93.184.216.34:443".parse().unwrap())
247 );
248 assert_eq!(
249 resolve_public("127.0.0.1", 80, false).await,
250 Err(ResolveError::NotPublic("127.0.0.1".parse().unwrap()))
251 );
252 assert_eq!(
253 resolve_public("169.254.169.254", 80, false)
254 .await
255 .unwrap_err()
256 .to_string(),
257 "target address 169.254.169.254 is not public"
258 );
259 assert!(resolve_public("[::1]", 80, false).await.is_err());
262 assert_eq!(
263 resolve_public("::1", 80, false).await,
264 Err(ResolveError::NotPublic("::1".parse().unwrap()))
265 );
266 assert_eq!(
268 resolve_public("127.0.0.1", 80, true).await,
269 Ok("127.0.0.1:80".parse().unwrap())
270 );
271 assert_eq!(
273 resolve_public("no-such-host.invalid", 80, false).await,
274 Err(ResolveError::Resolution)
275 );
276 assert!(matches!(
278 resolve_public("localhost", 80, false).await,
279 Err(ResolveError::NotPublic(_))
280 ));
281 }
282
283 #[test]
284 fn token_shape_and_uniqueness() {
285 let tokens: HashSet<String> = (0..200).map(|_| random_token(16).unwrap()).collect();
286 assert_eq!(tokens.len(), 200);
287 assert!(tokens.iter().all(|t| t.len() == 32
288 && t.bytes()
289 .all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase())));
290 assert_eq!(random_token(0).unwrap(), "");
291 }
292
293 #[test]
294 fn alphanumeric_shape_and_coverage() {
295 let s = random_alphanumeric(2000).unwrap();
296 assert_eq!(s.len(), 2000);
297 assert!(s.bytes().all(|b| b.is_ascii_alphanumeric()));
298 let distinct: HashSet<u8> = s.bytes().collect();
300 assert_eq!(distinct.len(), 62);
301 assert_eq!(random_alphanumeric(0).unwrap(), "");
302 }
303}