netstack3_ip/device/opaque_iid.rs
1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Opaque interface identifier (IID) used in SLAAC.
6
7use core::fmt::{self, Debug};
8
9use hmac::Mac as _;
10use net_types::ip::{Ipv6Addr, Subnet};
11use rand::Rng;
12
13/// The length in bytes of the [`IidSecret`] secret key.
14const IID_SECRET_KEY_BYTES: usize = 32;
15
16/// A secret for generating [`OpaqueIid`]s.
17#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
18pub struct IidSecret([u8; IID_SECRET_KEY_BYTES]);
19
20impl IidSecret {
21 /// A static secret for use in tests.
22 #[cfg(any(test, feature = "testutils"))]
23 pub const ALL_ONES: Self = Self([1u8; IID_SECRET_KEY_BYTES]);
24
25 /// A static secret for use in tests.
26 #[cfg(any(test, feature = "testutils"))]
27 pub const ALL_TWOS: Self = Self([2u8; IID_SECRET_KEY_BYTES]);
28
29 /// Creates a new random secret with the provided `rng`.
30 pub fn new_random<R: Rng>(rng: &mut R) -> Self {
31 let mut bytes = [0u8; IID_SECRET_KEY_BYTES];
32 rng.fill(&mut bytes[..]);
33 Self(bytes)
34 }
35
36 /// Creates a new secret from the provided bytes.
37 pub fn new(bytes: [u8; IID_SECRET_KEY_BYTES]) -> Self {
38 Self(bytes)
39 }
40
41 /// Returns the inner bytes of the secret.
42 pub fn into_inner(self) -> [u8; IID_SECRET_KEY_BYTES] {
43 self.0
44 }
45}
46
47impl Debug for IidSecret {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 // Avoid putting the secret in logs.
50 f.write_str("IidSecret(..)")
51 }
52}
53
54type HmacSha256 = hmac::Hmac<sha2::Sha256>;
55
56/// An opaque interface identifier (IID).
57#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
58pub struct OpaqueIid(u128);
59
60impl OpaqueIid {
61 /// Computes an opaque interface identifier (IID) using the algorithm in [RFC
62 /// 7217 Section 5].
63 ///
64 /// Each argument to this function corresponds to an argument from Section 5 of
65 /// the RFC:
66 /// - `prefix` corresponds to the "Prefix" argument
67 /// - `net_iface` corresponds to the "Net_Iface" argument
68 /// - `net_id` corresponds to the "Network_ID" argument
69 /// - `nonce` corresponds to the "DAD_Counter" argument if nonce =
70 /// [`OpaqueIidNonce::DadCounter`]
71 /// - `secret_key` corresponds to the "secret_key" argument
72 ///
73 /// Callers can set `nonce` = [`OpaqueIidNonce::Random(x)`] to pass in a
74 /// randomly-generated value. This guarantees the caller similar privacy
75 /// properties as the original algorithm specified in the RFC without requiring
76 /// that they keep state in the form of a DAD count.
77 ///
78 /// For fixed inputs, the output of this function is guaranteed to be stable
79 /// across versions of this codebase.
80 ///
81 /// [RFC 7217 Section 5]: https://tools.ietf.org/html/rfc7217#section-5
82 pub fn new<IF, ID>(
83 prefix: Subnet<Ipv6Addr>,
84 net_iface: IF,
85 net_id: Option<ID>,
86 dad_counter: OpaqueIidNonce,
87 secret_key: &IidSecret,
88 ) -> Self
89 where
90 IF: AsRef<[u8]>,
91 ID: AsRef<[u8]>,
92 {
93 // OVERVIEW
94 //
95 // This algorithm is simple - use a cryptographically-secure hash-based
96 // message authentication code (HMAC). Use the `secret_key` as the HMAC's
97 // key, and the other arguments as the HMAC's input.
98 //
99 // HMACs and PRFs
100 //
101 // Per RFC 7217 Section 5, the function, "F()", must satisfy the following
102 // requirements:
103 //
104 // A pseudorandom function (PRF) that MUST NOT be computable from the
105 // outside (without knowledge of the secret key). F() MUST also be
106 // difficult to reverse, such that it resists attempts to obtain the
107 // secret_key, even when given samples of the output of F() and knowledge
108 // or control of the other input parameters. F() SHOULD produce an output
109 // of at least 64 bits. F() could be implemented as a cryptographic hash
110 // of the concatenation of each of the function parameters.
111 //
112 // For some HMACs, given an HMAC and a key, k, which is unknown to an
113 // attacker, F(p) = HMAC(k, p) is a PRF. HMAC-SHA256 is *almost certainly*
114 // one such HMAC. [1] Thus, the construction here satisfies the PRF
115 // requirement.
116 //
117 // ALGORITHM
118 //
119 // Our primary goal is to ensure that `OpaqueIid` implements a PRF. Our HMAC
120 // implements a PRF, and we just truncate its output to 128 bits and return it.
121 // [5] Thus, all we need to do is not somehow negate the HMAC's PRF property in
122 // constructing its input.
123 //
124 // A trivial way to do this is to ensure that any two distinct inputs to
125 // `OpaqueIid::new` will result in a distinct byte sequence being fed to the
126 // HMAC. We do this by feeding each input to the HMAC one at a time and, for the
127 // variable-length inputs, prefixing them with a fixed-length binary
128 // representation of their length. [6]
129 //
130 // [1] See [2]. There is some subtlety [3], however HMAC-SHA256 is used as a
131 // PRF in existing standards (e.g., [4]), and thus it is almost
132 // certainly good enough for the present purpose.
133 // [2] https://en.wikipedia.org/wiki/HMAC#Security
134 // [3] https://crypto.stackexchange.com/questions/88165/is-hmac-sha256-a-prf
135 // [4] https://tools.ietf.org/html/rfc4868
136 // [5] A PRF whose output is truncated is still a PRF. A quick proof sketch:
137 //
138 // A function is a PRF if, having been drawn uniformly at random from
139 // a larger set of functions (known as a "PRF family"), there does not
140 // exist a polynomial-time adversary who is able to distinguish the
141 // function from a random oracle [7] (a "distinguisher").
142 //
143 // Let f be a PRF. Let g(x) = truncate(f(x), N) be a truncated version
144 // of f which returns only the first N bits of f's output. Assume (by
145 // of contradiction) that g is not a PRF. Thus, there exists a
146 // distinguisher, D, for g. Given D, we can construct a new
147 // distinguisher, E, as follows: E(f) = D(g(x) = truncate(f(x), N)).
148 // Since truncate(f(x), N) is equivalent to g(x), then by definition,
149 // A(g(x) = truncate(f(x), N)) is able to distinguish its input from a
150 // random oracle. Thus, E is able to distinguish its input from a random
151 // oracle. This means that E is a distinguisher for f, which implies
152 // that f is not a PRF, which is a contradiction. Thus, g, the truncated
153 // version of f, is a PRF.
154 // [6] This representation ensures that it is always possible to reverse the
155 // encoding and decompose the encoding into the separate arguments to
156 // `OpaqueIid::new`. This implies that no two sets of inputs to
157 // `OpaqueIid::new` will ever produce the same encoding.
158 // [7] https://en.wikipedia.org/wiki/Random_oracle
159
160 fn write_u64(hmac: &mut HmacSha256, u: u64) {
161 hmac.update(&u.to_be_bytes());
162 }
163
164 fn write_usize(hmac: &mut HmacSha256, u: usize) {
165 // Write `usize` values as `u64` so that we always write the same number
166 // of bytes regardless of the platform.
167 //
168 // This `unwrap` is guaranteed not to panic unless we a) run on a 128-bit
169 // platform and, b) call `OpaqueIid::new` on a byte slice which is larger than
170 // 2^64 bytes.
171 write_u64(hmac, u.try_into().unwrap())
172 }
173
174 let IidSecret(secret_key) = secret_key;
175 let mut hmac = HmacSha256::new_from_slice(&secret_key[..]).expect("create new HmacSha256");
176
177 // Write prefix address; no need to prefix with length because this is
178 // always the same length.
179 hmac.update(&prefix.network().ipv6_bytes());
180 // Write prefix length, which is a single byte.
181 hmac.update(&[prefix.prefix()][..]);
182
183 // `net_iface` is variable length, so write its length first. We make sure
184 // to call `net_iface.as_ref()` once and then use its return value in case
185 // the `AsRef::as_ref` implementation doesn't always return the same number
186 // of bytes, which would break the security of this algorithm.
187 let net_iface = net_iface.as_ref();
188 write_usize(&mut hmac, net_iface.len());
189 hmac.update(net_iface);
190
191 // `net_id` is variable length, so write its length first. We make sure to
192 // call `net_iface.as_ref()` once and then use its return value in case the
193 // `AsRef::as_ref` implementation doesn't always return the same number of
194 // bytes, which would break the security of this algorithm.
195 if let Some(net_id) = net_id {
196 let net_id = net_id.as_ref();
197 write_usize(&mut hmac, net_id.len());
198 hmac.update(net_id);
199 } else {
200 // In a previous version of this function, `net_id` was not optional, and to
201 // signify its absence a caller would pass an empty byte slice or array. The
202 // zero length would then have been written to `hmac`. To maintain compatibility
203 // with `OpaqueIid`s generated before this change, write zero for an unspecified
204 // `net_id`.
205 write_usize(&mut hmac, 0);
206 }
207
208 write_u64(&mut hmac, dad_counter.into());
209
210 let hmac_bytes: [u8; 32] = hmac.finalize().into_bytes().into();
211 Self(u128::from_be_bytes((&hmac_bytes[..16]).try_into().unwrap()))
212 }
213
214 /// Copies the bytes from this identifier in Big Endian representation.
215 pub fn to_be_bytes(&self) -> [u8; 16] {
216 let Self(i) = self;
217 i.to_be_bytes()
218 }
219}
220
221/// Describes the value being used as the nonce for [`OpaqueIid`].
222///
223/// See the documentation on [`OpaqueIid::new`] for more info.
224#[derive(Copy, Clone, Debug)]
225#[allow(missing_docs)]
226pub enum OpaqueIidNonce {
227 DadCounter(u8),
228 Random(u64),
229}
230
231impl From<OpaqueIidNonce> for u64 {
232 fn from(nonce: OpaqueIidNonce) -> Self {
233 match nonce {
234 OpaqueIidNonce::DadCounter(count) => count.into(),
235 OpaqueIidNonce::Random(random) => random,
236 }
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use net_types::ip::Ipv6;
243
244 use super::*;
245
246 #[test]
247 fn test_generate_opaque_interface_identifier() {
248 // Default values for arguments. When testing a particular argument,
249 // these can be used for the values of the other arguments.
250 let default_prefix = Ipv6::SITE_LOCAL_UNICAST_SUBNET;
251 let default_net_iface = &[0, 1, 2];
252 let default_net_id = &[3, 4, 5];
253 let default_dad_counter = OpaqueIidNonce::DadCounter(0);
254 let default_secret_key = &IidSecret::ALL_ONES;
255
256 // Test that the same arguments produce the same output.
257 let iid0 = OpaqueIid::new(
258 default_prefix,
259 default_net_iface,
260 Some(default_net_id),
261 default_dad_counter,
262 default_secret_key,
263 );
264 let iid1 = OpaqueIid::new(
265 default_prefix,
266 default_net_iface,
267 Some(default_net_id),
268 default_dad_counter,
269 default_secret_key,
270 );
271 assert_eq!(iid0, iid1);
272
273 // Test that modifications to any byte of `net_iface` cause a change
274 // to the output.
275 let net_iface = &mut default_net_iface.clone();
276 let iid0 = OpaqueIid::new(
277 default_prefix,
278 &net_iface[..],
279 Some(default_net_id),
280 default_dad_counter,
281 default_secret_key,
282 );
283 for i in 0..net_iface.len() {
284 net_iface[i] += 1;
285 let iid1 = OpaqueIid::new(
286 default_prefix,
287 &net_iface[..],
288 Some(default_net_id),
289 default_dad_counter,
290 default_secret_key,
291 );
292 net_iface[i] -= 1;
293 assert_ne!(iid0, iid1);
294 }
295
296 // Test that modifications to any byte of `net_id` cause a change to the
297 // output.
298 let net_id = &mut default_net_id.clone();
299 let iid0 = OpaqueIid::new(
300 default_prefix,
301 default_net_iface,
302 Some(&net_id[..]),
303 default_dad_counter,
304 default_secret_key,
305 );
306 for i in 0..net_id.len() {
307 net_id[i] += 1;
308 let iid1 = OpaqueIid::new(
309 default_prefix,
310 default_net_iface,
311 Some(&net_id[..]),
312 default_dad_counter,
313 default_secret_key,
314 );
315 net_id[i] -= 1;
316 assert_ne!(iid0, iid1);
317 }
318
319 // Test that moving a byte between `net_iface` and `net_id` causes a
320 // change in the output.
321 let iid0 = OpaqueIid::new(
322 default_prefix,
323 &[0, 1, 2],
324 Some(&[3, 4, 5]),
325 default_dad_counter,
326 default_secret_key,
327 );
328 let iid1 = OpaqueIid::new(
329 default_prefix,
330 &[0, 1, 2, 3],
331 Some(&[4, 5]),
332 default_dad_counter,
333 default_secret_key,
334 );
335 let iid2 = OpaqueIid::new(
336 default_prefix,
337 &[0, 1],
338 Some(&[2, 3, 4, 5]),
339 default_dad_counter,
340 default_secret_key,
341 );
342 assert_ne!(iid0, iid1);
343 assert_ne!(iid0, iid2);
344 assert_ne!(iid1, iid2);
345
346 // Test that a change to `dad_counter` causes a change in the output.
347 let iid0 = OpaqueIid::new(
348 default_prefix,
349 default_net_iface,
350 Some(default_net_id),
351 default_dad_counter,
352 default_secret_key,
353 );
354 let iid1 = OpaqueIid::new(
355 default_prefix,
356 default_net_iface,
357 Some(default_net_id),
358 OpaqueIidNonce::DadCounter(1),
359 default_secret_key,
360 );
361 assert_ne!(iid0, iid1);
362
363 // Test that a change to `secret_key` causes a change in the output.
364 let iid0 = OpaqueIid::new(
365 default_prefix,
366 default_net_iface,
367 Some(default_net_id),
368 default_dad_counter,
369 default_secret_key,
370 );
371 let mut secret_key = default_secret_key.clone();
372 secret_key.0[0] += 1;
373 let iid1 = OpaqueIid::new(
374 default_prefix,
375 default_net_iface,
376 Some(default_net_id),
377 default_dad_counter,
378 &secret_key,
379 );
380 assert_ne!(iid0, iid1);
381 }
382
383 #[test]
384 fn test_stable_outputs() {
385 // `OpaqueIid` guarantees that it provides a stable output across codebase
386 // versions. This test case asserts that, and should not be changed!
387 const PREFIX: Subnet<Ipv6Addr> = Ipv6::SITE_LOCAL_UNICAST_SUBNET;
388 const NET_IFACE: &[u8] = &[0, 1, 2];
389 const NET_ID: Option<&[u8]> = Some(&[3, 4, 5]);
390 const DAD_COUNTER: OpaqueIidNonce = OpaqueIidNonce::DadCounter(0);
391
392 assert_eq!(
393 OpaqueIid::new(PREFIX, NET_IFACE, NET_ID, DAD_COUNTER, &IidSecret::ALL_ONES),
394 OpaqueIid(255541303695013087662815070945404751656),
395 );
396
397 assert_eq!(
398 OpaqueIid::new(PREFIX, NET_IFACE, None::<[_; 0]>, DAD_COUNTER, &IidSecret::ALL_ONES),
399 OpaqueIid(195123822206027417085233478881498908845),
400 );
401 }
402}