Skip to content

Commit

Permalink
feat!: update key manager hasher labels (#6329)
Browse files Browse the repository at this point in the history
Description
---
Updates key manager hasher labels used in cipher seeds. Improves the
ordering of MAC input data.

Closes #6328.

Motivation and Context
---
The hasher labels used for cipher seeds are vaguely named, which seems
like an unnecessary footgun. This PR updates them, incrementing to a new
cipher seed version.

We also take the opportunity of a new version to reorganize how cipher
seed MAC input data is ordered; specifically, we move the version byte
to the front, which is more in line with its use elsewhere. This doesn't
impose any particular security concerns (the MAC input data is never
directly parsed), but had a bad smell.

How Has This Been Tested?
---
Tests pass.

What process can a PR reviewer use to test or verify this change?
---
Check that the naming change and reordering do what they say on the tin.

Breaking Changes
---
Because the library only supports the most recent cipher seed version,
existing cipher seeds will fail to decrypt.

BREAKING CHANGE: Changes the construction of cipher seeds via a new
version; older cipher seeds will fail to decrypt.
  • Loading branch information
AaronFeickert committed May 9, 2024
1 parent f851125 commit ae63bab
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 22 deletions.
36 changes: 20 additions & 16 deletions base_layer/key_manager/src/cipher_seed.rs
Expand Up @@ -46,17 +46,18 @@ use crate::{
CipherSeedMacKey,
KeyManagerDomain,
SeedWords,
LABEL_ARGON_ENCODING,
LABEL_CHACHA20_ENCODING,
LABEL_MAC_GENERATION,
HASHER_LABEL_CIPHER_SEED_ENCRYPTION_NONCE,
HASHER_LABEL_CIPHER_SEED_MAC,
HASHER_LABEL_CIPHER_SEED_PBKDF_SALT,
};

// The version should be incremented for any breaking change to the format
// NOTE: Only the most recent version is supported!
// History:
// 0: initial version
// 1: fixed incorrect key derivation and birthday genesis
const CIPHER_SEED_VERSION: u8 = 1u8;
// 2: updated hasher domain labels and MAC input ordering
const CIPHER_SEED_VERSION: u8 = 2u8;

pub const BIRTHDAY_GENESIS_FROM_UNIX_EPOCH: u64 = 1640995200; // seconds to 2022-01-01 00:00:00 UTC
pub const DEFAULT_CIPHER_SEED_PASSPHRASE: &str = "TARI_CIPHER_SEED"; // the default passphrase if none is supplied
Expand Down Expand Up @@ -181,9 +182,9 @@ impl CipherSeed {

// Generate the MAC
let mac = Self::generate_mac(
CIPHER_SEED_VERSION,
&self.birthday.to_le_bytes(),
self.entropy.as_ref(),
CIPHER_SEED_VERSION,
self.salt.as_ref(),
&mac_key,
)?;
Expand Down Expand Up @@ -280,7 +281,7 @@ impl CipherSeed {
let birthday = u16::from_le_bytes(birthday_bytes);

// Generate the MAC
let expected_mac = Self::generate_mac(&birthday_bytes, entropy.reveal(), version, salt.as_ref(), &mac_key)?;
let expected_mac = Self::generate_mac(version, &birthday_bytes, entropy.reveal(), salt.as_ref(), &mac_key)?;

// Verify the MAC in constant time to avoid leaking data
if mac.ct_eq(&expected_mac).into() {
Expand All @@ -302,10 +303,11 @@ impl CipherSeed {
salt: &[u8],
) -> Result<(), KeyManagerError> {
// The ChaCha20 nonce is derived from the main salt
let encryption_nonce =
DomainSeparatedHasher::<Blake2b<U32>, KeyManagerDomain>::new_with_label(LABEL_CHACHA20_ENCODING)
.chain(salt)
.finalize();
let encryption_nonce = DomainSeparatedHasher::<Blake2b<U32>, KeyManagerDomain>::new_with_label(
HASHER_LABEL_CIPHER_SEED_ENCRYPTION_NONCE,
)
.chain(salt)
.finalize();
let encryption_nonce = &encryption_nonce.as_ref()[..size_of::<Nonce>()];

// Encrypt/decrypt the data
Expand All @@ -332,9 +334,9 @@ impl CipherSeed {

/// Generate a MAC using Blake2b
fn generate_mac(
version: u8,
birthday: &[u8],
entropy: &[u8],
cipher_seed_version: u8,
salt: &[u8],
mac_key: &CipherSeedMacKey,
) -> Result<Vec<u8>, KeyManagerError> {
Expand All @@ -350,10 +352,10 @@ impl CipherSeed {
}

Ok(
DomainSeparatedHasher::<Blake2b<U32>, KeyManagerDomain>::new_with_label(LABEL_MAC_GENERATION)
DomainSeparatedHasher::<Blake2b<U32>, KeyManagerDomain>::new_with_label(HASHER_LABEL_CIPHER_SEED_MAC)
.chain([version])
.chain(birthday)
.chain(entropy)
.chain([cipher_seed_version])
.chain(salt)
.chain(mac_key.reveal())
.finalize()
Expand All @@ -365,9 +367,11 @@ impl CipherSeed {
/// Use Argon2 to derive encryption and MAC keys from a passphrase and main salt
fn derive_keys(passphrase: &SafePassword, salt: &[u8]) -> DerivedCipherSeedKeys {
// The Argon2 salt is derived from the main salt
let argon2_salt = DomainSeparatedHasher::<Blake2b<U32>, KeyManagerDomain>::new_with_label(LABEL_ARGON_ENCODING)
.chain(salt)
.finalize();
let argon2_salt = DomainSeparatedHasher::<Blake2b<U32>, KeyManagerDomain>::new_with_label(
HASHER_LABEL_CIPHER_SEED_PBKDF_SALT,
)
.chain(salt)
.finalize();
let argon2_salt = &argon2_salt.as_ref()[..ARGON2_SALT_BYTES];

// Run Argon2 with enough output to accommodate both keys, so we only run it once
Expand Down
4 changes: 2 additions & 2 deletions base_layer/key_manager/src/key_manager.rs
Expand Up @@ -32,7 +32,7 @@ use tari_crypto::{
};
use zeroize::Zeroize;

use crate::{cipher_seed::CipherSeed, KeyManagerDomain, LABEL_DERIVE_KEY};
use crate::{cipher_seed::CipherSeed, KeyManagerDomain, HASHER_LABEL_DERIVE_KEY};

#[derive(Clone, Derivative, Serialize, Deserialize, Zeroize)]
#[derivative(Debug)]
Expand Down Expand Up @@ -102,7 +102,7 @@ where
// apply domain separation to generate derive key. Under the hood, the hashing api prepends the length of each
// piece of data for concatenation, reducing the risk of collisions due to redundancy of variable length
// input
let derive_key = DomainSeparatedHasher::<D, KeyManagerDomain>::new_with_label(LABEL_DERIVE_KEY)
let derive_key = DomainSeparatedHasher::<D, KeyManagerDomain>::new_with_label(HASHER_LABEL_DERIVE_KEY)
.chain(self.seed.entropy())
.chain(self.branch_seed.as_bytes())
.chain(key_index.to_le_bytes())
Expand Down
8 changes: 4 additions & 4 deletions base_layer/key_manager/src/lib.rs
Expand Up @@ -26,10 +26,10 @@ pub mod schema;

hash_domain!(KeyManagerDomain, "com.tari.base_layer.key_manager", 1);

const LABEL_ARGON_ENCODING: &str = "argon2_encoding";
const LABEL_CHACHA20_ENCODING: &str = "chacha20_encoding";
const LABEL_MAC_GENERATION: &str = "mac_generation";
const LABEL_DERIVE_KEY: &str = "derive_key";
const HASHER_LABEL_CIPHER_SEED_PBKDF_SALT: &str = "cipher_seed_pbkdf_salt";
const HASHER_LABEL_CIPHER_SEED_ENCRYPTION_NONCE: &str = "cipher_seed_encryption_nonce";
const HASHER_LABEL_CIPHER_SEED_MAC: &str = "cipher_seed_mac";
const HASHER_LABEL_DERIVE_KEY: &str = "derive_key";

hidden_type!(CipherSeedEncryptionKey, SafeArray<u8, CIPHER_SEED_ENCRYPTION_KEY_BYTES>);
hidden_type!(CipherSeedMacKey, SafeArray< u8, CIPHER_SEED_MAC_KEY_BYTES>);
Expand Down

0 comments on commit ae63bab

Please sign in to comment.