Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Keystore spec for BLS12-381 secret keys #777

Closed
wants to merge 25 commits into from

Conversation

pawanjay176
Copy link
Member

Issue Addressed

N/A

Proposed Changes

Supersedes #596 to maintain clean commit history.

@AgeManning AgeManning added this to the v0.2.0 milestone Jan 21, 2020
@AgeManning
Copy link
Member

Is this ready for review?

@pawanjay176
Copy link
Member Author

Yup it is. Forgot to mark this one as ready.

@pawanjay176 pawanjay176 added the ready-for-review The code is ready for review label Jan 21, 2020
Copy link
Member

@paulhauner paulhauner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, sorry it's taken so long to get around to this!

I spent about 30mins going through the Rust code, looking good! Just a few comments about zeroize and heap allocations.

I haven't had a chance to dig into the crypto, though. I'll get to that soon!

E: de::Error,
{
let bytes = hex::decode(v).map_err(E::custom)?;
Ok(from_slice(&bytes))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The from_slice function will panic unless bytes.len() == IV_SIZE.

I think it would be best to check bytes.len() here and return an error if it's not 16.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. I guess we can also return an Option from the from_slice function and handle the None case here.

impl Crypto {
/// Generate crypto module for `Keystore` given the password,
/// secret to encrypt, kdf params and cipher params.
pub fn encrypt(password: String, secret: &[u8], kdf: Kdf, cipher: Cipher) -> Self {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we're dealing with secrets/passwords we should be careful to zeroize them after they're done.

For password it would probably be safest to create a Password type that wraps a string and always calls zeroize on drop. The secret field is fine since we only have a reference to it, but we'd have to pay attention to this in upstream functions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will add the zeroize calls :)

Cipher::Aes128Ctr(cipher) => cipher.encrypt(&derived_key[0..16], secret),
};
// Generate checksum
let mut pre_image: Vec<u8> = derived_key[16..32].to_owned();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little pedantic, but we could avoid a couple of heap allocations here:

let mut pre_image = [0; 32];
pre_image[0..16].copy_from_slice(derived_key[16..32]);
pre_image[16..32].copy_from_slice(&cipher_message);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a vector here since we don't know the size of the secret in advance.
We can do this if we assume that the secret will always be a secret key of fixed length.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, you're right sorry! I thought it was producing a HMAC but it's encrypting :)

validator_client/src/keystore/crypto.rs Outdated Show resolved Hide resolved
///
/// An error will be returned if `cipher.message` is not in hex format or
/// if password is incorrect.
pub fn decrypt(&self, password: String) -> Result<Vec<u8>, String> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, zeroize.

/// An error is returned if the password provided is incorrect or if
/// keystore does not contain valid hex strings or if the secret contained is not a
/// BLS12-381 secret key.
fn to_keypair(&self, password: String) -> Result<Keypair, String> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, zeroize.


/// Pad 0's to a 32 bytes BLS secret key to make it compatible with the Milagro library
/// Note: Milagro library only accepts 48 byte bls12 381 private keys.
fn pad_secret_key(sk: &[u8]) -> [u8; PRIVATE_KEY_BYTES] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably wrap this return value in something so it also gets zeroized.

@@ -81,6 +105,19 @@ fn load_keypair(base_path: PathBuf, file_prefix: &str) -> Result<Keypair, String
.map_err(|e| format!("Unable to decode keypair: {:?}", e))
}

/// Load a `Keystore` from a file.
fn load_keystore(path: PathBuf, password: String) -> Result<Keystore, String> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, zeroize.

use std::default::Default;

// TODO: verify size of salt
const SALT_SIZE: usize = 32;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From some quick reading, RFC 2898 (PBKDF2-SHA-256) specifies an "eight octet" salt (64 bits) whilst RFC 7914 (scrypt) does not specify the salt size.

It seems the test vectors have 32-byte salts, though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC 2898 specifies that the salt should be atleast 64 bits.

I don't see any harm in reducing to the minimum recommended value. Its only while deriving the key that we use the SALT_SIZE constant. We will still be able to read keystores with larger salt values (for tests etc).

Also, scrypt uses pbkdf2 under the hood, so I guess we should have ultimately have similar salt sizes for both.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you suggesting to drop SALT_SIZE to 8 bytes (64 bits)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the other implementations (Go, Python, Typescript). All are using 32 byte salts. I suggest we keep it the same.

validator_client/src/keystore/mod.rs Show resolved Hide resolved
@paulhauner paulhauner added waiting-on-author The reviewer has suggested changes and awaits thier implementation. and removed ready-for-review The code is ready for review labels Mar 4, 2020
@pawanjay176 pawanjay176 added ready-for-review The code is ready for review and removed waiting-on-author The reviewer has suggested changes and awaits thier implementation. labels Mar 9, 2020
@paulhauner paulhauner mentioned this pull request Apr 28, 2020
@paulhauner
Copy link
Member

Closing in favour of #1071 which builds upon this work. Thanks @pawanjay176!

@paulhauner paulhauner closed this Apr 28, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready-for-review The code is ready for review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants