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}