Skip to content

Commit def962e

Browse files
authored
Merge pull request hyperledger-indy#811 from keichiri/feature/wallet_key_rotation
wallet key rotation
2 parents df6cdd8 + 0ff8342 commit def962e

File tree

3 files changed

+102
-8
lines changed

3 files changed

+102
-8
lines changed

libindy/src/services/wallet/mod.rs

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use self::storage::default::SQLiteStorageType;
3030
use self::storage::plugged::PluggedStorageType;
3131
use self::wallet::{Wallet, Keys, Tags};
3232
use self::indy_crypto::utils::json::{JsonDecodable, JsonEncodable};
33+
use utils::crypto::pwhash_argon2i13::PwhashArgon2i13;
3334

3435

3536
#[derive(Serialize, Deserialize, Debug)]
@@ -63,22 +64,30 @@ impl<'a> JsonDecodable<'a> for WalletConfig {}
6364
#[derive(Debug)]
6465
pub struct WalletCredentials {
6566
master_key: [u8; 32],
67+
rekey: Option<[u8; 32]>,
6668
storage_credentials: String,
6769
}
6870

69-
use utils::crypto::pwhash_argon2i13::PwhashArgon2i13;
7071

7172
impl WalletCredentials {
7273
fn from_json(json: &str, salt: &[u8; PwhashArgon2i13::SALTBYTES]) -> Result<WalletCredentials, WalletError> {
7374
if let serde_json::Value::Object(m) = serde_json::from_str(json)? {
74-
let master_key = if let Some(key) = m["key"].as_str() {
75+
let master_key = if let Some(key) = m.get("key").and_then(|s| s.as_str()) {
7576
let mut master_key: [u8; ChaCha20Poly1305IETF::KEYBYTES] = [0; ChaCha20Poly1305IETF::KEYBYTES];
7677
PwhashArgon2i13::derive_key(&mut master_key, key.as_bytes(), salt)?;
7778
master_key
7879
} else {
7980
return Err(WalletError::InputError(String::from("Credentials missing 'key' field")));
8081
};
8182

83+
let rekey = if let Some(key) = m.get("rekey").and_then(|s| s.as_str()) {
84+
let mut rekey: [u8; ChaCha20Poly1305IETF::KEYBYTES] = [0; ChaCha20Poly1305IETF::KEYBYTES];
85+
PwhashArgon2i13::derive_key(&mut rekey, key.as_bytes(), salt)?;
86+
Some(rekey)
87+
} else {
88+
None
89+
};
90+
8291
let storage_credentials = serde_json::to_string(
8392
&m.get("storage_credentials")
8493
.and_then(|storage_credentials| storage_credentials.as_object())
@@ -87,6 +96,7 @@ impl WalletCredentials {
8796

8897
Ok(WalletCredentials {
8998
master_key,
99+
rekey,
90100
storage_credentials
91101
})
92102
} else {
@@ -274,8 +284,7 @@ impl WalletService {
274284
let config = serde_json::from_str::<WalletConfig>(&config_json)
275285
.map_err(|err| CommonError::InvalidState(format!("Cannot deserialize Storage Config")))?;
276286

277-
let credentials = WalletCredentials::from_json(credentials, &config.salt)?
278-
;
287+
let credentials = WalletCredentials::from_json(credentials, &config.salt)?;
279288
let storage = storage_type.open_storage(name,
280289
Some(&config_json),
281290
&credentials.storage_credentials)?;
@@ -288,9 +297,13 @@ impl WalletService {
288297
Ok(keys_vector) => keys_vector,
289298
Err(_) => return Err(WalletError::AccessFailed("Invalid master key provided".to_string())),
290299
};
300+
291301
let keys = Keys::new(keys_vector);
292302

293303
let wallet = Wallet::new(name, &descriptor.pool_name, storage, keys);
304+
if let Some(ref rekey) = credentials.rekey {
305+
wallet.rotate_key(&rekey[..])?;
306+
}
294307
let wallet_handle = SequenceUtils::get_next_id();
295308
wallets.insert(wallet_handle, Box::new(wallet));
296309

@@ -727,6 +740,14 @@ mod tests {
727740
String::from(r#"{"key":"my_key"}"#)
728741
}
729742

743+
fn _rekey_credentials() -> String {
744+
String::from(r#"{"key":"my_key", "rekey": "my_new_key"}"#)
745+
}
746+
747+
fn _credentials_for_new_key() -> String {
748+
String::from(r#"{"key": "my_new_key"}"#)
749+
}
750+
730751
fn _cleanup() {
731752
let mut path = std::env::home_dir().unwrap();
732753
path.push(".indy_client");
@@ -877,6 +898,16 @@ mod tests {
877898
wallet_service.create_wallet("pool1", "test_wallet", None, None, &_credentials()).unwrap();
878899
wallet_service.open_wallet("test_wallet", None, &_credentials()).unwrap();
879900
}
901+
902+
#[test]
903+
fn wallet_service_open_wallet_without_master_key_in_credentials_returns_error() {
904+
_cleanup();
905+
906+
let wallet_service = WalletService::new();
907+
wallet_service.create_wallet("pool1", "test_wallet", None, None, &_credentials()).unwrap();
908+
let res = wallet_service.open_wallet("test_wallet", None, "{}");
909+
assert_match!(Err(WalletError::InputError(_)), res);
910+
}
880911
//
881912
// // #[test]
882913
// // fn wallet_service_open_works_for_plugged() {
@@ -1509,4 +1540,39 @@ mod tests {
15091540
let get_pool_name_res = wallet_service.get_pool_name(1);
15101541
assert_match!(Err(WalletError::InvalidHandle(_)), get_pool_name_res);
15111542
}
1543+
1544+
/**
1545+
Key rotation test
1546+
*/
1547+
#[test]
1548+
fn wallet_service_key_rotation() {
1549+
_cleanup();
1550+
1551+
let wallet_service = WalletService::new();
1552+
wallet_service.create_wallet("pool1", "test_wallet", None, None, &_credentials()).unwrap();
1553+
let wallet_handle = wallet_service.open_wallet("test_wallet", None, &_credentials()).unwrap();
1554+
1555+
wallet_service.add_record(wallet_handle, "type", "key1", "value1", "{}").unwrap();
1556+
let record = wallet_service.get_record(wallet_handle, "type", "key1", &_fetch_options(true, true, true)).unwrap();
1557+
assert_eq!("type", record.get_type().unwrap());
1558+
assert_eq!("value1", record.get_value().unwrap());
1559+
1560+
wallet_service.close_wallet(wallet_handle).unwrap();
1561+
1562+
let wallet_handle = wallet_service.open_wallet("test_wallet", None, &_rekey_credentials()).unwrap();
1563+
let record = wallet_service.get_record(wallet_handle, "type", "key1", &_fetch_options(true, true, true)).unwrap();
1564+
assert_eq!("type", record.get_type().unwrap());
1565+
assert_eq!("value1", record.get_value().unwrap());
1566+
wallet_service.close_wallet(wallet_handle).unwrap();
1567+
1568+
// Access failed for old key
1569+
let res = wallet_service.open_wallet("test_wallet", None, &_credentials());
1570+
assert_match!(Err(WalletError::AccessFailed(_)), res);
1571+
1572+
// Works ok with new key when reopening
1573+
let wallet_handle = wallet_service.open_wallet("test_wallet", None, &_credentials_for_new_key()).unwrap();
1574+
let record = wallet_service.get_record(wallet_handle, "type", "key1", &_fetch_options(true, true, true)).unwrap();
1575+
assert_eq!("type", record.get_type().unwrap());
1576+
assert_eq!("value1", record.get_value().unwrap());
1577+
}
15121578
}

libindy/src/services/wallet/storage/default/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ impl WalletStorage for SQLiteStorage {
541541
}
542542

543543
fn set_storage_metadata(&self, metadata: &Vec<u8>) -> Result<(), WalletStorageError> {
544-
match self.conn.execute("INSERT OR REPLACE INTO metadata(value) VALUES(?1)",&[metadata]) {
544+
match self.conn.execute("UPDATE metadata SET value = ?1",&[metadata]) {
545545
Ok(_) => Ok(()),
546546
Err(error) => {
547547
Err(WalletStorageError::IOError(format!("Error occurred while inserting the keys: {}", error)))

libindy/src/services/wallet/wallet.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,19 @@ impl Keys {
6666

6767
return ChaCha20Poly1305IETF::encrypt_as_not_searchable(&keys, &master_key);
6868
}
69+
70+
pub fn encrypt(&self, master_key: &[u8]) -> Vec<u8> {
71+
let mut keys = Vec::new();
72+
keys.extend_from_slice(&self.type_key);
73+
keys.extend_from_slice(&self.name_key);
74+
keys.extend_from_slice(&self.value_key);
75+
keys.extend_from_slice(&self.item_hmac_key);
76+
keys.extend_from_slice(&self.tag_name_key);
77+
keys.extend_from_slice(&self.tag_value_key);
78+
keys.extend_from_slice(&self.tags_hmac_key);
79+
80+
return ChaCha20Poly1305IETF::encrypt_as_not_searchable(&keys, &master_key);
81+
}
6982
}
7083

7184

@@ -239,6 +252,12 @@ impl Wallet {
239252
Ok(())
240253
}
241254

255+
pub(super) fn rotate_key(&self, new_master_key: &[u8]) -> Result<(), WalletError> {
256+
let new_metadata = self.keys.encrypt(new_master_key);
257+
self.storage.set_storage_metadata(&new_metadata)?;
258+
Ok(())
259+
}
260+
242261
pub fn get_pool_name(&self) -> String {
243262
self.pool_name.clone()
244263
}
@@ -286,8 +305,8 @@ mod tests {
286305

287306

288307
fn _cleanup() {
289-
std::fs::remove_dir_all(_wallet_path());
290-
std::fs::create_dir(_wallet_path());
308+
std::fs::remove_dir_all(_wallet_path()).unwrap();
309+
std::fs::create_dir(_wallet_path()).unwrap();
291310
}
292311

293312
fn _credentials() -> String {
@@ -322,6 +341,15 @@ mod tests {
322341
];
323342
}
324343

344+
fn _get_test_new_master_key() -> [u8; 32] {
345+
return [
346+
2, 2, 3, 4, 5, 6, 7, 8,
347+
2, 2, 3, 4, 5, 6, 7, 8,
348+
2, 2, 3, 4, 5, 6, 7, 8,
349+
2, 2, 3, 4, 5, 6, 7, 8
350+
];
351+
}
352+
325353
fn _get_test_keys() -> Vec<u8> {
326354
return vec![
327355
1, 2, 3, 4, 5, 6, 7, 8,
@@ -583,7 +611,7 @@ mod tests {
583611

584612
#[test]
585613
fn wallet_update_tags() {
586-
_cleanup();
614+
_cleanup();
587615
let mut wallet = _create_wallet();
588616
let type_ = "test";
589617
let name = "name";

0 commit comments

Comments
 (0)