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