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}