Skip to content

Commit 0390741

Browse files
committed
Merge branch 'rust-tests'
2 parents 2a6c2f8 + 482dc2c commit 0390741

File tree

6 files changed

+162
-336
lines changed

6 files changed

+162
-336
lines changed

src/rust/bitbox02-sys/build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ const ALLOWLIST_FNS: &[&str] = &[
9797
"memory_check_noise_remote_static_pubkey",
9898
"memory_get_attestation_bootloader_hash",
9999
"memory_get_attestation_pubkey_and_certificate",
100+
"memory_get_encrypted_seed_and_hmac",
100101
"memory_get_device_name",
101102
"memory_get_noise_static_private_key",
102103
"memory_get_seed_birthdate",
@@ -125,6 +126,7 @@ const ALLOWLIST_FNS: &[&str] = &[
125126
"progress_create",
126127
"progress_set",
127128
"random_32_bytes_mcu",
129+
"random_32_bytes",
128130
"random_mock_reset",
129131
"reboot_to_bootloader",
130132
"reset_reset",

src/rust/bitbox02/src/keystore.rs

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,68 @@ mod tests {
401401
.is_ok());
402402
}
403403

404+
#[test]
405+
fn test_secp256k1_schnorr_sign() {
406+
mock_unlocked_using_mnemonic("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", "");
407+
let keypath = [86 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0];
408+
let msg = [0x88u8; 32];
409+
410+
let expected_pubkey = {
411+
let pubkey =
412+
hex::decode("cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115")
413+
.unwrap();
414+
secp256k1::XOnlyPublicKey::from_slice(&pubkey).unwrap()
415+
};
416+
417+
// Test without tweak
418+
crate::random::mock_reset();
419+
let sig = secp256k1_schnorr_sign(&keypath, &msg, None).unwrap();
420+
let secp = secp256k1::Secp256k1::new();
421+
assert!(secp
422+
.verify_schnorr(
423+
&secp256k1::schnorr::Signature::from_slice(&sig).unwrap(),
424+
&secp256k1::Message::from_digest_slice(&msg).unwrap(),
425+
&expected_pubkey
426+
)
427+
.is_ok());
428+
429+
// Test with tweak
430+
crate::random::mock_reset();
431+
let tweak = {
432+
let tweak =
433+
hex::decode("a39fb163dbd9b5e0840af3cc1ee41d5b31245c5dd8d6bdc3d026d09b8964997c")
434+
.unwrap();
435+
secp256k1::Scalar::from_be_bytes(tweak.try_into().unwrap()).unwrap()
436+
};
437+
let (tweaked_pubkey, _) = expected_pubkey.add_tweak(&secp, &tweak).unwrap();
438+
let sig = secp256k1_schnorr_sign(&keypath, &msg, Some(&tweak.to_be_bytes())).unwrap();
439+
assert!(secp
440+
.verify_schnorr(
441+
&secp256k1::schnorr::Signature::from_slice(&sig).unwrap(),
442+
&secp256k1::Message::from_digest_slice(&msg).unwrap(),
443+
&tweaked_pubkey
444+
)
445+
.is_ok());
446+
}
447+
448+
#[test]
449+
fn test_secp256k1_nonce_commit() {
450+
lock();
451+
let keypath = [44 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 5];
452+
let msg = [0x88u8; 32];
453+
let host_commitment = [0xabu8; 32];
454+
455+
// Fails because keystore is locked.
456+
assert!(secp256k1_nonce_commit(&keypath, &msg, &host_commitment).is_err());
457+
458+
mock_unlocked();
459+
let client_commitment = secp256k1_nonce_commit(&keypath, &msg, &host_commitment).unwrap();
460+
assert_eq!(
461+
hex::encode(client_commitment),
462+
"0381e4136251c87f2947b735159c6dd644a7b58d35b437e20c878e5129f1320e5e",
463+
);
464+
}
465+
404466
#[test]
405467
fn test_bip39_mnemonic_to_seed() {
406468
assert!(bip39_mnemonic_to_seed("invalid").is_err());
@@ -550,6 +612,22 @@ mod tests {
550612
assert!(bip39_mnemonic_from_seed(b"foo").is_err());
551613
}
552614

615+
#[test]
616+
fn test_lock() {
617+
lock();
618+
assert!(is_locked());
619+
620+
let seed = hex::decode("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044")
621+
.unwrap();
622+
assert!(encrypt_and_store_seed(&seed, "password").is_ok());
623+
assert!(unlock("password").is_ok());
624+
assert!(is_locked()); // still locked, it is only unlocked after unlock_bip39.
625+
assert!(unlock_bip39("foo").is_ok());
626+
assert!(!is_locked());
627+
lock();
628+
assert!(is_locked());
629+
}
630+
553631
#[test]
554632
fn test_unlock() {
555633
mock_memory();
@@ -624,7 +702,6 @@ mod tests {
624702

625703
assert!(encrypt_and_store_seed(&seed, "password").is_ok());
626704
assert!(unlock("password").is_ok());
627-
assert!(is_locked()); // still locked, it is only unlocked after unlock_bip39.
628705
assert!(unlock_bip39("foo").is_ok());
629706

630707
// Check that the retained bip39 seed was encrypted with the expected encryption key.
@@ -647,6 +724,68 @@ mod tests {
647724
assert_eq!(decrypted.as_slice(), expected_bip39_seed.as_slice());
648725
}
649726

727+
#[test]
728+
fn test_create_and_store_seed() {
729+
let mock_salt_root =
730+
hex::decode("3333333333333333444444444444444411111111111111112222222222222222")
731+
.unwrap();
732+
733+
let host_entropy =
734+
hex::decode("25569b9a11f9db6560459e8e48b4727a4c935300143d978989ed55db1d1b9cbe25569b9a11f9db6560459e8e48b4727a4c935300143d978989ed55db1d1b9cbe")
735+
.unwrap();
736+
737+
// Invalid seed lengths
738+
for size in [8, 24, 40] {
739+
assert!(matches!(
740+
create_and_store_seed("password", &host_entropy[..size]),
741+
Err(Error::SeedSize)
742+
));
743+
}
744+
745+
// Hack to get the random bytes that will be used.
746+
let seed_random = {
747+
crate::random::mock_reset();
748+
crate::random::random_32_bytes()
749+
};
750+
751+
// Derived from mock_salt_root and "password".
752+
let password_salted_hashed =
753+
hex::decode("e8c70a20d9108fbb9454b1b8e2d7373e78cbaf9de025ab2d4f4d3c7a6711694c")
754+
.unwrap();
755+
756+
// expected_seed = seed_random ^ host_entropy ^ password_salted_hashed
757+
let expected_seed: Vec<u8> = seed_random
758+
.into_iter()
759+
.zip(host_entropy.iter())
760+
.zip(password_salted_hashed)
761+
.map(|((a, &b), c)| a ^ b ^ c)
762+
.collect();
763+
764+
for size in [16, 32] {
765+
mock_memory();
766+
crate::random::mock_reset();
767+
crate::memory::set_salt_root(mock_salt_root.as_slice().try_into().unwrap()).unwrap();
768+
lock();
769+
770+
assert!(create_and_store_seed("password", &host_entropy[..size]).is_ok());
771+
assert!(unlock("password").is_ok());
772+
assert_eq!(copy_seed().unwrap().as_slice(), &expected_seed[..size]);
773+
// Check the seed has been stored encrypted with the expected encryption key.
774+
// Decrypt and check seed.
775+
let cipher = crate::memory::get_encrypted_seed_and_hmac().unwrap();
776+
777+
// Same as Python:
778+
// import hmac, hashlib; hmac.digest(b"unit-test", b"password", hashlib.sha256).hex()
779+
// See also: mock_securechip.c
780+
let expected_encryption_key =
781+
hex::decode("e56de448f5f1d29cdcc0e0099007309afe4d5a3ef2349e99dcc41840ad98409e")
782+
.unwrap();
783+
let decrypted =
784+
bitbox_aes::decrypt_with_hmac(&expected_encryption_key, &cipher).unwrap();
785+
assert_eq!(decrypted.as_slice(), &expected_seed[..size]);
786+
}
787+
}
788+
650789
// This tests that you can create a keystore, unlock it, and then do this again. This is an
651790
// expected workflow for when the wallet setup process is restarted after seeding and unlocking,
652791
// but before creating a backup, in which case a new seed is created.

src/rust/bitbox02/src/memory.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ pub fn get_attestation_pubkey_and_certificate(
9292
}
9393
}
9494

95+
#[cfg(feature = "testing")]
96+
pub fn get_encrypted_seed_and_hmac() -> Result<alloc::vec::Vec<u8>, ()> {
97+
let mut out = vec![0u8; 96];
98+
let mut len = 0u8;
99+
match unsafe { bitbox02_sys::memory_get_encrypted_seed_and_hmac(out.as_mut_ptr(), &mut len) } {
100+
true => {
101+
out.truncate(len as _);
102+
Ok(out)
103+
}
104+
false => Err(()),
105+
}
106+
}
107+
95108
pub fn get_noise_static_private_key() -> Result<zeroize::Zeroizing<[u8; 32]>, ()> {
96109
let mut out = zeroize::Zeroizing::new([0u8; 32]);
97110
match unsafe { bitbox02_sys::memory_get_noise_static_private_key(out.as_mut_ptr()) } {

src/rust/bitbox02/src/random.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ pub fn mcu_32_bytes(out: &mut [u8; 32]) {
2929
}
3030
}
3131

32+
#[cfg(feature = "testing")]
33+
pub fn random_32_bytes() -> alloc::boxed::Box<zeroize::Zeroizing<[u8; 32]>> {
34+
let mut out = alloc::boxed::Box::new(zeroize::Zeroizing::new([0u8; 32]));
35+
unsafe { bitbox02_sys::random_32_bytes(out.as_mut_ptr()) }
36+
out
37+
}
38+
3239
#[cfg(feature = "testing")]
3340
pub fn mock_reset() {
3441
unsafe {

test/unit-test/CMakeLists.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,6 @@ target_link_libraries(u2f-util PUBLIC ${HIDAPI_LDFLAGS})
228228
set(TEST_LIST
229229
cleanup
230230
"-Wl,--wrap=util_cleanup_32"
231-
keystore
232-
"-Wl,--wrap=secp256k1_anti_exfil_sign,--wrap=memory_is_initialized,--wrap=memory_is_seeded,--wrap=memory_get_failed_unlock_attempts,--wrap=memory_reset_failed_unlock_attempts,--wrap=memory_increment_failed_unlock_attempts,--wrap=memory_set_encrypted_seed_and_hmac,--wrap=memory_get_encrypted_seed_and_hmac,--wrap=memory_get_salt_root,--wrap=reset_reset,--wrap=random_32_bytes"
233231
keystore_antiklepto
234232
""
235233
gestures

0 commit comments

Comments
 (0)