Skip to content
This repository has been archived by the owner on Jun 3, 2020. It is now read-only.

Commit

Permalink
yubihsm setup: use hkd32 crate to derive key hierarchy
Browse files Browse the repository at this point in the history
`hkd32` is an implementation of the same hierarchical key derivation
algorithm the KMS was previously using, which is an extracted subset of
the symmetric parts of BIP32 derivation (to the point it could
potentially be used to implement a full BIP32).

The `hkd32` crate has the advantage of using a zeroize-on-drop type for
all key material, as opposed to some of the manual zeroization this
crate was previously using. In addition, it has some richer types for
things like derivation paths, which may be potentially useful in the
future.

There is one case that deviated from the previous implementation, which
is the behavior of calling derive with an empty derivation path.
Before it would output the "chain code" derived after inputting the
`DERIVATION_VERSION`, whereas when using `hkd32` it correctly outputs
the other half of the derived key material, which is intended to be
used as a secret key.

Nothing presently calls the derivation function with an empty derivation
path, except for a test I just added today in #299. While the output for
this case differs, it has no practical impact, and if anything the
function outputting the raw chain code for the first level of the
hierarchy (which is the version number) is a sharp edge that could
potentially leak what is the root key to the entire hierarchy if it were
to be called with an empty derivation path.

`hkd32` uses a fully uniform derivation algorithm which treats the
`DERIVATION_VERSION` like any other part of the path, and therefore does
not have this sharp edge.

Test vectors for path lengths of 1, 2, and 3 all pass with the original
vectors.
  • Loading branch information
