e1353088e0
## Issue Addressed Resolves #1879 ## Proposed Changes Do NFKD normalization for keystore passwords.
313 lines
7.8 KiB
Rust
313 lines
7.8 KiB
Rust
#![cfg(test)]
|
|
#![cfg(not(debug_assertions))]
|
|
|
|
use bls::Keypair;
|
|
use eth2_keystore::{
|
|
default_kdf,
|
|
json_keystore::{Kdf, Pbkdf2, Prf, Scrypt},
|
|
Error, Keystore, KeystoreBuilder, DKLEN,
|
|
};
|
|
use std::fs::OpenOptions;
|
|
use tempfile::tempdir;
|
|
|
|
const GOOD_PASSWORD: &[u8] = &[42, 42, 42];
|
|
const BAD_PASSWORD: &[u8] = &[43, 43, 43];
|
|
|
|
#[test]
|
|
fn empty_password() {
|
|
assert_eq!(
|
|
KeystoreBuilder::new(&Keypair::random(), "".as_bytes(), "".into())
|
|
.err()
|
|
.unwrap(),
|
|
Error::EmptyPassword
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn string_round_trip() {
|
|
let keypair = Keypair::random();
|
|
|
|
let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into())
|
|
.unwrap()
|
|
.build()
|
|
.unwrap();
|
|
|
|
let json = keystore.to_json_string().unwrap();
|
|
let decoded = Keystore::from_json_str(&json).unwrap();
|
|
|
|
assert_eq!(
|
|
decoded.decrypt_keypair(BAD_PASSWORD).err().unwrap(),
|
|
Error::InvalidPassword,
|
|
"should not decrypt with bad password"
|
|
);
|
|
|
|
assert_eq!(
|
|
decoded.decrypt_keypair(GOOD_PASSWORD).unwrap().pk,
|
|
keypair.pk,
|
|
"should decrypt with good password"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn file() {
|
|
let keypair = Keypair::random();
|
|
let dir = tempdir().unwrap();
|
|
let path = dir.path().join("keystore.json");
|
|
|
|
let get_file = || {
|
|
OpenOptions::new()
|
|
.write(true)
|
|
.read(true)
|
|
.create(true)
|
|
.open(path.clone())
|
|
.expect("should create file")
|
|
};
|
|
|
|
let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into())
|
|
.unwrap()
|
|
.build()
|
|
.unwrap();
|
|
|
|
keystore
|
|
.to_json_writer(&mut get_file())
|
|
.expect("should write to file");
|
|
|
|
let decoded = Keystore::from_json_reader(&mut get_file()).expect("should read from file");
|
|
|
|
assert_eq!(
|
|
decoded.decrypt_keypair(BAD_PASSWORD).err().unwrap(),
|
|
Error::InvalidPassword,
|
|
"should not decrypt with bad password"
|
|
);
|
|
|
|
assert_eq!(
|
|
decoded.decrypt_keypair(GOOD_PASSWORD).unwrap().pk,
|
|
keypair.pk,
|
|
"should decrypt with good password"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn scrypt_params() {
|
|
let keypair = Keypair::random();
|
|
let salt = vec![42; 32];
|
|
|
|
let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into())
|
|
.unwrap()
|
|
.build()
|
|
.unwrap();
|
|
let json = keystore.to_json_string().unwrap();
|
|
let decoded = Keystore::from_json_str(&json).unwrap();
|
|
assert_eq!(
|
|
decoded.decrypt_keypair(BAD_PASSWORD).err().unwrap(),
|
|
Error::InvalidPassword,
|
|
"should not decrypt with bad password"
|
|
);
|
|
assert_eq!(
|
|
decoded.decrypt_keypair(GOOD_PASSWORD).unwrap().pk,
|
|
keypair.pk,
|
|
"should decrypt with good password"
|
|
);
|
|
|
|
// n <= 1
|
|
let my_kdf = Kdf::Scrypt(Scrypt {
|
|
dklen: DKLEN,
|
|
n: 1,
|
|
p: 1,
|
|
r: 8,
|
|
salt: salt.clone().into(),
|
|
});
|
|
let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into())
|
|
.unwrap()
|
|
.kdf(my_kdf.clone())
|
|
.build();
|
|
assert_eq!(keystore, Err(Error::InvalidScryptParam));
|
|
|
|
// p != 0
|
|
let my_kdf = Kdf::Scrypt(Scrypt {
|
|
dklen: DKLEN,
|
|
n: 16,
|
|
p: 0,
|
|
r: 8,
|
|
salt: salt.clone().into(),
|
|
});
|
|
let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into())
|
|
.unwrap()
|
|
.kdf(my_kdf.clone())
|
|
.build();
|
|
assert_eq!(keystore, Err(Error::InvalidScryptParam));
|
|
|
|
// r != 0
|
|
let my_kdf = Kdf::Scrypt(Scrypt {
|
|
dklen: DKLEN,
|
|
n: 16,
|
|
p: 1,
|
|
r: 0,
|
|
salt: salt.clone().into(),
|
|
});
|
|
let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into())
|
|
.unwrap()
|
|
.kdf(my_kdf.clone())
|
|
.build();
|
|
assert_eq!(keystore, Err(Error::InvalidScryptParam));
|
|
|
|
// 128 * n * p * r overflow
|
|
let my_kdf = Kdf::Scrypt(Scrypt {
|
|
dklen: DKLEN,
|
|
n: 1 << 31,
|
|
p: 1 << 31,
|
|
r: 1 << 31,
|
|
salt: salt.clone().into(),
|
|
});
|
|
let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into())
|
|
.unwrap()
|
|
.kdf(my_kdf.clone())
|
|
.build();
|
|
assert_eq!(keystore, Err(Error::InvalidScryptParam));
|
|
}
|
|
|
|
#[test]
|
|
fn pbkdf2_params() {
|
|
let keypair = Keypair::random();
|
|
|
|
let salt = vec![42; 32];
|
|
|
|
let my_kdf = Kdf::Pbkdf2(Pbkdf2 {
|
|
dklen: DKLEN,
|
|
c: 80_000_001,
|
|
prf: Prf::HmacSha256,
|
|
salt: salt.clone().into(),
|
|
});
|
|
let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into())
|
|
.unwrap()
|
|
.kdf(my_kdf.clone())
|
|
.build();
|
|
assert_eq!(keystore, Err(Error::InvalidPbkdf2Param));
|
|
|
|
let my_kdf = Kdf::Pbkdf2(Pbkdf2 {
|
|
dklen: DKLEN + 1,
|
|
c: 4,
|
|
prf: Prf::HmacSha256,
|
|
salt: salt.clone().into(),
|
|
});
|
|
let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into())
|
|
.unwrap()
|
|
.kdf(my_kdf.clone())
|
|
.build();
|
|
assert_eq!(keystore, Err(Error::InvalidPbkdf2Param));
|
|
}
|
|
|
|
#[test]
|
|
fn custom_scrypt_kdf() {
|
|
let keypair = Keypair::random();
|
|
|
|
let salt = vec![42; 32];
|
|
|
|
let my_kdf = Kdf::Scrypt(Scrypt {
|
|
dklen: DKLEN,
|
|
n: 2,
|
|
p: 1,
|
|
r: 8,
|
|
salt: salt.clone().into(),
|
|
});
|
|
|
|
assert!(my_kdf != default_kdf(salt));
|
|
|
|
let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into())
|
|
.unwrap()
|
|
.kdf(my_kdf.clone())
|
|
.build()
|
|
.unwrap();
|
|
|
|
assert_eq!(keystore.kdf(), &my_kdf);
|
|
}
|
|
|
|
#[test]
|
|
fn custom_pbkdf2_kdf() {
|
|
let keypair = Keypair::random();
|
|
|
|
let salt = vec![42; 32];
|
|
|
|
let my_kdf = Kdf::Pbkdf2(Pbkdf2 {
|
|
dklen: DKLEN,
|
|
c: 2,
|
|
prf: Prf::HmacSha256,
|
|
salt: salt.clone().into(),
|
|
});
|
|
|
|
assert!(my_kdf != default_kdf(salt));
|
|
|
|
let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into())
|
|
.unwrap()
|
|
.kdf(my_kdf.clone())
|
|
.build()
|
|
.unwrap();
|
|
|
|
assert_eq!(keystore.kdf(), &my_kdf);
|
|
}
|
|
|
|
#[test]
|
|
fn utf8_control_characters() {
|
|
let keypair = Keypair::random();
|
|
|
|
let password = vec![42, 42, 42];
|
|
let password_with_control_chars = vec![0x7Fu8, 42, 42, 42];
|
|
|
|
let keystore1 = KeystoreBuilder::new(&keypair, &password_with_control_chars, "".into())
|
|
.unwrap()
|
|
.build()
|
|
.unwrap();
|
|
|
|
let keystore2 = KeystoreBuilder::new(&keypair, &password, "".into())
|
|
.unwrap()
|
|
.build()
|
|
.unwrap();
|
|
|
|
assert_eq!(keystore1.pubkey(), keystore2.pubkey());
|
|
|
|
// Decode same keystore with nfc and nfkd form passwords
|
|
let decoded1 = keystore1
|
|
.decrypt_keypair(&password_with_control_chars)
|
|
.unwrap();
|
|
let decoded2 = keystore1.decrypt_keypair(&password).unwrap();
|
|
|
|
assert_eq!(decoded1.pk, keypair.pk);
|
|
assert_eq!(decoded2.pk, keypair.pk);
|
|
}
|
|
|
|
#[test]
|
|
fn normalization() {
|
|
use unicode_normalization::UnicodeNormalization;
|
|
|
|
let keypair = Keypair::random();
|
|
let password_str = "Zoë";
|
|
|
|
let password_nfc: String = password_str.nfc().collect();
|
|
let password_nfkd: String = password_str.nfkd().collect();
|
|
|
|
assert_ne!(password_nfc, password_nfkd);
|
|
|
|
let keystore_nfc = KeystoreBuilder::new(&keypair, password_nfc.as_bytes(), "".into())
|
|
.unwrap()
|
|
.build()
|
|
.unwrap();
|
|
|
|
let keystore_nfkd = KeystoreBuilder::new(&keypair, password_nfkd.as_bytes(), "".into())
|
|
.unwrap()
|
|
.build()
|
|
.unwrap();
|
|
|
|
assert_eq!(keystore_nfc.pubkey(), keystore_nfkd.pubkey());
|
|
|
|
// Decode same keystore with nfc and nfkd form passwords
|
|
let decoded_nfc = keystore_nfc
|
|
.decrypt_keypair(password_nfc.as_bytes())
|
|
.unwrap();
|
|
let decoded_nfkd = keystore_nfc
|
|
.decrypt_keypair(password_nfkd.as_bytes())
|
|
.unwrap();
|
|
|
|
assert_eq!(decoded_nfc.pk, keypair.pk);
|
|
assert_eq!(decoded_nfkd.pk, keypair.pk);
|
|
}
|