Initial commit for vaults #4312
Conversation
@@ -24,7 +24,8 @@ use std::fmt; | |||
use std::collections::HashMap; | |||
use std::time::{Instant, Duration}; | |||
use util::RwLock; | |||
use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore, random_string}; | |||
use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated this file just to make it work with new ethstore API. Its own API will be updated in next PR[s] (about RPCs).
.expect("last component of path is checked in make_vault_dir_path; it contains only valid unicode characters; to_str fails when file_name is not valid unicode; qed") | ||
} | ||
|
||
fn set_key(&self, key: VaultKey, new_key: VaultKey) -> Result<(), SetKeyError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As there's no way to make atomic cross-platform swap of two directories, here's my attempt to minimize potential losses:
- create temp vault
- copy all accounts to the temp vault [with same file names]
- move all files from temp vault to self [with replace] - if crash/shutdown occurs here, directory is partially moved => user can finish this move.
I also thought on some making some '.dirty' file when vault is in inconsistent state && then automatically continue moving on next opening.
But I think it is too much && it could corrupt vault totally.
build is broken with message:
but all tests I have written are in ethstore crate && it has passed the check |
agree.
not sure what this is about. there should be no additional "vault file" and an empty vault is simply an empty directory. encrypted content is stored (as hex) in the JSON key file under the field
that's fine. |
@gavofyork |
Options that I've rejected:
|
Option 3 - is to remove
I.e. vault is still referenced by name, but it's unique key is name + password. |
Alternative which persists empty vaults is to place a CSRNGed token encrypted with the vault key in the path, which I guess is similar to your original solution (e.g. a single file containing 2x 32 bytes one is the plain text and the other is encrypted). This is considered secure (it's known in crypto speak as a MAC). I'm fairly ambivalent. |
I would prefer to leave it as is then, because otherwise (as I told above) this would be a total mess just because vault could then contain keys, encrypted with different passwords (part of these will be ignored when opened with pass1, another part - when opened with pass2). + vault currently is a filesystem-entity (dir) && I can't delete it when there are some other files, which I do not manage (like keys, encryptd with different password). |
how could it get into a state where keys are encrypted with different vault passwords? (vault password could only "change" when the vault is empty/non-existent) |
In my impl this is impossible, as I could only open vault with the password passed to |
because it breaks things? :-) if we're up against a user who is happy to messing up our file structure then they only have themselves to blame for any ensuing confusion. that said, i'm fairly ambivalent; if you prefer to mac approach, go for it. |
But it is allowed - and most of users do not know how it works internally :) I could want to restore keys from backup && just select wrong directory for restore. Or restore keys, which have been made using old password. There are too many options to make such a mistake. |
if a user restores a database backup but places files in different paths (or mixes old files with new), the database will break and the user will have only them self to blame. this is a type of database, so i don't see all that much difference. |
@@ -24,7 +24,8 @@ use std::fmt; | |||
use std::collections::HashMap; | |||
use std::time::{Instant, Duration}; | |||
use util::RwLock; | |||
use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore, random_string}; | |||
use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore | |||
, random_string, SecretVaultRef, StoreAccountRef}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: comma on newline?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
use account::{Cipher, Kdf, Aes128Ctr, Pbkdf2, Prf}; | ||
|
||
#[derive(Debug, PartialEq, Clone)] | ||
pub struct Crypto { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just take this code && moved to separate file, as it has grown a bit :) Also the whole ethstore crate is undocumented - just thought it is not a good idea to document 1/100 of undocumented code :) Anyway - I've tried to add docs for this struct && others I've added. But I wonder - is there some policy on documentation? I mean - if mod makes something pub - it should be documented? Even if this is some technical mod, unavailable to all others, but its parent?
} | ||
|
||
impl DiskKeyFileManager { | ||
pub fn new() -> Self { | ||
DiskKeyFileManager {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
braces unnecessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually, new
is unnecessary, it's less work to write let _ = DiskKeyFileManager
than let _ = DiskKeyFileManager::new()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
} | ||
|
||
impl SetKeyError { | ||
pub fn fatal(err: Error) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why have these functions? the variants are public. just seems like more code to maintain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lack of documentation on public items is a problem. |
could do with merging master so the tests don't fail |
@svyatonik assuming you're going for the mac approach, the most cryptographically secure means i can think of is to encrypt the hash of the password with a KDF of the salted password, so file contains two 32-byte arrays:
for the wallet files within this path, the secret for their metadata should be similarly derived (though the format of |
@gavofyork ok - thanks for the advice. I'll update implementation => in progress |
done - now vault.json will contain original password hash, encrypted with derived key + salt |
conflicts... |
Initial commit for vaults
This is first part of implementation of #3501. Here's outline (and some caveats) of implementation:
Another approach is to unlink name of vault from name of vault-dir && have some metadata file in keys directory, which will be of form [{ "vault_name": "MyVault", "vault_dir": "abcdef-123141-125421-12" }].
For me, the former is better, as it is more self-descriptive (it maybe more convenient for users who want to copy vaults) + we avoid some errors (like adding vaults with duplicate names => unable to work with both vaults at all, etc).
Anyway, this is discussable.
2.1) vault can be empty, but it still has to be protected with password. So here's vault.json file (aka vault_file.rs) in each vault, which contains encrypted password. Password is encrypted with the same password, so that decrypt(what: encrypted, with: password) = password. I'm not sure this is cryptographically strong, but I don't have another ideas how to achieve the same result.
2.2) some operations on accounts require no password (for example: changing name of account, changing meta). But this data is password-protected for vault accounts => password is stored in memory for all the time, when vault is open. I don't see another option currently. Maybe we should change API to support vault accounts? Or use some OS-backed protected memory (haven't found any cross-platform solutions for now) in future.
JSONRPC && Parity Wallet integration is yet to be done.