tony-iqlusion committed Jul 24, 2019
1 parent b749ea5 commit fde830b
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 49 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ bytes = "0.4"
chrono = "0.4"
failure = "0.1"
gumdrop = "0.6"
hkd32 = "0.1"
hkdf = "0.7"
hmac = "0.7"
lazy_static = "1"
Expand Down
70 changes: 21 additions & 49 deletions src/commands/yubihsm/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use crate::prelude::*;
use abscissa_core::{Command, Runnable};
use bip39::Mnemonic;
use chrono::{SecondsFormat, Utc};
use hkd32::KeyMaterial;
use hkdf::Hkdf;
use hmac::{Hmac, Mac};
use rand_os::{rand_core::RngCore, OsRng};
use sha2::Sha512;
use std::{
Expand All @@ -20,7 +20,7 @@ use yubihsm::{
setup::{Profile, Role},
wrap, AuditOption, Capability, Connector, Credentials, Domain,
};
use zeroize::Zeroize;
use zeroize::{Zeroize, Zeroizing};

/// Domain separation string used as "info" for HKDF
const HKDF_MNEMONIC_INFO: &[u8] = b"yubihsm setup BIP39 derivation";
Expand Down Expand Up @@ -103,14 +103,11 @@ impl Runnable for SetupCommand {

// Re-derive wrap key for display
// TODO(tarcieri): allow access to the underlying wrap key secret in `yubihsm` crate to avoid this
let mut wrapkey_hex = {
let mut bytes =
derive_secret_from_mnemonic(&mnemonic, &[b"wrap", serialize_key_id(1).as_bytes()]);
let wrapkey_material =
derive_secret_from_mnemonic(&mnemonic, &[b"wrap", serialize_key_id(1).as_bytes()]);

let hex_str = String::from_utf8(hex::encode(bytes)).unwrap();
bytes.zeroize();
hex_str
};
let wrapkey_hex =
Zeroizing::new(String::from_utf8(hex::encode(wrapkey_material.as_bytes())).unwrap());

if self.print_only {
if self.restore {
Expand Down Expand Up @@ -146,8 +143,7 @@ impl Runnable for SetupCommand {
validator_password.as_str()
);

println!("- wrapkey 0x0001 [primary]: {}", &wrapkey_hex);
wrapkey_hex.zeroize();
println!("- wrapkey 0x0001 [primary]: {}", wrapkey_hex.as_str());

if self.print_only {
process::exit(0);
Expand Down Expand Up @@ -363,7 +359,7 @@ impl RolePassword {
// convenient.
//
// For that reason, truncate the derived secret to 16-bytes
let truncated_secret_key = &secret_key[..(KEY_SIZE / 2)];
let truncated_secret_key = &secret_key.as_bytes()[..(KEY_SIZE / 2)];

let result = RolePassword(
Bech32::default().encode(format!("kms-{}-password-", role_name), truncated_secret_key),
Expand Down Expand Up @@ -418,7 +414,8 @@ fn derive_wrap_key_from_mnemonic(mnemonic: &Mnemonic, key_id: object::Id) -> wra
// has been compromised.
wrap::Key::from_bytes(
key_id,
&derive_secret_from_mnemonic(mnemonic, &[b"wrap", serialize_key_id(key_id).as_bytes()]),
derive_secret_from_mnemonic(mnemonic, &[b"wrap", serialize_key_id(key_id).as_bytes()])
.as_bytes(),
)
.unwrap()
.label(wrap_key_label)
Expand All @@ -434,7 +431,9 @@ fn serialize_key_id(key_id: object::Id) -> String {

/// Derive secrets from the given BIP39 `Mnemonic` ala a BIP32 (hardened)
/// derivation hierarchy.
fn derive_secret_from_mnemonic(mnemonic: &Mnemonic, path: &[&[u8]]) -> [u8; KEY_SIZE] {
fn derive_secret_from_mnemonic(mnemonic: &Mnemonic, path: &[&[u8]]) -> KeyMaterial {
assert!(!path.is_empty(), "cannot derive keys for the root path");

debug!(
"deriving secret for path: /{}",
path.iter()
Expand All @@ -443,36 +442,16 @@ fn derive_secret_from_mnemonic(mnemonic: &Mnemonic, path: &[&[u8]]) -> [u8; KEY_
.join("/")
);

// Domain separate the toplevel of the derivation hierarchy ala BIP43
let mut seed_hmac = Hmac::<Sha512>::new_varkey(mnemonic.entropy()).unwrap();
seed_hmac.input(DERIVATION_VERSION);

let mut seed = [0u8; KEY_SIZE];
seed.copy_from_slice(&seed_hmac.result().code()[KEY_SIZE..]);
let ikm = KeyMaterial::from_bytes(mnemonic.entropy()).unwrap();

// Simplified BIP32-like hierarchical derivation
path.iter()
.enumerate()
.fold(seed, |mut parent_key, (i, elem)| {
let mut hmac = Hmac::<Sha512>::new_varkey(&parent_key).unwrap();
hmac.input(elem);
let mut p = hkd32::PathBuf::new();
p.push(hkd32::Component::new(DERIVATION_VERSION).unwrap());

let hmac_result = hmac.result().code();
parent_key.zeroize();

let (secret_key, chain_code) = hmac_result.split_at(KEY_SIZE);
let mut child_key = [0u8; KEY_SIZE];

if i < path.len() - 1 {
// Use chain code for all but the last element
child_key.copy_from_slice(chain_code);
} else {
// Use secret key for the last element
child_key.copy_from_slice(secret_key);
}
for component in path {
p.push(hkd32::Component::new(component).unwrap());
}

child_key
})
ikm.derive_subkey(p)
}

/// Create a label for a newly generated object which tags it with the date
Expand Down Expand Up @@ -554,13 +533,6 @@ mod tests {
#[test]
fn derive_test_vectors() {
let test_vectors = &[
DeriveVector::new(
&[],
[
64, 221, 114, 137, 202, 149, 139, 31, 99, 190, 117, 111, 131, 176, 29, 0, 36,
196, 164, 172, 183, 124, 208, 114, 154, 230, 82, 17, 105, 74, 6, 180,
],
),
DeriveVector::new(
&[b"1"],
[
Expand All @@ -586,7 +558,7 @@ mod tests {

for vector in test_vectors {
let derived_key = derive_secret_from_mnemonic(&test_mnemonic(), vector.path);
assert_eq!(&derived_key, &vector.output);
assert_eq!(derived_key.as_bytes(), &vector.output);
}
}
}

0 comments on commit fde830b

Please sign in to comment.