From 513e0325148fe909657afcb0906bf3055bd3bc4d Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 13 Mar 2018 18:41:47 +0100 Subject: [PATCH 1/6] improve ed25519 bindings --- substrate/ed25519/Cargo.toml | 1 - substrate/ed25519/src/lib.rs | 49 ++++++++++++++++++++++++------------ 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/substrate/ed25519/Cargo.toml b/substrate/ed25519/Cargo.toml index 1241bd69bf15d..66a65f66b7da6 100644 --- a/substrate/ed25519/Cargo.toml +++ b/substrate/ed25519/Cargo.toml @@ -7,4 +7,3 @@ authors = ["Parity Technologies "] ring = "0.12" untrusted = "0.5" substrate-primitives = { version = "0.1", path = "../primitives" } -hex-literal = "0.1" diff --git a/substrate/ed25519/src/lib.rs b/substrate/ed25519/src/lib.rs index 4d8117186bc61..797d71e455b3d 100644 --- a/substrate/ed25519/src/lib.rs +++ b/substrate/ed25519/src/lib.rs @@ -19,7 +19,6 @@ extern crate ring; extern crate substrate_primitives as primitives; extern crate untrusted; -#[macro_use] extern crate hex_literal; use ring::{rand, signature}; use primitives::hash::H512; @@ -55,6 +54,12 @@ pub struct Public(pub [u8; 32]); /// A key pair. pub struct Pair(signature::Ed25519KeyPair); +impl From for Pair { + fn from(key: signature::Ed25519KeyPair) -> Self { + Pair(key) + } +} + impl Public { /// A new instance from the given 32-byte `data`. pub fn from_raw(data: [u8; 32]) -> Self { @@ -105,28 +110,32 @@ impl Into<[u8; 32]> for Public { } impl Pair { - /// Generate new secure (random) key pair. - pub fn new() -> Pair { + /// Generate new secure (random) key pair, yielding it and the corresponding pkcs#8 bytes. + pub fn generate_with_pkcs8() -> (Self, [u8; 85]) { let rng = rand::SystemRandom::new(); - let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng).unwrap(); - Pair(signature::Ed25519KeyPair::from_pkcs8(untrusted::Input::from(&pkcs8_bytes)).unwrap()) + let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng).expect("system randomness is available; qed"); + let pair = Self::from_pkcs8(&pkcs8_bytes).expect("just-generated pkcs#8 data is valid; qed"); + + (pair, pkcs8_bytes) + } + + /// Generate new secure (random) key pair. + pub fn generate() -> Pair { + let (pair, _) = Self::generate_with_pkcs8(); + pair + } + + /// Generate from pkcs#8 bytes. + pub fn from_pkcs8(pkcs8_bytes: &[u8]) -> Result { + signature::Ed25519KeyPair::from_pkcs8(untrusted::Input::from(&pkcs8_bytes)).map(Pair) } /// Make a new key pair from a seed phrase. + /// NOTE: prefer pkcs#8 unless security doesn't matter -- this is used primarily for tests. pub fn from_seed(seed: &[u8; 32]) -> Pair { Pair(signature::Ed25519KeyPair::from_seed_unchecked(untrusted::Input::from(&seed[..])).unwrap()) } - /// Make a new key pair from the raw secret and public key (it will check to make sure - /// they correspond to each other). - pub fn from_both(secret_public: &[u8; 64]) -> Option { - let mut pkcs8_bytes: Vec<_> = hex!("3053020101300506032b657004220420").to_vec(); - pkcs8_bytes.extend_from_slice(&secret_public[0..32]); - pkcs8_bytes.extend_from_slice(&[0xa1u8, 0x23, 0x03, 0x21, 0x00]); - pkcs8_bytes.extend_from_slice(&secret_public[32..64]); - signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(untrusted::Input::from(&pkcs8_bytes)).ok().map(Pair) - } - /// Sign a message. pub fn sign(&self, message: &[u8]) -> Signature { let mut r = [0u8; 64]; @@ -195,7 +204,7 @@ mod test { #[test] fn generated_pair_should_work() { - let pair = Pair::new(); + let pair = Pair::generate(); let public = pair.public(); let message = b"Something important"; let signature = pair.sign(&message[..]); @@ -214,4 +223,12 @@ mod test { println!("Correct signature: {}", HexDisplay::from(&signature.0)); assert!(verify_strong(&signature, &message[..], &public)); } + + #[test] + fn generate_with_pkcs8_recovery_possible() { + let (pair1, pkcs8) = Pair::generate_with_pkcs8(); + let pair2 = Pair::from_pkcs8(&pkcs8).unwrap(); + + assert_eq!(pair1.public(), pair2.public()); + } } From 6006b8282417baaa1f205fae5b3cae1af8ceabe1 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 14 Mar 2018 13:19:24 +0100 Subject: [PATCH 2/6] probably broken child derivation --- Cargo.lock | 8 ++++++++ Cargo.toml | 1 + polkadot/keystore/Cargo.toml | 8 ++++++++ polkadot/keystore/src/lib.rs | 35 +++++++++++++++++++++++++++++++++++ substrate/ed25519/Cargo.toml | 1 + substrate/ed25519/src/lib.rs | 31 ++++++++++++++++++++++++------- 6 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 polkadot/keystore/Cargo.toml create mode 100644 polkadot/keystore/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 1b364299fbb2c..0c9b6ffbc1058 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1075,6 +1075,14 @@ dependencies = [ "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "polkadot-keystore" +version = "0.1.0" +dependencies = [ + "ed25519 0.1.0", + "ethkey 0.3.0 (git+https://github.com/paritytech/parity.git)", +] + [[package]] name = "polkadot-primitives" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 03d67a0e3f094..70c34c66442ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "polkadot/collator", "polkadot/consensus", "polkadot/executor", + "polkadot/keystore", "polkadot/primitives", "polkadot/runtime", "polkadot/statement-table", diff --git a/polkadot/keystore/Cargo.toml b/polkadot/keystore/Cargo.toml new file mode 100644 index 0000000000000..510b7e0b07217 --- /dev/null +++ b/polkadot/keystore/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "polkadot-keystore" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +ethkey = { git = "https://github.com/paritytech/parity", default_features = false } +ed25519 = { path = "../../substrate/ed25519" } diff --git a/polkadot/keystore/src/lib.rs b/polkadot/keystore/src/lib.rs new file mode 100644 index 0000000000000..8c460881dbb6a --- /dev/null +++ b/polkadot/keystore/src/lib.rs @@ -0,0 +1,35 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Keystore (and session key management) for polkadot. + +extern crate ethcrypto as crypto; +extern crate ed25519; + +use ed25519::Pair; + +/// Key store. +pub struct Store { + +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/substrate/ed25519/Cargo.toml b/substrate/ed25519/Cargo.toml index 66a65f66b7da6..1241bd69bf15d 100644 --- a/substrate/ed25519/Cargo.toml +++ b/substrate/ed25519/Cargo.toml @@ -7,3 +7,4 @@ authors = ["Parity Technologies "] ring = "0.12" untrusted = "0.5" substrate-primitives = { version = "0.1", path = "../primitives" } +hex-literal = "0.1" diff --git a/substrate/ed25519/src/lib.rs b/substrate/ed25519/src/lib.rs index 797d71e455b3d..a72e4ac92c273 100644 --- a/substrate/ed25519/src/lib.rs +++ b/substrate/ed25519/src/lib.rs @@ -23,6 +23,10 @@ extern crate untrusted; use ring::{rand, signature}; use primitives::hash::H512; +#[cfg(test)] +#[macro_use] +extern crate hex_literal; + /// Alias to 512-bit hash when used in the context of a signature on the relay chain. pub type Signature = H512; @@ -54,12 +58,6 @@ pub struct Public(pub [u8; 32]); /// A key pair. pub struct Pair(signature::Ed25519KeyPair); -impl From for Pair { - fn from(key: signature::Ed25519KeyPair) -> Self { - Pair(key) - } -} - impl Public { /// A new instance from the given 32-byte `data`. pub fn from_raw(data: [u8; 32]) -> Self { @@ -133,7 +131,10 @@ impl Pair { /// Make a new key pair from a seed phrase. /// NOTE: prefer pkcs#8 unless security doesn't matter -- this is used primarily for tests. pub fn from_seed(seed: &[u8; 32]) -> Pair { - Pair(signature::Ed25519KeyPair::from_seed_unchecked(untrusted::Input::from(&seed[..])).unwrap()) + let key = signature::Ed25519KeyPair::from_seed_unchecked(untrusted::Input::from(&seed[..])) + .expect("seed has valid length; qed"); + + Pair(key) } /// Sign a message. @@ -150,6 +151,16 @@ impl Pair { r.copy_from_slice(pk); Public(r) } + + /// Derive a child key. Probably unsafe and broken. + // TODO: proper HD derivation https://cardanolaunch.com/assets/Ed25519_BIP.pdf + pub fn derive_child_probably_bad(&self, chain_data: &[u8]) -> Pair { + let sig = self.sign(chain_data); + let mut seed = [0u8; 32]; + seed.copy_from_slice(&sig.0[..32]); + + Pair::from_seed(&seed) + } } /// Verify a signature on a message. @@ -231,4 +242,10 @@ mod test { assert_eq!(pair1.public(), pair2.public()); } + + #[test] + fn derive_child() { + let pair = Pair::generate(); + let _pair2 = pair.derive_child_probably_bad(b"session_1234"); + } } From f52c0df7da2f36f8f19acddbd07ccf6f9dfe51f1 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 14 Mar 2018 16:18:31 +0100 Subject: [PATCH 3/6] basic keystore --- Cargo.lock | 35 +++++- polkadot/keystore/Cargo.toml | 11 +- polkadot/keystore/src/lib.rs | 213 ++++++++++++++++++++++++++++++++++- substrate/ed25519/src/lib.rs | 5 +- 4 files changed, 257 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c9b6ffbc1058..85b7c74e47211 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -531,6 +531,11 @@ dependencies = [ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "hex-literal" version = "0.1.0" @@ -1080,7 +1085,14 @@ name = "polkadot-keystore" version = "0.1.0" dependencies = [ "ed25519 0.1.0", - "ethkey 0.3.0 (git+https://github.com/paritytech/parity.git)", + "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcrypto 0.1.0 (git+https://github.com/paritytech/parity.git)", + "hex 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1263,6 +1275,15 @@ dependencies = [ "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "remove_dir_all" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ring" version = "0.12.1" @@ -1718,6 +1739,15 @@ name = "take" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "tempdir" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "termion" version = "1.5.1" @@ -2078,6 +2108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum globset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "464627f948c3190ae3d04b1bc6d7dca2f785bda0ac01278e6db129ad383dbeb6" "checksum hashdb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d97be07c358c5b461268b4ce60304024c5fa5acfd4bd8cd743639f0252003cf5" "checksum heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461" +"checksum hex 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "459d3cf58137bb02ad4adeef5036377ff59f066dbb82517b7192e3a5462a2abc" "checksum hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd546ef520ab3745f1aae5f2cdc6de9e6498e94d1ab138b9eb3ddfbf335847fb" "checksum hex-literal-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2ea76da4c7f1a54d01d54985566d3fdd960b2bbd7b970da024821c883c2d9631" "checksum httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2f407128745b78abc95c0ffbe4e5d37427fdc0d45470710cfef8c44522a2e37" @@ -2140,6 +2171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa" "checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" "checksum relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a" +"checksum remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d2f806b0fcdabd98acd380dc8daef485e22bcb7cddc811d1337967f2528cf5" "checksum ring 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6f7d28b30a72c01b458428e0ae988d4149c20d902346902be881e3edc4bb325c" "checksum rlp 0.2.1 (git+https://github.com/paritytech/parity.git)" = "" "checksum rlp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "babe6fce20c0ca9b1582998734c4569082d0ad08e43772a1c6c40aef4f106ef9" @@ -2172,6 +2204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" +"checksum tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f73eebdb68c14bcb24aef74ea96079830e7fa7b31a6106e42ea7ee887c1e134e" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" diff --git a/polkadot/keystore/Cargo.toml b/polkadot/keystore/Cargo.toml index 510b7e0b07217..44edeafcb948c 100644 --- a/polkadot/keystore/Cargo.toml +++ b/polkadot/keystore/Cargo.toml @@ -4,5 +4,14 @@ version = "0.1.0" authors = ["Parity Technologies "] [dependencies] -ethkey = { git = "https://github.com/paritytech/parity", default_features = false } +ethcrypto = { git = "https://github.com/paritytech/parity", default_features = false } ed25519 = { path = "../../substrate/ed25519" } +error-chain = "0.11" +hex = "0.3" +rand = "0.4" +serde_json = "1.0" +serde = "1.0" +serde_derive = "1.0" + +[dev-dependencies] +tempdir = "0.3" diff --git a/polkadot/keystore/src/lib.rs b/polkadot/keystore/src/lib.rs index 8c460881dbb6a..3b69b4053daa4 100644 --- a/polkadot/keystore/src/lib.rs +++ b/polkadot/keystore/src/lib.rs @@ -18,18 +18,223 @@ extern crate ethcrypto as crypto; extern crate ed25519; +extern crate rand; +extern crate serde_json; +extern crate serde; +extern crate hex; -use ed25519::Pair; +#[macro_use] +extern crate serde_derive; + +#[macro_use] +extern crate error_chain; + +#[cfg(test)] +extern crate tempdir; + +use std::path::PathBuf; +use std::fs::{self, File}; +use std::io::{self, Write}; + +use crypto::Keccak256; +use ed25519::{Pair, Public, PKCS_LEN}; + +pub use crypto::KEY_ITERATIONS; + +error_chain! { + foreign_links { + Io(io::Error); + Json(serde_json::Error); + } + + errors { + InvalidPassword { + description("Invalid password"), + display("Invalid password"), + } + InvalidPKCS8 { + description("Invalid PKCS#8 data"), + display("Invalid PKCS#8 data"), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InvalidPassword; + +#[derive(Serialize, Deserialize)] +struct EncryptedKey { + mac: [u8; 32], + salt: [u8; 32], + ciphertext: Vec, // TODO: switch to fixed-size when serde supports + iv: [u8; 16], + iterations: u32, +} + +impl EncryptedKey { + fn encrypt(plain: &[u8; PKCS_LEN], password: &str, iterations: u32) -> Self { + use rand::{Rng, OsRng}; + + let mut rng = OsRng::new().expect("OS Randomness available on all supported platforms; qed"); + + let salt: [u8; 32] = rng.gen(); + let iv: [u8; 16] = rng.gen(); + + // two parts of derived key + // DK = [ DK[0..15] DK[16..31] ] = [derived_left_bits, derived_right_bits] + let (derived_left_bits, derived_right_bits) = crypto::derive_key_iterations(password, &salt, iterations); + + // preallocated (on-stack in case of `Secret`) buffer to hold cipher + // length = length(plain) as we are using CTR-approach + let mut ciphertext = vec![0; PKCS_LEN]; + + // aes-128-ctr with initial vector of iv + crypto::aes::encrypt(&derived_left_bits, &iv, plain, &mut *ciphertext); + + // KECCAK(DK[16..31] ++ ), where DK[16..31] - derived_right_bits + let mac = crypto::derive_mac(&derived_right_bits, &*ciphertext).keccak256(); + + EncryptedKey { + salt, + iv, + mac, + iterations, + ciphertext, + } + } + + fn decrypt(&self, password: &str) -> Result<[u8; PKCS_LEN]> { + let (derived_left_bits, derived_right_bits) = + crypto::derive_key_iterations(password, &self.salt, self.iterations); + + let mac = crypto::derive_mac(&derived_right_bits, &self.ciphertext).keccak256(); + + if mac != self.mac { + return Err(ErrorKind::InvalidPassword.into()); + } + + let mut plain = [0; PKCS_LEN]; + crypto::aes::decrypt(&derived_left_bits, &self.iv, &self.ciphertext, &mut plain[..]); + Ok(plain) + } +} /// Key store. pub struct Store { + path: PathBuf, +} + +impl Store { + /// Create a new store at the given path. + pub fn open(path: PathBuf) -> Result { + fs::create_dir_all(&path)?; + Ok(Store { path }) + } + /// Generate a new key, placing it into the store. + pub fn generate(&self, password: &str) -> Result { + let (pair, pkcs_bytes) = Pair::generate_with_pkcs8(); + let key_file = EncryptedKey::encrypt(&pkcs_bytes, password, KEY_ITERATIONS as u32); + + let mut file = File::create(self.key_file_path(&pair.public()))?; + ::serde_json::to_writer(&file, &key_file)?; + + file.flush()?; + + Ok(pair) + } + + /// Load a key file with given public key. + pub fn load(&self, public: &Public, password: &str) -> Result { + let path = self.key_file_path(public); + let file = File::open(path)?; + + let encrypted_key: EncryptedKey = ::serde_json::from_reader(&file)?; + let pkcs_bytes = encrypted_key.decrypt(password)?; + + Pair::from_pkcs8(&pkcs_bytes[..]).map_err(|_| ErrorKind::InvalidPKCS8.into()) + } + + /// Get public keys of all stored keys. + pub fn contents(&self) -> Result> { + let mut public_keys = Vec::new(); + for entry in fs::read_dir(&self.path)? { + let entry = entry?; + let path = entry.path(); + + // skip directories and non-unicode file names (hex is unicode) + if let Some(name) = path.file_name().and_then(|n| n.to_str()) { + if name.len() != 64 { continue } + + match hex::decode(name) { + Ok(ref hex) if hex.len() == 32 => { + let mut buf = [0; 32]; + buf.copy_from_slice(&hex[..]); + + public_keys.push(Public(buf)); + } + _ => continue, + } + } + } + + Ok(public_keys) + } + + fn key_file_path(&self, public: &Public) -> PathBuf { + let mut buf = self.path.clone(); + buf.push(hex::encode(public.as_slice())); + buf + } } #[cfg(test)] mod tests { + use super::*; + use tempdir::TempDir; + #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } + fn encrypt_and_decrypt() { + let plain = [1; PKCS_LEN]; + let encrypted_key = EncryptedKey::encrypt(&plain, "thepassword", KEY_ITERATIONS as u32); + + let decrypted_key = encrypted_key.decrypt("thepassword").unwrap(); + + assert_eq!(&plain[..], &decrypted_key[..]); + } + + #[test] + fn decrypt_wrong_password_fails() { + let plain = [1; PKCS_LEN]; + let encrypted_key = EncryptedKey::encrypt(&plain, "thepassword", KEY_ITERATIONS as u32); + + assert!(encrypted_key.decrypt("thepassword2").is_err()); + } + + #[test] + fn decrypt_wrong_iterations_fails() { + let plain = [1; PKCS_LEN]; + let mut encrypted_key = EncryptedKey::encrypt(&plain, "thepassword", KEY_ITERATIONS as u32); + + encrypted_key.iterations -= 64; + + assert!(encrypted_key.decrypt("thepassword").is_err()); + } + + #[test] + fn basic_store() { + let temp_dir = TempDir::new("keystore").unwrap(); + let store = Store::open(temp_dir.path().to_owned()).unwrap(); + + assert!(store.contents().unwrap().is_empty()); + + let key = store.generate("thepassword").unwrap(); + let key2 = store.load(&key.public(), "thepassword").unwrap(); + + assert!(store.load(&key.public(), "notthepassword").is_err()); + + assert_eq!(key.public(), key2.public()); + + assert_eq!(store.contents().unwrap()[0], key.public()); + } } diff --git a/substrate/ed25519/src/lib.rs b/substrate/ed25519/src/lib.rs index a72e4ac92c273..cbbbd08bff384 100644 --- a/substrate/ed25519/src/lib.rs +++ b/substrate/ed25519/src/lib.rs @@ -30,6 +30,9 @@ extern crate hex_literal; /// Alias to 512-bit hash when used in the context of a signature on the relay chain. pub type Signature = H512; +/// Length of the PKCS#8 encoding of the key. +pub const PKCS_LEN: usize = 85; + /// A localized signature also contains sender information. #[derive(PartialEq, Eq, Clone, Debug)] pub struct LocalizedSignature { @@ -109,7 +112,7 @@ impl Into<[u8; 32]> for Public { impl Pair { /// Generate new secure (random) key pair, yielding it and the corresponding pkcs#8 bytes. - pub fn generate_with_pkcs8() -> (Self, [u8; 85]) { + pub fn generate_with_pkcs8() -> (Self, [u8; PKCS_LEN]) { let rng = rand::SystemRandom::new(); let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng).expect("system randomness is available; qed"); let pair = Self::from_pkcs8(&pkcs8_bytes).expect("just-generated pkcs#8 data is valid; qed"); From 852bead147dd1415cfa48528ce34c26fd049de8b Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 14 Mar 2018 20:21:35 +0100 Subject: [PATCH 4/6] keystore integration in CLI --- Cargo.lock | 40 +++++++++++++++++++++++++++++++++++++++ polkadot/cli/Cargo.toml | 2 ++ polkadot/cli/src/cli.yml | 6 +++++- polkadot/cli/src/error.rs | 7 +++++++ polkadot/cli/src/lib.rs | 27 ++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 85b7c74e47211..047335f72d326 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,6 +16,17 @@ name = "ansi_term" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "app_dirs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "shell32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "arrayvec" version = "0.3.25" @@ -895,6 +906,15 @@ name = "odds" version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ole32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "owning_ref" version = "0.3.3" @@ -1014,6 +1034,7 @@ dependencies = [ name = "polkadot-cli" version = "0.1.0" dependencies = [ + "app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.29.4 (registry+https://github.com/rust-lang/crates.io-index)", "ed25519 0.1.0", "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1021,6 +1042,7 @@ dependencies = [ "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "polkadot-executor 0.1.0", + "polkadot-keystore 0.1.0", "polkadot-primitives 0.1.0", "polkadot-runtime 0.1.0", "substrate-client 0.1.0", @@ -1423,6 +1445,15 @@ dependencies = [ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "shell32-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "slab" version = "0.2.0" @@ -2029,6 +2060,11 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "xdg" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "xml-rs" version = "0.3.6" @@ -2054,6 +2090,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" "checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455" "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" +"checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4" "checksum arrayvec 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)" = "06f59fe10306bb78facd90d28c2038ad23ffaaefa85bac43c8a434cde383334f" "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" "checksum assert_matches 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9e772942dccdf11b368c31e044e4fca9189f80a773d2f0808379de65894cbf57" @@ -2148,6 +2185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "9936036cc70fe4a8b2d338ab665900323290efb03983c86cbe235ae800ad8017" "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" "checksum odds 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22" +"checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c" "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" "checksum parity-wasm 0.15.4 (registry+https://github.com/rust-lang/crates.io-index)" = "235801e9531998c4bb307f4ea6833c9f40a4cf132895219ac8c2cd25a9b310f7" "checksum parity-wordlist 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d0dec124478845b142f68b446cbee953d14d4b41f1bc0425024417720dce693" @@ -2190,6 +2228,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0" "checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5" "checksum serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9db7266c7d63a4c4b7fe8719656ccdd51acf1bed6124b174f933b009fb10bcb" +"checksum shell32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ee04b46101f57121c9da2b151988283b6beb79b34f5bb29a58ee48cb695122c" "checksum slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6dbdd334bd28d328dad1c41b0ea662517883d8880d8533895ef96c8003dec9c4" "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" "checksum slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d" @@ -2241,6 +2280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +"checksum xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a66b7c2281ebde13cf4391d70d4c7e5946c3c25e72a7b859ca8f677dcd0b0c61" "checksum xml-rs 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7ec6c39eaa68382c8e31e35239402c0a9489d4141a8ceb0c716099a0b515b562" "checksum xmltree 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "472a9d37c7c53ab2391161df5b89b1f3bf76dab6ab150d7941ecbdd832282082" "checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml index 4bb48abf2b886..37d920c3c00be 100644 --- a/polkadot/cli/Cargo.toml +++ b/polkadot/cli/Cargo.toml @@ -12,6 +12,7 @@ log = "0.3" hex-literal = "0.1" triehash = "0.1" ed25519 = { path = "../../substrate/ed25519" } +app_dirs = "1.1" substrate-client = { path = "../../substrate/client" } substrate-codec = { path = "../../substrate/codec" } substrate-runtime-io = { path = "../../substrate/runtime-io" } @@ -22,3 +23,4 @@ substrate-rpc-servers = { path = "../../substrate/rpc-servers" } polkadot-primitives = { path = "../primitives" } polkadot-executor = { path = "../executor" } polkadot-runtime = { path = "../runtime" } +polkadot-keystore = { path = "../keystore" } diff --git a/polkadot/cli/src/cli.yml b/polkadot/cli/src/cli.yml index a23ba5c2356da..f67b8f4228b97 100644 --- a/polkadot/cli/src/cli.yml +++ b/polkadot/cli/src/cli.yml @@ -5,7 +5,11 @@ args: - log: short: l value_name: LOG_PATTERN - help: Sets a custom logging + help: Sets a custom logging filter + takes_value: true + - keystore-path: + value_name: KEYSTORE_PATH + help: specify custom keystore path takes_value: true subcommands: - collator: diff --git a/polkadot/cli/src/error.rs b/polkadot/cli/src/error.rs index 6b1e2b602469f..6c9e22cd55ad0 100644 --- a/polkadot/cli/src/error.rs +++ b/polkadot/cli/src/error.rs @@ -26,4 +26,11 @@ error_chain! { links { Client(client::error::Error, client::error::ErrorKind) #[doc="Client error"]; } + errors { + /// Key store errors + Keystore(e: ::keystore::Error) { + description("Keystore error"), + display("Keystore error: {:?}", e), + } + } } diff --git a/polkadot/cli/src/lib.rs b/polkadot/cli/src/lib.rs index 807ff6c2cb8aa..c5e34af10a4ba 100644 --- a/polkadot/cli/src/lib.rs +++ b/polkadot/cli/src/lib.rs @@ -18,6 +18,7 @@ #![warn(missing_docs)] +extern crate app_dirs; extern crate env_logger; extern crate ed25519; extern crate triehash; @@ -29,6 +30,7 @@ extern crate substrate_rpc_servers as rpc; extern crate polkadot_primitives; extern crate polkadot_executor; extern crate polkadot_runtime; +extern crate polkadot_keystore as keystore; #[macro_use] extern crate hex_literal; @@ -41,9 +43,12 @@ extern crate log; pub mod error; +use std::path::{Path, PathBuf}; + use codec::Slicable; use polkadot_runtime::genesismap::{additional_storage_with_genesis, GenesisConfig}; use client::genesis; +use keystore::Store as Keystore; /// Parse command line arguments and start the node. /// @@ -79,12 +84,19 @@ pub fn run(args: I) -> error::Result<()> where bonding_duration: 90, // 90 days per bond. approval_ratio: 667, // 66.7% approvals required for legislation. }; + let prepare_genesis = || { storage = genesis_config.genesis_map(); let block = genesis::construct_genesis_block(&storage); storage.extend(additional_storage_with_genesis(&block)); (primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect()) }; + + let keystore_path = matches.value_of("keystore") + .map(|x| Path::new(x).to_owned()) + .unwrap_or_else(default_keystore_path); + + let _keystore = Keystore::open(keystore_path).map_err(::error::ErrorKind::Keystore)?; let client = client::new_in_mem(executor, prepare_genesis)?; let address = "127.0.0.1:9933".parse().unwrap(); @@ -109,6 +121,21 @@ pub fn run(args: I) -> error::Result<()> where Ok(()) } +fn default_keystore_path() -> PathBuf { + use app_dirs::{AppInfo, AppDataType}; + + let app_info = AppInfo { + name: "Polkadot", + author: "Parity Technologies", + }; + + app_dirs::get_app_dir( + AppDataType::UserData, + &app_info, + "keystore", + ).expect("app directories exist on all supported platforms; qed") +} + fn init_logger(pattern: &str) { let mut builder = env_logger::LogBuilder::new(); // Disable info logging by default for some modules: From 81e9b23e9a29021a6d083b587be3c593269589f7 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 15 Mar 2018 12:31:24 +0100 Subject: [PATCH 5/6] constant-time mac comparison --- Cargo.lock | 7 +++++++ polkadot/keystore/Cargo.toml | 1 + polkadot/keystore/src/lib.rs | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 047335f72d326..4ee1b8806815e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1114,6 +1114,7 @@ dependencies = [ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1747,6 +1748,11 @@ dependencies = [ "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "subtle" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "syn" version = "0.11.11" @@ -2240,6 +2246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum subtle 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b811576c12506ff3f6da145585dc833edc32ee34c9fc021127d90e8134cc05c" +"checksum subtle 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc7f6353c2ee5407358d063a14cccc1630804527090a6fb5a9489ce4924280fb" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" diff --git a/polkadot/keystore/Cargo.toml b/polkadot/keystore/Cargo.toml index 44edeafcb948c..16ec9817693e2 100644 --- a/polkadot/keystore/Cargo.toml +++ b/polkadot/keystore/Cargo.toml @@ -12,6 +12,7 @@ rand = "0.4" serde_json = "1.0" serde = "1.0" serde_derive = "1.0" +subtle = "0.5" [dev-dependencies] tempdir = "0.3" diff --git a/polkadot/keystore/src/lib.rs b/polkadot/keystore/src/lib.rs index 3b69b4053daa4..fac2a48036ac6 100644 --- a/polkadot/keystore/src/lib.rs +++ b/polkadot/keystore/src/lib.rs @@ -17,6 +17,7 @@ //! Keystore (and session key management) for polkadot. extern crate ethcrypto as crypto; +extern crate subtle; extern crate ed25519; extern crate rand; extern crate serde_json; @@ -109,7 +110,7 @@ impl EncryptedKey { let mac = crypto::derive_mac(&derived_right_bits, &self.ciphertext).keccak256(); - if mac != self.mac { + if subtle::slices_equal(&mac[..], &self.mac[..]) != 1 { return Err(ErrorKind::InvalidPassword.into()); } From 763fdc38f019a0c491d18bea049c3503a95a68ff Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 16 Mar 2018 16:44:16 +0100 Subject: [PATCH 6/6] fix spaces --- polkadot/keystore/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/keystore/src/lib.rs b/polkadot/keystore/src/lib.rs index fac2a48036ac6..ece74d0021a6e 100644 --- a/polkadot/keystore/src/lib.rs +++ b/polkadot/keystore/src/lib.rs @@ -194,7 +194,7 @@ mod tests { use super::*; use tempdir::TempDir; - #[test] + #[test] fn encrypt_and_decrypt() { let plain = [1; PKCS_LEN]; let encrypted_key = EncryptedKey::encrypt(&plain, "thepassword", KEY_ITERATIONS as u32);