diem_types/network_address/encrypted.rs
1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4// Copyright 2021 Conflux Foundation. All rights reserved.
5// Conflux is free software and distributed under GNU General Public License.
6// See http://www.gnu.org/licenses/
7
8use crate::{
9 account_address::AccountAddress,
10 network_address::{NetworkAddress, ParseError},
11};
12use aes_gcm::{
13 aead::{generic_array::GenericArray, AeadInPlace},
14 Aes256Gcm, KeyInit,
15};
16use diem_crypto::{compat::Sha3_256, hkdf::Hkdf};
17#[cfg(any(test, feature = "fuzzing"))]
18use proptest::prelude::*;
19use serde::{Deserialize, Serialize};
20use std::mem;
21
22/// The length in bytes of the AES-256-GCM authentication tag.
23pub const AES_GCM_TAG_LEN: usize = 16;
24
25/// The length in bytes of the AES-256-GCM nonce.
26pub const AES_GCM_NONCE_LEN: usize = 12;
27
28/// The length in bytes of the `shared_val_netaddr_key` and per-validator
29/// `derived_key`.
30pub const KEY_LEN: usize = 32;
31
32/// Convenient type alias for the `shared_val_netaddr_key` as an array.
33pub type Key = [u8; KEY_LEN];
34pub type KeyVersion = u32;
35
36/// Constant key + version so we can push `EncNetworkAddress` everywhere
37/// without worrying about getting the key in the right places. these will be
38/// test-only soon.
39// TODO(philiphayes): feature gate for testing/fuzzing only
40pub const TEST_SHARED_VAL_NETADDR_KEY: Key = [0u8; KEY_LEN];
41pub const TEST_SHARED_VAL_NETADDR_KEY_VERSION: KeyVersion = 0;
42
43/// We salt the HKDF for deriving the account keys to provide application
44/// separation.
45///
46/// Note: modifying this salt is a backwards-incompatible protocol change.
47///
48/// For readers, the HKDF salt is equal to the following hex string:
49/// `"7ffda2ae982a2ebfab2a4da62f76fe33592c85e02445b875f02ded51a520ba2a"` which
50/// is also equal to the hash value
51/// `SHA3-256(b"DIEM_ENCRYPTED_NETWORK_ADDRESS_SALT")`.
52///
53/// ```
54/// use diem_crypto::hash::HashValue;
55/// use diem_types::network_address::encrypted::HKDF_SALT;
56///
57/// let derived_salt =
58/// HashValue::sha3_256_of(b"DIEM_ENCRYPTED_NETWORK_ADDRESS_SALT");
59/// assert_eq!(HKDF_SALT.as_ref(), derived_salt.as_ref());
60/// ```
61pub const HKDF_SALT: [u8; 32] = [
62 0x7f, 0xfd, 0xa2, 0xae, 0x98, 0x2a, 0x2e, 0xbf, 0xab, 0x2a, 0x4d, 0xa6,
63 0x2f, 0x76, 0xfe, 0x33, 0x59, 0x2c, 0x85, 0xe0, 0x24, 0x45, 0xb8, 0x75,
64 0xf0, 0x2d, 0xed, 0x51, 0xa5, 0x20, 0xba, 0x2a,
65];
66
67/// An encrypted [`NetworkAddress`].
68///
69/// ### Threat Model
70///
71/// Encrypting the on-chain network addresses is purely a defense-in-depth
72/// mitigation to minimize attack surface and reduce DDoS attacks on the
73/// validators by restricting the visibility of their public-facing network
74/// addresses only to other validators.
75///
76/// These encrypted network addresses are intended to be stored on-chain under
77/// each validator's advertised network addresses in their [`ValidatorConfig`]s.
78/// All validators share the secret `shared_val_netaddr_key`, though each
79/// validator's addresses are encrypted using a per-validator `derived_key`.
80///
81/// ### Account Key
82///
83/// ```txt
84/// derived_key := HKDF-SHA3-256::extract_and_expand(
85/// salt=HKDF_SALT,
86/// ikm=shared_val_netaddr_key,
87/// info=account_address,
88/// output_length=32,
89/// )
90/// ```
91///
92/// where `HKDF-SHA3-256::extract_and_expand` is
93/// [HKDF extract-and-expand](https://tools.ietf.org/html/rfc5869) with SHA3-256,
94/// [`HKDF_SALT`] is a constant salt for application separation,
95/// `shared_val_netaddr_key` is the shared secret distributed amongst all the
96/// validators, and `account_address` is the specific validator's
97/// [`AccountAddress`].
98///
99/// We use per-validator `derived_key`s to limit the "blast radius" of
100/// nonce reuse to each validator, i.e., a validator that accidentally reuses a
101/// nonce will only leak information about their network addresses or
102/// `derived_key`.
103///
104/// ### Encryption
105///
106/// A raw network address, `addr`, is then encrypted using AES-256-GCM like:
107///
108/// ```txt
109/// enc_addr := AES-256-GCM::encrypt(
110/// key=derived_key,
111/// nonce=nonce,
112/// ad=key_version,
113/// message=addr,
114/// )
115/// ```
116///
117/// where `nonce` is a 96-bit integer as described below, `key_version` is
118/// the key version as a u32 big-endian integer, `addr` is the serialized
119/// [`NetworkAddress`], and `enc_addr` is the encrypted network address
120/// concatenated with the 16-byte authentication tag.
121///
122/// ### Nonce
123///
124/// ```txt
125/// nonce := seq_num || addr_idx
126/// ```
127///
128/// where `seq_num` is the `seq_num` field as a u64 big-endian integer and
129/// `addr_idx` is the index of the encrypted network address in the list of
130/// network addresses as a u32 big-endian integer.
131///
132/// ### Sequence Number
133///
134/// In order to reduce the probability of nonce reuse, validators should use the
135/// sequence number of the rotation transaction in the `seq_num` field.
136///
137/// ### Key Rotation
138///
139/// The `EncNetworkAddress` struct contains a `key_version` field, which
140/// identifies the specific `shared_val_netaddr_key` used to encrypt/decrypt the
141/// `EncNetworkAddress`.
142///
143/// [`ValidatorConfig`]: https://github.com/diem/diem/blob/main/language/diem-framework/modules/doc/ValidatorConfig.md
144#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
145pub struct EncNetworkAddress {
146 key_version: KeyVersion,
147 seq_num: u64,
148 #[serde(with = "serde_bytes")]
149 enc_addr: Vec<u8>,
150}
151
152///////////////////////
153// EncNetworkAddress //
154///////////////////////
155
156impl EncNetworkAddress {
157 /// ### Panics
158 ///
159 /// encrypt will panic if `addr` length > 64 GiB.
160 pub fn encrypt(
161 addr: NetworkAddress, shared_val_netaddr_key: &Key,
162 key_version: KeyVersion, account: &AccountAddress, seq_num: u64,
163 addr_idx: u32,
164 ) -> Result<Self, ParseError> {
165 // unpack the NetworkAddress into its base Vec<u8>
166 let mut addr_vec: Vec<u8> = bcs::to_bytes(&addr)?;
167
168 let derived_key = Self::derive_key(shared_val_netaddr_key, account);
169 let aead = Aes256Gcm::new(GenericArray::from_slice(&derived_key));
170
171 // nonce := seq_num || addr_idx
172 //
173 // concatenate seq_num and addr_idx into a 12-byte AES-GCM nonce. both
174 // seq_num and addr_idx are big-endian integers.
175 //
176 // ex: seq_num = 0x1234, addr_idx = 0x04
177 // ==> nonce_slice == &[0, 0, 0, 0, 0, 0, 0x12, 0x34, 0, 0, 0, 0x4]
178 let nonce =
179 (((seq_num as u128) << 32) | (addr_idx as u128)).to_be_bytes();
180 let nonce_slice = &nonce[mem::size_of::<u128>() - AES_GCM_NONCE_LEN..];
181 let nonce_slice = GenericArray::from_slice(nonce_slice);
182
183 // the key_version is in-the-clear, so we include it in the integrity
184 // check using the "associated data"
185 let ad_buf = key_version.to_be_bytes();
186 let ad_slice = &ad_buf[..];
187
188 // encrypt the raw network address in-place
189 // note: this can technically panic if the serialized network address
190 // length is > 64 GiB
191 let auth_tag = aead
192 .encrypt_in_place_detached(nonce_slice, ad_slice, &mut addr_vec)
193 .expect("addr.len() must be <= 64 GiB");
194
195 // append the authentication tag
196 addr_vec.extend_from_slice(auth_tag.as_slice());
197
198 Ok(Self {
199 key_version,
200 seq_num,
201 enc_addr: addr_vec,
202 })
203 }
204
205 pub fn decrypt(
206 self, shared_val_netaddr_key: &Key, account: &AccountAddress,
207 addr_idx: u32,
208 ) -> Result<NetworkAddress, ParseError> {
209 let key_version = self.key_version;
210 let seq_num = self.seq_num;
211 let mut enc_addr = self.enc_addr;
212
213 // ciphertext is too small to even contain the authentication tag, so it
214 // must be invalid.
215 if enc_addr.len() < AES_GCM_TAG_LEN {
216 return Err(ParseError::DecryptError);
217 }
218
219 let derived_key = Self::derive_key(shared_val_netaddr_key, account);
220 let aead = Aes256Gcm::new(GenericArray::from_slice(&derived_key));
221
222 // nonce := seq_num || addr_idx
223 //
224 // concatenate seq_num and addr_idx into a 12-byte AES-GCM nonce. both
225 // seq_num and addr_idx are big-endian integers.
226 //
227 // ex: seq_num = 0x1234, addr_idx = 0x04
228 // ==> nonce_slice == &[0, 0, 0, 0, 0, 0, 0x12, 0x34, 0, 0, 0, 0x4]
229 let nonce =
230 (((seq_num as u128) << 32) | (addr_idx as u128)).to_be_bytes();
231 let nonce_slice = &nonce[mem::size_of::<u128>() - AES_GCM_NONCE_LEN..];
232 let nonce_slice = GenericArray::from_slice(nonce_slice);
233
234 // the key_version is in-the-clear, so we include it in the integrity
235 // check using the "additonal data"
236 let ad_buf = key_version.to_be_bytes();
237 let ad_slice = &ad_buf[..];
238
239 // split buffer into separate ciphertext and authentication tag slices
240 let auth_tag_offset = enc_addr.len() - AES_GCM_TAG_LEN;
241 let (enc_addr_slice, auth_tag_slice) =
242 enc_addr.split_at_mut(auth_tag_offset);
243 let auth_tag_slice = GenericArray::from_slice(auth_tag_slice);
244
245 aead.decrypt_in_place_detached(
246 nonce_slice,
247 ad_slice,
248 enc_addr_slice,
249 auth_tag_slice,
250 )
251 .map_err(|_| ParseError::DecryptError)?;
252
253 // remove the auth tag suffix, leaving just the decrypted network
254 // address
255 enc_addr.truncate(auth_tag_offset);
256
257 bcs::from_bytes(&enc_addr).map_err(|e| e.into())
258 }
259
260 /// Given the shared `shared_val_netaddr_key`, derive the per-validator
261 /// `derived_key`.
262 fn derive_key(
263 shared_val_netaddr_key: &Key, account: &AccountAddress,
264 ) -> Vec<u8> {
265 let salt = Some(HKDF_SALT.as_ref());
266 let info = Some(account.as_ref());
267 Hkdf::<Sha3_256>::extract_then_expand(salt, shared_val_netaddr_key, info, KEY_LEN).expect(
268 "HKDF_SHA3_256 extract_then_expand is infallible here since all inputs \
269 have valid and well-defined lengths enforced by the type system",
270 )
271 }
272
273 pub fn key_version(&self) -> KeyVersion { self.key_version }
274
275 pub fn seq_num(&self) -> u64 { self.seq_num }
276}
277
278#[cfg(any(test, feature = "fuzzing"))]
279impl Arbitrary for EncNetworkAddress {
280 type Parameters = ();
281 type Strategy = BoxedStrategy<Self>;
282
283 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
284 let shared_val_netaddr_key = TEST_SHARED_VAL_NETADDR_KEY;
285 let key_version = TEST_SHARED_VAL_NETADDR_KEY_VERSION;
286 let account = AccountAddress::ZERO;
287 let seq_num = 0;
288 let addr_idx = 0;
289
290 any::<NetworkAddress>()
291 .prop_map(move |addr| {
292 EncNetworkAddress::encrypt(
293 addr,
294 &shared_val_netaddr_key,
295 key_version,
296 &account,
297 seq_num,
298 addr_idx,
299 )
300 .unwrap()
301 })
302 .boxed()
303 }
304}
305
306///////////
307// Tests //
308///////////
309
310#[cfg(test)]
311mod test {
312 use super::*;
313
314 // Ensure that modifying the ciphertext or associated data causes a
315 // decryption error.
316 #[test]
317 fn expect_decryption_failures() {
318 let shared_val_netaddr_key = TEST_SHARED_VAL_NETADDR_KEY;
319 let key_version = TEST_SHARED_VAL_NETADDR_KEY_VERSION;
320 let account = AccountAddress::ZERO;
321 let seq_num = 0x4589;
322 let addr_idx = 123;
323 let addr = NetworkAddress::mock();
324 let enc_addr = addr
325 .clone()
326 .encrypt(
327 &shared_val_netaddr_key,
328 key_version,
329 &account,
330 seq_num,
331 addr_idx,
332 )
333 .unwrap();
334
335 // we expect decrypting a properly encrypted address to work
336 let dec_addr = enc_addr
337 .clone()
338 .decrypt(&shared_val_netaddr_key, &account, addr_idx)
339 .unwrap();
340 assert_eq!(addr, dec_addr);
341
342 // modifying the seq_num should cause decryption failure
343 let mut malicious_enc_addr = enc_addr.clone();
344 malicious_enc_addr.seq_num = 1234;
345 malicious_enc_addr
346 .decrypt(&shared_val_netaddr_key, &account, addr_idx)
347 .unwrap_err();
348
349 // modifying the key_version should cause decryption failure
350 let mut malicious_enc_addr = enc_addr.clone();
351 malicious_enc_addr.key_version = 9999;
352 malicious_enc_addr
353 .decrypt(&shared_val_netaddr_key, &account, addr_idx)
354 .unwrap_err();
355
356 // modifying the auth_tag should cause decryption failure
357 let mut malicious_enc_addr = enc_addr.clone();
358 let buf = &mut malicious_enc_addr.enc_addr;
359 let buf_len = buf.len();
360 buf[buf_len - 1] ^= 0x55;
361 malicious_enc_addr
362 .decrypt(&shared_val_netaddr_key, &account, addr_idx)
363 .unwrap_err();
364
365 // modifying the enc_addr ciphertext should cause decryption failure
366 let mut malicious_enc_addr = enc_addr.clone();
367 malicious_enc_addr.enc_addr = vec![0x42u8; 123];
368 malicious_enc_addr
369 .decrypt(&shared_val_netaddr_key, &account, addr_idx)
370 .unwrap_err();
371
372 // modifying the account address should cause decryption failure
373 let malicious_account =
374 AccountAddress::new([0x33; AccountAddress::LENGTH]);
375 enc_addr
376 .clone()
377 .decrypt(&shared_val_netaddr_key, &malicious_account, addr_idx)
378 .unwrap_err();
379
380 // modifying the shared_val_netaddr_key should cause decryption failure
381 let malicious_shared_val_netaddr_key = [0x88; KEY_LEN];
382 enc_addr
383 .clone()
384 .decrypt(&malicious_shared_val_netaddr_key, &account, addr_idx)
385 .unwrap_err();
386
387 // modifying the addr_idx should cause decryption failure
388 let malicious_addr_idx = 999;
389 enc_addr
390 .decrypt(&shared_val_netaddr_key, &account, malicious_addr_idx)
391 .unwrap_err();
392 }
393
394 proptest! {
395 #[test]
396 fn encrypt_decrypt_roundtrip(
397 addr in any::<NetworkAddress>(),
398 ) {
399 let shared_val_netaddr_key = TEST_SHARED_VAL_NETADDR_KEY;
400 let key_version = TEST_SHARED_VAL_NETADDR_KEY_VERSION;
401 let account = AccountAddress::ZERO;
402 let seq_num = 0;
403 let addr_idx = 0;
404 let enc_addr = addr.clone().encrypt(&shared_val_netaddr_key, key_version, &account, seq_num, addr_idx);
405 let dec_addr = enc_addr.unwrap().decrypt(&shared_val_netaddr_key, &account, addr_idx);
406 assert_eq!(addr, dec_addr.unwrap());
407 }
408
409 #[test]
410 fn encrypt_decrypt_roundtrip_all_parameters(
411 shared_val_netaddr_key in any::<Key>(),
412 key_version in any::<KeyVersion>(),
413 account in any::<[u8; AccountAddress::LENGTH]>(),
414 seq_num in any::<u64>(),
415 addr_idx in any::<u32>(),
416 addr in any::<NetworkAddress>(),
417 ) {
418 let account = AccountAddress::new(account);
419 let enc_addr = addr.clone().encrypt(&shared_val_netaddr_key, key_version, &account, seq_num, addr_idx);
420 let dec_addr = enc_addr.unwrap().decrypt(&shared_val_netaddr_key, &account, addr_idx);
421 assert_eq!(addr, dec_addr.unwrap());
422 }
423 }
424}